Sunday, February 24, 2019

AWS Lambda & Perl - Using Docker to Build Lambda Layers

As the Perl Lambda adventure continues, I'm learning more about the Perl toolchain, Lambdas and Docker.  After running into an issue with OpenSSL versioning on the supposed AMI that AWS documents as being the official Lambda execution environment it appears that it is not.

What does appear to be true is that the image is similar to the runtime environment. For a truer representation of the Lambda runtime there is a Docker image lambci/lambda that has proven to be a closer representation of the Lambda runtime environment.  In a previous post I noted that the version of OpenSSL on the AMI is 1.0.2 while the Lambda runtime environment version is 1.0.1.  The Docker image has OpenSSL version 1.0.1 installed, so empirically the Docker image does appear to be similar to the Lambda execution environment.  Why is this important?
Well, if you are building Lambdas that require objects that link against shared libraries, you'ĺl want to make sure you are linking against libraries that are the same version OR if the shared libraries are not present in the runtime, you'll need to provide them yourself.  You can provide your own libraries in /opt/lib since that is where LD_LIBRARY_PATH is pointing to.

Yesterday was a day to re-think the strategy of trying to build Lambdas packages and Lambda Layers on an EC2 image.  Given the discovery that the Docker image is closer to the runtime environment and the AMI that is documented has proven to be somewhat problematic, it makes more sense to use the Docker instead to build a custom runtime environment and to build Lambdas...although this approach can be problematic at times as well and introduces a new dependency on the Perl Lambda toolchain, namely Docker.

It appears I am not the first person to come to this conclusion and I've found that there is a very similar effort to produce a custom runtime environment for Perl going on here.

Inspired by that project and that fact that someone else is thinking along these same lines I continued down the Docker path and by the end of Saturday I had re-tooled my Custom Perl Runtime Layer creation toolchain to use Docker.  Hereś what I ended up with.

FROM perl-5.28.1:latest
RUN curl -s -L https://cpanmin.us | /opt/perl-5.28.1/bin/perl - App::cpanminus  COPY Amazon-Lambda-Runtime-0.0.1.tar.gz /tmp
RUN cd /tmp; \
    /opt/perl-5.28.1/bin/cpanm -n --install Amazon-Lambda-Runtime-0.0.1.tar.gz
RUN tar xfvz /tmp/Amazon-Lambda-Runtime-0.0.1.tar.gz \
    -O Amazon-Lambda-Runtime-0.0.1/bootstrap > /opt/bootstrap; \
    chmod +x /opt/bootstrap
RUN cd /opt; \
    zip -r -9 /tmp/perl-5.28.1-runtime.zip * 

Again, taking a cue from the aforementioned project (https://github.com/pplu/perl-lambda-byor) I am re-thinking the method I used to create the custom runtime and package dependencies.  First, I think the idea of bundling the version of Perl to be used and the custom runtime protocol handlers into one layer is the best approach.  I had initially believed it might be useful to use the default Perl version in the runtime environment but found that is probably not very useful and you'll end up installing so many dependencies that are missing you might as well build a new Perl version...so a Custom Perl Runtime Layer by definition should now include a Perl interpreter.

My second conclusion is that building the Perl interpreter and bunding the runtime should be done using Docker...so the above Dockerfile is the way to go.  Logistically, I built the Perl interpreter first and tagged that image since it may be useful as a starting point AND the Docker recipe above can be configured to use other versions of Perl.

Lastly, by using  cpanm to build the actual Lambda (and its dependencies) in a Docker container,  installing it to a separate directory from the Perl interpreter and zipping only those files will help keep the deployment package reasonably sized so that you might even be able to use the AWS Lambda console.  If the package becomes too large, then it may make sense to move the dependencies to a Lambda Layer.  That might manifest itself in a feature of the toolchain to create Lambda Layers essentially from a cpanfile that contains the dependencies.  In practice, depending on the dependencies this can be trivial or extremely maddening when shared libraries are involved.

Packaging a Lambda would proceed by first creating a CPAN distribution that contains your Lambda, then using the custom runtime Docker image as the base, layering your Lambda and its dependencies on top like so:

FROM perl-5.28.1-runtime:latest    
COPY MyLambda-0.1.tar.gz /tmp    
RUN export PATH=/opt/perl-5.28.1/bin:$PATH; cd /tmp; \
    cpanm -n --verbose --install -l local MyLambda-0.1.tar.gz         
RUN cd /opt; zip -r -9 MyLambda.zip local/*

..and then we can proceed to copy the Lambda from the container and publish that as a Lambda.

docker build -t  my-lambda .
docker --name my-lambda run my-lambda
docker cp my-lambda:/tmp/MyLambda.zip
docker rm my-lambda
docker rmi my-lambda

While this was a pretty good breaking point for the day, I continued to investigate some of the issues that this technique might present and re-visited a use case I have - developing a Lambda to interpret QR codes.  Previously, using the first incarnation of my toolchain along with some sweat and blood, I was able to get a Lambda working that is triggered by dropping a png file into an S3 bucket.  The challenges here are mostly centered around installing the necessary libraries to build the two required Perl modules:
  • Image::Magick
  • Barcode::ZBar
While I was able to accomplish this on an EC2, accomplishing this in a Docker container presented some challenges.  In my next blog post, I'll begin to discuss the process and some of my findings.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.