見出し画像

AWS SAMを使ってGolang製LambdaのためのCICDをGitHub Actionsで構築してみた

はじめに

こんにちは、SHIFT にて自動化アーキテクトとしてテスト自動化・DevOps 導入支援などをしていますKatayamaです。

Golang 製 Lambda のための CICD を GitHub Actions で構築してみたでは単純に zip を作成しそれを AWS CLI コマンドを使って Build・Deploy する CICD パイプラインを構築した。今回は「まとめとして」に書いていたAWS SAMを用いた CICD パイプラインの構築をやってみた。

構成

今回は、Lambda(Go)の CICD を GitHub Actions で構築で Deploy していた Lambda と同じものを Deploy するが、AWS SAM が IaC なので Lambda 関数の構築からをまるっと行った。 その構成としては以下。

上記は、

1、開発者が Code Commit に push
2、それをトリガーに Lambda が実行
3、Code Commit からコミット情報を取得
4、そのコミット情報を Backlog の課題に記載する

という developer experience 向上と、管理側の要望を織り込むための仕組みとして構築した。

AWS SAM の template.yaml を作成する

作成したい AWS サービスの構成としては上記の構成の通りなので、以下のようなリソースの設定が必要。

・Lambda:イベントトリガー時に動く
・Cloud Watch Events:Code Commit への push で Lambda をトリガーする
・IAM:Lambda が Code Commit にアクセスできるようにする(実行ロールの作成)、Cloud Watch Events が Lambda を呼び出せるようにする(リソースベースのポリシーの作成)

※ただ、この後見ていくと分かるが、IAM は AWS SAM(Cloud Formation)の方で大部分を自動で作成してくれるので、設定が必要なのは実行ロールの部分だけ。

template.yaml は以下のようになる。

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: SAM Template for sam-go-lambda

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

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  SamGoLambdaFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: ./
      Handler: main
      FunctionName: sam-go-lambda
      Description: Lambda function create by aws sam.
      Runtime: go1.x
      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実行"
      Policies:
        - Statement:
            - Sid: CodeCommitGetCommitPolicy
              Effect: Allow
              Action:
                - codecommit:GetCommit
              Resource:
                - !Sub "arn:aws:codecommit:ap-northeast-1:${AWS::AccountId}:*"
      Events:
        CodeCommitPush:
          Type: CloudWatchEvent
          Properties:
            Pattern:
              source:
                - aws.codecommit
              "detail-type":
                - "CodeCommit Repository State Change"
              detail:
                eventSource:
                  - codecommit.amazonaws.com

Outputs:
  SamGoLambdaFunction:
    Description: "Go Lambda Function ARN"
    Value: !GetAtt SamGoLambdaFunction.Arn
  SamGoLambdaFunctionIamRole:
    Description: "Implicit IAM Role created for Go function"
    Value: !GetAtt SamGoLambdaFunctionRole.Arn
  SamGoLambdaFunctionCodeCommitPush:
    Description: "Cloud Watch Events for Go function. Triger is Code Commit push."
    Value: !GetAtt SamGoLambdaFunctionCodeCommitPush.Arn


template.yaml の中身について見ていく。

・参考:YAML フォーマット

Globals:

共通のプロパティを定義するためのセクションで、Resource AWS::Serverless::Function と AWS::Serverless::Api についてはここに設定されている内容を継承する(内容で上書きされる)。

※指定できる Resource の種類と設定項目はサポートされているリソースとプロパティを参照。

・参考:グローバルセクション AWS SAM テンプレート

Resources:

AWS の各サービスを指定するセクションで、ここに書かれたリソースでサーバーレスアプリケーションを構築する。

※AWS SAM の公式で紹介されているもの(サーバーレスアプリケーション構築でよく使われるもの)はAWS SAM リソースおよびプロパティのリファレンスを参照。(やろうと思えば For reference information for all the AWS resource and property types that are supported by AWS CloudFormation and AWS SAM, see AWS Resource and Property Types Reference in the AWS CloudFormation User Guide. と書かれているので CloudFormation で設定できるリソースなら何でもできるのだろう)。

Outputs:

AWS SAM でサーバーレスアプリケーションを構築すると、自動的に色々なものを作成するので、自分で作成したのか AWS SAM で作成したのかなど何でそのリソースが作成されたか?が分からなくなる。
それを避けるために、Outputs:に各リソースの説明や ARN が分かるように書くようにするのがいい(らしい)。

※他にも用途はあり、別スタックで値を参照(ImportValue)する、という用途にも使う。

Outputs: を記載しておく事で、以下のように AWS SAM の sam deploy を行った時にコンソールに値が出力される。

CloudFormation outputs from deployed stack
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 HelloWorldFunctionIamRole
Description         Implicit IAM Role created for Hello World function
Value               arn:aws:iam::xxxxxxxxxxxx:role/sam-app-HelloWorldFunctionRole-L66VGZMK2N4H

Key                 HelloWorldApi
Description         API Gateway endpoint URL for Prod stage for Hello World function
Value               https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/

Key                 HelloWorldFunction
Description         Hello World Lambda Function ARN
Value               arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:sam-app-HelloWorldFunction-kgSd4eLqjt3y
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Management Console から見る場合、Cloud Formation のスタックの出力というタブに以下の画像のように確認できる。

Environment:

今回は Type: AWS::Serverless::Function で Lambda 関数なので、ここに記載されているような Properties をもつ。

Environment は、環境変数の設定ができるキーで、シークレットのものは sam deploy --parameter-overrides でオーバライドする事で設定するようにしている。

・参考:AWS::Serverless::Function Environment

Policies:

Policies は、Lambda 関数の実行ロールにアタッチするポリシーを設定する事ができるキーで、記載方法はAWS SAM テンプレートのポリシーとロールを使用して Lambda 関数に権限を付与する方法を教えてください。の『インラインポリシードキュメントを使用する例:』が参考になる。

※基本的には IAM の JSON の構造を yaml に変換して書けばいい。
 複数の Sid, Action, Resource を設定したい場合には、それぞれ yaml のリスト(配列)記法で書けばいい。

・参考:AWS::Serverless::Function Examples
・参考:プログラマーのための YAML 入門 (初級編) 配列

Events:

関数をトリガーするイベントを設定する事ができるキーで、記載方法はEventSource Syntaxとそれぞれの Type のリンク先に書かれている。

今回は Cloud Watch Events なので Properties の中身は、CloudWatchEventのページに書かれている通り。 さらに今回は Code Commit に関するイベントなので、Cloud Watch Events のイベントパターンに設定する内容を yaml 形式で記述すればいい。

Role:

※SAM テンプレート上には明記していないが、Lambda の log を Cloud Watch Logs で確認できるようにする(Lambda の実行ロールに Cloud Watch Logs の権限を付与する)のために、自動的に AWSLambdaBasicExecutionRole というロールが付与される。

AWSLambdaBasicExecutionRole の中身は以下。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    }
  ]
}

・参考:Lambda 機能の AWS マネージドポリシー

その他

・!Sub "arn:aws:codecommit:ap-northeast-1:${AWS::AccountId}:*"
  AWS CloudFormation のスタック管理のための関数を使って、動的に AWS Account Id を設定するようにしている。これにより、異なる AWS 環境(Account Id が異なる環境)に対して同じテンプレートでインフラ構築できる。

・CodeUri: ./
 今回はディレクトリ構成が以下のようになっているので ./ でルートディレクトリ以下を指定している。

$ tree
.
├── README.md
├── config.yaml
├── go.mod
├── go.sum
├── lambda.go
└── template.yaml

・Value: !GetAtt SamGoLambdaFunction.Arn
 !GetAtt でリソースの属性値を取得できるので、上記の例では Lambda の ARN を取得している(取得できる属性値は、例えば Lambda であればAWS::Lambda::Function Fn::GetAttに書かれている)。

・参考:Fn::Sub

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

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

name: lambda-build-deploy-with-aws-sam

on:
  push:
    branches: [sam]

env:
  ARTIFACTS_BUCKET: aws-sam-cli-managed-default-samclisourcebucket-jqifq82awfzk

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

      - 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: Sam build
        run: sam build --use-container

      - name: Add config.yaml into build artifact
        run: cp config.yaml .aws-sam/build/SamGoLambdaFunction/config.yaml

      - name: Upload artifacts to artifact buckets
        run: |
          sam package \
            --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/checkout@v2
      - uses: aws-actions/setup-sam@v1
      - 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: Sam deploy
        run: |
          sam deploy \
            --template packaged-template.yaml \
            --parameter-overrides BacklogApiKey=${{ secrets.BACKLOG_API_KEY }} BacklogDomein=${{ secrets.BACKLOG_DOMEIN }} SlackApiToken=${{ secrets.SLACK_API_TOKEN }} \
            --no-confirm-changeset \
            --no-fail-on-empty-changeset

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

※今回は、Golang 製 Lambda のための CICD を GitHub Actions で構築してみたと同じような考え方で Build・Deploy を区切ったため、 sam package というコマンドを実行しているが、公式のGitHub アクションを使用したデプロイに書かれているようにシンプルなパイプラインにする事も可能。

・参考:aws-sam-cli で独自の環境変数を扱うには?

sam build

AWS のリソースを構築するためのもろもろ(AWS SAM テンプレート、アプリケーションコード、言語固有のファイル・依存関係など)を作成する。

実際に sam build を実行すると、以下のような log が出力される。

$ sam build
Building codeuri: C:/Users/user/xxxxx/go runtime: go1.x metadata: {} functions: ['SamGoLambdaFunction']
Running GoModulesBuilder:Build

Build Succeeded

Built Artifacts  : .aws-sam\build
Built Template   : .aws-sam\build\template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

※今回は --use-container というオプションを付けているが、これを付けると build を docker コンテナ内でやってくれるので、自分の環境に Go の環境構築をする必要がなくなるので便利。
 実際に sam build --use-container で build とすると以下のような log になる(ただし初回は docker image のサイズがそこそこ大きく 636MB あるので通信速度が遅いと build に時間がかかる・・・)。

$ sam build --use-container
Starting Build inside a container
Building codeuri: /home/yuta-katayama-23/Go runtime: go1.x metadata: {} functions: ['SamGoLambdaFunction']

Fetching public.ecr.aws/sam/build-go1.x:latest Docker container image...............................................................................................
Mounting /home/yuta-katayama-23/Go as /tmp/samcli/source:ro,delegated inside runtime container

Build Succeeded

以下省略

・参考:sam build
・参考:イメージリポジトリ
・参考:Install Docker Engine
・参考:Manage Docker as a non-root user

sam package

コードと依存関係に関する.zip ファイルを作成し、そのファイルを Amazon Simple Storage Service (Amazon S3) にアップロードする。 その後、AWS SAM テンプレートのコピーを return し、ローカルのアーティファクトへの参照を、アーティファクトをアップロードした S3 の場所に置き換える。

実際に sam package を実行すると、以下のような log が出力される(log に出力されている sam テンプレートが「AWS SAM テンプレートのコピーを return・・・」で return されたもの)。

$ sam package --s3-bucket aws-sam-cli-managed-default-samclisourcebucket-1b0rpp4kbz6ml
File with same data already exists at 3630b9934405076fac76422b01ac8a14, skipping upload
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Template for sam-app
Globals:
  Function:
    Timeout: 3
Resources:
  SamGoLambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: s3://aws-sam-cli-managed-default-samclisourcebucket-1b0rpp4kbz6ml/3630b9934405076fac76422b01ac8a14
      # 以下省略

・参考:sam package

sam deploy

sam テンプレートの内容に沿って AWS リソースを作成する。 内部的には Cloud Formation が動き、Cloud Formation の Stack を作成して各リソースを構築する。

実際の sam deploy 実行時の log はステップ 3: アプリケーションを AWSCloudを参照。

※初回の sam deploy 時には、aws-sam-cli-managed-default という Cloud Formation stack が作成され、この Stack で S3 の Bucket や Bucket のポリシーを作成する。
仮に誤って AWS SAM のテンプレートやアプリケーションコードのアーティファクトが保存される S3 を削除してしまった場合には、aws-sam-cli-managed-default という Stack を削除し、sam deploy をすれば S3 の Bucket の作成からやってくれる。

※sam deploy を実行する際には、設定が必要になるので sam deploy --guided というコマンドで対話形式で設定する事もできるが、 samconfig.toml を用意しておく事で設定を読み込ませることもできる( sam deploy --guided を実行した時に、config(samconfig.toml)を作成するか?を聞かられるので、そこで yes としても samconfig.toml を作成する事ができる)。

version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "sam-go-lambda"
s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-jqifq82awfzk"
s3_prefix = "sam-go-lambda"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
image_repositories = []

※sam deploy --parameter-overrides のようにしている部分については、SAM テンプレート内の Parameters:のパラメータの値を上書きしており、上書きする値を GitHub Actions の Secrets に設定する事で他の人から見えないように CICD パイプラインを構築している。

・参考:sam deploy

まとめとして

AWS SAM を使って Deploy をするという方法をやってみたが、AWS SAM を使うと IaC も同時にできるので 非常に便利だと思った。また、サーバーレスアプリケーションを構築する際には AWS SAM を使った方がいいと実感。今度は あえて Cloud Formation と あとは Terafform を使った CICD パイプラインを構築してみたい。

※sam build の際に、config.yaml を build 成果物(アーティファクト)に組み込む方法が分からず、cp コマンドで実行しているが、sam build のオプション等でできるのかは今後の課題。
  Node.js の Lambda で sam build をしてみたが、CodeUri で指定したディレクトリ以下全てのファイルが build アーティファクトに含まれていたので、go1.x の場合はダメ??なのかも・・・。

参考文献

GitHub AWS Serverless Application Model (SAM)
CloudFormation の参照周りで意識すべきポイント・Tips

おまけ

AWS CLI コマンドいう所の何をしているのか?を意識してみていくと結構わかりやすかった。 具体的には、今回作成した AWS SAM の template.yaml の Events・Policies とかは、AWS CLI コマンドでいう所の aws events put-rule, aws iam put-role-policy と同じか、と考えるイメージでやった。

・参考:チュートリアル: Hello World アプリケーションのデプロイ
・参考:コンテナ Lambda を AWS SAM でデプロイしよう !


■マガジンができました AWS DevOpsソリューション

■このライターの他の記事を読む

__________________________________

執筆者プロフィール: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/


みんなにも読んでほしいですか?

オススメした記事はフォロワーのタイムラインに表示されます!