[GCP][Terraform]Cloud Run + Artifact Registryの初期インフラ構築でぶつかった問題

# 問題
Artifact Registry(以降GAR)にpushしたイメージを元にして、Cloud Run上へコンテナを起動したい。
プライベートな開発で、Terraformで以下のような感じで記述していた。

```main.tf
resource "google_cloud_run_v2_service" "api" {
  # サービス名
  name     = "api"
  description = "外部公開するAPIのCloud Run services設定"
  location = var.default_region
  # 外部からのトラフィックの受け入れ許可設定
  ingress = "INGRESS_TRAFFIC_ALL"
  # このサービスにリビジョンを作成する時に使われるテンプレート設定
  template {
    # リビジョンインスタンスを実行する環境の世代
    execution_environment = "EXECUTION_ENVIRONMENT_GEN2"
    # リビジョンインスタンスのオートスケーリング設定
    scaling {
      min_instance_count = var.cloud_run_api.min_instance_count
      max_instance_count = var.cloud_run_api.max_instance_count
    }
    containers {
      name = "go-application"
      # イメージのURL
      image = "${var.default_region}-docker.pkg.dev/${var.default_project_id}/api/sample_app:latest"
      resources {
        # 上限設定
        limits = {
          cpu    = var.cloud_run_api.limit_cpu
          memory = var.cloud_run_api.limit_memory
        }
        # リクエストがあるときだけCPUを割り当てるか(=コールドスタートを許容するか)
        cpu_idle = var.cloud_run_api.cpu_idle
        # CPUブーストするか(コールドスタート時のレイテンシを低減する)
        startup_cpu_boost = var.cloud_run_api.startup_cpu_boost
      }
      ports {
        # プロトコル
        name = "h2c"
        # コンテナのポート番号(外部から内部への転送先。コンテナの環境変数PORTとしても設定される)
        container_port = var.cloud_run_api.container_port
      }

      # 環境変数
      env {
        name  = "GCP_PROJECT_ID"
        value = var.default_project_id
      }
      env {
        name  = "SPANNER_INSTANCE_ID"
        value = var.spanner_instance_dev.name
      }
      env {
        name  = "SPANNER_DATABASE_ID"
        value = var.spanner_database_dev.name
      }
    }
  }
  depends_on = [google_project_service.gcp_services["run.googleapis.com"]]
}

...

resource "google_artifact_registry_repository" "run-image" {
  project       = var.default_project_id
  location      = var.default_region
  repository_id = "api"
  description = "Cloud Run services (API)のイメージを格納するArtifact Registryのリポジトリ"
  format        = "DOCKER"
}
```

が、`terraform apply`するとエラーが発生

```
Error: Error waiting to create Service: Error waiting for Creating Service: Error code 13, message: Revision 'api-00001-nps' is not ready and cannot serve traffic. Image 'us-central1-docker.pkg.dev/dazzling-pillar-435904-a5/api/sample_app:latest' not found.
│
│   with google_cloud_run_v2_service.api,
│   on main.tf line 18, in resource "google_cloud_run_v2_service" "api":
│   18: resource "google_cloud_run_v2_service" "api" {
```

エラーメッセージから察するに、
「指定されているイメージがGARにないからCloud Runのリビジョンの作成がうまいこと完了しなかった」
ということかと。

# 解決策
google providerではGARへのイメージのデプロイは仕様上できない。
また、terraformの特性上、プロビジョニング処理も途中で失敗するとその後に続く処理が実行されなくなってしまう。(事実、再度terraform applyしようとするとまだプロビジョニングが終わってない部分で差分出ていた)

そのため、インフラ環境の初回構築時点では、以下の手順を通すのが素直な戦略だと思われる。
実際にいい感じにうまくいった。



```
1. [Terraform]`terraform apply`を実行(Cloud Runおよび依存する設定は除外※1)
2. [手動]イメージをビルド
3. [手動]イメージをGARへpush
4. [Terraform]terraform apply
```

※ 依存する設定というのは、例えばCloud RunのIAMとか。`terraform apply` では`-target`というこいつだけ適用してほしいというオプションはあれど、逆にこいつだけ除外してほしいというオプションは存在しないため、コメントアウトして適用するのが圧倒的に楽。


**2. 〜 3.の具体的なコマンド**

```
# イメージをビルド
docker build  -f <Cloud Runにデプロイするイメージの元になるDockerfileのパス> -t <Cloud Runにデプロイするイメージの完全名> .

# ~/.docker/config.json の credHelper へ設定(指定したArtifact Registryのホストでgcloudの認証情報を使う)を追加
gcloud auth configure-docker <GARのホスト名>

# GARの操作権限を持つGoogleアカウントでgcloudコマンド向けにログイン
gcloud auth login <Googleアカウント(xxxx@gmail.com)>

# GARへdockerコマンド向けにログイン
docker login <GARのホスト名>

# GARのリポジトリへイメージをpush
docker push <Cloud Runにデプロイするイメージの完全名>
```

**`<GARのホスト名>`のフォーマット**
`<ロケーション>-docker.pkg.dev`。
例えば `us-central1-docker.pkg.dev`

**`<Cloud Runにデプロイするイメージの完全名>`のフォーマット**
`<ロケーション>-docker.pkg.dev/<プロジェクトID>/<リポジトリ名>/<イメージ名>`。
例えば`us-central1-docker.pkg.dev/sample-project/api/sample_app`。

GARへのpushの流れは、

https://qiita.com/WisteriaWave/items/27bd24292a5a5f94e45f

を参照。


# 所感
Terraform使ってインフラ管理スッキリさせたくても、どうしてもこういう泥臭い作業は初期段階で発生しがち・・・。tfstateのbackendでのバケットの設定とかね。