はじめに
aws の security group を terraform で管理する場合、security_group の定義内部に rule を追加する方法と、aws_vpc_security_group_egress_rule、 aws_vpc_security_group_ingress_rule を使って外部で定義する方法があります。
terraform の公式 doc では後者を推奨して、前者の運用に警告を鳴らしています。なので新しくリソースを定義するのであれば迷いなく後者を選択すべきです。
でも、昔に書かれていたもので既にコードに記載されているものってありますよね。それを移行するのがめっちゃ面倒だったのでその話をこの記事でしたいと思います。
インラインルールではなぜダメなのか
インラインルールとは、aws_security_group リソースの中に直接 ingress / egress ブロックを記述する方法です。
例えば以下みたいなイメージですね。
resource "aws_security_group" "example" {
name = "example-app"
description = "example application"
vpc_id = var.vpc_id
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.example_lb.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
この書き方には以下の問題があります。
-
ルールの追加・削除時に他のルールも再作成される可能性がある
- インラインルールは Terraform 内部で一つのまとまりとして扱われます
- 1 つのルールを変更すると、既存のルールが一度削除されて新しいルールが追加されることがあります
- これにより、一時的に通信が遮断されるリスクがあります
-
他のモジュールからルールを追加しにくい
- セキュリティグループ本体と同じ場所にルールを書く必要があるため、モジュール分割が難しくなります
じゃあ新規追加する場合にはベストプラクティスに従おう!その通りです。
私もそう思い、新規追加する場合には推奨される aws_vpc_security_group_ingress_rule,aws_vpc_security_group_egress_rule へのを使おうと思いました。
ですがそう簡単にはいかないんです。
インラインルールと個別ルールの競合
Terraform の AWS Provider では、インラインルールと個別ルール(aws_vpc_security_group_ingress_rule など)を同じセキュリティグループに対して併用すると競合が発生します。
具体的には、1 つでも aws_vpc_security_group_ingress_rule を追加すると、Terraform はそのセキュリティグループの全ての ingress ルールを個別ルールで管理しようとします。
その結果、インラインで定義していた既存ルールが削除対象として検出されてしまいます。
自分達が取った対応策
最終的に以下の方針で移行を行いました。
1. 既存ルールを全て import ブロックで取り込む
既存のセキュリティグループルールを Terraform の state に取り込むために import ブロックを活用します。これにより、既存リソースを削除・再作成せずに管理できます。
resource "aws_security_group" "example" {
name = "example-app"
description = "example application"
vpc_id = var.vpc_id
tags = {
Name = "example-app"
Project = "example"
Stage = "production"
}
}
# 個別ルールとして定義
resource "aws_vpc_security_group_ingress_rule" "example_ingress_from_lb" {
security_group_id = aws_security_group.example.id
from_port = 8080
to_port = 8080
ip_protocol = "tcp"
referenced_security_group_id = aws_security_group.example_lb.id
}
# 既存リソースを import で取り込む
import {
to = aws_vpc_security_group_ingress_rule.example_ingress_from_lb
id = "sgr-xxxxxxxxxxxxxxxxx" # AWS コンソールや CLI で確認した実際の ID
}
# 個別ルールとして定義
resource "aws_vpc_security_group_egress_rule" "example_egress_to_all" {
security_group_id = aws_security_group.example.id
ip_protocol = "-1"
cidr_ipv4 = "0.0.0.0/0"
}
# 既存リソースを import で取り込む
import {
to = aws_vpc_security_group_egress_rule.example_egress_to_all
id = "sgr-yyyyyyyyyyyyyyyyy"
}
2. 全ルールを一度に移行する
部分的な移行は競合を引き起こすため、対象のセキュリティグループに関連する全てのルールを一度に移行する必要があります。
移行手順
実際に行った移行手順は以下の通りです。
- AWS コンソールまたは CLI で既存のセキュリティグループルール ID を確認
-
aws_security_groupからインラインのingress/egressブロックを削除 - 全てのルールを
aws_vpc_security_group_ingress_rule/aws_vpc_security_group_egress_ruleとして定義 - 各ルールに対応する
importブロックを追加 -
terraform planで差分がないことを確認 -
terraform applyで変更を反映 -
importブロックを削除(import 完了後は不要)
まとめ
aws_vpc_security_group_ingress_rule,aws_vpc_security_group_egress_rule への移行は公式でも推奨されていますが、aws_security_group のインラインルールと併用はできず移行時は地味に面倒な作業と戦う必要があります。
今手元にあるコードがインラインの場合には早めの移行を、新規でコードを書いている場合にはベスとプラクティスに従っておきましょう。
もしかしたら自分が取ったよりもスムーズな移行方法があるかもしれません。その時にはコメントで教えていただけると幸いです。