0

I am migrating a Ruby 2.7 Lambda from zip file deployment to container image deployment.

When I deploy my container to AWS Lambda, the container behaves as I hope that it would.

When I attempt to test the same image locally using docker run {my-image-name}, I am encountering 2 issues

  1. The parameters are not accessible from the event object in the same manner
  2. The return headers and status code are not honored in the same way as they are handled from Lambda

My Questions

  • Do I need to bundle something else into my dockerfile to assist with Lambda simulation?
  • Do I need to use a different entrypoint in my dockerfile?
  • I found documentation for the "AWS Lambda Runtime Interface Emulator", but I am unclear if it is designed to assist with the problems I am encountering.

Here is a simplified version of what I am attempting to test.

Dockerfile

FROM public.ecr.aws/lambda/ruby:2.7

COPY * ./

RUN bundle install 

CMD [ "lambda_function.LambdaFunctions::Handler.process" ]

Gemfile

source "https://rubygems.org"

lambda_function.rb



require 'json'

module LambdaFunctions
  class Handler
    def self.process(event:,context:)
      begin
        json = {
          message: 'This is a placeholder for your lambda code',
          event: event
        }.to_json
    
        {
          headers: {
            'Access-Control-Allow-Origin': '*'
          },
          statusCode: 200,
          body: json
        }
      rescue => e
        {
          headers: {
            'Access-Control-Allow-Origin': '*'
          },
          statusCode: 500,
          body: { error: e.message }.to_json
        }
      end
    end
  end
end

Running with AWS Lambda

We have a load balancer making this Lambda available

curl -v https://{my-load-balancer-url}/owners?path=owners

Response - note that the load balancer has converted my GET request to a POST request

{
    "message": "This is a placeholder for your lambda code",
    "event": {
        "requestContext": {
            "elb": {...}
        },
        "httpMethod": "POST",
        "path": "/owners",
        "queryStringParameters": {
            "path": "owners"
        },
        "headers": {
            "accept": "*/*",
            "accept-encoding": "gzip, deflate, br",
            "cache-control": "no-cache",
            "connection": "keep-alive",
            "content-length": "0",
            ...
        },
        "body": "",
        "isBase64Encoded": false
    }
}

Running in docker

docker run --rm --name lsample -p 9000:8080 -d {my-image-name}

POST request to local docker

curl -v http://localhost:9000/2015-03-31/functions/function/invocations -d '{"path": "owners"}'

Note that the headers are returned as part of the respose

Response Headers

< HTTP/1.1 200 OK
< Date: Fri, 18 Dec 2020 19:06:56 GMT
< Content-Length: 166
< Content-Type: text/plain; charset=utf-8

Response Body (formatted)

{
    "headers": {
        "Access-Control-Allow-Origin": "*"
    },
    "statusCode": 200,
    "body": "{\"message\":\"This is a placeholder for your lambda code\",\"event\":{\"path\":\"owners\"}}"
}

GET request to local docker

curl -v http://localhost:9000/2015-03-31/functions/function/invocations?path=owners

No content is returned

< HTTP/1.1 200 OK
< Date: Fri, 18 Dec 2020 19:10:08 GMT
< Content-Length: 0
2
  • This issue looks related: github.com/aws/aws-lambda-runtime-interface-emulator/issues/8 Commented Dec 18, 2020 at 23:46
  • 1
    It seems that you're comparing a Lambda Function invoked via API Gateway/Load Balancer on AWS vs the local Runtime Interface Emulator. The latter, while exposing a local server does not have any of the features of the former ones, all it does is passing the content of the body of the POST request as event parameter of the function. Commented Dec 21, 2020 at 14:08

1 Answer 1

1

Create a Sinatra App to simulate the actions of the Application Load Balancer

Based on the following document, I believe that I need to simulate the actions performed by the AWS Application Load Balancer that fronts my lambda code. See https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html

simulate-lambda-alb/alb_simulate.rb

require 'rubygems'
require 'bundler/setup'

require 'sinatra'
require 'sinatra/base'

require 'json'
require 'httpclient'

# ruby app.rb -o 0.0.0.0
set :port, 8091

set :bind, '0.0.0.0'

get '/web' do
  send_file "../web/index.html"
end
  
get '/web/' do
  send_file "../web/index.html"
end
  
get '/web/:filename' do |filename|
  send_file "../web/#{filename}"
end

get '/lambda*' do
  path = params['splat'][0]
  path=path.gsub(/^lambda\//,'')
  event = {path: path, queryStringParameters: params}.to_json
  cli = HTTPClient.new
  url = "#{ENV['LAMBDA_DOCKER_HOST']}/2015-03-31/functions/function/invocations"
  resp = cli.post(url, event)
  body = JSON.parse(resp.body)
  status body['statusCode']
  headers body['headers']
  body['body']
end

simulate-lambda-alb/Dockerfile

FROM ruby:2.7

RUN gem install bundler

COPY Gemfile Gemfile

RUN bundle install

COPY . .

EXPOSE 8091

CMD ["ruby", "alb_simulate.rb"]

docker-compose.yml

version: '3.7'
networks:
  mynet:
services:
  lambda-container:
    container_name: lambda-container
    # your container image goes here, or you can test with the following
    image: cdluc3/mysql-ruby-lambda
    stdin_open: true
    tty: true
    ports:
    - published: 8090
      target: 8080
    networks:
      mynet:
  alb-simulate:
    container_name: alb-simulate
    # this image contains the code in this answer
    image: cdluc3/simulate-lambda-alb
    networks:
      mynet:
    environment:
      LAMBDA_DOCKER_HOST: http://lambda-container:8080
    ports:
    - published: 8091
      target: 8091
    depends_on:
    - lambda-container

Run Sinatra to simulate Application Load Balancer

docker-compose up

Output

curl "http://localhost:8091/lambda?path=test&foo=bar"
{"message":"This is a placeholder for your lambda code","event":{"path":"","queryStringParameters":{"path":"test","foo":"bar","splat":[""]}}}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.