What's?
Terraformを使ってEC2を立てる時に、条件によってデフォルトのセキュリティグループを作る・作らないみたいな制御を入れていたら、妙な挙動をされたので困ったという話。
結論としては、EC2の定義に指定していたセキュリティグループの指定に使う属性が誤っていたのですが。
やりたかったこと
こんな感じに、変数でセキュリティグループを与えたら指定のものを使い、そうでなければデフォルトのセキュリティグループを作成する、みたいなことをやろうとしました。
variable "my_security_groups" {
type = list(string)
default = []
}
resource "aws_security_group" "this" {
count = length(var.my_security_groups) > 0 ? 0 : 1
...
}
resource "aws_instance" "this" {
ami = "..."
instance_type = "..."
subnet_id = "..."
security_groups = length(var.my_security_groups) > 0 ? var.my_security_groups : [aws_security_group.this[0].id]
}
まあ、ハマりましたと。
環境
今回の環境は、こちらです。
$ terraform version
Terraform v0.12.26
+ provider.aws v2.66.0
AWS Providerの指定とVPCの作成
VPCの作成までは、AWS VPC Terraform moduleを使ってさっくりと。
terraform {
required_version = "0.12.26"
}
provider "aws" {
version = "2.66.0"
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "2.39.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1c"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
map_public_ip_on_launch = true
enable_nat_gateway = false
}
EC2+セキュリティグループ
こんな感じで、セキュリティグループをInput Variableとして定義し、指定されていればEC2のセキュリティグループに設定し、そうでなければデフォルトのセキュリティグループを作って指定するように定義してみました。
variable "my_security_groups" {
type = list(string)
default = []
}
resource "aws_security_group" "this" {
count = length(var.my_security_groups) > 0 ? 0 : 1
name = "my-ec2-instance-sg"
vpc_id = module.vpc.vpc_id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "this" {
ami = "ami-0a1c2ec61571737db"
instance_type = "t2.micro"
subnet_id = module.vpc.public_subnets[0]
security_groups = length(var.my_security_groups) > 0 ? var.my_security_groups : [aws_security_group.this[0].id]
}
※ アウトバウンドしか許可していませんが、今回は簡単に済ませています
で、apply。
$ terraform apply
リソースの作成は、とりあえずうまくいきます。今回はInput Variableに値を指定していないので、デフォルトのセキュリティグループ、つまりこちらが作成されてEC2に割り当てられます。
resource "aws_security_group" "this" {
count = length(var.my_security_groups) > 0 ? 0 : 1
name = "my-ec2-instance-sg"
vpc_id = module.vpc.vpc_id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
ここで、そのままplanを実行してみます。
$ terraform plan
なんと、EC2の再作成が提示されます。
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# aws_instance.this must be replaced
-/+ resource "aws_instance" "this" {
セキュリティグループが再作成の理由みたいです。
~ security_groups = [ # forces replacement
+ "sg-0f3490bf5526991db",
]
実際、このままapplyするとEC2が再作成されてしまいます。
ちなみに、セキュリティグループ自体はなにも変わりませんし、再作成もされません。
この指定方法がいけないのかな?と思い
security_groups = length(var.my_security_groups) > 0 ? var.my_security_groups : [aws_security_group.this[0].id]
指定方法を変えたり
security_groups = length(var.my_security_groups) > 0 ? var.my_security_groups : aws_security_group.this.*.id
セキュリティグループだけ別モジュールに切り出したり、変数の定義方法を変えたり、ループをcount
ではなくfor_each
に変えてみたりしたのですが、やっぱり再作成になってしまいます。
で、いろいろ見ていたらこんな記事を見つけたので
Terraformで立てたEC2に後からSGを追加しようとするとEC2が再作成される
なるほど、security_groups
を使っているのがダメなんだということで、vpc_security_group_ids
にすると差分が出なくなりました。
vpc_security_group_ids = length(var.my_security_groups) > 0 ? var.my_security_groups : [aws_security_group.this[0].id]
#vpc_security_group_ids = length(var.my_security_groups) > 0 ? var.my_security_groups : aws_security_group.this.*.id # こちらも可
デフォルトのVPCを使っているわけではないので、これで大丈夫ですね。
めでたし、めでたし。
本当に?
EC2でセキュリティグループを指定する際に、security_groups
からvpc_security_group_ids
へ変更することで、この挙動を回避できたわけですが。
security_groups
とvpc_security_group_ids
の定義の差を見比べてみると、ForceNew
しか違いがありません。
差分があったら、リソースの再作成を強制するみたいですね。
ところで、今回のケースだと、問題になっているセキュリティグループ自体には差分がありません。
にも関わらず、EC2の再作成を強制させられます。
実際、security_groups
を使っている時に、terraform apply
でEC2を再作成してもセキュリティグループの定義や、EC2に割り当てているセキュリティグループの情報はterraform.tfstate
を見ても差がないんですよね。
ForceNew
で定義されている引数に対して、こういう条件指定したりすると、危ないかも?という話なんでしょうかね(リストで指定するのも微妙なのかなぁという気もしますが…)。
どっちにしろ、リソース作成後もちゃんとterraform plan
をして、妙な動きをしないか確認しようという気になりました…。