Google CloudのCloud Shellエディタで開発をし、Cloud Buildを使ってCloud Runで公開する

# 概略
最近Google Cloudの資格の勉強をして、Cloud Runが面白いなと思ったので、Google Cloud上でコードを書いて、Cloud Buildでビルドをし、Cloud Runで公開するまでの一連の流れを試してみる。

# Cloud Run
Function as a Serviceのコンテナ版のようなサービス。スパイクアクセスがあってもかなり素早くスケールしてくれるらしい。
1リクエストごとに1コンテナ割り当ててるとリソース効率悪い場合は、1コンテナごとにいくつリクエストを受けるかも設定可能。
また、リビジョンごとに割合でトラフィックを振り分けることもできるので、最新リビジョンに徐々にトラフィック切り替えて、問題があれば切り戻すのも簡単にできる。
公式ページは[こちら](https://cloud.google.com/run?hl=ja "Cloud Run")

# ひとまずお試し
### 開発
CloudRunTestというプロジェクトを作成し、Cloud Shellを実行する。
![Screenshot 2024-01-07 at 12.49.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149416/ef18a95d-ed37-1cbc-78e4-cb696fe830ac.png)

Cloud Shellにて、`~/work/cloudruntest`ディレクトリを作成する。
```sh
mkdir -p ~/work/cloudruntest
```
エディタを開くボタンを押し、Cloud Shell エディタを開く。
![Screenshot 2024-01-07 at 12.52.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149416/0ecfc833-4510-eda2-ff6f-93dde5782da5.png)

さきほど作成した`~/work/cloudruntest`ディレクトリを開き、開発を始める。
`package.json`を以下の内容で作成する。
```json:package.json
{
  "name": "helloworld",
  "description": "Simple hello world sample in Node",
  "version": "1.0.0",
  "private": true,
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "engines": {
    "node": ">=16.0.0"
  },
  "author": "Google LLC",
  "license": "Apache-2.0",
  "dependencies": {
    "express": "^4.17.1"
  }
}
```

また、`index.js`を以下の内容で作成する。
```js:index.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  const name = process.env.NAME || 'World';
  res.send(`Hello ${name}!`);
});

const port = parseInt(process.env.PORT) || 8080;
app.listen(port, () => {
  console.log(`helloworld: listening on port ${port}`);
});
```

### ビルド & デプロイ
Cloud Shellに戻り、ビルドとデプロイを開始する。以下を実行する。
```sh
gcloud run deploy
```
インタラクティブにいろいろ聞かれるので、プロジェクト名はデフォルトで進み、権限付与の許可が求められた場合はyを入力する。
実行するリージョンは任意だが、us-central1などを選択する。
非認証アクセスも許可しておく。
問題なく進んだら、最後にService URLが表示されるので、メモっておく。

### 確認
上記で表示されたService URLにアクセスする。
無事`Hello World!`が表示された。

### コード修正と再デプロイ
`Helo Wrold!`ではなく、`Hello Cloud Run!`が表示されるように修正してみる。
Cloud Shell エディタもしくはCloud Shell上で、`index.js`の5行目を以下のように修正する。
```js:index.js
  const name = process.env.NAME || 'Cloud Run';
```

その後、Cloud Shellにて再度ビルドとデプロイを行う。以下コマンドを実行する。
```sh
gcloud run deploy
```
また初回と同じようなことを聞かれるので、同じ選択をする。
完了したら、同じURLにアクセスする。
無事`Hello Cloud Run!`という表示になっていることが確認できた。

### リビジョン確認とトラフィック分割
Google Cloudのウェブページにアクセスし、Cloud Runサービスを選択する。
![Screenshot 2024-01-07 at 13.20.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149416/27570949-359b-bc69-ed60-4734019b62d7.png)

デプロイしたサービスを選択肢、リビジョンタブを選ぶ。
初回デプロイしたリビジョンと、新たにデプロイしたリビジョンの2つの行が表示されていることが確認できる。
![Screenshot 2024-01-07 at 9.37.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149416/09e737a6-8f27-e46f-2137-ffd5cbeb9530.png)

トラフィック分割のため、**操作**から**トラフィックを管理**を選択する。
![Screenshot 2024-01-07 at 9.37.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149416/cc6633d7-a5b0-3f5b-84c9-91d77a3789c5.png)

最新リビジョンと初回リビジョンで50%の振り分けをする。
![Screenshot 2024-01-07 at 9.38.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149416/2fa1dd17-cea6-676b-74e0-fec904fd9e92.png)

その後、ちょっと待ってからURLにアクセスする。リロードしてくと、`Hello World!`と`Hello Cloud Run!`のどちらかが表示されることが確認できる。

# ローカルでのテスト
### Cloud Shell エディタを使ったローカルテスト
さきほどコードを書く際に使用したCloud Shell エディタを表示する。
左側のアイコンの中からCloud Codeを選択する。
Cloud Runのタブを展開し、プロジェクトとして、今回作成したものを選ぶ。
Cloud Runタブの右側に実行っぽいアイコン(ホバーすると*Run App on Local Cloud Run Emulator*と表示される)があるので、それをクリックする。
![Screenshot 2024-01-07 at 13.01.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149416/704a593d-f362-8b0a-9c44-a453dfe946d3.png)

初回のみ頑張っていろいろ環境作ってくれるので時間がかかるが、やがて以下のメッセージが表示される。
```sh
**************URLs*****************
Deploy completed

Forwarded URL from service cloudruntest: http://localhost:8080
Update succeeded
***********************************
```

表示されているURLがlocalhostなので一見アクセスできないような気がするが、気にせず開いてみるとlocalhostではないURLに飛ばされて、そちらはアクセス可能。最新のコードの内容が表示されるので、ローカル環境のテストになっているようだ。

ちなみにこのURLはpublicではないようで、Incognito TabなどにURL貼り付けた場合はアクセス制限かかっていることが確認できる。

# Cloud Buildを使ったビルドと公開
### Dockerfileの準備
さきほどは`gcloud run deploy`が裏で良いようにやってくれたようだが、本来Cloud Runはコンテナイメージを実行するので、イメージの作成をしないといけない。
ということで、イメージ作成のためのDockerfileを作る。
GoogleCloud公式の[サンプルリポジトリ](https://github.com/GoogleCloudPlatform/nodejs-docs-samples)があるので、そこから拝借。
```Dockerfile:Dockerfile
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# [START cloudrun_helloworld_dockerfile]
# [START run_helloworld_dockerfile]

# Use the official lightweight Node.js image.
# https://hub.docker.com/_/node
FROM node:18-slim

# Create and change to the app directory.
WORKDIR /usr/src/app

# Copy application dependency manifests to the container image.
# A wildcard is used to ensure copying both package.json AND package-lock.json (when available).
# Copying this first prevents re-running npm install on every code change.
COPY package*.json ./

# Install production dependencies.
# If you add a package-lock.json, speed your build by switching to 'npm ci'.
# RUN npm ci --only=production
RUN npm install --only=production

# Copy local code to the container image.
COPY . ./

# Run the web service on container startup.
CMD [ "node", "index.js" ]

# [END run_helloworld_dockerfile]
# [END cloudrun_helloworld_dockerfile]
```
(Dockerfileに詳しくないので勘違いがあるかもしれないが、Dockerfileの記載はどの部分がどの環境での操作なのかよくわからない。上記だとCOPYの第一引数がローカル環境で、CMDがコンテナ実行環境で、その他はビルド環境なんだよね？)

### Cloud Buildでビルド
作成したDockerfileを使ってCloud BuildにてイメージのビルドとArtifact Registryへpushをする。
Artifact Registryに登録するので、登録先のリポジトリとパッケージの情報が必要。それらの情報をコマンドで取るのは面倒なので、Google Cloudのウェブページから取ってくる。
ウェブページからArtifact Registryサービスを選択する。
![Screenshot 2024-01-07 at 13.11.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149416/887a52ee-bef1-702d-9e3c-376933b0a7e7.png)

お試しの手順で勝手に作成された`cloud-run-source-deploy`があるはずなので、そちらを選択し、これまた勝手に作成された`cloudruntest`があることを確認する。
これで所望の情報が集まったので、Cloud Shell上で以下を実行し、ビルドとArtifact Registryへのpushを行う。

```sh
gcloud builds submit --tag us-central1-docker.pkg.dev/cloudruntest-410500/cloud-run-source-deploy/cloudruntest:latest
```

問題なく終われば、Artifact Registryのページをリロードすると最新のイメージが登録されていることが確認できる。

ちなみにウェブサイトをたどるのが嫌ならば、以下でリポジトリリストを取り、
```sh
gcloud artifacts repositories list
```
以下でpackage listを取れば良さそう。
```sh
gcloud artifacts packages list --repository cloud-run-source-deploy --location us-central1
```

### コマンドでデプロイ
Artifact Registryに登録されたイメージを以下のコマンドでCloud Runにデプロイする。
```sh
gcloud run deploy cloudruntest --image us-central1-docker.pkg.dev/cloudruntest-410500/cloud-run-source-deploy/cloudruntest:latest --region=us-central1
```

Cloud Buildのウェブサイトをリロードすると、無事にデプロイされていることが確認できる。

### Cloud Buildでビルドとデプロイ
Cloud Buildではyamlファイルに基づいてステップでの操作実行ができるようだ。さきほどの手順では2つのコマンドを使っていたが、今回はビルドとArtifact Registryへのpush、およびCloud RunをCloud Buildにて一括で行う。

Cloud Shell エディタなどでcloudbuild.yamlを作成し、以下を記載する。
```yaml:cloudbuid.yaml
steps:
# Build the container image
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'us-central1-docker.pkg.dev/cloudruntest-410500/cloud-run-source-deploy/cloudruntest:latest', '.']
# Push the container image to Artifact Registry
- name: 'gcr.io/cloud-builders/docker'
  args: ['push', 'us-central1-docker.pkg.dev/cloudruntest-410500/cloud-run-source-deploy/cloudruntest:latest']
# Deploy the image to Cloud Run
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
  entrypoint: gcloud
  args: ['run', 'deploy', 'cloudruntest', '--image', 'us-central1-docker.pkg.dev/cloudruntest-410500/cloud-run-source-deploy/cloudruntest:latest', '--region', 'us-central1']
images:
- us-central1-docker.pkg.dev/cloudruntest-410500/cloud-run-source-deploy/cloudruntest:latest
```

Cloud Shellに戻って、以下を実行。
```sh
cloud builds submit
```

エラーでコケた。
```
Step #2: ERROR: (gcloud.run.deploy) PERMISSION_DENIED: Permission 'run.services.get' denied on resource 'namespaces/cloudruntest-410500/services/cloudruntest' (or resource may not exist).
```
buildとArtifact Registryへのpushまではいけたが、Cloud Runがイメージを参照する権限が不足しているようだ。

[こちらのリンク](https://console.cloud.google.com/cloud-build/settings?hl=ja&_ga=2.74234833.277243140.1704585512-892198751.1702896189)から、Cloud Run 管理者ロールを有効化し、続いて表示されるポップアップにて**すべてのサービス アカウントにアクセス権を付与**を選択し、再度コマンド実行。
```sh
cloud builds submit
```

Cloud Runにも最新imageが登録されていることと、URLにアクセスした際に表示が変わっていることが確認できる。

# 終わりに
Google Cloudの以下のサービスを使って、リモートでの開発、ビルド、テスト、公開までのステップを確認した。
- Cloud Shell
- Cloud Shell エディタ
- Cloud Build
- Cloud Run

ローカルに環境をインストールせずとも、開発から公開までスムーズにいけたので便利だった。また、権限周りなど変につまるところもなく、マニュアルも読みやすかったので、Google Cloudはユーザーフレンドリーな印象を受けた。
次のステップとして、Google Cloudの他のサービスと連携するようなものもやっていけると良いだろう。

忘れずに、今回作成したProjectは削除しておく。