Terraform v0.8.6とCodeBuildを利用したServerless Frameworkのデプロイ
はじめに
こんにちは、中山です。
次期Terraformのリリースバージョンであるv0.8.6でAWS CodeBuild用リソースがマージされました。今回導入されたリソースは以下の通りです。
リソース名 | 用途 |
---|---|
aws_codebuild_project |
CodeBuildプロジェクトの作成 |
早速使ってみたので本エントリでまとめたいと思います。サンプルとなるコードをGitHubに作成しました。ご自由にお使いください。詳細な内容は後述しますが、Serverless Frameworkで作成したサンプルアプリをCodeBuildでビルド/デプロイするといった内容です。
執筆時点(2017/02/04)ではまだv0.8.6はリリースされていないため、自分でTerraformのバイナリをコンパイルしておいてください。このPRが取り込まれたコミットハッシュ値は「04102574535d9e3d94d60fbac8686fe97f4e4ca8」です。
CodeBuild関連リソースの引数
新規に導入されたCodeBuild関連リソースで設定可能な各種引数は以下の通りです。現状ドキュメントが用意されていないようなので、ソースコードにもとづいています。基本的にCloudFormationのAWS::CodeBuild::Projectリソースとほぼ同じです。このリソースについては以下のエントリでまとめているので参考にしていただければと思います。
- CloudFromationのアップデートでCodeBuildに対応しました #reinvent
-
aws_codebuild_project
設定 | 意味 | 必須の有無 | 備考 |
---|---|---|---|
artifacts |
プロジェクトで生成したアーティファクトの設定 | Yes | 詳細は下記参照 |
description |
プロジェクトの説明 | No | |
encryption_key |
アーティファクトを暗号化するためのKMSのARNまたはエイリアス | No | |
environment |
ビルド環境の設定 | Yes | 詳細は下記参照 |
name |
プロジェクト名 | Yes | |
service_role |
プロジェクトに関連付けるIAM Role | No | |
source |
プロジェクトでビルドするソースコードの設定 | Yes | 詳細は下記参照 |
timeout |
ビルドのタイムアウト時間(分) | No | |
tags |
プロジェクトに関連付けるタグ | No |
artifacts
で指定可能な引数は以下の通りです。
設定 | 意味 | 必須の有無 | 備考 |
---|---|---|---|
name |
アーティファクトを設置するディレクトリ | No | |
location |
アーティファクトを設置する場所 | No | |
namespace_type |
アーティファクトの設置場所(パス)に加える情報 | No | |
packaging |
アーティファクトのパッケージ方式 | No | Zipで固めるかどうか(デフォルトは何もしない) |
path |
name で指定した場所に加えるパス |
No | |
type |
アーティファクトのタイプ | Yes |
environment
で指定可能な引数は以下の通りです。
設定 | 意味 | 必須の有無 | 備考 |
---|---|---|---|
compute_type |
ビルド環境のスペック | Yes | |
environment_variable |
ビルド環境で利用する環境変数 | No | Key/Value形式で指定 |
image |
ビルド環境のコンテナイメージ | Yes | |
type |
ビルド環境のタイプ | Yes | 現状 LINUX_CONTAINER のみ指定可能 |
source
で指定可能な引数は以下の通りです。
設定 | 意味 | 必須の有無 | 備考 |
---|---|---|---|
auth |
CodeBuildがソースコードにアクセスする際の認証情報 | No | type (現状 OAUTH のみ)と resource で指定 |
buildspec |
buildspec.yml の場所 |
No | |
location |
リポジトリの場所 | No | |
type |
リポジトリのタイプ | Yes |
使ってみる
早速使ってみましょう。最終的に以下のような構成になります。
コード
以下で主要なコードを解説していきます。
tf-codebuild-demo/codebuild.tf
resource "aws_codebuild_project" "codebuild" { name = "${var.env}-project" service_role = "${aws_iam_role.codebuild.arn}" timeout = 60 artifacts { type = "S3" location = "${random_id.s3.hex}" } environment { compute_type = "BUILD_GENERAL1_SMALL" image = "aws/codebuild/nodejs:7.0.0" type = "LINUX_CONTAINER" environment_variable = { "name" = "S3" "value" = "${random_id.s3.hex}" } environment_variable = { "name" = "STAGE" "value" = "${var.codebuild_config["stage"]}" } } source { type = "GITHUB" location = "${var.codebuild_config["source_location"]}" } tags { "Environment" = "${var.env}-project" } }
Serverless FrameworkはNode.js製なので、コンテナイメージには aws/codebuild/nodejs:7.0.0
を利用しています。ビルド環境に指定可能なコンテナイメージの一覧は以下のコマンドで確認できます。
$ aws codebuild list-curated-environment-images \ --region us-east-1 { "platforms": [ { "languages": [ { "images": [ { "name": "aws/codebuild/eb-java-7-amazonlinux-64:2.1.3", "description": "AWS ElasticBeanstalk - Java 7 Running on Amazon Linux 64bit v2.1.3" }, <snip>
tf-codebuild-demo/iam.tf
data "aws_iam_policy_document" "codebuild_sts" { statement { sid = "CodeBuildAssumeRolePolicy" effect = "Allow" actions = ["sts:AssumeRole"] principals = { type = "Service" identifiers = ["codebuild.amazonaws.com"] } } } resource "aws_iam_role" "codebuild" { name = "${var.env}-codebuild-role" assume_role_policy = "${data.aws_iam_policy_document.codebuild_sts.json}" } data "aws_iam_policy_document" "codebuild_policy" { statement { sid = "CodeBuildCFnPolicy" effect = "Allow" resources = ["*"] actions = ["cloudformation:*"] } } resource "aws_iam_role_policy" "codebuild" { name = "${var.env}-codebuild-policy" role = "${aws_iam_role.codebuild.id}" policy = "${data.aws_iam_policy_document.codebuild_policy.json}" } resource "aws_iam_policy_attachment" "codebuild_lambda" { name = "${var.env}-AWSLambdaFullAccess" policy_arn = "arn:aws:iam::aws:policy/AWSLambdaFullAccess" roles = ["${aws_iam_role.codebuild.name}"] } resource "aws_iam_policy_attachment" "codebuild_api_gateway" { name = "${var.env}-AmazonAPIGatewayAdministrator" policy_arn = "arn:aws:iam::aws:policy/AmazonAPIGatewayAdministrator" roles = ["${aws_iam_role.codebuild.name}"] } resource "aws_iam_policy_attachment" "codebuild_iam" { name = "${var.env}-AmazonAPIGatewayAdministrator" policy_arn = "arn:aws:iam::aws:policy/IAMFullAccess" roles = ["${aws_iam_role.codebuild.name}"] }
CodeBuildに関連付けるIAM Roleを設定しています。CodeBuildのコンテナ上でServerless Frameworkを実行させる必要があるので、それなりの権限が必要です。実質的にAdmin権限が必要なのですが、もう少し絞りたいなと思ったのでこねくり回しています。Serverless Frameworkを実行するホスト上で必要な権限についてはこちらのIssueでやり取りされているようですね。まだベストプラクティスのようなものはなさそうなので、今回は上記設定にしました。インラインで細かくポリシーを指定するのはシンドいので、マネージドポリシーに倒す方向にしています。
serverless-sample-app/buildspec.yml
version: 0.1 phases: install: commands: - npm install -g serverless pre_build: commands: - aws configure set s3.signature_version s3v4 - aws s3 sync s3://$S3/${CODEBUILD_BUILD_ID%:*} .serverless build: commands: - sls deploy -s $STAGE artifacts: files: - .serverless/* discard-paths: yes
2017年02月08日追記
.serverlessディレクトリ内に、CloudFormationテンプレートがなくても同じスタックに対してアップデートをしてくれるようです。そのため、必ずしもS3とのsyncが必要という訳ではありません。
ビルド時に実行するコマンドを定義しています。ポイントは以下の点です。
Serverless Frameworkは.serverlessディレクトリ上のCloudFormationテンプレートを元にスタックの作製/アップデートをするのでpre_build
フェーズでS3からテンプレートをsyncしている- CodeBuildは
CODEBUILD_
という接頭辞を付けた各種環境変数を定義してくれているので、今回の例であればCODEBUILD_BUILD_ID
からプロジェクト名を取得している- 定義済み環境変数一覧はドキュメントに記載されてない?ようなので
env
で確認するといいかも
- 定義済み環境変数一覧はドキュメントに記載されてない?ようなので
build
フェーズでテンプレートのアップデート後、アーティファクト(Serverless Frameworkが生成したCloudFormationテンプレートなど)をS3にアップロードしている- 基本的にCloudFormationテンプレートだけ必要なのでServerless Frameworkが生成したデプロイメントパッケージは別にいらないかも
- ソースコードさえあれば
sls deploy -n
で生成できるので - ただ、デバッグ目的であった方がいいと思う
- テンプレートは上書き保存した方が管理しやすいので、S3のパスは常に同じになるようにしている
「Serverless Framework CodeBuild」などでググると、 buildspec.yml
の設定方法がいろいろと出てきます。 sls deploy
する前にテストなどを実行したいといった要件がある場合は以下のリンクが参考になりそうです。
- CodePipeline+CodeBuildで Serverless Frameworkのデプロイを楽にする
- AWS CodeBuildでサーバーレス環境をビルドしてみる
- CodePipelineでServerless Frameworkのデプロイを管理する
もし手元でも動作確認したい場合は、以下のようにS3からテンプレートを取得すればOKです。
# リポジトリへ移動 $ cd path/to/repo # sync $ aws s3 sync s3://<_YOUR_S3_BUCKET_>/<_YOUR_PROJECT_NAME_> .serverless # 確認 $ sls info -v
動作確認
いつものように plan
/ apply
後、AWS CLIでビルドを開始して意図した動作をするのか確認してみます。なお、そのままビルドを実行すると恐らく失敗すると思うので、下に記載している「利用する上での注意点」を参考にしてください。
- プロジェクトが生成されたことを確認
$ aws codebuild list-projects \ --region us-east-1 { "projects": [ "tf-codebuild-demo-project" ] }
- プロジェクトの内容が意図したものになっていることを確認
$ aws codebuild batch-get-projects \ --names "$(aws codebuild list-projects \ --region us-east-1 \ --query 'projects' \ --output text)" \ --region us-east-1 { "projectsNotFound": [], "projects": [ { "name": "tf-codebuild-demo-project", "serviceRole": "arn:aws:iam::************:role/tf-codebuild-demo-codebuild-role", "tags": [ { "value": "tf-codebuild-demo-project", "key": "Environment" } ], "artifacts": { "namespaceType": "NONE", "packaging": "NONE", "type": "S3", "location": "bcd1d3a6fa6d1004", "name": "tf-codebuild-demo-project" }, "lastModified": 1486275186.92, "timeoutInMinutes": 60, "created": 1486275122.2, "environment": { "computeType": "BUILD_GENERAL1_SMALL", "image": "aws/codebuild/nodejs:7.0.0", "type": "LINUX_CONTAINER", "environmentVariables": [ { "name": "S3", "value": "bcd1d3a6fa6d1004" }, { "name": "STAGE", "value": "dev" } ] }, "source": { "buildspec": "", "type": "GITHUB", "location": "https://github.com/knakayama/serverless-sample-app.git", "auth": { "type": "OAUTH" } }, "encryptionKey": "arn:aws:kms:us-east-1:************:alias/aws/s3", "arn": "arn:aws:codebuild:us-east-1:************:project/tf-codebuild-demo-project" } ] }
- ビルドの実行
$ aws codebuild start-build \ --project-name "$(aws codebuild list-projects \ --region us-east-1 \ --query 'projects' \ --output text)" \ --region us-east-1 { "build": { "buildComplete": false, "initiator": "dev", "artifacts": { "location": "arn:aws:s3:::bcd1d3a6fa6d1004/tf-codebuild-demo-project" }, "projectName": "tf-codebuild-demo-project", "timeoutInMinutes": 60, "buildStatus": "IN_PROGRESS", "environment": { "computeType": "BUILD_GENERAL1_SMALL", "image": "aws/codebuild/nodejs:7.0.0", "type": "LINUX_CONTAINER", "environmentVariables": [ { "name": "S3", "value": "bcd1d3a6fa6d1004" }, { "name": "STAGE", "value": "dev" } ] }, "source": { "buildspec": "", "type": "GITHUB", "location": "https://github.com/knakayama/serverless-sample-app.git", "auth": { "type": "OAUTH" } }, "currentPhase": "SUBMITTED", "startTime": 1486275509.793, "id": "tf-codebuild-demo-project:************************************", "arn": "arn:aws:codebuild:us-east-1:************:build/tf-codebuild-demo-project:************************************" } }
- ビルド結果の確認
$ aws codebuild batch-get-builds \ --ids <_YOUR_BUILD_ID_> \ --region us-east-1 { "buildsNotFound": [], "builds": [ { "buildComplete": true, "phases": [ { "phaseStatus": "SUCCEEDED", "endTime": 1486275517.655, "phaseType": "SUBMITTED", "durationInSeconds": 7, "startTime": 1486275509.793 }, { "contexts": [], "phaseType": "PROVISIONING", "phaseStatus": "SUCCEEDED", "durationInSeconds": 65, "startTime": 1486275517.655, "endTime": 1486275583.106 }, { "contexts": [], "phaseType": "DOWNLOAD_SOURCE", "phaseStatus": "SUCCEEDED", "durationInSeconds": 9, "startTime": 1486275583.106, "endTime": 1486275592.316 }, { "contexts": [], "phaseType": "INSTALL", "phaseStatus": "SUCCEEDED", "durationInSeconds": 22, "startTime": 1486275592.316, "endTime": 1486275614.401 }, { "contexts": [], "phaseType": "PRE_BUILD", "phaseStatus": "SUCCEEDED", "durationInSeconds": 15, "startTime": 1486275614.401, "endTime": 1486275630.006 }, { "contexts": [], "phaseType": "BUILD", "phaseStatus": "SUCCEEDED", "durationInSeconds": 29, "startTime": 1486275630.006, "endTime": 1486275659.743 }, { "contexts": [], "phaseType": "POST_BUILD", "phaseStatus": "SUCCEEDED", "durationInSeconds": 0, "startTime": 1486275659.743, "endTime": 1486275659.791 }, { "contexts": [], "phaseType": "UPLOAD_ARTIFACTS", "phaseStatus": "SUCCEEDED", "durationInSeconds": 0, "startTime": 1486275659.791, "endTime": 1486275660.485 }, { "contexts": [], "phaseType": "FINALIZING", "phaseStatus": "SUCCEEDED", "durationInSeconds": 3, "startTime": 1486275660.485, "endTime": 1486275663.761 }, { "phaseType": "COMPLETED", "startTime": 1486275663.761 } ], "logs": { "groupName": "****************************************", "deepLink": "https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#***************************************************************************************************", "streamName": "************************************" }, "artifacts": { "location": "arn:aws:s3:::bcd1d3a6fa6d1004/tf-codebuild-demo-project" }, "projectName": "tf-codebuild-demo-project", "timeoutInMinutes": 60, "initiator": "dev", "buildStatus": "SUCCEEDED", "environment": { "computeType": "BUILD_GENERAL1_SMALL", "image": "aws/codebuild/nodejs:7.0.0", "type": "LINUX_CONTAINER", "environmentVariables": [ { "name": "S3", "value": "bcd1d3a6fa6d1004" }, { "name": "STAGE", "value": "dev" } ] }, "source": { "buildspec": "", "type": "GITHUB", "location": "https://github.com/knakayama/serverless-sample-app.git", "auth": { "type": "OAUTH" } }, "currentPhase": "COMPLETED", "startTime": 1486275509.793, "endTime": 1486275663.761, "id": "tf-codebuild-demo-project:************************************", "arn": "arn:aws:codebuild:us-east-1:************:build/tf-codebuild-demo-project:************************************" } ] }
CloudFormationのアウトプットからAPI GatewayのURLを確認後、アクセスした際に以下のような出力が表示されたらOKです。
$ curl https://8ylep8hmwg.execute-api.us-east-1.amazonaws.com/dev -w '\n' {"message": "Hello World at v0.1!"}
サンプルアプリのコードを修正後、再度ビルドしてみるとちゃんとアップデートされていることが確認できます。
$ curl https://8ylep8hmwg.execute-api.us-east-1.amazonaws.com/dev -w '\n' {"message": "Hello World at v0.2!"}
利用する上での注意点
やはりまだ導入されてから日が経ってないという事情もあるので、いつくか利用する上で注意した方がよい点が散見されます。余裕があったらPR出して修正していきたいと思います。ただし、厳密に検証した訳ではなく、私の環境ではこういった事象が起きたという内容である点はご了承ください。
アップデート処理が微妙
新規リソースが追加された場合大抵このパターンになる傾向があるのですが、アップデート処理があまり安定していません。例えば、環境変数を追加しても「No changes. Infrastructure is up-to-date.」と言われてしまいます。
ソースリポジトリの認証処理が微妙
GitHubのリポジトリからソースを取得する際に、 auth
で OAUTH
を指定する必要があります。この設定をしないと DOWNLOAD_SOURCE
のフェーズで以下のように処理に失敗します。
ただTerraformでこの設定をするとクラッシュします。。。しょうがないのでTerraformでプロジェクト作成後、マネジメントコンソールから手動でリコネクトさせています。しかし、この操作をした後に refresh
などを実行すると再びクラッシュしてしまうので、Terraformの管理下から外さないといけなくなるのですが。。。以下のコマンドでリソースを削除可能です。
$ $GOPATH/bin/terraform state rm aws_codebuild_project.codebuild Item removal successful.
試してないですが、tfstateファイルを直接修正すればマネジメントコンソール上での変更に追従できるかもしれません。
まとめ
いかがだったでしょうか。
TerraformとCodeBuildを利用したServerless Frameworkのデプロイ方法をご紹介しました。一度コードを書いておけばこれらサービスを素早く立ち上げられるというのは便利だと思います。今回は利用しなかったのですが、CodePipelineを設定すればリポジトリへのpushを契機としたデプロイフローの定義が可能です。TerraformもCodePipelineに対応してほしいですね。もちろんaws_cloudformation_stackを利用する手もありますけど。
本エントリがみなさんの参考になれば幸いに思います。