🎯 最終演習

Terraformで書いて・確かめて・作って・消すまでを通しでやってみよう(総合演習)

ここまでの Terraform レッスンで学んだコマンドをひとつの流れにつないで、インフラを作って消すところまで通します

Terraformの日常は、「書く → 確かめる → 作る → 消す」のくり返しです。使うコマンドは驚くほど少なく、initplanapplydestroyの4つがその骨格になります。

この演習でいちばん覚えてほしいのはplanです。「実行する前に、何が起きるか全部見せてもらう」——これができるかどうかが、手でクラウドを触るのとの決定的な違いです。消えるはずのないサーバーが消える事故は、planを読まないときに起きます。

ページの一番下に本物の疑似ターミナルがあります。読むだけで終わらせず、必ず自分で打ってみてください。状態はコマンドをまたいで続くので、上から順に打てばそのまま完走できます。

ここは学習用の疑似環境です。作業フォルダのmain.tfにはaws_instance.webaws_s3_bucket.dataの2つが書いてある想定で、本物のクラウドにはつながっていません。料金も発生しません。

完成イメージ

最後まで進むと、こう表示されるところまで作ります。下の実行結果は、このページを作るときに実際にコードを走らせて得たものです。

$ terraform state list
$ terraform workspace list
* default
  staging

STEP 1作業フォルダを使える状態にする

🎯 このステップのゴール: terraform init でプロバイダを取ってきて、何が必要とされているか確かめる

terraform versionで本体とプロバイダの版を確かめたら、まずterraform initです。

これを済ませるまで、他のコマンドは全部失敗します。試しに先にterraform planを打つとCould not load pluginと返ってきます。

initが何をしているのか。設定ファイルを読んで「AWSを使うと書いてあるな」と判断し、AWS用のプロバイダ(部品)をダウンロードして.terraform/に置きます。Terraform本体はクラウドの操作方法を知らず、プロバイダが実際の通信を担当する——この分担があるから、同じTerraformでAWSもGCPも扱えます。

initを打ち直す場面は決まっています。プロバイダを追加したとき、版を変えたとき、モジュールを足したとき、そして誰かのリポジトリを最初にcloneしたときです。.terraform/はgitに入れないのが普通なので、cloneした直後は必ずinitからになります。

terraform providersで、この設定が必要としているプロバイダとその版の条件が読めます。~> 5.0は「5系の最新を使うが、6系には上げない」という意味です。版を固定しておかないと、ある日突然プロバイダが上がって挙動が変わります——チームで使うときは必ず範囲を指定します。

打つコマンド
terraform version
terraform init
terraform providers
実行結果
$ terraform version
Terraform v1.7.5
on darwin_arm64
+ provider registry.terraform.io/hashicorp/aws v5.40.0
$ terraform init
Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.40.0...
- Installed hashicorp/aws v5.40.0 (signed by HashiCorp)

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure.
$ terraform providers
Providers required by configuration:
.
└── provider[registry.terraform.io/hashicorp/aws] ~> 5.0

STEP 2作る前に、書いたものを確かめる

🎯 このステップのゴール: fmt で整え、validate で文法を見て、plan で「何が起きるか」を読む

いきなりapplyせず、3段階で確かめます。この順番には意味があります。

terraform fmtインデントや空白を公式の書式に整えるコマンドです。何も表示されなければ、直すところが無かったということ——整えたファイルがあれば、その名前が並びます。

見た目の話に見えて、実は差分の話です。書式がばらばらだと、中身を変えていない行まで差分に出て、レビューで本当の変更が埋もれます。だからチームではfmtを保存時に自動で走らせます。

terraform validate文法と型の検査です。閉じ括弧の抜け、存在しない属性名、型の食い違いを見つけます。クラウドには一切問い合わせません——だから一瞬で終わり、認証情報も要りません。

そしてterraform planこれがTerraformの心臓です。設定ファイルと現在の状態を突き合わせて、差を埋めるために何をするつもりかを全部見せてくれます。

読み方は記号です。+が作成、-が削除、~が変更、-/+が作り直し。最後の行のPlan: 2 to add, 0 to change, 0 to destroyが要約です。

-/+(作り直し)を見たら、必ず手を止めてください。設定を1文字変えただけのつもりが、サーバーをいったん削除してから作り直す計画になっていることがあります。中のデータは消えます。本番で起きる事故の多くは、この行を読み飛ばしたときに起きます。

打つコマンド
terraform fmt
terraform validate
terraform plan
実行結果
$ terraform fmt
$ terraform validate
Success! The configuration is valid.
$ terraform plan
Terraform will perform the following actions:

  # aws_instance.web will be created
  + resource "aws_instance" "web" {
      + id = (known after apply)
    }

  # aws_s3_bucket.data will be created
  + resource "aws_s3_bucket" "data" {
      + id = (known after apply)
    }

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

STEP 3確認した計画を、そのまま実行する

🎯 このステップのゴール: plan をファイルに保存し、apply に渡して「見たとおり」を実行する

terraform plan -out=tfplanで、計画をファイルに保存します。そしてterraform apply tfplanで、その保存した計画を実行します。

なぜわざわざ2段階にするのか。引数なしのterraform applyは、その場でもう一度planを作り直して、yesと打たせてから実行します。つまり、あなたが確認した計画と、実際に実行される計画は「別物」です。

ふだんは同じ結果になります。ただし、確認してから実行するまでの間に誰かが手で何かを変えていたら、違う計画が走ります。「レビューでは何も消えないはずだったのに、本番では消えた」——これが起きる仕組みです。

-outで保存した計画には、実行する内容がそのまま固定されています。だからapply tfplanは確認を聞いてきません。聞く必要が無いからです——中身は既にあなたが読んだものと同一だと保証されています。

チームで運用するときの定番がこれです。CIがplan -outを実行して結果をレビューに出し、承認されたらそのまったく同じファイルapplyする。人が見たものと実行されるものが一致します。

実行するとCreating...からCreation completeまで流れ、最後にApply complete! Resources: 2 addedこの時点で、Terraformはterraform.tfstateに「何を作ったか」を記録しています。次のステップで、その中身を見ます。

打つコマンド
terraform plan -out=tfplan
terraform apply tfplan
実行結果
$ terraform plan -out=tfplan
Terraform will perform the following actions:

  # aws_instance.web will be created
  + resource "aws_instance" "web" {
      + id = (known after apply)
    }

  # aws_s3_bucket.data will be created
  + resource "aws_s3_bucket" "data" {
      + id = (known after apply)
    }

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

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"
$ terraform apply tfplan
aws_instance.web: Creating...
aws_instance.web: Creation complete after 1s [id=aws_instance-00000001]
aws_s3_bucket.data: Creating...
aws_s3_bucket.data: Creation complete after 2s [id=aws_s3_bucket-00000002]

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

STEP 4Terraformが覚えている内容を読む

🎯 このステップのゴール: state list・show・output で、いま管理下にあるものを確かめる

terraform state listで、Terraformが管理しているリソースの一覧が出ます。aws_instance.webaws_s3_bucket.dataの2つです。

ここでstate(状態ファイル)の役割をはっきりさせておきます。Terraformはterraform.tfstate「設定ファイルのこの記述が、クラウド上のこのIDに対応する」という対応表を書いています。この対応表があるから、2回目のapplyで同じサーバーがもう1台できたりしないのです。

terraform showは、その中身を人が読める形で表示します。state listが名前だけの一覧なのに対し、showは各リソースの属性まで出します。実際に割り当てられたIDやIPは、ここで確かめます。

terraform output設定ファイルでoutputと宣言した値だけを取り出します。IPアドレスやバケット名——作ったあとで人や別のツールが必要とする値を、ここに出しておきます。

-jsonを付けると機械が読める形になるので、デプロイスクリプトに繋ぐときはこれを使います。

stateファイルは絶対にgitに入れないでください。データベースのパスワードなど、設定ファイルに書いた秘密がそのまま平文で入っています。チームで使うならS3などのリモートバックエンドに置き、同時に2人がapplyしないようロックを掛けます——同時に走ると、対応表が壊れます。

打つコマンド
terraform state list
terraform show
terraform output
実行結果
$ terraform state list
aws_instance.web
aws_s3_bucket.data
$ terraform show
# aws_instance.web:
resource "aws_instance" "web" {
    id = "aws_instance-example12345"
}

# aws_s3_bucket.data:
resource "aws_s3_bucket" "data" {
    id = "aws_s3_bucket-example12345"
}
$ terraform output
instance_public_ip = "203.0.113.10"
bucket_name = "my-terraform-bucket"

STEP 5同じコードで、本番と検証を分ける

🎯 このステップのゴール: workspace を新しく作り、state が本当に分かれていることを目で確かめる

同じ構成の環境を、本番と検証で2つ持ちたいとします。設定ファイルをコピーするのは最悪の手です——片方だけ直し忘れて、いつのまにか別物になります。

terraform workspace new stagingで、同じコードのまま、state だけを別にした環境を作れます。

ここを自分の目で確かめてください。切り替えた直後にterraform state listを打つと——何も出ません。default で作った2つのリソースは、staging からは見えないのです。

これがワークスペースの正体です。設定ファイルは1つ、state はterraform.tfstate.d/staging/に分かれて置かれます。コードは共有し、実体は分ける。だから片方だけ直し忘れる、ということが起きません。

terraform workspace select defaultで戻ると、2つのリソースがちゃんと戻ってきますworkspace list*が、いまどちらにいるかを示しています。

いまどのワークスペースにいるかを、applyの前に必ず確かめてください。ワークスペースの切り替えはプロンプトに表示されません。本番のつもりで検証を消した、検証のつもりで本番を消した——どちらも実際に起きています。terraform workspace listを打つ習慣が、いちばん確実な予防です。

なおワークスペースが向くのは「ほぼ同じ構成の環境」までです。本番と検証でサーバーの台数や構成そのものが違うなら、フォルダを分けたほうが素直になります。

打つコマンド
terraform workspace list
terraform workspace new staging
terraform state list
terraform workspace select default
terraform workspace list
terraform state list
実行結果
$ terraform workspace list
* default
$ terraform workspace new staging
Created and switched to workspace "staging"!

You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.
$ terraform state list
$ terraform workspace select default
Switched to workspace "default".
$ terraform workspace list
* default
  staging
$ terraform state list
aws_instance.web
aws_s3_bucket.data

STEP 6現実とのズレを見てから、全部消す

🎯 このステップのゴール: refresh で state を実物に合わせ、destroy で作ったものを片付ける

terraform refreshは、実際のクラウドに問い合わせて、state を現実に合わせ直すコマンドです。

なぜズレるのか。誰かが管理画面から手でサーバーの設定を変えると、クラウドの実物と Terraform の対応表が食い違います。これをドリフトと呼びます。refreshすると state が実物に合わせて更新され、次のplanで「戻すために何をするか」が出てきます。

Terraformを使い始めたら、手で触るのをやめる——面倒に見えて、これが結局いちばん安全です。手を入れた場所は必ずどこかで食い違います。

そしてterraform destroyこの設定で作ったものを、全部削除します。

「全部」の範囲を正確に言うと、いま選んでいるワークスペースの state に載っているもの全部です。だからステップ5の注意がここに効いてきます——打つ前にterraform workspace listで、自分がどこにいるか確かめてください。

destroyplanと同じで、実行前に消える対象を全部見せてからyesを求めます。その一覧を読まずにyesと打たない——これだけで、ほとんどの事故は防げます。

最後にterraform state listを打つと何も出ませんterraform planを打つとまた2 to add——設定ファイルは残っているので、applyすればいつでも同じものが作り直せます

これがコードでインフラを書く意味です。作ったものは消してよい。必要になったら、同じコードから同じものが出てくる。「あのとき何を設定したか」を思い出す必要が、もうありません。

ページ下の疑似ターミナルで、ここまでを最初から自分の手で通してみてください。特にplanを読む癖は、打った回数だけ身につきます。

打つコマンド
terraform refresh
terraform destroy
terraform state list
terraform plan
実行結果
$ terraform refresh
aws_instance.web: Refreshing state...
aws_s3_bucket.data: Refreshing state...
$ terraform destroy
aws_instance.web: Destroying...
aws_instance.web: Destruction complete after 1s
aws_s3_bucket.data: Destroying...
aws_s3_bucket.data: Destruction complete after 2s

Destroy complete! Resources: 2 destroyed.
$ terraform state list
$ terraform plan
Terraform will perform the following actions:

  # aws_instance.web will be created
  + resource "aws_instance" "web" {
      + id = (known after apply)
    }

  # aws_s3_bucket.data will be created
  + resource "aws_s3_bucket" "data" {
      + id = (known after apply)
    }

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

仕上げ自分で最初から打ってみよう

🎯 このステップのゴール: ここまでの流れを、自分の手で最後まで通す

下は本物の疑似ターミナルです。まっさらな状態から始まるので、STEP1のコマンドから順に打って、マージまで辿り着いてください。読んで分かった気になるのと、自分で打てるのとは別物です。

詰まったら上のステップに戻って構いません。resetと打てば最初からやり直せます。

$

よくある質問

terraform fmt を実行しても何も表示されません

それで正常です。整形するところが無かったという意味で、本物のTerraformも同じように黙ります。

直したファイルがあるときだけ、そのファイル名が並びます。何が変わるか先に見たいならterraform fmt -diff、変更せずに確認だけしたいならterraform fmt -checkです。CIでは後者を使って、整形されていないコードを弾きます。

plan と apply、validate は結局どう違うんですか

調べる深さが違います。

validate文法の検査だけ。クラウドに問い合わせないので一瞬で終わり、認証情報も要りません。括弧の抜けや存在しない属性名を見つけます。

plan実際のクラウドの状態と突き合わせて、これから何をするかを見せるコマンドです。何も変更しません。

applyだけが実際に変更しますvalidateplanapply の順に進めるのが基本です。

terraform.tfstate は git にコミットしてもいいですか

絶対にコミットしないでください。設定ファイルに書いたデータベースのパスワードなどが、そのまま平文で入っています。公開リポジトリなら即座に漏洩します。

.gitignore*.tfstate*.tfstate.*、そして.terraform/を入れておきます。

チームで共有するならS3などのリモートバックエンドに置きます。暗号化して保存でき、同時に2人がapplyしないようロックも掛かります。ロックが無いと、同時に走ったときに対応表が壊れます。

plan に -/+ (作り直し) と出ました。そのまま apply していいですか

止まってください。-/+は「いったん削除してから作り直す」という意味で、そのリソースの中のデータは消えます

なぜそうなるかというと、変えようとした項目が作成時にしか決められない種類のものだからです。plan の出力に# forces replacementと書かれた行があるので、そこを読むとどの項目が原因か分かります。

データを失いたくないなら、先にバックアップを取るか、その項目を変えない方法を探します。

ワークスペースとフォルダ分け、どちらを使えばいいですか

構成がほぼ同じならワークスペース、違うならフォルダ分けです。

ワークスペースは設定ファイルを1つに保ったまま state だけ分けます。本番と検証でサーバーの台数や大きさだけが違うような場合に向いています。

逆に、本番だけ冗長構成にする、検証だけ別のサービスを使う、といった構成そのものが違う場合は、条件分岐だらけになって読めなくなります。そのときは素直にフォルダを分けてください。

🚩 次はこのカテゴリへ

Terraformのコードはgitで管理してこそ意味があります。誰がいつ何を変えたかが残り、planの結果をレビューに掛けられるようになるからです。Gitカテゴリでは、その土台になるブランチ・差分の読み方・履歴の追い方を練習できます。「本番を壊さずに変更を進める」やり方は、ここから始まります。

Gitコマンドのレッスンへ進む
広告スペース(バナー)
広告スペース(記事内)