AWS SAMで開発したコードをGitHubのmainにpushしたタイミングでCodeBuildを使いCICDすると言う事をやってみたので、備忘録を兼ねて手順を残したいと思います。
題材としてはsam init
で作れるNode.jsのHello World Exampleを使います。
環境
OS: macOS Catalina(10.15.7)
SAM CLI: 1.15.0
やる事
- hello worldプロジェクトを作成
- buildspec.ymlを作成
- githubへpushする
- CloudFormation用のS3バケットを作成
- CodeBuild用のIAMロールを作成
- CodeBuildの設定
- ビルドする
SAMの話なのになぜCloudFormationの話が出るのか?
CodeBuildの環境にSAM CLIが入っていないので、SAMテンプレートをCodeBuild上でCloudFormationのテンプレートに変換してリソースの作成・デプロイをする事をここでは考えているからです。
SAM CLIをインストールする事も出来ますが、SAMプロジェクトのビルド・デプロイは、CodeBuildの環境にデフォルトで入っているAWS CLIのコマンドで代替出来るので、代替する方法で進めいたいと思います。
1. hello worldプロジェクトを作成
1-1. sam init
コマンドでプロジェクト作成
1-2. template.yamlがパラメーターを受け取れるように改造
1-2.については、普段sam deploy
する際にパラメーターを渡す事があると思います。CodeBuildでCICDする場合、どう渡せば良いのかを試してみます。
ここでは、prd・stg・devなど環境名をパラメーターで受け取り、その環境名をLambda関数の名前に接尾辞として付けると言う事をしようと思います(例:hello-world-prd、hello-world-stg)。
1-1. sam init
コマンドでプロジェクト作成
nodejsのプロジェクトでtemplateでは「1 - Hello World Example」を選びます。
[~/Desktop]$ sam init
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
What package type would you like to use?
1 - Zip (artifact is a zip uploaded to S3)
2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 1
Which runtime would you like to use?
1 - nodejs12.x
2 - python3.8
3 - ruby2.7
4 - go1.x
5 - java11
6 - dotnetcore3.1
7 - nodejs10.x
8 - python3.7
9 - python3.6
10 - python2.7
11 - ruby2.5
12 - java8.al2
13 - java8
14 - dotnetcore2.1
Runtime: 1
Project name [sam-app]: hello-world-app
Cloning app templates from https://github.com/aws/aws-sam-cli-app-templates
AWS quick start application templates:
1 - Hello World Example
2 - Step Functions Sample App (Stock Trader)
3 - Quick Start: From Scratch
4 - Quick Start: Scheduled Events
5 - Quick Start: S3
6 - Quick Start: SNS
7 - Quick Start: SQS
8 - Quick Start: App Backend using TypeScript
9 - Quick Start: Web Backend
Template selection: 1
-----------------------
Generating application:
-----------------------
Name: hello-world-app
Runtime: nodejs12.x
Dependency Manager: npm
Application Template: hello-world
Output Directory: .
Next steps can be found in the README file at ./hello-world-app/README.md
1-2. template.yamlがパラメーターを受け取れるように改造
繰り返しになりますが、prd・stg・devなど環境名をパラメーターで受け取り、その環境名をLambda関数の名前に接尾辞として付けると言う事をしようと思います(例:hello-world-prd、hello-world-stg)。
template.yamlを下記と置き換えます。
(↓デフォルトのテンプレートからの変更点)
・Globalsの項目の次にParametersを追加
・Parametersで受け取った値を関数名にくっつける
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
hello-world-app
Sample SAM Template for hello-world-app
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3
Parameters:
Env: # prd: 本番、stg: ステージング、dev: 開発
Type: String
AllowedValues:
- prd
- stg
- dev
Default: dev
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName:
!Join
- ''
- - 'hello-world'
- '-'
- !Ref Env
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs12.x
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
2. buildspec.ymlを作成
CodeBuildに実行して欲しいコマンドをbuildspec.ymlに記述します。
CodeBuild上でsam build
、sam deplly
コマンドを実行させたいところですが、CodeBuildの環境にはSAM CLIがインストールされていません。
勿論、SAM CLIをダウンロード・インストールしてSAMコマンドを使えるようにすると言うことも出来ますが、今回はより手軽な手段を取りたいと思います。
CodeBuildのビルド環境にはデフォルトでAWS CLIが入っているので、これで代替します。
具体的には、SAMのテンプレートをCloudFormationのテンプレートに変換、そしてデプロイします。
それらをbuildspec.ymlに記述します。
プロジェクト直下にbuildspec.yml
を作成して下記を貼り付けます。
version: 0.2
phases:
build:
commands:
- echo 'Building...'
- aws cloudformation package --template-file template.yaml --s3-bucket $S3_BUCKET --output-template-file output.yml
- aws cloudformation deploy --template-file output.yml --s3-bucket $S3_BUCKET --stack-name $STACKNAME --capabilities CAPABILITY_IAM --region $REGION --parameter-overrides Env=$ENV
- aws cloudformation describe-stacks --stack-name $STACKNAME
ディレクトリ構成は↓な感じです。
※補足
環境変数
後ほど、CodeBuildの管理画面上で設定します。
aws cloudformation packageは何してるの?
- SAMテンプレートからCloudFormationのテンプレートを作成する
-
--template-file
- SAMテンプレートを指定。これを元にCFテンプレートを作成します。
-
--output-template-file
- ここではファイル名をoutput.ymlとして、CFのテンプレートを作成します。
-
--s3-bucket
- hello-world配下のLambdaを固めてこのバケットにアップロードします。CloudFormationでLambdaをデプロイする際にLambdaのコードをzipで固めてS3にアップロードする作業と同等です。
-
aws cloudformation deployは何してるの?
- CloudFormationスタックを作成します
-
--template-file
- output.ymlをCFテンプレートとして使用します。
-
--s3-bucket
- テンプレートをアップロードする場所。
-
-stack-name
- CFのスタック名
-
--parameter-overrides
- SAMテンプレートに渡すパラメーター。Envとして環境変数$Envを渡しています。環境変数は後ほど設定します。
-
aws cloudformation describe-stacksは何している?
CloudFormationで作成したリソースを出力します。出力はCodeBuildの画面上で確認出来ます。このコマンドは無くても大丈夫なのですが、テンプレートから作成されるAPIGatewayのURLを確認するのに便利なので記述していいます。
3. githubへpushする
GitHubにリポジトリを作りpushします。masterブランチをmainブランチにリネームした上で、mainブランチにpushします。
リネームする理由は、GitHub上でリポジトリを作った際にリネームするように言われるので従っているだけです(ご存知の方が多いと思いますが、masterが奴隷制度を連想させる言葉なのでmainに置き換える流れが主流なようです)。
4. CloudFormation用のS3バケットを作成
CodeBuildでCloudFormationのスタックを作りますが、その際に、S3にCloudFormationのテンプレートをアップロードして置く必要があります。そのためのバケットを作成します。
buildspec.ymlのコマンド中で--s3-bucket $S3_BUCKET
と言う部分で使います。後ほど環境変数に、ここで作成したS3バケット名を設定します。
バケット名は適宜置き換えてください(世界中で一意なので)。バケット名の設定以外はすべてデフォルトでOKです。
5. CodeBuild用のIAMロールを作成
CodeBuildにさせようと思っている事を許可するロールを作成します。
IAMのページからIAMロールを作成します。
ユースケースとして「CodeBuild」を選択。必要なポリシーをアタッチして行きます。
今回は下記ポリシーをくっつけました。
ポリシーの割当が苦手なので、結構強い感じにしてしまっています。適宜いい感じにして貰えたらと。。。
- AmazonS3FullAccess
- CodeBuildからS3にテンプレートをアップするなど操作するため
- AmazonCloudFormationFullAccess
- CloudFormationスタックを作成するため
- AWSLambda_FullAccess
- CloudFormationでLambdaを作成するため
- AmazonAPIGatewayAdministrator
- CloudFormationでAPIGatewayを作成するため
- CloudWatchFullAccess
- CodeBuildのビルドの状況などCloudWatchにログを残すため
- IAMFullAccess
- LambdaにAPIGatewayからのキックを許可するのロールを作成するため
6. CodeBuildの設定
マネコンからCoceBuildに移動して、画面右上の「ビルドプロジェクトを作成する」をクリックします。
プロジェクトの設定・ソース
CodeBuildとGitHubを連携させる場合、初回はアカウントの紐付け作業があります。画面に従えば問題なく出来ると思います。
プライマリソースのウェブフックイベント
チェックを入れると画面が展開されます。
GitHubのmainにpushされるとCodeBuildが走るように設定します。
環境
オペレーティングシステム … Amazon Linux 2
ランタイム … Standard
イメージ … セレクトボックスの中から一番新しいバージョンを選択します(一番下)。
イメージのバージョン … セレクトボックスの中から一番新しいバージョンを選択します(一番下)。
サービスロール … 既存のサービスロール
ロールのARN … 先ほど作成したロールを選択
↑の「▶︎追加設定」をクリックして画面を展開します。
↑の画面で環境変数を設定します。
S3_BUCKET … 先ほど作成したS3バケット名
STACKNAME … hello-world-app
REGION … ap-northeast-1
ENV … prd
Buildspec・バッチ設定・アーティファクト・ログ
デフォルトのままで大丈夫です。
ページを一番下までスクロールして「ビルドプロジェクトを作成する」をクリックします。
7. ビルドする
画面右上の「ビルドを開始」をクリックします。
ステータスが進行中に変わります。
ステータスが成功に変わったらビルドが終了です。
↑の図で赤枠で囲んだ箇所に記載されたAPIGatewayのURLにアクセスします。
"hello world"と表示されたらビルド成功です。
GitHubとの連携も確認します。
hello-world/app.js
を下記と置き換えます。
let response;
exports.lambdaHandler = async (event, context) => {
try {
// const ret = await axios(url);
response = {
'statusCode': 200,
'body': JSON.stringify({
message: 'hello codebuild',
// location: ret.data.trim()
})
}
} catch (err) {
console.log(err);
return err;
}
return response
};
GitHubにpushします。
CodeBuildが自動で走ります。
「進行中」が「成功」と変わるのを待ちAPIGatewayを叩くと、、、
Lambdaが更新された事を確認出来ました。
終わりに
モチベーションとしては、複数環境(prd、stg、devなど)を切り替えながら使う事を想定した場合で、環境ごとに違うパラメーターを渡すなどしたい時に、都度、手動で渡すと言う煩わしさを回避出来る点が大きいかなー、と思いました。
tomlを複数用意する事でも運用は楽になりますが、個人的にはCodeBuildの環境変数に任せる方が楽だと感じました。
以上です。どなたかのお役に少しでも立つ事が出来たならば幸いです。