8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AWS SAMをCodeBuildでCICDする

Last updated at Posted at 2021-05-30

AWS SAMで開発したコードをGitHubのmainにpushしたタイミングでCodeBuildを使いCICDすると言う事をやってみたので、備忘録を兼ねて手順を残したいと思います。

題材としてはsam initで作れるNode.jsのHello World Exampleを使います。

ざっくりしたイメージです↓
architect.jpg

環境

OS: macOS Catalina(10.15.7)
SAM CLI: 1.15.0

やる事

  1. hello worldプロジェクトを作成
  2. buildspec.ymlを作成
  3. githubへpushする
  4. CloudFormation用のS3バケットを作成
  5. CodeBuild用のIAMロールを作成
  6. CodeBuildの設定
  7. ビルドする

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 buildsam 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

ディレクトリ構成は↓な感じです。

スクリーンショット 2021-05-26 19.55.02.png

※補足

環境変数

後ほど、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に置き換える流れが主流なようです)。

スクリーンショット 2021-05-26 19.59.07.png

4. CloudFormation用のS3バケットを作成

CodeBuildでCloudFormationのスタックを作りますが、その際に、S3にCloudFormationのテンプレートをアップロードして置く必要があります。そのためのバケットを作成します。

buildspec.ymlのコマンド中で--s3-bucket $S3_BUCKETと言う部分で使います。後ほど環境変数に、ここで作成したS3バケット名を設定します。

バケット名は適宜置き換えてください(世界中で一意なので)。バケット名の設定以外はすべてデフォルトでOKです。

スクリーンショット 2021-05-26 20.02.37.png

5. CodeBuild用のIAMロールを作成

CodeBuildにさせようと思っている事を許可するロールを作成します。

IAMのページからIAMロールを作成します。

ユースケースとして「CodeBuild」を選択。必要なポリシーをアタッチして行きます。

スクリーンショット 2021-05-26 20.05.32.png

今回は下記ポリシーをくっつけました。

ポリシーの割当が苦手なので、結構強い感じにしてしまっています。適宜いい感じにして貰えたらと。。。

  • AmazonS3FullAccess
    • CodeBuildからS3にテンプレートをアップするなど操作するため
  • AmazonCloudFormationFullAccess
    • CloudFormationスタックを作成するため
  • AWSLambda_FullAccess
    • CloudFormationでLambdaを作成するため
  • AmazonAPIGatewayAdministrator
    • CloudFormationでAPIGatewayを作成するため
  • CloudWatchFullAccess
    • CodeBuildのビルドの状況などCloudWatchにログを残すため
  • IAMFullAccess
    • LambdaにAPIGatewayからのキックを許可するのロールを作成するため

ロール.jpg

6. CodeBuildの設定

マネコンからCoceBuildに移動して、画面右上の「ビルドプロジェクトを作成する」をクリックします。

ビルド1.jpg

プロジェクトの設定・ソース

CodeBuildとGitHubを連携させる場合、初回はアカウントの紐付け作業があります。画面に従えば問題なく出来ると思います。

ビルド2.jpg

プライマリソースのウェブフックイベント

チェックを入れると画面が展開されます。

GitHubのmainにpushされるとCodeBuildが走るように設定します。

ビルド3.png

環境

オペレーティングシステム … Amazon Linux 2
ランタイム … Standard
イメージ … セレクトボックスの中から一番新しいバージョンを選択します(一番下)。
イメージのバージョン … セレクトボックスの中から一番新しいバージョンを選択します(一番下)。
サービスロール … 既存のサービスロール
ロールのARN … 先ほど作成したロールを選択

環境.jpg

↑の「▶︎追加設定」をクリックして画面を展開します。

環境変数.jpg

↑の画面で環境変数を設定します。

S3_BUCKET … 先ほど作成したS3バケット名
STACKNAME … hello-world-app
REGION … ap-northeast-1
ENV … prd

Buildspec・バッチ設定・アーティファクト・ログ

デフォルトのままで大丈夫です。

ページを一番下までスクロールして「ビルドプロジェクトを作成する」をクリックします。

7. ビルドする

画面右上の「ビルドを開始」をクリックします。

ビルド作成.jpg

ステータスが進行中に変わります。

進行中.jpg

ステータスが成功に変わったらビルドが終了です。

ビルド成功.jpg

↑の図で赤枠で囲んだ箇所に記載されたAPIGatewayのURLにアクセスします。
"hello world"と表示されたらビルド成功です。

スクリーンショット 2021-05-28 20.10.28.png

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が自動で走ります。

更新.jpg

「進行中」が「成功」と変わるのを待ちAPIGatewayを叩くと、、、

スクリーンショット 2021-05-28 20.17.20.png

Lambdaが更新された事を確認出来ました。

終わりに

モチベーションとしては、複数環境(prd、stg、devなど)を切り替えながら使う事を想定した場合で、環境ごとに違うパラメーターを渡すなどしたい時に、都度、手動で渡すと言う煩わしさを回避出来る点が大きいかなー、と思いました。

tomlを複数用意する事でも運用は楽になりますが、個人的にはCodeBuildの環境変数に任せる方が楽だと感じました。

以上です。どなたかのお役に少しでも立つ事が出来たならば幸いです。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?