GitHub Actions × Google Cloud Workload Identityを使ってCloud RunへデプロイしてSlackへ通知する

## はじめに
Advent Calendar 2023 2本目の投稿です。
以下の記事等でも何度か投稿しているCI/CD関連の投稿になります。

:::note info
* [GCPでJenkins Pipelineを使ってSonarQubeと連携させたCI/CD環境を構築し結果をSlackへ通知する](https://qiita.com/NagaokaKenichi/items/68df9ec9c43856948ae7)
* [GCPのCloud BuildでCI/CD環境を構築しSpring Bootアプリをビルド、デプロイする](https://qiita.com/NagaokaKenichi/items/c88fddbeb8ef56df7a51)
:::

今回は、Google Cloud Workload Identityを使ってGitHub ActionsからCloud RunへアプリをデプロイしてSlackへ通知する、ということをやってみたいと思います。

## 構築するCI/CD環境構成
今回構築するCI/CD環境構成は以下の通りです。

![Qiita_GitHubActions.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/c13f55dc-dc6c-7629-48a6-c92b1bb8b339.png)

#### トリガー
正直ここではなんでも良いのですが、今回は該当リポジトリにソースがPushされたことをトリガーに実行したいと思います。

#### ワークフロー
1. ビルドとテストを行いコンテナイメージの作成
2. 作成したコンテナイメージをArtifact Registryへ登録
3. Cloud Runへデプロ
3. デプロイ結果をSlackへ通知

## 事前準備
#### 1. Google Cloud側でWorkload Identityの作成
事前準備として、GitHub ActionsとGoogle Cloudを連携させるために、Google Cloud側でWorkload Identity　の設定をします。Workload Identityとは、Google Cloud外部のワークロードの中(GitHub ActionsやAWS、Azure等)からGoogle Cloudのサービスを利用するための認証設定になります。
[こちら](https://zenn.dev/cloud_ace/articles/7fe428ac4f25c8)を参考に設定させていただきました。

Workload Identityの設定内容としてはこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/7a13ef48-57b1-211f-34cd-e3638aaef645.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/4ad8e0f4-c939-1cb9-f623-60c94313fa06.png)

また、GitHub Actions用に作成したサービスアカウントの権限に一番下の形式でプリンシパルを追加する必要があります。

:::note warn
ここの追加をするのを忘れておりハマりました
:::

projets/の後ろはプロジェクト番号(下の設定図のプロジェクト番号）、attribute.repository/の後ろはGitHubのユーザー名となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/c8f6b4f3-26ed-1eb7-b375-ee3c2801cff0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/772d9dc9-6b9f-495d-0de1-9baa89150c20.png)

サービスアカウントのロールは以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/7fd44eeb-64cb-dba0-a128-87ddb0322bd7.png)

#### 2. Google Cloud側でArtifact Registryでリポジトリの作成
Artifact Registryでリポジトリを作成します。リポジトリ名や形式(今回はDockerイメージを利用するのでDockerを選択)、リージョンを指定して作成しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/d81287d4-13e2-d76a-4cd1-e5add5f948ed.png)

#### 3. GitHub Actions側でsecretの設定
何をsecretに持たせて何をenvに持たせるかは要検討ですが、今回は以下をsecretに持たせました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/37275878-f942-d64b-605f-26ab34aa8aab.png)
・ PROJECT_ID
Google CloudのプロジェクトIDです、dockerイメージをArtifact RegistryへPushする際に必要になります。

・ WIF_PROVIDER
Google Workload Identityのプロバイダの識別子です。以下の形式で設定します。
```
projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider
```
値はGoogle CloudのWorkload Identityの編集画面で確認できます（デフォルトのオーディエンスの下にあるURLのproject以降を指定します）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/046d418b-1100-369a-b78a-fde7e37ca9aa.png)

・ WIF_SERVICE_ACCOUNT
Google Cloud側で利用するサービスアカウントです。以下の形式で設定します。

```
my-service-account@my-project.iam.gserviceaccount.com
```

・ SLACK_WEBHOOK_URL
SlackのWebhookのURLです。[こちら](https://qiita.com/NagaokaKenichi/items/89305da92e79d45a84e5#1-slack%E5%81%B4%E3%81%A7%E9%80%9A%E7%9F%A5%E5%85%88%E3%81%AE%E3%83%81%E3%83%A3%E3%83%B3%E3%83%8D%E3%83%AB%E3%81%AEincoming-webhooks-url%E3%82%92%E5%8F%96%E5%BE%97)を参照ください。


## 構築してみる
#### 1. Dockerfileの作成
まずはDockerfileを作成します。今回は以前[「GCPでJenkins Pipelineを使ってSonarQubeと連携させたCI/CD環境を構築し結果をSlackへ通知する」](https://qiita.com/NagaokaKenichi/items/68df9ec9c43856948ae7#6-dockerfile%E3%81%AE%E4%BD%9C%E6%88%90)で作成したファイルを流用します。

```Dockerfile
# Spring BootアプリをビルドするためMaven/Java8のDockerイメージを利用
FROM maven:3.5-jdk-8-alpine AS builder

# ビルド時のワークディレクトリの設定
WORKDIR /app
COPY pom.xml .
COPY src ./src

RUN mvn package

# Spring Bootアプリの実行環境にopenjdk:8-jre-alpineを利用
FROM openjdk:8-jre-alpine

COPY --from=builder /app/target/Spring-Boot-RESTful-API-Test-Sample-1.0.0-SNAPSHOT.jar /app.jar

# Docker Run時にjavaコマンドでSpring Bootを起動(Embedded Tomcatを起動)
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"]
```

#### 2. GitHub Actions側でワークフローファイルの作成
続いて、GitHub Actions側でワークフローファイルの作成です。
構成としては、build、test、deployを行うジョブとSlackへ通知するジョブを分けています（1つのジョブで全部やっても良かったのですが、分けている意図は特にありません）。

```build-deploy-to-cloudrun.yml
name: build, test and deploy to cloud run
on:
  push:

jobs:
  build-test-deploy-to-cloudrun:
    runs-on: ubuntu-latest
    # Google Cloudの認証でgoogle-github-actions/authを利用する際に必要なpermissions設定
    # see. https://github.com/google-github-actions/auth?tab=readme-ov-file#usage
    permissions:
      id-token: write
      contents: read
    env:
      ARTIFACT_REGISTRY: github-actions
      REGION: asia-northeast1
      APP_NAME: spring-boot-restful-api-test
      
    steps:
      - uses: actions/checkout@v4
      - name: 'Google auth'
        uses: 'google-github-actions/auth@v1'
        with:
          # Google Cloud認証を通すためのInput、公式のコードはproject_idになっているがservice_account
          workload_identity_provider: '${{secrets.WIF_PROVIDER}}'
          service_account: '${{secrets.WIF_SERVICE_ACCOUNT}}'
          
      - name: 'Set up Cloud SDK'
        uses: 'google-github-actions/setup-gcloud@v1'
        with:
          version: '>= 379.0.0'
          
      - name: 'Docker auth'
        run: |-
          gcloud auth configure-docker ${{env.REGION}}-docker.pkg.dev
      
      - name: 'Build and push container'
        run: |-
          docker build -t "${{env.REGION}}-docker.pkg.dev/${{secrets.PROJECT_ID}}/${{env.ARTIFACT_REGISTRY}}/${{env.APP_NAME}}:${{github.sha}}" .
          docker push "${{env.REGION}}-docker.pkg.dev/${{secrets.PROJECT_ID}}/${{env.ARTIFACT_REGISTRY}}/${{env.APP_NAME}}:${{github.sha}}"

      - name: 'Deploy to Cloud Run'
        uses: 'google-github-actions/deploy-cloudrun@v0'
        with:
          service: ${{env.APP_NAME}}
          region: ${{env.REGION}}
          image: ${{env.REGION}}-docker.pkg.dev/${{secrets.PROJECT_ID}}/${{env.ARTIFACT_REGISTRY}}/${{env.APP_NAME}}:${{github.sha}}
    
  slack-notification:
    name: send-notification
    # needs指定をすると前のジョブが失敗した場合は実行されない。Slack通知は成功、失敗に限らず必ず実行させるため以下を指定することで常に通知されるよう設定
    if: always()
    # build-test-deploy-to-cloudrunジョブ後に実行したいためneeds指定
    needs: build-test-deploy-to-cloudrun
    runs-on: ubuntu-latest
    steps:
      - name: Send GitHub Action trigger data to Slack workflow failure
        if: ${{needs.build-test-deploy-to-cloudrun.result == 'failure'}}
        uses: slackapi/slack-github-action@v1.24.0
        with:
          payload: |
            {
              "text": "Build, Test and Deploy Failure."
            }
        env:
          SLACK_WEBHOOK_URL: ${{secrets.SLACK_WEBHOOK_URL}}
      - name: Send GitHub Action trigger data to Slack workflow success
        if: ${{needs.build-test-deploy-to-cloudrun.result == 'success'}}
        uses: slackapi/slack-github-action@v1.24.0
        with:
          payload: |
            {
              "text": "Build, Test and Deploy Success."
            }
        env:
          SLACK_WEBHOOK_URL: ${{secrets.SLACK_WEBHOOK_URL}}
```

## 動作確認
今回はPushをトリガーにしているので、適当にPushをしてGitHub Actionsが動作するか見てみます。当然ですが、実行はされます（一番上の実行中のがそれになります）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/ec77b4ff-4404-5cbe-9914-fb73403e436f.png)

少し待つと無事成功しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/b82e0b8c-0fa4-c94d-e33a-924f5575e349.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/c76ec2b7-5a49-adc1-769f-e0fe4954666f.png)

Slack側も確認してみます。こちらも無事通知が来ていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/5495d9b7-f92a-21ad-983f-eff4eb181548.png)


Google Cloud側でArtifact Registryも確認してみます。こちらも問題なくDockerイメージがPushされていました(何回も実行しているでたくさん表示されていますが、一番上のイメージが今回のものになります）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/1c862e9c-2245-2214-0062-c27e6a4911d2.png)

Cloud Run側も確認しデプロイされているか確認します。問題なくデプロイされていますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/b634bb1c-0c38-1eb8-1771-4212a1084ffb.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/9acad5cc-5652-4f3c-4509-ed83c8f96239.png)

実際に任意のAPIを叩いてみても期待通りのレスポンスが返ってきました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23101/579fbaa0-a1db-e1ef-54bd-9dca9dd72f1b.png)

以上です。

## 参考
* [GitHub Actionsを使用してCloud Runにデプロイする](https://cloud.google.com/blog/ja/products/devops-sre/deploy-to-cloud-run-with-github-actions)
* [Google-github-actions/auth](https://github.com/google-github-actions/auth?tab=readme-ov-file)
* [example-workflows](https://github.com/google-github-actions/example-workflows/blob/main/workflows/create-cloud-deploy-release/cloud-deploy-to-cloud-run.yml)
* [Github ActionsからOIDCでGoogle Container Registryにイメージをプッシュする](https://zenn.dev/jinugasachio/articles/ga_oidc_to_gcp)
* [About security hardening with OpenID Connect](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)