At re:Invent 2018, AWS announced Lambda Custom Runtimes where you can implement an AWS Lambda runtime in any programming language. In this blog we are going to create our own bootstrap in Go.
Bootstrap
Bootstrap is a file in the deployment archive, that is responsible for reading the ‘AWS_LAMBDA_RUNTIME_API’ environment variable, and interacting with the Lambda runtime API by getting the next event and sending a response. The Lambda runtime API is available as a webservice which makes it very easy to create a bootstrap application in Go. Below we see an example of a bootstrap
application that responds with an API Gateway Proxy response.
The bootstrap first gets the value from the ‘AWS_LAMBDA_RUNTIME_API’ environment variable which contains the endpoint to use for this function. Using the endpoint, it gets the next available event by calling the Lambda runtime API.
The response contains the event as a body value and the the ‘Lambda-Runtime-Aws-Request-Id’ as a header value. The value of ‘Lambda-Runtime-Aws-Request-Id’ must be used to post a response back to the Lambda runtime API.
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"time"
)
func main() {
awsLambdaRuntimeApi := os.Getenv("AWS_LAMBDA_RUNTIME_API")
if awsLambdaRuntimeApi == "" {
panic("Missing: 'AWS_LAMBDA_RUNTIME_API'")
}
for {
// get the next event
requestUrl := fmt.Sprintf("http://%s/2018-06-01/runtime/invocation/next", awsLambdaRuntimeApi)
resp, err := http.Get(requestUrl)
if err != nil {
log.Fatal(fmt.Errorf("Expected status code 200, got %d", resp.StatusCode))
}
requestId := resp.Header.Get("Lambda-Runtime-Aws-Request-Id")
// print the next event
eventData, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(fmt.Errorf("Error: %s"), err)
}
fmt.Println("Received event:", string(eventData))
// Assume API Gateway and respond with Hello World
responseUrl := fmt.Sprintf("http://%s/2018-06-01/runtime/invocation/%s/response", awsLambdaRuntimeApi, requestId)
responsePayload := []byte(<code>{"statusCode": 200, "body": "Hello World!"}
)
req, err := http.NewRequest("POST", responseUrl, bytes.NewBuffer(responsePayload))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
client.Timeout = time.Second * 1
postResp, err := client.Do(req)
if err != nil {
log.Fatal(fmt.Errorf("Error %s", err))
}
body, _ := ioutil.ReadAll(postResp.Body)
fmt.Println("Received response:", string(body))
}
}
Example
The example shows how to create, build, package and deploy a native Go binary as bootstrap to AWS as an API Gateway handler. To deploy the example type ‘make dep[oy’ and to delete type ‘make delete’.
Conclusion
Lambda can execute any Linux static binary which is great! Lambda still supports the managed lambda runtimes, but to get extra performance out of our lambda handlers we can create highly optimized binaries, have the fastest processing and lowest cost.