はじめに
こんにちは、株式会社COSM エンジニアの藤村です。
TerraformをDockerコンテナで実行しGoogle Cloudの環境構築をしたことを備忘録として残しておきます。
IaCを導入する際に少しでも参考になれば幸いです。
この環境を構築するにあたり2つの記事を参考にさせていただきました。
- Docker化周り
- entrypointやbashエイリアスなどの便利系設定周り
TL;DR
- TerraformをDockerコンテナで実行して、Google Cloud上のインフラを構築します。
- Dockerコンテナを使用することで、IaCを進める際の環境差異によるトラブルを避けてインフラ構築・管理を効率化することを目指します。
TerraformをDockerで実行するメリット
TerraformとDockerを組み合わせることでのメリットは3つあると考えています。
-
環境の一貫性
- Dockerコンテナ内で、Terraformを実行することで、開発者が異なるローカル環境・OSを持っていても、同じ環境でインフラを管理することが可能になります。
- Terraformのバージョンも統一できます。
-
簡単なセットアップとクリーンアップ
- Dokcerイメージを利用することで、Terraform環境のセットアップが非常に簡単になります。
- また、不要になった環境もすぐに削除できるため、ローカルマシンに無駄な設定が残らず、クリーンな状態を保つことができます。(私がローカルマシンが汚れることがあまり好きではないことも、今回Terraform実行環境をDocker化したことの理由です)
-
セキュリティ向上
- Google Cloudの認証情報や設定ファイルをDockerコンテナ内で管理することで、セキュリティリスクを軽減できます。
- 必要な時だけコンテナを起動し、不要になったら削除することで、認証情報がローカルに残るリスクを低減できます。
ディレクトリ構成
.
├── 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 の操作に専念
- terraformコンテナ
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 を実行するように設定
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
- 以下記事を参考にさせていただきました。
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"
}
}
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に認証を通します。
⚠️ Google Cloudにプロジェクトを作ってから実行してください-
make auth-{環境名}
で、各環境ごとに対応したプロジェクトに対して認証を通します。- 例)
make auth-dev
- 例)
-
make auth-{環境名}
を実行したら、https//accounts.google.com/~~~ のリンクを踏んでください。(ブラウザで認証の画面が開きますので、ログインしてください) -
下記画像の赤い四角の部分に文字列が表示されるのでコピーして、ターミナルに貼り付けてreturn/Enter キーを押したら認証完了です。
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で以下のように定義しています。
#boot docker and open bash
bash:
docker compose up -d && docker compose exec terraform /bin/bash -c "source ~/.bashrc && exec /bin/bash"
-
docker compose up -d
:このコマンドは、
compose.yml
ファイルに基づいてDockerコンテナをバックグラウンドで起動します(-d
は「デタッチモード」を意味し、コンテナをバックグラウンドで実行します)。これにより、指定されたサービス(この場合はTerraformなど)が起動されます。 -
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ディレクトリ内で、
-
最後に
TerraformとDockerを組み合わせてインフラ管理を行うことで、環境の一貫性を保ちながら、効率的にインフラをコードとして管理することが可能になります。
今回ご紹介したプロジェクト構成やコマンドの使い方を参考にして頂き、日々の運用をスムーズにし、チーム全体の生産性を向上することに少しでも寄与できれば幸いです。