blob: 569122e153c956786f71dc60424a9ac7b089a977 [file] [log] [blame]
AWSTemplateFormatVersion: '2010-09-09'
Description: A stack for deploying containerized applications onto a cluster of EC2
hosts using Elastic Container Service. This stack runs containers on
hosts that are in a public VPC subnet.
Parameters:
TemplateBucketName:
Description: S3 bucket containing cloudformation templates
Type: String
DesiredCapacity:
Type: Number
Default: '1'
Description: Number of EC2 instances to launch in your ECS cluster.
MaxSize:
Type: Number
Default: '6'
Description: Maximum number of EC2 instances that can be launched in your ECS cluster.
ECSAMI:
Description: AMI ID
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id
InstanceType:
Description: EC2 instance type
Type: String
Default: m4.large
AllowedValues: [t2.micro, t2.small, t2.medium, t2.large, m3.medium, m3.large,
m3.xlarge, m3.2xlarge, m4.large, m4.xlarge, m4.2xlarge, m4.4xlarge, m4.10xlarge,
c4.large, c4.xlarge, c4.2xlarge, c4.4xlarge, c4.8xlarge, c3.large, c3.xlarge,
c3.2xlarge, c3.4xlarge, c3.8xlarge, r3.large, r3.xlarge, r3.2xlarge, r3.4xlarge,
r3.8xlarge, i2.xlarge, i2.2xlarge, i2.4xlarge, i2.8xlarge]
ConstraintDescription: Please choose a valid instance type.
InternetGatewayIdProp:
Type: String
Default: ""
Description: Internet Gateway id. If empty Internet Gateway will be created
VPCIdProp:
Type: String
Default: ""
Description: VPC id. If empty VPC will be created
Subnet1IdProp:
Type: String
Default: ""
Description: ID of Subnet 1. If empty Network Stack will be created
Subnet1CIDR:
Type: String
Default: 10.0.0.0/24
Description: Subnet 1 CIDR.
Subnet1AZProp:
Type: String
Default: ""
Description: The Availability Zone of subnet1
Subnet2IdProp:
Type: String
Default: ""
Description: ID of Subnet 2. If empty Network Stack will be created
Subnet2CIDR:
Type: String
Default: 10.0.32.0/24
Description: Subnet 2 CIDR.
Subnet2AZProp:
Type: String
Default: ""
Description: The Availability Zone of subnet2
VPCCIDR:
Type: String
Default: 10.0.0.0/16
Description: VPC CIDR.
ECSKeyName:
Type: String
Default: gerrit-cluster-keys
Description: EC2 key pair name the cluter's instances
EnvironmentName:
Description: An environment name that will be prefixed to resource names
Type: String
Default: test
GerritVolumeId:
Description: Existing Gerrit volume id
Type: String
Default: ""
GerritVolumeSnapshotId:
Description: Id of the EBS snapshot for Gerrit volume
Type: String
Default: ""
GerritVolumeAttachMaxRetries:
Description: Maximum number of retries when attaching Gerrit Volume
Type: Number
Default: 5
GerritVolumeAttachRetryDelay:
Description: The delay in seconds between Gerrit Volume attach attempts
Type: Number
Default: 5
GerritVolumeSizeInGiB:
Description: Gerrit volume size in GiB
Type: Number
Default: 10
Resources:
# ECS Resources
ECSCluster:
Type: AWS::ECS::Cluster
EcsHostSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the ECS hosts that run containers
VpcId: !GetAtt ECSTaskNetworkStack.Outputs.VPCRef
SecurityGroupIngress:
# Allow access to NLB from anywhere on the internet
- CidrIp: 0.0.0.0/0
IpProtocol: -1
CloudWatchLogsGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Ref AWS::StackName
RetentionInDays: 14
# Autoscaling group. This launches the actual EC2 instances that will register
# themselves as members of the cluster, and run the docker containers.
ECSAutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
VPCZoneIdentifier:
- !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef
LaunchTemplate:
LaunchTemplateId: !Ref 'GerritLaunchTemplate'
Version: !GetAtt GerritLaunchTemplate.DefaultVersionNumber
MinSize: '1'
MaxSize: !Ref 'MaxSize'
DesiredCapacity: !Ref 'DesiredCapacity'
CreationPolicy:
ResourceSignal:
Timeout: PT15M
UpdatePolicy:
AutoScalingReplacingUpdate:
WillReplace: 'true'
GerritLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: !Sub ${AWS::StackName}-lt-gerrit
LaunchTemplateData:
ImageId: !Ref 'ECSAMI'
SecurityGroupIds:
- !GetAtt EcsHostSecurityGroup.GroupId
InstanceType: !Ref 'InstanceType'
IamInstanceProfile:
Arn: !GetAtt
- EC2InstanceProfile
- Arn
KeyName: !Ref ECSKeyName
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config
echo ECS_INSTANCE_ATTRIBUTES={\"target_group\":\"primary\"} >> /etc/ecs/ecs.config
# Make sure latest version of the helper scripts are installed as per recommendation:
# https://github.com/awsdocs/aws-cloudformation-user-guide/blob/master/doc_source/cfn-helper-scripts-reference.md#using-the-latest-version
yum install -y aws-cfn-bootstrap aws-cli xfsprogs
EC2_INSTANCE_ID=`curl -s http://169.254.169.254/latest/meta-data/instance-id`
VOLUME_ID=${GerritVolumeStack.Outputs.GerritVolumeRef}
echo "Volume Id: $VOLUME_ID"
aws ec2 attach-volume --region ${AWS::Region} --volume-id $VOLUME_ID --instance-id $EC2_INSTANCE_ID --device /dev/xvdg
for i in $(seq 1 ${GerritVolumeAttachMaxRetries}); do
echo "Waiting for volume $VOLUME_ID to be attached to the instace $EC2_INSTANCE_ID"
volumeStatus=`aws ec2 describe-volumes --region ${AWS::Region} --volume-ids $VOLUME_ID`
if [[ $volumeStatus =~ "\"State\": \"attached\"" ]]; then
echo "Volume $VOLUME_ID attached to the instace $EC2_INSTANCE_ID"
break
elif [[ "$i" -eq "${GerritVolumeAttachMaxRetries}" ]]; then
echo "Could not attach the volume $VOLUME_ID to the instace $EC2_INSTANCE_ID"
exit 1
fi
sleep ${GerritVolumeAttachRetryDelay}
done
if [[ "${GerritVolumeId}" = "" && "${GerritVolumeSnapshotId}" = "" ]]; then
echo "Create file system for Gerrit volume"
mkfs -t xfs /dev/xvdg
fi
mkdir /gerrit-mount-point
mount /dev/xvdg /gerrit-mount-point
if [[ "${GerritVolumeId}" = "" && "${GerritVolumeSnapshotId}" = "" ]]; then
echo "Create Gerrit directories"
mkdir -p /gerrit-mount-point/gerrit-logs \
/gerrit-mount-point/gerrit-cache \
/gerrit-mount-point/gerrit-data \
/gerrit-mount-point/gerrit-git \
/gerrit-mount-point/gerrit-index \
/gerrit-mount-point/gerrit-db
fi
chown 1000:1000 -R /gerrit-mount-point
# Get the CloudWatch Logs agent
echo -e "
{\"logs\":
{\"logs_collected\":
{\"files\":
{\"collect_list\":
[
{\"file_path\": \"/gerrit-mount-point/gerrit-logs/httpd_log\",
\"log_group_name\": \"${AWS::StackName}\",
\"log_stream_name\": \"${EnvironmentName}/{instance_id}/httpd_log\",
\"timezone\": \"UTC\"
},
{\"file_path\": \"/gerrit-mount-point/gerrit-logs/sshd_log\",
\"log_group_name\": \"${AWS::StackName}\",
\"log_stream_name\": \"${EnvironmentName}/{instance_id}/sshd_log\",
\"timezone\": \"UTC\"
},
{\"file_path\": \"/gerrit-mount-point/gerrit-logs/gc_log\",
\"log_group_name\": \"${AWS::StackName}\",
\"log_stream_name\": \"${EnvironmentName}/{instance_id}/gc_log\",
\"timezone\": \"UTC\"
},
{\"file_path\": \"/gerrit-mount-point/gerrit-logs/error_log\",
\"log_group_name\": \"${AWS::StackName}\",
\"log_stream_name\": \"${EnvironmentName}/{instance_id}/error_log\",
\"timezone\": \"UTC\"
},
{\"file_path\": \"/gerrit-mount-point/gerrit-logs/audit_log\",
\"log_group_name\": \"${AWS::StackName}\",
\"log_stream_name\": \"${EnvironmentName}/{instance_id}/audit_log\",
\"timezone\": \"UTC\"
}
]
}
}
}
}" >> /home/ec2-user/gerritlogsaccess.json
# Install the CloudWatch Logs agent
yum install -y wget
wget https://s3.amazonaws.com/amazoncloudwatch-agent/centos/amd64/latest/amazon-cloudwatch-agent.rpm
rpm -U ./amazon-cloudwatch-agent.rpm
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/home/ec2-user/gerritlogsaccess.json -s
# Signal to CloudFormation aws-cfn-bootstrap has been correctly updated
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region}
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles: [!Ref 'EC2Role']
# Role for the EC2 hosts. This allows the ECS agent on the EC2 hosts
# to communciate with the ECS control plane, as well as download the docker
# images from ECR to run on your host.
EC2Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ec2.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
Policies:
- PolicyName: ecs-service
PolicyDocument:
Statement:
- Effect: Allow
Action:
- 'ecs:CreateCluster'
- 'ecs:DeregisterContainerInstance'
- 'ecs:DiscoverPollEndpoint'
- 'ecs:Poll'
- 'ecs:RegisterContainerInstance'
- 'ecs:StartTelemetrySession'
- 'ecs:Submit*'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
- 'ecr:GetAuthorizationToken'
- 'ecr:BatchGetImage'
- 'ecr:GetDownloadUrlForLayer'
- 'ec2:AttachVolume'
- 'ec2:DescribeVolumes'
Resource: '*'
ECSTaskNetworkStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Join [ '', ['https://', !Ref TemplateBucketName, '.s3.amazonaws.com/cf-gerrit-network-stack.yml'] ]
TimeoutInMinutes: '25'
Parameters:
InternetGatewayIdProp: !Ref 'InternetGatewayIdProp'
VPCIdProp: !Ref 'VPCIdProp'
VPCCIDR: !Ref 'VPCCIDR'
Subnet1IdProp: !Ref 'Subnet1IdProp'
Subnet1CIDR: !Ref 'Subnet1CIDR'
Subnet1AZProp: !Ref 'Subnet1AZProp'
Subnet2IdProp: !Ref 'Subnet2IdProp'
Subnet2CIDR: !Ref 'Subnet2CIDR'
Subnet2AZProp: !Ref 'Subnet2AZProp'
GerritVolumeStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Join [ '', ['https://', !Ref TemplateBucketName, '.s3.amazonaws.com/cf-gerrit-volume.yml'] ]
TimeoutInMinutes: '25'
Parameters:
GerritVolumeId: !Ref 'GerritVolumeId'
GerritVolumeSnapshotId: !Ref 'GerritVolumeSnapshotId'
GerritVolumeSizeInGiB: !Ref 'GerritVolumeSizeInGiB'
AvailabilityZone: !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneAZ
Outputs:
ClusterName:
Description: The name of the ECS cluster
Value: !Ref 'ECSCluster'
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ClusterName' ] ]
VPCId:
Description: The ID of the VPC that this stack is deployed in
Value: !GetAtt ECSTaskNetworkStack.Outputs.VPCRef
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'VPCId' ] ]
PublicSubnetOne:
Description: Public subnet one
Value: !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneRef
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOne' ] ]
PublicSubnetOneAZ:
Description: Public subnet one AZ
Value: !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetOneAZ
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOneAZ' ] ]
PublicSubnetTwo:
Description: Public subnet two
Value: !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoRef
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetTwo' ] ]
PublicSubnetTwoAZ:
Description: Public subnet two AZ
Value: !GetAtt ECSTaskNetworkStack.Outputs.PublicSubnetTwoAZ
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetTwoAZ' ] ]
ClusterArn:
Description: The ARN of the ECS cluster
Value: !GetAtt ECSCluster.Arn
Export:
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ClusterArn' ] ]