5

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?

1ijk
  • 1,267
  • 2
  • 14
  • 29
  • A bit more detail: I printed out the contents of LOAD_PATH. I clearly see `./vendor/bundle/ruby/2.5.0/gems/pg-1.1.4/lib` but the error above references `/var/task/vendor/bundle/ruby/2.5.0/gems/pg-1.1.4/lib/pg.rb`. I think that's the same path.... but do I need to be more explicit? – 1ijk Jan 23 '19 at 15:40

2 Answers2

13

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/

Ruan Carlos
  • 435
  • 4
  • 9
  • This is not working for me with sam cli: sam build --debug -t template.yaml --use-container – prcoder Oct 05 '20 at 11:57
  • @prcoder, please provide more information. The question here is about loading gem extensions on AWS lambda, there is no CLI involved. – Ruan Carlos Oct 06 '20 at 12:57
  • Hey Ruan, apologies. The issue was with AWS Lambda Layers with SAM. This solution doesn't work with that. I tried the regular way by putting all the required native dependencies in the code and it worked. Thanks for this solution. – prcoder Oct 07 '20 at 09:07
  • 1
    An example code for ruby 2.7: https://github.com/proton/lambda-ruby-pg – proton Nov 29 '20 at 19:41
2

There are few good plugins to manage dependencies for AWS lambda. serverless-ruby-layer for ruby and serverless-python-requirements for python.

For your ruby case, you can use serverless-ruby-layer simply by adding plugin related config to your serverless.yml.

service: using-docker-yums

plugins:
  - serverless-ruby-layer

custom:
  rubyLayer:
    use_docker: true
    docker_yums:
      - postgresql-devel
    native_libs:
      - /usr/lib64/libpq.so.5

provider:
  name: aws
  runtime: ruby2.5

functions:
  hello:
    handler: handler.hello

And you need to install the plugin with the below command inside your serverless project folder,

sls plugin install -n serverless-ruby-layer

Now running sls deploy will automatically deploy the gems and libs to the layer.

Check out this example here in the docs

Navarasu
  • 5,221
  • 2
  • 17
  • 29