Terraform with docker でGoogle Cloud環境構築する

# はじめに

こんにちは、株式会社COSM エンジニアの藤村です。

TerraformをDockerコンテナで実行しGoogle Cloudの環境構築をしたことを備忘録として残しておきます。

IaCを導入する際に少しでも参考になれば幸いです。

この環境を構築するにあたり2つの記事を参考にさせていただきました。

- Docker化周り
    - https://laboratory.kiyono-co.jp/547/gcp/
- entrypointやbashエイリアスなどの便利系設定周り
    - https://qiita.com/sawa-akabee/items/ddb48f2f5a47988eae79

# TL;DR

![](https://storage.googleapis.com/zenn-user-upload/62cb67ed08d7-20240828.png)
- TerraformをDockerコンテナで実行して、Google Cloud上のインフラを構築します。
- Dockerコンテナを使用することで、IaCを進める際の環境差異によるトラブルを避けてインフラ構築・管理を効率化することを目指します。

https://github.com/shota-fujimura/terraform-gcp-with-docker

# TerraformをDockerで実行するメリット

TerraformとDockerを組み合わせることでのメリットは3つあると考えています。

1. **環境の一貫性**
    - Dockerコンテナ内で、Terraformを実行することで、開発者が異なるローカル環境・OSを持っていても、同じ環境でインフラを管理することが可能になります。
    - Terraformのバージョンも統一できます。
2. **簡単なセットアップとクリーンアップ**
    - Dokcerイメージを利用することで、Terraform環境のセットアップが非常に簡単になります。
    - また、不要になった環境もすぐに削除できるため、ローカルマシンに無駄な設定が残らず、クリーンな状態を保つことができます。（私がローカルマシンが汚れることがあまり好きではないことも、今回Terraform実行環境をDocker化したことの理由です）
3. **セキュリティ向上**
    - Google Cloudの認証情報や設定ファイルをDockerコンテナ内で管理することで、セキュリティリスクを軽減できます。
    - 必要な時だけコンテナを起動し、不要になったら削除することで、認証情報がローカルに残るリスクを低減できます。

# ディレクトリ構成

```markdown
.
├── Makefile                   # Docker操作やGCP認証などのコマンドをまとめたファイル
├── README.md                  
├── cloud-sql-proxy            
├── compose.yml                # Docker Composeの設定ファイル
├── gcloud-config              # Google Cloud SDKの認証情報と設定を管理
└── terraform                  # Terraformを使用したインフラ構成管理
    ├── Dockerfile             # Terraformを実行するDockerイメージの設定
    ├── home
    │   └── entrypoint.sh      # Dockerコンテナ起動時のエントリーポイントスクリプト
    ├── root                   # Dockerコンテナ内のルート設定ファイル
    │   └── .bashrc            # Dockerコンテナ起動時のbashコマンドのエイリアス
    └── src                    # Terraform構成ファイルを格納
        ├── environment        # 環境ごとのTerraform設定
        │   ├── dev            # 開発環境の設定ファイル
        │   │   ├── backend.tf                 # tfsateをGCPのcloud-storageに保管するための設定を記述
        │   │   ├── dev.tfvars                 # 開発環境用の変数定義(gitignore)
        │   │   ├── locals.tf                  # ローカル変数定義
        │   │   ├── main.tf                    # 開発環境の主要Terraform構成
        │   │   ├── outputs.tf                 # 出力変数定義
        │   │   └── variables.tf               # 変数定義ファイル
        │   ├── prod           # 本番環境の設定ファイル
        │   └── staging        # ステージング環境の設定ファイル
        └── shared             # 共有モジュールと設定
            └── modules        # 共有モジュールディレクトリ
                ├── cloud-storage                # Cloud Storageリソースの設定
                │   ├── main.tf
                │   ├── outputs.tf
                │   └── variables.tf
                └── (add other modules)   
```

# 主要なファイルの説明

## compose.yml

- 責任を分離したいので、2つのコンテナを定義します（terraformコンテナと、gcloudコンテナ）
    - terraformコンテナ
        - Terraformの操作に専念
    - gcloudコンテナ
        - gcloud sdk の操作に専念

```yaml
version: '3.9'

services:
  terraform:
    build: ./terraform
    container_name: terraform
    platform: linux/amd64
    environment:
      - GOOGLE_APPLICATION_CREDENTIALS=/root/.config/gcloud/application_default_credentials.json
    volumes:
      - ./terraform/src:/terraform/src
      - ./gcloud-config:/root/.config
    
  gcloud:
    image: google/cloud-sdk:stable
    container_name: gcloud
    working_dir: /terraform
    platform: linux/amd64
    volumes:
      - ./gcloud-config:/root/.config
    
volumes:
  gcloud-config:
```

## terraform/Dockerfile

- Terraformのバージョンは指定しています。（バージョン差異が起こらないように）
- [entorypoint.sh](http://entorypoint.sh) を実行するように設定

```docker
FROM hashicorp/terraform:1.9.3

WORKDIR /terraform

# install bash
RUN apk add --no-cache bash

COPY ./root/.bashrc /root/.bashrc

COPY . /terraform

RUN chmod +x /terraform/home/entrypoint.sh

ENTRYPOINT ["/terraform/home/entrypoint.sh"]
```

## terraform/home/entrypoint.sh と terraform/root/.bashrc

- 以下記事を参考にさせていただきました。
    - https://qiita.com/sawa-akabee/items/ddb48f2f5a47988eae79#terraformhomeentrypointsh

## Makefile

- 長いコマンド打つのが億劫になるので作成。

```makefile
# Makefile

#boot docker and open bash
bash:
	docker compose up -d && docker compose exec terraform /bin/bash -c "source ~/.bashrc && exec /bin/bash" 

#stop docker containers
stop:
	docker compose stop

#down docker
down:
	docker compose down

#delete image
rm_images:
	docker image rm google/cloud-sdk:stable && docker image rm terraform-gcp-with-docker:latest

# /// AUTHENTICATION TO GOOGLE CLOUD ///
#auth individual environment
auth-dev:
	docker compose run --rm gcloud gcloud auth application-default login --project sample-app-dev

auth-staging:
	docker compose run --rm gcloud gcloud auth application-default login --project sample-app-staging

auth-prod:
	docker compose run --rm gcloud gcloud auth application-default login --project sample-app-prod
```

## backend.tf

- .tfstateをGSC（Google Cloud Storage）で管理するための設定

```
terraform {
  backend "gcs" {
    bucket = "sample-app-dev-tfstate-bucket"
    prefix = "terraform/state"
  }
}
```

<aside>
⚠️ 事前にGoogle Cloud上でCloud Storageを作成しておく必要があります。このCloud StorageはTerraformでは管理せず、手動で作成する方が良いです。

</aside>

## terraform/src/environment/{dev,staging,prod}/*.tfvars

- .bashrcの`var-file`オプションにて指定している環境変数ファイルです。
- 秘密情報など大事めな変数はこちらに定義します。
- .gitignoreに設定して、gitの管理から外すことでセキュリティ向上を図っています。

```
env               = "dev"
credentials_file  = "/root/.config/gcloud/application_default_credentials.json"
project_id        = "sample-app"
location          = "asia-northeast1"
```

## terraform/src/environment/{dev,staging,prod}/main.tf

- `provider`定義と、`shared/modules`で定義するモジュールの呼び出しの役割です。

```
/*
===================================
Provider
===================================
*/
# define provider
provider "google" {
  credentials = file(var.credentials_file)
  project     = var.project_id
  region      = var.region
}

/*
===================================
Module Calls
===================================
*/

# call cloud storage module
module "cloud_storage" {
  source             = "../../shared/modules/cloud-storage"
  bucket_name        = local.bucket_name
  location           = var.location
  storage_class      = "STANDARD"
  force_destroy      = true
  lifecycle_rule_age = 30
}

```

## terraform/src/environment/{dev,staging,prod}/variables.tf

- main.tfで使用する変数の定義の役割です。

## terraform/src/shared/modules/cloud-storage

- `terraform/src/shared/modules/` 配下でモジュールごとにフォルダを作成し、そのフォルダの中に、`main.tf`, `variables.tf`,`outputs.tf`の3つのファイルを作成します。
- `terraform/src/shared/modules/` でリソース定義して、呼び出しは、`terraform/src/environments/{dev,staging,prod}/main.tf` で行うことで、各環境での再利用性を高めます。

**例）cloud-storage/main.tf**

```
resource "google_storage_bucket" "bucket" {
  name                        = var.bucket_name
  location                    = var.location
  storage_class               = var.storage_class
  force_destroy               = var.force_destroy
  uniform_bucket_level_access = true

  lifecycle_rule {
    action {
      type = "Delete"
    }
    condition {
      age = var.lifecycle_rule_age
    }
  }
}

```

**例）cloud-storage/outputs.tf**

```
output "bucket_name" {
  value = google_storage_bucket.bucket.name
}

output "bucket_url" {
  value = "gs://${google_storage_bucket.bucket.name}"
}

```

**例）cloud-storage/variables.tf**

```
variable "bucket_name" {
  description = "The name of the bucket"
  type        = string
}

variable "location" {
  description = "The location of the bucket"
  type        = string
  default     = "asia-northeast1"
}

variable "storage_class" {
  description = "The storage class of the bucket"
  type        = string
  default     = "STANDARD"
}

variable "force_destroy" {
  description = "The force destroy of the bucket"
  type        = bool
}

variable "lifecycle_rule_age" {
  description = "The lifecycle rule age of the bucket"
  type        = number
  default     = 30
}

```

# 実行方法

## まずはGoogle Cloudに認証を通します。

<aside>
⚠️ Google Cloudにプロジェクトを作ってから実行してください

</aside>

- `make auth-{環境名}`で、各環境ごとに対応したプロジェクトに対して認証を通します。
    - 例）`make auth-dev`
    
- `make auth-{環境名}`を実行したら、https//accounts.google.com/~~~ のリンクを踏んでください。（ブラウザで認証の画面が開きますので、ログインしてください）
- 下記画像の赤い四角の部分に文字列が表示されるのでコピーして、ターミナルに貼り付けてreturn/Enter キーを押したら認証完了です。
    ![スクリーンショット 2024-08-23 21.26.16.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3752047/2dd9d7c0-7fbd-201b-de7c-2ba4514a0d63.jpeg)

    

```makefile
shotafujimura@rooter terraform-gcp-with-docker % make auth-dev
docker compose run --rm gcloud gcloud auth application-default login --project sample-app-dev
[+] Building 0.0s (0/0)                                                                                                                                                                                               
[+] Creating 1/0
 ✔ Network terraform-gcp-with-docker_default  Created                                                                                                                                                                                 
[+] Building 0.0s (0/0)                                                                                                                                                                                               
Go to the following link in your browser, and complete the sign-in prompts:

    https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={もろもろ}

Once finished, enter the verification code provided in your browser: **{ここに文字列をペーストします。}**
```

## コンテナの実行方法

- `make bash` のみです。
- make bashの中身はMakefileで以下のように定義しています。

```makefile
#boot docker and open bash
bash:
	docker compose up -d && docker compose exec terraform /bin/bash -c "source ~/.bashrc && exec /bin/bash" 
```

1. **`docker compose up -d`**:
    
    このコマンドは、`compose.yml` ファイルに基づいてDockerコンテナをバックグラウンドで起動します（`-d` は「デタッチモード」を意味し、コンテナをバックグラウンドで実行します）。これにより、指定されたサービス（この場合はTerraformなど）が起動されます。
    
2. **`docker compose exec terraform /bin/bash -c "source ~/.bashrc && exec /bin/bash"`**:
    - **`docker compose exec terraform`**: 起動中の`terraform`コンテナ内でコマンドを実行します。
    - **`/bin/bash -c "source ~/.bashrc && exec /bin/bash"`**:
        - `/bin/bash -c`: Bashシェルを起動し、指定したコマンド（`"source ~/.bashrc && exec /bin/bash"`）を実行します。
        - `source ~/.bashrc`: `.bashrc` ファイルを読み込み、そこに定義されたエイリアスや環境変数を設定します。
        - `exec /bin/bash`: 新しいBashシェルを起動し、その環境で対話的に作業ができるようにします。

# terraform init/plan/apply/destroy

- `.bashrc` でエイリアスを定義しているので、{実行したいコマンド}:{環境名}を、コンテナ内のbashで実行します。
- 例）
    - **`init:dev`**
        - 開発環境における、`terraform init`
    - **`plan:dev`**
        - 開発環境における、`terraform plan`
    - **`apply:dev`**
        - 開発環境における、`terraform apply`
    - **`destroy:dev`**
        - 開発環境における、`terraform destroy`
    - **`fmt`**
        - terraformディレクトリ内で、`terraform fmt -recursive`

# 最後に

TerraformとDockerを組み合わせてインフラ管理を行うことで、環境の一貫性を保ちながら、効率的にインフラをコードとして管理することが可能になります。

今回ご紹介したプロジェクト構成やコマンドの使い方を参考にして頂き、日々の運用をスムーズにし、チーム全体の生産性を向上することに少しでも寄与できれば幸いです。