Lambda Layersを作成する時はdocker-lambdaやyumdaが便利

## 2020/7/23 追記
本記事の前半では docker-lambda のビルドイメージを使用した Lambda Layers の作成方法を
ご紹介していますが、AWS SAM CLI が 2020/7/21 に GA となり、AWS からオフィシャルに
ビルド用のイメージが提供開始されました。

https://hub.docker.com/search?q=amazon%2Faws-sam-cli-build&type=image
https://github.com/awslabs/aws-sam-cli/tree/master/build-image-src

上記のイメージを利用し、Lambda Layers を作成することも可能です。
以下のようにイメージ名の箇所を読み替えてください。

python3.8 の場合は `amazon/aws-sam-cli-build-image-python3.8:latest` を使用します。

```shell-session
$ docker run --rm -v $(pwd):/var/task amazon/aws-sam-cli-build-image-python3.8:latest \
> pip install -r requirements.txt -t python/lib/python3.8/site-packages/
```

追記おわり。

## はじめに

docker-lambda のビルドイメージで各ラインタイムバージョンに対応した Layer が作れる！

```shell-session
$ docker run --rm -v $(pwd):/var/task lambci/lambda:build-python3.8 \
> pip install -r requirements.txt -t python/lib/python3.8/site-packages/
```

AWS Lambdaで 実行したいソフトウエアパッケージは yumda で Layer 化できる！

```shell-session
$ docker run --rm -v $(pwd)/git-layer:/lambda/opt lambci/yumda:2 yum install -y <package>
```

## 一からやると環境準備は結構大変
AWS Lambda や Lambda Layers のよくある注意点としてネイティブバイナリの扱いがあります。
Lambda 実行環境は、特定の Amazon Linux 環境とカーネルバージョンに基づいているため、
Lambda 内で使用されるネイティブバイナリも該当の環境でコンパイルされている必要があります。

また現在は Amazon Linux のサポート終了の過渡期でもあり、使用するランタイムのバージョンにより
Amazon Linux ベースなのか Amazon Linux 2 ベースな異なるところもやっかいな点です。
例えば Python の場合は 2020/3/28 時点で以下のようになっています。

|Version|OS|
|---|---|---|
|Python 3.8|Amazon Linux 2|
|Python 3.7|Amazon Linux|
|Python 3.6|Amazon Linux|
|Python 2.7|Amazon Linux|

**AWS Lambda Runtimes**
https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html

パッケージ作成の度にEC2を起動するのもさすがに面倒です。
また Docker Hub で公開されている [Amazon Linuxのコンテナイメージ](https://hub.docker.com/_/amazonlinux) を使ってもよいのですが、
このイメージには最小限のパッケージセットしか含まれていないため、
これをベースイメージとして対象のランタイム環境を追加でインストールする必要があります。
複数のランタイムやバージョンを対象に開発する場合、環境毎に Dockerfile やイメージを管理
するのもちょっと大変かなと思います。

前置きが長くなりましたが、そんな時は docker-lambda や yumda が便利です。

## docker-lambda 
### docker-lambda とは
docker-lambda は Docker コンテナ内で AWS Lambda の実行環境を限りなく近く再現します。
（が、完全なコピーではありません）
Docker Hub の [lambci/lambda](https://hub.docker.com/r/lambci/lambda/) でコンテナイメージが公開されています。
AWS Severless Hero の [Michael Hart](https://aws.amazon.com/jp/developer/community/heroes/michael-hart/) 氏を中心に OSS として開発されています。

**docker-lambda**
https://github.com/lambci/docker-lambda

以前、AWS SAM CLI の中でも Lambda 関数の実行環境として採用されていました。
(現在は AWS がオフィシャルでイメージを提供しています。冒頭の追記参照。)

### docker-lambda のビルドイメージ
docker-lambda には Lambda 関数の ビルドとパッケージを行うためのビルドイメージも提供されています。
ビルドイメージは build-python3.x といったイメージタグでランタイムバージョンごとに用意されています。
ビルドイメージに追加でインストールされているパッケージは
https://hub.docker.com/r/lambci/lambda/#build-environment を参照してください。
AWS CLI や AWS SAM CLI も含まれているので、やろうと思えばコンテナ内でもろもろできちゃいます。

### python3.8 互換の layer を作成する例
`lambci/lambda:build-python3.8` を使用します。
requirements.txt に 追加したいライブラリを記載しておきます。
ここではシンプルに pandas のみを追加しています。

```requirements.txt
pandas
```

ビルドイメージ内で pip install して、ライブラリを Layer に含めます。

```shell-session
$ docker run --rm -v $(pwd):/var/task lambci/lambda:build-python3.8 \
> pip install -r requirements.txt -t python/lib/python3.8/site-packages/

$ zip -r pandas-1.0.3.zip ./python > /dev/null
```

複数バージョンに互換性を持たせた Layer としたい場合は 各バージョン毎にビルドイメージと
site-packages を指定して docker run してから zip します。
以下のようなディレクトリ構造になります。
展開後のパッケージサイズの制限（250MB）には注意する必要があります。

```
.
`-- python
    `-- lib
        |-- python3.6
        |   `-- site-packages
        |-- python3.7
        |   `-- site-packages
        `-- python3.8
            `-- site-packages
```

先ほどの Layer を使用して 以下のような超簡易コードで動作確認してみると、、、

```lambda_function.py
import pandas as pd;

def lambda_handler(event, context):
    pd.show_versions()
```

実行できました！

```
START RequestId: 813889ca-958c-4b60-ae1d-442870679429 Version: $LATEST

INSTALLED VERSIONS
------------------
commit           : None
python           : 3.8.2.final.0
python-bits      : 64
OS               : Linux
OS-release       : 4.14.165-102.205.amzn2.x86_64
machine          : x86_64
processor        : x86_64
byteorder        : little
LC_ALL           : None
LANG             : en_US.UTF-8
LOCALE           : en_US.UTF-8

pandas           : 1.0.3
numpy            : 1.18.2
~~以下省略~~
```

## yumda
yumda を使用すると AWS Lambda 環境で実行可能なソフトウェアパッケージを
yum コマンドで用意することができます。docker-lambda と同様 OSS として開発されています。

**yumda – yum for Lambda**
https://github.com/lambci/yumda

コンテナイメージは [lambci/yumda](https://hub.docker.com/r/lambci/yumda/) で公開されています。
Amazon Linux 2 環境向けのイメージとAmazon Linux 環境用のイメージが用意されていますので
nodejs10.x、nodejs12.x、python3.8、java11、ruby2.7 は `lambci/yumbda:2` を、
それ以外のランタイムバージョンでは `lambci/yumbda:1` を使用します。

Amazon Linux 2 環境向けに利用可能なパッケージを確認するには以下のようなコマンドを実行します。 

```shell-session
$ docker run --rm lambci/yumda:2 yum list available
Loaded plugins: ovl, priorities
Available Packages
GraphicsMagick.x86_64        1.3.34-1.lambda2                            lambda2
GraphicsMagick-c++.x86_64    1.3.34-1.lambda2                            lambda2
ImageMagick.x86_64           6.7.8.9-18.lambda2                          lambda2
OpenEXR.x86_64               1.7.1-7.lambda2.0.2                         lambda2
OpenEXR-libs.x86_64          1.7.1-7.lambda2.0.2                         lambda2
~~以下省略~~
```

もしくは 以下からも確認できます。
https://github.com/lambci/yumda/blob/master/amazon-linux-2/packages.txt

パッケージは独自のリポジトリで管理されており、GitHub Issues で追加のリクエストを提出できますが、
Amazon Linux core リポジトリおよび amazon-linux-extras リポジトリに既に存在するものを
追加する方針であるようです。

> 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).

### yumda で Layer を作成する例
ここでは 試しに python3.8 の Lmabda 関数で git コマンドを実行してみます。

```lambda_function.py
import subprocess

def lambda_handler(event, context):
    return subprocess.check_output(
        "git --version;exit 0",
        stderr=subprocess.STDOUT,
        shell=True
    )
```

当然ながら Lambda Layers を使わない場合、実行結果は commnad not found になります。

```shell-session
$ aws lambda invoke --function-name git-layer-test outfile
ExecutedVersion: $LATEST
StatusCode: 200

$ cat outfile
/bin/sh: git: command not found
```

yumda で Layer を作成します。

```shell-session
$ docker run --rm -v $(pwd)/git-layer:/lambda/opt lambci/yumda:2 yum install -y git
$ cd git-layer
$ zip -yr ../git-2.25.0-1.zip . > /dev/null
```

以下のようなディレクトリ構成で git とその依存関係がインストールされていることを確認できます。

```
.
|-- bin
|-- etc
|   |-- alternatives
|   |-- pki
|   |-- prelink.conf.d
|   `-- ssh
|-- lib
|   |-- fipscheck
|   `-- nss
|-- libexec
|   |-- git-core
|   `-- openssh
`-- share
    |-- git-core
    `-- licenses
```

Layer を publish して先ほどの Lambda 関数に設定します。

```shell-session
$ aws lambda publish-layer-version \
> --layer-name git-2-25-0-1 --zip-file fileb://git-2.25.0-1.zip

$ aws lambda update-function-configuration \
>  --function-name git-layer-test \
>  --layers arn:aws:lambda:ap-northeast-1:123456789012:layer:git-2-25-0-1:1
```

実行してみると git の version が返ってきました！

```shell-session
$ aws lambda invoke --function-name git-layer-test outfile
ExecutedVersion: $LATEST
StatusCode: 200

$ cat outfile
git version 2.25.0
```

## 参考: 公開済みの Lambda Layers を使う
ここまで書いておいてなんですが、[Sevelress Application Repository](https://serverlessrepo.aws.amazon.com/applications) でも Lambda Layers が
多数登録されていますし、[λ AWSome Lambda Layers](https://github.com/mthenw/awesome-layers) というGitHubリポジトリにも
様々な Layer のリストがまとめられています。
これらに必要なものが公開されていれば、それを使わせていただくのが楽ですね！

以上です。
参考になれば幸いです。