Terraform 実践 #1 プロジェクト設計とブートストラップ — 全体像、バックエンド、共通タグ
基礎シリーズの 9 本で文法と原理を身につけたので、今度は実際に使えるインフラを最初から最後まで構築する番です。この実践シリーズ 10 本の目標は 1 つです。Web サービス 1 つの運用に必要なネットワーク、コンピューティング、データベース、シークレット、権限、ドメインのすべてを Terraform のコードで作ることです。文法の説明は基礎編へのリンクに任せて、ここでは実務で実際に下すことになる設計判断を中心に進めます。
10 本かけて作るもの #
まず完成形を描いておきます。架空の Web サービス myapp のインフラです。
ユーザー
│
├── Route 53 (ドメイン) ← #10
│ │
│ ├── CloudFront ── S3 (静的アセット) ← #5, #10
│ │
│ └── ALB (HTTPS) ← #4, #10
│ │
│ ├── Auto Scaling EC2 ← #3, #4
│ └── ECS Fargate ← #8
│ │
│ └── RDS (PostgreSQL) ← #5
│
├── VPC: パブリック・プライベートサブネット × 2 AZ, NAT ← #2
├── シークレット: SSM・write-only 引数 ← #6
├── IAM: インスタンスロール, GitHub OIDC ← #7
└── 環境分離: dev・prod + モジュールリファクタリング ← #9コンピューティング層を EC2 Auto Scaling で先に作り、#8 で ECS Fargate に切り替える流れを意図的に入れています。実務のインフラは一度に完成するものではなく、部品を入れ替えながら進化していくもので、その切り替えを Terraform で安全にやり切る経験こそ、このシリーズでいちばん実践的な部分だからです。
費用の話を先にします #
基礎編の S3 バケットと違い、今回のシリーズのリソースには料金がかかります。大きいものだけ挙げると、NAT ゲートウェイ(時間あたり + データ処理)、ALB(時間あたり)、RDS(インスタンス時間)、ECS Fargate(vCPU・メモリ時間)です。少額ですが、点けたまま忘れるとある日請求が跳ね上がる経験をすることになります。そこで、このシリーズは 2 つの原則で進めます。
- 実習が終わったら
terraform destroyで削除します。コードが残っているので、次回の開始時に apply 1 回でそのまま復元できます。インフラを気軽に消しては作り直すこと自体が、IaC の力を体感する練習です。 - サイズは実習用の最小構成にします。t3.micro、db.t4g.micro、NAT 1 つのように最小の構成で作り、本番サイズとの違いは #9 の環境分離で変数として表現します。
ブートストラップ — state バケットとバックエンド #
基礎 #9で整理したとおり、state バケットは Terraform の外で 1 回だけ作ります。
aws s3api create-bucket --bucket myapp-tfstate \
--region ap-northeast-2 \
--create-bucket-configuration LocationConstraint=ap-northeast-2
aws s3api put-bucket-versioning --bucket myapp-tfstate \
--versioning-configuration Status=Enabledプロジェクトディレクトリを作り、骨組みのファイルを置きます。今は単一ディレクトリで始めて、コードが十分に育った #9 でモジュールにリファクタリングします。最初から完璧な構造を悩んで止まるより、動くコードをリファクタリングするほうが学びも多く、実務の実際の順序でもあります。
myapp-infra/
├── backend.tf # terraform ブロック、バックエンド、provider バージョン
├── providers.tf # provider 設定
├── variables.tf
├── outputs.tf
└── main.tf # これからリソースが積み上がる場所# backend.tf
terraform {
required_version = ">= 1.15.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
backend "s3" {
bucket = "myapp-tfstate"
key = "myapp/dev/terraform.tfstate"
region = "ap-northeast-2"
use_lockfile = true
}
}key を最初から myapp/dev/ にしている点に注目してください。今は dev だけですが、#9 で prod ができたときに myapp/prod/ が並ぶ場所を、あらかじめ用意してあります。
default_tags — すべてのリソースに自動でタグを付ける #
provider 設定には実務標準の仕掛けを 1 つ入れます。
# providers.tf
provider "aws" {
region = var.region
default_tags {
tags = {
Project = "myapp"
Env = var.env
ManagedBy = "terraform"
}
}
}default_tags は、この provider で作るすべてのリソースにタグを自動で付けます。リソースごとに tags を書く必要がなくなり、付け忘れるリソースもなくなります。タグは飾りではなく費用分析の基本軸で(Cost Explorer でのタグ別集計)、プロジェクト初日に設定しておくのと、後から数百個のリソースに遡って付けるのとでは手間がまるで違います。リソース個別の tags は default_tags の上にマージされるので、個別の Name タグはリソース側で付ければ十分です。
# variables.tf
variable "region" {
type = string
default = "ap-northeast-2"
}
variable "env" {
type = string
default = "dev"
validation {
condition = contains(["dev", "prod"], var.env)
error_message = "env は dev または prod のみ許可します。"
}
}初回実行 — 空のインフラの baseline #
リソースが 1 つもない状態で、初期化と plan を実行します。
$ terraform init
Initializing the backend...
Successfully configured the backend "s3"!
$ terraform plan
No changes. Your infrastructure matches the configuration.「No changes」がこのプロジェクトの出発点です。バックエンドが S3 につながり、ロックが効き、コードと現実が一致した状態から始めます。これから 9 本かけてこのディレクトリにリソースを積み上げていき、毎回の締めくくりは常に「plan が意図と一致しているか」で検証します。
まとめ #
今回扱った内容です。
- 10 本の完成形は、VPC 上の ALB、Auto Scaling EC2 から ECS Fargate への切り替え、RDS と S3、シークレットと IAM、ドメインと HTTPS まで備えた Web サービスのインフラです
- NAT、ALB、RDS、Fargate は有料です。実習後に destroy で削除し、次回に apply で復元するリズムで進めます
- state バケットは Terraform の外で作り、バックエンドの key は環境分離を見据えて
myapp/dev/にしてあります - provider の default_tags で、すべてのリソースに Project、Env、ManagedBy タグが自動で付きます。費用分析の基本軸は初日に設定しておきます
- 単一ディレクトリで始めて #9 でモジュールにリファクタリングします。動くコードをリファクタリングするのが実務の順序です
次回(#2 VPC ネットワークのコード化)では、すべての土台であるネットワークを作ります。パブリック・プライベートサブネットを 2 つの AZ に配置し、ルーティングと NAT ゲートウェイまでコードで構築します。