{"id":1593,"date":"2020-08-10T10:15:50","date_gmt":"2020-08-10T08:15:50","guid":{"rendered":"https:\/\/blog.besharp.it\/?p=1593"},"modified":"2021-03-18T16:20:34","modified_gmt":"2021-03-18T15:20:34","slug":"how-to-run-any-programming-language-on-aws-lambda-custom-runtimes","status":"publish","type":"post","link":"https:\/\/blog.besharp.it\/how-to-run-any-programming-language-on-aws-lambda-custom-runtimes\/","title":{"rendered":"How to run any programming language on AWS Lambda: Custom Runtimes."},"content":{"rendered":"

AWS Lambda Functions as a Service (FaaS) have quickly become the Swiss Army knife of the AWS cloud DevOps since it is possible to use them for a plethora of different tasks. From the backend of a web application to the ingestion of an AWS IoT application, from simple infrastructure automation to the real-time analysis of messages delivered through AWS SQS or AWS Kinesis. Furthermore, they are cheap, versatile, scalable, reasonably priced, and trivial to set up and maintain.\u00a0<\/span><\/p>\n

To make them more attractive for developers and devops AWS offers an increasing number of Lambda runtimes, which allow you to write your code in different versions of several programming languages. At the moment of this writing, AWS Lambda natively supports Java, Go, PowerShell, Node.js, C#, Python, and Ruby.\u00a0<\/span><\/p>\n

However, there are lots of other programming languages out there that you may want to use in your Lambda function to migrate to AWS Lambda existing applications currently deployed on-premises or on EC2 instances. Since rewriting a current code is often infeasible due to lack of time or missing libraries and functionality AWS recently began to offer a new possibility:\u00a0<\/span>you can create a custom Runtime for Lambda and use the programming language you prefer!<\/span><\/strong><\/p>\n

A Lambda function with a custom runtime environment differs from a normal lambda function because it not only contains the code the Lambda will run when the function is invoked but also all the compiled libraries needed to make the code run and – if the language you choose is interpreted like PHP or just in time compiled like Julia – you also need to include the binary interpreter. For most programming languages, a custom runtime prepared by third parties is usually available on github and is often either usable directly or a good base for a custom solution.<\/span><\/p>\n

In the following section, however, we will describe how to create a generic runtime and present two examples created by AWS:\u00a0<\/span>bash and C++<\/span><\/strong>. Finally, we will compare the lambda response time of the custom runtimes with the one of a native runtime (Python 3.8). Creating a custom runtime also allows us to understand how the lambda service works under the hood, which is often very useful for solving problems that may arise with lambda functions (e.g., cold starts).<\/span><\/p>\n

How does Lambda Works?<\/h2>\n

AWS Lambda consists of two main parts: the\u00a0<\/span>Lambda\u00a0<\/span>service<\/span><\/em><\/strong>\u00a0which manages the execution requests, and the\u00a0<\/span>Amazon Linux micro virtual machines<\/span><\/strong>\u00a0provisioned using AWS Firecracker, which actually runs the code. A Firecracker VM is started the first time a given Lambda function receives an execution request (the so-called \u201cCold Start\u201d), and as soon as the VM starts, it begins to poll the Lambda service for messages. When the VM receives a message, it runs your function code handler, passing the received message JSON to the function as the event object.\u00a0<\/span><\/p>\n

Thus every time the Lambda service receives a Lambda execution request, it checks if there is a Firecracker microVM available to manage the execution request. If so, it delivers the message to the VM to be executed. In contrast, if no available Firecracker VM is found, it starts a new VM to manage the message.\u00a0<\/span><\/p>\n

Each VM executes one message at a time, so if a lot of concurrent requests are sent to the Lambda service, for example due to a traffic spike received by an API gateway, several new Firecracker VMs will be started to manage the requests and the average latency of the requests will be higher since each VM takes roughly a second to start.\u00a0<\/span><\/p>\n

In a lambda function using a native runtime, you do not need to worry about how your function will poll for messages from the lambda service and send execution reports back to the lambda service; the native runtime will take care of that for you. However, this is not the case for a custom runtime. When a Lambda function is created with a custom runtime, AWS Lambda Service will start a basic AmazonLinux VM without any installed libraries except for vanilla bash and a few basic unix commands (e.g., ls, curl). Differently from a normal Lambda, in addition to your code and external libraries, you\u2019ll also need to include in the deployment package a script or executable called \u201cbootstrap\u201d that will manage the interaction between the function VM and the Lambda service. AWS Lambda Service exposes a simple HTTP interface for runtimes to receive invocation events from Lambda and send response data back.<\/span><\/p>\n

The bootstrap program needs to perform the following tasks:<\/span><\/p>\n

    \n
  1. Get an event<\/span><\/strong>: Call the invocation API to get the next event. The response body contains the event data. Response headers contain the request ID and other information.<\/span><\/li>\n<\/ol>\n
      \n
    1. Propagate:<\/span><\/strong>\u00a0the tracing header \u2013 Get the X-Ray tracing header from the Lambda-Runtime-Trace-Id header in the API response. Set the _X_AMZN_TRACE_ID environment variable locally with the same value. The X-Ray SDK uses this value to connect trace data between services.<\/span><\/li>\n<\/ol>\n
        \n
      1. Create a context object<\/span><\/strong>: Create an object with context information from environment variables and headers in the API response.<\/span><\/li>\n<\/ol>\n
          \n
        1. Invoke the function handler<\/span><\/strong>: Pass the event and context object to the handler.<\/span><\/li>\n<\/ol>\n
            \n
          1. Handle the response<\/span><\/strong>: Call the invocation response API to post the response from the handler.<\/span><\/li>\n<\/ol>\n
              \n
            1. Handle errors<\/span><\/strong>: If an error occurs, call the invocation error API.<\/span><\/li>\n<\/ol>\n
                \n
              1. Cleanup<\/span><\/strong>: Release unused resources, send data to other services, or perform additional tasks before getting the next event.<\/span><\/li>\n<\/ol>\n

                The bootstrap script\/executable and other language level libraries and interpreters (e.g., PHP interpreter) can be included in a dedicated lambda layer to generate a generic and portable custom runtime used on several Lambda Functions.<\/span><\/p>\n

                How to create a Bash Lambda with custom runtimes<\/h2>\n

                The easiest way to get going with custom runtime is through the AWS Console: from the Lambda Service Dashboard select Create Lambda and in the runtime section select Custom Runtime with Use Default bootstrap and click Create Function<\/span><\/p>\n

                \"create<\/p>\n

                Using these default settings, the Lamba service will create a basic Bash Lambda with a default bootstrap script. Let\u2019s take a look at the previously generated bootstrap script:<\/p>\n

                \"<\/p>\n

                 <\/p>\n

                #!\/bin\/sh\r\nset -euo pipefail\r\n\r\n# Handler format: .\r\n#\r\n# The script file .sh  must be located at the root of your\r\n# function's deployment package, alongside this bootstrap executable.\r\nsource $(dirname \"$0\")\/\"$(echo $_HANDLER | cut -d. -f1).sh\"\r\n\r\nwhile true\r\ndo\r\n    # Request the next event from the Lambda runtime\r\n    HEADERS=\"$(mktemp)\"\r\n    EVENT_DATA=$(curl -v -sS -LD \"$HEADERS\" -X GET \"http:\/\/${AWS_LAMBDA_RUNTIME_API}\/2018-06-01\/runtime\/invocation\/next\")\r\n    INVOCATION_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id \"$HEADERS\" | tr -d '[:space:]' | cut -d: -f2)\r\n\r\n    # Execute the handler function from the script\r\n    RESPONSE=$($(echo \"$_HANDLER\" | cut -d. -f2) \"$EVENT_DATA\")\r\n\r\n    # Send the response to Lambda runtime\r\n    curl -v -sS -X POST \"http:\/\/${AWS_LAMBDA_RUNTIME_API}\/2018-06-01\/runtime\/invocation\/$INVOCATION_ID\/response\" -d \"$RESPONSE\"\r\ndone\r\n<\/pre>\n

                Let\u2019s quickly review what this script does:\u00a0<\/span><\/p>\n