概要
AWS CloudFormationで以下のエラーに遭遇しました。
本記事ではその原因と解決方法を記載します。
Resource handler returned message: "Invalid request provided: CreateService error: The target group with targetGroupArn arn:aws:elasticloadbalancing:ap-northeast-1:1234567890:targetgroup/Sample-Web-Blue/00b3e54db86b7e99 does not have an associated load balancer.
原因は依存関係にあった
このエラーは、「LBに紐づいているターゲットグループが存在せずに、サービスが作成されているよ」ということになります。
しかし、実際のテンプレートでは、LBのリスナーもターゲットグループも作成していました。
#*******************************************************************************
# TargetGroup
# https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html
#*******************************************************************************
TargetGroupWebBlue:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
HealthCheckIntervalSeconds: 5
HealthCheckPath: "/"
HealthCheckPort: "traffic-port"
HealthCheckProtocol: "HTTP"
HealthCheckTimeoutSeconds: 2
HealthyThresholdCount: 2
UnhealthyThresholdCount: 2
Matcher:
HttpCode: "200"
Name: !Sub ${SysTag}-Web-Blue
Port: 80
Protocol: "HTTP"
VpcId: !Sub ${rVpcId}
TargetGroupAttributes:
-
Key: deregistration_delay.timeout_seconds
Value: 0
TargetGroupWebGreen:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
HealthCheckIntervalSeconds: 5
HealthCheckPath: "/"
HealthCheckPort: "traffic-port"
HealthCheckProtocol: "HTTP"
HealthCheckTimeoutSeconds: 2
HealthyThresholdCount: 2
UnhealthyThresholdCount: 2
Matcher:
HttpCode: "200"
Name: !Sub ${SysTag}-Web-Green
Port: 80
Protocol: "HTTP"
VpcId: !Sub ${rVpcId}
TargetGroupAttributes:
-
Key: deregistration_delay.timeout_seconds
Value: 0
#*******************************************************************************
# Listener
# https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-listener.html
#*******************************************************************************
HttpListenerRuleWebBlue:
Type: 'AWS::ElasticLoadBalancingV2::ListenerRule'
Properties:
Actions:
- Type: forward
ForwardConfig:
TargetGroups:
- TargetGroupArn: !Ref TargetGroupWebBlue
Weight: 1
Conditions:
- Field: host-header
Values:
- xxx.sample.com
ListenerArn: !ImportValue HttpDefaultListener-XXX
Priority: 2
HttpListenerRuleWebGreen:
Type: 'AWS::ElasticLoadBalancingV2::ListenerRule'
Properties:
Actions:
- Type: forward
ForwardConfig:
TargetGroups:
- TargetGroupArn: !Ref TargetGroupWebGreen
Weight: 1
Conditions:
- Field: host-header
Values:
- xxx.sample.com
ListenerArn: !ImportValue HttpDefaultListenerTest-XXX
Priority: 2
#*******************************************************************************
# Service
# https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-service.html
#*******************************************************************************
ServiceXXX:
Type: AWS::ECS::Service
DependsOn: HttpListenerRuleWebBlue
Properties:
Cluster: !Sub ${rEcsName}
DesiredCount: !Ref TaskDeployNumWeb
ServiceName: !Sub ${SysTag}
LaunchType: "EC2"
PlacementStrategies:
- Field: "attribute:ecs.availability-zone"
Type: spread
- Field: "instanceId"
Type: spread
DeploymentController:
Type: CODE_DEPLOY
LoadBalancers:
- TargetGroupArn: !Ref TargetGroupWebBlue
ContainerPort: 80
ContainerName: Web
TaskDefinition: !Ref TaskWeb
あれ、やっぱりちゃんと記載しているよなぁ...
おかしいなぁ...と詰まりました。
ということで、以下の記事を見つけました。
The target group does not have an associated load balancer
上記の記事では、CloudFormationがリソースを作成する際の依存関係のところで、問題が発生していると指摘しています。
つまり、今回で言えば、ServiceXXX
リソースが依存するHttpListenerRuleWebBlue
リソースが正しく作成される前に、ServiceXXX
リソースが作成されようとしていたため、このエラーが発生したと言えます。
ServiceXXX
のLoadBalancers
プロパティがTargetGroupWebBlue
を指定しているので、そのターゲットグループはロードバランサーに関連している必要があります。したがって、TargetGroupWebBlue
がロードバランサーに関連付けられていること必要があります。
エラーメッセージにあったdoes not have an associated load balancer
の意味がこれでわかりました。
CloudFormationは、リソースの作成順序を決定する際に、リソース間の依存関係を自動的に解決しようとしてくれますが、リソース間で相互に依存し合う場合や、外部リソースに依存する場合などに、依存関係の解決が難しくなることがあるようです。
まず、今回は以下のような関係でした。
・HttpListenerRuleWebBlue
はTargetGroupWebBlue
を必要としている
・HttpListenerRuleWebBlue
は、HttpDefaultListener-XXX
(外部スタックのテンプレートからインポート)を必要としている
・HttpDefaultListener-XXX
はLoadBalancer
を必要としている
・ServiceXXX
はTargetGroupWebBlue
を必要としている
ここで、おそらくCloudFormationは以下の順序で作成を試みたのでしょう(推測)。
①ふむふむ、HttpListenerRuleWebBlue
はTargetGroupWebBlue
を必要としているのか、それでは先にTargetGroupWebBlue
を作らないと。
②TargetGroupWebBlue
を作ったぞ。
③お、TargetGroupWebBlue
を必要としているリソースが他にもある。ServiceXXX
で!Ref
されているからこっち先に作っておこう。
④あれ、ServiceXXX
はHttpListenerRuleWebBlue
が必要だって。そんなリソースないぞ?
↓
結果:ServiceXXX
のターゲットグループにELBが紐づいていない、というエラーになる
解決策はDependsOn
上記記事のように、DependsOn: Listener
を追加してあげます。
これだけでOKです!
ServiceXXX:
Type: AWS::ECS::Service
DependsOn: HttpListenerRuleWebBlue # これを追加!
Properties:
DependsOn
プロパティは、CloudFormationスタック内のリソース間の依存関係を定義するために使用されます。特定のリソースが他のリソースに依存している場合、DependsOn
を使用して、依存関係を明示的に設定できるとのこと。
もう少し丁寧にすると、CloudFormationに対してServiceXXX
リソースはHttpListenerRuleWebBlue
リソースに依存しているよ、ということを明示的に伝えます。
すると、HttpListenerRuleWebBlue
リソースが正常に作成されてから ServiceXXX
リソースが作成されるように制御されます。
そして、HttpListenerRuleWebBlue
リソースの作成にはTargetGroupWebBlue
リソースが正しく関連付けられているため、エラーが解消された、というわけです。
これにより、CloudFormationはリソースを適切な順序で作成または更新することができました。
CloudFormationプロフェッショナルへの道は遠い...