はじめに
Terraformでセキュリティグループを定義するとき、ingress/egressブロックを直接書く方法と、aws_vpc_security_group_ingress_rule/aws_vpc_security_group_egress_ruleという別リソースで定義する方法があります。
後者が推奨される理由は循環依存の回避ですが、初心者にとっては「なぜブロック体だとダメで、別リソースだと解決するのか」がわかりにくいポイントです。
この記事では、Terraformの依存関係の仕組みから丁寧に解説します。
対象読者
- セキュリティグループの書き方で迷っている方
- 「循環依存」と言われてもピンとこない方
そもそも「依存関係」とは
Terraformでは、あるリソースが別のリソースの値(IDなど)を参照すると、依存関係が生まれます。
resource "aws_security_group" "b" {
# ...
}
resource "aws_security_group" "a" {
ingress {
source_security_group_id = aws_security_group.b.id # BのIDを参照
}
}
この場合、AはBに依存しています。TerraformはBを先に作成し、そのあとでAを作成します。
循環依存とは
AがBに依存し、BもAに依存する状態を循環依存と呼びます。
循環依存が発生する例(ブロック体)
アプリケーションサーバー(SG-A)とデータベース(SG-B)が相互に通信するケースを考えます。
resource "aws_security_group" "app" {
ingress {
# DBからの通信を受け入れる → appはdbに依存
source_security_group_id = aws_security_group.db.id
}
}
resource "aws_security_group" "db" {
ingress {
# アプリからの通信を受け入れる → dbはappに依存
source_security_group_id = aws_security_group.app.id
}
}
何が起きるか
appを作りたい
→ ingressブロックを評価する必要がある
→ db.idが必要
→ dbを先に作らなければ…
dbを作りたい
→ ingressブロックを評価する必要がある
→ app.idが必要
→ appを先に作らなければ…
→ どちらも先に作れない! = 循環依存エラー
Terraformは Error: Cycle を出力し、terraform apply が失敗します。
なぜブロック体だと循環依存が起きるのか
ここが最も重要なポイントです。
ingress/egressブロックは、aws_security_groupリソースの"一部"として扱われます。
つまり、ブロック内で別のセキュリティグループを参照すると、セキュリティグループ自体が別のセキュリティグループに依存することになります。
aws_security_group.app(ingressブロック含む)
└→ aws_security_group.db に依存
aws_security_group.db(ingressブロック含む)
└→ aws_security_group.app に依存
→ 循環!
Terraformから見ると、「リソースAを作るにはリソースBが必要、リソースBを作るにはリソースAが必要」という解決不可能な状態になります。
なぜ別リソースだと解決できるのか
別リソース方式のコード
# セキュリティグループA(ルールを含まない)
resource "aws_security_group" "app" {
name = "app-sg"
description = "Application server security group"
vpc_id = aws_vpc.main.id
}
# セキュリティグループB(ルールを含まない)
resource "aws_security_group" "db" {
name = "db-sg"
description = "Database security group"
vpc_id = aws_vpc.main.id
}
# ルール1: appからdbへの通信を許可
resource "aws_vpc_security_group_egress_rule" "app_to_db" {
security_group_id = aws_security_group.app.id
referenced_security_group_id = aws_security_group.db.id
from_port = 3306
to_port = 3306
ip_protocol = "tcp"
}
# ルール2: dbがappからの通信を受け入れる
resource "aws_vpc_security_group_ingress_rule" "db_from_app" {
security_group_id = aws_security_group.db.id
referenced_security_group_id = aws_security_group.app.id
from_port = 3306
to_port = 3306
ip_protocol = "tcp"
}
依存関係の流れ
aws_security_group.app → 何にも依存しない
aws_security_group.db → 何にも依存しない
aws_vpc_security_group_egress_rule.app_to_db
└→ app と db に依存(一方向)
aws_vpc_security_group_ingress_rule.db_from_app
└→ db と app に依存(一方向)
作成順序
1. aws_security_group.app を作成(完了)
2. aws_security_group.db を作成(完了)
3. egress_rule を作成(app と db を参照 → 両方作成済みなので問題なし)
4. ingress_rule を作成(db と app を参照 → 両方作成済みなので問題なし)
セキュリティグループ同士は互いに依存せず、ルールリソースが両方を参照するだけなので、循環が発生しません。
図で理解する
ブロック体(循環する)
┌──────────────┐ ┌──────────────┐
│ SG: app │──依存→│ SG: db │
│ (ingress含) │←依存──│ (ingress含) │
└──────────────┘ └──────────────┘
↑↓ 循環! どちらも先に作れない
別リソース(循環しない)
┌──────────┐ ┌──────────┐
│ SG: app │ │ SG: db │
│ (ルール │ │ (ルール │
│ なし) │ │ なし) │
└────┬─────┘ └────┬─────┘
│ │
│ ┌────────────┐ │
└────┤ egress_rule├───┘
└────────────┘
│ ┌─────────────┐ │
└────┤ ingress_rule├──┘
└─────────────┘
ルールが両方を参照するだけ → 循環なし!
実際のコード例:VPCエンドポイント用セキュリティグループ
現在は相互参照がない場合でも、将来の拡張に備えて別リソース方式で書くのが推奨です。
# セキュリティグループ(ルールなし)
resource "aws_security_group" "vpc_endpoint" {
name = "vpc-endpoint-sg"
description = "Security Group for VPC Endpoints"
vpc_id = aws_vpc.main.id
tags = {
Name = "vpc-endpoint-sg"
}
}
# インバウンドルール: VPC内からのHTTPS通信を許可
resource "aws_vpc_security_group_ingress_rule" "vpc_endpoint_https" {
security_group_id = aws_security_group.vpc_endpoint.id
cidr_ipv4 = var.vpc_cidr
from_port = 443
to_port = 443
ip_protocol = "tcp"
}
# アウトバウンドルール: すべてのアウトバウンドトラフィックを許可
resource "aws_vpc_security_group_egress_rule" "vpc_endpoint_all" {
security_group_id = aws_security_group.vpc_endpoint.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "-1"
}
まとめ
| 項目 | ブロック体 | 別リソース |
|---|---|---|
| ルールの定義場所 |
aws_security_groupの中 |
aws_vpc_security_group_*_rule |
| 依存関係 | SG同士が直接依存 | ルールが両方のSGを参照 |
| 循環依存のリスク | あり | なし |
| ルールの個別管理 | 不可 | 可能 |
ポイントは「ルールをセキュリティグループの外に出す」こと。 これにより、セキュリティグループ自体は互いに依存せず、ルールリソースが両方を一方向に参照するだけになるため、循環依存を回避できます。
現在は相互参照がなくても、将来の拡張性を考慮して別リソース方式で書いておくのがベストプラクティスです。