はじめに
今回からは「実践編」として、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コマンドを実行します。
03 | Initializing the backend... |
05 | Initializing provider plugins... |
06 | - Reusing previous version of hashicorp/aws from the dependency lock file |
07 | - Installing hashicorp/aws v5.42.0... |
08 | - Installed hashicorp/aws v5.42.0 (signed by HashiCorp) |
10 | Terraform has been successfully initialized! |
初期化が成功すると.terraformディレクトリと.terraform.lock.hclファイルが作成されます。
.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
コードを実行したときにどのような変更が入るのか、実行計画を出力します。リソースが作成されるのか、変更されるのか、削除されるのかは出力の先頭を見れば分かります。+
が付いている項目はこれから作成されるリソースです。-
は削除、~
は変更を意味します。
03 | data.aws_caller_identity.current: Reading... |
04 | data.aws_caller_identity.current: Read complete after 0s [id=123456789012] |
06 | Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the |
10 | Terraform will perform the following actions: |
12 | # aws_s3_bucket.this will be created |
13 | + resource "aws_s3_bucket" "this" { |
14 | + acceleration_status = (known after apply) |
15 | + acl = (known after apply) |
16 | + arn = (known after apply) |
17 | + bucket = "poc-dummy-bucket" |
18 | + bucket_domain_name = (known after apply) |
19 | + bucket_prefix = (known after apply) |
20 | + bucket_regional_domain_name = (known after apply) |
21 | + force_destroy = false |
22 | + hosted_zone_id = (known after apply) |
23 | + id = (known after apply) |
24 | + object_lock_enabled = (known after apply) |
25 | + policy = (known after apply) |
26 | + region = (known after apply) |
27 | + request_payer = (known after apply) |
28 | + tags_all = (known after apply) |
29 | + website_domain = (known after apply) |
30 | + website_endpoint = (known after apply) |
33 | Plan: 1 to add, 0 to change, 0 to destroy. |
35 | ─────────────────────────────────────────────────────────────────────────────────────────────────────────── |
37 | Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you |
38 | run "terraform apply" now. |
このようにplanの結果を見れば何が作られて、何が削除されるのかが一目瞭然です。コードレビューをするときはplanの内容も共有できると期待通りの動作なのかを踏まえたレビューができるようになります。
$ terraform apply
コードを環境に適用していきます。出力はほぼplanのときと一緒ですが、コードの適用前に最終確認が入ります。yes
と入力することで処理は続行されますが、この段階で中止したければyes
以外、もしくは[Ctrl]+[C]などで中止してください。
05 | Do you want to perform these actions? |
06 | Terraform will perform the actions described above. |
07 | Only 'yes' will be accepted to approve. |
11 | aws_s3_bucket.this: Creating... |
12 | aws_s3_bucket.this: Creation complete after 3s [id=poc-dummy-bucket] |
14 | Apply complete! Resources: 1 added, 0 changed, 0 destroyed. |
Apply complete!
と表示されれば成功です。たまにplanまでは問題なく通り、applyでエラーになることがあります。planはAPIとやりとりしないため対象のクラウドやサーバーの状態を把握しておらず、ステートファイルとコードの差分を出力しているのに対し、applyはAPIを通じて対象に変更を加えていくからです。対象のリソースに予期しない値を設定していたり、作成しようとしていたリソースがすでに存在していたりなど理由は様々ですが、エラー内容をよく読み冷静に対処しましょう。
$ terraform destroy
作成したリソースを削除します。applyのときと同様に続行するか聞かれるので、yes
で進めてください。
03 | data.aws_caller_identity.current: Reading... |
04 | data.aws_caller_identity.current: Read complete after 0s [id=123456789012] |
06 | No changes. No objects need to be destroyed. |
08 | Either you have not created any objects yet or the existing objects were already deleted outside of Terraform. |
10 | Do you really want to destroy all resources? |
11 | Terraform will destroy all your managed infrastructure, as shown above. |
12 | There is no undo. Only 'yes' will be accepted to confirm. |
Terraformを使用なら覚えておくと便利なコマンド
ここからは、オペレーションに必須ではありませんが、覚えておくときっと役に立つコマンドを紹介します。
$ terraform fmt
Terraformにはコードフォーマットを整えるためのはフォーマッターが付属しています。コードと同じ階層でfmtコマンドを実行することで、拡張子がtfやtfvarsのファイルに対してフォーマッターが動作します。標準出力にはフォーマットを適用したファイル名が出力されます。
フォーマッターが適用済みかチェックするだけの-check
オプションも用意されています。CIなどで実行する場合にはコードを書き換えて自動でコミットを積むより、チェックだけを実行し通知するのが良いでしょう。標準出力にはフォーマットが適用されていないファイル名が出力されます。
$ terraform validate
コード検証するためのvalidateコマンドもCIなどで実行するには有用です。
3 | │ Error: Unsupported argument |
5 | │ on main.tf line 8, in resource "aws_s3_bucket" "this": |
6 | │ 8: backet = "dummy-${data.aws_caller_identity.current.account_id}" |
8 | │ An argument named "backet" is not expected here. Did you mean "bucket"? |
この例では、bucket
であるべきところがbacket
になっていてエラーが出力されました。「もしかして"bucket"?」と指摘もしてくれています。
validateコマンドはコードを検証するのみで、リモートの状態やAPIを通じた検証は行いません。つまり、applyコマンドを実行するまで気付けない問題には対応していません。このような検証も行いたければtflintの使用をおすすめします。
$ terraform console
Terraformには対話型のインターフェイスが用意されています。コードと同じ階層でconsoleコマンドを実行することで、対話的にコードが評価されます。以下のコードが含まれるファイルと同じ階層でconsoleコマンドを実行してみます。
2 | data "aws_caller_identity" "current" {} |
03 | > data.aws_caller_identity.current |
05 | "account_id" = "123456789012" |
06 | "arn" = "arn:aws:iam::123456789012:user/tf-user" |
08 | "user_id" = "ABCDEFGHIJK1234567890" |
data.aws_caller_identity.current
で取得できる値を表示できました。終了するときは[Ctrl]+[C]か[Ctrl]+[D]でコンソールから抜けられます。
consoleコマンドは標準入力からコードを受け取って実行できます。
1 | $ echo 'data.aws_caller_identity.current' | terraform console |
4 | "account_id" = "123456789012" |
5 | "arn" = "arn:aws:iam::123456789012:user/tf-user" |
7 | "user_id" = "ABCDEFGHIJK1234567890" |
consoleコマンドはソースコードに含まれているコードを評価し出力しますが、TerraformのFunctionはソースコードに含まれていなくても実行できます。
1 | $ echo 'cidrsubnets("10.0.0.0/16", 4, 4, 8, 4)' | terraform console |
おわりに
今回は、Terraformの動作と基本的な使い方、補助的に使えるコマンドについて解説しました。また、最近話題のOpenTofuについても触れています。IaCを使用することで構成管理や環境の構築・破棄が楽になり、属人性を排除できます。必ずしもTerraformである必要はありませんが、ぜひIaCを導入し運用負荷を減らしましょう。
次回は、Terraformのコードについて実践を踏まえて解説していきます。