Search code examples
amazon-web-servicesyamlcloudaws-cloudformation

Crcular Dependency in Cloud Formation


I'm stuck at circular dependency loop in Cfn (ECS Stack), This can be easily resolve by segregating resources in different stack, but challenge is to resolve it within same/single stack. Spent a night solving it, still not getting any close to resolve it. After a lot of debugging finally though of seeking some help, let me know if anyone can assist me in this, I'd really appreciate any leads or help. `

AWSTemplateFormatVersion: '2010-09-09'
Description: This stack will deploy following resources , May 
# Metadata:
Parameters:
  VPC:
    Description: Select One VPC available in your existing account
    Type: AWS::EC2::VPC::Id
  PubSubnets:
    Type: 'List<AWS::EC2::Subnet::Id>'
    Description: The list of PubSubnetIds in selected VPC)
  PvtSubnets:
    Type: 'List<AWS::EC2::Subnet::Id>'
    Description: The list of PvtSubnetIds in selected VPC)
  ClientName:
    Type: String
    Default: test

# Mappings: 

# Conditions: 

Resources:
  ELBTargetGroup:
   Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
  #  DependsOn:
  #   - ElasticLoadBalancer
   Properties:
     Name: "ELB-TG"
     HealthCheckIntervalSeconds: 6
     HealthCheckTimeoutSeconds: 5
     HealthyThresholdCount: 2
     Port: 80
     Protocol: HTTP
     UnhealthyThresholdCount: 2
     VpcId: !Ref VPC
     TargetType: instance
  
  ELBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: "ELBTraffic"
      GroupDescription: "Enable HTTP access on the inbound port for ELB"
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: ELBSecurityGroup
  
  ElasticLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    # DependsOn:
    #   - ELBSecurityGroup
    Properties:
      Subnets: 
      - !Ref PubSubnets
      SecurityGroups: 
        - !Ref ELBSecurityGroup
  
  ElbListener:
   Type: 'AWS::ElasticLoadBalancingV2::Listener'
  #  DependsOn:
  #   - ElasticLoadBalancer
   Properties:
     DefaultActions:
       - Type: forward
         TargetGroupArn: !Ref ELBTargetGroup
     LoadBalancerArn: !Ref ElasticLoadBalancer
     Port: 80
     Protocol: HTTP
  
  AsgConfig:
    Type: AWS::EC2::LaunchTemplate
    DependsOn:
      - ELBSecurityGroup
    Properties:
      LaunchTemplateName: !Sub ${ClientName}-launch-template
      LaunchTemplateData:
        ImageId: ami-0171959e760b38d59
        InstanceType: t3.medium
        SecurityGroups:
        - !Ref ELBSecurityGroup
        #ImageId: "ami-0171959e760b38d59"
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash -xe
            echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config
            sudo yum install -y python-pip pip
            yum install -y aws-cfn-bootstrap
            /opt/aws/apitools/cfn-init-2.0-6/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource AsGroup --region ${AWS::Region}
            /opt/aws/apitools/cfn-init-2.0-6/bin/cfn-init -v --stack ${AWS::StackName} --resource AsgConfig --region ${AWS::Region} -c default
  
  AsGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    DependsOn:
      - AsgConfig
    Properties:
      VPCZoneIdentifier: 
        - !Ref PvtSubnets
      LaunchTemplate:
        LaunchTemplateId: !Ref AsgConfig
        Version: !GetAtt AsgConfig.LatestVersionNumber
      # LaunchConfigurationName: !Ref AsgConfig
      MinSize: '1'
      DesiredCapacity: '2'
      MaxSize: '4'
      TargetGroupARNs:
        - !Ref ELBTargetGroup
  
  CapacityProvider:
    Type: AWS::ECS::CapacityProvider
    DependsOn:
      - AsGroup
    Properties:
      AutoScalingGroupProvider:
        AutoScalingGroupArn: !Ref AsGroup 
  
  CodeCommitRepository1:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryDescription: "HTML code"
      RepositoryName: "HTML_code"
  CodeCommitRepository2:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryDescription: "Python code"
      RepositoryName: "Python_code"
  CodeCommitRepository3:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryDescription: "Node code"
      RepositoryName: "Node_code"
  
  ECRrepo:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: "cfn_repo"
  
  ECSCluster:
    Type: AWS::ECS::Cluster
    DependsOn:
      - CapacityProvider
    Properties:
      CapacityProviders:
        - !Ref CapacityProvider
      ClusterName: !Ref ClientName
      Configuration:
       ExecuteCommandConfiguration:
          Logging: DEFAULT
      ClusterSettings:
        - Name: containerInsights
          Value: enabled
  
  ECSServiceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "ecs-service-role"
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole
  
  ExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "ecs-execution-role"
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
  
  ContainerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: "ContainerSecurityGroup"
      GroupDescription: "Security group for container"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
  Service:
    Type: AWS::ECS::Service
    DependsOn:
      - ECSCluster
      - ElasticLoadBalancer
      # - ECSServiceRole
      # - TaskDefinition
      - ELBTargetGroup
    Properties:
      Cluster: !Ref ECSCluster
      Role: !Ref ECSServiceRole
      DesiredCount: 1
      TaskDefinition: !Ref TaskDefinition
      LaunchType: EC2
      LoadBalancers:
        - ContainerName: "deployment-container"
          ContainerPort: 80
          TargetGroupArn: !Ref ELBTargetGroup
  
  AutoScalingRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: service-auto-scaling-role
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: [application-autoscaling.amazonaws.com]
            Action: ["sts:AssumeRole"]
      Policies:
        - PolicyName: service-auto-scaling-policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ecs:DescribeServices
                  - ecs:UpdateService
                  - cloudwatch:PutMetricAlarm
                  - cloudwatch:DescribeAlarms
                  - cloudwatch:DeleteAlarms
                Resource:
                  - "*"

  ScalableTarget:
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    # DependsOn:
    #   - 
    Properties:
      RoleARN: !GetAtt AutoScalingRole.Arn
      ResourceId: !Sub service/${ClientName}/Service
      ServiceNamespace: ecs
      ScalableDimension: ecs:Service:DesiredCount
      MinCapacity: 1
      MaxCapacity: 5
  
  ScalingPolicy:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    DependsOn:
      - ScalableTarget
    Properties:
      PolicyName: service-auto-scaling-policy
      PolicyType: TargetTrackingScaling
      ScalingTargetId: !Ref ScalableTarget
      TargetTrackingScalingPolicyConfiguration:
        PredefinedMetricSpecification:
          PredefinedMetricType: ECSServiceAverageCPUUtilization
        TargetValue: 80.0

  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    # DependsOn:
    #   - ExecutionRole
      # - Service
    Properties:
      Family: deployment-task
      Cpu: "256"
      Memory: "512"
      NetworkMode: bridge
      ExecutionRoleArn: !Ref ExecutionRole
      ContainerDefinitions:
        - Name: deployment-container
          Image: cfn_repo/cfnrepo:latest
          PortMappings:
            - ContainerPort: 80      
      RequiresCompatibilities:
        - EC2
       
# Outputs:
#   outputELBTargetGroup:
#     Description: A reference to the created Target Group
#     Value: !Ref ELBTargetGroup
#   outputELBSecurityGroup:
#     Description: A reference to the created Security Group
#     Value: !Ref ELBSecurityGroup
#   outputElasticLoadBalancer:
#     Description: A reference to the created Elastic Load Balancer
#     Value: !Ref ElasticLoadBalancer
#   outputElasticListener:
#     Description: A reference to the created Elastic Load Balancer Listener
#     Value: !Ref ElbListener
#   outputAsgConfig: 
#     Description: Id for autoscaling launch configuration
#     Value: !Ref AsgConfig
#   outputAsgGroup: 
#     Description: Id for autoscaling group
#     Value: !Ref AsgGroup 
#   outputECSCluster: 
#     Description: Cluster name
#     Value: !Ref ECSCluster

`

Erorr on console


Solution

  • You have a circular dependency, as follows:

    1. AsGroup depends on AsgConfig
    2. AsgConfig depends on ECSCluster because of echo ECS_CLUSTER=${ECSCluster} in user data
    3. ECSCluster depends on CapacityProvider
    4. CapacityProvider depends on AsGroup (which is #1 above)

    I suggest that instead of configuring the ECSCluster with the CapacityProvider, you simply create the ECSCluster without a capacity provider (and without the DependsOn) and add a later AWS::ECS::ClusterCapacityProviderAssociations to associate the CapacityProvider with the ECSCluster.

    For example (note that I have not tested this so some tweaks may be required):

    CapacityProvider:
      Type: AWS::ECS::CapacityProvider
      DependsOn:
        - AsGroup
      Properties:
        AutoScalingGroupProvider:
          AutoScalingGroupArn: !Ref AsGroup 
    
    ECSCluster:
      Type: AWS::ECS::Cluster
      Properties:
        ClusterName: !Ref ClientName
        Configuration:
          ExecuteCommandConfiguration:
            Logging: DEFAULT
        ClusterSettings:
          - Name: containerInsights
            Value: enabled
    
    ClusterCapacityProviderAssociation:
      Type: AWS::ECS::ClusterCapacityProviderAssociations
      Properties: 
        CapacityProviders: 
          - !Ref CapacityProvider
        Cluster: ECSCluster
        DefaultCapacityProviderStrategy: 
          - Base: 0
            Weight: 1
            CapacityProvider: !Ref CapacityProvider