Terraformで立てたEC2に後からSGを追加しようとするとEC2が再作成される
About
初回に立てた時はSGが1つだったが、あとからSGを追加したくなったときどうなるか試した結果です。
バージョン
- terraform v0.6.11
テスト環境の準備
とりあえず雑に作りました。
resource "aws_instance" "test" {
ami = "ami-b80b6db8"
instance_type = "t2.micro"
key_name = "${var.key_name}"
# Our Security group to allow SSH access
security_groups = ["${aws_security_group.test.name}"]
root_block_device {
delete_on_termination = "true"
volume_size = "8"
}
tags {
Name = "test"
Project = "test"
}
}
resource "aws_security_group" "test" {
name = "test"
description = "Used in the terraform"
# SSH access from anywhere
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "add-test" {
name = "add-test"
description = "Used in the terraform"
# SSH access from anywhere
ingress {
from_port = 21
to_port = 21
protocol = "tcp"
cidr_blocks = ["10.0.0.0/0"]
}
}
上記設定でEC2を起動すると、SGとしてはtestにのみ所属されている状態です。
実験
起動している状態で新たなSGをEC2に設定してみる。
security_groups = ["${aws_security_group.test.name}", "${aws_security_group.add-test.name}"]
の用に修正します。この状態でplanを実行すると下記の通りforce new resouceになります。

これは一旦EC2を破壊してから作成し直す動きになります。実際にapplyするとその動きが確認できます。破壊したくないサーバでやると悲惨な感じになりそうです。
管理コンソールから追加する
一旦最初の状態に戻してます。その後管理コンソールから、作成したEC2にSGを追加してみます。
Actions -> Networking -> Change security groupsを選択し、add-test も設定します。この状態でterraformを動かします。
terraform planを実行するとまた先ほどと同じ表示が…

applyするともちろんインスタンスが一度破壊されたのちに再作成されます。うーむ
回避策
こうなってしまうと運用上どうしてもSGを追加したくなったときにTerraformを諦めるか、サーバを破壊するかの選択になってしまいます。Terraform側で何個か回避できそうな設定があったので試してみます。
lifecycleとは
ドキュメントを見るとv0.6.0系からprevent_destroyが追加されたようです。
terraform/CHANGELOG.md at master · hashicorp/terraform · GitHub
lifecycleはTerraformのリソースに属性を与えるような仕組みで、現在3つの設定が可能です。
create_before_destroy
既存のリソースが有った場合に一旦削除してから作り直すようになります。
prevent_destroy
この設定があるリソースを削除しようとするとエラーになります。保護目的ですね。
ignore_changes
この設定はリストで記述することができて、実際のリソースとTerraform管理下のリソースの差分があったとしても無視してくれるようになります。
今回は3つの中の1番したを使って、SGを増やしたり減らしたりしても影響ないようにしてみます。
lifecycleを設定する
一旦最初の状態にしたのち、下記のようにmain.tfを編集します。
resource "aws_instance" "test" {
ami = "ami-b80b6db8"
instance_type = "t2.micro"
key_name = "${var.key_name}"
# Our Security group to allow HTTP and SSH access
security_groups = ["${aws_security_group.test.name}"]
root_block_device {
delete_on_termination = "true"
volume_size = "8"
}
tags {
Name = "test"
Project = "test"
}
lifecycle { "ignore_changes" = ["security_groups"]}
}
tagsの下にlifecycleが追加され、security_groupsは無視するという設定をしました。ここで先ほどと同様にAWSのコンソールからSGを追加してみます。
その後、planを実行すると…
Plan: 1 to add, 0 to change, 1 to destroy.
なんだと…applyしたら見事にインスタンスが削除されてました。
どうやら、これは初回起動時に設定しておかないとダメなようです。。。設定後にterraform planを叩いてみると、security_groupsの項目が消えて管理されてなさそうなことがわかります。ただ、試しにterraform applyするとエラーになりましたががが。
どうするか
どうしようかなぁというところです。terraforming使ってmergeすればというのも考えたのですが、うまくいかなかったorz
20160225追記
twitterでご指摘いただき、vpc_security_group_idsだと問題ないとのことでした。帰宅したら試します!ご指摘ありがとうございます!
帰宅したので試す。
そもそもaws_instanceのArgumentには2種類ありsecurity_groupsとvpc_security_group_idsがあります。どちらも複数指定できるのですが、デフォルトVPCを使っていない場合は後者を使えとのことでした。
初期状態にした上で下記のようにコードを修正します。
security_groups = ["${aws_security_group.test.name}"]
vpc_security_group_ids = ["${aws_security_group.add-test.id}"]
ここでplanすると
~ aws_instance.test
vpc_security_group_ids.#: "0" => "1"
vpc_security_group_ids.XXX: "" => "add-test"
Plan: 0 to add, 1 to change, 0 to destroy.
お、いけそうでは?ということでapplyします。
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
行けた!と思ったのもつかの間、元々設定していたものが外れてますね。
# security_groups = ["${aws_security_group.test.name}"]
vpc_security_group_ids = ["${aws_security_group.add-test.id}"]
上記の状態でplan実行すると変更なしとでました。ということは…
# security_groups = ["${aws_security_group.test.name}"]
vpc_security_group_ids = ["${aws_security_group.add-test.id}","${aws_security_group.test.id}"]
としてapplyしたらEC2を再作成せずにSGを追加することができました!
なぜ違う?
軽くソース見るとこんな感じになってました。
"security_groups": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Computed: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"vpc_security_group_ids": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
ForceNewという属性が付いているので、この挙動の違いが起きてそうでした。
Terraformもっと勉強します!!