While on a project I encountered the following problem. The application was a Ruby program. It utilized some external gems (modules) and packaging it up was quite a feat even with the documentation available. Especially because we encountered following problems
As a good DevOps I read the documentation before doing stupid things. Yes, I wasn’t planning to waste a full day on experimenting instead of just reading the official guidelines.
Which brought me to the following AWS Documentation page “Updating a function with additional dependencies”
What I didn’t know was that step 1 would already produce some very shitty bug if you copy each line separately.
bundle config set --local path 'vendor/bundle' \
This command is meant to tell the bundle packager that he should not use some global path for the gemfiles but instead do it locally in the root folder. After that you do a bundle install
and you see the vendor/bundle
folder being created with all your dependencies! Good job!
Except that the first command could possible create a wrong .bundle/config
with a whitespace at the end if you just copy it line per line.
Which would generate the following config file for bundle
---
BUNDLE_PATH: "vendor/bundle "
Now, the problem that arises here is that when you do bundle install
a folder will be created with the following path vendor/bundle
. If you don’t see it, there is a space at the end! Which your favorite IDE/ISE will probably just show as… a space. Oh what do I hate spaces in paths as they are invisible for most of the time.
So if you receive errors that your gemfiles/modules/… aren’t found… Check if your bundle config file doesn’t have a space at the end. And shame on you for not just copying all lines and pasting them in your console!
Fix for this: Edit the .bundle/config
file and remove the whitespace at the end of BUNDLE_PATH or execute the first line without the \
at the end.
The error would show like
"errorMessage": "libwhateveri.so.8: cannot open shared object file: No such file or directory - /var/task/vendor/bundle/ruby/2.7.0/gems/libwhateveri-1.15.3/lib/libwhateveri_c.so",
But after checking the zip file you see it does exist. The file is there, the directory is correct. You’ve checked the permissions and user settings with all sort of tricks but in the end the error was just a very confusing error.
The file is actually there. It is just that you compiled it under the wrong *NIX and now it tries to find libraries with other paths. Fix for that? Spin up a Docker Lambda and compile everything in there. For our beloved Ruby that would be the following Dockerfile
FROM lambci/lambda:build-ruby2.7
RUN yum -y install clang make ruby-dev
RUN gem update bundler
CMD "/bin/bash"
Now build the Docker image with the command docker build -t lambda-ruby-worker .
This image will spin up a “development environment” for the moment. It will run until you exit that bash shell. If you want to use this for your CICD, just remove the CMD with /bin/bash
and use other commands to build, package and send your code to AWS.
To enter this development environment you use the following docker command
docker run --rm -it -v ${PWD}:/var/task lambda-ruby-worker
This will mount your current directory inside /var/task
. That way you can still easily code with your favorite IDE/ISE but run/install in a Lambda environment. Do a bundle install
in that environment and zip everything up and send it to your AWS environment. Suddenly your compiled libraries will work just fine!
Hooray!
Ok, we have our development Docker and it is cool, very cool. But I hate sending my code to Lambda to test it, can’t I just locally develop it before sending it to AWS Lambda?
Yes you can! As any Lambda enthusiast knows, you must define a Lambda handler somewhere that AWS can call. But we can call that handler as well! For example, if your handler is in run.rb
and looks like the following
def handler(event:, context:)
event: JSON.generate(event), context: JSON.generate(context.inspect) }
{ end
than you can call it on your dev environment as
ruby -r "./run.rb" -e "handler '{ AWSEvent....}' '{ AWSContext'}" $
And that is it! That is how the Lambda Handler run.handler
looks like.