Last active
March 24, 2024 05:11
-
-
Save dgt0011/d25ee89ab3fce5c94c99aa7d8c72ac3f to your computer and use it in GitHub Desktop.
Cloudformation template to deploy SonarQube TO ECS Fargate. Note that this template depends on stacks created from the my-vpc.yaml and rds.yaml gists
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
AWSTemplateFormatVersion: '2010-09-09' | |
Transform: AWS::SecretsManager-2020-07-23 | |
Description: ECS Cluster for SonarQube CE. | |
Dependent on an RDS instance/stack and a VPC stack. | |
Parameters: | |
ServiceName: | |
Type: String | |
Default: sonarqube-app | |
Description: A name for the service. | |
This name will be used to create the ECS service, task definition, | |
and used as a prefix for other resources. | |
VpcId: | |
Type: AWS::EC2::VPC::Id | |
Description: The VPC where the service will be deployed | |
Change this default (or remove it) as appropriate | |
Default: vpc-0123456789abcdef0 | |
PublicSubnetIds: | |
Type: List<AWS::EC2::Subnet::Id> | |
Description: The public subnets where the load balancer service will be deployed. | |
Change these defaults (or remove them) as appropriate | |
Default: "subnet-a123456789abcdef0,subnet-b123456789abcdef0b,subnet-c123456789abcdef0" | |
PrivateSubnetIds: | |
Type: List<AWS::EC2::Subnet::Id> | |
Description: The private subnets where the containers will be deployed | |
Change these defaults (or remove them) as appropriate | |
Default: "subnet-d123456789abcdef0,subnet-e123456789abcdef0,subnet-f123456789abcdef0" | |
ECRRepository: | |
Type: String | |
Default: sonarqube | |
Description: The name of the ECR repository where the docker image is stored. | |
This is not set up as part of this stack and needs to exist prior to this template being used | |
ECRTag: | |
Type: String | |
Default: latest | |
Description: The tag of the docker image to use | |
ContainerPort: | |
Type: Number | |
Default: 9000 | |
Description: What port number the application inside the docker container is binding to | |
ContainerCpu: | |
Type: Number | |
Default: 1024 | |
Description: How much CPU to give the container. 1024 is 1 CPU | |
ContainerMemory: | |
Type: Number | |
Default: 3072 | |
Description: How much memory in megabytes to give the container | |
Path: | |
Type: String | |
Default: "*" | |
Description: A path on the public load balancer that this service | |
should be connected to. Use * to send all load balancer | |
traffic to this service. | |
Priority: | |
Type: Number | |
Default: 1 | |
Description: The priority for the routing rule added to the load balancer. | |
This only applies if your have multiple services which have been | |
assigned to different paths on the load balancer. | |
DesiredCount: | |
Type: Number | |
Default: 1 | |
Description: How many copies of the service task to run. | |
Note cluster set up of SonarQube is exclusive to the Data Center Edition | |
CertificateId: | |
Type: String | |
Description: The ID of the certificate to use for HTTPS, e.g. 12345678-1234-1234-1234-123456789012 | |
DBInstancePasswordSecretName: | |
Type: String | |
Description: The name of the secret in Secrets Manager that contains the database password | |
This will have been created as part of the stack creation for the 'rds.yaml' template | |
Resources: | |
# Security Groups | |
SonarQubeECSSecurityGroup: | |
Type: AWS::EC2::SecurityGroup | |
Properties: | |
GroupName: !Sub ${ServiceName}-ecs-sg | |
GroupDescription: Security group for SonarQube ECS cluster | |
VpcId: !Ref VpcId | |
SecurityGroupEgress: | |
- IpProtocol: tcp | |
FromPort: 5432 | |
ToPort: 5432 | |
DestinationSecurityGroupId: !ImportValue SonarQubeRDSSG | |
Description: Postgresql default port (Internal) for access from ECS cluster | |
- IpProtocol: tcp | |
FromPort: 443 | |
ToPort: 443 | |
CidrIp: 0.0.0.0/0 | |
Description: HTTPS access to the internet | |
- IpProtocol: tcp | |
FromPort: 53 | |
ToPort: 53 | |
CidrIp: !ImportValue vpc-cidr | |
Description: VPC DNS access | |
Tags: | |
- Key: Name | |
Value: !Sub ${ServiceName}-ecs-sg | |
SonarQubeEFSSecurityGroup: | |
Type: AWS::EC2::SecurityGroup | |
Properties: | |
GroupName: !Sub ${ServiceName}-efs-sg | |
GroupDescription: Security group for SonarQube EFS | |
VpcId: !Ref VpcId | |
SecurityGroupIngress: | |
- IpProtocol: tcp | |
FromPort: 2049 | |
ToPort: 2049 | |
SourceSecurityGroupId: !Ref SonarQubeECSSecurityGroup | |
Description: NFS access from ECS cluster | |
Tags: | |
- Key: Name | |
Value: !Sub ${ServiceName}-efs-sg | |
SonarQubeELBSecurityGroup: | |
Type: AWS::EC2::SecurityGroup | |
Properties: | |
GroupName: !Sub ${ServiceName}-elb-sg | |
GroupDescription: Security group for SonarQube ELB | |
VpcId: !Ref VpcId | |
SecurityGroupIngress: | |
- IpProtocol: tcp | |
FromPort: 80 | |
ToPort: 80 | |
CidrIp: 0.0.0.0/0 | |
- IpProtocol: tcp | |
FromPort: 443 | |
ToPort: 443 | |
CidrIp: 0.0.0.0/0 | |
SecurityGroupEgress: | |
- IpProtocol: tcp | |
FromPort: 9000 | |
ToPort: 9000 | |
DestinationSecurityGroupId: !Ref SonarQubeECSSecurityGroup | |
Tags: | |
- Key: Name | |
Value: !Sub ${ServiceName}-elb-sg | |
SonarQubeECSToEFSEgress: | |
Type: AWS::EC2::SecurityGroupEgress | |
Properties: | |
GroupId: !Ref SonarQubeECSSecurityGroup | |
IpProtocol: tcp | |
FromPort: 2049 | |
ToPort: 2049 | |
DestinationSecurityGroupId: !Ref SonarQubeEFSSecurityGroup | |
Description: EFS access from ECS cluster | |
SonarQubeECSFromELBIngress: | |
Type: AWS::EC2::SecurityGroupIngress | |
Properties: | |
GroupId: !Ref SonarQubeECSSecurityGroup | |
IpProtocol: tcp | |
FromPort: 9000 | |
ToPort: 9000 | |
SourceSecurityGroupId: !Ref SonarQubeELBSecurityGroup | |
Description: ELB to ECS access | |
SonarQubeECSToRDSIngress: | |
Type: AWS::EC2::SecurityGroupIngress | |
Properties: | |
GroupId: !ImportValue SonarQubeRDSSG | |
IpProtocol: tcp | |
FromPort: 5432 | |
ToPort: 5432 | |
SourceSecurityGroupId: !Ref SonarQubeECSSecurityGroup | |
Description: Postgresql default port (Internal) for access from ECS cluster | |
#Load Balancer, TargetGroups and Listeners | |
TargetGroup: | |
Type: AWS::ElasticLoadBalancingV2::TargetGroup | |
Properties: | |
HealthCheckIntervalSeconds: 300 | |
HealthCheckProtocol: HTTP | |
HealthCheckPort: !Ref 'ContainerPort' | |
HealthCheckTimeoutSeconds: 120 | |
HealthyThresholdCount: 2 | |
TargetType: ip | |
Name: !Sub '${ServiceName}-tg' | |
Port: !Ref 'ContainerPort' | |
Protocol: HTTP | |
UnhealthyThresholdCount: 2 | |
VpcId: !Ref VpcId | |
LoadBalancer: | |
Type: AWS::ElasticLoadBalancingV2::LoadBalancer | |
Properties: | |
Name: !Sub '${ServiceName}-elb' | |
Scheme: internet-facing | |
SecurityGroups: | |
- !Ref SonarQubeELBSecurityGroup | |
Subnets: !Ref PublicSubnetIds | |
Type: application | |
HttpLoadBalancerListener: | |
Type: AWS::ElasticLoadBalancingV2::Listener | |
DependsOn: | |
- LoadBalancer | |
Properties: | |
DefaultActions: | |
- Type: redirect | |
RedirectConfig: | |
Protocol: HTTPS | |
Port: '443' | |
Host: '#{host}' | |
Path: '/#{path}' | |
Query: '#{query}' | |
StatusCode: HTTP_301 | |
LoadBalancerArn: | |
Ref: LoadBalancer | |
Port: 80 | |
Protocol: HTTP | |
HttpsLoadBalancerListener: | |
Type: AWS::ElasticLoadBalancingV2::Listener | |
DependsOn: | |
- LoadBalancer | |
Properties: | |
Certificates: | |
- CertificateArn: !Sub arn:aws:acm:${AWS::Region}:${AWS::AccountId}:certificate/${CertificateId} | |
DefaultActions: | |
- TargetGroupArn: !Ref TargetGroup | |
Type: 'forward' | |
LoadBalancerArn: !Ref LoadBalancer | |
Port: 443 | |
Protocol: HTTPS | |
HttpsLoadBalancerListenerRule: | |
Type: AWS::ElasticLoadBalancingV2::ListenerRule | |
Properties: | |
Actions: | |
- TargetGroupArn: !Ref TargetGroup | |
Type: 'forward' | |
Conditions: | |
- Field: path-pattern | |
Values: [!Ref 'Path'] | |
ListenerArn: !Ref HttpsLoadBalancerListener | |
Priority: !Ref 'Priority' | |
# EFS shared file system | |
FileSystem: | |
Type: AWS::EFS::FileSystem | |
Properties: | |
FileSystemTags: | |
- Key: Name | |
Value: !Sub '${ServiceName}-efs' | |
PerformanceMode: generalPurpose | |
Encrypted: true | |
LifecyclePolicies: | |
- TransitionToIA: AFTER_30_DAYS | |
ThroughputMode: bursting | |
BackupPolicy: | |
Status: DISABLED | |
AccessPoint1: | |
Type: AWS::EFS::AccessPoint | |
Properties: | |
FileSystemId: !Ref FileSystem | |
PosixUser: | |
Uid: '1000' | |
Gid: '1000' | |
RootDirectory: | |
CreationInfo: | |
OwnerGid: '1000' | |
OwnerUid: '1000' | |
Permissions: '755' | |
Path: '/sonarqube_data' | |
AccessPointTags: | |
- Key: Name | |
Value: !Sub '${ServiceName}-data' | |
AccessPoint2: | |
Type: AWS::EFS::AccessPoint | |
Properties: | |
FileSystemId: !Ref FileSystem | |
PosixUser: | |
Uid: '1000' | |
Gid: '1000' | |
RootDirectory: | |
CreationInfo: | |
OwnerGid: '1000' | |
OwnerUid: '1000' | |
Permissions: '755' | |
Path: '/sonarqube_extensions' | |
AccessPointTags: | |
- Key: Name | |
Value: !Sub '${ServiceName}-extensions' | |
AccessPoint3: | |
Type: AWS::EFS::AccessPoint | |
Properties: | |
FileSystemId: !Ref FileSystem | |
PosixUser: | |
Uid: '1000' | |
Gid: '1000' | |
RootDirectory: | |
CreationInfo: | |
OwnerGid: '1000' | |
OwnerUid: '1000' | |
Permissions: '755' | |
Path: '/sonarqube_logs' | |
AccessPointTags: | |
- Key: Name | |
Value: !Sub '${ServiceName}-logs' | |
MountTarget1: | |
Type: AWS::EFS::MountTarget | |
Properties: | |
FileSystemId: !Ref FileSystem | |
SubnetId: !Select [ 0, !Ref PrivateSubnetIds] | |
SecurityGroups: | |
- !Ref SonarQubeEFSSecurityGroup | |
MountTarget2: | |
Type: AWS::EFS::MountTarget | |
Properties: | |
FileSystemId: !Ref FileSystem | |
SubnetId: !Select [ 1, !Ref PrivateSubnetIds] | |
SecurityGroups: | |
- !Ref SonarQubeEFSSecurityGroup | |
MountTarget3: | |
Type: AWS::EFS::MountTarget | |
Properties: | |
FileSystemId: !Ref FileSystem | |
SubnetId: !Select [ 2, !Ref PrivateSubnetIds] | |
SecurityGroups: | |
- !Ref SonarQubeEFSSecurityGroup | |
# IAM Roles | |
SonarQubeTaskRole: | |
Type: AWS::IAM::Role | |
Properties: | |
RoleName: !Sub '${ServiceName}-task-role' | |
AssumeRolePolicyDocument: | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: [ecs-tasks.amazonaws.com] | |
Action: ['sts:AssumeRole'] | |
Path: / | |
Policies: [] | |
SonarQubeTaskExecutionRole: | |
Type: AWS::IAM::Role | |
Properties: | |
RoleName: !Sub '${ServiceName}-execution-role' | |
AssumeRolePolicyDocument: | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: [ecs-tasks.amazonaws.com] | |
Action: ['sts:AssumeRole'] | |
Path: / | |
Policies: | |
- PolicyName: ECRTokenAccessPolicy | |
PolicyDocument: | |
Statement: | |
- Effect: Allow | |
Action: | |
- 'ecr:GetAuthorizationToken' | |
Resource: "*" | |
- PolicyName: ECRReadAccess | |
PolicyDocument: | |
Statement: | |
- Effect: Allow | |
Action: | |
- 'ecr:BatchCheckLayerAvailability' | |
- 'ecr:GetDownloadUrlForLayer' | |
- 'ecr:BatchGetImage' | |
Resource: !Sub 'arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/${ECRRepository}' | |
- PolicyName: LogWriteAccess | |
PolicyDocument: | |
Statement: | |
- Effect: Allow | |
Action: | |
- 'logs:CreateLogStream' | |
- 'logs:PutLogEvents' | |
Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/ecs/${ServiceName}:log-stream:*' | |
- PolicyName: DatabaseSecretReadAccess | |
PolicyDocument: | |
Statement: | |
- Effect: Allow | |
Action: | |
- 'secretsmanager:GetSecretValue' | |
Resource: | |
!Join [ | |
'', | |
[ | |
'arn:aws:secretsmanager:', | |
!Ref AWS::Region, | |
':', | |
!Ref AWS::AccountId, | |
':', | |
'secret:', | |
!Ref DBInstancePasswordSecretName, | |
'-??????' | |
] | |
] | |
# log group | |
LogGroup: | |
Type: AWS::Logs::LogGroup | |
Properties: | |
LogGroupName: !Sub '/ecs/${ServiceName}' | |
RetentionInDays: 14 | |
# ECS Resources | |
SonarQubeCluster: | |
Type: AWS::ECS::Cluster | |
Properties: | |
ClusterName: !Sub '${ServiceName}-cluster' | |
ClusterSettings: | |
- Name: containerInsights | |
Value: enabled | |
SonarQubeTaskDefinition: | |
Type: AWS::ECS::TaskDefinition | |
DependsOn: | |
- AccessPoint1 | |
- AccessPoint2 | |
- AccessPoint3 | |
- FileSystem | |
Properties: | |
Family: !Ref 'ServiceName' | |
Cpu: !Ref 'ContainerCpu' | |
Memory: !Ref 'ContainerMemory' | |
NetworkMode: awsvpc | |
RequiresCompatibilities: | |
- FARGATE | |
ExecutionRoleArn: !Ref SonarQubeTaskExecutionRole | |
TaskRoleArn: !Ref SonarQubeTaskRole | |
Volumes: | |
- Name: sonarqube_data | |
EFSVolumeConfiguration: | |
FilesystemId: !Ref FileSystem | |
TransitEncryption: ENABLED | |
AuthorizationConfig: | |
AccessPointId: !Ref AccessPoint1 | |
- Name: sonarqube_extensions | |
EFSVolumeConfiguration: | |
FilesystemId: !Ref FileSystem | |
TransitEncryption: ENABLED | |
AuthorizationConfig: | |
AccessPointId: !Ref AccessPoint2 | |
- Name: sonarqube_logs | |
EFSVolumeConfiguration: | |
FilesystemId: !Ref FileSystem | |
TransitEncryption: ENABLED | |
AuthorizationConfig: | |
AccessPointId: !Ref AccessPoint3 | |
ContainerDefinitions: | |
- Name: !Ref 'ServiceName' | |
Cpu: !Ref 'ContainerCpu' | |
Memory: !Ref 'ContainerMemory' | |
ReadonlyRootFilesystem: false | |
Essential: true | |
Image: | |
!Join [ | |
'.', | |
[ | |
!Ref AWS::AccountId, | |
'dkr.ecr', | |
!Ref AWS::Region, | |
!Sub 'amazonaws.com/${ECRRepository}:${ECRTag}' | |
] | |
] | |
PortMappings: | |
- ContainerPort: !Ref 'ContainerPort' | |
Name: !Sub '${ServiceName}-9000-tcp' | |
HostPort: 9000 | |
AppProtocol: http | |
Protocol: tcp | |
LogConfiguration: | |
LogDriver: awslogs | |
Options: | |
awslogs-group: !Ref LogGroup | |
awslogs-region: !Ref 'AWS::Region' | |
awslogs-stream-prefix: ecs | |
Environment: | |
- Name: SONARQUBE_JDBC_URL | |
Value: | |
!Join [ | |
'', | |
[ | |
'jdbc:postgresql://', | |
!ImportValue SonarQubeDBInstanceEndpointAddress, | |
':5432/', | |
!ImportValue SonarQubeDBInstanceName | |
] | |
] | |
- Name: SONARQUBE_JDBC_USERNAME | |
Value: !Sub '{{resolve:secretsmanager:${DBInstancePasswordSecretName}:SecretString:username}}' | |
- Name: SONARQUBE_JDBC_PASSWORD | |
Value: !Sub '{{resolve:secretsmanager:${DBInstancePasswordSecretName}:SecretString:password}}' | |
- Name: SONAR_SEARCH_JAVAADDITIONALOPTS | |
Value: '-Dnode.store.allow_mmap=false,-Ddiscovery.type=single-node' | |
MountPoints: | |
- ContainerPath: /opt/sonarqube/data | |
SourceVolume: sonarqube_data | |
ReadOnly: false | |
- ContainerPath: /opt/sonarqube/extensions | |
SourceVolume: sonarqube_extensions | |
ReadOnly: false | |
- ContainerPath: /opt/sonarqube/logs | |
SourceVolume: sonarqube_logs | |
ReadOnly: false | |
Ulimits: | |
- HardLimit: 65535 | |
Name: nofile | |
SoftLimit: 65535 | |
SonarQubeService: | |
Type: AWS::ECS::Service | |
DependsOn: LoadBalancer | |
Properties: | |
ServiceName: !Ref 'ServiceName' | |
Cluster: !Ref SonarQubeCluster | |
LaunchType: FARGATE | |
DeploymentConfiguration: | |
MaximumPercent: 100 | |
MinimumHealthyPercent: 0 | |
DesiredCount: !Ref DesiredCount | |
NetworkConfiguration: | |
AwsvpcConfiguration: | |
AssignPublicIp: DISABLED | |
SecurityGroups: | |
- !Ref SonarQubeECSSecurityGroup | |
Subnets: | |
- !Select [ 0, !Ref PrivateSubnetIds] | |
- !Select [ 1, !Ref PrivateSubnetIds] | |
- !Select [ 2, !Ref PrivateSubnetIds] | |
TaskDefinition: !Ref SonarQubeTaskDefinition | |
LoadBalancers: | |
- ContainerName: !Ref 'ServiceName' | |
ContainerPort: !Ref 'ContainerPort' | |
TargetGroupArn: !Ref 'TargetGroup' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment