概要

Terraformのリファレンスを見ているとたびたびリソース内にインラインで定義することを推奨せず、 リソースにアタッチする形式で、別にリソースを定義することを推奨しているNOTEがあります。

例えば、aws_security_groupでは単一のリソースでingressルールをまとめて設定することができますが、 ルールは別途aws_vpc_security_group_ingress_ruleのリソースを作成することで設定することを推奨しています。

リソースを別々に設定する利点として、セキュリティグループ自体の設定とingress(egress)ルールの設定それぞれに 固有IDがつくことで説明やタグ付けを細かく設定できることをあげています。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group.html

また、AWSのドキュメントとして公開しているTerraformのベストプラクティスでも、アタッチメントリソースを使用することを推奨する記載があります。 https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/terraform-aws-provider-best-practices/structure.html#attachment-resources

この記事ではセキュリティグループを題材に実際にリソースを作成してtfstateの中身を見てみることにより、同等の設定でどのような違いがあるかを確認します。

環境

  • Terraform v1.9.8
  • aws provider 5.75.1

確認方法

セキュリティグループを設定するaws_security_groupに加え、 ルールをリソースとして取り出したaws_security_group_ruleaws_vpc_security_group_ingress_ruleについて リソースを確認後tfstateファイルの中身を確認します。その後 terraform plan で差分を確認したときの違いも確認します。

aws_security_group_ruleaws_vpc_security_group_ingress_ruleの違いですが、 aws_vpc_security_group_ingress_rule が後発でingressのみ設定できるリソースとなっていることに加え、 CIDRを1つしか設定することができません。それによりひとつのCIDRに関するルール毎のにリソースを作成するよう制限できます。 また、ルール毎にタグも設定できるようになったことで細かい管理が可能になっています。

aws_security_group_ruleではingressとegress両方を指定することができ、 CIDRをリストとしてひとつのリソースで複数していすることができます。

1. aws_security_groupにまとめて設定する方法

terraformで以下のように設定します。

resource "aws_security_group" "all" {
  vpc_id      = local.vpc_id
  name = "all"
  tags = {
    "Name": "1リソースにまとめて表示"
  }
  
  ingress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

terraform apply を実行するとtfstateでも以下のように単一リソース内ですべての設定がされました。 ingressが配列で管理されており配列内の各ルールを識別するIDがないことがわかります。

{
    "schema_version": 1,
    "attributes": {
    "arn": "arn:aws:ec2:ap-northeast-1:000000000000:security-group/sg-01e0c54718561d832",
    "description": "Managed by Terraform",
    "egress": [
        {
        "cidr_blocks": [
            "0.0.0.0/0"
        ],
        "description": "",
        "from_port": 0,
        "ipv6_cidr_blocks": [],
        "prefix_list_ids": [],
        "protocol": "-1",
        "security_groups": [],
        "self": false,
        "to_port": 0
        }
    ],
    "id": "sg-01e0c54718561d832",
    "ingress": [
        {
        "cidr_blocks": [
            "0.0.0.0/0"
        ],
        "description": "",
        "from_port": 0,
        "ipv6_cidr_blocks": [],
        "prefix_list_ids": [],
        "protocol": "-1",
        "security_groups": [],
        "self": false,
        "to_port": 0
        }
    ],
    "name": "all",
    "name_prefix": "",
    "owner_id": "000000000000",
    "revoke_rules_on_delete": false,
    "tags": {
        "Name": "1リソースにまとめて表示"
    },
    "tags_all": {
        "Name": "1リソースにまとめて表示"
    },
    "timeouts": null,
    "vpc_id": "vpc-c6b450a0"
    },
    "sensitive_attributes": [],
    "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6OTAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0="
}

ingressルールを1つ追加して terraform plan を実施すると今設定しているingressルールを削除して2つのルールを作り直していることがわかります。

Terraform will perform the following actions:

  # aws_security_group.all will be updated in-place
  ~ resource "aws_security_group" "all" {
        id                     = "sg-01e0c54718561d832"
      ~ ingress                = [
          - {
              - cidr_blocks      = [
                  - "0.0.0.0/0",
                ]
              - from_port        = 0
              - ipv6_cidr_blocks = []
              - prefix_list_ids  = []
              - protocol         = "-1"
              - security_groups  = []
              - self             = false
              - to_port          = 0
                # (1 unchanged attribute hidden)
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + from_port        = 0
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "-1"
              + security_groups  = []
              + self             = false
              + to_port          = 0
            },
          + {
              + cidr_blocks      = [
                  + "1.1.1.1/32",
                ]
              + from_port        = 80
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 80
                # (1 unchanged attribute hidden)
            },
        ]
        name                   = "all"
        tags                   = {
            "Name" = "1リソースにまとめて表示"
        }
        # (8 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

2. 個別に設定する方法(aws_security_group_rule)

aws_security_group_rule リソースでそれぞれ独立したリソースとして設定する。 ingressかどうかはtypeで指定できます。

resource "aws_security_group" "rule1" {
  name        = "rule1"
  vpc_id      = local.vpc_id
  tags = {
    Name = "ルール毎にリリース表示1"
  }
}

resource "aws_security_group_rule" "rule1_ingress" {
    type = "ingress"
    security_group_id = aws_security_group.rule1.id
    from_port         = 0
    protocol = "tcp"
    to_port           = 0
    cidr_blocks = ["0.0.0.0/0"]
}

resource "aws_security_group_rule" "rule1_egress" {
    type = "egress"
    security_group_id = aws_security_group.rule1.id
    from_port         = 0
    protocol       = "tcp"
    to_port           = 0
    cidr_blocks = ["0.0.0.0/0"]
}

tfstateを見るとルール事にリソースを分けることができています。 CIDRは配列で記述されています。 また、ルールにtagsキーがないことも確認できます。

{
    "mode": "managed",
    "type": "aws_security_group",
    "name": "rule1",
    "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
    "instances": [
    {
        "schema_version": 1,
        "attributes": {
        "arn": "arn:aws:ec2:ap-northeast-1:000000000000:security-group/sg-0782fcfd658537f3a",
        "description": "Managed by Terraform",
        "egress": [
            {
            "cidr_blocks": [
                "0.0.0.0/0"
            ],
            "description": "",
            "from_port": 0,
            "ipv6_cidr_blocks": [],
            "prefix_list_ids": [],
            "protocol": "tcp",
            "security_groups": [],
            "self": false,
            "to_port": 0
            }
        ],
        "id": "sg-0782fcfd658537f3a",
        "ingress": [
            {
            "cidr_blocks": [
                "0.0.0.0/0"
            ],
            "description": "",
            "from_port": 0,
            "ipv6_cidr_blocks": [],
            "prefix_list_ids": [],
            "protocol": "tcp",
            "security_groups": [],
            "self": false,
            "to_port": 0
            }
        ],
        "name": "rule1",
        "name_prefix": "",
        "owner_id": "000000000000",
        "revoke_rules_on_delete": false,
        "tags": {
            "Name": "ルール毎にリリース表示1"
        },
        "tags_all": {
            "Name": "ルール毎にリリース表示1"
        },
        "timeouts": null,
        "vpc_id": "vpc-c6b450a0"
        },
        "sensitive_attributes": [],
        "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6OTAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0="
    }
    ]
},
{
    "mode": "managed",
    "type": "aws_security_group_rule",
    "name": "rule1_egress",
    "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
    "instances": [
    {
        "schema_version": 2,
        "attributes": {
        "cidr_blocks": [
            "0.0.0.0/0"
        ],
        "description": null,
        "from_port": 0,
        "id": "sgrule-1608454179",
        "ipv6_cidr_blocks": null,
        "prefix_list_ids": null,
        "protocol": "tcp",
        "security_group_id": "sg-0782fcfd658537f3a",
        "security_group_rule_id": "sgr-0816d3cfb80590aa1",
        "self": false,
        "source_security_group_id": null,
        "timeouts": null,
        "to_port": 0,
        "type": "egress"
        },
        "sensitive_attributes": [],
        "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozMDAwMDAwMDAwMDB9LCJzY2hlbWFfdmVyc2lvbiI6IjIifQ==",
        "dependencies": [
        "aws_security_group.rule1"
        ]
    }
    ]
},
{
    "mode": "managed",
    "type": "aws_security_group_rule",
    "name": "rule1_ingress",
    "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
    "instances": [
    {
        "schema_version": 2,
        "attributes": {
        "cidr_blocks": [
            "0.0.0.0/0"
        ],
        "description": null,
        "from_port": 0,
        "id": "sgrule-540708150",
        "ipv6_cidr_blocks": null,
        "prefix_list_ids": null,
        "protocol": "tcp",
        "security_group_id": "sg-0782fcfd658537f3a",
        "security_group_rule_id": "sgr-07fd116c273d71cb6",
        "self": false,
        "source_security_group_id": null,
        "timeouts": null,
        "to_port": 0,
        "type": "ingress"
        },
        "sensitive_attributes": [],
        "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozMDAwMDAwMDAwMDB9LCJzY2hlbWFfdmVyc2lvbiI6IjIifQ==",
        "dependencies": [
        "aws_security_group.rule1"
        ]
    }
    ]
}

terraform planを実行すると既存のルールは表示されず、追加したingressルールのみが表示されていることを確認できました。

Terraform will perform the following actions:

  # aws_security_group_rule.rule1_ingress_2 will be created
  + resource "aws_security_group_rule" "rule1_ingress_2" {
      + cidr_blocks              = [
          + "1.1.1.1/32",
        ]
      + from_port                = 80
      + id                       = (known after apply)
      + protocol                 = "tcp"
      + security_group_id        = "sg-0782fcfd658537f3a"
      + security_group_rule_id   = (known after apply)
      + self                     = false
      + source_security_group_id = (known after apply)
      + to_port                  = 80
      + type                     = "ingress"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

個別に設定する方法(aws_vpc_security_group_ingress_rule)

aws_vpc_security_group_ingress_rule リソースでも確認します。

resource "aws_security_group" "rule2" {
  name        = "rule2"
  vpc_id      = local.vpc_id
  tags = {
    Name = "ルール毎にリリース表示2"
  }
}

resource "aws_vpc_security_group_ingress_rule" "rule2" {
  security_group_id = aws_security_group.rule2.id
  cidr_ipv4         = "0.0.0.0/0"
  from_port         = 0
  ip_protocol       = "tcp"
  to_port           = 0
}

resource "aws_vpc_security_group_egress_rule" "rule2" {
  security_group_id = aws_security_group.rule2.id
  cidr_ipv4         = "0.0.0.0/0"
  from_port         = 0
  ip_protocol       = "tcp"
  to_port           = 0
}

tfstateを確認するとCIDRが配列ではなく文字列型で管理されていることと、tagsキーが追加されていることを確認できました。

{
    "mode": "managed",
    "type": "aws_security_group",
    "name": "rule2",
    "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
    "instances": [
    {
        "schema_version": 1,
        "attributes": {
        "arn": "arn:aws:ec2:ap-northeast-1:000000000000:security-group/sg-08b0deaeb5db410cf",
        "description": "Managed by Terraform",
        "egress": [],
        "id": "sg-08b0deaeb5db410cf",
        "ingress": [],
        "name": "rule2",
        "name_prefix": "",
        "owner_id": "000000000000",
        "revoke_rules_on_delete": false,
        "tags": {
            "Name": "ルール毎にリリース表示2"
        },
        "tags_all": {
            "Name": "ルール毎にリリース表示2"
        },
        "timeouts": null,
        "vpc_id": "vpc-c6b450a0"
        },
        "sensitive_attributes": [],
        "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6OTAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0="
    }
    ]
},
{
    "mode": "managed",
    "type": "aws_vpc_security_group_egress_rule",
    "name": "rule2",
    "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
    "instances": [
    {
        "schema_version": 0,
        "attributes": {
        "arn": "arn:aws:ec2:ap-northeast-1:000000000000:security-group-rule/sgr-05f0c3666a41e82c8",
        "cidr_ipv4": "0.0.0.0/0",
        "cidr_ipv6": null,
        "description": null,
        "from_port": 0,
        "id": "sgr-05f0c3666a41e82c8",
        "ip_protocol": "tcp",
        "prefix_list_id": null,
        "referenced_security_group_id": null,
        "security_group_id": "sg-08b0deaeb5db410cf",
        "security_group_rule_id": "sgr-05f0c3666a41e82c8",
        "tags": null,
        "tags_all": {},
        "to_port": 0
        },
        "sensitive_attributes": [],
        "dependencies": [
        "aws_security_group.rule2"
        ]
    }
    ]
},
{
    "mode": "managed",
    "type": "aws_vpc_security_group_ingress_rule",
    "name": "rule2",
    "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
    "instances": [
    {
        "schema_version": 0,
        "attributes": {
        "arn": "arn:aws:ec2:ap-northeast-1:000000000000:security-group-rule/sgr-05c781d1dd5d98792",
        "cidr_ipv4": "0.0.0.0/0",
        "cidr_ipv6": null,
        "description": null,
        "from_port": 0,
        "id": "sgr-05c781d1dd5d98792",
        "ip_protocol": "tcp",
        "prefix_list_id": null,
        "referenced_security_group_id": null,
        "security_group_id": "sg-08b0deaeb5db410cf",
        "security_group_rule_id": "sgr-05c781d1dd5d98792",
        "tags": null,
        "tags_all": {},
        "to_port": 0
        },
        "sensitive_attributes": [],
        "dependencies": [
        "aws_security_group.rule2"
        ]
    }
    ]
}

terraform planは aws_security_group_rule と同様の結果です。

Terraform will perform the following actions:

  # aws_vpc_security_group_ingress_rule.rule2_2 will be created
  + resource "aws_vpc_security_group_ingress_rule" "rule2_2" {
      + arn                    = (known after apply)
      + cidr_ipv4              = "1.1.1.1/32"
      + from_port              = 80
      + id                     = (known after apply)
      + ip_protocol            = "tcp"
      + security_group_id      = "sg-0a1ef418ac7f2788d"
      + security_group_rule_id = (known after apply)
      + tags_all               = {}
      + to_port                = 80
    }

Plan: 1 to add, 0 to change, 0 to destroy.

比較まとめ

aws_security_groupにまとめて設定するとterraform planで差分取得時に既存のルールをすべて削除して再作成することを確認できました。

個別に設定する方法(aws_security_group_rule)ではterraform planで追加削除をルール毎に確認でき、ルール毎に複数のCIDRを指定できることを確認しました。

個別に設定する方法(aws_vpc_security_group_ingress_rule)ではterraform planで追加削除をルール毎に確認できることを同様に確認しました。 さらにルール毎にタグを設定できることと、ルールごとに単一のCIDRを指定するを確認しました。

小規模であればなんでもいいとなりそうですが、ある程度ルールが増えると aws_vpc_security_group_ingress_rule で個別にルールを指定したほうが 運用しやすそうです。