[GCP]CloudBuild + CloudKMS + GitHubでタグプッシュデプロイを実現する

## やりたいこと
- `GoogleCloudBuild`を利用
- ソースコードは`GitHub`で管理
- `GitHub`のリポジトリにタグ`Push`→`GoogleAppEngine`にデプロイ
- `CloudBuild⇆GitHub`のSSH鍵は`GoogleKeyManagementService`を利用
![GAE+CB+KMS+GitHub.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/31fef7b7-4a39-b416-66f4-7746c8be2cdb.jpeg "ToBe-Architecture")

## 前提
- GCP（Google Cloud Platform）のアカウントを所持している
- GCPにプロジェクトを作成できる
- GitHubのアカウントを所持している
- サンプルで利用する言語はGo言語
- 投稿者の環境はMacOS(intel, Catalina)

## サンプルのディレクトリ構成
```terminal:cloudbuild-helloworld
.
├── .github_cloudbuild.enc
├── .known_hosts
├── submodule # submodule
|   └── submodule_dummy.txt
├── app.yaml
├── cloudbuild.yaml
└── helloworld.go

```

## 事前準備
1. GitHubにリポジトリの作成(private) ※既存のリポジトリを利用する場合は`Owner`権限が必要
2. GCPにプロジェクトを作成

### Cloud KMS(Cloud Key Management Service API)の設定
- APIの有効化
![KMS検索.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/af67ee87-df4a-9433-7daa-11e13aa802ad.png "KMS検索")
![KMSAPI有効化.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/2468e760-a0c7-d109-27cf-b5773837f1cb.png "KMS有効化")
- Keyringの作成(後述)
- Keyの作成(後述)

### App Engine Applicationの作成(今回初めてGAEを使う人)

```terminal
$ gcloud app create

Please choose the region where you want your App Engine application 
located:

 [1] asia-east2    (supports standard and flexible and search_api)
 [2] asia-northeast1 (supports standard and flexible and search_api)
 [3] asia-northeast2 (supports standard and flexible and search_api)
 [4] asia-northeast3 (supports standard and flexible and search_api)
 [5] asia-south1   (supports standard and flexible and search_api)
 [6] asia-southeast2 (supports standard and flexible and search_api)
 [7] australia-southeast1 (supports standard and flexible and search_api)
 [8] europe-central2 (supports standard and flexible)
 [9] europe-west   (supports standard and flexible and search_api)
 [10] europe-west2  (supports standard and flexible and search_api)
 [11] europe-west3  (supports standard and flexible and search_api)
 [12] europe-west6  (supports standard and flexible and search_api)
 [13] northamerica-northeast1 (supports standard and flexible and search_api)
 [14] southamerica-east1 (supports standard and flexible and search_api)
 [15] us-central    (supports standard and flexible and search_api)
 [16] us-east1      (supports standard and flexible and search_api)
 [17] us-east4      (supports standard and flexible and search_api)
 [18] us-west2      (supports standard and flexible and search_api)
 [19] us-west3      (supports standard and flexible and search_api)
 [20] us-west4      (supports standard and flexible and search_api)
 [21] cancel
Please enter your numeric choice:  3 # 好きなロケーションのインデックスを入力する。この場合はasia-northeast2

Creating App Engine application in project [cloudbuild-qiita] and region [asia-northeast2]....done.                                                                                                                                                    
Success! The app is now created. Please use `gcloud app deploy` to deploy your first app.
```

### App Engine Admin APIの設定
- APIの有効化
![スクリーンショット.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/217e719a-959f-b590-b89b-c94fd5d4c3e9.png "AdminAPI検索")
![AppEngineAdminAPI有効化.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/d6c2d79b-5e37-ac97-a000-05f95225c3af.png "AdminAPI有効化")

### Cloud Buildの設定
- CloudBuildトリガーの作成&GitHubリポジトリの接続
![CloudBuildTrigger.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/94280545-c302-bc65-7753-7dfc3e2c4431.png)
![Trigger設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/28043279-d103-c5f3-1d15-b767c16ec9ab.png)
![リポジトリ接続.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/d225765f-3e78-a754-cf48-aa89415d769e.png)
![リポジトリ接続２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/5aea70eb-3d29-9deb-7dc9-4598d62fa281.png)
![GitHubモーダル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/b54c5481-24cc-1fa4-1b1f-2688d2b9e35a.png)
![GitHubモーダル２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/6be3c629-0d72-b6c2-7ba0-0fa0bcaa8628.png)
![リポジトリ選択.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/cc7247c0-384c-bb24-b9ae-484dda6a0e84.png)
![リポジトリ接続同意＆接続.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/651fbe0d-787b-ef45-2a60-62a9030b67d3.png)
![Trigger設定2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/947c3c60-49e8-4a42-abec-f053de3f5b67.png)
![Trigger設定3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/baafffc7-251f-f69c-9691-817884f54f39.png)

### IAM(Cloud Identity and Access Management)の設定
- cloudbuild.gserviceaccount.comアカウントへ下記権限の付与
 + `AppEngineデプロイ担当者`：デプロイに必要
 + `AppEngineサービス管理者`：デプロイ後のトラフィック変更に必要
 + `CloudBuildサービスアカウント`：元々付与されている
 + `クラウドKMS暗号鍵の復号化`：GitHubとの連携時KMSを利用する場合には必要
 + `サービスアカウントユーザー`：新しいバージョンをデプロイするために必要, IAMからは追加できず、CloudBuildの設定から有効にする
 + `ストレージ管理者`：GitHubリポジトリのミラーリングに必要

### GitHubの設定
- Adminロールの設定(CloudBuildの接続に利用するGitHubアカウント)

## 手順
#### KMSを利用したGitHubSSH鍵の暗号化(※コードブロック内大文字は自由入力)
##### 1. 意図しないプロジェクトへの接続を防ぐため、config再セット

```terminal
$ gcloud config set project PROJECT_NAME
(PROJECT_NAMEは連携設定をするプロジェクト名)
```

##### 2. 暗号鍵のキーリングを作成

```terminal
$ gcloud kms keyrings create KEYRING \ 
--location LOCATION
(KEYRINGは設定したいkeyringの名称)
```

```terminal:example
$ gcloud kms keyrings create github-deploy-keyring \
$ --location global
```

##### 3. 暗号鍵のキーを作成

```terminal
$ gcloud kms keys create KEY \
--keyring KEYRING \
--location LOCATION \
--purpose encryption
(KEYは設定したいkeyの名称, KEYRINGは手順2で設定したkeyringの名称, LOCATIONはkeyを保管したいCloudの場所)
```

```terminal:example
$ gcloud kms keys create github-key \
--keyring github-deploy-keyring \
--location global \
--purpose encryption
```

##### 4. SSH鍵の作成

```terminal
$ cd ~/.ssh
$ ssh-keygen -t ed25519 -C ”your_mail@email.sample”
$ Enter file in which to save the key: id_ed25519
$ Enter passphrase (empty for no passphrase):
$ Enter same passphrase again:
```
下記メッセージが出れば成功

```terminal
Your identification has been saved in id_ed25519.
Your public key has been saved in id_ed25519.pub.
```

##### 5. ssh-agentへの登録
バックグラウンドで`ssh-agent`を開始

```terminal
$ eval "$(ssh-agent -s)" 
> Agent pid 59566
```

`~/.ssh/config`ファイルの確認

```terminal
$ open ~/.ssh/config
> The file /Users/you/.ssh/config does not exist.
```
上記エラーの時はファイルが存在しないので、ファイルを作成する

```terminal
$ touch ~/.ssh/config
```
`~/.ssh/config` ファイルを開いて今回の接続情報を作成する

```text:~/.ssh/config
Host cloudbuild-qiita  # 好きな名前を設定, 汎用の場合は'*'
  AddKeysToAgent yes
  UseKeychain yes
  IdentityFile ~/.ssh/id_ed25519  # id_ed25519キーの場所と名前がデフォルト以外の場合はここを置き換える
```

##### 6. SSH 秘密鍵を`ssh-agent`に追加して、パスフレーズをキーチェーンに保存

```terminal
$ ssh-add -K ~/.ssh/id_ed25519
```

##### 7. 作成したSSH鍵をGitHubへ登録
公開鍵をクリップボードへコピー

```terminal
$ pbcopy < id_ed25519.pub
```
- GitHubのプロフィール画像をクリックし、続いて`Settings`をクリック
- ユーザ設定サイドバーで`SSH and GPG keys`をクリック
- `[Add SSH key]`をクリック
- `[Title]`フィールドで、新しいキーを説明するラベルを追加
- キーを`[Key]`フィールドに貼り付ける
- `[Add SSH key]`をクリック

##### 8. KMSを利用したSSH鍵の暗号化

```terminal
$ gcloud kms encrypt \
--key KEY \
--keyring KEYRING \
--location LOCATION \
--plaintext-file id_ed25519 \
--ciphertext-file CIPHERTEXT_FILE
(KEY/KEYRING/LOCATIONは手順3までで設定したもの, CIPHERTEXT_FILEは設定したい名称)
```

```terminal:example
$ gcloud kms encrypt \
--key github-key \
--keyring github-deploy-keyring \
--location global \
--plaintext-file id_ed25519 \
--ciphertext-file .github_cloudbuild.enc
```

##### 9. 暗号化したSSH鍵をプロジェクトフォルダ内に移動させる

```terminal
$ mv CIPHERTEXT_FILE ~/go/src/github.com/YOUR_PROJECT_DIRECTORY/
(CIPHERTEXT_FILEは手順8で設定した名称、YOUR_PROJECT_DIRECTORYはCloudBuildでデプロイしたいプロジェクトのディレクトリPATH)
```

```terminal:example
$ mv .github_cloudbuild.enc ~/go/src/github.com/Rererr/cloudbuild-helloworld/
```
##### 10.下記コマンドで、プロジェクトフォルダに.known_hostファイルを作成

```terminal
$ ssh-keyscan -t rsa github.com > ~/go/src/github.com/YOUR_PROJECT_DIRECTORY/.known_hosts
```

```terminal:example
$ ssh-keyscan -t rsa github.com > ~/go/src/github.com/Rererr/cloudbuild-helloworld/.known_hosts
```

##### 11. プロジェクトディレクトリに必要なファイル群の確認
- `CIPHERTEXT_FILE` ex.) .github_cloudbuild.enc
- `.known_hosts`

#### `cloudbuild.yaml`の設定
1. `SSH鍵`の複合化を実行, 複合化したキーを`/root/.ssh/id_ed25519`へ保存
1. `/root/.ssh/id_ed25519`の権限変更
1. `/root/.ssh/config`へ接続情報の書き込み
1. `known_hosts`の移動
1. `submodule update --init`の実行
1. `app deploy`の実行
1. 最新19バージョンを残し、古いバージョンの削除を実行

```yaml:cloudbuild.yaml
steps:
  - name: "gcr.io/cloud-builders/gcloud"
    args:
      - --project=$PROJECT_ID
      - kms
      - decrypt
      - --ciphertext-file=.github_cloudbuild.enc
      - --plaintext-file=/root/.ssh/id_ed25519
      - --location=global
      - --keyring=github-deploy-keyring
      - --key=github-key
    volumes:
      - name: "ssh"
        path: /root/.ssh
    id: 'decrypt-ssh'

  - name: "gcr.io/cloud-builders/git"
    entrypoint: "bash"
    args:
      - "-c"
      - |
        chmod 600 /root/.ssh/id_ed25519
        cat <<EOF >/root/.ssh/config
        Hostname github.com
        IdentityFile /root/.ssh/id_ed25519
        EOF
        mv .known_hosts /root/.ssh/known_hosts
    volumes:
      - name: "ssh"
        path: /root/.ssh
    waitFor:
      - 'decrypt-ssh'
    id: 'git-ssh'

 - name: gcr.io/cloud-builders/git
    args: ["submodule", "update", "--init"]
    volumes:
      - name: "ssh"
        path: /root/.ssh
    waitFor:
      - 'git-ssh'
    id: 'git-submodule-update'

  - name: "gcr.io/cloud-builders/gcloud"
    args:
      - "app"
      - "deploy"
      - "--project=$PROJECT_ID"
      - "app.yaml"
    waitFor:
      - 'git-submodule-update'
    timeout: 3600s

  - name: "gcr.io/cloud-builders/gcloud"
    entrypoint: "bash"
    args:
      - "-c"
      - "gcloud app versions list | sort -rk 4,4 | tail -n +20 | awk '{print $2;}' | xargs -I {} gcloud app versions delete {}"
      
timeout: 3600s 
```

#### `app.yaml`の記載(本筋じゃないのでruntimeのみ記載)

```yaml:app.yaml
runtime: go115
```
#### `hello.go`の作成(本筋じゃないのでHello, World!を返すだけのHTTP Server)

```go:helloworld.go
package main

import (
        "fmt"
        "log"
        "net/http"
        "os"
)

func main() {
        http.HandleFunc("/", handle)
        port := os.Getenv("PORT")
        if port == "" {
                port = "8080"
        }
        log.Printf("Listening on port %s", port)
        if err := http.ListenAndServe(":"+port, nil); err != nil {
                log.Fatal(err)
        }
}

func handle(w http.ResponseWriter, r *http.Request) {
        if r.URL.Path != "/" {
                http.NotFound(w, r)
                return
        }
        fmt.Fprint(w, "Hello world!")
}
```

### GitHubへタグプッシュ

```terminal
$ git branch # 念の為現在のブランチの確認
$ git tag dev-2021053001 # tagの作成
$ git push origin dev-2021053001 # tagのpush
```

### CloudBuildでビルド&デプロイができているか確認
![ビルドデプロイ実行中.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/a31b00e8-84ce-ee55-893d-f4b571913a54.png)
![ビルドデプロイ完了.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/8da03744-b8d7-7088-3bbc-053ff86c2638.png)

### AppEngineへのデプロイ確認
![デプロイ確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/a41637aa-c3f7-5422-f0b7-989d2105a6c5.png)
Hello world!が表示されていれば成功
![HelloWorld.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/120df54b-ed9b-39d7-f4be-f3a40befbf9d.png)

### 後片付け
#### プロジェクトの削除(今回のために作成したプロジェクトならこれが一番早くて確実)
![プロジェクト削除1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/0b475181-c992-aaf8-6acf-a9a3d828e3b2.png)
![プロジェクト削除2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/a1c8bc47-1950-6502-bd8e-0c4d6075106a.png)
![プロジェクト削除3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/eca8be9e-ab8b-f1a2-918f-95d0ec230a43.png)

#### KMS(プロジェクトを消せないなら)
KMSの課金は、アクティブな鍵に対して行われるので、鍵を非アクティブに変更します
![KMS削除.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/cafd215c-0f91-1889-6a4c-ba195d2615cb.png)
![KMS削除2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/c0252f4c-7a3f-3742-7b33-c9dcf4d04de3.png)
![KMS削除3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/294664fc-22d7-d521-ccf2-cb7ca5d65dc4.png)
![KMS削除4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/ff1d410a-92b8-bf3f-17c5-bfb47ea02371.png)

#### GitHub
今回のために作成したSSH鍵の削除
![GitHubSSH削除.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309580/8e598f17-8e59-56ee-2611-d68f0069a87b.png)
あと、リポジトリも不要なら消しておきましょう！

#### `~/.ssh/config`
今回追記した部分は削除しておきましょう

```text:~/.ssh/config　下記部分は削除
Host cloudbuild-qiita
     AddKeysToAgent yes
     UseKeychain yes
     IdentityFile ~/.ssh/id_ed25519
```

## あとがき
思ったよりもめちゃくちゃ長くなりました....
もし不明な点や追記希望などありましたら、コメントや編集リクエストいただけると嬉しいです。