AWS AppRunnerにデプロイする前に、GithubActionsを経由してテストを行う

[AWS App Runner Advent Calendar 2022](https://qiita.com/advent-calendar/2022/aws-app-runner)の14日目の記事です。

AWS AppRunnerはさまざまな言語に対応し始めていたり、GithHubのブランチを監視して自動デプロイしてくれたり、オートスケールもある程度できたり、インフラの高度な知識がなくてもデプロイができる大変便利なサービスです。

任意のブランチを監視して、コミットされたら自動デプロイされる機能はとても便利なのですが、間にテストなどを挟めないので、バグが残ったままのコードがデプロイされ得る状態です。

今回は、Github Actionsを経由してAppRunnerにデプロイしてみようと思います。

:::note warn
[問題点]
`awslabs/amazon-app-runner-deploy`を使っていますが、`AutoDeploymentsEnabled`が指定できないため、デフォルトの`true`となっています。
このままではGithub Actions側でデプロイする際に既にデプロイが開始しているのでコンフリクトしてしまいます。なので、この方法はまだ実運用は非推奨です。

この記事公開後に[プルリクエスト](https://github.com/awslabs/amazon-app-runner-deploy) をする予定です。近々対応されたら追記します。
:::

## 前提
- 開発環境と本番環境、そしてレビュー用の環境があるという過程で行います。
- 東京リージョンを使います
- 実行環境はNode.jsです
- 今回はコンテナリポジトリからのデプロイではなくソースコードデプロイを想定します

## 事前準備
### 1. GitHubと連携する
   この工程は、リポジトリ/Organizationのオーナーが行う必要があります。

https://docs.aws.amazon.com/apprunner/latest/dg/manage-connections.html?icmpid=docs_apprunner_console

### 2. 開発環境と本番環境のServiceを作る
固定のServiceとして、開発環境と本番環境用のServiceを事前に作っておきます。
サクッと`aws cli`から作っちゃいます。
このときに作ったarnを控えておきます。

:::note info
`{{}}`内はご自身の環境に読み替えてください
:::

:::note info
例えば`yarn`を使う僕の場合、`BuildCommand`は`yarn install && yarn build`、`StartCommand`は`yarn start:prod`としています
:::

```bash:開発環境
aws apprunner create-service --service-name {{service-name}}_develop --source-configuration \
              '{
                "AuthenticationConfiguration": {
                    "ConnectionArn": "{{Github Connection arn}}"
                },
                "AutoDeploymentsEnabled": false,
                "CodeRepository": {
                    "RepositoryUrl": "{{RepositoryUrl}}",
                    "SourceCodeVersion": {
                        "Type": "BRANCH",
                        "Value": "develop"
                    },
                    "CodeConfiguration": {
                        "ConfigurationSource": "API",
                        "CodeConfigurationValues": {
                            "Runtime": "NODEJS_16",
                            "BuildCommand": "{{依存関係の解決・ビルドコマンド}}",
                            "StartCommand": "{{アプリケーションを立ち上げるコマンド}}",
                            "Port": "3000",
                            "RuntimeEnvironmentVariables":
                                {
                                    "NODE_ENV": "production"
                                }
                        }
                    }
                }
              }'

```

```bash:本番環境
aws apprunner create-service --service-name {{service-name}}_production --source-configuration \
              '{
                "AuthenticationConfiguration": {
                    "ConnectionArn": "{{Github Connection arn}}"
                },
                "AutoDeploymentsEnabled": false,
                "CodeRepository": {
                    "RepositoryUrl": "{{RepositoryUrl}}",
                    "SourceCodeVersion": {
                        "Type": "BRANCH",
                        "Value": "main"
                    },
                    "CodeConfiguration": {
                        "ConfigurationSource": "API",
                        "CodeConfigurationValues": {
                            "Runtime": "NODEJS_16",
                            "BuildCommand": "{{依存関係の解決・ビルドコマンド}}",
                            "StartCommand": "{{アプリケーションを立ち上げるコマンド}}",
                            "Port": "3000",
                            "RuntimeEnvironmentVariables":
                                {
                                    "NODE_ENV": "main"
                                }
                        }
                    }
                }
              }'

```

### 3. Github Secretを設定する
workflowから読み取る環境情報や、シークレットなどをリポジトリSecretsに設定していきます。

:::note
本当は、`Environments Secrets`を使った方がいいのですが、読みやすさの便宜上割愛します
:::

> `AWS_APPRUNNER_ACCESS_KEY_ID`と`AWS_APPRUNNER_ACCESS_SECRET_KEY`はそれぞれ、`AWSAppRunnerFullAccess`ポリシーを付与したIAMユーザーのアクセスキーとシークレットキーです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/b3f6c88b-c269-e374-d4ac-31b8bc9378d0.png)

より良い命名があったら教えてください。

# Developにマージされたら開発環境のServiceにデプロイする
基本的なことしかしてません。developブランチにコミットされたらテストを実行し、テストがパスしたら先ほど作成した開発環境のServiceにデプロイします。

```yml:develop.yml
name: Test & Deploy to develop

on:
  push:
    branches:
      - "develop"

jobs:
  test:
    permissions:
      id-token: write
      contents: read
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Setup Node.js environment
      uses: actions/setup-node@v3.5.1
      with:
        node-version: 16.18.0
    - name: Run test
      run: |
        npm install
        npm run test
  deploy:
    needs: test
    environment: develop
    runs-on: ubuntu-latest
    steps:
        - uses: actions/checkout@v3

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

        - name: deploy to app runner
          run: |
            aws apprunner start-deployment --service-arn ${{ secrets.DEVELOP_APPRUNNER_SERVICE_ARN }}

```

参考

https://awscli.amazonaws.com/v2/documentation/api/latest/reference/apprunner/index.html

# PullRequestがOpenしたらレビュー用のサービスを作成する&コミットされたら更新する
PullRequestを発行したらレビュー用のサービスを作成します。
デプロイが完了したら、ホスティング先のURLをコメントで残します。
![スクリーンショット 2022-12-13 15.54.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256853/5a5c41be-5f86-b6c1-cf45-62b84335678e.png)

:::note
`wait-for-service-stability-seconds`でデプロイが完了するのを待機できます。
デプロイにかかる時間はアプリケーションの規模やコード量によって変わりますのでご自身で調整してみてください。
:::

```yaml:review-create.yml
name: Test & Create Review Service

on:
  pull_request:
    types: [ labeled, ready_for_review ]

jobs:
  test:
    permissions:
      id-token: write
      contents: read
    if: ${{ github.base_ref != 'main' }}
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Setup Node.js environment
      uses: actions/setup-node@v3.5.1
      with:
        node-version: 16.18.0

    - name: Test
      run: |
        npm install
        npm run test

  deploy:
    runs-on: ubuntu-latest
    needs: test
    env:
      PR_NUMBER: ${{ github.event.number }}
    outputs:
      service-url: ${{ steps.deploy-apprunner.outputs.service-url }}
    steps:
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          aws-region: "ap-northeast-1"
          aws-access-key-id: ${{ secrets.AWS_APPRUNNER_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_APPRUNNER_ACCESS_SECRET_KEY }}

      - name: Deploy to App Runner
        id: deploy-apprunner
        uses: awslabs/amazon-app-runner-deploy@main
        env:
          SERVER_PORT: 3000
          NODE_ENV: develop
        with:
          service: review-app_${{ github.event.repository.name }}_${{ env.PR_NUMBER }}
          source-connection-arn: ${{ secrets.APPRUNNER_GITHUB_CONNECTION_ARN }}
          repo: https://github.com/${{ github.repository }}
          branch: ${{ github.head_ref }}
          runtime: NODEJS_16
          build-command: yarn install && yarn build
          start-command: yarn start:prod
          port: ${{ env.SERVER_PORT }}
          region: ap-northeast-1
          cpu : 1
          memory : 2
          wait-for-service-stability-seconds: 600
          copy-env-vars: |
            NODE_ENV

  comment:
    runs-on: ubuntu-latest
    permissions: write-all
    needs: deploy
    env:
      PR_NUMBER: ${{ github.event.number }}
    steps:
      - uses: actions/checkout@v3

      - name: Comment Review App URL
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh pr comment ${{ env.PR_NUMBER }} --body "App runner URL https://${{ needs.deploy.outputs.service-url }}"

```

# PullRequestがMerge、Closeされたらレビュー用のサービスを削除する
コードレビューが完了し、役目を終えたサービスを削除することでクリーンアップします(そうしないと課金が止まりませんからね)

:::note warn
Runnning状態のものしか停止できません。
:::

```yaml:review-delete.yml
name: Delete Review Service

on:
  pull_request:
    types: [ closed ]

jobs:
  delete:
    runs-on: ubuntu-latest
    steps:
        - uses: actions/checkout@v3
        - name: Configure AWS Credentials
          uses: aws-actions/configure-aws-credentials@v1
          with:
            aws-region: "ap-northeast-1"
            aws-access-key-id: ${{ secrets.AWS_APPRUNNER_ACCESS_KEY_ID }}
            aws-secret-access-key: ${{ secrets.AWS_APPRUNNER_ACCESS_SECRET_KEY }}

        - name: Get Service Arn
          id: get-service-arn
          env:
            PR_NUMBER: ${{ github.event.number }}
          run: |
            SERVICE_ARN=`aws apprunner list-services --query "ServiceSummaryList[?ServiceName=='review-app_${{ github.event.repository.name }}_$PR_NUMBER']|[0].ServiceArn"`
            echo "service-arn=$SERVICE_ARN" >> $GITHUB_OUTPUT

        - name: Delete AppRunner Service
          run: |
            aws apprunner delete-service --service-arn ${{ steps.get-service-arn.outputs.service-arn }}

```
# 改善箇所
僕がどうにかできることではないですが、デプロイ中は何も操作ができないのは解消されると幅が広がりますね。


## 参考
https://awscli.amazonaws.com/v2/documentation/api/latest/reference/apprunner/index.html#cli-aws-apprunner

https://devblog.thebase.in/entry/2021/12/22/110000