独学初心者でもTerraformでCloud Runにデプロイしたい

## はじめに
「やっぱり作ったアプリはデプロイしたい…！」「格好良くIaCも触りたい…！」という独学未経験の初心者の奮闘記録です。

もし間違い等ありましたらご指摘頂けると嬉しいです。


## Terraformって何なの？
**インフラ構成をコードで管理できる**IaC(Infrastructure as Code)ツールの１つ。

クラウドインフラの構成は、コンソール画面やSDKを用いて行えますが、どういった構成にしているかが見えず共有しにくくなります。また、以前の構成に戻したくなっても操作が面倒です。

それに対してIaCでは、コードで構成を管理できるので、インフラを可視化、共有がしやすく、変更履歴も残せます。

細かい部分については、以下の記事をとてもわかりやすいのでご覧ください。

https://qiita.com/takengineer1216/items/09e1b840d564a16b84eb

https://qiita.com/souhei-etou/items/7876767f041543321006

https://qiita.com/WisteriaWave/items/eeeb39ab1e1349cf0d00

## Terraformの手順
Terraformのバージョン管理を簡単に切り替えながら取り扱えるので、[tfenv](https://github.com/tfutils/tfenv)をインストールします。

```sh
$ brew install tfenv  # tfenvのインストール

$ tfenv install x.x.x<使用したいバージョン>  # 使用したいTerraformのバージョンをインストール

$ tfenv use x.x.x<使用したいバージョン>  # 使用したいTerraformのバージョンを選択
```

https://zenn.dev/mihohoi/articles/4e15a098a08c25

素の[Terraform](https://developer.hashicorp.com/terraform/install?product_intent=terraform)をインストールしても良いです。そして以下の順序でインフラ構成を適用します。

　
1. プロジェクトのルートディレクトリにて、Terraform用ディレクトリを作成します。
2. 作成したディレクトリに移動し、`main.tf`を作成、インフラ構成を書く。**以降、ディレクトリ移動せず実施する。**
3. `terraform init`を実行、初期化する。
4. `terraform plan`を実行、作成されるインフラ構成の確認をする。
5. `terraform apply`を実行、④で確認した内容でインフラを構成する。エラーがあれば吐かれる。
　
:::note
私はよく確認しておらず、ディレクトリに移動し`init`することを知りませんでした。変なところでハマってしまい時間を費やしてまった…。
:::

## Terraformの構成
今回、デプロイ先はCloud Runで、さらにnginxを使いたかったのでコンテナを２つ立ち上げる必要がありました。「どうしたらええんや…」の頭を抱えていたところ、以下の記事を見つけましたので参考にさせて頂きました。

https://zenn.dev/jj/articles/cloudrun-v2-with-nginx


### Cloud Runの認証設定
TerraformがCloud Runにアクセス出来るよう、まず認証設定をします。
以下２点を済ませておきましょう。

1. `gcloud CLI`をインストール
1. GCPにてプロジェクトを作成

作成したプロジェクトIDを用いて、`gcloud`での操作対象としてアクティブにします。

```
$ gcloud config set project <プロジェクトID>
```

サービスアカウントを作成します。

```sh
gcloud iam service-accounts create サービスアカウントの名前 \
		--description="説明文（任意）" \
		--display-name="表示名"
```

サービスアカウントに権限を付与します。

```sh
gcloud projects add-iam-policy-binding <プロジェクトID> \
		--member="serviceAccount:<作成したサービスアカウント名>@<プロジェクトID>.iam.gserviceaccount.com" \
		--role="roles/editor"
```

サービスアカウントキーをローカルに取得します。

```sh
gcloud iam service-accounts keys create <任意のパス>/key.json \
		--iam-account <作成したサービスアカウント名>@<プロジェクトID>.iam.gserviceaccount.com
```

:::note warn
サービスアカウントキーを用いた認証は公式で推奨されてませんが、手早くデプロイするため採用しました。
[キーの管理のベストプラクティス](https://cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys?hl=ja)が紹介されていますので、参考にし管理には十分注意しましょう。
公式の[認証方法の選択フロー](https://cloud.google.com/docs/authentication?hl=ja#auth-decision-tree)もあるので、キーを使いたくない場合はこちらも参考にしてください。
:::


### Artifact Registryの設定
```terraform:main.tf
provider "google" {
    project = var.project_id
    region  = var.default_region
    credentials = file("path/to/key.json")  # サービスアカウントキーのパス
}


provider "google-beta" {
    project = var.project_id
    region  = var.default_region
}


resource "google_artifact_registry_repository" "your_project_image_repository" {
    provider      = google-beta
    location      = var.default_region
    repository_id = var.repository_id
    description   = "your-project"
    format        = "DOCKER"

    cleanup_policy_dry_run = false
    cleanup_policies {
        id     = "keep_minimum-versions"
        action = "KEEP"
        most_recent_versions {
        keep_count = 3
        }
    }
}
```

```terraform:variable.tf
variable "default_region" {
    description = "default region"
    type        = string
    default     = "asia-northeast1"
}

variable "project_id" {
    description = "project id"
    type        = string
    default     = "your-api-project"
}

variable "repository_id" {
    description = "repository id"
    type        = string
    default     = "your-project-image-repository"
}
```

まずは、コンテナを立ち上げるためのdockerイメージのリポジトリを作ります。
上記の内容で`terraform init`　▷　`terraform plan`　▷　`terraform apply`を実行することで、Artifact Registryが作成されます。


### Artifact Registryにdockerイメージをプッシュする
デプロイ先でコンテナを立ち上げるためのdockerイメージを、Dockerfileがあるディレクトリでビルドし、プッシュします。

ちなみにnginx側とアプリ側のDockerfileは以下のようになっています。

```docker:nginx側のDockerfile
FROM nginx:1.17.4-alpine

RUN rm /etc/nginx/conf.d/default.conf
COPY default.conf /etc/nginx/conf.d
```

```docker:アプリ側のDockerfile
FROM python:3.11

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /code

COPY requirements.txt ./
RUN python3 -m pip install --upgrade pip setuptools
RUN pip install -r requirements.txt

COPY . ./

CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 config.wsgi:application
```

ビルドの際、[公式](https://cloud.google.com/artifact-registry/docs/docker/store-docker-container-images?hl=ja#add-image)の通り、リージョン（`asia-northeast1`）やホスト名（`docker.pkg.dev`）、プロジェクトIDなどを含めてタグ付けします。

>イメージにレジストリ名をタグ付けする
>
>Docker イメージにレポジトリ名をタグ付けすると、イメージを特定の場所に push するように docker push コマンドが構成されます。

```docker
docker build --platform linux/amd64 \
             -f Dockerfile \
             -t asia-northeast1-docker.pkg.dev/your-api-project/your-project-image-repository/your-web-back-image:v1 .

docker push asia-northeast1-docker.pkg.dev/your-api-project/your-project-image-repository/your-web-back-image:v1
```

```
docker build --platform linux/amd64 \
             -f Dockerfile \
             -t asia-northeast1-docker.pkg.dev/your-api-project/your-project-image-repository/your-nginx-image:v1 .

docker push asia-northeast1-docker.pkg.dev/your-api-project/your-project-image-repository/your-nginx-image:v1
```

:::note warn
ビルドする際、`--platform`でOSとアーキテクチャをlinux/amd64に指定しましょう。私はここで、エラーにハマりました…。
:::


### Cloud Runの設定
Cloud Runの構成のため、`main.tf`に以下を追記します。

```terraform:main.tf
resource "google_cloud_run_v2_service" "your-project-name" {
    provider = google-beta
    name     = "your-project-name"
    location = var.default_region
    ingress  = "INGRESS_TRAFFIC_ALL"

    template {
        containers {
            name  = "your-web-back-container"
            image = "asia-northeast1-docker.pkg.dev/${var.project_id}/${var.repository_id}/your-web-back-image:v1"
            env {
                name = "PORT"
                value = "8000"
            }
            startup_probe {
                failure_threshold     = 1
                initial_delay_seconds = 0
                timeout_seconds       = 240
                period_seconds        = 240
                tcp_socket {
                    port = 8000
                }
            }
        }

        containers {
            name = "your-nginx-container"
            image = "asia-northeast1-docker.pkg.dev/${var.project_id}/${var.repository_id}/your-nginx-image:v1"
            ports {
                container_port = 8080
            }
            depends_on = [ "your-web-back-container" ]
            startup_probe {
                failure_threshold     = 1
                initial_delay_seconds = 0
                timeout_seconds       = 240
                period_seconds        = 240
                tcp_socket {
                    port = 8080
                }
            }
        }

        scaling {
            min_instance_count = 0
            max_instance_count = 1
        }
    }

    traffic {
        type    = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
        percent = 100
    }
}
```

再度、`terraform init`　▷　`terraform plan`　▷　`terraform apply`を実行して、無事にCloud Runにデプロイ出来ました！


## おわりに
アプリを公開するって本当に大変ですね…。