rubyaws-lambdarubygemssequelpg

How to correctly load a gem extension in AWS Lambda


I'm having trouble working through a gem load error on AWS Lambda.

{
  "errorMessage": "LoadError: libpq.so.5: cannot open shared object file: No such file or directory - /var/task/vendor/bundle/ruby/2.5.0/gems/pg-1.1.4/lib/pg_ext.so",
  "errorType": "Function<Sequel::AdapterNotFound>",
  "stackTrace": [
    "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'",
    "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'",
    "/var/task/vendor/bundle/ruby/2.5.0/gems/pg-1.1.4/lib/pg.rb:4:in `<top (required)>'",
    "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'",
    "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'",
    "/var/task/vendor/bundle/ruby/2.5.0/gems/sequel-5.16.0/lib/sequel/adapters/postgres.rb:6:in `<top (required)>'",
    "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'",
    "/var/lang/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'",
    "/var/task/vendor/bundle/ruby/2.5.0/gems/sequel-5.16.0/lib/sequel/database/connecting.rb:88:in `load_adapter'",
    "/var/task/vendor/bundle/ruby/2.5.0/gems/sequel-5.16.0/lib/sequel/database/connecting.rb:17:in `adapter_class'",
    "/var/task/vendor/bundle/ruby/2.5.0/gems/sequel-5.16.0/lib/sequel/database/connecting.rb:45:in `connect'",
    "/var/task/vendor/bundle/ruby/2.5.0/gems/sequel-5.16.0/lib/sequel/core.rb:121:in `connect'",
    "/var/task/vendor/bundle/ruby/2.5.0/gems/sequel-5.16.0/lib/sequel/core.rb:399:in `adapter_method'",
    "/var/task/vendor/bundle/ruby/2.5.0/gems/sequel-5.16.0/lib/sequel/core.rb:406:in `block (2 levels) in def_adapter_method'",
    "/var/task/lib/warehouse/loader.rb:5:in `connection'",
    "/var/task/lib/warehouse/loader.rb:24:in `initialize'",
    "/var/task/lib/warehouse/update.rb:43:in `new'",
    "/var/task/lib/warehouse/update.rb:43:in `block in handle'",
    "/var/task/lib/warehouse/update.rb:42:in `each'",
    "/var/task/lib/warehouse/update.rb:42:in `handle'",
    "/var/task/lambda.rb:11:in `handler'"
  ]
}

I am using the Sequel library to make a PSQL connection from AWS Lambda, but it seems that the function cannot find the so file. I have packaged the dependencies in vendor/bundle, built in Ubuntu on CodeBuild, and verified that the .so file is present in the resulting artifacts uploaded to lambda. I've also edited the $LOAD_PATH, but that doesn't seem to help.

Anyone else encountered this difficulty? Any further tips on resolving or debugging?


Solution

  • Do you have libpq.so.5 on your lib folder?

    Your error is saying that did not find libpq.so.5 on the $PATH, in AWS Lambda the folder lib is automatically loaded on the path, thus, you just need to have this file there.

    Executables created outside the lambda world do not run on Lambda, furthermore, you need to compile the executables by your own on a Lambda image. This is an example in how to do that:

    Gemfile

    source "https://rubygems.org"
    
    gem "pg"
    gem "mysql2"
    

    handler.rb

    require 'pg'
    require 'mysql2'
    
    def run(event:, context:)
      {
        postgres_client_version: PG.library_version,
        mysql_client_version: Mysql2::VERSION
      }
    end
    
    

    Dockerfile

    FROM lambci/lambda:build-ruby2.5
    
    RUN yum install -y postgresql postgresql-devel mysql mysql-devel
    RUN gem update bundler
    
    ADD Gemfile /var/task/Gemfile
    ADD Gemfile.lock /var/task/Gemfile.lock
    
    RUN bundle install --path /var/task/vendor/bundle --clean
    

    This is going to build your image, then run it to generate the PG and MYSQL executables, then copy it to your lib folder.

    build.sh

    #!/bin/bash -x
    set -e
    
    rm -rf lib && rm -rf vendor && mkdir lib && mkdir vendor
    
    docker build -t pg_mysql_layer -f Dockerfile .
    
    CONTAINER=$(docker run -d pg_mysql_layer false)
    
    docker cp \
        $CONTAINER:/var/task/vendor/ \
        ./
    
    docker cp \
        $CONTAINER:/usr/lib64/libpq.so.5.5 \
        lib/libpq.so.5
    
    docker cp \
        $CONTAINER:/usr/lib64/mysql/. \
        lib/
    
    docker rm $CONTAINER
    
    

    After running ./build.sh it is going to generate the folder lib and vendor with all you need, now you just need to deploy your lambda function.

    To test locally you can run: docker run --rm -it -v $PWD:/var/task -w /var/task lambci/lambda:ruby2.5 handler.run

    It is going to return something similar to this:

    Lambda execution

    REF: https://www.stevenringo.com/ruby-in-aws-lambda-with-postgresql-nokogiri/

    REF: https://www.reddit.com/r/ruby/comments/a3e7a1/postgresql_on_aws_lambda_ruby/