あめがえるのITブログ

頑張りすぎない。ほどほどに頑張るブログ。

Terraformのlifecycleについて調べてついでに検証してみた。

Terraform文を見ているとlifecycleなるまたよくわからないキーワードが出てきたので調べてみた。
※知ったかぶりはダサいからわからないことは「わからない」と言うと「調べてこい」おじさんが出てくるのでIT業界は面倒だね。。。

Terraformのlifecycleとは

 ・tfファイルのresourceブロックに記載する構文。
 ・記載されたリソースに変更が発生する際の挙動を変更できる。

lifecycleで指定できるオプション

公式はこちら。
developer.hashicorp.com  ・create_befor_destroy:新しいリソースを作成し、破棄する。先に作成するのでリソースが重複する状態が発生する。
 ・prevent_destroy:リソースの破棄を防ぐ。破棄しようとするとエラーとなる。
 ・ignore_changes:リソースの指定属性の変更を無視する。作成後Terraformもしくは手動で変更されても無視する。
 ・replace_triggered_by:Terraform1.2で追加された機能。参照されたアイテムのいずれかが変更されたときにリソースを置き換える。
 ※ほぼ名前の通りですね。

やること

AWSのリソース(今回はVPCとSubnet)をTerraformで作成し、lifecycleを指定して挙動を確認していく。
※今回はWindows11でやっています。Linuxを使う場合"(ダブルクォーテーション)など一部変える必要あるかも。
※下記を参考にaws vaultを使いaws cliのprofile設定を行ってます。profileは任意で変えてOK。
amegaeru.hatenablog.jp

検証項目

 1.通常の変更時の挙動確認
 2.通常の削除時の挙動
 3.create_before_destroy検証
 4.prevent_destroy検証
 5.ignore_changes検証
 6.replace_triggered_by検証
 7.後始末

今回使うTerraformコード

provider "aws" {
    region = "ap-northeast-1"
    profile = "testvault"
}

variable "env" {
    default = {
        env_name = "test"
        vpc_cidr = "10.0.0.0/16"

        sb_az1a = "ap-northeast-1a"
        sb_az1a_cidr = "10.0.1.0/24"
    }
}

resource "aws_vpc" "vpc" {
    cidr_block = "${var.env.vpc_cidr}"
    tags = {
        Name = "${var.env.env_name}vpc"
    }
}

resource "aws_subnet" "public_1a" {
  vpc_id = "${aws_vpc.vpc.id}"
  availability_zone = "${var.env.sb_az1a}"
  cidr_block        = "${var.env.sb_az1a_cidr}"
  tags = {
    Name = "${var.env.env_name}public_1a"
  }
}

実践!

0.事前準備
0-1.Terraformコードのtfファイルを作成
 今回は下記に作成。※どこでもよいです。
  c:\test\main.tf
0-2.作業ディレクトリ移動

# cd c:\test

0-3.リソースを作成

# terraform plan
# terraform apply

0-4.リソース作成状況確認

> aws ec2 describe-vpcs --profile testvault | jq ".Vpcs[] | .CidrBlock,.VpcId"
"10.0.0.0/16"
"vpc-08a330246600a9a35"
> aws ec2 describe-subnets --profile testvault | jq ".Subnets[] | .Tags[].Value,.SubnetId"
"testpublic_1a"
"subnet-0edba038b4ea7a111"


下記のようなエラーが出る場合

jq: error (at <stdin>:102): Cannot iterate over null (null)

vpcやsubnetが複数あり、jqで指定されたキーを持っていない場合エラーとなる。
 取得順序によっては先に持っていないキーを参照し、エラーとなって以降が取得されない事態になる。今回でいうとSubnetのTagsを持っていない可能性あり。デフォルトで作成されているSubnetはTagsを持っていねぇ。。。
【回避策】
※エラーを回避したいキーの最後に?を入れる。

> aws ec2 describe-subnets --profile testvault | jq ".Subnets[] | .Tags[]?.Value,.SubnetId"


1.通常の変更時の挙動確認
1-1.Subnetを変更

variable "env" {
    default = {
        env_name = "test"
        vpc_cidr = "10.0.0.0/16"

        sb_az1a = "ap-northeast-1a"
        # 変更
        sb_az1a_cidr = "10.0.2.0/24"
     # -
    }
}

1-2.適用

# terraform plan
Plan: 1 to add, 0 to change, 1 to destroy.
※ 作成、破棄ですね。
# terraform apply

1-3.動作確認

>aws ec2 describe-vpcs --profile testvault | jq ".Vpcs[] | .CidrBlock,.VpcId"
"10.0.0.0/16"
"vpc-08a330246600a9a35"
>aws ec2 describe-subnets --profile testvault | jq ".Subnets[] | .Tags[]?.Value,.SubnetId"
"testpublic_1a"
"subnet-02ba12a759c99030a"
※Subnetが再作成されてますね。


2.通常の削除時の挙動
2-1.Subnetを削除

### コメントアウト
# resource "aws_subnet" "public_1a" {
#  vpc_id = "${aws_vpc.vpc.id}"
#  availability_zone = "${var.env.sb_az1a}"
#  cidr_block        = "${var.env.sb_az1a_cidr}"
#  tags = {
#    Name = "${var.env.env_name}public_1a"
#  }
#}

2-2.適用

# terraform plan
Plan: 0 to add, 0 to change, 1 to destroy.
※ 破棄ですね。
# terraform apply

2-3.動作確認

>aws ec2 describe-vpcs --profile testvault | jq ".Vpcs[] | .CidrBlock,.VpcId"
"10.0.0.0/16"
"vpc-08a330246600a9a35"
>aws ec2 describe-subnets --profile testvault | jq ".Subnets[] | .Tags[]?.Value,.SubnetId"
※Subnet消えた

2-4.戻し作業(コメントアウトを消して適用)

3.create_befor_destroy検証
3-1.terraformファイル修正

resource "aws_subnet" "public_1a" {
  vpc_id = "${aws_vpc.vpc.id}"
  availability_zone = "${var.env.sb_az1a}"
  cidr_block        = "${var.env.sb_az1a_cidr}"
  tags = {
    Name = "${var.env.env_name}public_1a"
  }
 ## 追加
  lifecycle {
    create_before_destroy = true
  }
 ## -
}

3-2.適用

# terraform plan
Plan: 1 to add, 0 to change, 1 to destroy.
※ 変更と変わりなし。
# terraform apply
※ 作成後追加なのですばやくapply後素早く3-3を実行します。

3-3.動作確認

>aws ec2 describe-vpcs --profile testvault | jq ".Vpcs[] | .CidrBlock,.VpcId"
"10.0.0.0/16"
"vpc-08a330246600a9a35"
>aws ec2 describe-subnets --profile testvault | jq ".Subnets[] | .Tags[]?.Value,.SubnetId"
"testpublic_1a"
"subnet-0347c08b19f390f5b"
※ 早すぎてCLIじゃ追えない、、、Webでも見てみたがだめでした。。。


4.prevent_destroy検証
4-1.terraformファイル修正

resource "aws_subnet" "public_1a" {
  vpc_id = "${aws_vpc.vpc.id}"
  availability_zone = "${var.env.sb_az1a}"
  cidr_block        = "${var.env.sb_az1a_cidr}"
  tags = {
    Name = "${var.env.env_name}public_1a"
  }
  lifecycle {
  ## 変更
    prevent_destroy = true
  ## -
  }
}

4-2.適用

# terraform plan
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.
※No changesと出るが適用されるっぽい。4-4参照
# terraform apply

4-3.terraformファイル修正

variable "env" {
    default = {
        env_name = "test"
        vpc_cidr = "10.0.0.0/16"

        sb_az1a = "ap-northeast-1a"
        ## 変更
        sb_az1a_cidr = "10.0.3.0/24"
        ## -
    }
}

4-4.適用

# terraform plan
Planning failed. Terraform encountered an error while generating this plan.


│ Error: Instance cannot be destroyed

│   on main.tf line 23:
│   23: resource "aws_subnet" "public_1a" {

│ Resource aws_subnet.public_1a has lifecycle.prevent_destroy set, but
│ the plan calls for this resource to be destroyed. To avoid this error
│ and continue with the plan, either disable lifecycle.prevent_destroy
│ or reduce the scope of the plan using the -target flag.
※変更できない

4-5.元に戻す(LifeCycel削除)

resource "aws_subnet" "public_1a" {
  vpc_id = "${aws_vpc.vpc.id}"
  availability_zone = "${var.env.sb_az1a}"
  cidr_block        = "${var.env.sb_az1a_cidr}"
  tags = {
    Name = "${var.env.env_name}public_1a"
  }
  ## 削除
  lifecycle {
    prevent_destroy = true
  }
 ##-
}
# terraform plan
# terraform apply


5.ignore_changes検証
5-1.terraformファイル修正

resource "aws_subnet" "public_1a" {
  vpc_id = "${aws_vpc.vpc.id}"
  availability_zone = "${var.env.sb_az1a}"
  cidr_block        = "${var.env.sb_az1a_cidr}"
  tags = {
    Name = "${var.env.env_name}public_1a"
  }
  lifecycle {
  ## 変更
    ignore_changes = [tags]
  ## -
  }
}

5-2.適用

# terraform plan
# terraform apply

5-3.事前設定確認

>aws ec2 describe-vpcs --profile testvault | jq ".Vpcs[] | .CidrBlock,.VpcId"
"10.0.0.0/16"
"vpc-08a330246600a9a35"
>aws ec2 describe-subnets --profile testvault | jq ".Subnets[] | .Tags[]?.Value,.SubnetId"
"testpublic_1a"
"subnet-078eeb25be1be6f4a"

5-4.Tags変更

resource "aws_subnet" "public_1a" {
  vpc_id = "${aws_vpc.vpc.id}"
  availability_zone = "${var.env.sb_az1a}"
  cidr_block        = "${var.env.sb_az1a_cidr}"
  tags = {
  ## 変更
    Name = "testtesttest"
  ## -
  }
  lifecycle {
    ignore_changes = [tags]
  }
}

5-5.適用

# terraform plan
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.
※No changesですね。
# terraform apply
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
※ 念のためapply。変更なし。

5-6.lifecycleを外したらどうなるか

resource "aws_subnet" "public_1a" {
  vpc_id = "${aws_vpc.vpc.id}"
  availability_zone = "${var.env.sb_az1a}"
  cidr_block        = "${var.env.sb_az1a_cidr}"
  tags = {
    Name = "testtesttest"
  }
 ## 削除
 ## -
}

5-7.適用

# terraform plan
Terraform used the selected providers to generate the following
execution plan. Resource actions are indicated with the following
symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_subnet.public_1a will be updated in-place
  ~ resource "aws_subnet" "public_1a" {
        id                                             = "subnet-078eeb25be1be6f4a"
      ~ tags                                           = {
          ~ "Name" = "testpublic_1a" -> "testtesttest"
        }
      ~ tags_all                                       = {
          ~ "Name" = "testpublic_1a" -> "testtesttest"
        }
        # (15 unchanged attributes hidden)
    }

Plan: 0 to add, 1 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.
※変えてくれそう
# terraform apply
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
※ステータス上は変わった

5-8.設定確認

>aws ec2 describe-vpcs --profile testvault | jq ".Vpcs[] | .CidrBlock,.VpcId"
"10.0.0.0/16"
"vpc-08a330246600a9a35"
>aws ec2 describe-subnets --profile testvault | jq ".Subnets[] | .Tags[]?.Value,.SubnetId"
"testtesttest"
"subnet-078eeb25be1be6f4a"
※変わった!


6.replace_triggered_by
vpcが作り直しされたらsubnetが作り直しがされないかを検証しようかと思ったけど、vpcが消えたらsubnetを消えるからこの環境では検証は無理と悟りました。甘かった。。。また今度。。。

7.後始末

# terraform destroy
※楽ちん!



感想

簡単に終わらせようと思ったけど以外と時間かかった。まぁこういうのの積み重ねが重要だよね。。。きっと( 一一)