AWS CDK ✖︎ GitHub Actionsで実現するIaCのCI/CD ①

# はじめに
実務への応用を効かせようとIaCのCI/CDについて学習を進めるべく、手始めに、AWS CDKとサーバーレスアプリケーション（Lambda ✖︎ API Gateway）を使用した構成を実現しました。

また、AWS Codeシリーズを使用したCI/CDも検討しましたが、cdkに記述する内容が多くなってしまう点と、ミニマムかつシンプルに実装できる点からCI/CDにはGithub Actionsを使用しました。

備忘がてら実装の記録を残します！

今回作成したコードは以下のリポジトリにあげています。
- [severless-cdk-ci-cd](https://github.com/parupappa/severless-cdk-ci-cd)


# 開発環境

> - CDK : 2.72.1 (build ddbfac7)
> - TypeScript : typescript@4.9.5

# Stack
cdkのinitや最初に必要な手順は省略します。

メインとなるappは以下のようになっています。

```ts:lib/cdk-github-actions.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import { Construct } from 'constructs';

export class CdkGithubActionsStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here
    
    // Create a Lambda function with code from the "src/lambda" directory
    const hello = new lambda.Function(this, 'HelloHandler', {
      runtime: lambda.Runtime.NODEJS_16_X,
      code: lambda.Code.fromAsset('src/'),
      handler: 'hello.handler',
    });

    // defines an API Gateway REST API resource backed by our "hello" function.
    new apigateway.LambdaRestApi(this, 'GithubActionsEndpoint', {
      handler: hello,
      endpointTypes: [ apigateway.EndpointType.REGIONAL ],
    });

  }
}

```

# CI/CD
CI/CDを検討するにあたり、ブランチ戦略を決めます！

今回は、シンプルさを優先して`Github Flow`で実装しました。
（実務では`Git Flow`を使用する場合がほとんどだと思いますが）

ざっくりとパイプラインの流れを説明すると・・・

1. mainブランチから機能ブランチを作成し、プッシュ
1. github上でpull requestを出すと1つ目のトリガーが起動
1. cdkのbuildや単体テスト、スナップショットテストが実行
1. mainブランチへのマージトリガーに2つ目のトリガーが起動
1. cdkがAWS環境にデプロイされる

![スクリーンショット 2023-04-07 23.30.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669969/974ab87c-e9b9-5dfb-4d26-076e0dad1938.png)

パイプラインの実行ファイルを作成し、`cdk.yml`に内容を記載

```bash
$ mkdir .github/workflows && touch cdk.yml
```

```yml:.github/workflows/cdk.yml
name: cdk-github-actions
on:
  push:
    branches:
      - main
  pull_request:
jobs:
  build-test-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js 16.x
        uses: actions/setup-node@v1
        with:
          node-version: 16.x

      - name: Setup dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Unit tests
        if: contains(github.event_name, 'pull_request')
        run: npm run test:app

      - name: CDK Diff Check
        if: contains(github.event_name, 'pull_request')
        run: npm run cdk:diff
        env:
          AWS_DEFAULT_REGION: 'ap-northeast-1'
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Snapshot tests
        if: contains(github.event_name, 'pull_request')
        run: npm run test:snapshot -- -u

      - name: CDK Deploy
        if: contains(github.event_name, 'push')
        run: npm run cdk:deploy
        env:
          AWS_DEFAULT_REGION: 'ap-northeast-1'
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

```



# Snapshot Testing
cdkのテスト方法には、`Fine-grained tests`と`Snapshot tests`があるが、
ここでは、IaCのCI/CDの実行確認がメインなので、`Snapshot Testing`を使用して、テンプレートの差分を確認するだけにとどめたいと思います。

Snapshot Testingは、事前に記録されたスナップショット（今回は CloudFormation のテンプレートにあたる）に対して、テスト実行時の実装から作成した CloudFormation のテンプレートを検証（比較）します。ここで、差分がなければテスト結果OK、差分があればテスト結果NG となります。



```ts:cdk-githubactions.test.ts
import { App } from 'aws-cdk-lib';
import { Template } from 'aws-cdk-lib/assertions';
import { CdkGithubActionsStack } from '../lib/cdk-github-actions-stack';

test('SnapshotTest', () => {
  const app = new App();
  const stack = new CdkGithubActionsStack(app, 'CdkGithubActionsStack', {});
  const template = Template.fromStack(stack);
  expect(template.toJSON()).toMatchSnapshot();
});
```

# エラーへの対処
tsの型が合わず、`this`でエラーになってしまう


![スクリーンショット 2023-04-06 23.13.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669969/fea44edd-f0f1-624d-3342-3ca2d7e782dd.png)


**【結論】**
- `packge.json`のaws-libのバージョンを揃える必要がある
    
    1.`packege.lock.json`の削除
    2.`packege.json`からバージョンを変更
    3.`npm install `

![スクリーンショット 2023-04-07 8.25.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669969/d6820fca-4a62-9ad1-71ae-b1cfa298cbf9.png)


https://ktbbrk.hatenablog.com/entry/2021/02/11/144344

# 確認
コードを`commit → push　→ pull request作成　→ merge`し、GithubActionsを確認してみると・・・
それぞれしっかりとパイプラインが実行されています！

- pull request作成時
    ![スクリーンショット 2023-04-07 23.56.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669969/969eb8d5-bc8f-38ac-de2a-2799d5213ad1.png)

- mainブランチへマージ時
    ![スクリーンショット 2023-04-07 23.56.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669969/4bb78213-e53f-e9e9-9a29-1a6679269fd2.png)

# さいごに
IaCのCI/CDについて最もシンプルな構成から実現してみました。

次回はより実運用に近くなるよう、`開発環境,Staging環境,本番環境`を用意し、複数環境に対するIacのCI/CD方法をやってみたいと思います。


# 参考
## AWS CDK
https://www.youtube.com/watch?v=EpiGUb50bIE&t=1406s

https://www.youtube.com/watch?v=xLrCEHEYcCM

https://pages.awscloud.com/rs/112-TZM-766/images/CDK%E3%81%A7%E3%82%82%E3%83%86%E3%82%B9%E3%83%88%E3%81%8C%E3%81%97%E3%81%9F%E3%81%84.pdf

## Github Actions
https://docs.github.com/ja/actions/using-workflows/workflow-syntax-for-github-actions