Lambdaで動くアプリやフレームワークの事例はよく見るのですが、LambdaのCIやCDにしやすさに主眼をおいた紹介はあんまり見ないので現時点での自分のベストプラクティスのメモです
- tl;dr;
- このエントリで書いていること
- Lambdaをデプロイするのに肝になること
- デプロイしやすさに着眼したフレームワーク紹介
- native extension問題と戦う
- native extension問題と無縁な言語
- 8/12 01:20追記
- 2019/8/12 11:00追記
- 8/12 17:20追記
- 2020/9/18追記
tl;dr;
- ライブラリインストール時にmakeが走る言語(例:Ruby, Python, nodejsなど)の場合はServerless Framework使うのが楽
- それ以外はServerless FrameworkかAWS SAMのどっちかならまず困らない
このエントリで書いていること
- 自分が過去に試した手法
- 逆に言えば自分がやったことないことは書いてないので誰かいい感じに紹介してほしいw
Lambdaをデプロイするのに肝になること
- AWSのAPIを実行するために必要なライブラリ(Rubyならaws-sdk gem)は実行環境に含まれているのでインストールする必要はないが、それ以外のライブラリを使いたい場合にはインストール済の状態でzipに固めてアップロードする
- さらにライブラリインストール時にmakeが走る場合(Rubyのgemだとnative extensionと呼ばれるもの)、Lambdaと同じ実行環境(Amazon Linux)上でコンパイルをする必要がある。(以降、native extension問題と呼称)
- Lambdaを使いたい場合、大抵の場合Lambda以外のリソースも必要になるのでそれらを含めたデプロイを考える
デプロイしやすさに着眼したフレームワーク紹介
論外
コンソールからアップロードする
軽くお試し利用するくらいならコンソール上で直接編集してもいいんだけど、各種ライブラリを使いたい場合にはzipで固めてアップロードする必要があります。
問題点
- Vim のような使い慣れたエディタが使えない
- gitによる履歴管理できない
- CIとの連携ができない
- 手でzipを固めてアップロードするのは時代遅れ感
- 冒頭で書いたように真面目にやるならEC2にAmazon Linuxでインスタンスたててからインストールしてzipに固める必要があるので面倒
- ライブラリを使わないんならそこまで気にしなくてもいいんだけど、一定規模以上のアプリ作る場合にはライブラリは必須だし、そのライブラリのインストール時にmakeが必要かを依存してるライブラリのその先の依存まで人間が全部気にかける必要はないと思ってます。(それはbundlerやnpmなどのパッケージマネージャの役割)
できなくはないがかなり厳しい
Terraform
AWS全般のオーケストレーションといえばTerraformが一番有名ですが、Lambdaのデプロイには向かないです
理由
- Terraformだけだとライブラリのインストール込のzip圧縮ができない
- 仮にTerraformでzipを作った場合、zip上は差分が全くないのにTerraform上で差分が出ることがある
- ローカルだと差分がないのにCIだと差分が出ることが割とよくある(その逆もよくある)
- Terraform使えばAWSに関することはだいたいなんでもできるんだけど、AWSのAPIをそのまま使うのとそんなに変わらないので記述が割と冗長になる
- 後述のServerless FrameworkやSAM(実質Cloud Formation)の方が多少楽
Apex
- https://github.com/apex/apex
- AWSのLambdaに特化したフレームワーク
- 他のクラウドは非対応
メリット
- Terraformに比べたらAWSのAPIを意識しなくていい
- golang製のバイナリなので実行環境を選ばないのがCI的に嬉しい
- デプロイ時に
Gemfile
に書いてるgemを自動でbundle installしてzipに含めることができる - 作りがシンプルなので学習コストは低い
デメリット
- 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
- https://github.com/serverless/serverless
- サーバレスのための全部入りフレームワーク
- AWS以外も対応してる
メリット
デメリット
- npmなので実行環境にnodejsが必要
- デプロイにCloudFormationを利用してるので、デプロイ時にエラーになったらCloudFormationのjsonを読む必要があって大変
AWS SAM
- https://aws.amazon.com/jp/serverless/sam/
- Serverless FrameworkのAWS公式版
- AWSにしか対応してない以外は守備範囲はServerless Frameworkとだいたい一緒
- バックエンドがCloudFormationなので使い勝手もServerless Frameworkとだいたい一緒
- 合わせて読みたい
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_workspace
と attach_workspace
を使えば同一ワークフローの複数のジョブ間でファイルの受け渡しが可能です。
https://circleci.com/docs/2.0/configuration-reference/
もしCircleCIでRubyのアプリをSAMでデプロイしようとするなら
lambci/lambda:ruby2.5
を使ったジョブでbundle installしてpersist_to_workspace
する- ここでzip圧縮をしないのは
lambci/lambda:ruby2.5
にはzipがインストールされていない&権限の関係でインストールもできないため
- ここでzip圧縮をしないのは
- 後続のジョブで
attach_workspace
してインストール済のライブラリを受け取り、zip圧縮してSAMでデプロイする
という構成になるでしょう。
Serverless Frameworkを使う場合に比べたらジョブを分ける必要があって若干手間ですが、個人的にはギリギリ許せる範囲かなと思っています。
【9/4 0:00追記】sam build --use-containerを使う
sam build
に --use-container
があるという知り合い情報
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.
なるほどー
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.
トノコト
【2021/2/4 20:30追記】
Lambdaでコンテナ対応されたのでデフォルトのランタイムではなくコンテナを使うのがよさそうです。 *3
native extension問題と無縁な言語
Go言語はクロスコンパイルに対応しているのでlinux_amd64でビルドしていればどの環境でビルドしてもLambdaで動くバイナリが作れて楽です。(Javaもnative extension問題と無縁だと思うけど自信ない)
この手の言語で開発するのであればServerless FrameworkとAWS SAMのどっちで作ったとしてもまず困ることはないと思います。
8/12 01:20追記
ブコメレス
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分半前後)
2019/8/12 11:00追記
ブコメレス
そもそも例であげてるlambdaでmysqlって構成どうなのという気も。そこ主題じゃないか。
重箱の隅をつつくような指摘ありがとうございます。
一応補足しておくと、RDSやAuroraのMySQLの監視を行うスクリプトがpythonで、そいつをLambdaで動かしていたのです。
8/12 17:20追記
Apexは"This software is no longer being maintainted and should not be chosen for new projects."なので挙げるにしてもそこを書いた方が
あー、元々は半年以上前に会社のesaに書いてたやつなので、社外向けに書き直した時にリンク先のリポジトリは見てなかったです。追記しました。
2020/9/18追記
Cloud Functions版はこちらをどうぞ
*1:https://www.terraform.io/docs/providers/archive/d/archive_file.html
*2:むっちゃ頑張ればできるかもしれないけど未確認
*3:追記時点で試していないので解決するかどうか断言はできないですが