Docker で Python 用の Lambda レイヤーを作る
2020-05-16AWS Lambda の関数を Python で実装する際に使用するレイヤーを作ったので、その時のメモです。
目次
概要
Python 3.8 をランタイムとして、 AWS Lambda の関数を実装することを考えます。その際に、 pip
でインストールしたモジュールを Lambda レイヤーとして構築します。
AWS Lambda ではラインタイムごとに実行環境 (OS) が異なります。このエントリを書いている時点 (2020/05/16) では、 Python 3.8 の実行環境は Amazon Linux 2 となっています。
今回は Lambda レイヤー用のモジュール群 (zip ファイル) を、 Docker を使って Amazon Linux 2 環境で作成します。
Docker イメージの作成
まずは Python 3.8 をインストールした Amazon Linux 2 の Docker イメージを作成します。Amazon Linux 2 のイメージは Docker Hub で公開されているので、今回はその中から 2.0.20200406..0
を使います。
イメージサイズの削減
とりあえず作成したとき、イメージのサイズが 1.17 GB になってしまいました。さすがにサイズが大きすぎると思ったので、削減方法について調べてみました。
Dockerfile の RUN の挙動
Dockerfile では RUN
コマンドを使って Linux コマンドを記述して Python 3.8 をインストールしていきます。この RUN
コマンドは実行するたびにレイヤを重ねていく形になります。なので、 Python 3.8 用のインストールパッケージなど不要な一時ファイルを削除する場合は、同じ RUN
コマンド内で処理する必要があります。
イメージビルド時のサイズの変遷を調べる
docker build
でイメージをビルドしていく過程で、イメージのサイズがどのように変化していったかを確認することができます。
$ docker history IMAGE
実行結果は次のような形で出力されます。
docker history 6a20bde9c6da
IMAGE CREATED CREATED BY SIZE COMMENT
6a20bde9c6da 10 hours ago /bin/sh -c pip3.8 -V 0B
49f06a13c22d 10 hours ago /bin/sh -c python3.8 -V 0B
669423823b31 10 hours ago /bin/sh -c rm -rf Python-3.8.3 0B
9e98a158748d 10 hours ago /bin/sh -c rm -rf Python-3.8.3.tgz 0B
2af6cd8cdd37 10 hours ago /bin/sh -c cd Python-3.8.3 && ./configure --… 360MB
22c8ca1d8b4c 10 hours ago /bin/sh -c tar xzf Python-3.8.3.tgz 84.1MB
b2acff82a0d2 10 hours ago /bin/sh -c wget https://www.python.org/ftp/p… 24.1MB
621e604d00a9 10 hours ago /bin/sh -c yum -y install gcc openssl-devel … 332MB
7f335821efb5 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:c64a7d77d1e9f4364… 163MB
イメージ内のディレクトリごとのサイズを調べる
ビルド後のイメージについて、ディレクトリごとのサイズを確認します。
$ docker run -it --rm 6a20bde9c6da du / | sort -n | tail
179120 /var/cache/yum/x86_64/2/amzn2-core
179128 /var/cache/yum/x86_64/2
179132 /var/cache/yum/x86_64
179136 /var/cache/yum
179160 /var/cache
190324 /var
212184 /usr/local/lib
230872 /usr/local
544248 /usr
736976 /
これで不要なディレクトリがあれば削除するようなコマンドを Dockerfile
に追記して再ビルド -> 確認 -> 再ビルド … していきます。
最終的に 550 MB くらいになりましたが、これでも大きいですよね。多分。
Docker Hub へ Push
不要ファイルを削除してサイズ削減した Dockerfile は次のようになりました。
FROM amazonlinux:2.0.20200406.0
RUN yum -y install \
gcc \
openssl-devel \
bzip2-devel \
libffi-devel \
wget \
tar \
gzip \
make \
wget https://www.python.org/ftp/python/3.8.3/Python-3.8.3.tgz && \
tar xzf Python-3.8.3.tgz && \
cd Python-3.8.3 && \
./configure --enable-optimizations && \
make altinstall && \
cd ~ && \
rm -rf /var/cache/yum/* && \
rm -rf /Python-3.8.3
RUN python3.8 -V
RUN pip3.8 -V
この Dockerfile を使ってイメージを作成します。このとき、 -t
で指定するタグ名は {Docker Hub のアカウント ID}/{Docker Hub のリポジトリ名}
にしておきます。
$ docker build -t michimani/amzn2py38 .
イメージが作成できたら、 Docker Hub にログインします。
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: michimani
Password:
Login Succeeded
ログインできたら、先ほど作成したイメージを Docker Hub に Push します。
$ docker push michimani/amzn2py38:latest
Push されたイメージを Docker Hub で確認します。
レイヤー用 zip ファイルの作成
必要な Python モジュールを requirements.txt
に記述しておきます。そして、下記のようなディレクトリ構成を作ります。
.
├── Dockerfile
├── docker-compose.yml
└── requirements.txt
Dockerfile
は次のように記述します。
FROM michimani/amzn2py38:latest
RUN yum -y install zip
RUN mkdir /home/dist
docker-compose.yml
は次のように記述します。
version: "3"
services:
app:
build: .
volumes:
- ".:/home"
command: >
bash -c "
mkdir /home/dist &&
mkdir -p /home/tmp/python &&
pip3.8 install -r /home/requirements.txt -t /home/tmp/python --upgrade &&
cd /home/tmp &&
zip -r /home/dist/layer.zip ./python &&
cd /home &&
rm -rf /home/tmp"
あとは次のコマンドを実行すれば、 dist/layer.zip
が生成されます。
$ docker-compose up
Lambda レイヤーの登録
Lambda レイヤーの登録は、下記の AWS CLI コマンドで実行できます。
$ aws lambda publish-layer-version \
--layer-name my_layer \
--zip-file fileb://dist/layer.zip
まとめ
Python 3.8 用の Lambda レイヤーを Docker を使って作成してみた話でした。
Docker まだまだよくわからん状態なので、最初にイメージサイズが 1 GB 超えたときは焦りました。 RUN
の挙動についてもちゃんとわかっていなかったので、調べる機会になってよかったです。
Lambda レイヤーについては、外部モジュールを Lambda 自体のデプロイパッケージサイズを小さくすることができ、さらに他の関数でも利用できるので、用途別にレイヤーを作成しておけば Lambda 関数の実装がしやすくなりそうです。
comments powered by Disqus