1
votes

I’m creating a generic stack template using CloudFormation, and I’ve hit a rather annoying circular reference.

Overall Requirements:

  1. I want to be able to provision (a lot of other things, but mainly) an ECS Cluster Service that auto-scales using capacity providers, the capacity providers are using auto-scaling groups, and the auto scaling groups are using a launch template.

  2. I don’t want static resource names. This causes issues if a resource has to be re-created due to an update and that particular resource has to have a unique name.

Problem:

Without the launch template “knowing the cluster name” (via UserData) the service tasks get stuck in a PROVISIONING state.

So we have the first dependency chain:

Launch Template <- Cluster (Name)

But the Cluster has a dependency chain of:

Cluster <- Capacity Provider <- AutoScalingGroup <- Launch Template

Thus, we have a circular reference: Cluster <-> Launch Template

——

One way I can think of resolving this is to add a suffix to another resource’s name (one that lives outside of this dependency chain, e.g., the target group) as the Cluster’s name; in that way, it is not static but also removes the circular reference.

My question is: is there a better way?

It feels like there should be a resource that the cluster can subscribe to and the ec2 instance can publish to, which would remove the circular dependency as well as the need to assign resource names.

2

2 Answers

2
votes

There is no such resource to break the dependency and the cluster name must be pre-defined. This has already been recognized as a problem and its part of open github issue:

One of the issues noted is:

Break circular dependency so that unnamed clusters can be created

At the moment one work around noted is to partially predefine the name, e.g.:

ECSCluster:
  Type: AWS::ECS::Cluster
  Properties:
    ClusterName: !Sub ${AWS::StackName}-ECSCluster

LaunchConfiguration:
  Type: AWS::AutoScaling::LaunchConfiguration
  Properties:
    UserData:
      Fn::Base64: !Sub |
          #!/bin/bash
          echo ECS_CLUSTER=${AWS::StackName}-ECSCluster >> /etc/ecs/ecs.config

Alternatively, one could try to solve that by development of some custom resource that would be in the form of a lambda function. So you could probably create your unnamed cluster with launch template (LT) that has some dummy name for cluster. Then once the cluster is running, you would use the custom resource to create new version of LT with updated cluster name and refresh your auto-scaling group to use the new LT version. But I'm not sure if this would work. Nevertheless, its something that can be considered at least.

1
votes

Sharing an update from the GitHub issue. The circular dependency has been broken by introducing a new resource: Cluster Capacity Provider Associations.

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-clustercapacityproviderassociations.html

To use it in my example, you:

  1. Create Cluster (without specifying name)
  2. Create Launch Template (using Ref to get cluster name)
  3. Create Auto Scaling Group(s)
  4. Create Capacity Provider(s)
  5. Create Cluster Capacity Provider Associations <- This is new!

The one gotcha is that you have to wait for the new association to be created before you can create a service on the cluster. So be sure that your service "DependsOn" these associations!