💬

SAMパイプラインでCI/CDパイプラインを構築

2023/12/09に公開

はじめに

最近マイクロサービス化したタスクの実装でAWS SAMを用いて構築しています。
実装優先で進めていたため、手動デプロイで凌いできましたが、
ある程度実装が落ち着いてきたこともあり、CI/CD周りを実装しようと思ったところ、
SAMにCI/CDパイプラインを構築できるツールが組み込まれていることを知り、
使い勝手を試してみようと思います。

SAMのアプリケーションは以前ブログで書いたものを使用します。
https://zenn.dev/kat/articles/2a230e980284be

SAMパイプラインでできること

  • SAMパイプラインを用いることで開発環境と本番環境それぞれにデプロイを分ける2ステージデプロイのパイプラインが簡単に作成できます。
  • デプロイに必要なロールやリソースなどは対話形式で指定するか自動で作成してくれるため権限不足によるデプロイ失敗の手間を減らせます。

一言で言うと、上記のようなデプロイの準備作業とCI/CDを行うための定義ファイルを対話式に作成できるツールです。

SAMパイプラインが対応しているCI/CDサービス

  • AWS CodePipeline
  • Jenkins
  • GitLab CI/CD
  • GitHub Actions
  • Bitbucket Pipelines

例えば、AWS CodePipelineであれば、CodePipeline や EventBridgeルールなどのSAMテンプレートファイルが作成されます。
GitLabであれば.gitlab-ci.yml
GitHubであれば.github/workflowspipeline.yamlが作成されます。

※今回試してみるまでは、SAMパイプライン自体がパイプラインを生成から実行まですべてを行ってくれるものかと勘違いしておりましたが、実際のデプロイは各種CI/CDサービスで実行されます。

そのため、すでにSAMやCloudformationsで構築したデプロイ環境がある場合はあえてSAMパイプラインを用いずに流用するのも良いと思います。

CI/CDパイプライン構築用のSAMコマンド

以下2つのコマンドが提供されています。

  • sam pipeline bootstrap
      CI/CDを実行するために必要なAWSリソースを作成
  • sam pipeline init
      CI/CDパイプラインの構成ファイルを作成
sam pipeline --help
Usage: sam pipeline [OPTIONS] COMMAND [ARGS]...

  Manage the continuous delivery of the application

Options:
  -h, --help  Show this message and exit.

Commands:
  bootstrap  Generates the required AWS resources to connect your CI/CD system.
  init       Generates a CI/CD pipeline configuration file.

bootstrapコマンドでCI/CDに必要なリソースを作成

sam pipeline bootstrapで開始します。

sam pipeline bootstrap

sam pipeline bootstrap generates the required AWS infrastructure resources to connect
to your CI/CD system. This step must be run for each deployment stage in your pipeline,
prior to running the sam pipeline init command.

We will ask for [1] stage definition, [2] account details, and
[3] references to existing resources in order to bootstrap these pipeline resources.

大きく3つのセクションで構成されているようです。

  1. ステージ定義(開発、検証、本番などデプロイする単位をステージと称しています。)
  2. アカウント詳細(この
  3. パイプラインに必要なリソースの指定や作成
[1] Stage definition
Enter a configuration name for this stage. This will be referenced later when you use the sam pipeline init command:
Stage configuration name: dev

ステージの名称を設定します。pipeline initの際に指定することになります。
どの環境へデプロイするか、等でわかりやすい名前にするのが良いと思います。
ここでは、開発用としてdevとします。

[2] Account details
The following AWS credential sources are available to use.
To know more about configuration AWS credentials, visit the link below:
https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html                
        1 - Environment variables (not available)
        2 - default (named profile)
	〜 中略 〜
        9 - sdtoki (named profile)
        10 - sam (named profile)
        q - Quit and configure AWS credentials
Select a credential source to associate with this stage: 10
Associated account {AWSアカウントID} with configuration dev.

認証情報を指定します。ローカルのprofile情報から読み取っていると思われます。
この後作成するリソース作成が可能な権限を持っている必要があります。
適切なプロファイルを指定します。

Enter the region in which you want these resources to be created [ap-northeast-1]: 

リージョンを指定します。デフォルトで。

Select a user permissions provider:
        1 - IAM (default)
        2 - OpenID Connect (OIDC)
Choice (1, 2): 2

認証方法を選択します。
IAMかOIDCが選べます。
OIDCにします。

Select an OIDC provider:
        1 - GitHub Actions
        2 - GitLab
        3 - Bitbucket
Choice (1, 2, 3): 1

OIDCのプロバイダーを選択します。
今回はGitHub Actionsにしたいので 1 を選択

Enter the URL of the OIDC provider [https://token.actions.githubusercontent.com]: 

OIDCプロバイダーのURLを指定します。
デフォルト値がすでに設定されているので、そのままエンターで良いと思います。

Enter the OIDC client ID (sometimes called audience) [sts.amazonaws.com]: 

OIDCクライアントIDの指定です。
こちらもデフォルト値で。

Enter the GitHub organization that the code repository belongs to. If there is no organization enter your username instead: KaT0819

Githubの組織名かユーザ名を指定します。

Enter GitHub repository name: aws-step-function-sample

リポジトリ名を指定します。

Enter the name of the branch that deployments will occur from [main]: develop

このデプロイが実行される対象となるブランチを指定します。

[3] Reference application build resources
Enter the pipeline execution role ARN if you have previously created one, or we will create one for you []: 
Enter the CloudFormation execution role ARN if you have previously created one, or we will create one for you []: 
Please enter the artifact bucket ARN for your Lambda function. If you do not have a bucket, we will create one for you []: 
Does your application contain any IMAGE type Lambda functions? [y/N]:  

デプロイする際に必要となるリソースを作成していきます。
指定したい場合は入力します。何も入力せずエンターで自動作成してくれます。

  • パイプラインを実行するためのロールのARN
  • パイプライン実行時のCloudFormationの実行ロールのARN
  • Lambda関数のためのバケットのARN
[4] Summary
Below is the summary of the answers:
        1 - Account: {AWSアカウントID}
        2 - Stage configuration name: dev
        3 - Region: ap-northeast-1
        4 - OIDC identity provider URL: https://token.actions.githubusercontent.com
        5 - OIDC client ID: sts.amazonaws.com
        6 - GitHub organization: KaT0819
        7 - GitHub repository: aws-step-function-sample
        8 - Deployment branch:  develop
        9 - Pipeline execution role: [to be created]
        10 - CloudFormation execution role: [to be created]
        11 - Artifacts bucket: [to be created]
        12 - ECR image repository: [skipped]
Press enter to confirm the values above, or select an item to edit the value: 

入力した内容の確認です。
問題なければエンター、修正したい箇所があれば修正したい項目の数値を入れてエンター

This will create the following required resources for the 'dev' configuration: 
        - IAM OIDC Identity Provider
        - Pipeline execution role
        - CloudFormation execution role
        - Artifact bucket
Should we proceed with the creation? [y/N]: y
        Creating the required resources...

最終確認です。ここで最後の実行確認です。
y で実行 n でキャンセル可能です。
デフォルトはキャンセルなので、まちがってエンター押してしまうと最初からやり直しになります。。

実行開始するとCloudformationが実行され、以下のようなリソースが作成されています。

SAMパイプラインは2ステージデプロイのため、同様の手順でもう一つステージを作成します。
今度のステージはprdとします。
他は一緒なので内容は割愛します。

initコマンドでCI/CDパイプライン構成ファイルを生成

sam pipeline initで開始します。

sam pipeline init

sam pipeline init generates a pipeline configuration file that your CI/CD system
can use to deploy serverless applications using AWS SAM.
We will guide you through the process to bootstrap resources for each stage,
then walk through the details necessary for creating the pipeline config file.

Please ensure you are in the root folder of your SAM application before you begin.

Select a pipeline template to get started:
        1 - AWS Quick Start Pipeline Templates
        2 - Custom Pipeline Template Location
Choice: 1

パイプラインのテンプレートを選択します。
1を選択します。

Cloning from https://github.com/aws/aws-sam-cli-pipeline-init-templates.git (process may take a moment)                                         
Select CI/CD system
        1 - Jenkins
        2 - GitLab CI/CD
        3 - GitHub Actions
        4 - Bitbucket Pipelines
        5 - AWS CodePipeline
Choice: 3

CI/CDのサービスを選択します。
Githubなので3を選択します。

You are using the 2-stage pipeline template.
 _________    _________ 
|         |  |         |
| Stage 1 |->| Stage 2 |
|_________|  |_________|

Checking for existing stages...

2 stage(s) were detected, matching the template requirements. If these are incorrect, delete .aws-sam/pipeline/pipelineconfig.toml and rerun

This template configures a pipeline that deploys a serverless application to a testing and a production stage.

2ステージデプロイのパイプラインを構築していきます。
先ほどbootstrapコマンドで作成したステージの存在を確認し、この先の設定に進みます。
1つしかステージを作成していない場合はここでエラーが発生します。

What is the git branch used for production deployments? [main]:        

本番環境のブランチを指定します。mainなのでデフォルトで。

What is the template file path? [template.yaml]: 

SAMテンプレートを指定します。こちらもデフォルトで。

We use the stage configuration name to automatically retrieve the bootstrapped resources created when you ran `sam pipeline bootstrap`.

Here are the stage configuration names detected in .aws-sam/pipeline/pipelineconfig.toml:
        1 - dev
        2 - prd
Select an index or enter the stage 1's configuration name (as provided during the bootstrapping): 1

1つ目のステージを選択します。最初は開発用にしたいので、1を選択

What is the sam application stack name for stage 1? [sam-app]: sam-step-function-sample-dev

SAMデプロイ時のスタック名を指定します。ステージごとに名前を分けておくのが良さそう。

Stage 1 configured successfully, configuring stage 2.

Here are the stage configuration names detected in .aws-sam/pipeline/pipelineconfig.toml:
        1 - dev
        2 - prd
Select an index or enter the stage 2's configuration name (as provided during the bootstrapping): 2

次に2つ目のステージを選択します。本番用なので、2を選択

What is the sam application stack name for stage 2? [sam-app]: sam-step-function-sample-prd
Stage 2 configured successfully.

SAMデプロイ時のスタック名を指定します。

SUMMARY
We will generate a pipeline config file based on the following information:
        Select a user permissions provider.: OpenID Connect (OIDC)
        What is the git branch used for production deployments?: main
        What is the template file path?: template.yaml
        Select an index or enter the stage 1's configuration name (as provided during the bootstrapping): 1
        What is the sam application stack name for stage 1?: sam-step-function-sample-dev
        What is the pipeline execution role ARN for stage 1?: arn:aws:iam::〜〜
        What is the CloudFormation execution role ARN for stage 1?: arn:aws:iam::〜〜
        What is the S3 bucket name for artifacts for stage 1?: aws-sam-cli-managed-dev-pipeline-r-artifactsbucket-dwojsqvhcloc
        What is the ECR repository URI for stage 1?: 
        What is the AWS region for stage 1?: ap-northeast-1
        Select an index or enter the stage 2's configuration name (as provided during the bootstrapping): 2
        What is the sam application stack name for stage 2?: sam-step-function-sample-prd
        What is the pipeline execution role ARN for stage 2?: arn:aws:iam::〜〜
        What is the CloudFormation execution role ARN for stage 2?: arn:aws:iam::〜〜
        What is the S3 bucket name for artifacts for stage 2?: aws-sam-cli-managed-prd-pipeline-r-artifactsbucket-vw7fgewbnmwl
        What is the ECR repository URI for stage 2?: 
        What is the AWS region for stage 2?: ap-northeast-1
Successfully created the pipeline configuration file(s):
        - .github/workflows/pipeline.yaml

サマリが表示され、パイプラインのファイルが作成されます。
ここでは最終確認はなく作成まで進みます。

生成されたファイルの確認

ディレクトリ構成

├── .aws-sam
│   ├── build
│   ├── build.toml
│   └── pipeline
│       └── pipelineconfig.toml
├── .github
│   └── workflows
│       └── pipeline.yaml
├── samconfig.toml
└── template.yaml

関連するファイルのみ抜粋
※samconfig.toml はこの後消えることになりますので不要です。

.aws-sam/pipeline/pipelineconfig.toml

bootstrapコマンド実行時に作成されます。
作成したARNなどが保持されています。

version = 0.1
[default.pipeline_bootstrap.parameters]
oidc_provider_url = "https://token.actions.githubusercontent.com"
oidc_client_id = "sts.amazonaws.com"
github_org = "KaT0819"
github_repo = "aws-step-function-sample"
deployment_branch = "develop"
oidc_provider = "github-actions"
permissions_provider = "OpenID Connect (OIDC)"

[dev.pipeline_bootstrap.parameters]
pipeline_execution_role = "arn:aws:iam::〜〜"
cloudformation_execution_role = "arn:aws:iam::〜〜"
artifacts_bucket = "aws-sam-cli-managed-dev-pipeline-r-artifactsbucket-dwojsqvhcloc"
image_repository = ""
region = "ap-northeast-1"

.github/workflows/pipeline.yaml

initコマンドで作成されたファイルです。

pipeline.toml
name: Pipeline

on:
  push:
    branches:
      - 'main'
      - 'feature**'
  delete:
    branches:
      - 'feature**'

env:
  SAM_TEMPLATE: template.yaml
  TESTING_STACK_NAME: sam-step-function-sample-dev
  TESTING_PIPELINE_EXECUTION_ROLE: arn:aws:iam::〜〜
  TESTING_CLOUDFORMATION_EXECUTION_ROLE: arn:aws:iam::〜〜
  TESTING_ARTIFACTS_BUCKET: aws-sam-cli-managed-dev-pipeline-r-artifactsbucket-dwojsqvhcloc
  # If there are functions with "Image" PackageType in your template,
  # uncomment the line below and add "--image-repository ${TESTING_IMAGE_REPOSITORY}" to
  # testing "sam package" and "sam deploy" commands.
  # TESTING_IMAGE_REPOSITORY = '0123456789.dkr.ecr.region.amazonaws.com/repository-name'
  TESTING_REGION: ap-northeast-1
  PROD_STACK_NAME: sam-step-function-sample-prd
  PROD_PIPELINE_EXECUTION_ROLE: arn:aws:iam::〜〜
  PROD_CLOUDFORMATION_EXECUTION_ROLE: arn:aws:iam::〜〜
  PROD_ARTIFACTS_BUCKET: aws-sam-cli-managed-prd-pipeline-r-artifactsbucket-vw7fgewbnmwl
  # If there are functions with "Image" PackageType in your template,
  # uncomment the line below and add "--image-repository ${PROD_IMAGE_REPOSITORY}" to
  # prod "sam package" and "sam deploy" commands.
  # PROD_IMAGE_REPOSITORY = '0123456789.dkr.ecr.region.amazonaws.com/repository-name'
  PROD_REGION: ap-northeast-1

permissions:
  id-token: write
  contents: read
jobs:
  test:
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: |
          # trigger the tests here

  delete-feature:
    if: startsWith(github.event.ref, 'feature') && github.event_name == 'delete'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: aws-actions/setup-sam@v2
        with:
          use-installer: true

      - name: Assume the testing pipeline user role
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          aws-region: ${{ env.TESTING_REGION }}
          role-to-assume: ${{ env.TESTING_PIPELINE_EXECUTION_ROLE }}
          role-session-name: testing-packaging
          role-duration-seconds: 3600
          role-skip-session-tagging: true

      - name: Delete feature branch stack
        env:
          FEATURE_BRANCH_NAME: ${{ github.event.ref }}
        run: |
          sam delete \
            --stack-name $(echo ${FEATURE_BRANCH_NAME##*/} | tr -cd '[a-zA-Z0-9-]') \
            --region ${TESTING_REGION} \
            --no-prompts

  build-and-deploy-feature:
    # this stage is triggered only for feature branches (feature*),
    # which will build the stack and deploy to a stack named with branch name.
    # https://github.com/actions/setup-python
    # https://github.com/aws-actions/configure-aws-credentials#notice-node12-deprecation-warning
    if: startsWith(github.ref, 'refs/heads/feature')
    needs: [test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: aws-actions/setup-sam@v2
        with:
          use-installer: true
      - run: sam build --template ${SAM_TEMPLATE} --use-container

      - name: Assume the testing pipeline user role
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          aws-region: ${{ env.TESTING_REGION }}
          role-to-assume: ${{ env.TESTING_PIPELINE_EXECUTION_ROLE }}
          role-session-name: feature-deployment
          role-duration-seconds: 3600
          role-skip-session-tagging: true

      - name: Deploy to feature stack in the testing account
        shell: bash
        run: |
          sam deploy --stack-name $(echo ${GITHUB_REF##*/} | tr -cd '[a-zA-Z0-9-]') \
            --capabilities CAPABILITY_IAM \
            --region ${TESTING_REGION} \
            --s3-bucket ${TESTING_ARTIFACTS_BUCKET} \
            --no-fail-on-empty-changeset \
            --role-arn ${TESTING_CLOUDFORMATION_EXECUTION_ROLE}

  build-and-package:
    if: github.ref == 'refs/heads/main'
    needs: [test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: aws-actions/setup-sam@v2
        with:
          use-installer: true

      - name: Build resources
        run: sam build --template ${SAM_TEMPLATE} --use-container

      - name: Assume the testing pipeline user role
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          aws-region: ${{ env.TESTING_REGION }}
          role-to-assume: ${{ env.TESTING_PIPELINE_EXECUTION_ROLE }}
          role-session-name: testing-packaging
          role-duration-seconds: 3600
          role-skip-session-tagging: true

      - name: Upload artifacts to testing artifact buckets
        run: |
          sam package \
            --s3-bucket ${TESTING_ARTIFACTS_BUCKET} \
            --region ${TESTING_REGION} \
            --output-template-file packaged-testing.yaml

      - uses: actions/upload-artifact@v3
        with:
          name: packaged-testing.yaml
          path: packaged-testing.yaml

      - name: Assume the prod pipeline user role
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          aws-region: ${{ env.PROD_REGION }}
          role-to-assume: ${{ env.PROD_PIPELINE_EXECUTION_ROLE }}
          role-session-name: prod-packaging
          role-duration-seconds: 3600
          role-skip-session-tagging: true

      - name: Upload artifacts to production artifact buckets
        run: |
          sam package \
            --s3-bucket ${PROD_ARTIFACTS_BUCKET} \
            --region ${PROD_REGION} \
            --output-template-file packaged-prod.yaml

      - uses: actions/upload-artifact@v3
        with:
          name: packaged-prod.yaml
          path: packaged-prod.yaml

  deploy-testing:
    if: github.ref == 'refs/heads/main'
    needs: [build-and-package]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: aws-actions/setup-sam@v2
        with:
          use-installer: true
      - uses: actions/download-artifact@v3
        with:
          name: packaged-testing.yaml

      - name: Assume the testing pipeline user role
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          aws-region: ${{ env.TESTING_REGION }}
          role-to-assume: ${{ env.TESTING_PIPELINE_EXECUTION_ROLE }}
          role-session-name: testing-deployment
          role-duration-seconds: 3600
          role-skip-session-tagging: true

      - name: Deploy to testing account
        run: |
          sam deploy --stack-name ${TESTING_STACK_NAME} \
            --template packaged-testing.yaml \
            --capabilities CAPABILITY_IAM \
            --region ${TESTING_REGION} \
            --s3-bucket ${TESTING_ARTIFACTS_BUCKET} \
            --no-fail-on-empty-changeset \
            --role-arn ${TESTING_CLOUDFORMATION_EXECUTION_ROLE}

  integration-test:
    if: github.ref == 'refs/heads/main'
    needs: [deploy-testing]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: |
          # trigger the integration tests here

  deploy-prod:
    if: github.ref == 'refs/heads/main'
    needs: [integration-test]
    runs-on: ubuntu-latest
    # Configure GitHub Action Environment to have a manual approval step before deployment to production
    # https://docs.github.com/en/actions/reference/environments
    # environment: <configured-environment>
    steps:
      - uses: actions/checkout@v3
      - uses: aws-actions/setup-sam@v2
        with:
          use-installer: true
      - uses: actions/download-artifact@v3
        with:
          name: packaged-prod.yaml

      - name: Assume the prod pipeline user role
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          aws-region: ${{ env.PROD_REGION }}
          role-to-assume: ${{ env.PROD_PIPELINE_EXECUTION_ROLE }}
          role-session-name: prod-deployment
          role-duration-seconds: 3600
          role-skip-session-tagging: true

      - name: Deploy to production account
        run: |
          sam deploy --stack-name ${PROD_STACK_NAME} \
            --template packaged-prod.yaml \
            --capabilities CAPABILITY_IAM \
            --region ${PROD_REGION} \
            --s3-bucket ${PROD_ARTIFACTS_BUCKET} \
            --no-fail-on-empty-changeset \
            --role-arn ${PROD_CLOUDFORMATION_EXECUTION_ROLE}

作成されたpipeline.yaml内容をざっくり確認します。

トリガー対象

  • mainブランチのプッシュ
  • featureで始まるブランチのプッシュ
  • featureで始まるブランチの削除

developブランチを指定したつもりでしたが、なぜかfeature**になってしまっていました。
テスト用と本番用で環境変数がそれぞれ定義されており、
ステージ1がテスト用、ステージ2が本番用となっています。

定義されているジョブの確認

便宜上、feature**をテスト用ブランチ、mainを本番用ブランチと表現します。

  • test: テスト用、本番用ブランチにプッシュされた場合にテストを実行します。
  • delete-feature: テスト用ブランチが削除された場合に、関連するスタックを削除します。sam deleteが実行されます。
  • build-and-deploy-feature:
    テスト用ブランチがプッシュされ、testジョブが完了した場合に実行されます。
    ビルドしてデプロイします。
  1. sam buildが実行されます。
  2. sam deployが実行されます。
  • build-and-package:
    本番用ブランチがプッシュされ、testジョブが完了した場合に実行されます。
  1. sam buildが実行されます。
  2. テスト用のバケットにビルドしたファイルをアップロードします。
  3. 本番用のバケットにビルドしたファイルをアップロードします。
  • deploy-testing:
    本番用ブランチがプッシュされ、build-and-packageジョブが完了した場合に実行されます。
    テスト環境にデプロイします。

  • integration-test:
    本番用ブランチがプッシュされ、deploy-testingジョブが完了した場合に実行されます。
    本番デプロイ前のテストを実行します。

  • deploy-prod:
    本番用ブランチがプッシュされ、integration-testジョブが完了した場合に実行されます。
    本番環境にデプロイします。

ARNなどをGitHubシークレット化

yaml内で環境変数として定義されていますが、ARNの名前などがベタ書きされているため、
それらの情報をGitHubのシークレットに保存して、その値を参照するように修正します。

リポジトリを開き、Settings > Secrets and variables の Actions を開きます。

「Repository secrets」の「New repositody secret」で追加していきます。


追加しました。

yamlファイルをシークレット参照に書き換えます。
以下抜粋

env:
  SAM_TEMPLATE: template.yaml
  TESTING_STACK_NAME: ${{ secrets.TESTING_STACK_NAME }}
  TESTING_PIPELINE_EXECUTION_ROLE: ${{ secrets.TESTING_PIPELINE_EXECUTION_ROLE }}
  TESTING_CLOUDFORMATION_EXECUTION_ROLE: ${{ secrets.TESTING_CLOUDFORMATION_EXECUTION_ROLE }}
  TESTING_ARTIFACTS_BUCKET: ${{ secrets.TESTING_ARTIFACTS_BUCKET }}
  TESTING_REGION: ap-northeast-1
  PROD_STACK_NAME: ${{ secrets.PROD_STACK_NAME }}
  PROD_PIPELINE_EXECUTION_ROLE: ${{ secrets.PROD_PIPELINE_EXECUTION_ROLE }}
  PROD_CLOUDFORMATION_EXECUTION_ROLE: ${{ secrets.PROD_CLOUDFORMATION_EXECUTION_ROLE }}
  PROD_ARTIFACTS_BUCKET: ${{ secrets.PROD_ARTIFACTS_BUCKET }}
  PROD_REGION: ap-northeast-1

デプロイ確認

yamlファイルをプッシュします。
ブランチ名はfeature/sam-pipelineとしました。

GitHub Actionsのイメージ

※一発でデプロイできなかったので対処

むしろこちらの解消に時間がかかりました。。

pipeline.yamlのディレクトリ階層違い

複数のSAMを1つのリポジトリで管理している想定で試していたので、
SAMのリポジトリ構成がプロジェクト直下にSAMアプリケーションを配置していないことから、
作成されるyamlファイルがリポジトリ直下に作成されませんでした。
pipeline.yamlの各アクションにworking-directoryを指定して参照ディレクトリ位置を調整して解決

Goのビルドでエラー

      - name: Build resources
        run: sam build --template ${SAM_TEMPLATE} --use-container

エラー内容

Error: GoModulesBuilder:Resolver - Path resolution for runtime: provided of binary: go was not successful

sam buildでエラーが発生

→ ローカルでも試してみましたが、--use-containerを指定したコンテナでのビルドがうまく行かないようです。
LambdaのRuntime問題なのか、いろいろ試しましたがうまく行きません。
AWS CLIの最新化、SAM CLIの最新化もしてみましたが結果は変わらず。
コンテナビルドするとうまくいかないので--use-containerオプションを外して解決

Assumeロール権限が不足

      - name: Assume the testing pipeline user role
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          aws-region: ${{ env.TESTING_REGION }}
          role-to-assume: ${{ env.TESTING_PIPELINE_EXECUTION_ROLE }}
          role-session-name: feature-deployment
          role-duration-seconds: 3600
          role-skip-session-tagging: true

エラー内容

(node:5491) NOTE: We are formalizing our plans to enter AWS SDK for JavaScript (v2) into maintenance mode in 2023.

Please migrate your code to use AWS SDK for JavaScript (v3).
For more information, check the migration guide at https://a.co/7PzMCcy
(Use `node --trace-warnings ...` to show where the warning was created)
Error: Not authorized to perform sts:AssumeRoleWithWebIdentity

ここは2点修正ありました。

  • 1点目
    aws-actions/configure-aws-credentials@v1-node16
    アクションのバージョンが古いため、上げないといけない旨の警告が出ているため、
    公式見るとv4の例が出ていたので、v4にしました。
    https://github.com/aws-actions/configure-aws-credentials

この修正でエラーが発生せずに実行はされるのですが、何回もリトライし、タイムアウトになってしまい、結果的にsts:AssumeRoleWithWebIdentityの権限がないという同じエラーが発生。

bootstrapコマンドで作成されたIAMロールの信頼関係を確認したところ、GitHub ActionsとIAMロールの信頼関係の設定に差異があることが分かりました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::{アカウントID}:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "ForAllValues:StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:KaT0819/aws-step-function-sample:ref:refs/heads/develop",
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                }
            }
        }
    ]
}

Conditionで指定しているブランチの指定がdevelopとなっています。
token.actions.githubusercontent.com:sub": "repo:KaT0819/aws-step-function-sample:ref:refs/heads/develop
GitHub Actionsで動作しているのはfeatureブランチのため、これでは許可されません。
これはテンプレート作成バグではないだろうか・・・
対話形式で進めている最中にdevelopを指定したので、GitHub Actionsのファイルもdevelopであることを期待していたので、yamlファイルのfeature**を追加します。

            "Condition": {
                "ForAllValues:StringLike": {
                    "token.actions.githubusercontent.com:sub": [
		        "repo:KaT0819/aws-step-function-sample:ref:refs/heads/develop",
			"repo:KaT0819/aws-step-function-sample:ref:refs/heads/feature**"
		    ],
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                }
            }

テスト環境デプロイ時にエラー

      - name: Deploy to feature stack in the testing account
        shell: bash
        run: |
          sam deploy --stack-name $(echo ${GITHUB_REF##*/} | tr -cd '[a-zA-Z0-9-]') \
            --capabilities CAPABILITY_IAM \
            --region ${TESTING_REGION} \
            --s3-bucket ${TESTING_ARTIFACTS_BUCKET} \
            --no-fail-on-empty-changeset \
            --role-arn ${TESTING_CLOUDFORMATION_EXECUTION_ROLE}

エラー内容

Error: Cannot use both --resolve-s3 and --s3-bucket parameters in non-guided deployments. Please use only one or use the --guided option for a guided deployment.

デプロイコマンドのs3-bucketresolve-s3は同時に使えないとのこと。
resolve-s3はコマンドに書いてないので何だろうと思いましたが、
ローカルでデプロイした際に作成されたsamconfig.tomlもプッシュしていたので、
こちらの設定ファイルの内容も見てしまってそうな雰囲気を感じたので、
samconfig.tomlを削除してプッシュ。
これが正解でした。

これでようやくビルドが通るようになりました。

本番環境デプロイ時にエラー

Assuming role with OIDC
Assuming role with OIDC
Assuming role with OIDC
Error: Could not assume role with OIDC: Not authorized to perform sts:AssumeRoleWithWebIdentity

またもやAssumeRoleの不足でエラー。
mainブランチデプロイ時に、develop側にも何かしようとしているため、許可しているブランチの条件に一致せずエラーです。
やはりテンプレートが・・・
mainデプロイ時はテスト環境へのアクセスは不要なので、テスト関係のアクションを除去しようかと思いました。
が、出来上がったテンプレートの動作を一旦確認したいと思い、必要なブランチを許可しました。

修正内容はテスト環境デプロイ時の対策と同様、role-to-assumeに指定しているIAMロールの信頼関係へ必要なブランチを追加してあげて解決。

アーティファクトのダウンロードに失敗

actions/download-artifact@v3によるダウンロードが失敗しました。

Run actions/download-artifact@v3
Starting download for packaged-testing.yaml
Error: Unable to find any artifacts for the associated workflow

遡ってみると、actions/download-artifact@v3によるアップロードで警告が出ています。

Run actions/upload-artifact@v3
Warning: No files were found with the provided path: packaged-testing.yaml. No artifacts will be uploaded.

やろうとしてることは、sam packageコマンド実行時にtemplate.yamlpackaged-testing.yamlという名前指定で出力し、
このpackaged-testing.yamlをアップロード。
次のジョブでpackaged-testing.yamlをダウンロードして、デプロイに使用しています。

色々試した結果、先のディレクトリ階層違いが影響しており、
アップロード時にパスが不適切な部分を修正し、アップロードの警告は消えました。

しかし、ダウンロードしようとするとファイルが取れずエラー。こちらもパスを修正します。

パス関係のみ抜粋すると下記のようになりました。
あまり関係ない部分はアクションのみに省略しています。

  build-and-package:
    if: github.ref == 'refs/heads/main'
    needs: [test]
    defaults:
      run:
        working-directory: ./sam-step-function-sample
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: aws-actions/setup-sam@v2
      - name: Build resources
        run: sam build --template ${SAM_TEMPLATE}

      - name: Assume the testing pipeline user role
        uses: aws-actions/configure-aws-credentials@v4

      - name: Upload artifacts to testing artifact buckets
        run: |
          sam package \
            --s3-bucket ${TESTING_ARTIFACTS_BUCKET} \
            --region ${TESTING_REGION} \
            --output-template-file packaged-testing.yaml

      - uses: actions/upload-artifact@v3
        with:
          name: packaged-testing.yaml
          path: ./sam-step-function-sample/packaged-testing.yaml

      - name: Assume the prod pipeline user role
        uses: aws-actions/configure-aws-credentials@v4

      - name: Upload artifacts to production artifact buckets
        run: |
          sam package \
            --s3-bucket ${PROD_ARTIFACTS_BUCKET} \
            --region ${PROD_REGION} \
            --output-template-file packaged-prod.yaml

      - uses: actions/upload-artifact@v3
        with:
          name: packaged-prod.yaml
          path: ./sam-step-function-sample/packaged-prod.yaml

  deploy-testing:
    if: github.ref == 'refs/heads/main'
    needs: [build-and-package]
    defaults:
      run:
        working-directory: ./sam-step-function-sample
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: aws-actions/setup-sam@v2
      - uses: actions/download-artifact@v3
        with:
          name: packaged-testing.yaml

      - name: Assume the testing pipeline user role

      - name: Deploy to testing account
        run: |
          sam deploy --stack-name ${TESTING_STACK_NAME} \
            --template ../packaged-testing.yaml \
            --capabilities CAPABILITY_IAM \
            --region ${TESTING_REGION} \
            --s3-bucket ${TESTING_ARTIFACTS_BUCKET} \
            --no-fail-on-empty-changeset \
            --role-arn ${TESTING_CLOUDFORMATION_EXECUTION_ROLE}

defaultsでworking-directoryを指定しています。
samのコマンドではworking-directoryの指定が効いてくれるのですが、
actions/upload-artifactのアクションには効いてくれず、ファイルが存在しないエラーが発生していました。
actions/download-artifactも同様です。

最終系がこちら
https://github.com/KaT0819/aws-step-function-sample/blob/main/.github/workflows/pipeline.yaml

まとめ

  • SAM Pipelineを使用することでCI/CDパイプラインのテンプレートが簡単に作成できます。
  • OIDCの信頼関係構築やデプロイ時の権限周りが簡単につくれるのは良い。
  • 作成されたテンプレートは若干構成が怪しい。
    • ポリシーの信頼関係とGitHub Actionsの対象ブランチが一致していません。
    • コンテナでのsam buildが失敗します。(※Go特有な要因ではあります)
    • 本番用のデプロイフローのはずが、テスト用と本番用を同じフローで続けてデプロイしています。

まったくのゼロ知識でCI/CDが構築できるわけではなかったのが残念でした。
IAM認証であればすんなりいけるかもしれません。
OIDCの場合、AWS側のロールとActionsの定義のずれを見つけるのは何を作られるかを知っていないと解決は難しそうな気がしました。
この辺は今後のSAMパイプラインの進化に期待ですね。

今回のSAMパイプラインを検証したリポジトリはこちら。
Actionsの履歴がひどいことになっていますw
https://github.com/KaT0819/aws-step-function-sample

参考サイト

レスキューナウテックブログ

Discussion