【GCP】DockerイメージをCloudBuild でbuild してCloudRun にdeploy する

# はじめに

Streamlit のサービスをCloudRunにデプロイするCI/CDを作ろう、と思ったら大変でした。
最初はGithub Actions で頑張ったのですがハマってしまい、結局、CloudBuild で実現しました。

Gemini先生がたくさん教えてくれたのですが、それでも肝心なところはうまくいきませんでした。主に権限の設定あたりです。メモを残しておきます。

# 内容

## 何をするか

まず最初にできたこと

- ターミナルから docker build, docker push で Google Cloud のArtifact Registry に push すること。
- ターミナルから cloudbuild.yaml を gcloud submit で指定して build/push する 
- gcloud deploy run で　Cloud run にdeploy する。
- github repository にpush すると cloudbuild で　docker build/push し CloudRun に deploy する。

必要だったことは、Artifact Registryと service account の両方に権限の設定です。

- Artifact Registgry の作成
- そのArtifact Registryにサービスアカウントへアクセス権を与えること。
- サービスアカウントに Artifact への書き込み権限を与えること

## ファイル構成

同じディレクトリに全てを置いてあります。

```
tree .
.
├── Dockerfile
├── app.py
├── cloudbuild.yaml
├── compose.yaml
├── pages
│   ├── 1_something.py
│   └── 2_foo.py
└── requirements.txt
```

### Docker関係

pip のときroot だと警告が出たので、仮想環境で実行するようにしています。

```Dockerfile
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN bash -c "python3 -m venv .venv && source .venv/bin/activate && \
    pip install --upgrade pip && \
    pip install -r requirements.txt"

COPY app.py .
COPY pages/*.py /app/pages/

EXPOSE 8501
CMD [".venv/bin/streamlit", "run", "app.py", "--server.port=8501"]
```

ここでdocker build/ run は下記のコマンドで実行できます。

```
docker build --no-cache -t streamlit-app .
docker run -rm -p 8501:8501 --name my-streamlit-app streamlit-app:latest
```

これをまとめて管理するために、compose.yaml を書くこともできます。

```compose.yaml
version: "3.9"

services:
  streamlit-app:  # service name
    build:
      context: .  # Dockerfile directory
      dockerfile: Dockerfile
      no_cache: true  # (docker build --no-cache)
    ports:
      - "8501:8501"  # port(host):port(container)
    container_name: my-streamlit-app  # container name (docker run --name)
    image: streamlit-app:latest  # image name:tag (docker build -t)
```

ターミナルで書きコマンドでdocker build/run できます。もちろん、build, up 

```bash
docker compose up -d --build
```


## Google Cloudでのアカウント

ターミナルで認証を通します。

```
gcloud auth login
```

今のconfiguration について調べます。

```bash
$ gcloud config configurations list
NAME     IS_ACTIVE  ACCOUNT              PROJECT         COMPUTE_DEFAULT_ZONE  COMPUTE_DEFAULT_REGION
default  True       myname012@gmail.com  my-project
$ gcloud config list
[core]
account = myname012@gmail.com
disable_usage_reporting = True
project = my-project

Your active configuration is: [default]
```

### サービスアカウントの設定

登録しているサービスアカウントの調べてみます。デフォルトのサービスアカウントは下記のようです。

```
$ gcloud builds get-default-service-account
name: projects/my-cloud-run01/locations/global/defaultServiceAccount
serviceAccountEmail: projects/639188470809/serviceAccounts/012345678901-compute@developer.gserviceaccount.com
```

このアカウントをCI/CDに使っても良いのですが、別のものを用意することにします。
サービスアカウントの作成は、CUIでもブラウザ(console)からでも実行可能です。

Cloud Build のデフォルトのサービスアカウントの確認

```bash
$ gcloud projects describe my-project --format="value(serviceAccounts.email)"
```

プロジェクトに登録されているサービスアカウントは下記で確認できます。

```bash
gcloud iam service-accounts list --project=my-project
```

Cloud Buildのアカウントを作る。

```bash
$ gcloud iam service-accounts create cloudbuild --display-name="Cloud Build service account" --project=my-project
Created service account [cloudbuild].
```

### Artifact Registry の作成

名前が `app01` であるArtifact Registry が存在するか確認します。Region は東京にしています。
hogeが今回pushする予定のregistry です。

```bash
$ gcloud artifacts repositories list --location=asia-northeast1
Listing items under project my-project, location asia-northeast1.

                                                                                 ARTIFACT_REGISTRY
REPOSITORY               FORMAT  MODE                 DESCRIPTION                   LOCATION         LABELS  ENCRYPTION          CREATE_TIME          UPDATE_TIME          SIZE (MB)
cloud-run-source-deploy  DOCKER  STANDARD_REPOSITORY  Cloud Run Source Deployments  asia-northeast1          Google-managed key  2025-02-07T13:56:33  2025-02-07T13:58:13  210.468
hoge                     DOCKER  STANDARD_REPOSITORY  test                          asia-northeast1          Google-managed key  2025-02-07T14:59:03  2025-02-17T23:40:48  3941.308
```

もしリポジトリが存在しない場合は、作成する必要があります。作成はconsoleからもできます。

```bash
gcloud artifacts repositories create hoge \
    --repository-format=docker \
    --location=asia-northeast1
    --description="foo bar" \
    --immutable-tags \
    --async
```

imutable-tags のオプションがあると、push されているイメージのタグと同じになるイメージはpush できなくなります。
例えばlatest やv1.0 のように同じtag のモノをpush しようとするとエラーになりました。

あと、大事なのは**このレジストリへの書き込み権限を使用するサービスアカウントに与える**ことです。

### Artifact Registryへのpush (docker コマンド)

DockerのGoogle Cloudとの間でのcredential で意味は理解できていません。が設定が必要です。

`docker-credential-gcr` ヘルパーがインストールされていなければインストールします。

```bash
gcloud components install docker-credential-gcr
```

良く分かりませんが、下記を実行します。

```bash
docker-credential-gcr configure-docker
/home/xt/.docker/config.json configured to use this credential helper for GCR registries
```

そしてdocker registry に push します。

```
docker push asia-northeast1-docker.pkg.dev/my-project/hoge/app:tagtag
```

### Artifact Registryへのpush (CloudBuildを利用)

Google Cloud のサービスであるCloud Buildを用いて同じことを行います。
内容をcloudbuild.yaml に書きます。

```cloudbuild.yaml
steps:
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'asia-northeast1-docker.pkg.dev/${PROJECT_ID}/foo1/my-python-app:${COMMIT_SHA}', '.']

  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'asia-northeast1-docker.pkg.dev/${PROJECT_ID}/foo1/my-python-app:${COMMIT_SHA}']

  - name: 'gcr.io/cloud-builders/gcloud'
    entrypoint: 'gcloud'
    args: ['run', 'deploy', 'myapp',
            '--image', 'asia-northeast1-docker.pkg.dev/${PROJECT_ID}/foo1/my-python-app:${COMMIT_SHA}', 
            '--region', 'asia-northeast1',
            '--platform', 'managed',
            '--port', '8501',
            '--memory', '1G',
            '--max-instances', '3']

options:
  defaultLogsBucketBehavior: REGIONAL_USER_OWNED_BUCKET
serviceAccount: my_cicd@my-cloud-run01.iam.gserviceaccount.com
```

環境変数PROJECT_IDはGoogle Cloud 1での project IDであり、COMMIT_SHA は git repository でのcommit の番号で、実行時に代入されます。

option で指定しているのはCloud Build を実行するサービスアカウントで、ターミナルで実行するときはサービスアカウントを指定する必要があります。ただし、指定が無ければdefault のサービスアカウントが使われるそうです。

下記でbuild + push ができました。

```bash
gcloud builds submit --config cloudbuild.yaml \
   --service-account projects/my-project/serviceAccounts/github-delivery@my-project.iam.gserviceaccount.com \
   --substitutions COMMIT_SHA=$(git rev-parse HEAD) .
```

### Artifact Registry にあるdocker image の管理

ブラウザ(google cloud console)からいろいろできます。
ターミナルからも操作可能で、Registryにあるimage は下記コマンドで見れました。

```bash
$ gcloud artifacts docker images list asia-northeast1-docker.pkg.dev/PROJECT_ID/REPOSITORY_NAME/ --project=my-project
```

Artifact Registry に利用できる地域を表示します。

```bash
gcloud artifacts locations list
```

例えば今のcommit に対応するimage をregistry から削除するには下記でできました。

```bash
gcloud artifacts docker images delete asia-northeast1-docker.pkg.dev/PROJECT_ID/REPOSITORY_NAME/APP:$(git rev-parse HEAD) --quiet

```

### Github 経由のCICD

CloudBuild の設定をconsole (ブラウザ画面)の設定で行いました。


## 参考ページ

- [Streamlit with Google Cloud: Hello, world!](https://zenn.dev/google_cloud_jp/articles/streamlit-01-hello)


# まとめ

とりあえずGithub から Docker container を CloudRunにCI/CDの環境は作れました。Cloud側のIAMの設定でかなりはまりましたが、Artifact へのアクセス権さえうまく管理できれば、あとはgeminiさんの言う通りにすれば大丈夫だと思います。

折をみて文章を修正しようと思いますが、とりあえず今はこのあたりで。。
(2025/2/20)