Terraform 基礎 #1 IaC と Terraform — コンソールクリック運用の限界、インストールと初めての apply

読了 8分

AWS コンソールで数回クリックして EC2 インスタンスを起動し、セキュリティグループを開け、バケットを作ってサービスを動かしました。数か月後、同じ構成がもう 1 つ必要になったとき、どの画面で何を選んだのか誰も覚えていません。ドキュメントも残っておらず、本番環境が今どんな設定なのかは、コンソールを画面ごとに掘り返さないと分かりません。インフラを手作業で作ってきたチームなら、一度は経験する状況です。この問題をコードで解決する道具が Terraform で、このシリーズはその Terraform を土台から扱う基礎 9 本です。IaC の考え方から始めて HCL の文法、変数、state、モジュールを経て、チームで共有するリモート state まで登っていきます。続く実践 10 本で AWS インフラ全体をコードで構築し、運用 7 本でチームのワークフローを扱うトラックの最初のシリーズです。AWS アカウントを持っていて基本的なサービスに触れたことがある前提(AWS 基礎程度)で進めます。

クリックで作ったインフラが崩れていく過程 #

コンソール運用の問題は、作るときではなく時間が経ってから表面化します。

  • 再現できません: 本番と同じ構成をステージングに作るには、数十画面ぶんの選択をそのまま繰り返す必要があります。1 つでも違えば「ステージングでは動くのに本番では動かない」というたぐいの問題が生まれ、その違いを探す作業は 2 つのコンソールを並べて目で見比べる手作業になります。
  • 履歴がありません: CloudTrail に API 呼び出しの記録は残りますが、誰がなぜ変えたのかは残りません。セキュリティグループで開いている 22 番ポートが意図した設定なのか、一時対応の残骸なのか、知る方法がありません。
  • レビューがありません: コードはマージ前に同僚の目を通りますが、コンソールのクリックは実行した瞬間がそのまま反映される瞬間です。タイプミス 1 つ、思い違い 1 つがそのまま本番障害になります。
  • 人に依存します: 構成の全体を知る人がチームを離れると、インフラは誰も全容を知らないブラックボックスになります。

この 4 つに共通する原因は 1 つです。インフラの現在の状態が、どこにも文章として書かれていないことです。

IaC — あるべき状態を宣言すれば、道具が現実を合わせます #

IaC(Infrastructure as Code)は、インフラをクリックではなくコードファイルで定義する方式です。ここで重要なのはコードの性質です。AWS CLI のコマンドを順番に並べたシェルスクリプトもコードですが、それは「バケットを作れ」という命令の羅列なので、2 回実行するとエラーになるか重複が生まれます。Terraform のコードは「この名前のバケットが存在すべきだ」というあるべき状態の宣言です。実行すると Terraform が現在の状態と宣言を比較し、足りないものだけ作り、余分なものだけ消し、変わったものだけ直します。すでに宣言と一致していれば何もしません。何度実行しても結果が同じ、ということです。

インフラがコードファイルになった瞬間、ソフトウェア開発が積み上げてきた道具がすべてインフラにも適用されます。Git に上げればすべての変更にコミット履歴が残り、変更は PR でレビューを通り、事故が起きれば以前のコミットに戻せて、同じコードを別のリージョンや別のアカウントに適用すれば同一の環境を再現できます。前の節の 4 つの問題への答えが、すべてここから出てきます。

Terraform の位置 — CloudFormation、CDK、Pulumi との比較 #

IaC の道具は Terraform だけではありません。AWS を使うなら、候補はふつう 4 つです。

ツール対応範囲コードの形特徴
Terraformマルチクラウド、SaaSHCL(宣言型の専用言語)事実上の標準、プロバイダーのエコシステムが最大
CloudFormationAWS 専用JSON、YAMLAWS マネージド、状態を AWS が保管
AWS CDKAWS 専用TypeScript、Python など汎用言語で書いて CloudFormation に変換
PulumiマルチクラウドTypeScript、Python など汎用言語を好むチームに向く

CloudFormation と CDK は AWS に深く統合される代わりに、AWS の外を定義できません。Terraform はプロバイダーというプラグイン構造を持ち、AWS、GCP、Azure のようなクラウドはもちろん、GitHub リポジトリ、Cloudflare の DNS、Datadog のモニターまで同じ文法で扱えます。実務のインフラがクラウド 1 つで完結することはまれで、この汎用性が Terraform を事実上の標準にしました。求人票の IaC の欄にいちばんよく登場する名前でもあります。

1 つ知っておきたい背景があります。Terraform は 2023 年にライセンスをオープンソースから BSL に変更し、これに反発したコミュニティが OpenTofu というフォークを作って今も維持しています。2 つのツールは現在も文法がほぼ同じで、このシリーズの内容の大部分はそのまま通用します。どちらを選ぶかの基準は、運用シリーズの最終回で別途扱います。

インストールと準備 #

このシリーズは Terraform 1.15、AWS プロバイダー 6.x を基準に書きます。macOS は Homebrew でインストールします。

インストール(macOS)
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

Ubuntu 系は HashiCorp のリポジトリを登録して apt でインストールします。

インストール(Ubuntu)
wget -O - https://apt.releases.hashicorp.com/gpg | \
  sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
  https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
  sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

インストールを確認します。

バージョン確認
$ terraform version
Terraform v1.15.8

Terraform が AWS にリソースを作るには認証情報が必要です。AWS CLI を設定済みなら(aws configure または SSO ログイン)、Terraform は同じ認証情報を自動的に使うので追加の設定はありません。

最初のリソース — S3 バケットをコードで作る #

空のディレクトリを 1 つ作り、main.tf ファイルを書きます。S3 バケットは作っておくだけなら料金がかからないので、最初の実習に安全です。

main.tf
terraform {
  required_version = ">= 1.15.0"

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

provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_s3_bucket" "hello" {
  bucket = "my-terraform-hello-20260822"

  tags = {
    ManagedBy = "terraform"
  }
}

文法は次回にじっくり見るとして、今は 3 つのかたまりの役割だけ押さえます。terraform ブロックは Terraform 自体とプロバイダーのバージョンを固定し、provider ブロックはどのリージョンに作るかを決め、resource ブロックが「この名前の S3 バケットが存在すべきだ」という宣言です。S3 バケット名は世界中で一意である必要があるので、my-terraform-hello-20260822 の部分は自分だけの値に変えてください。

このディレクトリで最初の 1 回だけ、初期化を実行します。

初期化
$ terraform init
Initializing provider plugins...
- Installing hashicorp/aws v6.x.x...
Terraform has been initialized!

init はコードに宣言されたプロバイダーのプラグインをダウンロードする準備段階です。次が、このシリーズ全体でいちばん重要なコマンドの plan です。

計画の確認
$ terraform plan
Terraform will perform the following actions:

  # aws_s3_bucket.hello will be created
  + resource "aws_s3_bucket" "hello" {
      + bucket = "my-terraform-hello-20260822"
      ...
    }

Plan: 1 to add, 0 to change, 0 to destroy.

plan はまだ何も作りません。宣言と現在の状態を比較して、何をするつもりなのかを事前に見せてくれるコマンドです。コンソールのクリックにはなかった関門がここに生まれました。実行前に変更内容を目で確認でき、チームならこの出力をレビューに載せられます。計画が意図と一致したら適用します。

適用
$ terraform apply
...
Do you want to perform these actions?
  Enter a value: yes

aws_s3_bucket.hello: Creating...
aws_s3_bucket.hello: Creation complete after 2s

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

コンソールの S3 画面を開くと、バケットが実際にできています。クリックの代わりに、コードファイルとコマンド 2 つでインフラを作ったわけです。この状態で terraform plan をもう一度実行すると「No changes」と出ます。宣言と現実がすでに一致しているのでやることがない、という意味で、これが先ほど説明した宣言型の動作です。

作ったものをコードで消す #

実習が終わったので片付けます。コンソールでバケットを探して消す必要はなく、コマンド 1 つで済みます。

削除
$ terraform destroy
Plan: 0 to add, 0 to change, 1 to destroy.
  Enter a value: yes

Destroy complete! Resources: 1 destroyed.

Terraform は自分が作ったリソースをすべて把握していて、そのまま逆にたどって消します。実習用のインフラを作っては消すのに、これほど向いた道具はありません。ところで、Terraform は自分が作ったものをどうやって覚えていたのでしょうか。ディレクトリを見ると terraform.tfstate というファイルができています。Terraform が管理中のリソースの一覧と状態を記録したファイルで、Terraform の動作原理の全体がこのファイルにかかっています。#5 で詳しく扱います。

まとめ #

今回扱った内容です。

  • コンソールクリック運用は、再現不能、履歴の不在、レビューの不在、人への依存という問題を生みます。共通の原因は、インフラの現在の状態がどこにも書かれていないことです
  • IaC はあるべき状態をコードで宣言する方式です。道具が現在の状態と比較して差分だけを反映するので何度実行しても結果が同じで、Git の履歴、レビュー、ロールバックがインフラにも適用されます
  • CloudFormation と CDK は AWS 専用、Terraform と Pulumi はマルチクラウドです。プロバイダーのエコシステムでも求人市場でも、Terraform が事実上の標準です
  • terraform init はプロバイダーの準備、plan は変更内容のプレビュー、apply は適用、destroy は管理中のリソースの削除です
  • Terraform は管理中のリソースを terraform.tfstate ファイルに記録します。このファイルの正体は #5 で扱います

次回(#2 HCL の基本文法)では、今日は素通りした main.tf の文法をきちんと分解します。block、argument、リソースアドレスといった HCL の骨格と、plan、apply、destroy のサイクルを手に馴染ませていきます。

X