はじめに
Lambda関数をはじめとするAWSリソースの設置/更新はAWSコンソールから直接操作もできる。
とはいえ、事故リスクを減らすためにもできる限り自動化しておいたほうが安心安全。
ワンボタンでgithubからコード取得 〜 CloudFormationでLambdaと
API Gatewayを自動更新する環境をつくる手順をまとめる。
サンプルコード↓
AWS-SAM + CloudFormation + CodeBuildを使うメリット
- AWSコンソールから各種リソースの設定を意図せずに変更するリスクを潰せる
- AWSリソースに関する設定変更の履歴を全てgitで管理できる
- Jenkinsなど別途環境を用意しなくても、AWSコンソール内でディプロイ作業を完結できる
- 更新作業を簡易化 & 効率化して、ディプロイ頻度を上げられる
- CloudFormation自体は無料(設置したリソースは課金される)、CodeBuildは100分/月までジョブ実行無料
やること
- AWS-SAMのテンプレートを用意して、CloudFormationで自動ディプロイする
- CodeBuildでディプロイジョブを用意して、ワンボタンでCloudFormationを呼べるようにする
- 必要なIAMの権限を整理する
- 設置したLambdaが動くことを確認する
前提
- ちゃんと動くLambda関数のコードが用意されていること
本ページではTypescriptでnodejsランタイムを利用するが、
Lambda関数の処理内容やTypescriptのトランスパイル手順については触れない。
また、ここで紹介する手順はPythonをはじめとする他のランタイムにも応用できる。
本ページ上部のサンプルコードも参考に。
もくじ
- ディプロイジョブの成果物を保管するS3 bucketを作成する
- Lambdaに紐付けるIAMロールを作成する
- AWS-SAMのテンプレートファイルを作成する
- CloudFormationを動かすshellスクリプトを作成する
- CodeBuildのビルドジョブを作成する
- IAMの権限を整理する
- ビルドジョブを実行して、CloudFormationを見守る
- 生成されたLambdaを実行してみる
1. ディプロイジョブの成果物を保管するS3 bucketを作成する
1. uni-cloudformation-artifact-dev
2. uni-codebuild-artifact
本ページでは上記の名称でS3 bucketを2つ新規作成する。
それぞれ名称の通り、CloudFormationとCodeBuildが走った際に生成される成果物を保存するのに使う。
S3 bucketを作成する手順(1-1を参照)↓
2. Lambdaに紐付けるIAMロールを作成する
新規設置するLambdaに紐付けるIAMロールを作成する。
(IAMロールの自動生成もたぶんできるが、個人的には事故を起こしそうで怖いため権限周りは手動でやる。)
IAMのコンソールを開いたら、Create role
をクリック。
Lambda関数の処理内容に応じて必要な権限を追加して次へ。
(本ページで設置するLambda関数はpingを返すだけなので、
なにも権限を付与しない空のIAMロールを作成する。)
作成するIAMロールの名称を設定して、完了。
ここではlambda-sam-deploy-ts-role
とした。
IAMロール作成完了したら、arnを控えておく(手順3で使う)。
3. AWS-SAMのテンプレートファイルを作成する
そもそもAWS-SAMとは↓
下記の通り、CloudFormationに読み込ませるテンプレートファイル(template.yaml)を用意する。
注意すべきはRole, CodeUri, Handler, Runtime
の4箇所。
Role
には手順2で作成したIAMロールのarnを書く。
CodeUri
とHandler
にはLambdaに配置する関数コードのファイルパスを書くが、
template.yamlを起点とする相対パスで書く点に注意。
下記のtemplate.yamlの場合、各ファイルの位置関係の例は下記の通り。
index.jsではhandler関数が定義されており、これがLambda関数として設置される想定。
ocean_lambda_sam_deploy_ts/
├ aws/ - sam/ - dev/ - template.yaml
└ built/ - dev/ - ping/ - index.js
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Timeout: 2
Api:
OpenApiVersion: 3.0.2
Resources:
UniLambdaBasicApi: # API Gatewayの設定
Type: AWS::Serverless::Api
Properties:
Name: ApigwSamDeployTypescript # 設置するリソース名(任意)
StageName: dev
GatewayResponses:
DEFAULT_4xx:
ResponseParameters:
Headers:
Access-Control-Expose-Headers: "'WWW-Authenticate'"
Access-Control-Allow-Origin: "'*'"
UniPingFunc: # Lambdaの設定
Type: AWS::Serverless::Function
Properties:
FunctionName: PingLambdaSamDeployTypescript # 設置するリソース名(任意)
Runtime: nodejs14.x # 利用するランタイム
Role: arn:aws:iam::${12桁のAWS-ID}:role/lambda-sam-deploy-ts-role # 手順2で作ったIAMロール
CodeUri: ../../../built/dev/ping # Lambda関数が置いてあるファイルパス
Handler: index.handler # Lambda関数の ${ファイル名.メソッド名}
MemorySize: 128
Events:
Vote:
Type: Api
Properties:
Path: /ping # Lambdaを実行するパス
Method: get
RestApiId: !Ref UniLambdaBasicApi # 紐付けるAPI Gateway
4. CloudFormationを動かすshellスクリプトを作成する
そもそもCloudFormationとは↓
shellスクリプトでaws-cliからCloudFormationを呼んで、手順3で用意したtemplate.yamlを渡す。
このshellスクリプト(lambda_sam_deploy_ts.sh
)を下記のファイルパスに設置する。
ocean_lambda_sam_deploy_ts/
├ aws/ - sam/ - dev/ - template.yaml
| └ - code_build/ - lambda_sam_deploy_ts.sh
|
└ built/ - dev/ - ping/ - index.js
#!/bin/sh
if [ $# < 1 ]; then
echo "[ERROR!] one or more arguments are required"
exit -1
fi
STAGE=$1 # このサンプルではdev/prdを第一引数として受け取って代入する
npm i
npm run build-${STAGE}
aws cloudformation package \
--template-file aws/sam/${STAGE}/template.yaml \
--s3-bucket uni-cloudformation-artifact-${STAGE} \ # 手順1で作成したS3 bucket
--s3-prefix sam/lambda_sam_deploy_ts/${STAGE}/package \ # 任意のファイルパス
--output-template-file packaged.yaml
aws cloudformation deploy \
--template-file packaged.yaml \
--stack-name lambda-sam-deploy-ts # 任意のスタック名
5. CodeBuildのビルドジョブを作成する
5-1. buildspec.yamlを作成する
CodeBuildのジョブで処理される内容はbuildspec.yaml
で定義される。
このyamlファイルをgitにpushしておいて、後ほどジョブ新規作成時に参照パスを指定すると、
定義された処理内容を順に実行してくれる。
本ページの例では、手順4で作成したlambda_sam_deploy_ts.sh
を実行するような
buildspec.yaml
を作成し、下記のファイルパスに設置した。
(ややこしくなってしまったので、ページ上部のサンプルコードも参考に)
ocean_lambda_sam_deploy_ts/
├ aws/ - sam/ - dev/ - template.yaml
| └ - code_build/ - lambda_sam_deploy_ts.sh
| └ - dev/ - buildspec.yaml
└ built/ - dev/ - ping/ - index.js
version: 0.2
phases:
pre_build:
commands:
- echo "start->lambda_sam_deploy_ts"
build:
commands:
- sh aws/code_build/lambda_sam_deploy_ts.sh dev # 手順4で作成したshellを実行(引数devを渡す)
post_build:
commands:
- echo "finish->lambda_sam_deploy_ts"
artifacts:
files:
- built/dev/**/* # 手順1で作成したS3 bucketに設置する成果物
5-2. CodeBuildのコンソールからプロジェクトを新規作成する
CodeBuildのコンソール画面を開いて、Create build project
をクリック。
① ジョブで使うコードの参照元を選択する(本ページではGitHubを選択)。
② 本ページではパブリックリポジトリを選択(プライベートリポジトリでは、アカウント認証が必要)
③ リポジトリのURLを指定
④ 参照するブランチを指定
(参照先にLambda関数のコード、shellスクリプト、template.yaml
、buildspec.yaml
があること)
① 実行環境を選択(よしなに)
② 新規作成するプロジェクトに紐付けるIAMロールを指定
(本ページではcodebuild-sam-deoloy-service-role
という名称でIAMロールを新規作成)
① 実行するジョブの処理内容を定義する方法を選択
② buildspec.yaml
のファイルパスを指定
(本ページでは手順5-1で作成したbuildspec.yaml
を選択)
ビルドの成果物を保存する設定(よしなに)
(本ページでは手順1で作成したS3 bucketにトランスパイル済のjsファイルを保存)
CloudWatchにログを出力する設定(よしなに)
全て設定したら、Create build project
をクリック
これでCloudFormationを起動するプロジェクトを作成できた(まだ実行しない)。
6. IAMの権限を整理する
IAMロールのコンソールを開いて、手順5-2で作成したCodeBuildのIAMロールを選択
画面上部のJSONタブをクリックする。
必要な権限に応じてJSONの内容を書き換えたら、Review policy
をクリック
(本ページでは下記LambdaSamCreationPolicyDev.json
の通りIAMポリシーを設定した。)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Resource": [
"arn:aws:logs:ap-northeast-1:260006775748:log-group:/aws/codebuild/lambda_sam_deploy_ts",
"arn:aws:logs:ap-northeast-1:260006775748:log-group:/aws/codebuild/lambda_sam_deploy_ts:*"
],
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
},
{
"Effect": "Allow",
"Resource": ["arn:aws:s3:::codepipeline-ap-northeast-1-*"],
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketAcl",
"s3:GetBucketLocation"
]
},
{
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::uni-codebuild-artifact",
"arn:aws:s3:::uni-codebuild-artifact/*"
],
"Action": ["s3:PutObject"]
},
{
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::uni-cloudformation-artifact-dev",
"arn:aws:s3:::uni-cloudformation-artifact-dev/*"
],
"Action": ["s3:GetObject", "s3:PutObject"]
},
{
"Effect": "Allow",
"Action": [
"codebuild:CreateReportGroup",
"codebuild:CreateReport",
"codebuild:UpdateReport",
"codebuild:BatchPutTestCases",
"codebuild:BatchPutCodeCoverages"
],
"Resource": [
"arn:aws:codebuild:ap-northeast-1:260006775748:report-group/lambda_sam_deploy_ts-*"
]
},
{
"Effect": "Allow",
"Action": ["iam:PassRole"],
"Resource": ["arn:aws:iam::260006775748:role/lambda-sam-deploy-ts-role"]
},
{
"Effect": "Allow",
"Action": [
"cloudformation:CreateStack",
"cloudformation:CreateChangeSet",
"cloudformation:DescribeStacks",
"cloudformation:DescribeChangeSet",
"cloudformation:DescribeStackEvents",
"cloudformation:ExecuteChangeSet",
"cloudformation:GetTemplateSummary"
],
"Resource": ["*"]
},
{
"Effect": "Allow",
"Action": [
"lambda:CreateFunction",
"lambda:DeleteFunction",
"lambda:GetFunction",
"lambda:AddPermission",
"lambda:RemovePermission",
"lambda:UpdateFunctionCode"
],
"Resource": ["*"]
},
{
"Effect": "Allow",
"Action": [
"apigateway:GET",
"apigateway:POST",
"apigateway:PATCH",
"apigateway:DELETE"
],
"Resource": ["*"]
}
]
}
付与した権限の一覧に誤りが無いか確認して、`Save changes`をクリック。 ![Screen Shot 2021-06-06 at 21.40.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1050582/7250596e-e0c9-57a1-fc30-ee89869cd116.png)
7. ビルドジョブを実行して、CloudFormationを見守る
これでジョブ実行の準備が整った。
CodeBuildのコンソールに戻って、Start build
をクリック。
buildspec.yaml
の内容に沿って、順番に処理が実行される。
ジョブ実行中もしくは終了後に、CloudFormationのコンソールを開いて、新規作成したStackを選択。
画面上部のEventタブをクリックすると、CloudFormationくんが頑張っている様子を覗ける。
Statusが全てCREATE_COMPLETE
になって、CodeBuildのジョブが完了したら、おわり。
8. 生成されたLambdaを実行してみる
Lambdaのコンソールを開いて、新規作成されたFunctionをクリック。
Lambdaに紐付けられたAPI Gatewayをクリック。
Details
タブをクリックすると展開されるAPI endpoint
のURLをクリック。
トラブルシュート
CloudFormationが途中でコケる
だいたいIAMロールの権限が足りていない/権限の付与対象が間違っている。
手順7に従ってCloudFormationののEventのエラーログを見れば足りていない権限がわかる場合が多い。
あるいはCodeBuildやCloudWatchに出力されたログが役に立つかもしれない。
CloudFormationのstackが中途半端なStatusのまま残る
リソース作成に失敗したとき、stackのStatusがUPDATE_ROLLBACK_FAILED
から変わらなくなることがある。
これはリソースの作成に失敗したが、失敗したリソースを消すこともできずに立ち往生している状態を指す。
この状態では、CodeBuildからビルドジョブを再実行してもstackを生成できずにジョブがコケる。
CloudFormationのコンソールから、Delete
をクリックしてStackを削除すれば良い。
(stackに紐付けられたリソースが全削除されるため注意が必要)
参考↓
webpack5でトランスパイルしたTypescriptで、index.handlerが認識されない
個人的にハマったところ。
詳細は下記ページで議論されている。
とりあえず、webpack4を使うか、CloudFormationの代わりにserverlessを使うのが手っ取り早い(?) (本ページの根底から揺らぐけど)