メインコンテンツまでスキップ

Terraform 基礎: HCL の最小文法と最初の apply

前章では、Tasuku の要件と、それを AWS の 7 サービスへ割り当てる全体設計を確認しました。この章からは、その設計を実際にコードとして書き始めます。

最初に apply するのは、VPC でも RDS でもなく、時間課金の発生しない AWS Budgets の予算アラートです。課金を気にせず手を動かせる対象から始めることで、この章の主題である HCL の文法と state という概念に集中できます。

この章は、次の依存チェーンの起点にあたります。

2.1. インフラをコードで書く理由

AWS のリソースは、コンソールの画面からでも作成できます。VPC を作り、サブネットを切り、EC2 インスタンスを起動する操作は、すべてブラウザ上で完結します。

Terraform を使う理由は、操作の手間を減らすことではありません。コンソールでの変更は、いつ誰が何を変更したかが画面上にしか残らず、変更後に他人がレビューする手段がありません。HCL でリソースを定義すれば、変更はコードの差分として残り、pull request でレビューできます。適用前に「何が変わるか」を terraform plan で確認できる点も、コンソール操作にはない性質です。

Tasuku の構成もこの先、VPC や ECS、RDS と増えていきますが、最初に書くのはこの章の数百行に満たない HCL です。

2.2. HCL の最小文法

HCL (HashiCorp Configuration Language) は、次の 4 種類のブロックを組み合わせて書きます。

  • terraform ブロック: Terraform 自体のバージョンや使用する provider など、Terraform の動作を設定します1
  • provider ブロック: 対象クラウド (今回は AWS) への接続設定を宣言します。同じ provider に複数の設定を持たせることもでき、典型的な用途はマルチリージョンです2
  • resource ブロック: 実際に作成するインフラを定義します。設定内で参照するときは <TYPE>.<LABEL> の形式を使います3
  • variable ブロック: モジュールをソースの変更なしにカスタマイズするための入力値を定義します。default 引数を指定すると、値を省略したときにその既定値が使われます4

resource ブロックの基本形は次のとおりです。

resource "<リソースタイプ>" "<ローカル名>" {
<引数名> = <値>
}

<リソースタイプ> は provider (今回は AWS) が定義する型で、aws_vpcaws_budgets_budget のように決まっています。<ローカル名> は、この設定ファイルの中だけで通用する参照名であり、AWS コンソールに表示される実際のリソース名とは別物です。

2.3. provider を固定する

Terraform はバージョンによって挙動が変わることがあるため、terraform ブロックで CLI 自体の許容バージョンと provider の要求バージョンを固定します。versions.tf は次のとおりです。

terraform {
required_version = ">= 1.10"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.53"
}
}
}

required_version = ">= 1.10" は、Terraform CLI が 1.10 以上のバージョンであることを要求します。

version = "~> 6.53"~> は pessimistic constraint operator と呼ばれ、指定したバージョン文字列の最後のセグメントだけが変動することを許容します5。ここでは 6.53 という 2 セグメントの指定なので、右端のセグメント (マイナーバージョン) の増加を許し、6.536.54 は許容しますが 7.0 は許容しません。同じ演算子でも ~> 6.53.0 のように 3 セグメントで書くと、変動してよいのはパッチバージョンだけになるため、この 2 つの書き方を混同しないようにします。

provider 自体の設定は providers.tf に書きます。

provider "aws" {
region = var.region

# ここで付けたタグは、この provider 経由で作る全リソースに自動付与される
default_tags {
tags = {
Project = var.project
Env = var.env
ManagedBy = "terraform"
}
}
}

region をはじめ、コード中の var.xxxvariables.tf で定義した変数への参照です。

variable "project" {
description = "プロジェクト名。リソース名の接頭辞に使う"
type = string
default = "tasuku"
}

variable "env" {
description = "環境識別子 (dev / prod など)"
type = string
default = "dev"
}

variable "region" {
description = "デプロイ先リージョン"
type = string
default = "ap-northeast-1"
}

variable "budget_limit_usd" {
description = "月額予算アラートのしきい値 (USD)"
type = string
default = "50"
}

variable "alert_email" {
description = "予算アラートの通知先メールアドレス"
type = string
}

projectenvregionbudget_limit_usd には既定値があり、値を省略すればそのまま使われます。alert_email だけ default がないため、値を指定しないと terraform plan の実行時に入力を求められます。実際の値の指定方法は 2.6 で扱います。default_tags の中身は 2.5 で扱います。

2.4. state とは何か

terraform apply を実行すると、Terraform は作成したリソースの情報を state というファイルに記録します。state の役割は、設定ファイル上の宣言と、実際に AWS 上に存在するリソースを対応づけることです6。たとえば resource "aws_budgets_budget" "monthly" という宣言は、それだけでは単なる HCL の記述に過ぎません。apply 後にどの実リソース (実際の budget ID) がこの宣言に対応するかを覚えているのは state です。設定ファイルから宣言を削除したとき、Terraform が「対応する実リソースを削除すればよい」と判断できるのも、state にその対応が記録されているためです。

state の保存先を決めるのが backend です。何も指定しなければ local backend が既定で使われ、ルートモジュール相対の terraform.tfstate というファイルにリソースの情報を保存します7。この章ではまだ local backend のまま進めます。複数人での共同作業や、state ファイル自体の安全な保管が必要になるのは、第 5 章で S3 に移行してからです。

2.5. name_prefix と default_tags

locals.tf は、Tasuku 全体で使う命名規則を 1 箇所にまとめます。

locals {
# 全リソース名の接頭辞。環境分離 (第 11 章) を見据えて env を含める
name_prefix = "${var.project}-${var.env}"
}

local.name_prefixtasuku-dev のような文字列になり、この先の章で作るリソース名の接頭辞として繰り返し使います。env を含めているのは、第 11 章で dev / prod の環境分離を扱うときに、同じ名前のリソースが環境間で衝突しないようにするためです。

タグについては、リソースひとつずつに tags = { ... } を書く代わりに、2.3 で見た provider ブロックの default_tags を使います。default_tags は、その provider インスタンスが扱う全リソースに横断的にタグを適用する設定です8tags を実装するほぼすべてのリソースに効きますが、aws_autoscaling_group だけは対象外という例外があります9 (この連載で Auto Scaling Group が登場するのは第 11 章以降のため、今の時点では意識しなくて構いません)。

ProjectEnvManagedBy の 3 つのタグを default_tags に置いておけば、この先の章で新しいリソースを追加するたびに、同じタグ付けをリソースごとに書き直さずに済みます。

2.6. 最初の apply: 予算アラートを作る

ここまでの terraformprovidervariable の設定に、実際のリソースを 1 つ追加します。budget.tf が定義する aws_budgets_budget です。

resource "aws_budgets_budget" "monthly" {
name = "${local.name_prefix}-monthly-budget"
budget_type = "COST"
limit_amount = var.budget_limit_usd
limit_unit = "USD"
time_unit = "MONTHLY"

# 実績がしきい値の 80% を超えたら通知
notification {
comparison_operator = "GREATER_THAN"
threshold = 80
threshold_type = "PERCENTAGE"
notification_type = "ACTUAL"
subscriber_email_addresses = [var.alert_email]
}

# 月末予測がしきい値を超えそうなら通知
notification {
comparison_operator = "GREATER_THAN"
threshold = 100
threshold_type = "PERCENTAGE"
notification_type = "FORECASTED"
subscriber_email_addresses = [var.alert_email]
}
}

この budget は、月間コストが上限額 (var.budget_limit_usd、既定 50 USD) の 80 % に達した実績と、月末時点で 100 % を超えそうな予測の 2 つを、それぞれ var.alert_email 宛のメールで知らせます。

このリソース自体に費用はかかりません。予算アクション (しきい値超過時に IAM ポリシーの適用や EC2 / RDS インスタンスの制御などを自動で行う機能10) を有効にした budget は 3 件目から 1 日 0.10 USD の課金が始まりますが、通知だけの budget を監視・通知する分には費用がかかりません11

通知タイミングの制約

Budgets の予算情報は 1 日に最大 3 回更新され、前回の更新から 8 〜 12 時間後に更新されるのが通常です12。加えて、AWS のリソース使用から実際の課金確定までにも遅延があるため、しきい値を超えてから通知が届くまでにタイムラグが生じます12。想定外の課金に早く気づくための仕組みではありますが、リアルタイムの支出監視ではありません。

alert_email に既定値がないことは 2.3 で述べたとおりです。実際の値は terraform.tfvars に書きます。terraform.tfvars.example をコピーして、少なくとも alert_email を自分のメールアドレスに差し替えてください。

# terraform.tfvars にコピーして自分の値を設定する
# project = "tasuku"
# env = "dev"
# region = "ap-northeast-1"
alert_email = "you@example.com"

2.7. 動作確認

ここまでの versions.tfproviders.tflocals.tfvariables.tfbudget.tf、それに terraform.tfvars を同じディレクトリにまとめたら、次の順で確認します。Terraform 自体のインストール手順は第 1 章 1.7 節を参照してください。

terraform fmt -check -recursive -diff
terraform init
terraform validate
terraform plan

terraform fmt は HCL の整形、terraform validate は構文と provider スキーマとの整合を確認します。terraform plan は AWS 認証情報を使って現在の状態を読みに行き、apply した場合に何が作成・変更・削除されるかを表示します。ここで budget が 1 件追加される計画が表示されることを確認してから、terraform apply に進んでください。

本章のまとめ

  • コンソール操作と違い、Terraform はインフラの変更をコードの差分として残し、適用前に terraform plan でレビューできます
  • HCL は terraformproviderresourcevariable の 4 種類のブロックを組み合わせて書き、provider のバージョンは ~> 演算子で固定します
  • state は設定ファイルの宣言と実際の AWS リソースを対応づける記録で、この章では既定の local backend にそのまま保存します
  • default_tags を provider に設定すると、以降のリソースにタグ付けを個別に書かずに済みます

次に読む

Footnotes

  1. 出典: Terraform block reference

  2. 出典: Provider block reference

  3. 出典: resource block reference

  4. 出典: variable block reference

  5. 出典: Version Constraints。2 セグメント指定 (~> 1.1 相当) は右端セグメントの増加のみを許容します。

  6. 出典: Purpose of Terraform State

  7. 出典: Backend Type: local

  8. 出典: AWS Provider の default_tags Configuration Block

  9. 出典: 同上。default_tagsaws_autoscaling_group を除く、tags を実装する全リソースに適用されます。

  10. 出典: Configuring budget actions。IAM ポリシー、Service Control Policy (SCP)、EC2 / RDS インスタンスの制御の 3 種類が行えます。

  11. 出典: AWS Budgets Pricing

  12. 出典: Budget details 2