terraform apply実行時のエラー「A duplicate Security Group rule was found on (sg-...」に対するトラブルシューティング

# 用語
SG: セキュリティグループ
SGR: セキュリティグループルール

# 注意
`terraform apply`実行時のエラーに対するトラブルシューティングという内容ですが、後述するように、そもそも、`terraform state mv`コマンドを適切に事前に実行すれば、回避できるエラーです。
私は、[tfmigrate](https://github.com/minamijoyo/tfmigrate) を使っているので、`terraform state mv`コマンドをコードに起こして確認してから実行しています。

# 目的
- SGR(セキュリティグループルール) を改名して、 `terraform apply` を実施したときに起きることがあるエラーのトラブルシューティングの方法を知る。
※ 後で説明するが、エラーが起きないときもある。
```
│ Error: [WARN] A duplicate Security Group rule was found on (sg-XXXXXXXXXXXXXXX). This may be
```
- SGR(セキュリティグループルール)の削除と作成のタイミングを知る。
- 事前に上記エラーを回避する方法を知る。

# 結論
- 上記エラーが起きたら、もう一度 `terraform apply`をやれば成功する。
  - ※ただ、そもそも、`terraform apply`を実施してこのエラーを起こす前に、`terraform state mv`コマンドや `tfmigrate apply`コマンド([tfmigrate](https://github.com/minamijoyo/tfmigrate) のインストール必須)を実施しよう。
- SGR(セキュリティグループルール)の削除と作成のタイミングは、かなり適当で、上記の`terraform apply`はエラーになったりならなかったりする。
- リソースの改名や置き換えの時は、`state`コマンドを使ってから`terraform apply`を実施するべきなので、`terraform apply`実施前に必ず検討しよう。

# 説明
## エラーを見てみる
モジュール内のSGRを `sgr_ingress` から `sgr_ingress_renamed` に改名した。

``` modules/security_group/aws_security_group_rule.tf
resource "aws_security_group_rule" "sgr_ingress_renamed" {
  type              = "ingress"
  from_port         = var.port
  to_port           = var.port
  protocol          = "tcp"
  cidr_blocks       = var.cidr_blocks_ingress
  security_group_id = aws_security_group.normal.id

  timeouts {
    create = "1m"
  }
}

resource "aws_security_group_rule" "sgr_egress" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.normal.id

  timeouts {
    create = "1m"
  }
}
```
``` modules/security_group/aws_security_group.tf
resource "aws_security_group" "normal" {
  name   = var.name
  vpc_id = var.vpc_id

  timeouts {
    create = "1m"
    delete = "1m"
  }
}
```
``` aws_security_group.tf
module "allow_http" {
  source              = "./modules/security_group"
  name                = "AllowHTTP_2"
  vpc_id              = aws_vpc.tf_my_vpc.id
  port                = 80
  cidr_blocks_ingress = ["0.0.0.0/0"]
}

module "allow_https" {
  source              = "./modules/security_group"
  name                = "AllowHTTPS_2"
  vpc_id              = aws_vpc.tf_my_vpc.id
  port                = 443
  cidr_blocks_ingress = ["0.0.0.0/0"]
}

module "allow_ssh" {
  source              = "./modules/security_group"
  name                = "AllowSSH_2"
  vpc_id              = aws_vpc.tf_my_vpc.id
  port                = 22
  cidr_blocks_ingress = ["0.0.0.0/0"]
}
```
そして、`terraform apply`を実施すると、下記のエラーが出るときがある。 ※ この記事の趣旨でもあるが、場合によってはエラーは出ない。
```
Plan: 3 to add, 0 to change, 3 to destroy.
module.allow_https.aws_security_group_rule.sgr_ingress: Destroying... [id=sgrule-XXXXXXXXXX]
module.allow_http.aws_security_group_rule.sgr_ingress: Destroying... [id=sgrule-XXXXXXXXXX]
module.allow_ssh.aws_security_group_rule.sgr_ingress: Destroying... [id=sgrule-XXXXXXXXXX]
module.allow_https.aws_security_group_rule.sgr_ingress_renamed: Creating...
module.allow_http.aws_security_group_rule.sgr_ingress_renamed: Creating...
module.allow_ssh.aws_security_group_rule.sgr_ingress_renamed: Creating...
module.allow_http.aws_security_group_rule.sgr_ingress: Destruction complete after 0s
module.allow_ssh.aws_security_group_rule.sgr_ingress: Destruction complete after 0s
module.allow_https.aws_security_group_rule.sgr_ingress: Destruction complete after 1s
module.allow_http.aws_security_group_rule.sgr_ingress_renamed: Creation complete after 1s [id=sgrule-XXXXXXXXXX]
module.allow_ssh.aws_security_group_rule.sgr_ingress_renamed: Creation complete after 2s [id=sgrule-XXXXXXXXXX]
╷
│ Error: [WARN] A duplicate Security Group rule was found on (sg-XXXXXXXXXXXXXXX). This may be
│ a side effect of a now-fixed Terraform issue causing two security groups with
│ identical attributes but different source_security_group_ids to overwrite each
│ other in the state. See https://github.com/hashicorp/terraform/pull/2376 for more
│ information and instructions for recovery. Error: operation error EC2: AuthorizeSecurityGroupIngress, https response error StatusCode: 400, RequestID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, api error InvalidPermission.Duplicate: the specified rule "peer: 0.0.0.0/0, TCP, from port: 443, to port: 443, ALLOW" already exists
│ 
│   with module.allow_https.aws_security_group_rule.sgr_ingress_renamed,
│   on modules/security_group/aws_security_group_rule.tf line 1, in resource "aws_security_group_rule" "sgr_ingress_renamed":
│    1: resource "aws_security_group_rule" "sgr_ingress_renamed" {
│ 
╵
Error: Process completed with exit code 1.
```
下記の部分に注目してほしい。
`module.allow_http.aws_security_group_rule.sgr_ingress`と`module.allow_ssh.aws_security_group_rule.sgr_ingress`は、無事に破壊されて、リネームされた上で再作成されている。
一方、`module.allow_https.aws_security_group_rule.sgr_ingress` はしっかり破壊されているものの、リネームしたSGRの再作成に失敗している。
```
module.allow_http.aws_security_group_rule.sgr_ingress: Destruction complete after 0s
module.allow_ssh.aws_security_group_rule.sgr_ingress: Destruction complete after 0s
module.allow_https.aws_security_group_rule.sgr_ingress: Destruction complete after 1s
module.allow_http.aws_security_group_rule.sgr_ingress_renamed: Creation complete after 1s [id=sgrule-XXXXXXXXXX]
module.allow_ssh.aws_security_group_rule.sgr_ingress_renamed: Creation complete after 2s [id=sgrule-XXXXXXXXXX]
╷
│ Error: [WARN] A duplicate Security Group rule was found on (sg-XXXXXXXXXXXXXXX). This may b
```

## エラーの原因
エラーの原因は、リネーム前のSGR(`module.allow_https.aws_security_group_rule.sgr_ingress`)が破壊されるタイミングと、リネーム後のSGR(`module.allow_https.aws_security_group_rule.sgr_ingress_renamed`)が作成されようとしているタイミングが近すぎて、リネーム後のSGRを作成しようとした時に、リネーム前のSGRがまだ破壊されていないと判断されてしまったことである。
リネーム前のSGRとリネーム後のSGRは、名前が違うだけで内容が全く同じなので、同じSGの中に共存できないのである。なので、リネーム後のSGRの内容を少し変更すれば、このようなエラーは起きないはずである(たぶん)。

## エラーが起きるときと起きないときがある
何回か実験してみたら、まったくエラーがでないときもあれば、上記とは違って、`module.allow_http.aws_security_group_rule.sgr_ingress`を改名したSGRの作成がエラーになるときもあった。

## トラブルシューティング
状況としては、リネーム前のSGRの削除に成功して、リネーム後のSGRの作成に失敗しているので、`terraform apply`を再度実行すれば解決する。

## そもそもこのようなエラーを起こさないために
`terraform state mv`コマンドや `tfmigrate apply`コマンド(tfmigrate のインストール必須)を実施してから、`terraform apply`を実施しよう。
ちなみに、`tfmigrate` を使う場合、下記のようなマイグレーションファイルを用意して、`tfmigrate apply`を実施することになる。
``` tfmigrate/20241019212900_rename_sgr_ingress_to_sgr_ingress_renamed.hcl
migration "state" "20241019212900_rename_sgr_ingress_to_sgr_ingress_renamed" {
  actions = [
    "mv module.allow_http.aws_security_group_rule.sgr_ingress module.allow_http.aws_security_group_rule.sgr_ingress_renamed",
    "mv module.allow_https.aws_security_group_rule.sgr_ingress module.allow_https.aws_security_group_rule.sgr_ingress_renamed",
    "mv module.allow_ssh.aws_security_group_rule.sgr_ingress module.allow_ssh.aws_security_group_rule.sgr_ingress_renamed"
  ]
}
```