LambCIを利用したサーバーレスなデプロイ環境の構築
はじめに
こんにちは、中山です。
以前から気になっていたのですが、なかなか触る機会のなかった LambCI を使ってみたのでご紹介します。これは一言で言うと「AWS Lambdaを利用したデプロイシステム」です。GitHub上のリポジトリに対するpushを契機に、各種AWSリソースが実行されます。その中でLambdaを実行し、Lambda上でデプロイを実施することが可能です。
今までこういったシステムを構築しようとするとJenkinsかCircleCIなどのSaaSを利用することが多かったと思います。とても便利なプロダクト/サービスではあるのですが、以下のような問題をよく指摘されていると思います。
- Jenkinsの保守が大変
- SaaSは高い
何を優先するのかは個人/組織の環境によって異なると思いますが、上記問題点を考慮するのであればLambCIは1つの選択肢になるかもしれません。
LambCIのアーキテクチャ
READMEに書かれている概要をご覧になると全体像が把握できるので引用します。
- Receives notification from GitHub (via SNS)
- Looks up config in DynamoDB
- Clones git repo using a bundled git binary
- Looks up config files in repo
- Runs install and build cmds on Lambda (or starts ECS task)
- Updates Slack and GitHub statuses along the way (optionally SNS for email, etc)
- Uploads build logs/statuses to S3
要するにSNSをトリガーにしてLambdaを実行し、そのLambdaの中でコードをcloneしてきて各種シェルコマンドを実行している訳です。やっている処理自体は非常にシンプルな構成になっています。特徴として、全てAWSのマネージドサービスを利用しているため今流行のサーバレスアーキテクチャになっています。「何をもってサーバーレスと呼ぶのか」はいろいろと議論があるようですが、Lambdaを中心とした典型的なFaaSアーキテクチャ(≒サーバーレスアーキテクチャ)になっているようです。
こういった仕組みになっているため、Jenkinsのように自分でサーバのお守りをする必要はありません。さらに、利用した分だけの従量課金なのでお値段的にもお安いです。というか、Lambdaは無料利用枠が充実しているので余程デプロイをしていなければこの範囲内に収まるのではないでしょうか。
設定ファイルは一般的なCIサービス(CircleCIであれば circle.yml
) によく似た記法で記述可能なので、そういったサービスをすでに利用したことがある方ならすぐに理解できると思います。また、デプロイ結果をS3に出力する機能まであります(なかなか「趣のあるUI」ではありますが)。
一件いいこと尽くめのように聞こえますが、、、
やはり「社会は甘くない」のでいろいろと制限はあります。特にLambdaの制限が正直厳しいと思っています。こちらのドキュメントから引用します。
- No root access
- 5 min max build time
- Bring-your-own-binaries – Lambda has a limited selection of installed software
- 1.5GB max memory
- Linux only
Lambdaは小さなコードをパズルのピースのように関連させて動作させるという思想上、1つのLambda関数にはいろいろと制限が設けられています(2016年11月7日現在)。そのLambdaを中心としたLambCIにも各種制限が設けられているという訳です。また、当然ですがデプロイやテストに失敗したからといって調査目的でLambda内にSSHログインすることはできません。
ただし、アーキテクチャの説明で引用した文章中に 「(or starts ECS task)」と書かれているように、ECSを利用することもできるようです。ですが、まだ実験的な機能なのかこちらに書かれているようにドキュメントにはまとまっていません。この仕組みを利用すればよりできることが広がりそうです。今回は割愛しますが、別エントリでまとめたいと思います。
使ってみる
では、早速使ってみたいと思います。インストール方法はREADMEに丁寧にまとめられているので簡単に初められます。本エントリでは以下の作業はすでに完了しているものとして進めさせていただきます。
- GitHub tokenの取得
- Slack tokenの取得
サンプルコード
簡単に利用できるようにサンプルとなるTerraformのコードを用意しました。ご自由にお使いください。
サンプルコードの全体像は以下のとおりです。VPC上にEC2を1台構築するだけの非常にシンプルなものになっています。この構成をLambCIで構築してみます。
トップディレクトリにある .lambci.json
がLambCIの設定ファイルです(設定ファイル名はいろいろと指定できるようです)。
{ "cmd": "./bin/install.sh && ./bin/test.sh && ./bin/deploy.sh", "build": "false", "branches": { "master": "true", "/^release/(dev|stg|prd)$/": "true", "/(release/)?(dev|stg|prd)/?": "true" } }
利用している設定についてその意味を以下にまとめます。より詳細な設定については先程のリンクを参照してください。
設定 | 意味 | 今回の設定 |
---|---|---|
cmd |
Lambda上で実行するシェルコマンド。 | シェルスクリプトにコマンドをまとめて && でつなげて実行。注意点として、シェルスクリプトは実行権限が必要。 |
build |
デフォルトでビルド( cmd の実行)するか。 |
下の設定と組み合わせて特定のブランチでのみビルドを実施。 |
branches |
ビルドするブランチを指定。正規表現が利用可能。 | ビルドするブランチ( true )とそうでないブランチ( false )を指定。 |
bin
ディレクトリ以下にLambda上で実行するシェルスクリプトを配置しています。それぞれ以下のとおりです。
bin/install.sh
#!/usr/bin/env bash TF_VER="0.7.9" BIN_PATH="${HOME}/.local/bin" [[ ! -d "$BIN_PATH" ]] && mkdir -p "$BIN_PATH" curl "https://releases.hashicorp.com/terraform/${TF_VER}/terraform_${TF_VER}_linux_amd64.zip" \ -o "${HOME}/terraform.zip" unzip "${HOME}/terraform.zip" -d "$BIN_PATH"
Terraformのバイナリをインストールして展開しています。LambCIはデフォルトで /tmp/lambci/home
がホームディレクトリになります。 /tmp/lambci/home/.local/bin
にパスが通っているのでそこに展開したバイナリファイルを配置しています。
余談ですが、LambCIではLambdaで各種コマンドを実行する都合上、Lambdaにどういったコマンドが入っているのかなどを確認したい場合が多くなると思います。その場合、以下のエントリを参照していただくとREPL形式でコマンドを実行できるので便利かと思います。
- 【小ネタ】簡単にLambda関数のコンテナ内でコマンドを実行する方法
-
bin/test.sh
#!/usr/bin/env bash if [[ "$LAMBCI_BRANCH" =~ (release/)?dev/? ]]; then make remote-enable ENV="dev" make terraform ENV="dev" ARGS="get -update" make terraform ENV="dev" ARGS="plan" elif [[ "$LAMBCI_BRANCH" =~ (release/)?stg/? ]]; then make remote-enable ENV="stg" make terraform ENV="stg" ARGS="get -update" make terraform ENV="stg" ARGS="plan" elif [[ "$LAMBCI_BRANCH" =~ (release/)?prd/?|^master$ ]]; then make remote-enable ENV="prd" make terraform ENV="prd" ARGS="get -update" make terraform ENV="prd" ARGS="plan" fi
正規表現で特定のブランチにマッチした場合、 plan
でテストを実行しています。LambCIは LAMBCI_BRANCH
という環境変数にpushしたブランチ名を保存しておいてくれるのでそれを利用しています。その他にもいろいろと環境変数を設定してくれるようです。 env
コマンドの実行結果を貼り付けておきます。
LAMBCI=true SHELL=/bin/bash TERM=xterm-256color AWS_SESSION_TOKEN=************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************ SLACK_TOKEN=****************************************** MOCHA_COLORS=true LD_LIBRARY_PATH=/tmp/lambci/home/usr/lib64:/usr/local/lib64/node-v4.3.x/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib LAMBCI_CLONE_REPO=knakayama/lambci-tf-demo AWS_REQUEST_ID=************************************ PATH=/tmp/lambci/home/.local/bin:/tmp/lambci/home/usr/bin:/var/task/vendor/python/bin:/var/task/node_modules/.bin:/usr/local/lib64/node-v4.3.x/bin:/usr/local/bin:/usr/bin/:/bin _=/usr/bin/env LAMBCI_PULL_REQUEST= PWD=/tmp/lambci/build/knakayama/lambci-tf-demo LAMBCI_CHECKOUT_BRANCH=test/dev AWS_SECRET_ACCESS_KEY=**************************************** NODE_PATH=/var/runtime:/var/task:/var/runtime/node_modules AWS_REGION=us-east-1 GIT_TEMPLATE_DIR=/tmp/lambci/home/usr/share/git-core/templates FORCE_COLOR=true LAMBCI_BRANCH=test/dev AWS_ACCESS_KEY_ID=******************** HOME=/tmp/lambci/home SHLVL=2 CI=true LAMBCI_COMMIT=6c37bbdc4f24b36583cfbf30ae7bb7bf0ac692b1 PYTHONPATH=/var/task/vendor/python/lib/python2.7/site-packages NPM_CONFIG_COLOR=always GITHUB_TOKEN=**************************************** LAMBCI_BUILD_NUM=13 LAMBCI_REPO=knakayama/lambci-tf-demo GIT_EXEC_PATH=/tmp/lambci/home/usr/libexec/git-core
bin/deploy.sh
#!/usr/bin/env bash if [[ "$LAMBCI_BRANCH" =~ ^release/(dev|stg|prd)$ ]]; then _env="$(echo "$LAMBCI_BRANCH" | grep -oE '(dev|stg|prd)$')" make terraform ENV="$_env" ARGS="get -update" make terraform ENV="$_env" ARGS="apply" make terraform ENV="$_env" ARGS="remote push" fi
こちらも同様特定ブランチにpushされた場合に apply
しています。
1. リポジトリのfork
手っ取り早く動作確認するために先程のリポジトリをforkしておいてください。また、Terraformのリモートステートという機能を利用しているので、stateファイルを保存しておくバケットを作成しておいてください。
$ aws s3 mb s3://<_S3_BUCKET_>
バケット名が Makefile
に書かれているのでそれも修正する必要があります。
--- Makefile 2016-11-06 15:20:34.000000000 +0900 +++ Makefile.orig 2016-11-06 10:32:06.000000000 +0900 @@ -1,4 +1,4 @@ -BUCKET_NAME = <_S3_BUCKET_> +BUCKET_NAME = lambci-tf-demo REGION = ap-northeast-1 CD = [ -d env/${ENV} ] && cd env/${ENV} ENV = $1
2. LambCI CloudFormationスタックの作成
AWSのマネジメントコンソールにログインした状態でこちらのリンクをクリックしてください。スタックの作成画面が開くので、以下のようにパラメータを埋めてください(スタックのリージョンは現時点で北部バージニア固定のようです)。
スタックの中のLambda-backed custom resourceにより、パラメータで指定したリポジトリに対してSNSとのIntegrationが自動で実施されます。スタック作成後以下のようにIntegrationが設定されていることを確認してください。
これでLambCIの実行環境は作成完了です。簡単ですね。
3. LambdaのIAM Roleを変更
こちらに付いてはTerraformを利用しているために必要な作業です。TerraformでさまざまなAWSリソースを作成するためにそれなりの権限、というか実質Administrator権限をLambdaのIAM Roleに付与する必要があります。必要な権限のみ付与すべきという原則からすると、正直微妙ですね。。。こちらに付いてはよりよい方法が見つかったら追記しておきます。
4. テストの実行
forkしたリポジトリからコードをcloneした後、まずはテストが実行されるのか確認してみたいと思います。テスト用ブランチ(例えば test/dev
)にcheckout後、空コミットでpushしてみます。
$ git checkout -b test/dev $ git commit --allow-empty -m 'test' $ git push origin test/dev
するとSlackのNotificationが以下のように飛んで来ます。
リンクをクリックするとS3上に保存されたデプロイ結果を確認できます。
5. デプロイ
テストが上手くいったようなので実際にデプロイしてみます。リリースブランチ(例えば release/dev
) にcheckoutして空コミットしてみます。
$ git checkout -b release/dev $ git commit --allow-empty -m 'release test' $ git push origin release/dev
先程と同じようにSlackの通知が届くのでS3上でデプロイ結果を確認して、以下のように表示されればOKです。
使ってみた感想
- アーキテクチャがシンプルなのはとても良い
- やろうと思えば簡単にカスタマイズできる
- サーバの管理しなくてよいのも嬉しい
- 安い
- Lambdaの制限が痛い
- 実行時間5分&ソフトウェアのインストール制限がキツイ
- まだproduction readyとは言い難い
- 個人ユースで使う分にはいいと思う
- GitHub Enterpriseで使えるようにして欲しい
- イシューには上がっている模様
- Lambda on VPCに対応するといろいろおもしろいことできるかも
まとめ
いかがだったでしょうか。
まだまだ物足りない機能もあるようですが、サーバーレスでデプロイ環境を作れるというのは魅力的だと思います。使い始めたばかりなのでもう少し使ってから再度エントリにまとめていきたいと思います。今後も追いかけていきたいプロダクトの1つになりそうです。
本エントリがみなさんの参考になれば幸いです。