Terraform 1.5 で追加された import ブロックと HCL 自動生成オプションの組合せが便利
こんにちは、SRE 部の小堀内です。
Terraform v1.5.0 で追加された import ブロックと terraform plan
時の HCL コードの自動生成オプション -generate-config-out
がとても便利だったのでその使い方をご紹介します。
はじめに
Terraform v1.5.0 より前のバージョンで、実環境のリソースを .tfstate
に反映させるためには、下記のように import コマンド を使用する必要がありました。
$ terraform import google_project_iam_member.default "my-project roles/viewer user:foo@example.com"
さらに、その後は .tf
と import 後の .tfstate
の差分を無くすためにドキュメント等を確認しながら .tf
を更新していく必要がありました。
しかし、v1.5.0
以降のバージョンでは、少々手間に感じていた上記の作業を効率化することができるようになりました。
それが、本記事のテーマとなっている import ブロック & HCL コードの自動生成オプション となります。
検証環境
- IaC Tool
- Terraform v1.6.0
- Google Provider v4.56.0
- Terraform v1.6.0
- Infrastructure
- Google Cloud
使用方法
それでは、import ブロックと HCL コード自動生成オプションを用いた import の方法を紹介していきます。
それに伴い、今回の検証で import 対象とする Google Cloud リソースを下記の 3 つとしました。
- google_compute_network
- google_project_iam_member
- google_storage_bucket
1. import ブロックの定義
構文は下記の通りです。
import {
id = import_id
to = resource_id + resource_name
}
構文の確認が終わったところで、実際に 3 リソース分の import ブロックを import.tf
に定義していきます。
# google_compute_network
import {
id = "projects/${local.project}/global/networks/default"
to = google_compute_network.default
}
# google_project_iam_member
import {
id = "${local.project} roles/editor user:foo@example.com"
to = google_project_iam_member.foo_editor
}
# google_storage_bucket
import {
id = "${local.project}/my-storage-bucket"
to = google_storage_bucket.my_storage_bucket
}
terraform plan
の実行
2. HCL コード自動生成オプションを付与した terraform plan
コマンドの HCL コード自動生成オプション -generate-config-out
を使用して import リソースに対応する .tf
を自動生成します。
公式ドキュメント に記載通りの構文でコマンドを実行します。
$ terraform plan -generate-config-out=generated.tf
実環境のリソース状態を .tfstate
へ反映する旨の plan 結果となり、さらに .tfstate
に対応する .tf
が生成されました。
Terraform will perform the following actions:
# google_compute_network.default will be imported
resource "google_compute_network" "default" {
auto_create_subnetworks = true
delete_default_routes_on_create = false
description = "Default network for the project"
enable_ula_internal_ipv6 = false
id = "projects/my-project/global/networks/default"
mtu = 0
name = "default"
project = "my-project"
routing_mode = "REGIONAL"
self_link = "https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default"
timeouts {}
}
# google_project_iam_member.foo_editor will be imported
resource "google_project_iam_member" "foo_editor" {
etag = "abcdefg"
id = "my-project/roles/editor/user:foo@example.com"
member = "user:foo@example.com"
project = "my-project"
role = "roles/editor"
}
# google_storage_bucket.my_storage_bucket will be imported
resource "google_storage_bucket" "my_storage_bucket" {
default_event_based_hold = false
force_destroy = false
id = "my-storage-bucket"
labels = {}
location = "ASIA-NORTHEAST1"
name = "my-storage-bucket"
project = "my-project"
public_access_prevention = "enforced"
requester_pays = false
self_link = "https://www.googleapis.com/storage/v1/b/my-storage-bucket"
storage_class = "STANDARD"
uniform_bucket_level_access = true
url = "gs://my-storage-bucket"
timeouts {}
}
Plan: 3 to import, 0 to add, 0 to change, 0 to destroy.
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.
# __generated__ by Terraform from "projects/my-project/global/networks/default"
resource "google_compute_network" "default" {
auto_create_subnetworks = true
delete_default_routes_on_create = false
description = "Default network for the project"
enable_ula_internal_ipv6 = false
internal_ipv6_range = null
mtu = 0
name = "default"
project = "my-project"
routing_mode = "REGIONAL"
timeouts {
create = null
delete = null
update = null
}
}
# __generated__ by Terraform from "my-project roles/editor user:foo@example.com"
resource "google_project_iam_member" "foo_editor" {
member = "user:foo@example.com"
project = "my-project"
role = "roles/editor"
}
# __generated__ by Terraform from "my-project/my-storage-bucket"
resource "google_storage_bucket" "my_storage_bucket" {
default_event_based_hold = false
force_destroy = false
labels = {}
location = "ASIA-NORTHEAST1"
name = "my-storage-bucket"
project = "my-project"
public_access_prevention = "enforced"
requester_pays = false
storage_class = "STANDARD"
uniform_bucket_level_access = true
timeouts {
create = null
read = null
update = null
}
}
terraform apply
の実行
3. 次は、3 リソース分の import 計画を実行に移します。
すると .tfstate
に実環境リソースの状態が反映されます。
{
"mode": "managed",
"type": "google_project_iam_member",
"name": "foo_editor",
"provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"condition": [],
"etag": "abcdefg",
"id": "my-project/roles/editor/user:foo@example.com",
"member": "user:foo@example.com",
"project": "my-project",
"role": "roles/editor"
},
"sensitive_attributes": [],
"private": "abcdef"
}
]
},
また、3 リソース分の import が完了した旨のメッセージが出力されます。
Apply complete! Resources: 3 imported, 0 added, 0 changed, 0 destroyed.
terraform plan
の再実行
確認のため .tfstate
への実環境リソース反映が完了したので、再度 terraform plan
を実行してみます。
自動生成オプションの使用により .tfstate
に対応する .tf
も出来上がっているため、差分無しを示す No changes. が出力されるはずです。
No changes. Your infrastructure matches the configuration.
想定通りの結果となりました。
まとめ
Terraform で import を行う場合、従来のように手作業で .tf
ファイルに HCL コードを書いていくのではなく、import ブロック & HCL コード自動生成オプション を使用することで import にかかる時間を大幅に短縮することができました。
注意点としては、既に CI/CD パイプラインが構築されたプロジェクトにおいて、import ブロックを使用したい場合で Terraform バージョンが 1.5.0 未満の場合は、バージョンアップによる影響を十分に考慮する必要があります。
その他の点に関して、import ブロックによる .tfstate
への反映は terraform apply
時に行われるため CI/CD パイプラインの大幅な変更は必要ないように感じました。
これからも IaC に対する理解をさらに深めていきたいと思います。
Discussion