2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Terraform初心者向け】セキュリティグループの循環依存とは?なぜ別リソースで解決できるのか

2
Posted at

はじめに

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を参照
循環依存のリスク あり なし
ルールの個別管理 不可 可能

ポイントは「ルールをセキュリティグループの外に出す」こと。 これにより、セキュリティグループ自体は互いに依存せず、ルールリソースが両方を一方向に参照するだけになるため、循環依存を回避できます。

現在は相互参照がなくても、将来の拡張性を考慮して別リソース方式で書いておくのがベストプラクティスです。

参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?