くりにっき

フルスタックキュアエンジニアです

AWS Lambda CI/CD俺的ベストプラクティス

Lambdaで動くアプリやフレームワークの事例はよく見るのですが、LambdaのCIやCDにしやすさに主眼をおいた紹介はあんまり見ないので現時点での自分のベストプラクティスのメモです

tl;dr;

  • ライブラリインストール時にmakeが走る言語(例:Ruby, Python, nodejsなど)の場合はServerless Framework使うのが楽
  • それ以外はServerless FrameworkかAWS SAMのどっちかならまず困らない

このエントリで書いていること

  • 自分が過去に試した手法
  • 逆に言えば自分がやったことないことは書いてないので誰かいい感じに紹介してほしいw

Lambdaをデプロイするのに肝になること

  • AWSAPIを実行するために必要なライブラリ(Rubyならaws-sdk gem)は実行環境に含まれているのでインストールする必要はないが、それ以外のライブラリを使いたい場合にはインストール済の状態でzipに固めてアップロードする
  • さらにライブラリインストール時にmakeが走る場合(Rubyのgemだとnative extensionと呼ばれるもの)、Lambdaと同じ実行環境(Amazon Linux)上でコンパイルをする必要がある。(以降、native extension問題と呼称)
    • ローカルのMacUbuntuとかで bundle install してzipに固めてデプロイするのは絶対ダメ
    • Rubyに限らずPythonとかでも同じことが言える
      • LambdaのPythonからMySQLdb(PythonMySQLを使うためのライブラリ)を使おうとするとAmazon Linuxでビルド済の mysqlclient.so もzipに含める必要がある
  • Lambdaを使いたい場合、大抵の場合Lambda以外のリソースも必要になるのでそれらを含めたデプロイを考える
    • 例えばLambdaでHTTPリクエストを受けたい場合にはAPI Gatewayが必要だし、cronみたいに定期実行させたい場合にはCloudWatchが必要だし、Lambdaでこれらのリソースを操作するにはIAMの権限を追加する必要がある
    • 純粋にLambda(とLambdaの実行しかできないIAMロール)だけで済むケースは見たことないです

デプロイしやすさに着眼したフレームワーク紹介

論外

コンソールからアップロードする

軽くお試し利用するくらいならコンソール上で直接編集してもいいんだけど、各種ライブラリを使いたい場合にはzipで固めてアップロードする必要があります。

問題点

  • Vim のような使い慣れたエディタが使えない
  • gitによる履歴管理できない
  • CIとの連携ができない
  • 手でzipを固めてアップロードするのは時代遅れ感
  • 冒頭で書いたように真面目にやるならEC2にAmazon Linuxインスタンスたててからインストールしてzipに固める必要があるので面倒
    • ライブラリを使わないんならそこまで気にしなくてもいいんだけど、一定規模以上のアプリ作る場合にはライブラリは必須だし、そのライブラリのインストール時にmakeが必要かを依存してるライブラリのその先の依存まで人間が全部気にかける必要はないと思ってます。(それはbundlerやnpmなどのパッケージマネージャの役割)

できなくはないがかなり厳しい

Terraform

www.terraform.io

AWS全般のオーケストレーションといえばTerraformが一番有名ですが、Lambdaのデプロイには向かないです

理由

  • Terraformだけだとライブラリのインストール込のzip圧縮ができない
    • ファイル名直打ちやディレクトリ丸ごと圧縮とかはできる *1
    • Terraform外でzipに固めてそれをTerraformに渡せばまあできないことはなさそうだけど、そこを自分で作り込むくらいなら他の出来るツールを使った方がいい
  • 仮にTerraformでzipを作った場合、zip上は差分が全くないのにTerraform上で差分が出ることがある
    • ローカルだと差分がないのにCIだと差分が出ることが割とよくある(その逆もよくある)
  • Terraform使えばAWSに関することはだいたいなんでもできるんだけど、AWSAPIをそのまま使うのとそんなに変わらないので記述が割と冗長になる
    • 後述のServerless FrameworkやSAM(実質Cloud Formation)の方が多少楽

Apex

メリット

デメリット

  • Lambdaの実行にLambda以外のものが必要な場合、結局Terraformが必要になる
    • Lambda実行用のIAMロールは自動で作ってくれるんだけど、それに新しくポリシーを追加したい時はTerraformにimportする必要がある
    • Apex自体はTerraform連携機能があるんだけど、最初からそれ用のディレクトリ構成にする必要があるので移行コストがある
    • 上でも書いたけどTerraformだと記述が冗長になりがち

8/12 17:20追記

リポジトリトップに

This software is no longer being maintainted and should not be chosen for new projects.

って書いてるので新規に採用しない方がよさそう。

実用レベル

Serverless Framework

メリット

  • Terraformに比べたらAWSAPIを意識しなくていい
  • プラグインが充実してる(後述)
  • Lambda以外のリソースも作れるので、Serverlessのリポジトリだけで完結する

デメリット

  • npmなので実行環境にnodejsが必要
  • デプロイにCloudFormationを利用してるので、デプロイ時にエラーになったらCloudFormationのjsonを読む必要があって大変

AWS SAM

native extension問題と戦う

Amazon LinuxのEC2インスタンス内でビルドする

AWS Lambda RubyでNative Extensionsを使用するgemを使うには?serverlessも使ってみた! - GA technologies Tech Blog に書かれているやり方

ただしこれだとCircleCIなどとの連携がやりづらいという欠点があります。(GitLab CIであれば自分でRunnerを立ててshell executorで動かせば使えないことはない)

Amazon Linux互換のDockerイメージを使う

  • https://hub.docker.com/r/lambci/lambda/
  • AWS公式ではないけど、活発にメンテされていて信用できる
  • LambdaのCIをしようとするとだいたいこのDockerイメージにいきつくくらいにはメジャー

昨今のCIサービスはDockerイメージを使うのがデファクトなので、 lambci/lambda を使うのがいいでしょう

ただし注意点として lambci/lambda のコンテナ内ではrootユーザではない&sudoもないので権限がだいぶ制限されてます。

Serverless Frameworkのプラグインを使う

Serverless Frameworkはプラグインが充実しています。その中にライブラリのインストール時に任意のDockerイメージからコンテナを起動して、コンテナ中でライブラリのインストールを行ってデプロイしてくれるプラグインがあります。

僕が把握しているのは下記です

これらを使えばCIで使うのも容易でしょう。

ただしCIが起動したDockerコンテナの上でさらに lambci/lambda のコンテナを起動する構図になるのでちょっと特殊な構成になります。

また、そのためにはDockerのprivilegedオプションを有効にしてdind(Docker in Docker)が必要になるので、CIサービスによってはセキュリティ的な観点で使えないケースもあるかもしれません。(GitLab CIであればprivilegedオプションを有効化したRunnerを自分で作れば可、CircleCIはやったことないので不明)

ライブラリをインストールするジョブとデプロイするジョブを分ける

AWS SAMのクライアントはnpmなので実行環境にnodejsが必要になります。

例えばSAMでデプロイするためにlambci/lambda 内でbundle installすることを考えると、もし1つのイメージで行おうとするとlambci/lambda 内にnodejsとRubyのランタイムをインストールする必要があるのですが、先に書いたようにlambci/lambda 内では権限が制限されているためインストールができません。*2

そのため、「ライブラリのインストールを行うジョブ」と「デプロイを行うジョブ」をわけて、それぞれのジョブで使うDockerイメージを別々にするのが現実的な解決方法だと思います。

CircleCIであれば persist_to_workspaceattach_workspace を使えば同一ワークフローの複数のジョブ間でファイルの受け渡しが可能です。

https://circleci.com/docs/2.0/configuration-reference/

もしCircleCIでRubyのアプリをSAMでデプロイしようとするなら

  1. lambci/lambda:ruby2.5 を使ったジョブでbundle installして persist_to_workspace する
    • ここでzip圧縮をしないのは lambci/lambda:ruby2.5 にはzipがインストールされていない&権限の関係でインストールもできないため
  2. 後続のジョブで attach_workspace してインストール済のライブラリを受け取り、zip圧縮してSAMでデプロイする

という構成になるでしょう。

Serverless Frameworkを使う場合に比べたらジョブを分ける必要があって若干手間ですが、個人的にはギリギリ許せる範囲かなと思っています。

【9/4 0:00追記】sam build --use-containerを使う

sam build--use-container があるという知り合い情報

docs.aws.amazon.com

If your functions depend on packages that have natively compiled dependencies, use this flag to build your function inside an AWS Lambda-like Docker container.

なるほどー

aws.amazon.com

If we include dependencies with native extensions, such as nokogiri, we want to make sure we’re building on an Amazon Linux-compatible image. With SAM CLI, you can do that in a single command as well.

トノコト

native extension問題と無縁な言語

Go言語はクロスコンパイルに対応しているのでlinux_amd64でビルドしていればどの環境でビルドしてもLambdaで動くバイナリが作れて楽です。(Javaもnative extension問題と無縁だと思うけど自信ない)

この手の言語で開発するのであればServerless FrameworkとAWS SAMのどっちで作ったとしてもまず困ることはないと思います。

8/12 01:20追記

ブコメレス

id:asuka0801

Serverless Framework使うためだけにnodejs入れるのが嫌

ローカルでの話なのかCIでの話なのか分からないけど(まぁこのエントリではCIのことしか書いてないので後者と予想)、公式じゃないもののserverlessインストール済のDockerイメージもあるのでCIでnodejs入れる必要はないです。(公式じゃないから嫌とかDocker入れるのが嫌とかいうのはさすがに面倒見きれない)

https://hub.docker.com/r/amaysim/serverless

とはいえ amaysim/serverless だとdockerが入っていなくてdindができないので実際のところ docker:dind にnodejs入れてさらに npm ci が現実解だと思います。(社のserverlessリポジトリだとそうやってた。deployジョブの実行時間見たら諸々のインストールの時間含めて1分半前後)

8/12 11:00追記

ブコメレス

id:esodov

そもそも例であげてるlambdaでmysqlって構成どうなのという気も。そこ主題じゃないか。

重箱の隅をつつくような指摘ありがとうございます。

一応補足しておくと、RDSやAuroraのMySQLの監視を行うスクリプトpythonで、そいつをLambdaで動かしていたのです。

8/12 17:20追記

id:YonmanHasse

Apexは"This software is no longer being maintainted and should not be chosen for new projects."なので挙げるにしてもそこを書いた方が

あー、元々は半年以上前に会社のesaに書いてたやつなので、社外向けに書き直した時にリンク先のリポジトリは見てなかったです。追記しました。

*1:https://www.terraform.io/docs/providers/archive/d/archive_file.html

*2:むっちゃ頑張ればできるかもしれないけど未確認