GitHub にプッシュして Cloud Run に Cloud Build でオートデプロイできる環境を構築するまでをできるだけ CLI でやる 〜 Next.js 編 〜 シェルスクリプトもあるよ

正直インフラはよくわからんのですが、しばらくはこのままフロントエンドを中心として仕事をしていく可能性はまだ大いにあるわけで、まあそうなると多少はこういった知識も身に着けないとなとは考えてしまうわけですよ。今の時代。
まあ飽き性なので、流石に Web フルスタックなスキルに全振りするほどの気持ちはないのですけれども。

タイトルですべて書いちゃっていますが、これからつくる環境の動きを順番に説明すると以下のようになります。

1. Nuxt.js のプロジェクトを
2. GitHub にプッシュすると
3. Cloud Build でビルドされて
4. Cloud Run にデプロイされる

こんな感じですね。

なぜ GCP なのか？
なぜ Cloud Run なのか？

そんなのは気が向いたらからかつ、AWS への逆張り以外のなにものでもないのですが、それでもまあ、思想も使い心地も悪いものではなかったので、おすすめするほどの知識はないですが、なんていうかほら良かったから使ってみたら？程度の気持ちでこの記事を書いています。

そんなに時間はないし、大体わかってるからサクッと試したいって方は[シェルスクリプト](#6-おまけのシェルスクリプト)だけ試しても良きかも。

それでは、どうぞごゆるりと。



# 1. 前提
これがないと話を進めようもないので、用意ができてない方は対応をしてください。

- GitHub アカウントの登録
- Google Cloud Platform の登録 （登録する為には以下が必要です）
    - Google アカウントの登録
    - クレジットカード

（一応ですが、Git とか Node.js とかサービス以外のことは説明しないです）


# 2. できるだけ CLI で進める為に
一応たいていのことはブラウザとかの GUI で進めれるはずです。
今回はもともと社内 LT で 5 分で終わらせる想定で進めてたので、シェルスクリプトを書いてました。（まあ、エラーとか起きて失敗したんですけれどね。。。）

とうことで、この記事ではできるだけ CLI で対応する方向で話を進めます。
GitHub と GCP の CLI を使うために準備を先にしておきます。
すでに CLI が使える状態の方はこのステップを飛ばしてください。

## 2-1. GitHub CLI
これは、GitHub のプロジェクト作成くらいにしか使わないので「そこはブラウザでやるわ」って方はここを飛ばしてもらって問題ないです。

### 2-1-1. GitHub CLI のインストール
[GitHub CLI](https://cli.github.com/) に書いてあるとおりに、コマンドまたはダウンロードしてインストールしてください。
![CleanShot 2020-07-19 at 17.16.27@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/87dbad34-3b86-ab7a-c76a-280dc2ff41c2.png)

### 2-1-2. GitHub CLI のプロトコルを SSH に変更
これは GitHub CLI のデフォルトのプロトコルが HTTPS のためです。

```sh
$ gh config set git_protocol ssh
```

## 2-2. Google Cloud SDK
これも GitHub CLI 同様に、ブラウザ上で対応する方は不要ですので飛ばしても大丈夫です。
ただ、ブラウザ上で対応する方向はこの記事では説明しませんし、GCP は多少複雑ではあるのでよくわかっていない方は素直に対応するとよいです。
（CLI を使うのが素直なのかは疑問が残りますが）

### 2-2-1. Google Cloud SDK のインストール＆初期化
[Cloud SDK: コマンドライン インターフェース](https://cloud.google.com/sdk?hl=ja) から「クイックスタートを見る」でドキュメントが見れるので手順したがってインストール＆初期化してください。
![CleanShot 2020-07-19 at 17.46.52@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/b1fce8c7-9197-72aa-3d27-efe224139adb.png)



# 3. 必要な情報
今回は以下の情報が必要になります。
（プロジェクト名だけ GitHub にも関係があって、それ以外は GCP のみに関係のある情報です）

```
請求先アカウント ID: 0X0X0X-0X0X0X-0X0X0X （確認方法を後述します）
プロジェクト ID: sample-project （今回は GitHub と GCP を同じ命名で進めます）
プロジェクト番号: 000000000000 （確認方法を後述します）
コンテナイメージ名: sample-project-image
リージョン: asia-northeast1 （今回は東京にで進めます）
```

**プロジェクト ID** と **コンテナイメージ名** は好きなように命名してください。
ただし、 **プロジェクト ID は GCP 全体で一意**である必要があります。これ、けっこう重要です。
ですので、以降 `sample-project` と記載されている **プロジェクト ID** の部分は世界中で自分だけのユニークなものに書き換え＆読み替えて進めてくださいね。

**アカウント ID** は GCP への登録後に、**プロジェクト番号** は GCP のプロジェクト作成後に確認することができます。

つまり、ここまで手順通りに従っていれば **請求先アカウント ID** を確認することは可能になっています。
以下のコマンドを実行することで、「ACCOUNT_ID」に `0X0X0X-0X0X0X-0X0X0X` ような形式で **請求先アカウント ID** が表示されます。

```sh
$ gcloud alpha billing accounts list
```
![CleanShot 2020-07-19 at 18.00.27@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/7e563034-c994-6f6e-824b-f28b7f231211.png)

**プロジェクト番号** だけはプロジェクトを作成しないと確認できないので、必要な時に説明します。



# 4. プロジェクト作成からデプロイ環境の構築まで
色々と準備してきましたが、ここからが本番です。
心苦しいですが、どうしても一部ブラウザ上での操作が必要になってしまいます。
みなさん、すいません。

## 4-1. GitHub にプロジェクトを追加
**プロジェクト ID** でプロジェクトを作成。

```sh
$ gh repo create sample-project
$ cd sample-project
```

実行すると以下のように聞かれるので `Y` を押してください。
<img width="651" alt="CleanShot 2020-07-19 at 20.36.49@2x.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/6e5eadea-dded-4c13-e163-644037b6809a.png">
作成後にディレクトリに移動しておきます。

## 4-2. GCP にプロジェクトを作成
**プロジェクト ID** でプロジェクトを作成。

```sh
$ gcloud projects create sample-project --set-as-default
```

## 4-3. GitHub app の Google Cloud Build を有効化
ここです。そうここでブラウザを立ち上げる必要があります。
しぶしぶ以下のページから設定を進めていきます。

https://github.com/apps/google-cloud-build

とはいえほぼほぼ一本道なので難しいことはないかと思います。


### 4-3-1. Cloud Build の設定を開始
![CleanShot 2020-07-19 at 20.07.33@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/befb125f-044f-c718-c968-9c742c6f7c26.png)

### 4-3-2. インストールするアカウントまたはチームを選択
![CleanShot 2020-07-19 at 20.09.02@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/dbc49628-741b-7019-1446-109730411c6c.png) 

### 4-3-3. インストールするリポジトリを選択
![CleanShot 2020-07-19 at 20.10.52@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/0a83147a-155e-df9d-92f4-2b1433881053.png)

### 4-3-4. 接続先の GCP のプロジェクトを選択して同意する
![CleanShot 2020-07-19 at 20.12.27@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/b11844c7-d237-9240-8646-e6677195b1af.png)

### 4-3-5. 接続するリポジトリを選択
![CleanShot 2020-07-19 at 20.16.45@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/34865fdf-6b63-014e-a46e-63abfa46e845.png)

### 4-3-6. push トリガーの作成
![CleanShot 2020-07-19 at 20.18.10@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/8e7317d5-b44e-d9fc-8790-5e8d50ac8591.png)

### 4-3-7. 接続完了
![CleanShot 2020-07-19 at 20.18.51@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/fe395075-0bcc-cb86-9690-638cc9e3c0ba.png)

## 4-4. 請求先アカウントをプロジェクトに紐付ける
**プロジェクト ID** と **請求先アカウント ID** を使って紐付けます。

```sh
$ gcloud beta billing projects link sample-project --billing-account=0X0X0X-0X0X0X-0X0X0X
```

## 4-5. Google Cloud API を有効化
今回の環境に必要な 4 つの API を有効化します。

- Cloud Run
- Cloud Build
- Container Registry
- Resource Manager

```sh
$ gcloud services enable \
    run.googleapis.com \
    cloudbuild.googleapis.com \
    containerregistry.googleapis.com \
    cloudresourcemanager.googleapis.com
```

## 4-6. プロジェクトに権限を追加

### 4-6-1. プロジェクト番号の確認
以下のコマンドを実行することで、プロジェクトの一覧情報を確認することができ、「PROJECT_NUMBER」に **プロジェクト番号** が表示さています。

```sh
$ gcloud projects list
```
![CleanShot 2020-07-19 at 21.47.58@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/73093c95-35e0-34e7-4e35-b7b695d98ecf.png)


### 4-6-2. IAM で権限を追加
**プロジェクト ID** と **プロジェクト番号** を使ってプロジェクトに権限を追加します。

```sh
$ gcloud projects add-iam-policy-binding sample-project \
    --member "serviceAccount:000000000000@cloudbuild.gserviceaccount.com" \
    --role roles/run.admin

$ gcloud iam service-accounts add-iam-policy-binding \
    000000000000-compute@developer.gserviceaccount.com \
    --member="serviceAccount:000000000000@cloudbuild.gserviceaccount.com" \
    --role="roles/iam.serviceAccountUser"
```

Cloud Build でエラーが起きていて困っていたのですが、以下の記事を参考に解決できました。

> [Google Cloud Build + Google Cloud Run: Fixing “ERROR: (gcloud.run.deploy) PERMISSION_DENIED: The caller does not have …](https://phpnews.io/feeditem/google-cloud-build-google-cloud-run-fixing-error-gcloud-run-deploy-permission-denied-the-caller-does-not-have-permission)


## 4-7. Next.js プロジェクトを用意
なんでもよくはあるのですが、手っ取り早く作成できてビルドと動作を確認できるということで今回は Next.js をデプロイすることにしました。

### 4-7-1. Next.js プロジェクトを作成
```sh
$ npx next-create-app .
```

途中で以下のように聞かれますが、そのままエンターでよいです。
<img width="455" alt="CleanShot 2020-07-19 at 20.31.14@2x.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/d32448fe-9ad7-b6ef-d1dc-0ea37eebc70a.png">


### 4-7-2. cloudbuild.yaml の追加
Colud Build の設定をします。
**コンテナイメージ名** と **リージョン** を自身の設定に合わせてください。

```yaml
steps:
# コンテナイメージのビルド
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'gcr.io/$PROJECT_ID/sample-project-image:$COMMIT_SHA', '.']
# コンテナイメージを Container Registry に追加
- name: 'gcr.io/cloud-builders/docker'
  args: ['push', 'gcr.io/$PROJECT_ID/sample-project-image:$COMMIT_SHA']
# コンテナイメージを Colud Run にデプロイ
- name: 'gcr.io/cloud-builders/gcloud'
  args:
  - 'run'
  - 'deploy'
  - '$PROJECT_ID'
  - '--image'
  - 'gcr.io/$PROJECT_ID/sample-project-image:$COMMIT_SHA'
  - '--region'
  - 'asia-northeast1'  # リージョン
  - '--platform'
  - 'managed'
images:
- 'gcr.io/$PROJECT_ID/sample-project-image'
```

### 4-7-2. Dockerfile の追加
Cloud Run で実行される Docker の設定をします。

```
FROM node:latest

WORKDIR /src

COPY . .
RUN yarn install \
    --prefer-offline \
    --frozen-lockfile\
    --non-interractive \
    --production=false

RUN yarn build

CMD ["yarn", "start"]
```

### 4-7-３. .gcloudignore の追加
デプロイ時にアップロード不要のファイルを設定します。

```
.git
.gitignore

node_modules/
.next/
out/
```

### 4-7-4. package.json を変更
Next.js の起動時に Cloude Run のポート変数を設定するため、 `-p $PORT` の部分を追加します。

```json
{
  "scripts": {
    "start": "next start -p $PORT"
  },
}
```


## 4-8. GitHub にプッシュ
いつもどおりのファイルを追加して、コミットしてして、プッシュです。

```sh
$ git add .
$ git commit -m "init"
$ git push origin master
```

ここは GitHub Desktop とかいつもの GUI でやっても問題ないです。
すごいどうでもよいのですが、僕は [oh-my-zsh の git alias](https://github.com/ohmyzsh/ohmyzsh/wiki/Cheatsheet) 使ってるのでこんなんですね。

```sh
$ ga .
$ gcmsg "init"
$ ggp
```

## 4-9. Cloud Build によるビルドが実行される
問題がなければ GitHub にプッシュされたプロジェクトを、Cloud Build がビルドを開始しているはずです。
以下の Cloud Build のページから確認できると思います。

https://console.cloud.google.com/cloud-build/dashboard
※ 違うプロジェクトになっている場合は切り替えてください

このように実行中になります。
![CleanShot 2020-07-19 at 20.46.41@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/4641a396-fafe-2b3f-4c48-120fc575aa21.png)
詳細を見ることができ、すべて成功するとこのようになります。
![CleanShot 2020-07-19 at 20.49.00@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/5449fa4d-e48a-d583-ccc1-8ffa6d496344.png)

## 4-10. Cloud Run にデプロイされたコンテナの一般公開

###  4-10-1. デプロイされたコンテナの確認
デプロイに成功したコンテナは、以下の Cloud Run のページで確認できます。

https://console.cloud.google.com/run
※ 違うプロジェクトになっている場合は切り替えてください

成功するとこのよう表示されます。
![CleanShot 2020-07-19 at 20.52.58@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/72b888b1-6849-4fed-d7b9-7a24206e8f70.png)

###  4-10-2. コンテナを一般公開するために権限を追加
一般公開するには対象のコンテナを選択し、情報パネルを表示して「メンバーを追加」します。
![CleanShot 2020-07-19 at 20.56.48@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/ae35d90b-1497-3a0b-02fe-10c4dcbf3dc2.png)
次に、新しいメンバーに「allUsers」を入力して、ロールを選択で「Cloud Run 起動元」を選択します。
![CleanShot 2020-07-19 at 21.01.29@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/3ffeda32-460f-8846-6008-92166304f6a6.png)
最後に「保存」をします。
![CleanShot 2020-07-19 at 21.04.03@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/803018d2-c5d2-31df-aab1-c156b93f4a12.png)

###  4-10-3. 一般公開されたかの確認
コンテナの詳細ページを見ると URL が活性化しています。
（一般公開するまではリンクになっていないです）
![CleanShot 2020-07-19 at 21.07.26@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/321657bb-d400-4606-92f7-94366ad90ef7.png)
実際にリンク先を確認してみると Next.js が起動していることを確認できます。
<img width="830" alt="CleanShot 2020-07-19 at 21.10.05@2x.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/f14355f1-80d5-31b7-f064-5b85bb41cdff.png">

これで完了です。
あとはプロジェクトに変更を加えて、GitHub にプッシュして再度デプロイして試してみると良いかと思います。



# 5. プロジェクトの削除
そのまま使用するのであれば、このまま終了でよいです。
ただ試してみただけということであれば、料金が発生し続けるのでプロジェクトの削除までした方がよいかもです。

以下のページから削除することができます。

https://console.cloud.google.com/cloud-resource-manager

ただし、場合によってはそのままにした方がよいこともあります。理由としてはプロジェクトの作成数に上限があり、削除してもシャットダウンとなり削除保留中になります。そして 30 日後に削除されることになります。つまり、完全に削除されるまでは上限に含まれてしまうということです。
![CleanShot 2020-07-19 at 21.23.11@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25729/7e1b12c9-934f-b13f-11f1-ed041a4a9cd6.png)
色々と試したいということであれば、そのあたりを考えておくと良いかと思います。
「増加をリクエスト」することはできるようで、フォームからできるところまでは確認したのですが、試してないのでそのあたりは調べて教えてもらえると嬉しいです。
僕はこの記事を書きながら試してる途中で上限に達しました。



# 6. おまけのシェルスクリプト
そして、これをシェルスクリプトにしました。
LT 用に書いたものを使いやすく調整したものです。
（mac で書いたので windows とかは自分で頑張ってください）

https://gist.github.com/syuji-higa/bb7ce8c598f40fd84faf3076d2eb00d6
※ 簡単にコピーして使えるように請求先アカウントとプロジェクトの一覧を表示するようにしているので、実行するときは一応は気をつけてください

コンテナの一般公開まではしていないので、ブラウザから同じ手順で対応してください。

## 5-1 実行時の注意点
- 同じプロジェクト名が存在すると GitHub や GCP でのプロジェクト作成時に失敗します
- 途中で失敗すると作成したディレクトリに入ってしまいますので、再度実行する際はディレクトリを確認してください

まさに私が LT の時に失敗した内容です。
両方とも認識していたのですが、慌ててしまいました。。。
Cloud Build でのビルド時間を考えると、5 分という時間は本当にギリギリ間に合うかどうかだったので。

この記事をみている皆さんは慌てることもないので、どうぞゆっくり進めてください。
そんなに難しいことはないかと思いますので。



# 6. 所感
Cloud Run の話なんて 1 年以上前からあるし、正式サービスになったのも去年の話しだし、大してすごいこともしてない内容だから記事にするほどのことかなとか悩んだりはしたけれども、Cloud Build + Cloud Run は良いものだと感じることはできたし、フロントエンドからすると Vercel とか Netlify とかもっと気軽なサービスもあるけれども、まあ、これはこれでそこまでは大変じゃないし面白いよ的なことを伝えたいと思いつつ、この記事の一連の流れを通して書いている良い記事とかが見当たらなかったので、LT 用に雑に書いたスライドを記事ってやつに起こしてみましょうかね。ってことでここに至るわけです。

まあ、インフラをゴリゴリできるわけではないけれども、コンテナデプロイとかの今どきのインフラの一端に触れて感じることはできたのは、一応フロントエンドエンジニアとして生計を立てている身としても良き刺激になったかなと思う。前に VPS とかちまちま触ってた程度だからさ。

次は GKE（Google Kubernetes Engine）とかで遊ぼうかなって考えるところ。
まあ、飽き性なのでとりあえず全然関係ないの触ってそうだけどれども。
また良き話ができそうならば、その時にでも。ではでは。