| 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' ] ] |