AWSTemplateFormatVersion: 2010-09-09 Description: > This is designed for application layer on top spot fleet. Parameters: keyPairName: Description: "Key pair name for ec2." Type: String ami: Description: "Amazon Image ID." Type: String baseVpc: Description: "VPC to launch virtual server in." Type: AWS::EC2::VPC::Id s3Dns: Description: "S3 DNS." Type: String s3cf: Description: "S3 bucket and folder where to store cloudformation templates." Type: String publicSubnet1a: Description: "Subnet to launch virtual server in." Type: AWS::EC2::Subnet::Id publicSubnet1b: Description: "Subnet to launch virtual server in." Type: AWS::EC2::Subnet::Id privateSubnet1a: Description: "Subnet to launch virtual server in." Type: AWS::EC2::Subnet::Id privateSubnet1b: Description: "Subnet to launch virtual server in." Type: AWS::EC2::Subnet::Id # ecsClusterTargetCapacity: # Description: Number of EC2 Spot instances to initially launch in the ECS cluster # Type: Number # Default: 4 instanceType: Description: EC2 instance type to use for ECS cluster Type: String AllowedValues: - c4.large - c5.large - m4.large - r4.large - r5.large Default: c4.large volSize: Description: The size of root volume for ec2. Type: Number Default: 40 ecsCluster: Description: ECS Cluster for application running. Type: String cpuTargetValue: Description: The target value for CPU metric. Type: String Default: 1 desiredCount: Description: The number of desired ec2-instance. Type: Number Default: 4 onDemandPercentage: Description: On-demand capacity percentage in the whole capacity. On-demand/Spot, example 25->25%. Type: Number Default: 25 ip4Ec2: Description: DNS of the identity provider for EC2. Type: String nodesRole: Description: Role for nodes in ECS cluster. Type: String Resources: AppsvrSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow http to shost VpcId: !Ref baseVpc SecurityGroupIngress: #ssh - IpProtocol: TCP FromPort: 22 ToPort: 22 CidrIp: '0.0.0.0/0' #linderd - IpProtocol: TCP FromPort: 4140 ToPort: 4140 CidrIp: '10.0.0.0/16' #linkerd-viz - IpProtocol: TCP FromPort: 3000 ToPort: 3000 CidrIp: '10.0.0.0/16' #linkerd-ui admin - IpProtocol: -1 CidrIp: '10.0.0.0/16' Tags: - Key: Name Value: !Sub '${AWS::StackName}-node-sg' ELBSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow tcp to shost VpcId: !Ref baseVpc SecurityGroupIngress: - IpProtocol: TCP FromPort: '8000' ToPort: '9000' CidrIp: 0.0.0.0/0 Tags: - Key: Name Value: !Sub '${AWS::StackName}-elb-sg' Ec2InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: "/" Roles: - !Ref nodesRole FleetLaunchTemplate: Type: AWS::EC2::LaunchTemplate Metadata: AWS::CloudFormation::Init: config: files: /etc/ecs/ecs.config: content: !Sub | ECS_CLUSTER=${ecsCluster} ECS_AVAILABLE_LOGGING_DRIVERS=["awslogs","fluentd"] /home/ec2-user/first-run.sh: content: !Sub | #!/bin/bash mkdir -p /home/ec2-user/cloudwatch cd /home/ec2-user/cloudwatch curl https://${s3Dns}/amazoncloudwatch-agent-${AWS::Region}/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm -o amazon-cloudwatch-agent.rpm sudo rpm -U ./amazon-cloudwatch-agent.rpm aws s3 cp s3://${s3cf}/templates/cloudwatch4ecs/amazon-cloudwatch-agent-ecs.json amazon-cloudwatch-agent-ecs.json --endpoint-url https://${s3Dns}/ sudo cp ./amazon-cloudwatch-agent-ecs.json /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json cd /opt/aws/amazon-cloudwatch-agent/etc sudo sed -i -e "s/{aws_stack_name}/${AWS::StackName}/g" amazon-cloudwatch-agent.json EC2_INSTANCE_ID=$(curl -s http://169.254.169.254/1.0/meta-data/instance-id/) sudo sed -i -e "s/{instance_id}/$EC2_INSTANCE_ID/g" amazon-cloudwatch-agent.json sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s mkdir -p /home/ec2-user/awslogs cd /home/ec2-user/awslogs aws s3 cp s3://${s3cf}/templates/awslogs/daemon.json daemon.json --endpoint-url https://${s3Dns}/ sudo cp ./daemon.json /etc/docker/daemon.json cd /etc/docker/ sudo sed -i -e "s/{aws_stack_name}/${AWS::StackName}/g" daemon.json sudo sed -i -e "s/{cluster}/${ecsCluster}/g" daemon.json sudo sed -i -e "s/{container_instance_id}/$EC2_INSTANCE_ID/g" daemon.json #docker & ecs-agent (removed due to use ecs-optimiezed AMI) # sudo amazon-linux-extras disable docker # sudo amazon-linux-extras install -y ecs # sudo systemctl enable ecs # sudo systemctl restart docker # sudo systemctl restart --no-block ecs # sudo usermod -a -G docker ec2-user # Install ssm-agent(added due to use ecs-optimiezed AMI) sudo yum install -y https://${s3Dns}/amazon-ssm-${AWS::Region}/latest/linux_amd64/amazon-ssm-agent.rpm sudo systemctl enable amazon-ssm-agent sudo systemctl start amazon-ssm-agent # # This script generates config to be used by their respective Task Definitions: # 1. consul-registrator startup script # 2. Consul Agent config # 3. linkerd config # Gather metadata for linkerd and Consul Agent EC2_INSTANCE_IP_ADDRESS=$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4) EC2_INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) # # Generate consul-registrator startup file # mkdir -p /opt/consul-registrator/bin cat << EOF > /opt/consul-registrator/bin/start.sh #!/bin/sh exec /bin/registrator -ip $EC2_INSTANCE_IP_ADDRESS -retry-attempts -1 consul://$EC2_INSTANCE_IP_ADDRESS:8500 EOF chmod a+x /opt/consul-registrator/bin/start.sh # # Generate Consul Agent config file # mkdir -p /opt/consul/data mkdir -p /opt/consul/config cat << EOF > /opt/consul/config/consul-agent.json { "advertise_addr": "$EC2_INSTANCE_IP_ADDRESS", "client_addr": "0.0.0.0", "node_name": "$EC2_INSTANCE_ID", "retry_join": [ "provider=aws tag_key=Name tag_value=l5d-demo-consul-server" ] } EOF # # Generate linkerd config file # # The linkerd ECS task definition is configured to mount this config file into # its own Docker environment. mkdir -p /etc/linkerd cat << EOF > /etc/linkerd/linkerd.yaml admin: ip: 0.0.0.0 port: 9990 namers: - kind: io.l5d.consul host: $EC2_INSTANCE_IP_ADDRESS port: 8500 telemetry: - kind: io.l5d.prometheus - kind: io.l5d.recentRequests sampleRate: 0.25 usage: orgId: linkerd-examples-ecs routers: - protocol: http label: outgoing servers: - ip: 0.0.0.0 port: 4140 interpreter: kind: default transformers: # tranform all outgoing requests to deliver to incoming linkerd port 4141 - kind: io.l5d.port port: 4141 dtab: | /svc => /#/io.l5d.consul/dc1; - protocol: http label: incoming servers: - ip: 0.0.0.0 port: 4141 interpreter: kind: default transformers: # filter instances to only include those on this host - kind: io.l5d.specificHost host: $EC2_INSTANCE_IP_ADDRESS dtab: | /svc => /#/io.l5d.consul/dc1; EOF mode: "000755" owner: "ec2-user" group: "ec2-user" commands: agent1: command: "./first-run.sh >./run.log 2>./run.log" env: STACK_NAME: !Sub '${AWS::StackName}' AWS_DEFAULT_REGION: !Sub '${AWS::Region}' ECS_CLUSTER: !Ref ecsCluster cwd: "/home/ec2-user" ignoreErrors: false Properties: LaunchTemplateData: BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: VolumeSize: !Ref volSize DeleteOnTermination: true # CapacityReservationSpecification: # CapacityReservationSpecification # CpuOptions: # CoreCount: 8 # ThreadsPerCore: 4 # CreditSpecification: # CreditSpecification # DisableApiTermination: Boolean EbsOptimized: false # ElasticGpuSpecifications: # - ElasticGpuSpecification # ElasticInferenceAccelerators: # - LaunchTemplateElasticInferenceAccelerator # HibernationOptions: # HibernationOptions IamInstanceProfile: Arn: !GetAtt Ec2InstanceProfile.Arn ImageId: !Ref ami #InstanceInitiatedShutdownBehavior: terminate #spot has to be # InstanceMarketOptions: # InstanceMarketOptions InstanceType: !Ref instanceType # KernelId: String KeyName: !Ref keyPairName # LicenseSpecifications: # - LicenseSpecification Monitoring: Enabled: true # NetworkInterfaces: # - NetworkInterface # Placement: # Placement # RamDiskId: String SecurityGroupIds: - !GetAtt AppsvrSecurityGroup.GroupId # TagSpecifications: # - ResourceType: instance # Tags: # - Key: Name # Value: !Sub '${AWS::StackName}-fleet' UserData: Fn::Base64: !Sub | #!/bin/bash set -e sudo yum update -y sudo yum install -y aws-cfn-bootstrap awscli jq wget /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource FleetLaunchTemplate --region ${AWS::Region} /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource AppsvrAutoscalingGroup --region ${AWS::Region} #sudo reboot LaunchTemplateName: !Sub '${AWS::StackName}-fleet-template' # Ec2FleetServiceLinkedRole: # Type: AWS::IAM::ServiceLinkedRole # Properties: # AWSServiceName: ec2fleet.amazonaws.com # Description: Default EC2 Fleet Service Linked Role # AutoScalingServiceLinkedRole: # Type: AWS::IAM::ServiceLinkedRole # Properties: # AWSServiceName: autoscaling.amazonaws.com # Description: Default Service-Linked Role enables access to AWS Services and Resources # used or managed by Auto Scaling AppsvrAutoscalingGroup: Type: AWS::AutoScaling::AutoScalingGroup DependsOn: - AppsvrElasticLoadBalancing - FleetLaunchTemplate # - AutoScalingServiceLinkedRole Properties: AvailabilityZones: - Fn::Select: - 0 - Fn::GetAZs: "" - Fn::Select: - 1 - Fn::GetAZs: "" Cooldown: 180 #in seconds DesiredCapacity: !Ref desiredCount # HealthCheckGracePeriod: Integer #in seconds HealthCheckType: EC2 # InstanceId: String #optional # LaunchTemplate: !Ref FleetLaunchTemplate TargetGroupARNs: - !Ref AppsvrElbTargetGroup MaxSize: 10 MinSize: !Ref desiredCount MixedInstancesPolicy: InstancesDistribution: #OnDemandAllocationStrategy: prioritized #only valid value is prioritized OnDemandBaseCapacity: 0 OnDemandPercentageAboveBaseCapacity: !Ref onDemandPercentage SpotAllocationStrategy: lowest-price #only valid value is lowest-price SpotInstancePools: 4 #SpotMaxPrice: String #maximum Spot price is set at the On-Demand price LaunchTemplate: LaunchTemplateSpecification: LaunchTemplateId: !Ref FleetLaunchTemplate Version: !GetAtt FleetLaunchTemplate.LatestVersionNumber Overrides: - InstanceType: c5.large - InstanceType: c4.large - InstanceType: m4.large - InstanceType: r5.large - InstanceType: r4.large MetricsCollection: - Granularity: "1Minute" Metrics: - "GroupMinSize" - "GroupMaxSize" - "GroupDesiredCapacity" - "GroupInServiceInstances" - "GroupPendingInstances" - "GroupStandbyInstances" - "GroupTerminatingInstances" - "GroupTotalInstances" Tags: - Key: Name Value: !Sub '${AWS::StackName}-appsvr-asg' PropagateAtLaunch: true - Key: Member Value: appserver-of-AutoScalingGroup PropagateAtLaunch: true VPCZoneIdentifier: - !Ref privateSubnet1a - !Ref privateSubnet1b UpdatePolicy: AutoScalingScheduledAction: IgnoreUnmodifiedGroupSizeProperties: 'true' AutoScalingRollingUpdate: MinInstancesInService: '1' MaxBatchSize: '2' WaitOnResourceSignals: 'true' MinSuccessfulInstancesPercent: 100 # PauseTime: PT15M CreationPolicy: ResourceSignal: Count: !Ref desiredCount Timeout: PT15M AppsvrAutoscalingPolicy: Type: AWS::AutoScaling::ScalingPolicy Properties: AdjustmentType: ChangeInCapacity AutoScalingGroupName: !Ref AppsvrAutoscalingGroup Cooldown: 600 # unit: second PolicyType: TargetTrackingScaling # ScalingAdjustment: 1 - not supported for a TargetTracking policy TargetTrackingConfiguration: PredefinedMetricSpecification: PredefinedMetricType: ASGAverageCPUUtilization TargetValue: !Ref cpuTargetValue AppsvrElasticLoadBalancing: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: IpAddressType: ipv4 Type: application Scheme: internet-facing SecurityGroups: - !Ref ELBSecurityGroup Subnets: - !Ref publicSubnet1a - !Ref publicSubnet1b Tags: - Key: Name Value: !Sub '${AWS::StackName}-elb' AppsvrElasticLoadBalancingListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - Type: forward TargetGroupArn: !Ref AppsvrElbTargetGroup LoadBalancerArn: !Ref AppsvrElasticLoadBalancing Port: 8000 Protocol: HTTP AppsvrElbTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckIntervalSeconds: 30 HealthCheckPort: traffic-port HealthCheckProtocol: HTTP HealthCheckTimeoutSeconds: 10 HealthyThresholdCount: 3 HealthCheckPath: / Port: 80 Protocol: HTTP Tags: - Key: Name Value: !Sub '${AWS::StackName}-appsvr-tg' TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: 20 TargetType: instance VpcId: !Ref baseVpc Outputs: appElbTargetGroup: Description: ELB target grpup. Value: !Ref AppsvrElbTargetGroup appElasticLoadBalancing: Description: Load Balancer for containers. Value: !Ref AppsvrElasticLoadBalancing elbDns: Description: DNS name of Load Balancer. Value: !GetAtt AppsvrElasticLoadBalancing.DNSName appsvrSecurityGroup: Description: Sercurity group for nodes. Value: !Ref AppsvrSecurityGroup