出力を入力へ

プログラミングに関する自分が考えた事を中心にまとめます

TerraformでAWS ELBリスナーに登録されているターゲットグループを再生成する

やっとのことALBロードバランサをTerraform管理しようとしたら 設定変更や再生成まわりで時間を取られたのでそのまとめ。

ターゲットグループの設定変更の問題

AWSロードバランサにおけるターゲットグループがリスナーとしてロードバランサに登録されていると、 一度リスナー登録を削除しない限りターゲットグループを削除できない。 ターゲットグループの基本的な設定として、ターゲットの種類やプロトコル、ポートなどは AWSの仕様上、設定変更できないので再作成し直す必要がある。

特にTerraformでロードバランサを管理していると (もちろん基本的な設定を変更する機会というのはほとんどないのだが) ターゲットグループを変更したくても変更できず手詰まりになる。 具体的には以下のようなエラーログが出力される。

Error: Error deleting Target Group: ResourceInUse: Target group 'arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxx' is currently in use by a listener or a rule
    status code: 400, request id: 65dda169-3495-4d43-8aab-70721d2122ea

このときだけ手作業で修正するというのも1つの手ではあるけれど、 できればTerraformでもいい感じに対応したい。

ちなみに、以降の解決策は issueに記載の解決策そのままであるが ぴんとこなかったので追試した内容であり、同じ結論に辿りついている。

解決ステップ1: create_before_destroy

上記のようなリソースの依存関係により削除できないケースは ターゲットグループにも複数存在する。 こういったケースでの主な解決策は lifecycle で create_before_destroy を指定する方法である。

この挙動について、公式ドキュメント に全部記載されているのだが、 通常はリソースを削除してから作成するのだが、このオプションを有効にすることで 先にリソースを作成してから古いリソースを削除する。 具体的には以下のように設定する。

resource "aws_lb_target_group" "sample" {
  name = "sample-alb-target-group"

  port = 80
  protocol = "HTTP"
  target_type = "ip"
  vpc_id = var.vpc_id

  lifecycle {
    create_before_destroy = true
  }
}

ただし、この方法には注意点があり、これも公式ドキュメントに記載さているのだが、 同一の名前でリソースが作成できない場合に上手く動作しない。 ターゲットグループは名前がユニークである必要があるので、 プロトコルやポートを変更しようとして create_before_destroy を有効にしていても 同名のリソースが作成できないのでエラーになる。

Error: Error creating LB Target Group: DuplicateTargetGroupName: A target group with the same name 'sample-alb-target-group' exists, but with different settings
    status code: 400, request id: 29d9468f-6665-4b7f-b404-3f12ca0fb8b3

解決ステップ2: name_prefixを利用する

ではこのような課題にどう対処すればよいかというと、 名前の先頭だけ指定し、末尾に名前がユニークになるようなランダムな文字列を付与すればよい。 terraformがこのような動作をサポートしており、nameの替わりにname_prefixを指定することで 勝手に名前がユニークとなることが保証される。

resource "aws_lb_target_group" "sample" {
  name_prefix = "sample-alb-target-group"

  port = 80
  protocol = "HTTP"
  target_type = "ip"
  vpc_id = var.vpc_id

  lifecycle {
    create_before_destroy = true
  }
}

しかし、この方法では ターゲットグループの場合のみ上手くいかない場合があり、 それは name_prefix は6文字以下にする必要があるという制限である。 これは ドキュメント にも記載がある。

"name_prefix" cannot be longer than 6 characters

Cannot be longer than 6 characters.

しかも、ターゲットグループには descriptionのように説明を記載する項目がないので、 名前を6文字に制限するとそのターゲットグループが何なのか一目で判断するのが難しくなる。 そのためのterraformコード管理ではあるのだが、できればAWSリソースの名前でも目的等が 上手く識別できるように命名したい。

実際、GitHubでも名前が6文字以下では役に立たないよね、という趣旨のissueが登録されている。

解決ステップ3: 自前でname_prefix相当機能を実装する

name_prefix では解決できないとわかったので、 自前で name_prefix 相当の機能を実装することで回避する。 具体的には 名前の末尾にランダムな文字列を付与する。

resource "aws_lb_target_group" "sample" {
  name = "sample-alb-target-group-${substr(uuid(), 0, 6)}"

  port = 80
  protocol = "HTTP"
  target_type = "ip"
  vpc_id = var.vpc_id

  lifecycle {
    create_before_destroy = true
  }
}

上記の例ではUUIDで生成した文字列の先頭6文字をターゲットグループの名前の末尾に追記している。 もちろん、このランダムな文字列6文字が偶然重複するとエラーになるのだが、 その可能性はかなり低いし心配なら文字数を増やせばよい。 AWSの仕様上、名前は最大32文字までらしいので、 32文字になる最大の文字数だけ付与すればよい (上記例では sample-alb-target-group- が24文字なので8文字のランダムな文字列を付与できる)。

なお、名前にランダムな文字列を付与しようとすると terraform planを実行する度に差分が発生してしまうので、 この差分は無視する必要がある。 具体的には、lifecycleで名前を ignore_changes に追加すればよい。

resource "aws_lb_target_group" "sample" {
  name = "sample-alb-target-group-${substr(uuid(), 0, 6)}"

  port = 80
  protocol = "HTTP"
  target_type = "ip"
  vpc_id = var.vpc_id

  lifecycle {
    create_before_destroy = true
    ignore_changes = [name]
  }
}

以上により、ターゲットグループがリスナーとして登録されている場合に 設定変更で再生成が必要でも上手く対処できるようになった。