前置き
AWS LambdaはEC2インスタンス無しでイベント駆動で必要な時に必要なだけコードを実行でき、課金も立ち上げているだけで料金がかかるEC2とは違い、完全なる使った分だけ課金という非常に強力で魅力的なサービスです。
しかし、このサービスはその軽量でシンプルなコンセプトとは裏腹に従来無かった独自の概念が多いためか、面倒なことや悩ましいことが多いと感じています。
だからこそ、もっと簡単かつ実用的に使いたいという想いからLamveryというツールを作っている訳なのですが、その中でも実用面で大きな課題となると感じていたデプロイの部分において、現時点で自分の中でのベストプラクティスと言って差し支えないフローを組むことができたので紹介したいと思います。
Lambda functionのdeployにおける3つの課題
①デプロイパッケージの作成
-
Node.js
npm install
してnode_modules
ごとzipにまとめれば良いので割と簡単
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-create-deployment-pkg.html -
Java
Maven
とかGradle
とか割りとベーシックな感じ
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-java-how-to-create-deployment-package.html -
Python
割りと残念な手順(Virtualenv で作成した Python 環境を使用してデプロイパッケージを作成するの章)
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html
②バージョン管理
- Lambdaの特殊なバージョニング仕様
http://dev.classmethod.jp/cloud/aws/lambda-versioning/ - Git等のbranch運用とLambda環境の関連付け(stagingとproduction等)
③デプロイフローの整備
ベストプラクティスはまだ無いというか、ほとんどこの辺の話を見かけたことがない。1
今回の方法によって解決するもの
①Pythonデプロイパッケージの作成(Node.jsもいけるはず)
まず、Lamvery自体がvirtaulenv内で簡単にデプロイパッケージを作り、それをデプロイするための機能を持っています。なので、CircleCIのPython環境でクリーンなvirutalenv環境を作り、その中で必要なライブラリをインストールしてデプロイするだけです。
単純にカレントディレクトリ以下をzipにまとめてデプロイすることもできるので、Node.jsもpackage.json
をリポジトリに含めてnpm install
を叩かせれば同じようにいけるはずです。2
②Lambdaの特殊なバージョニング仕様を活かしたGit branchとの紐付け
Lambdaのバージョンに付けられるエイリアスを活用し、エイリアスを個々に指定してfunctionの実行が可能である特性を活かして、branchと紐付けた個別の実行環境を提供します。
③Pull Requestベースのデプロイフロー
いわゆるこういうやつ → http://d.hatena.ne.jp/naoya/20140502/1399027655
②ができれば、そりゃできるよねって話ではあるんですが。
方法と手順
ちなみに今回の内容を実際に検証したものがこちらになります。
https://github.com/marcy-terui/lamvery-circleci-deploy
CircleCIで該当のリポジトリを有効にする所は通常と同じなので割愛します。
1. IAM設定
Lambda functionに割り当てるIAM Roleはもちろんですが、CircleCIに設定するIAM Userが必要になります。
Lambda functionに割り当てるIAM Roleのポリシー
これは、そのfunctionに何をさせたいかによりけりなのですが、最低限の権限とLamveryの固有機能であるKMSを利用した機密情報の受け渡しを行う場合は以下の様な権限となります。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": [
"arn:aws:kms:<region>:<account-number>:key/<key-id>"
]
}
]
}
<region>
,<account-number>
,<key-id>
は適宜置き換えてください。
KMSのキーの作り方(key-idの発行)についてはこちら↓
https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/create-keys.html
このRoleは以降の章で使用するので、ARN3を控えておきましょう。
CircleCIに設定するIAM Userのポリシー
ここはあまり変動はないと思います。
一々設定するのが面倒ならlambda:*
は全リソース許可("Resource": "*"
)でもまあ場合によってはアリかなとは思いますが、iam:PassRole
は全リソース許可すると大変危険なので止めたほうが良いです。4
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:*",
"iam:PassRole"
],
"Resource": [
"arn:aws:lambda:<region>:<account-number>:function:<function-name>",
"arn:aws:lambda:<region>:<account-number>:function:<function-name>:*",
"<function-role-arn>"
]
},
{
"Effect": "Allow",
"Action": [
"lambda:CreateFunction",
"lambda:ListFunctions",
"lambda:ListVersionsByFunction"
],
"Resource": [
"*"
]
}
]
}
<region>
,<account-number>
,<function-name>
は適宜置き換えてください。
<function-role-arn>
には先ほど作成したRoleのARN3が入ります。
このUserのCredentialを次の章で使用しますので、控えておきましょう。
IAM UserのCredentialを取得し、CircleCIへ設定
2. Lamveryのインストールと設定
Lamveryをインストールして設定ファイルを生成
$ pip install lamvery
$ lamvery init
lamvery: Output initial file: .lamvery.yml
lamvery: Output initial file: .lamvery.exclude.yml
lamvery: Output initial file: .lamvery.event.yml
lamvery: Output initial file: .lamvery.secret.yml
設定ファイルを編集する
region
及びconfiguration
以下の各設定は適宜置き換えてください。
{{ env['AWS_LAMBDA_ROLE'] }}
の部分はPrivateリポジトリならRoleのARNくらいはリポジトリに入ってても問題ないかなという気はします。
同じように環境変数から渡したい場合はCircleCI上にて「Project Settings」→「Environment Variables」で設定できます。
profile: null
region: us-east-1
versioning: false
default_alias: master
configuration:
name: lamvery-deploy-sample
runtime: python2.7
role: {{ env['AWS_LAMBDA_ROLE'] }}
handler: lambda_function.lambda_handler
description: This is a sample lambda function.
timeout: 10
memory_size: 128
設定のポイント
-
versioning: false
デフォルトではLambdaのバージョニングを無効にして、必要に応じてコマンドオプションで有効化します。 -
default_alias: master
コマンドオプションで指定しない場合にデプロイ時に付けるエイリアスです。master
じゃなくても良いのですが、オペミス防止の為production用のエイリアス(後述)は避けましょう。
Functionを実装し、必要なライブラリを列挙する
functionの実装はただのPythonコーディングなので割愛します。上記の例ではhandler
がlambda_function.lambda_handler
のとなっているので、以下の様なファイルになります。
import lamvery
def lambda_handler(event, context):
print(lamvery.secret.get('foo'))
Pythonプロジェクトではrequirements.txt
に書くことが多いので、今回はそのようにしています。virtualenv内で以下のように必要なライブラリをインストールした上でファイルに書き出すという方法がおすすめです。
pip install flake8
pip freeze > requirements.txt
こうすることでCircleCI上で以下を実行することで同じライブラリがインストールできます。
pip install -r requirements.txt
CircleCIの設定を記述
以下のように記述します。
---
machine:
python:
version: 2.7
dependencies:
pre:
- |
virtualenv .venv
source .venv/bin/activate
pip install -r requirements.txt
test:
override:
- |
source .venv/bin/activate
flake8 lambda_function.py
deployment:
master-head:
branch: master
commands:
- |
source .venv/bin/activate
lamvery deploy
staging:
branch: staging
commands:
- |
source .venv/bin/activate
lamvery deploy -a staging -p
production:
branch: production
commands:
- |
source .venv/bin/activate
lamvery deploy -a production -p
設定のポイント
-
dependencies.pre
でクリーンなvirtaulenv環境を作り、その中で必要なライブラリをインストール
Lamveryもこの中に入っています(機密情報の受け渡しを行わない場合はFunction自体にLamveryは必要ないので、別途インストールしても良いと思います) -
master
ブランチではデプロイ時のコマンドオプション無し
オプション無しの場合、versioning: false
,default_alias: master
となっているため、常に$LATEST
というバージョニングが無効な場合に使われる特殊なバージョン5にmaster
というエイリアスが付くことになります。
これにより、Lambdaには容量制限があるためmasterブランチの更新で常に新しいバージョンが発行されるのは避けたいが、HEADは常に実行可能という状態にできます。 -
staging
及びproduction
では明示的にエイリアス名とバージョニングを有効にするオプションを付ける
-a
でエイリアス名指定、-p
でバージョニングを有効にできます。
Let's deploy!
プルリクエストを作ってマージしてみます。
デプロイ結果を見てみましょう。
色付きのログを見ると、4
という新しいバージョンが発行されてそこにproduction
のエイリアスが付け替えられているのがなんとなくお分かりいただけるかと思います。
ちなみに併せて production-pre
というエイリアスが設定されているかと思いますが、これはロールバック用で、以下のように緊急時のロールバックができます。手元で叩いても良いですし、ChatBotなんかに叩かせても良いと思います。
$ lamvery rollback -a production
lamvery: [Function] Previous version: 2
lamvery: [Alias] production: 4 -> 2
デプロイされたfunctionの実行について
各種Event等から実行する場合、エイリアス毎に個別のARN3がついているので注意しましょう。
以下の様な指定になります。APIの場合、Qualifier
というパラメータでエイリアスを指定する場合もあります。
arn:aws:lambda:<region>:<account-number>:function:<function-name>:<alias>
まとめ
Lamveryというツールを作る目的の一つであった、自分の中で「これだ!」と思えるLambda functionのデプロイフローができたのでご紹介させてもらいました。
今までEC2等で行われていた手法と大きな違いがなく、違和感なく使えるフローを意識していますが、もっとこうした方が良い等ご意見ありましたら、是非以下からフィードバックいただけると嬉しいです。
TODO: CircleCIの手前までを自動化するCloudFormationテンプレートの作成