CF template for single Gerrit master in ECS
CF to deploy a single Gerrit master with ECS.
The configuration is currently using the standard Gerrit Docker image
with default configuration.
We still need to:
- define an entry in Route53 with the URL for the Gerrit cluster
- persist volumes with the repositories content
- forward logs to CloudWatch
- define a proper health check
- enable SSL termination for HTTP
- allow custom Gerrit configuration
Feature: Issue 12445
Change-Id: I038817ad5210106a45c94ceb45c82422fe7f6324
diff --git a/README.md b/README.md
index 294b4a8..fa75603 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,33 @@
-# aws-gerrit
-Collection of AWS setup scripts for setting up Gerrit Code Review
+## Gerrit AWS Templates
+Those are a collection of [AWS CloudFormation](https://aws.amazon.com/cloudformation/)
+templates and scripts to deploy Gerrit in AWS.
+
+The aim is to provide some guidelines and example on how to deploy different Gerrit
+setups in the Cloud using AWS as provider.
+
+## Outline
+
+- [Overview](#overview)
+- [Pre-requisites](#pre-requisites)
+- [Templates](#templates)
+
+## Overview
+
+The goal of Gerrit AWS Templates is to provide fully-functional Gerrit installations
+to helps users deploying Gerrit on AWS by providing out-of-the-box templates.
+
+With Gerrit AWS Templates, developers and administrator can create a production-ready
+installation on the cloud in minutes and in a repeatable way, allowing them
+to focus on fine tuning of the Gerrit configuration to suit the user needs.
+
+The provided CloudFormation templates automate the entire creation and deployment
+of the infrastructure and the application.
+
+## Pre-requisites
+
+To manage your AWS services via command line you will need to install
+[AWS CLI](https://aws.amazon.com/cli/) and set it up to point to your account.
+
+## Templates
+
+* [Standalone Gerrit master sandbox without authentication](#single-master/README.md)
diff --git a/single-master/README.md b/single-master/README.md
new file mode 100644
index 0000000..2e187c6
--- /dev/null
+++ b/single-master/README.md
@@ -0,0 +1,103 @@
+# Gerrit Single Master
+
+This set of Templates provide all the components to deploy a single Gerrit master
+in ECS
+
+## Architecture
+
+Two templates are provided in this example:
+* `cf-cluster`: define the ECS cluster and the networking stack
+* `cf-service`: defined the service stack running Gerrit
+
+### Networking
+
+* Single VPC:
+ * CIDR: 10.0.0.0/16
+* Single Availability Zone
+* 1 public Subnets:
+ * CIDR: 10.0.0.0/24
+* 1 public NLB exposing:
+ * HTTP on port 8080
+ * SSH on port 29418
+* 1 Internet Gateway
+
+### Deployment type
+
+* Latest Gerrit version deployed using the official [Docker image](https://hub.docker.com/r/gerritcodereview/gerrit)
+* Application deployed in ECS on a single EC2 instance
+
+### Logging
+
+* Gerrit `error_log` is exported in a Log Group in CloudWatch
+* Other Gerrit logs still need to be exported
+
+### Monitoring
+
+* Standard CloudWatch monitoring metrics for each component
+
+## How to run it
+
+### Getting Started
+
+* Create a key pair to access the EC2 instances in the cluster:
+
+```
+aws ec2 create-key-pair --key-name gerrit-cluster-keys
+```
+
+*NOTE: the EC2 key pair are useful when you need to connect to the EC2 instances
+for troubleshooting purposes. Store them in a `pem` file to use when ssh-ing into your
+instances as follow: `ssh -i yourKeyPairs.pem <ec2_instance_ip>`*
+
+* Create the cluster stack:
+
+```
+aws cloudformation create-stack \
+ --stack-name gerrit-cluster \
+ --capabilities CAPABILITY_IAM \
+ --template-body file://<full_path_to_the_template>/aws-gerrit/single-master/cf-cluster.yml
+```
+
+* Create the service stack:
+
+```
+aws cloudformation create-stack \
+ --stack-name gerrit-service \
+ --capabilities CAPABILITY_NAMED_IAM \
+ --template-body file://<full_path_to_the_template>/aws-gerrit/single-master/cf-service.yml \
+ --parameters ParameterKey=ClusterStackName,ParameterValue=gerrit-cluster
+```
+
+*NOTE*: the `ClusterStackName` value has to be name of the cluster stack created
+in the previous step (i.e.: `gerrit-cluster` in this case)
+
+### Cleaning up
+
+```
+aws cloudformation delete-stack --stack-name gerrit-cluster
+### Wait for the "gerrit-cluster" stack to be deleted
+aws cloudformation delete-stack --stack-name gerrit-service
+```
+
+### Access your Gerrit
+
+You can find the Gerrit public URL by running the following command:
+
+```
+aws cloudformation describe-stacks --stack-name gerrit-service
+```
+
+In the `Outputs` section the URL is set in the `PublicLoadBalancerUrl` key:
+
+```
+ "Outputs": [
+ {
+ "OutputKey": "PublicLoadBalancerUrl",
+ "OutputValue": "http://gerri-LoadB-1CDG276QVT8K8-e28c5bca2e024135.elb.us-east-2.amazonaws.com",
+ "Description": "The url of the external load balancer",
+ "ExportName": "gerrit-ponch-service:PublicLoadBalancerUrl"
+ }
+ ],
+```
+
+The available ports are `8080` for HTTP and `29418` for SSH
diff --git a/single-master/cf-cluster.yml b/single-master/cf-cluster.yml
new file mode 100644
index 0000000..ada2e62
--- /dev/null
+++ b/single-master/cf-cluster.yml
@@ -0,0 +1,215 @@
+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:
+ 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: c4.xlarge
+ 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.
+ ECSKeyName:
+ Type: String
+ Default: gerrit-cluster-keys
+ Description: EC2 key pair name the cluter's instances
+Mappings:
+ # Hard values for the subnet masks. These masks define
+ # the range of internal IP addresses that can be assigned.
+ # The VPC can have all IP's from 10.0.0.0 to 10.0.255.255
+ # There is the subnet which cover the ranges:
+ #
+ # 10.0.0.0 - 10.0.0.255
+ SubnetConfig:
+ VPC:
+ CIDR: '10.0.0.0/16'
+ PublicOne:
+ CIDR: '10.0.0.0/24'
+Resources:
+ VPC:
+ Type: AWS::EC2::VPC
+ Properties:
+ EnableDnsSupport: true
+ EnableDnsHostnames: true
+ CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR']
+
+ # Public subnets, where containers can have public IP addresses
+ PublicSubnetOne:
+ Type: AWS::EC2::Subnet
+ Properties:
+ AvailabilityZone:
+ Fn::Select:
+ - 0
+ - Fn::GetAZs: {Ref: 'AWS::Region'}
+ VpcId: !Ref 'VPC'
+ CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR']
+ MapPublicIpOnLaunch: true
+
+ # Setup networking resources for the public subnets. Containers
+ # in the public subnets have public IP addresses and the routing table
+ # sends network traffic via the internet gateway.
+ InternetGateway:
+ Type: AWS::EC2::InternetGateway
+ GatewayAttachement:
+ Type: AWS::EC2::VPCGatewayAttachment
+ Properties:
+ VpcId: !Ref 'VPC'
+ InternetGatewayId: !Ref 'InternetGateway'
+ PublicRouteTable:
+ Type: AWS::EC2::RouteTable
+ Properties:
+ VpcId: !Ref 'VPC'
+ PublicRoute:
+ Type: AWS::EC2::Route
+ DependsOn: GatewayAttachement
+ Properties:
+ RouteTableId: !Ref 'PublicRouteTable'
+ DestinationCidrBlock: '0.0.0.0/0'
+ GatewayId: !Ref 'InternetGateway'
+ PublicSubnetOneRouteTableAssociation:
+ Type: AWS::EC2::SubnetRouteTableAssociation
+ Properties:
+ SubnetId: !Ref PublicSubnetOne
+ RouteTableId: !Ref PublicRouteTable
+
+ # ECS Resources
+ ECSCluster:
+ Type: AWS::ECS::Cluster
+
+ EcsHostSecurityGroup:
+ Type: AWS::EC2::SecurityGroup
+ Properties:
+ GroupDescription: Access to the ECS hosts that run containers
+ VpcId: !Ref 'VPC'
+ SecurityGroupIngress:
+ # Allow access to NLB from anywhere on the internet
+ - CidrIp: 0.0.0.0/0
+ IpProtocol: -1
+
+ # 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:
+ - !Ref PublicSubnetOne
+ LaunchConfigurationName: !Ref 'ContainerInstances'
+ MinSize: '1'
+ MaxSize: !Ref 'MaxSize'
+ DesiredCapacity: !Ref 'DesiredCapacity'
+ CreationPolicy:
+ ResourceSignal:
+ Timeout: PT15M
+ UpdatePolicy:
+ AutoScalingReplacingUpdate:
+ WillReplace: 'true'
+ ContainerInstances:
+ Type: AWS::AutoScaling::LaunchConfiguration
+ Properties:
+ ImageId: !Ref 'ECSAMI'
+ SecurityGroups: [!Ref 'EcsHostSecurityGroup']
+ InstanceType: !Ref 'InstanceType'
+ IamInstanceProfile: !Ref 'EC2InstanceProfile'
+ KeyName: !Ref ECSKeyName
+ UserData:
+ Fn::Base64: !Sub |
+ #!/bin/bash -xe
+ echo ECS_CLUSTER=${ECSCluster} >> /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
+ # Signal to CloudFormation aws-cfn-bootstrap has been correctly updated
+ /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region}
+ AutoscalingRole:
+ Type: AWS::IAM::Role
+ Properties:
+ AssumeRolePolicyDocument:
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service: [application-autoscaling.amazonaws.com]
+ Action: ['sts:AssumeRole']
+ Path: /
+ Policies:
+ - PolicyName: service-autoscaling
+ PolicyDocument:
+ Statement:
+ - Effect: Allow
+ Action:
+ - 'application-autoscaling:*'
+ - 'cloudwatch:DescribeAlarms'
+ - 'cloudwatch:PutMetricAlarm'
+ - 'ecs:DescribeServices'
+ - 'ecs:UpdateService'
+ Resource: '*'
+ 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'
+ Resource: '*'
+
+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: !Ref 'VPC'
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'VPCId' ] ]
+ PublicSubnetOne:
+ Description: Public subnet one
+ Value: !Ref 'PublicSubnetOne'
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOne' ] ]
diff --git a/single-master/cf-service.yml b/single-master/cf-service.yml
new file mode 100644
index 0000000..bb12213
--- /dev/null
+++ b/single-master/cf-service.yml
@@ -0,0 +1,173 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Deploy a service into an ECS cluster behind a public load balancer.
+Parameters:
+ GerritServiceName:
+ Type: String
+ Default: gerrit-master
+ ClusterStackName:
+ Description: Stack name of the ECS cluster to deply the serivces
+ Type: String
+ Default: gerrit-cluster
+ EnvironmentName:
+ Description: An environment name that will be prefixed to resource names
+ Type: String
+ Default: test
+ DockerImage:
+ Description: Gerrit official Docker image
+ Type: String
+ Default: gerritcodereview/gerrit:latest
+ DesiredCount:
+ Description: How many instances of this task should we run across our cluster?
+ Type: Number
+ Default: 1
+ HTTPPort:
+ Description: Gerrit HTTP port
+ Type: Number
+ Default: 8080
+ SSHPort:
+ Description: Gerrit SSH port
+ Type: Number
+ Default: 29418
+
+Resources:
+ Service:
+ Type: AWS::ECS::Service
+ DependsOn:
+ - HTTPListener
+ - SSHListener
+ Properties:
+ Cluster:
+ Fn::ImportValue:
+ !Join [':', [!Ref 'ClusterStackName', 'ClusterName']]
+ DesiredCount: !Ref DesiredCount
+ TaskDefinition: !Ref TaskDefinition
+ LoadBalancers:
+ - ContainerName: !Ref GerritServiceName
+ ContainerPort: !Ref HTTPPort
+ TargetGroupArn: !Ref HTTPTargetGroup
+ - ContainerName: !Ref GerritServiceName
+ ContainerPort: !Ref SSHPort
+ TargetGroupArn: !Ref SSHTargetGroup
+
+ TaskDefinition:
+ Type: AWS::ECS::TaskDefinition
+ Properties:
+ Family: !Join ['', [!Ref GerritServiceName, TaskDefinition]]
+ TaskRoleArn: !Ref ECSTaskExecutionRole
+ ExecutionRoleArn: !Ref ECSTaskExecutionRole
+ NetworkMode: bridge
+ ContainerDefinitions:
+ - Name: !Ref GerritServiceName
+ Essential: true
+ Image: !Ref DockerImage
+ Environment:
+ - Name: "region"
+ Value: !Ref AWS::Region
+ Cpu: 1024
+ Memory: 2048
+ PortMappings:
+ - ContainerPort: !Ref HTTPPort
+ HostPort: !Ref HTTPPort
+ Protocol: tcp
+ - ContainerPort: !Ref SSHPort
+ HostPort: !Ref SSHPort
+ Protocol: tcp
+ LogConfiguration:
+ LogDriver: awslogs
+ Options:
+ awslogs-group: !Ref AWS::StackName
+ awslogs-region: !Ref AWS::Region
+ awslogs-stream-prefix: !Ref EnvironmentName
+
+ CloudWatchLogsGroup:
+ Type: AWS::Logs::LogGroup
+ Properties:
+ LogGroupName: !Ref AWS::StackName
+ RetentionInDays: 14
+
+ LoadBalancer:
+ Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+ Properties:
+ Type: network
+ Scheme: internet-facing
+ Subnets:
+ - Fn::ImportValue:
+ !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetOne']]
+ Tags:
+ - Key: Name
+ Value: !Join ['-', [!Ref 'EnvironmentName', !Ref 'GerritServiceName', 'nlb']]
+
+ HTTPTargetGroup:
+ Type: AWS::ElasticLoadBalancingV2::TargetGroup
+ DependsOn: LoadBalancer
+ Properties:
+ VpcId:
+ Fn::ImportValue:
+ !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
+ Port: !Ref HTTPPort
+ Protocol: TCP
+
+ HTTPListener:
+ Type: AWS::ElasticLoadBalancingV2::Listener
+ DependsOn: LoadBalancer
+ Properties:
+ DefaultActions:
+ - Type: forward
+ TargetGroupArn: !Ref HTTPTargetGroup
+ LoadBalancerArn: !Ref LoadBalancer
+ Port: !Ref HTTPPort
+ Protocol: TCP
+
+ SSHTargetGroup:
+ Type: AWS::ElasticLoadBalancingV2::TargetGroup
+ DependsOn: LoadBalancer
+ Properties:
+ VpcId:
+ Fn::ImportValue:
+ !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
+ Port: !Ref SSHPort
+ Protocol: TCP
+
+ SSHListener:
+ Type: AWS::ElasticLoadBalancingV2::Listener
+ DependsOn: LoadBalancer
+ Properties:
+ DefaultActions:
+ - Type: forward
+ TargetGroupArn: !Ref SSHTargetGroup
+ LoadBalancerArn: !Ref LoadBalancer
+ Port: !Ref SSHPort
+ Protocol: TCP
+
+ # This is a role which is used by the ECS tasks themselves.
+ ECSTaskExecutionRole:
+ Type: AWS::IAM::Role
+ Properties:
+ AssumeRolePolicyDocument:
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service: [ecs-tasks.amazonaws.com]
+ Action: ['sts:AssumeRole']
+ Path: /
+ Policies:
+ - PolicyName: AmazonECSTaskExecutionRolePolicy
+ PolicyDocument:
+ Statement:
+ - Effect: Allow
+ Action:
+ # Allow the ECS Tasks to download images from ECR
+ - 'ecr:GetAuthorizationToken'
+ - 'ecr:BatchCheckLayerAvailability'
+ - 'ecr:GetDownloadUrlForLayer'
+ - 'ecr:BatchGetImage'
+ # Allow the ECS tasks to upload logs to CloudWatch
+ - 'logs:CreateLogStream'
+ - 'logs:PutLogEvents'
+ Resource: '*'
+Outputs:
+ PublicLoadBalancerUrl:
+ Description: The url of the external load balancer
+ Value: !Join ['', ['http://', !GetAtt 'LoadBalancer.DNSName']]
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicLoadBalancerUrl' ] ]