lambci / yumda

Yum for AWS Lambda
MIT License
284 stars 21 forks source link

yumda – yum for Lambda

A Linux distro of software packages that have been recompiled for an AWS Lambda environment, with a yum configuration to install them (requires Docker).


Contents


Quickstart

Usage:

docker run lambci/yumda:<version> yum <yum-args>

For newer Amazon Linux 2 Lambda runtimes use lambci/yumda:2. For older runtimes (python2.7, python3.6 ,python3.7, ruby2.5, java8, go1.x, dotnetcore2.1 or provided) use lambci/yumda:1.

Eg, to see what packages are available for Amazon Linux 2 runtimes:

$ docker run --rm lambci/yumda:2 yum list available

Loaded plugins: ovl, priorities
Available Packages
GraphicsMagick.x86_64        1.3.32-1.lambda2                            lambda2
GraphicsMagick-c++.x86_64    1.3.32-1.lambda2                            lambda2
ImageMagick.x86_64           6.7.8.9-15.lambda2.0.2                      lambda2
OpenEXR.x86_64               1.7.1-7.lambda2.0.2                         lambda2
OpenEXR-libs.x86_64          1.7.1-7.lambda2.0.2                         lambda2
alsa-lib.x86_64              1.1.4.1-2.lambda2                           lambda2
apr.x86_64                   1.6.3-5.lambda2.0.2                         lambda2

# etc...

To install a dependency (eg, ghostscript) into a local directory (which could be zipped up into a layer):

$ mkdir -p gs-layer
$ docker run --rm -v "$PWD"/gs-layer:/lambda/opt lambci/yumda:2 yum install -y ghostscript

Loaded plugins: ovl, priorities
Resolving Dependencies
--> Running transaction check
---> Package ghostscript.x86_64 0:9.06-8.lambda2.0.5 will be installed
--> Processing Dependency: urw-fonts >= 1.1 for package: ghostscript-9.06-8.lambda2.0.5.x86_64
--> Processing Dependency: lcms2 >= 2.6 for package: ghostscript-9.06-8.lambda2.0.5.x86_64
--> Processing Dependency: poppler-data for package: ghostscript-9.06-8.lambda2.0.5.x86_64
--> Processing Dependency: libtiff.so.5(LIBTIFF_4.0)(64bit) for package: ghostscript-9.06-8.lambda2.0.5.x86_64
--> Processing Dependency: libpng15.so.15(PNG15_0)(64bit) for package: ghostscript-9.06-8.lambda2.0.5.x86_64

# etc...

# Then you can zip it up and publish a layer

$ cd gs-layer
$ zip -yr ../gs-layer.zip .
$ cd ..
$ aws lambda publish-layer-version --layer-name gs-layer --zip-file fileb://gs-layer.zip --description "Ghostscript Layer"

Full example with AWS SAM

Let's say you want to create a Lambda function that needs to clone a git repository and then manipulate an image using GraphicsMagick. For fun, we'll also convert it to ASCII art and log it.

The example we'll walk through below uses nodejs10.x runtime (and hence lambci/yumda:2). The code for this example lives in the examples/nodejs10.x directory, but we'll walk through the steps of creating it from scratch. For older runtimes, see examples/python3.7 and just replace any usage below of lambci/yumda:2 with lambci/yumda:1.

Start off by creating a new SAM app:

sam init --runtime nodejs10.x --name yumda-example
cd yumda-example

We'll edit the function code in hello-world/app.js to run the commands we want:

const { execSync } = require('child_process')

const shell = cmd => execSync(cmd, { cwd: '/tmp', encoding: 'utf8', stdio: 'inherit' })

exports.lambdaHandler = async (event, context) => {
  shell('git clone --depth 1 https://github.com/lambci/yumda')

  shell('gm convert ./yumda/examples/sam_squirrel.jpg -negate -contrast -resize 100x100 thumbnail.jpg')

  // Normally we'd perhaps upload to S3, etc... but here we just convert to ASCII:

  shell('jp2a --width=69 thumbnail.jpg')
}

These binaries (git, gm, jp2a) don't exist on Lambda, so we'll need to install them – this is where yumda comes in:

# Assume we're still in the yumda-example directory
mkdir -p dependencies
docker run --rm -v "$PWD"/dependencies:/lambda/opt lambci/yumda:2 yum install -y git GraphicsMagick jp2a

Now we have the binaries (and their dependencies) in a local directory that can be deployed as a layer alongside our function.

We can declare our layer in template.yaml, so the whole app looks like this:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs10.x
      Layers:
        - !Ref DependenciesLayer

  DependenciesLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      ContentUri: dependencies/

You can use the AWS SAM CLI to test it:

$ sam local invoke --no-event

Invoking app.lambdaHandler (nodejs10.x)
DependenciesLayer is a local Layer in the template
Image was not found.
Building image...
Requested to skip pulling images ...

Mounting /tmp/sam-app/hello-world as /var/task:ro,delegated inside runtime container
START RequestId: 6908f297-72af-143f-dd79-2b6128c5c428 Version: $LATEST
Cloning into 'yumda'...
                             ....'',,,''....                         
                      .';codddddddddddddddddddol;'.                  
                  .,ldxdddddddddddddddddodddooddddolc;.              
               .codddddoddddddddddddddddddddddddddddolllc'           
            .;odddddddddddddddddddddddddddddddddddddddollll:.        
           :doddddddddddddddddddddddddddddddddddddoddddolooodc       
         'ddodooooddddoooddddddddddddddddddddddddddddddddlldlod'     
        cddodxkOOOOOOOOOxdddddddddddddddddddddddoddddddddooloood:    
       :ddxO00OOOOOO0OOO0Oxdddddoddddddddoddoddoodddddddddooooooxl   
      ,xxk00OOO00OOOOO0OOO0OdoxdxoclllllllddddddddodddddddoooooooO;  
      dOKkdoodk0OOO00OOOO00Oko:c:,..''.'.';ccldddddddddddddooooookO  
     .O:.      .lO0k00O0kc,::;,''''''''.''''',;;';lodddddddooooodkK' 
                 '0OOOk:'....'',,.'.'''.''',,''....'cddodddoloood0K; 
                  oKx:''....','','''.,.''''',,''. .'',ldddooooookOK: 
                  ld;'.'.,:;'',,''.,0Wx.'''',,,,;;'.''':oddoooox0OK; 
                 'o;,';:;;,,,,,,'''.okNx..'',,,,,,;:;,,;:oolood00kK. 
                .x:;;;;',''',,''''.oWclNO,.'',',''',;;;;;lloodO0O0d  
               :0x;;;;,'''',,,,,''.:,'',;'',,;,,'''',;;;;:oodOO0kK.  
             .x00d;;;:;;::::::::;c::c::c:::::c:::::::;;;;:lkOOOOK;   
           .l0OO0x;;::;;,,,,,,,,,,,,,,',,,,,,',,,,,,;;::;cOOO0kKc    
          ;O0O0OOdol.''..''...''...''''.'...''....''..',xx0O0O0:     
        .x0OOOkdodO''..'.,;:;;;;;,..''...';:;;;;;;'..''.d00k0k'      
       cKO00kxoddxd..'.':'.       ..'..'..       .;,'...:0OKl        
     .d0O0kddodddkl.'.'.            ...            ..''.;0l.         
    .OO0OxodddddoOl''..   .  ;l.     ..     .l:     ..'..            
   .O0Oxoddddddddxx'.'.    .0WWN.          lXWWc     .''             
   xOOdooddddddodkl.''. .   'od'           .cd:     ..''.    . x,x   
  ;Kxodddddddood',......    .  .  ........         ......    'lkOlo. 
  Oxoddddoddoddl   .             ..,'..,'.                           
 'Ododdddodddxxl.    .             .':;..                      .     
 :kooddddddddodk;   .            ..      ..                   ..     
 :ddddddodddddddxd.   .         .........'..                 ...     
 :xdddddddddddddddxxo.    .       ...'...       .          ......    
 'kodddddddddddddddx'   ..''...................'.'.   . ........     
 .dddodddddddddddoxl   ...'...'...'....'''''......''............     
  ,ddododdddddddddx:  ......'''....      ....''.'..'..........       
   cxoxooddddddddddo ..........              ...'..''....'..         
    cxlxdddddddododdc..........    . .        ...''..'''.            
     :xdoodddddddxll:............ .:x.          ..'..'..             
      .okxdddddd:............... .OXlx;          .'.......           
        .okOxxx,.........'.      .O;x.           .'.........         
           ;xOd..........'.       .              ...........         
             .'.............       .            ............         
               ..............                 ..............         
                ...........''.               ..'...........          
                 ...........''.....      ....'............           
                   ..........''.''........''''..........             
                     ......'...''''''''''''.''........               
                     ...'..'..               ..'..''..               

END RequestId: 6908f297-72af-143f-dd79-2b6128c5c428
REPORT RequestId: 6908f297-72af-143f-dd79-2b6128c5c428  Duration: 530.74 ms Billed Duration: 600 ms Memory Size: 128 MB Max Memory Used: 41 MB  
null

Packaging and deploying

To package and deploy our Lambda, we can also use sam, but there's currently a bug packaging any layers that contain symlinks.

You can work around that by creating the layer zip yourself:

cd dependencies
zip -yr ../dependencies.zip .
cd ..

And then change the ContentUri in your template.yaml from dependencies/ to dependencies.zip:

# ...
  DependenciesLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      ContentUri: dependencies.zip

Then you can run sam package (assuming you've created an S3 bucket to save your SAM artifacts to):

sam package --output-template packaged.yaml --s3-bucket <sam-bucket>

And then you can deploy:

sam deploy --template-file packaged.yaml --stack-name yumda-example --capabilities CAPABILITY_IAM

(your Lambda function will be named yumda-example-HelloWorldFunction-<suffix> if you want to invoke it via the AWS CLI or web console)

Example with Serverless Framework

We'll use the same code layout as in the example above, which you can find in the examples directory.

So our lambda function code lives in ./hello-world/app.js and our dependencies are in ./dependencies.

Our serverless.yaml looks like this:

service: yumda-example

provider:
  name: aws
  runtime: nodejs10.x

package:
  individually: true
  exclude:
    - ./**

functions:
  hello-world:
    handler: hello-world/app.lambdaHandler
    package:
      include:
        - hello-world/**
    layers:
      - {Ref: DependenciesLambdaLayer}

layers:
  dependencies:
    path: dependencies
    package:
      artifact: dependencies.zip # Needed until https://github.com/serverless/serverless/issues/6580 is fixed

We install the dependencies the same way as in the previous example, using yumda:

# Assume we're still in the yumda-example directory
mkdir -p dependencies
docker run --rm -v "$PWD"/dependencies:/lambda/opt lambci/yumda:2 yum install -y git GraphicsMagick jp2a

And then we can test this out locally:

sls invoke local --docker -f hello-world

Packaging and deploying

Unfortunately the Serverless Framework also has bugs packaging up layers that have symlinks, so we'll need to zip up the dependencies ourselves to deploy them.

cd dependencies
zip -yr ../dependencies.zip .
cd ..

Then we can deploy:

sls deploy

No devel Packages?

yumda is for installing runtime dependencies – not build-time dependencies (because you're usually not building on Lambda). So a typical workflow is to build native dependencies using a build container of some sort (eg those provided by docker-lambda) and then create a layer for any necessary runtime libraries using yumda.

Here's an example to illustrate that will create a layer containing the OpenEXR bindings for python:

mkdir exr-layer

# First we use build-python3.8 from docker-lambda to *build* the library.
# We install OpenEXR-devel because the pip package needs it to build,
# and then we can use pip to install the python package:

docker run --rm -v "$PWD"/exr-layer:/opt lambci/lambda:build-python3.8 \
  bash -c "yum install -y OpenEXR-devel && pip install OpenEXR -t /opt/python"

# At runtime, on Lambda itself, we don't need OpenEXR-devel – we only need the
# libraries that the built python package depends on, which is just OpenEXR-libs.
# We want them in our layer, so this is where yumda comes in:

docker run --rm -v "$PWD"/exr-layer:/lambda/opt lambci/yumda:2 \
  yum install -y OpenEXR-libs

# Now we have everything we need in ./exr-layer and we can deploy it as a layer

Requesting Packages to Add

Please file a GitHub Issue with your request and add the package suggestion label. For now we'll only be considering additions that already exist in the Amazon Linux core repositories, or the amazon-linux-extras repositories (including epel).

Building/Hosting Your Own Packages

More words are needed here...

For now, you can see all the .spec files for the compiled RPM packages in the specs/lambda2 directory – and compare them with the corresponding specs/amzn2 files to see what's been modified to get them running for a Lambda environment, as an inspiration to build your own.

The build image uses a set of rpmmacros so the software is compiled for a /opt environment (as well as using lib instead of lib64 as the library path).