この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
問題
S3バケットを作成するTerraformのコードを書きました。命名のためにlocal変数を使っています。
locals {
bucket_name = "kazue-hogehoge"
}
resource "aws_s3_bucket" "sample" {
bucket = local.bucket_name
}
このバケットに、さらにバージョニングの設定を足したいと思います。ここで問題です。以下2つのコード、どちらがより良いコードと言えるでしょうか?
A: bucket attributeの参照元としてlocal変数を使う
resource "aws_s3_bucket_versioning" "sample" {
bucket = local.bucket_name
versioning_configuration {
status = "Enabled"
}
}
B: bucket attributeの参照元としてaws_s3_bucketのattributeを使う
resource "aws_s3_bucket_versioning" "sample" {
bucket = aws_s3_bucket.sample.bucket
versioning_configuration {
status = "Enabled"
}
}
私は、「B: bucket attributeの参照元としてaws_s3_bucketのattributeを使う」の方がより良いコードだと考えます。理由は、エラーが起きにくいからです。
解説
aws_s3_bucket_versioning
リソースは、設定対象となるS3バケットに依存しています。当然ですね。バケットの設定項目の一つですから。
ですが、Aのコードだとその依存関係をTerraformは理解していません。故に、「バケットをプロビジョニングする前にバージョニングのプロビジョニングを行おうとしてエラーになる」というパターンが発生し得ます。
Aでターゲットを絞ってプロビジョニングしてみる
例を挙げます。さっきのAのコードで、-target
オプションを使って、aws_s3_bucket_versioning
リソースだけプロビジョニングしてみましょう。
プラン結果は以下です。当然 aws_s3_bucket_versioning
だけが作成予定です。
% terraform plan -target="aws_s3_bucket_versioning.sample"
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_versioning.sample will be created
+ resource "aws_s3_bucket_versioning" "sample" {
+ bucket = "kazue-hogehoge"
+ id = (known after apply)
+ versioning_configuration {
+ mfa_delete = (known after apply)
+ status = "Enabled"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
(略)
applyするとエラーになります。そりゃバケットがないのでそうですよね。
% terraform apply -target="aws_s3_bucket_versioning.sample" -auto-approve
(略)
│ Error: error creating S3 bucket versioning for kazue-hogehoge: NoSuchBucket: The specified bucket does not exist
│ status code: 404, request id: MA1QPNDWZSAP0PZX, host id: WCEsy4o7hf4w7i8vJSuQ6QmFvCGFIvz0r8tCvlJ7ej7qubpkNvPyCWI1PaMUxmNMZ2icb4sneqM=
│
│ with aws_s3_bucket_versioning.sample,
│ on main.tf line 25, in resource "aws_s3_bucket_versioning" "sample":
│ 25: resource "aws_s3_bucket_versioning" "sample" {
│
Bでターゲットを絞ってプロビジョニングしてみる
では、同じことをBのコードでもやってみましょう。
% terraform plan -target="aws_s3_bucket_versioning.sample"
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.sample will be created
+ resource "aws_s3_bucket" "sample" {
+ acceleration_status = (known after apply)
+ acl = (known after apply)
+ arn = (known after apply)
+ bucket = "kazue-hogehoge"
+ bucket_domain_name = (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)
+ cors_rule {
+ allowed_headers = (known after apply)
+ allowed_methods = (known after apply)
+ allowed_origins = (known after apply)
+ expose_headers = (known after apply)
+ max_age_seconds = (known after apply)
}
+ grant {
+ id = (known after apply)
+ permissions = (known after apply)
+ type = (known after apply)
+ uri = (known after apply)
}
+ lifecycle_rule {
+ abort_incomplete_multipart_upload_days = (known after apply)
+ enabled = (known after apply)
+ id = (known after apply)
+ prefix = (known after apply)
+ tags = (known after apply)
+ expiration {
+ date = (known after apply)
+ days = (known after apply)
+ expired_object_delete_marker = (known after apply)
}
+ noncurrent_version_expiration {
+ days = (known after apply)
}
+ noncurrent_version_transition {
+ days = (known after apply)
+ storage_class = (known after apply)
}
+ transition {
+ date = (known after apply)
+ days = (known after apply)
+ storage_class = (known after apply)
}
}
+ logging {
+ target_bucket = (known after apply)
+ target_prefix = (known after apply)
}
+ object_lock_configuration {
+ object_lock_enabled = (known after apply)
+ rule {
+ default_retention {
+ days = (known after apply)
+ mode = (known after apply)
+ years = (known after apply)
}
}
}
+ replication_configuration {
+ role = (known after apply)
+ rules {
+ delete_marker_replication_status = (known after apply)
+ id = (known after apply)
+ prefix = (known after apply)
+ priority = (known after apply)
+ status = (known after apply)
+ destination {
+ account_id = (known after apply)
+ bucket = (known after apply)
+ replica_kms_key_id = (known after apply)
+ storage_class = (known after apply)
+ access_control_translation {
+ owner = (known after apply)
}
+ metrics {
+ minutes = (known after apply)
+ status = (known after apply)
}
+ replication_time {
+ minutes = (known after apply)
+ status = (known after apply)
}
}
+ filter {
+ prefix = (known after apply)
+ tags = (known after apply)
}
+ source_selection_criteria {
+ sse_kms_encrypted_objects {
+ enabled = (known after apply)
}
}
}
}
+ server_side_encryption_configuration {
+ rule {
+ bucket_key_enabled = (known after apply)
+ apply_server_side_encryption_by_default {
+ kms_master_key_id = (known after apply)
+ sse_algorithm = (known after apply)
}
}
}
+ versioning {
+ enabled = (known after apply)
+ mfa_delete = (known after apply)
}
+ website {
+ error_document = (known after apply)
+ index_document = (known after apply)
+ redirect_all_requests_to = (known after apply)
+ routing_rules = (known after apply)
}
}
# aws_s3_bucket_versioning.sample will be created
+ resource "aws_s3_bucket_versioning" "sample" {
+ bucket = "kazue-hogehoge"
+ id = (known after apply)
+ versioning_configuration {
+ mfa_delete = (known after apply)
+ status = "Enabled"
}
}
Plan: 2 to add, 0 to change, 0 to destroy.
(略)
aws_s3_bucket
もplanに含まれましたね。(attribute多すぎでとても縦長…)
これは-target
オプションは、そこに指定されたリソース(今回だとaws_s3_bucket_versioning
)に加えて、そのリソースが依存している、つまりそのリソースを作るのに必要になる別のリソース(今回だとaws_s3_bucket
)も併せてプロビジョニングするからです。aws_s3_bucket_versioning.sample
のbucket
arrtibute値にaws_s3_bucket.sample
のattributeを使ったのでこういう結果になりました。この後applyも成功しました。applyのログを見ていると以下のようになっており、バケットの作成完了を待ってからバージョニングの設定が始まっていることがわかります。
aws_s3_bucket.sample: Creating...
aws_s3_bucket.sample: Creation complete after 6s [id=kazue-hogehoge]
aws_s3_bucket_versioning.sample: Creating...
aws_s3_bucket_versioning.sample: Creation complete after 3s [id=kazue-hogehoge]
でも-target
オプションなんてそうそう使わへんやん
「-target
なんてほとんど使わないオプション持ち出して、『こっちの方が良いコードです(`・ω・´)キリッ』なんて言われてもねぇ…」とシラけたあなた。おっしゃるとおりです。私もそう思います。
ですが、-target
オプションを使わずとも、先に挙げたような「バケットをプロビジョニングする前にバージョニングのプロビジョニングを行おうとしてエラーになる」ケースは結構発生すると考えています。依存関係をTerraformが理解していない以上、どちらが先にプロビジョニングされるかは、完全にTerraform任せになります。
- 通常私達はTerraformで複数リソースを同時並行的にプロビジョニングします。デフォルト並列数は10です。依存関係にあるリソースが、同時に作成開始するケースが発生します。それどころか、他のリソースももっと大量にプロビジョニングするコードである場合は、他のリソースの作成に紛れて依存しているリソース(バージョニング)→依存されている(バケット)の順番で実行される場合もあります。
- 今回のS3バケットのような、すぐに作成されるリソースの場合、たとえそれに依存しているリソース(バージョニング)と同時に作成開始したとしてもエラーになる確率は低いでしょう。(実際上記Aのコードも
-target
を外せばapplyは正常完了しました。) ですが、もっと作成に時間がかかるリソース、例えばEKSクラスター等の場合はより注意が必要です。
おまけ: グラフで確認
terraform graph
コマンドでAとBのコードのリソースの依存関係を図示してみました。
% terraform graph | dot -Tpng > graph.png
Aのグラフ
aws_s3_bucket
と aws_s3_bucket_versioning
に依存関係はありませんね。
Bのグラフ
aws_s3_bucket_versioning
がaws_s3_bucket
に依存しています。
まとめ
以上、リソース間の依存関係をTerraformが理解できるように書きましょう、という話でした。細かい話ですが、これが原因でエラーが出たとき問題特定が辛かったなぁと思いだして書きました(S3バケットではなかったですが)。日々applyしているような環境ではあまり起きないんですが、久しぶりに別環境を一から作成する時に発生しがちです。お気をつけください。また、明示的に依存関係が表せない場合は、depends_on
の使用も検討しましょう。