0
0

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のfor_eachでミスってダウンタイムを出した話

Posted at

以前の出来事ですが、TF素人だった私は下記のようなTFを書いてました。

locals {
  sg_ingress_rules = [
        {
          ip_protocol = "tcp"
          cidr_ipv4   = cidr
          from_port   = 443
          to_port     = 443
        },
        {
          ip_protocol = "tcp"
          cidr_ipv4   = cidr
          from_port   = 6666
          to_port     = 6666
        },
    ]
  }

  
resource "aws_security_group" "this" {
  name   = "${local.prefix}-sg"
  vpc_id = var.vpc_id
  tags = {
    Name = "${local.prefix}-sg"
  }
}
resource "aws_vpc_security_group_ingress_rule" "this" {
  for_each          = { for idx, rule in local.sg_ingress_rules : idx => rule }
  security_group_id = aws_security_group.this.id
  cidr_ipv4         = each.value.cidr_ipv4
  from_port         = each.value.from_port
  to_port           = each.value.to_port
  ip_protocol       = each.value.ip_protocol
}

このTFどこが悪いかお分かりになりますか?

正解は...

resource "aws_vpc_security_group_ingress_rule" "this" {
  for_each          = { for idx, rule in local.sg_ingress_rules : idx => rule } #<===== ココ
  security_group_id = aws_security_group.this.id
  cidr_ipv4         = each.value.cidr_ipv4
  from_port         = each.value.from_port
  to_port           = each.value.to_port
  ip_protocol       = each.value.ip_protocol
}

なぜでしょうか?

まず、とりあえずこれ、applyしたらリソースは普通にできます。

では次に、このsgのingress ruleをport 3306も許可するように更新しようとします。

locals {
  sg_ingress_rules = [
        {
          ip_protocol = "tcp"
          cidr_ipv4   = cidr
          from_port   = 443
          to_port     = 443
        },
        # Newly added!!
         {
          ip_protocol = "tcp"
          cidr_ipv4   = cidr
          from_port   = 3306
          to_port     = 3306
        },
        {
          ip_protocol = "tcp"
          cidr_ipv4   = cidr
          from_port   = 6666
          to_port     = 6666
        },
    ]
  }

Planを流すと下記のようになるでしょう。

  + create
  ~ update in-place
OpenTofu will perform the following actions:
  # aws_vpc_security_group_ingress_rule.this["1"] will be updated in-place
  ~ resource "aws_vpc_security_group_ingress_rule" "this" {
      ~ from_port              = 6666 -> 3306
        id                     = "sgr-<UUID1>"
      ~ to_port                = 6666 -> 3306
        # (7 unchanged attributes hidden)
    }
  # aws_vpc_security_group_ingress_rule.this["2"] will be created
  + resource "aws_vpc_security_group_ingress_rule" "this" {
      + arn                    = (known after apply)
      + cidr_ipv4              = "0.0.0.0/0"
      + from_port              = 6666
      + id                     = (known after apply)
      + ip_protocol            = "tcp"
      + region                 = "ap-northeast-1"
      + security_group_id      = "sg-<UUID2>"
      + security_group_rule_id = (known after apply)
      + tags_all               = {}
      + to_port                = 6666
    }
 
Plan: 1 to add, 1 to change, 0 to destroy.

たぶん、ここで注意深い人は、あ、1 to changeを見て、これはなんか嫌な予感がすると気づくはずです。

しかし、ミーティング前にPRをマージを完了させてタスクをdoneにしたかった私は急いでマージをクリック、applyのログを眺めていると...

  
  
   Error: creating VPC Security Group Rule
   
     with aws_vpc_security_group_ingress_rule.this["2"],
     on security_groups.tf line N, in resource "aws_vpc_security_group_ingress_rule" "this":
    N: resource "aws_vpc_security_group_ingress_rule" "this" {
   
   operation error EC2: AuthorizeSecurityGroupIngress, https response error
   StatusCode: 400, RequestID: <UUID>, api error
   InvalidPermission.Duplicate: the specified rule "peer: 0.0.0.0/0, TCP, from
  │ port: 6666, to port: 6666, ALLOW" already exists
  

無事、コンフリクトエラーでました...

あせってretryを流すも、再実行に3分くらいかかり、その間6666のポートがブロックされた状態になってしまいました...

結果、アプリのダウンタイムにつながってしまいました...
Devだったのでまだ良かったですが、Prodだと大惨事です。


じゃあどうすれば良かったのかというと...

普通に下記のように、for_each用のkeyを用意しておけば良かったかなと思います。

そうすれば、Planも Plan: 1 to add, 0 to change, 0 to destroy. のようにただのaddになって失敗することもなかったでしょう。

locals {
  sg_ingress_rules = {
        "tcm_cidr_443": {
          ip_protocol = "tcp"
          cidr_ipv4   = cidr
          from_port   = 443
          to_port     = 443
        },
        "tcm_cidr_6666": {
          ip_protocol = "tcp"
          cidr_ipv4   = cidr
          from_port   = 6666
          to_port     = 6666
        },
    ]
  }

  
resource "aws_security_group" "this" {
  name   = "${local.prefix}-sg"
  vpc_id = var.vpc_id
  tags = {
    Name = "${local.prefix}-sg"
  }
}
resource "aws_vpc_security_group_ingress_rule" "this" {
  for_each          = local.sg_ingress_rules
  security_group_id = aws_security_group.this.id
  cidr_ipv4         = each.value.cidr_ipv4
  from_port         = each.value.from_port
  to_port           = each.value.to_port
  ip_protocol       = each.value.ip_protocol
}

ということで、みなさん、for_eachでarrayを使いたい場合、適当にindexを使ったりせずに、arrayをmapにして、keyをしっかり定義した方がよさそうです!

ではでは、良いOpenTofu/Terraformライフを〜!

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?