見出し画像

Cloud Formationを使ってGolang製LambdaのCICDをGitHub Actionsで構築してみた

はじめに

こんにちは、SHIFT の開発部門に所属しているKatayamaです。今期から転属になり、開発を担当していくことになりました。 ただ、前期はDevOps導入支援等に携わっていた関係で今回はCICD関係の記事を書こうと思います。

以前に投稿させて頂いた記事(AWS SAM を使って Golang 製 Lambda のための CICD を GitHub Actions で構築してみた)では、AWS SAMを使った CICD を構築してみた。今回は AWS SAM の何がいいのかをより理解する目的+ちょっと Cloud Formation もやってみたかったという事で Cloud Formation を使った Lambda の CICD の構築をやってみた。

構成

AWS SAM を使って Golang 製 Lambda のための CICD を GitHub Actions で構築してみた#構成と全く同じなのでそちらを参照。

Lambda の CICD の準備(S3 Bucket を構築する template.yaml)

今回は zip をアプロードするタイプで CICD を構築するので S3 の Bucket が必要になる。まずはそれを Cloud Formation で作成する。
作成対象は S3 の Bucket のみだが、BucketPolicy も設定したいので以下のようなテンプレートにした。

AWSTemplateFormatVersion: 2010-09-09
Description: The AWS CloudFormation template for artifact S3 Bucket of cloudformation-go-lambda

Parameters:
  UserName:
    Type: String
    Description: AWS user name.

Resources:
  ArtifactBucket:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: aws-cloudformation-build-artifact-go-lambda
      VersioningConfiguration:
        Status: Enabled

  ArtifactBucketBucketPolicy:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      PolicyDocument:
        Statement:
          - Action:
              - "s3:GetObject"
            Effect: Allow
            Resource: !Join
              - ""
              - - "arn:"
                - !Ref "AWS::Partition"
                - ":s3:::"
                - !Ref ArtifactBucket
                - /*
            Principal:
              AWS: !Join
                - ""
                - - !Sub arn:aws:iam::${AWS::AccountId}:user/
                  - !Ref UserName
      Bucket: !Ref ArtifactBucket

Outputs:
  BucketName:
    Description: Bucket for Lmabda build artifact.
    Value: !GetAtt ArtifactBucket.Arn

※組み込み関数(!Join, !Ref など)を複数使うような場合は Fn:Join を使う(以下のような書き方はできない)。

AWS: !Sub !Ref arn:aws:iam::${AWS::AccountId}:user/UserName

上記のテンプレートを AWS CLI コマンドで deploy すればいいので、

aws cloudformation deploy --stack-name s3-bucket-for-cloudfromation-go-lambda --template-file s3-template.yaml --parameter-overrides UserName=xxxxxxxxxx

というコマンドを実行すれば、S3 Bucket が作成される。

※create-stack コマンドでも stack を作成する事はできるが、deploy を使う方が今後変更が入った時に change set が作られるので deploy にしている(change set とは以下の図の「変更セット」の事)。

・参考:AWS::S3::Bucket
・参考:AWS::S3::BucketPolicy
・参考:Principals
・参考:AWS JSON ポリシーの要素: Principal
・参考:Fn::Join
・参考:AWS::Partition
・参考:aws.cloudformation.deploy
・参考:Return values

Lambda 関数を構築する template.yaml

次に Lambda 関数を構築する template.yaml を書く。

AWSTemplateFormatVersion: "2010-09-09"
Description: The AWS CloudFormation template for cloudformation-go-lambda

Parameters:
  BacklogApiKey:
    Type: String
    Description: Backlog api key.
  BacklogDomein:
    Type: String
    Description: Backlog domein.
  SlackApiToken:
    Type: String
    Description: Slack api token.

Resources:
  GoLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code: ./main.zip
      Description: Lambda function create by aws cloudformation.
      Handler: main
      FunctionName: cloudformation-go-lambda
      Runtime: go1.x
      Timeout: 3
      Environment:
        Variables:
          BACKLOG_API_KEY: !Ref BacklogApiKey
          BACKLOG_DOMEIN: !Ref BacklogDomein
          BACKLOG_ISSUE_STATUS: 未対応
          BACKLOG_STATUS_ID: 3
          SLACK_API_TOKEN: !Ref SlackApiToken
          SLACK_CHANNEL: "#lambda実行"
      Role: !GetAtt GoLambdaFunctionRole.Arn

  GoLambdaFunctionRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - "sts:AssumeRole"
            Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      Policies:
        - PolicyName: CloudFormationGoLambdaFunctionRolePolicy-CodeCommit
          PolicyDocument:
            Statement:
              - Action:
                  - "codecommit:GetCommit"
                Resource:
                  - !Sub "arn:aws:codecommit:ap-northeast-1:${AWS::AccountId}:*"
                Effect: Allow
                Sid: CodeCommit
      Path: /service-role/

  GoLambdaFunctionCodeCommitPushPermission:
    Type: "AWS::Lambda::Permission"
    Properties:
      Action: "lambda:InvokeFunction"
      Principal: events.amazonaws.com
      FunctionName: !Ref GoLambdaFunction
      SourceArn: !GetAtt GoLambdaFunctionCodeCommitPush.Arn

  GoLambdaFunctionCodeCommitPush:
    Type: "AWS::Events::Rule"
    Properties:
      EventPattern:
        source:
          - aws.codecommit
        detail:
          eventSource:
            - codecommit.amazonaws.com
        detail-type:
          - CodeCommit Repository State Change
      Targets:
        - Id: GoLambdaFunctionCodeCommitPushLambdaTarget
          Arn: !GetAtt GoLambdaFunction.Arn

Outputs:
  GoLambdaFunction:
    Description: "Go Lambda Function ARN"
    Value: !GetAtt GoLambdaFunction.Arn
  GoLambdaFunctionIamRole:
    Description: "Implicit IAM Role created for Go function"
    Value: !GetAtt GoLambdaFunctionRole.Arn
  GoLambdaFunctionCodeCommitPush:
    Description: Cloud Watch Events for Go function. Triger is Code Commit push.
    Value: !GetAtt GoLambdaFunctionCodeCommitPush.Arn

以下 template.yaml について見ていく。

・参考:AWS::Lambda::Function

GoLambdaFunction Code

AWS::Lambda::Function Codeを見ると分かるが、 AWS SAM と違って template.yaml があるプロジェクト内のコードを指定する事はできない。
ZipFile:を使えば、Node.js/Python ではインラインでコードが書けるが、基本は S3 の zip or ECR を指定する事になると思う。

※今回は aws cloudformation package でローカルアーティファクトをパッケージ化し、その後 aws cloudformation deploy を実行する方法を取っているので、./main.zip になっている(これは Cloud Formation の UpdateStack を実行した時に、コードのみを変更している場合、テンプレートが変わらないので No updates are to be performed と言うエラーになるため)。

・参考:S3Bucket と S3Key による指定の際におこる問題

GoLambdaFunction Environment

環境変数を設定している。

※API key や Token などの他から見えてはいけないものは、aws cloudformation deploy 時の --parameter-overrides オプションで上書きする事で設定し、それを Resources の方で、組み込み関数 Ref で参照している。

・参考:パラメータ
・参考:AWS::Lambda::Function Environment
・参考:Ref

GoLambdaFunctionRole AssumeRolePolicyDocument

Role をアタッチできるサービスを設定している(信頼されたエンティティというやつ)。
※信頼されたエンティティについては、以下の図を参照。

・参考:AWS::IAM::Role

GoLambdaFunctionRole ManagedPolicyArns

Lambda 機能の AWS マネージドポリシーに書かれている AWSLambdaBasicExecutionRole をアタッチしている(Lambda の実行ログを CloudWatch にアップロードするためのアクセス権限)。

GoLambdaFunctionRole Policies

単純なインラインポリシー。今回は CodeCommit にアクセス(GetCommit)したいので、それを設定している。

GoLambdaFunctionRole Path

今回の Role は AWS の各サービスがユーザに代わって何かを行う権限を付与するためのロールなので、サービスロールに該当するのでIAM 識別子に基づいて、 /service-role/ とした。

GoLambdaFunctionCodeCommitPushPermission

Lambda 関数の権限で注意点で書いたとおり、Lambda 関数の権限には ① 実行ロール、② リソースベースのポリシーの 2 つがあり、② の方の設定をしないと Cloud Watch Events から Lambda をトリガーできない。
なのでここで permission の設定をしている。

・参考:AWS::Lambda::Permission

GoLambdaFunctionCodeCommitPush

Cloud Watch Events(EventBridge)の設定を行っている部分で、Code Commit の push をトリガーに Lambda を呼び出す設定。

・参考:AWS::Events::Rule

Outputs

Cloud Formation の stack の出力タブに表示させたいものを定義している。

・参考:AWS::Lambda::Function Return values
・参考:AWS::Events::Rule Return values

AWS Cloud Formation によるリソースの構築(GitHub Actions の CICD パイプライン)

AWS SAM による Build・Deploy と同様に、GitHub Actions 上で CICD を実行させるようにした。 GitHub Actions の pipeline.yaml は以下の通り。

name: lambda-build-deploy-with-cloudformation

on:
  push:
    branches: [cloudformation]

env:
  ARTIFACTS_BUCKET: aws-cloudformation-build-artifact-go-lambda

jobs:
  build-and-package:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Setup Go environment
        uses: actions/setup-go@v2.1.3
        with:
          go-version: "1.17.1"

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: Go get aws lambda library
        run: go get github.com/aws/aws-lambda-go/lambda

      - name: Go build
        run: GOOS=linux go build -o main lambda.go

      - name: Create zip
        run: zip main.zip main config.yaml

      - name: Upload artifacts to artifact buckets
        run: |
          aws cloudformation package \
            --template-file template.yaml \
            --s3-bucket ${ARTIFACTS_BUCKET} \
            --output-template-file packaged-template.yaml

      - uses: actions/upload-artifact@v2
        with:
          name: packaged-template.yaml
          path: packaged-template.yaml

  deploy:
    needs: [build-and-package]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v2
        with:
          name: packaged-template.yaml

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: Cloudformation deploy
        run: |
          aws cloudformation deploy \
            --template-file packaged-template.yaml \
            --stack-name cloudformation-go-lambda \
            --capabilities CAPABILITY_IAM \
            --parameter-overrides BacklogApiKey=${{ secrets.BACKLOG_API_KEY }} BacklogDomein=${{ secrets.BACKLOG_DOMEIN }} SlackApiToken=${{ secrets.SLACK_API_TOKEN }}

以下で各コマンドについてみていく。

※今回は、Golang 製 Lambda のための CICD を GitHub Actions で構築してみたと同じような考え方で Build・Deploy を区切った。aws cloudformation package を行っている理由についてはS3Bucket と S3Key による指定の際におこる問題を参照。

go build

Golang 製 Lambda のための CICD を GitHub Actions で構築してみた#GitHub Actionsを参照。

aws cloudformation package/deploy

Lambda のコードの修正を stack に反映させるためにまず package を行い、その後その package 化されたものを deploy している。

・参考:aws.cloudformation.package
・参考:aws.cloudformation.deploy


まとめとして

Cloud Formation の場合、AWS SAM と違って自分で全てのリソースの設定をしなければならないので大変・・・。また、どのリソースタイプになるのか?も数が多いので把握が大変かもしれない。

やってみて感じたが、ほぼ AWS CLI と同じ事を yaml に書いてやる感じなので、やはり CLI のコマンドで構築する方法から、そのリソースタイプになるのか?プロパティは何か?を把握していくといいのかもと感じた。

ex) ECR へのレプリケーションをトリガーに ECS のサービス更新を実行する#AWS CLI コマンドからの設定の Cloud Watch Events の設定のように、CLI コマンドが分かればどのリソースか?が把握しやすい気がする。

__________________________________

執筆者プロフィール:Katayama Yuta
SaaS ERPパッケージベンダーにて開発を2年経験。 SHIFTでは、GUIテストの自動化やUnitテストの実装などテスト関係の案件に従事したり、DevOpsの一環でCICD導入支援をする案件にも従事。 最近開発部門へ異動し、再び開発エンジニアに。座学で読み物を読むより、色々手を動かして試したり学んだりするのが好きなタイプ。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/