Iacツール「Terraform」の基本的な使い方

2024年4月19日(金)
田中 智明
今回から「実践編」に入ります。実践編第1回の今回は、Iacツール「Terraform」の動作と基本的な使い方、補助的に使えるコマンドを解説します。

はじめに

今回からは「実践編」として、DevOpsで使用するツールの使い方やコードの書き方について解説していきます。

第14回でIaCについて触れました。IaCを実践するには構成管理ツールが必要です。構成管理ツールには様々な実装がありますが、大きく「手続き型」と「宣言型」の2つに分類できます。

手続き型は構築手順をコード化し、手順を積み重ねることであるべき姿へ向かっていきます。よく使われているツールとしてはシェルスクリプトや、もっと高機能で書きやすいAnsibleなどが有名です。宣言型はあるべき姿を宣言し、ツールがその構成となるよう自動で構築していきます。

今回は宣言型のIaCツール「Terraform」について、そもそもTerraformとは何なのか、基本的な使い方や覚えておくと便利なコマンドについて解説します。

Terraformとは

Terraformは、HashiCorp社により開発・保守されている構成管理ツールです。HashiCorp configuration language(HCL)フォーマットで記述されたコードによって、インフラの構成を宣言的に管理できます。

TerraformはAPIを通じて対象のクラウドやサービスを操作します。APIとのやりとりを実装しているのがプロバイダーです。例えばAWSを操作する場合、AWSプロバイダーを読み込み、プロバイダーの実装通りにコードを記述することになります。ドキュメントもプロバイダーごとに存在します。プロバイダーはTerraform Registryで管理され「Official」「Partner」「Community」「Archived」の4つの階層で区切られています。

階層 説明
Official Hashicorpにより開発・保守・検証・公開されています
Partner Hashicorpテクノロジーパートナープログラムに参加している企業により開発・保守・検証・公開されます
Community コミュニティメンバーにより開発・保守・検証・公開されます
Archived APIが非推奨になったり、関心が低かったりした場合にアーカイブされます

特別な理由がない限りOfficialやPartnerの使用を検討しましょう。Communityプロバイダーを使用するならソースコードに問題がないことを確認しましょう。Communityは誰でもプロバイダーを公開できるため悪意のあるコードを含んでいる可能性があるからです。Archivedはすでにメンテナンスが終了しているため使用すべきではありません。

Terraform Registryでプロバイダー検索するなら、サイドバーにあるフィルターでOfficialやPartnerに絞り込むのが良いでしょう。

Terraform Registryで検索する階層を絞り込む

TerraformとOpenTofu

Terraformはv1.6.0のリリースでライセンスをオープンソースライセンスのMPL 2.0から商用利用に制限のあるBSL 1.1に変更しました。この変更によりTerraformのソースコードを商用的に利用して競合サービスを運営できなくなりました。詳しくはHashiCorp社のブログエントリー「HashiCorp adopts Business Source License」をご覧ください。

オープンソースライセンスから変更することに不満のあった開発者により、ライセンス変更前のソースコードv1.5系をフォークしてOpenTofuプロジェクトが発足されました。現在OpenTofuはLinux Foundationによりホストされています。

Terraform v1.5とOpenTofuには互換性があり、v1.5系を想定したコードはそのままOpenTofuでも実行できます。TerraformからOpenTofuへの移行は公式ドキュメント「Migrating to OpenTofu from Terraform」で解説されています。

Terraform v1.6.0以降の機能についてはOpenTofuで独自に実装しています。例えばtestコマンドです。コミット履歴を見るとTerraformにあった古い実装が削除され、新しく実装が追加されていることが分かります。

また、両者のtest.goを見比べてみると、独自に実装されていることが分かります。

terraform/internal/command/test.go
opentofu/internal/command/test.go

このように、Terraformの機能をそのままOpenTofuには流用できないため、今後機能面で互換性を保ち続けられるかは分かりません。そのことを十分理解した上でOpenTofuに乗り換えるか、Terraformを使い続けるかを検討すると良いでしょう。

Terraformを使用する際に理解しておくこと

Terraformはステートファイルと呼ばれるファイルにコードの実行結果を保存します。コードの実行結果とは、対象のクラウドやサーバーに変更を加え、その結果リソースがどうなったのか、という情報です。Terraformはコードの実行時にステートファイルとコードの差分を算出し、変更があれば対象に向けて処理を実行します。つまり、ステートファイルと対象の状態がイコールでなくてはなりません。ステートファイルと対象の状態がズレていた場合は問題です。

例えば、コードで作成したリソースを手動で削除したとしましょう。ステートファイルにはそのリソースが存在するはずですが、対象の環境からはすでに削除された状態です。Terraformはステートファイルにあるそのリソースをもう一度削除しようとしエラーで処理が失敗します。復旧するためにはステートファイルからそのエントリーを削除するか、同じリソースを手で作りなおさなければなりません。逆もまたしかりです。ステートファイルにはまだ存在しないはずのリソースが手動で作成されていては、そのリソースを作成する段階でエラーが発生します。Terraformはリソースの有無をコードが実行されるまで知ることができないため、テストなどをしたとしてもこのエラーに気づくことができません。

このようにステートファイルはとても重要なファイルなのですが、デフォルトの状態ではローカルに出力されてしまいます。間違って削除してしまう恐れもありますし、複数人で管理している場合はステートファイルを共有しておく必要が出てきます。チーム内で共有することを考えた場合、バージョン管理ツールの使用が思い浮かびますが、これは御法度です。ステートファイルには機密情報などが平文で保存されてしまうため、バージョン管理ツールなどで管理することは公式で非推奨となっています。幸いなことに、Terraformにはこれらの問題を解決するため「backend」と呼ばれる設定があり、backendを設定することでステートファイルを外部のストレージに置いておくことができます。

Terraformを使用する上に必要になるコマンド

$ terraform init

Terraformを使用するには、まず初期化処理が必要です。Terraformの初期化はステートファイルの格納先を設定したりプロバイダーのダウンロードをしたりします。初期化するにはTerraformのコードと同じ階層に移動するか、Terraformコマンドに-chdir=<working-directory>オプションを指定しinitコマンドを実行します。

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Installing hashicorp/aws v5.42.0...
- Installed hashicorp/aws v5.42.0 (signed by HashiCorp)

Terraform has been successfully initialized!

初期化が成功すると.terraformディレクトリと.terraform.lock.hclファイルが作成されます。

.
├── .terraform
├── .terraform.lock.hcl
└── main.tf

.terraformディレクトリにはコード内で使用されているプロバイダーがダウンロードされます。また、ステートファイルの保存先であるバックエンドの設定をしていた場合、.terraform/terraform.tfstateファイルが出力されます。.terraformディレクトリと同じ階層に作られるterraform.tfstateはリソースの状態を管理しているファイルですが、.terraform/terraform.tfstateはTerraformの状態を管理しています。同じ名前でも管理対象は別なので混同しないように注意してください。

.terraform.lock.hclファイルには使用しているプロバイダーのバージョンとそのハッシュ値が記録されます。.terraform.lock.hclはGitなどで管理することが公式で推奨されています。このファイルに変更があるということはプロバイダーのバージョンに変更がある、ということでレビューを通すべきという意図です。

初期化処理はプロバイダーの追加やアップグレード時にも必要になります。アップグレードが必要なときは-upgradeオプションを指定してinitコマンドを実行します。

ステートファイルの格納先を変更した場合にも再初期化が必要です。変更を適用するには-migrate-stateオプションか-reconfigureオプションを指定します。多くの場合は-migrate-stateオプションを使用していれば問題ありません。-reconfigureオプションの使い所についてはこちらのIssue(Reconfigure flag in terraform init)が分かりやすいです。簡単に説明すると、何らかの問題でステートファイルのマイグレーションができなくなってしまった場合、とりあえず設定だけリモートからローカルに切り替える際などに使用します。

$ terraform plan

コードを実行したときにどのような変更が入るのか、実行計画を出力します。リソースが作成されるのか、変更されるのか、削除されるのかは出力の先頭を見れば分かります。+が付いている項目はこれから作成されるリソースです。-は削除、~は変更を意味します。

$ terraform plan

data.aws_caller_identity.current: Reading...
data.aws_caller_identity.current: Read complete after 0s [id=123456789012]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # aws_s3_bucket.this will be created
  + resource "aws_s3_bucket" "this" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "poc-dummy-bucket"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags_all                    = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)
    }

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

───────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you
run "terraform apply" now.

このようにplanの結果を見れば何が作られて、何が削除されるのかが一目瞭然です。コードレビューをするときはplanの内容も共有できると期待通りの動作なのかを踏まえたレビューができるようになります。

$ terraform apply

コードを環境に適用していきます。出力はほぼplanのときと一緒ですが、コードの適用前に最終確認が入ります。yesと入力することで処理は続行されますが、この段階で中止したければyes以外、もしくは[Ctrl]+[C]などで中止してください。

$ terraform apply

...

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_s3_bucket.this: Creating...
aws_s3_bucket.this: Creation complete after 3s [id=poc-dummy-bucket]

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

Apply complete!と表示されれば成功です。たまにplanまでは問題なく通り、applyでエラーになることがあります。planはAPIとやりとりしないため対象のクラウドやサーバーの状態を把握しておらず、ステートファイルとコードの差分を出力しているのに対し、applyはAPIを通じて対象に変更を加えていくからです。対象のリソースに予期しない値を設定していたり、作成しようとしていたリソースがすでに存在していたりなど理由は様々ですが、エラー内容をよく読み冷静に対処しましょう。

$ terraform destroy

作成したリソースを削除します。applyのときと同様に続行するか聞かれるので、yesで進めてください。

$ terraform destroy

data.aws_caller_identity.current: Reading...
data.aws_caller_identity.current: Read complete after 0s [id=123456789012]

No changes. No objects need to be destroyed.

Either you have not created any objects yet or the existing objects were already deleted outside of Terraform.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

Terraformを使用なら覚えておくと便利なコマンド

ここからは、オペレーションに必須ではありませんが、覚えておくときっと役に立つコマンドを紹介します。

$ terraform fmt

Terraformにはコードフォーマットを整えるためのはフォーマッターが付属しています。コードと同じ階層でfmtコマンドを実行することで、拡張子がtfやtfvarsのファイルに対してフォーマッターが動作します。標準出力にはフォーマットを適用したファイル名が出力されます。

$ terraform fmt

main.tf
terraform.tfvars
...

フォーマッターが適用済みかチェックするだけの-checkオプションも用意されています。CIなどで実行する場合にはコードを書き換えて自動でコミットを積むより、チェックだけを実行し通知するのが良いでしょう。標準出力にはフォーマットが適用されていないファイル名が出力されます。

$ terraform fmt -check

terraform.tfvars
...

$ terraform validate

コード検証するためのvalidateコマンドもCIなどで実行するには有用です。

$ terraform validate

│ Error: Unsupported argument
│
│   on main.tf line 8, in resource "aws_s3_bucket" "this":
│    8:   backet = "dummy-${data.aws_caller_identity.current.account_id}"
│
│ An argument named "backet" is not expected here. Did you mean "bucket"?

この例では、bucketであるべきところがbacketになっていてエラーが出力されました。「もしかして"bucket"?」と指摘もしてくれています。

validateコマンドはコードを検証するのみで、リモートの状態やAPIを通じた検証は行いません。つまり、applyコマンドを実行するまで気付けない問題には対応していません。このような検証も行いたければtflintの使用をおすすめします。

$ terraform console

Terraformには対話型のインターフェイスが用意されています。コードと同じ階層でconsoleコマンドを実行することで、対話的にコードが評価されます。以下のコードが含まれるファイルと同じ階層でconsoleコマンドを実行してみます。

main.tf
data "aws_caller_identity" "current" {}
$ terraform console

> data.aws_caller_identity.current
{
  "account_id" = "123456789012"
  "arn" = "arn:aws:iam::123456789012:user/tf-user"
  "id" = "123456789012"
  "user_id" = "ABCDEFGHIJK1234567890"
}
>

data.aws_caller_identity.currentで取得できる値を表示できました。終了するときは[Ctrl]+[C]か[Ctrl]+[D]でコンソールから抜けられます。

consoleコマンドは標準入力からコードを受け取って実行できます。

$ echo 'data.aws_caller_identity.current' | terraform console

{
  "account_id" = "123456789012"
  "arn" = "arn:aws:iam::123456789012:user/tf-user"
  "id" = "123456789012"
  "user_id" = "ABCDEFGHIJK1234567890"
}

consoleコマンドはソースコードに含まれているコードを評価し出力しますが、TerraformのFunctionはソースコードに含まれていなくても実行できます。

$ echo 'cidrsubnets("10.0.0.0/16", 4, 4, 8, 4)' | terraform console
tolist([
  "10.0.0.0/20",
  "10.0.16.0/20",
  "10.0.32.0/24",
  "10.0.48.0/20",
])

おわりに

今回は、Terraformの動作と基本的な使い方、補助的に使えるコマンドについて解説しました。また、最近話題のOpenTofuについても触れています。IaCを使用することで構成管理や環境の構築・破棄が楽になり、属人性を排除できます。必ずしもTerraformである必要はありませんが、ぜひIaCを導入し運用負荷を減らしましょう。

次回は、Terraformのコードについて実践を踏まえて解説していきます。

日本仮想化技術株式会社
ソーシャルゲーム業界で10年間インフラエンジニアとして活動し、現在は日本仮想化技術でOpsエンジニアを担当。DevOps支援サービス「かんたんDevOps」では仕組み作りや導入支援、技術調査などを行っている。

連載バックナンバー

設計/手法/テスト技術解説
第25回

AWSの監視サービス「CloudWatch」でサーバー監視を試してみよう

2024/8/9
本連載も今回で最終回となります。今回は、AWSの監視サービス「CloudWatch」を使って、簡単なサーバー監視を試してみましょう。
設計/手法/テスト技術解説
第24回

CI環境を構築して「ESLint」で静的解析を実行してみよう

2024/7/26
実践編第8回の今回は、「Dev Containers」でCI環境を構築し、静的解析ツール「ESLint」で静的解析を実行するまでの流れを解説します。
設計/手法/テスト技術解説
第23回

テストコードを書いて「GitHub Actions」でCIを実行してみよう

2024/7/12
実践編第7回の今回は、Webフロントエンド開発を例に、テストコードを書いて「GitHub Actions」でCIを実行するまでの流れを解説します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています