19
☁️ 拡大編 Chapter 19

インフラをコードで

手作業のインフラ構築から、再現可能なコード管理へ

約55分
Infrastructure as Code · intro Terraform · intro 環境管理 · intro
目次(33セクション)
🎬 Story — Introduction

クラウド移行は成功した。ShimaLinkは安定して稼働している。ある日、あなたがやってきた。


あなた: 「大きなニュースがある。隣の県の企業から、ShimaLinkを導入したいって連絡が来た!」

あなた: 「すごい!沖縄以外にも展開できるってことですね。」

あなた: 「そう。でも、セキュリティの関係で、沖縄のシステムとは完全に分離した環境が必要なんだ。同じ構成をもう一つ作らないといけない。」

あなた: 「えっと…Cloud Run、Cloud SQL、Cloud Storage、ロードバランサー、ネットワーク設定…全部手動で作り直すの?前回、コンソールでポチポチやるのに丸一日かかったんだけど。」

あなた: 「しかも、設定を一つでも間違えたら動かない。前回もネットワーク設定でハマったよね。」


Yuki: 「手動でインフラを作るのは、もう限界だね。」

あなた: 「でも、他に方法が…」

Yuki: 「あるよ。Infrastructure as Code(IaC)——インフラの構成をコードとして書く方法だ。」

あなた: 「インフラをコードで?」

Yuki: 「そう。クラウドのリソースをすべてコードファイルに定義する。実行すると、そのコード通りのインフラが自動的に構築される。Terraform(テラフォーム) というツールが業界標準だよ。」

あなた: 「つまり、コードを実行するだけで、同じ環境をいくつでも作れるってことですか?」

Yuki: 「そういうこと。しかもGitで管理できるから、“誰が、いつ、何を変更したか”も追跡できる。もう”あの時の設定どうだったっけ”と悩まなくて済む。」


インフラ構築を「職人技」から「再現可能な科学」に変える旅が始まる。

なぜ Infrastructure as Code が必要なのか

「手動で作ったインフラは”雪の結晶”。二度と同じものは作れない。」 ——Yuki

手動インフラ管理の問題点

クラウドコンソールで手動でリソースを作成・設定する方法には、多くの問題があります。

1. 再現性がない

「前回どういう設定でDB作ったっけ?」
「ネットワーク設定のパラメータ何だったかな...」
「あの時のファイアウォールルール、スクショ撮ってないな...」

2. ドリフト(設定のずれ)

本番環境: DB のメモリ = 8GB(誰かがコンソールで変更)
ステージング環境: DB のメモリ = 2GB(元の設定のまま)
→ テスト環境と本番環境で挙動が異なる

3. 変更履歴がない

  • 誰がいつ何を変更したかわからない
  • 問題が起きても原因を追跡できない
  • 変更を元に戻す手段がない

4. 属人化

「インフラの設定は田中さんしか知らない」
→ 田中さんが休んだら誰も対応できない

Infrastructure as Code(IaC)とは

インフラの構成をコードファイルとして記述し、そのコードを実行することでインフラを自動構築する手法です。

比較手動管理IaC
構築方法コンソールでクリックコードを実行
再現性なしあり(同じコードで同じ結果)
変更管理なしGitで履歴管理
レビューできないコードレビューが可能
テスト本番で確認するしかない事前にプランを確認できる
共有ドキュメント(古くなりがち)コード自体がドキュメント

IaC ツールの比較

ツール提供元特徴
TerraformHashiCorpマルチクラウド対応、業界標準
CloudFormationAWSAWS専用、JSONまたはYAML
PulumiPulumi一般的なプログラミング言語で記述可能
CDKAWSTypeScript等でCloudFormationを生成

ShimaLinkではTerraformを使います。マルチクラウド対応で、将来のプロバイダー変更にも柔軟に対応できるからです。

IaC のワークフロー

1. コードを書く(main.tf)

2. プランを確認する(terraform plan)
    ↓ 「何が作られ/変更されるか」が表示される
3. 適用する(terraform apply)
    ↓ 実際にインフラが構築される
4. Gitにコミットする
    ↓ 変更履歴が記録される

Yuki: 「IaCのいいところは、terraform plan で実際に何が起きるか事前に確認できること。“やってみないとわからない”がなくなるんだ。」

ポイント

  • 手動インフラ管理は再現性・追跡性・共有性に欠ける
  • IaCはインフラの構成をコードとして管理する手法
  • コードなのでGitでバージョン管理、レビュー、共有が可能
  • Terraformはマルチクラウド対応の業界標準IaCツール
  • plan で事前確認、apply で適用、という安全なワークフロー

Terraform の基本 — HCL、プロバイダー、リソース

「Terraformは”インフラの設計図”を書くツール。設計図通りにクラウドを自動構築してくれる。」 ——Yuki

HCL(HashiCorp Configuration Language)

TerraformはHCLという独自の言語でインフラを定義します。JSONのように宣言的で、読みやすいのが特徴です。

# これがHCLの基本構文
resource "リソースタイプ" "名前" {
  設定項目 = "値"
}

Terraform プロジェクトの構成

shimalink-infra/
├── main.tf          # メインのリソース定義
├── variables.tf     # 変数の定義
├── outputs.tf       # 出力値の定義
├── terraform.tfvars # 変数の値(Git管理しない場合もある)
└── providers.tf     # プロバイダーの設定

プロバイダーの設定

プロバイダーはTerraformとクラウドを接続するプラグインです。

# providers.tf
terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = "asia-northeast1"
}

リソースの定義

ShimaLinkのインフラをTerraformで定義してみましょう。

# main.tf

# Cloud Run サービス
resource "google_cloud_run_v2_service" "shimalink_web" {
  name     = "shimalink-web"
  location = "asia-northeast1"

  template {
    containers {
      image = "asia-northeast1-docker.pkg.dev/shimalink/repo/web:latest"

      ports {
        container_port = 3000
      }

      env {
        name  = "NODE_ENV"
        value = "production"
      }

      env {
        name = "DATABASE_URL"
        value_source {
          secret_key_ref {
            secret  = google_secret_manager_secret.db_url.secret_id
            version = "latest"
          }
        }
      }

      resources {
        limits = {
          cpu    = "1"
          memory = "512Mi"
        }
      }
    }

    scaling {
      min_instance_count = 1
      max_instance_count = 10
    }
  }
}

# Cloud SQL データベース
resource "google_sql_database_instance" "shimalink_db" {
  name             = "shimalink-db"
  database_version = "POSTGRES_15"
  region           = "asia-northeast1"

  settings {
    tier = "db-f1-micro"

    backup_configuration {
      enabled    = true
      start_time = "03:00"
    }
  }

  deletion_protection = true
}

# データベース
resource "google_sql_database" "shimalink" {
  name     = "shimalink"
  instance = google_sql_database_instance.shimalink_db.name
}

変数の定義

# variables.tf
variable "project_id" {
  description = "GCPプロジェクトID"
  type        = string
}

variable "environment" {
  description = "環境名(dev, staging, prod)"
  type        = string
  default     = "dev"
}

variable "db_tier" {
  description = "Cloud SQLのインスタンスタイプ"
  type        = string
  default     = "db-f1-micro"
}
# terraform.tfvars
project_id  = "shimalink-project"
environment = "prod"
db_tier     = "db-g1-small"

出力値

# outputs.tf
output "web_url" {
  description = "ShimaLink WebアプリのURL"
  value       = google_cloud_run_v2_service.shimalink_web.uri
}

output "db_connection" {
  description = "データベース接続名"
  value       = google_sql_database_instance.shimalink_db.connection_name
  sensitive   = true
}

基本コマンド

# プロバイダーをダウンロード
terraform init

# 実行プランを確認(何が作られるか)
terraform plan

# インフラを構築
terraform apply

# インフラを削除
terraform destroy

# 現在の状態を表示
terraform show

Yuki:terraform plan が一番大事なコマンドだよ。実際に変更を適用する前に、何が起きるかを確認できる。Pull Requestでもplan結果をレビューするのがベストプラクティスだ。」

ポイント

  • HCLは宣言的な言語で、何を作るかを記述する(どう作るかはTerraformが判断)
  • プロバイダーでクラウドとの接続を設定する
  • リソースでクラウド上のインフラ(サーバー、DB等)を定義する
  • 変数で環境ごとの値を柔軟に変えられる
  • initplanapply の3ステップが基本ワークフロー

環境の管理 — dev、staging、production

「同じコードで、dev・staging・prodを作れる。それがIaCの真骨頂だよ。」 ——Yuki

なぜ複数の環境が必要か

環境用途利用者
dev(開発)新機能の開発・実験開発者
staging(検証)本番デプロイ前の最終確認開発者・QA
production(本番)実際のユーザーが使うエンドユーザー
dev → staging → production
 │       │          │
 │       │          └ 実ユーザーがアクセス
 │       └ 本番と同じ構成でテスト
 └ 自由に壊して試せる環境

Terraform Workspaces

Terraformのワークスペース機能で、同じコードから複数の環境を管理できます。

# ワークスペースの作成
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod

# ワークスペースの切り替え
terraform workspace select prod

# 一覧
terraform workspace list

環境ごとの変数ファイル

shimalink-infra/
├── main.tf
├── variables.tf
├── outputs.tf
├── environments/
│   ├── dev.tfvars
│   ├── staging.tfvars
│   └── prod.tfvars
# environments/dev.tfvars
environment    = "dev"
db_tier        = "db-f1-micro"
min_instances  = 0
max_instances  = 2
# environments/prod.tfvars
environment    = "prod"
db_tier        = "db-g1-small"
min_instances  = 1
max_instances  = 10
# dev環境を構築
terraform apply -var-file=environments/dev.tfvars

# prod環境を構築
terraform apply -var-file=environments/prod.tfvars

環境ごとのリソース命名

# main.tf — 環境名をリソース名に含める
resource "google_cloud_run_v2_service" "shimalink_web" {
  name     = "shimalink-web-${var.environment}"
  location = "asia-northeast1"

  template {
    containers {
      image = var.web_image

      resources {
        limits = {
          cpu    = var.environment == "prod" ? "2" : "1"
          memory = var.environment == "prod" ? "1Gi" : "512Mi"
        }
      }
    }

    scaling {
      min_instance_count = var.min_instances
      max_instance_count = var.max_instances
    }
  }
}

モジュール化

同じリソースの組み合わせを再利用可能なモジュールにまとめます。

shimalink-infra/
├── modules/
│   ├── web-service/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   └── database/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── environments/
│   ├── dev/
│   │   └── main.tf
│   └── prod/
│       └── main.tf
# environments/prod/main.tf
module "web" {
  source = "../../modules/web-service"

  environment   = "prod"
  min_instances = 1
  max_instances = 10
  cpu           = "2"
  memory        = "1Gi"
}

module "database" {
  source = "../../modules/database"

  environment = "prod"
  tier        = "db-g1-small"
  backup      = true
}

環境間のプロモーション

コード変更

terraform plan(dev.tfvars)→ terraform apply
    ↓ devで動作確認
terraform plan(staging.tfvars)→ terraform apply
    ↓ stagingで最終テスト
terraform plan(prod.tfvars)→ terraform apply
    ↓ 本番リリース

Yuki: 「新しい県への展開もこれでできる。terraform apply -var-file=environments/kagoshima.tfvars とするだけで、鹿児島環境が8分で完成するんだ。」

ポイント

  • dev、staging、productionの3環境を用意するのが標準的
  • 同じTerraformコードで環境ごとの変数ファイルを切り替える
  • リソース名に環境名を含めて衝突を防ぐ
  • モジュール化で再利用可能なインフラ部品を作る
  • 環境のプロモーション(dev → staging → prod)で安全にリリースする

ステート管理 — Terraform の心臓部

「ステートファイルはTerraformの記憶。これを失うと、Terraformは何を管理しているか忘れてしまう。」 ——Yuki

Terraform ステートとは

Terraformは、管理しているインフラの現在の状態を**ステートファイル(terraform.tfstate)**に記録します。

コード(main.tf)    ←→  ステート(terraform.tfstate)  ←→  実際のクラウドリソース
  「こうあるべき」          「こうなっている」                「実際の状態」

terraform plan は、コードとステートを比較して「何を変更する必要があるか」を計算します。

ステートの重要性

# ステートに記録される情報の例
{
  "resources": [
    {
      "type": "google_cloud_run_v2_service",
      "name": "shimalink_web",
      "instances": [
        {
          "attributes": {
            "id": "projects/shimalink/locations/asia-northeast1/services/shimalink-web",
            "uri": "https://shimalink-web-xxxxx.run.app"
          }
        }
      ]
    }
  ]
}

ローカルステート vs リモートステート

ローカルステート(デフォルト)

自分のPC
└── shimalink-infra/
    └── terraform.tfstate  ← 自分のPCにだけ存在

問題点:

  • チームメンバーと共有できない
  • PCが壊れたらステートが失われる
  • 2人が同時に terraform apply すると競合する

リモートステート(推奨)

クラウドストレージ(GCS / S3)
└── terraform.tfstate  ← チーム全員がアクセス可能

開発者A ──→ リモートステート ←── 開発者B
              ↕ ロック機構
           同時書き込みを防止

リモートバックエンドの設定

# backend.tf — GCS(Google Cloud Storage)を使う場合
terraform {
  backend "gcs" {
    bucket = "shimalink-terraform-state"
    prefix = "prod"
  }
}
# backend.tf — S3(AWS)を使う場合
terraform {
  backend "s3" {
    bucket         = "shimalink-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "ap-northeast-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

ステートロック

複数人が同時に terraform apply を実行すると、ステートが壊れる可能性があります。ロック機構がこれを防ぎます。

開発者A: terraform apply
  → ステートをロック ← 開発者Bはここでブロックされる
  → リソースを変更
  → ステートを更新
  → ロックを解放
  → 開発者Bが実行可能に

ステート操作コマンド

# ステートの内容を表示
terraform show

# 特定のリソースの詳細を表示
terraform state show google_cloud_run_v2_service.shimalink_web

# リソース一覧
terraform state list

# リソースをステートから除外(手動管理に移す)
terraform state rm google_cloud_run_v2_service.shimalink_web

# リソースをインポート(既存リソースをTerraform管理下に)
terraform import google_sql_database_instance.shimalink_db shimalink-db

ステート管理のベストプラクティス

プラクティス理由
リモートバックエンドを使うチーム共有・安全性のため
ステートをGitにコミットしない機密情報が含まれるため
ロック機構を有効にする同時書き込みの競合を防ぐ
定期的にバックアップする万一の消失に備える
ステートを手動で編集しない一貫性が壊れるため

.gitignore の設定

# Terraformのステートファイル
*.tfstate
*.tfstate.backup

# ローカルの変数値(機密情報を含む場合)
*.tfvars
!*.tfvars.example

# Terraformのローカルディレクトリ
.terraform/

Yuki: 「ステートファイルにはDBのパスワードなどの機密情報が含まれることがある。絶対にGitにコミットしないこと。リモートバックエンドに保存して、暗号化を有効にするのがセキュリティの鉄則だよ。」

ポイント

  • ステートファイルはTerraformが管理するインフラの「記憶」
  • チーム開発ではリモートバックエンド(GCS/S3)が必須
  • ロック機構で同時操作の競合を防ぐ
  • ステートファイルには機密情報が含まれるため、Gitにコミットしない
  • terraform import で既存リソースをTerraform管理下に取り込める
📖 Story — Conclusion

Terraformで定義したShimaLinkのインフラ。新しい環境の構築にかかった時間は——

$ terraform apply
...
Apply complete! Resources: 12 added, 0 changed, 0 destroyed.

所要時間: 8分23秒

あなた: 「8分!?前回は丸一日かかったのに!」

あなた: 「しかも、沖縄環境とまったく同じ構成です。設定ミスの心配もない。」

Yuki: 「これがIaCの力だよ。コードを実行するだけで、何度でも同じ環境を再現できる。」


あなた: 「でもさ、これ、もし本番環境で何か問題が起きたとき、すぐに気づけるのかな?今は朝の確認でしか状態を見てないし…」

Mika: 「実は先週、夜中にサイトが遅くなってたみたいで。翌朝お客さんから連絡が来てはじめて気づいたんです。」

あなた: 「ユーザーよりも先に問題に気づきたいですね…」

Yuki: 「いい課題だね。次はモニタリングとアラートの仕組みを作ろう。サーバーの状態を常に監視して、問題が起きたら即座に通知が来るようにする。“見守る目”を持つことが、信頼性の高いサービスの条件なんだ。」


次のチャプター: Chapter 20: 見守る目 — サービスの健康状態をリアルタイムで把握する。ログ、メトリクス、アラートで「知らなかった」をゼロに。

🧠 理解度チェック

Q1.Infrastructure as Code(IaC)の最大のメリットは?

💡 手動で丸一日かかった環境構築が、Terraformなら8分で再現できたことを思い出そう。

Q2.Terraform の HCL で正しい構文は?

💡 ShimaLinkのCloud Runサービスを定義したHCLの構文だ。

Q3.terraform plan コマンドの役割は?

💡 Yukiが「planが一番大事」と言っていた。事前に確認できるから安心してapplyできる。

Q4.Terraform のステートファイルを Git にコミットしてはいけない理由は?

💡 セキュリティ監査で学んだ教訓。機密情報はバージョン管理に入れない!

Q5.Terraform で複数の環境(dev/staging/prod)を管理する方法として適切なのは?

💡 沖縄環境と同じ構成を別の県に展開するとき、変数ファイルを変えるだけで済んだ。

Q6.Terraform のリモートバックエンドを使う主な理由は?

💡 KentaとあなたがTerraformの作業を分担するとき、ステートの競合を防ぐ仕組みが必要だった。

Q7.terraform import コマンドの用途は?

💡 以前手動で作ったCloud SQLのインスタンスをTerraform管理に移行する際に使った。

よくある質問

Terraformのインストール方法がわからない

OS別のインストール手順です。 **macOSの場合:** ```bash # Homebrewでインストール brew tap hashicorp/tap brew install hashicorp/tap/terraform # 確認 terraform --version ``` **初回セットアップ:** ```bash cd shimalink-infra # プロバイダーをダウンロード terraform init ``` `terraform init` はプロジェクトごとに1回(またはプロバイダー変更時に)実行します。

terraform planで「Error: No credentials」と表示される

クラウドプロバイダーの認証が設定されていません。 **GCPの場合:** ```bash # ブラウザでログイン gcloud auth application-default login # または、サービスアカウントキーを使う export GOOGLE_APPLICATION_CREDENTIALS="/path/to/key.json" ``` **AWSの場合:** ```bash # AWS CLIで認証設定 aws configure # Access Key, Secret Key, Regionを入力 # または環境変数で設定 export AWS_ACCESS_KEY_ID="..." export AWS_SECRET_ACCESS_KEY="..." ```

terraform applyで予期しないリソースが削除される

**まず `terraform plan` で変更内容を確認**してください。 `-` が付いたリソースは削除されます。 ``` # terraform plan の出力例 - google_cloud_run_v2_service.web # ← 削除される! + google_cloud_run_v2_service.new # ← 新規作成 ~ google_sql_database.db # ← 変更される ``` **よくある原因:** - リソース名を変更した(Terraformは削除→再作成と認識する) - 別のワークスペース/ステートを参照している - 手動変更がステートと矛盾している **対策:** ```bash # 削除したくない場合は lifecycle で保護 resource "google_sql_database_instance" "db" { lifecycle { prevent_destroy = true } } ```

HCLの書き方がわからない。参考になるドキュメントは?

**公式ドキュメント:** - [Terraform Language](https://developer.hashicorp.com/terraform/language) - [Provider ドキュメント](https://registry.terraform.io/) **基本構文のチートシート:** ```hcl # 文字列 name = "shimalink" # 数値 count = 3 # 真偽値 enabled = true # リスト tags = ["web", "production"] # マップ labels = { env = "prod" team = "shimalink" } # 条件式 tier = var.env == "prod" ? "db-g1-small" : "db-f1-micro" # 参照 url = google_cloud_run_v2_service.web.uri ``` **VS Code拡張機能:** 「HashiCorp Terraform」をインストールすると補完が効きます。

ステートファイルが壊れた/紛失したらどうなる?

Terraformは管理しているリソースを「忘れて」しまいます。 **影響:** - `terraform plan` が全リソースを「新規作成」と認識する - 既存リソースとの紐づけが切れる **復旧方法:** 1. **バックアップから復元** ```bash # .tfstate.backup がある場合 cp terraform.tfstate.backup terraform.tfstate ``` 2. **terraform import で再取り込み** ```bash terraform import google_cloud_run_v2_service.web \ projects/shimalink/locations/asia-northeast1/services/shimalink-web ``` **予防策:** - リモートバックエンド(GCS/S3)でバージョニング有効化 - 定期的なバックアップ

Terraformのモジュールとは何?

モジュールは再利用可能なTerraformコードのパッケージです。関数のようなものだと考えてください。 **モジュールの構造:** ``` modules/web-service/ ├── main.tf # リソース定義 ├── variables.tf # 入力パラメータ └── outputs.tf # 出力値 ``` **使い方:** ```hcl module "web_prod" { source = "./modules/web-service" environment = "prod" cpu = "2" memory = "1Gi" } module "web_dev" { source = "./modules/web-service" environment = "dev" cpu = "1" memory = "512Mi" } ``` 同じモジュールを異なるパラメータで何度も使えます。

terraform destroyが怖い。安全に使うには?

`terraform destroy` は管理下の全リソースを削除する強力なコマンドです。 **安全対策:** 1. **重要リソースに保護を設定** ```hcl resource "google_sql_database_instance" "db" { deletion_protection = true lifecycle { prevent_destroy = true } } ``` 2. **特定リソースだけ削除** ```bash terraform destroy -target=google_cloud_run_v2_service.web ``` 3. **必ずplanを確認** ```bash terraform plan -destroy # 何が削除されるか確認 ``` 4. **本番環境では権限を制限** - destroy権限を持つIAMロールを限定する

IaCとCI/CDの組み合わせ方は?

Terraformの変更もCI/CDパイプラインで自動化できます。 ```yaml # .github/workflows/terraform.yml name: Terraform on: pull_request: paths: ["infra/**"] jobs: plan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: hashicorp/setup-terraform@v3 - run: terraform init - run: terraform plan # PRにplanの結果をコメントとして投稿 ``` **推奨ワークフロー:** 1. PRを作成 → 自動で `terraform plan` 実行 2. チームメンバーがplan結果をレビュー 3. PRをマージ → 自動で `terraform apply` 実行 これにより、インフラ変更もコードレビューの対象になります。

宣言的(declarative)と命令的(imperative)の違いは?

IaCを理解する上で重要な概念です。 **命令的(Imperative):** 「どうやるか」を手順で書く ```bash # シェルスクリプトの例 gcloud sql instances create shimalink-db ... gcloud run deploy shimalink-web ... ``` **宣言的(Declarative):** 「どうなるべきか」を状態で書く ```hcl # Terraformの例 resource "google_sql_database_instance" "db" { name = "shimalink-db" # 「このDBが存在するべき」と宣言するだけ } ``` **宣言的のメリット:** - 現在の状態に関係なく、望む状態に収束する - 冪等性がある(何回実行しても同じ結果) - Terraformが差分を計算してくれる Terraformは宣言的アプローチです。

既存のクラウドリソースをTerraform管理に移行したい

`terraform import` を使って既存リソースをステートに取り込みます。 **手順:** 1. **コードを書く**(リソース定義) ```hcl resource "google_sql_database_instance" "existing_db" { name = "shimalink-db" # ... 現在の設定を記述 } ``` 2. **importを実行** ```bash terraform import google_sql_database_instance.existing_db shimalink-db ``` 3. **planで差分を確認** ```bash terraform plan # → 差分がゼロになるようにコードを調整 ``` 4. **コードを調整して差分をゼロに** **注意:** importはステートに追加するだけで、コードは自動生成されません。自分でコードを書く必要があります。