フューチャー技術ブログ

FutureTechNight#20 TerraformState縛りの勉強会に登壇しました

勉強会に登壇しました

こんにちは。
TIGの棚井龍之介です。

2022年2月17日(木)に、Terraform stateをテーマにした勉強会「Future Tech Night #20 Terraform State縛りの勉強会」が開かれました。私は後半パートでクラウドリソース自体をdestroy/createdせずに、Terraformリソース定義の記述場所を変更する方法について話しました。

登壇に向けた宣伝文がいい感じに背景を説明しているため、以下に全文を記載します。

クラウドサービス上で稼働するリソースには一切手を付けずに、Terraformの定義記載場所だけを変更する方法を話します。

Terraformを利用していると「このディレクトリ配置じゃダメだ。配置変えしたいのだけれど、リソースの再作成はできない。次にインフラ設計するときは、〇〇に注意しよう」という運用ナレッジが貯まると思います。スタート時点で完璧なTerraformディレクトリ設計ができれば御の字ですが、それが不可能なことは、この分野でベストプラクティスが確立されていないことにより証明されています。

本パートでは「Terraformのディレクトリ配置には定石がないのだから、運用状況に合わせて柔軟に配置換えすべき」という観点から、「動作中リソースに影響なく、Terraform定義箇所を移植する方法」について話します。

こういった前提のもと、terraform state mv pull push を利用した、Terraformリソースのディレクトリ移動方法を発表しました。

簡単に内容を説明すると

(1)Terraformを運用していると、↓画像のように「.tfファイルの場所を移動」したくなることがあります。何も考えずに移動すると、リソースはdestroy/createdされてしまうのですが、データベースや24/365の稼働要件があるリソースはdestroyすべきではありません。

リソースを再作成せずTerraform記述を移動する

(2)そんな時は、terraform stateコマンドを活用することで、実リソースに手をつけることなく定義場所のディレクトリ移動ができます。さらに、↓画像のモデルをベースとすれば、リソース操作状況を視覚的にイメージしながら作業ができます。

.tfstateの移植操作

という内容でした。

「state操作を図で表現する」というのが推しポイントだったのですが、勉強会後のツイートで Terraformの「moved block」という方法を教えて頂きました。実際に使ってみたことろ、terraform mvと同様の操作を、私が勉強会で説明した方法よりも安全に実行できると感じたため、この方法について以下で説明します。

moved blockを試す

事前準備

まずは、こちらの記事「LocalStackに向けてTerraformを実行する」に沿って環境構築を進めて、ローカル環境でTerraformが操作できるようにします。

moved blockは v1.1.0 により追加された機能のため、Terraformは最新バージョンを入れます。

私は tfenv を利用し、v1.1.7 を入れました。

$ tfenv install 1.1.7
Installing Terraform v1.1.7
Downloading release tarball from https://releases.hashicorp.com/terraform/1.1.7/terraform_1.1.7_darwin_amd64.zip
############################################################################################################################################################################################################# 100.0%
Downloading SHA hash file from https://releases.hashicorp.com/terraform/1.1.7/terraform_1.1.7_SHA256SUMS
No keybase install found, skipping OpenPGP signature verification
Archive: /var/folders/0r/3y_v9zrd75n9dgv2f0cy6qq40000gn/T/tfenv_download.XXXXXX.qdVCVbhE/terraform_1.1.7_darwin_amd64.zip
inflating: /Users/tanai3022/.anyenv/envs/tfenv/versions/1.1.7/terraform
Installation of terraform v1.1.7 successful. To make this your default version, run 'tfenv use 1.1.7'

$ tfenv use 1.1.7
Switching default version to v1.1.7
Switching completed

$ terraform version
Terraform v1.1.7
on darwin_amd64

作業用ディレクトリを用意し、以下のようにファイルを配置します。

$ tree
.
├── docker-compose.yml
└── main.tf

0 directories, 2 files

今回の動作検証には、Terraform の null_resource を利用します。

main.tf
resource "null_resource" "resource_A" {}
resource "null_resource" "resource_B" {}
resource "null_resource" "resource_C" {}

作業ディレクトリで terraform init / plan / apply を実行して、null_resource を作成します。
これにより、stateファイル(terraform.tfstate) が作成されます。

$ tree
.
├── docker-compose.yml
├── main.tf
└── terraform.tfstate

0 directories, 3 files

stateファイルを直接覗いて見ると、
3つのリソース「resource_A」「resource_B」「resource_C」が作成されていることが分かります。

$ cat terraform.tfstate
{
"version": 4,
"terraform_version": "1.1.7",
"serial": 1,
"lineage": "ea534414-b327-eee0-bd9c-b6374db37db2",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "null_resource",
"name": "resource_A",
"provider": "provider[\"registry.terraform.io/hashicorp/null\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "592263413744525319",
"triggers": null
},
"sensitive_attributes": [],
"private": "bnVsbA=="
}
]
},
{
"mode": "managed",
"type": "null_resource",
"name": "resource_B",
"provider": "provider[\"registry.terraform.io/hashicorp/null\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "5369048601034101090",
"triggers": null
},
"sensitive_attributes": [],
"private": "bnVsbA=="
}
]
},
{
"mode": "managed",
"type": "null_resource",
"name": "resource_C",
"provider": "provider[\"registry.terraform.io/hashicorp/null\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "3453046184153615927",
"triggers": null
},
"sensitive_attributes": [],
"private": "bnVsbA=="
}
]
}
]
}

terraform state list でも、リソース名を確認できます。

$ terraform state list
null_resource.resource_A
null_resource.resource_B
null_resource.resource_C

以上で準備は完了です。

moved blockでリソースを改名する

さっそく、moved block を利用して、terraform mv と同様に「リソース名の変更」を実施してみましょう。

まずは、同一ディレクトリに moved.tf を追加します。

moved.tf
moved {
from = null_resource.resource_A
to = null_resource.resource_X
}

このタイミングで、すぐに terraform plan してみると…

$ terraform plan
null_resource.resource_X: Refreshing state... [id=592263413744525319]
null_resource.resource_B: Refreshing state... [id=5369048601034101090]
null_resource.resource_C: Refreshing state... [id=3453046184153615927]

│ Error: Moved object still exists

│ on moved.tf line 1:
│ 1: moved {

│ This statement declares a move from null_resource.resource_A, but that resource is still declared at main.tf:1,1.

│ Change your configuration so that this resource will be declared as null_resource.resource_X instead.

null_resource.resource_Xに改名しようとしていますが、main.tf にはnull_resource.resource_Aのままで残っていますよ。Terraform の定義を変更してください」

と、丁寧なエラー文で通知してくれます。
terraform state mv でリソースを改名する場合、state操作に集中するあまり、Terraform本体側コードの書き換えを忘れがちですが、moved blockを使うと事前に通知してくれるのが嬉しい。

main.tf を正しく書き換えて、再度 plan してみます。

main.tf
resource "null_resource" "resource_X" {} # renamed resource_A to resource_X
resource "null_resource" "resource_B" {}
resource "null_resource" "resource_C" {}
$ terraform plan
null_resource.resource_B: Refreshing state... [id=5369048601034101090]
null_resource.resource_C: Refreshing state... [id=3453046184153615927]
null_resource.resource_X: Refreshing state... [id=592263413744525319]

Terraform will perform the following actions:

# null_resource.resource_A has moved to null_resource.resource_X
resource "null_resource" "resource_X" {
id = "592263413744525319"
}

Plan: 0 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.

# null_resource.resource_A has moved to null_resource.resource_X

というメッセージで、「どこが変わるのか」が分かりますね。
applyして、変化後の状況を確認してみます。

$ terraform apply
null_resource.resource_X: Refreshing state... [id=592263413744525319]
null_resource.resource_B: Refreshing state... [id=5369048601034101090]
null_resource.resource_C: Refreshing state... [id=3453046184153615927]

Terraform will perform the following actions:

# null_resource.resource_A has moved to null_resource.resource_X
resource "null_resource" "resource_X" {
id = "592263413744525319"
}

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

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


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

$ terraform state list
null_resource.resource_B
null_resource.resource_C
null_resource.resource_X

リソース名が、resource_A から resource_X に変化しています。
これにより moved.tf ファイルは役目を果たしたので、削除しても以降の plan / apply には影響しません。

$ rm moved.tf # delete moved.tf file
$ terraform plan
null_resource.resource_X: Refreshing state... [id=592263413744525319]
null_resource.resource_C: Refreshing state... [id=3453046184153615927]
null_resource.resource_B: Refreshing state... [id=5369048601034101090]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

以上で、moved block を利用したTerraformリソースの改名作業は終了です。

Terraformのリファクタリングには「(1)stateファイルの操作」と「(2)Terraformコード自体の操作」の2つが必要であり、(1)に集中することで(2)を忘れてしまうことがありますが、moved block を利用すればそれを防げそうです。

私の中では、Terraform stateファイルの操作には「terraform stateコマンドしかない」と思っていましたが、今回の勉強会に参加することで、結果的に新しい方法を知ることができました。改めて、継続的な技術インプット & 定期的なアウトプットが重要だなと実感した勉強会でした。