Description: "Ufo ECS stack <%= @stack_name %>" Parameters: # required Vpc: Description: Existing vpc id Type: AWS::EC2::VPC::Id ElbSubnets: Description: Existing subnet ids for ELB Type: List EcsSubnets: Description: Existing subnet ids for ECS Type: List EcsSecurityGroups: Description: Existing ecs security group ids Type: String Default: '' ElbSecurityGroups: Description: Existing elb security group ids. List with commas. Type: String Default: '' ElbTargetGroup: Description: Existing target group Type: String Default: '' # when blank the automatically created TargetGroup is used CreateElb: Description: Create elb Type: String Default: true EcsDesiredCount: Description: Ecs desired count Type: String Default: 1 EcsTaskDefinition: Description: Ecs task definition arn Type: String # Using to keep state ElbEipIds: Description: ELB EIP Allocation ids to use for network load balancer Type: String Default: '' EcsSchedulingStrategy: Description: The scheduling strategy to use for the service Type: String Default: 'REPLICA' Conditions: CreateElbIsTrue: !Equals [ !Ref CreateElb, true ] ElbTargetGroupIsBlank: !Equals [ !Ref ElbTargetGroup, '' ] CreateTargetGroupIsTrue: !And - !Condition CreateElbIsTrue - !Condition ElbTargetGroupIsBlank ElbSecurityGroupsIsBlank: !Equals [ !Ref ElbSecurityGroups, '' ] EcsSecurityGroupsIsBlank: !Equals [ !Ref EcsSecurityGroups, '' ] EcsDesiredCountIsBlank: !Equals [ !Ref EcsDesiredCount, '' ] Resources: Elb: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Condition: CreateElbIsTrue Properties: <% if ENV['UFO_FORCE_ELB'] -%> # Error: SetSubnets is not supported for load balancers of type 'network' # Happens: When changing subnets for an ELB # Solution: Rename the ELB to force a replacement of it <% random = (0...3).map { (65 + rand(26)).chr }.join.downcase %> Name: <%= "#{@stack_name}-#{random}" %> <% end -%> Type: <%= @elb_type %> Tags: - Key: Name Value: <%= @stack_name %> <% if @elb_type == "application" -%> # Add additional extra security groups if parameters set SecurityGroups: !Split - ',' - !If - ElbSecurityGroupsIsBlank - !Ref ElbSecurityGroup - !Join [',', [!Ref ElbSecurityGroups, !Ref ElbSecurityGroup]] <% end -%> <% if @elb_type == "network" && @subnet_mappings && !@subnet_mappings.empty? -%> SubnetMappings: <% @subnet_mappings.each do |allocation_id, subnet_id| -%> - AllocationId: <%= allocation_id %> SubnetId: <%= subnet_id %> <% end -%> <% else -%> Subnets: !Ref ElbSubnets <% end -%> <%= custom_properties(:Elb) %> TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Condition: CreateTargetGroupIsTrue Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: <%= @stack_name %> <% if ENV['UFO_FORCE_TARGET_GROUP'] -%> # When adding and removing EIPs # Error: TargetGroup cannot be associated with more than one load balancer # Solution: https://forums.aws.amazon.com/thread.jspa?threadID=254544 # Note: we truncate the stack name because target group names can be only 32 chars long Name: !Join - '-' - - <%= @stack_name[0..-6] %> - !Select [ 2, !Split [ '-', !GetAtt Elb.LoadBalancerName]] <% end -%> Protocol: <%= @default_target_group_protocol %> <% if @container[:network_mode] == "awsvpc" -%> TargetType: ip <% end -%> <% if @elb_type == "network" && @network_mode == "awsvpc" -%> # target groups with network load balancers need to check the container # port dirtectly and will be using HealthCheckPort: <%= @container[:port] %> <% end -%> <%= custom_properties(:TargetGroup) %> Listener: Type: AWS::ElasticLoadBalancingV2::Listener Condition: CreateElbIsTrue Properties: DefaultActions: - Type: forward TargetGroupArn: !If [ElbTargetGroupIsBlank, !Ref TargetGroup, !Ref ElbTargetGroup] LoadBalancerArn: !Ref Elb Protocol: <%= @default_listener_protocol %> <%= custom_properties(:Listener) %> <% if @create_listener_ssl -%> ListenerSsl: Type: AWS::ElasticLoadBalancingV2::Listener Condition: CreateElbIsTrue Properties: DefaultActions: - Type: forward TargetGroupArn: !If [ElbTargetGroupIsBlank, !Ref TargetGroup, !Ref ElbTargetGroup] LoadBalancerArn: !Ref Elb Protocol: <%= @default_listener_ssl_protocol %> <%= custom_properties(:ListenerSsl) %> <% end -%> <% if @elb_type == "application" -%> ElbSecurityGroup: Type: AWS::EC2::SecurityGroup Condition: CreateElbIsTrue Properties: GroupDescription: Allow http to client host VpcId: !Ref Vpc SecurityGroupIngress: - IpProtocol: tcp FromPort: '<%= cfn[:listener][:port] %>' ToPort: '<%= cfn[:listener][:port] %>' CidrIp: 0.0.0.0/0 <% if @create_listener_ssl -%> - IpProtocol: tcp FromPort: '<%= cfn[:listener_ssl][:port] %>' ToPort: '<%= cfn[:listener_ssl][:port] %>' CidrIp: 0.0.0.0/0 <% end -%> SecurityGroupEgress: - IpProtocol: tcp FromPort: '0' ToPort: '65535' CidrIp: 0.0.0.0/0 Tags: - Key: Name Value: <%= @stack_name %>-elb <%= custom_properties(:ElbSecurityGroup) %> <% end -%> Ecs: Type: AWS::ECS::Service <% if @create_elb -%> DependsOn: Listener <% end -%> Properties: Cluster: <%= @cluster %> DesiredCount: !If - EcsDesiredCountIsBlank - !Ref AWS::NoValue - !Ref EcsDesiredCount TaskDefinition: !Ref EcsTaskDefinition <% if pretty_name? -%> ServiceName: <%= @stack_name %> <% end -%> <% if @container[:fargate] -%> LaunchType: FARGATE <% end -%> <% if @container[:network_mode] == "awsvpc" -%> NetworkConfiguration: AwsvpcConfiguration: Subnets: !Ref EcsSubnets # required SecurityGroups: !Split - ',' - !If - EcsSecurityGroupsIsBlank - !Ref EcsSecurityGroup - !Join [',', [!Ref EcsSecurityGroups, !Ref EcsSecurityGroup]] <% if @container[:fargate] -%> AssignPublicIp: ENABLED # Works with fargate but doesnt seem to work with non-fargate <% end -%> <% end -%> # Default to port 80 to get template to validate. For worker processes # there is no actual port used. LoadBalancers: !If - CreateTargetGroupIsTrue - - ContainerName: <%= @container[:name] %> ContainerPort: <%= @container[:port] || 80 %> TargetGroupArn: !Ref TargetGroup - !If - ElbTargetGroupIsBlank - [] - - ContainerName: <%= @container[:name] %> ContainerPort: <%= @container[:port] || 80 %> TargetGroupArn: !Ref ElbTargetGroup SchedulingStrategy: !Ref EcsSchedulingStrategy <%= custom_properties(:Ecs) %> EcsSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow http to client host VpcId: !Ref Vpc <% if @elb_type == "network" -%> SecurityGroupIngress: - IpProtocol: tcp FromPort: '<%= @container[:port] %>' ToPort: '<%= @container[:port] %>' CidrIp: 0.0.0.0/0 Description: docker ephemeral port range for network elb <% end -%> # Outbound access: instance needs access to internet to pull down image # or else get CannotPullContainerError SecurityGroupEgress: - IpProtocol: tcp FromPort: '0' ToPort: '65535' CidrIp: 0.0.0.0/0 Description: outbound traffic Tags: - Key: Name Value: <%= @stack_name %> <%= custom_properties(:EcsSecurityGroup) %> <% if @elb_type == "application" -%> # Allow all traffic from ELB SG to ECS SG EcsSecurityGroupRule: Type: AWS::EC2::SecurityGroupIngress Condition: CreateElbIsTrue Properties: IpProtocol: tcp FromPort: '0' ToPort: '65535' SourceSecurityGroupId: !GetAtt ElbSecurityGroup.GroupId GroupId: !GetAtt EcsSecurityGroup.GroupId Description: application elb access to ecs <%= custom_properties(:EcsSecurityGroupRule) %> <% end -%> <% if @create_route53 -%> Dns: Type: AWS::Route53::RecordSet Properties: Comment: cname to load balancer Type: CNAME TTL: '60' # ttl has special casing ResourceRecords: - !GetAtt Elb.DNSName <%= custom_properties(:Dns) %> <% end -%> Outputs: ElbDns: Description: Elb Dns Condition: CreateElbIsTrue Value: !GetAtt Elb.DNSName <% if @create_route53 -%> Route53Dns: Description: Route53 Dns Value: !Ref Dns <% end -%>