WeasyPrintをAWS Lambdaで使用するアプリケーションをAWS CDKに移植してみた
こんにちは。サービス部の武田です。
以前臼田がWeasyPrintをAWS Lambdaで使用するエントリを書きました。ここではWeasyPrintはLambda Layersとして実装し、それを利用するアプリケーションをLambda関数として実装していました。
今回はこれをAWS CDKに移植してみたので、ポイントなどを紹介します。
なおソースコード一式はGitHubに上げてあります。
TAKEDA-Takashi/weasyprint-cdk: weasyprint-cdk
挙動についておさらい
アプリケーションとしては大きく変わっていません。一部オブジェクトのキーなどを変えたくらいです。次のコマンドでAWSにデプロイできます。
$ npm install $ npx cdk bootstrap $ npx cdk deploy
あとはマネジメントコンソールでLambda関数にアクセスし、テスト実行します。
S3バケットにレポートが生成されます。
中身も問題ありません。
移植のポイント
CDKへ移植にするにあたって一番のネックは、Lambda Layersのビルドをどうするか、でした。CDKでは、ランタイムがPythonのLayerを組み込む場合@aws-cdk/aws-lambda-python-alpha
モジュールを使用します。このモジュールはLayerのビルドにDockerを使用します。
一方で、cloud-print-utils
で用意されているMakefileでは、Layerのzipを作成するにあたってDockerを使用しています。そのためCDKから実行するDockerイメージの中で、さらにDockerを使用する必要がありました。私は経験がなかったのですが、 Docker in Docker(dind) と呼ばれる利用形態です。今回は、厳密には Docker outside of Docker(dood) らしいのですが、区別せずDocker in Docker
と呼ぶことにします。
この問題を解決するためにいくつかのステップが必要でした。
まず、CDKがデフォルトで使用するDockerイメージではdocker
コマンドが使用できません。そのため、docker:rc-dind
などのイメージを使用します。しかし、このイメージではbash
コマンドなどが使用できません。そんなわけで、それをベースにして必要なコマンドをインストールするDockerfileを用意します。
FROM docker:rc-dind RUN apk add --no-cache bash make zip CMD []
次に、ボリュームのマウントで問題が起きました。Makefileでは次のようにpwd
コマンドを使用してマウントする部分があります。
build/fonts-layer.zip: fonts/layer_builder.sh | _build docker run --rm \ -e INSTALL_MS_FONTS="${INSTALL_MS_FONTS}" \ -v `pwd`/fonts:/out \ -t lambci/lambda:build-${RUNTIME} \ bash /out/layer_builder.sh mv -f ./fonts/layer.zip $@
このコマンドはホストで実行する分にはもちろんうまくいくのですが、今回の環境ではうまくいきませんでした。なぜならpwd
コマンドで得られるパスはDockerイメージ内部のもので、Dockerとしてはホスト上のパスを渡されないと認識できないからです。そのため偶然そのパスに何かある可能性はありますが、意図したファイルではないでしょう。
具体的には次のエラーが発生しました。
bash: /out/layer_builder.sh: No such file or directory
そのため該当箇所を次のようにpwd
コマンドを使用しないものに変更します。${HOST_PATH}
は環境変数を参照していますが、これはCDKがビルドする際に渡すホスト上のパスを想定しています。
build/fonts-layer.zip: fonts/layer_builder.sh | _build docker run --rm \ -e INSTALL_MS_FONTS="${INSTALL_MS_FONTS}" \ -v ${HOST_PATH}/fonts:/out \ -t lambci/lambda:build-${RUNTIME} \ bash /out/layer_builder.sh mv -f ./fonts/layer.zip $@
一番のポイントであるLambda Layerを作成するCDKのソースは次のようになりました。
const weasyprintLayer = new lambdaPython.PythonLayerVersion( this, "WeasyprintLayer", { layerVersionName: "weasyprint-layer", entry: path.resolve(__dirname, "../cloud-print-utils"), compatibleRuntimes: [lambda.Runtime.PYTHON_3_8], bundling: { image: cdk.DockerImage.fromBuild("./docker/"), environment: { HOST_PATH: path.resolve(__dirname, "../cloud-print-utils"), }, user: "root", command: [ "bash", "-c", [ "make build/weasyprint-layer-python3.8.zip", "cp build/weasyprint-layer-python3.8.zip /asset-output", ].join(" && "), ], volumes: [ { hostPath: "/var/run/docker.sock", containerPath: "/var/run/docker.sock", }, ], }, } );
bundling
プロパティを指定することで、かなり細かくカスタマイズできます。image
ではビルドに使用するDockerイメージを使用しますが、先ほど用意したDockerfileが使用されるようにします。environment
ではMakefileで使用される環境変数を設定しています。
command
がDockerコンテナで実行されるコマンドの指定です。ビルドのために使用していたmake
だけでなく、ホストにアーティファクトを連携するため、/asset-output
にzipファイルをコピーしています。これはCDKで使用するDockerコンテナのルールにのっとっています。
volumes
でマウントするボリュームを指定します。今回はホストのsock
ファイルを共有しています。この指定でdood
の環境となり、コンテナの中でDockerを操作します。ちなみに、これを指定しないと次のようなエラーが起きてしまいました。
docker: error during connect: Post "http://docker:2375/v1.24/containers/create": dial tcp: lookup docker on 192.168.5.3:53: lame referral.
今回M1 MacのRancher Desktopを使用しているのですが、他の環境ではこのエラーは起きない可能性があります。よければ試してみてください。
まとめ
WeasyPrintのレイヤー化をCDKで実装してみました。Docker in Docker
やbundling
を使用したビルドのカスタマイズなど、これまで経験のなかった範囲ですが、なんとか実現できてよかったです。
料金はほぼ発生しませんが、後片付けは忘れずに。