Last active
June 25, 2021 06:20
-
-
Save twasink/9f8262a7229fb5e7e1801e686626fc40 to your computer and use it in GitHub Desktop.
Jenkins ECS CloudFormation
This file contains hidden or 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 | |
Description: Jenkins ECS Service | |
# This configures a Jenkins instance, using a custom Docker image, | |
# running as a container on Amazon's ECS. | |
Parameters: | |
AvailabilityZone: | |
Type: AWS::EC2::AvailabilityZone::Name | |
Default: us-east-1a | |
KeyName: | |
Description: Name of an existing public/private key pair. If you do not have one in this AWS Region, | |
please create it before continuing. | |
Type: 'AWS::EC2::KeyPair::KeyName' | |
DeployService: | |
Type: String | |
Default: true | |
AllowedValues: [ "true", "false" ] | |
Description: Deploy the Jenkins ECS service. Useful for debugging/changing | |
DeployServer: | |
Type: String | |
Default: true | |
AllowedValues: [ "true", "false" ] | |
Description: Deploy the server for the Jenkins service. Useful for debugging/changing | |
EcsAmi: | |
Type: AWS::EC2::Image::Id | |
Default: ami-0dbd8c88f9060cf71 # Correct as of June 24 2021 | |
Description: | |
Amazon Linux 2 ECS enabled AMI. | |
See https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html | |
InstanceType: | |
Type: String | |
Default: c5.xlarge | |
Description: EC2 instance type used for the Jenkins server | |
AllowedValues: | |
- c5.large | |
- c5.xlarge # 4 CPU | |
- c5.2xlarge | |
EcsCluster: | |
Type: String | |
Description: the name of the ECS Cluster to deploy into | |
Default: DevCluster | |
Conditions: | |
DeployService: !Equals [ !Ref DeployService, true ] | |
DeployServer: !Equals [ !Ref DeployServer, true ] | |
Resources: | |
JenkinsDomainName: | |
Type: AWS::Route53::RecordSet | |
Properties: | |
HostedZoneId: !ImportValue PublicHostedZone | |
Name: !Sub | |
- "jenkins.${DNS}" | |
- DNS: !ImportValue DNS | |
AliasTarget: | |
DNSName: !ImportValue WebLoadBalancerDnsName | |
HostedZoneId: !ImportValue WebLoadBalancerZoneId | |
Type: "A" | |
# # Load Balancer Target Group | |
JenkinsTargetGroup: | |
Type: AWS::ElasticLoadBalancingV2::TargetGroup | |
Condition: DeployService | |
Properties: | |
Name: JenkinsService | |
Port: 8080 | |
Protocol: HTTP | |
TargetType: instance | |
HealthCheckPath: /login | |
VpcId: !ImportValue "VPC-VPCID" | |
TargetGroupAttributes: | |
- Key: deregistration_delay.timeout_seconds | |
Value: 10 | |
JenkinsListenerRule: | |
Type: AWS::ElasticLoadBalancingV2::ListenerRule | |
Condition: DeployService | |
Properties: | |
Actions: | |
- Type: forward | |
TargetGroupArn: !Ref JenkinsTargetGroup | |
Conditions: | |
- Field: host-header | |
HostHeaderConfig: | |
Values: | |
- !Ref JenkinsDomainName | |
ListenerArn: !ImportValue WebListener | |
Priority: 41000 | |
# EFS File System | |
JenkinsEFSFileSystem: | |
Type: AWS::EFS::FileSystem | |
Properties: | |
AvailabilityZoneName: !Ref AvailabilityZone | |
BackupPolicy: | |
Status: ENABLED | |
Encrypted: false | |
LifecyclePolicies: | |
- TransitionToIA: AFTER_90_DAYS | |
PerformanceMode: generalPurpose | |
JenkinsMountPoint: | |
Type: AWS::EFS::MountTarget | |
Properties: | |
FileSystemId: !Ref JenkinsEFSFileSystem | |
SecurityGroups: | |
- !ImportValue "EFSSecurityGroup" | |
SubnetId: !ImportValue 'VPC-PublicSubnet1ID' | |
JenkinsEFSAccessPoint: | |
Type: AWS::EFS::AccessPoint | |
Properties: | |
FileSystemId: !Ref JenkinsEFSFileSystem | |
PosixUser: | |
Gid: 1000 | |
Uid: 1000 # the group and user id are what are used by the Jenkins docker image; conveniently the same as the ec2-user | |
RootDirectory: | |
CreationInfo: | |
OwnerGid: 1000 | |
OwnerUid: 1000 | |
Permissions: 700 | |
Path: /jenkins_home | |
JenkinsLogsEFSAccessPoint: | |
Type: AWS::EFS::AccessPoint | |
Properties: | |
FileSystemId: !Ref JenkinsEFSFileSystem | |
PosixUser: | |
Gid: 1000 | |
Uid: 1000 # the group and user id are what are used by the Jenkins docker image | |
RootDirectory: | |
CreationInfo: | |
OwnerGid: 1000 | |
OwnerUid: 1000 | |
Permissions: 744 | |
Path: /jenkins_logs | |
JenkinsSecurityGroup: | |
Type: AWS::EC2::SecurityGroup | |
Properties: | |
GroupName: "Jenkins_ServerSecurityGroup" | |
GroupDescription: Security group for the Jenkins Servers. Allows SSH, HTTP in, anything out. | |
SecurityGroupIngress: | |
- IpProtocol: tcp | |
FromPort: 8080 | |
ToPort: 8080 | |
CidrIp: !ImportValue "VPC-VPCCIDR" | |
- IpProtocol: tcp | |
FromPort: 22 | |
ToPort: 22 | |
CidrIp: !ImportValue "VPC-VPCCIDR" | |
VpcId: !ImportValue "VPC-VPCID" | |
JenkinsLogs: | |
Type: AWS::Logs::LogGroup | |
Properties: | |
LogGroupName: DevServers/Jenkins | |
RetentionInDays: 90 | |
JenkinsTaskRole: | |
Type: 'AWS::IAM::Role' | |
Properties: | |
RoleName: "JenkinsServerTaskRole" | |
Description: "Role used to run the Jenkins server" | |
AssumeRolePolicyDocument: | |
Version: "2008-10-17" | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: | |
- ecs-tasks.amazonaws.com | |
Action: | |
- 'sts:AssumeRole' | |
Policies: | |
- PolicyName: SessionManager # Allows the use of System Session Manager to connect to the instances; e.g. AWS Exec | |
PolicyDocument: | |
Version: "2012-10-17" | |
Statement: | |
- Effect: "Allow" | |
Action: | |
- "ssmmessages:CreateControlChannel" | |
- "ssmmessages:CreateDataChannel" | |
- "ssmmessages:OpenControlChannel" | |
- "ssmmessages:OpenDataChannel" | |
Resource: "*" | |
# Task Definition | |
JenkinsTaskDefinition: | |
Type: AWS::ECS::TaskDefinition | |
Properties: | |
Family: "JenkinsTask" | |
Cpu: 4096 # 4 vCPU - c5.xlarge | |
# EphemeralStorage: | |
# EphemeralStorage | |
# ExecutionRoleArn: String | |
Memory: 7168 # Leave a GB for the server, which should have 8GB of memory. | |
NetworkMode: host | |
ExecutionRoleArn: !Sub 'arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole' # created via the console; deal with it. | |
TaskRoleArn: !Ref JenkinsTaskRole | |
RequiresCompatibilities: | |
- EC2 | |
Volumes: | |
- Name: jenkinsDataVolume | |
EFSVolumeConfiguration: | |
FilesystemId: !Ref JenkinsEFSFileSystem | |
AuthorizationConfig: | |
AccessPointId: !Ref JenkinsEFSAccessPoint | |
RootDirectory: / | |
TransitEncryption: ENABLED | |
- Name: jenkinsLogVolume | |
EFSVolumeConfiguration: | |
FilesystemId: !Ref JenkinsEFSFileSystem | |
AuthorizationConfig: | |
AccessPointId: !Ref JenkinsLogsEFSAccessPoint | |
RootDirectory: / | |
TransitEncryption: ENABLED | |
- Name: docker_sock | |
Host: | |
SourcePath: /var/run/docker.sock | |
ContainerDefinitions: | |
- Name: "JenkinsServer" | |
Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/jenkins-server" | |
LogConfiguration: | |
LogDriver: awslogs | |
Options: | |
awslogs-region: us-east-1 | |
awslogs-group: !Ref JenkinsLogs #will need to make this. | |
awslogs-stream-prefix: jenkins | |
MountPoints: # TBD with the EFS configuration | |
- ContainerPath: /var/jenkins_home | |
ReadOnly: false # needs to be able to write to it, after all | |
SourceVolume: jenkinsDataVolume | |
- ContainerPath: /var/log/jenkins | |
ReadOnly: false # needs to be able to write to it, after all | |
SourceVolume: jenkinsLogVolume | |
- ContainerPath: /var/run/docker.sock | |
ReadOnly: false | |
SourceVolume: docker_sock | |
PortMappings: | |
- ContainerPort: 8080 # The Jenkins image runs on port 8080 | |
Protocol: tcp | |
Ulimits: | |
- Name: nofile # Jenkins likes to open lots of files... | |
SoftLimit: 65536 | |
HardLimit: 65536 | |
Privileged: true # needs to be privileged for docker-in-docker to work. | |
StartTimeout: 600 # ten minutes to start | |
StopTimeout: 120 # two minutes to stop. | |
EC2InstanceProfile: | |
Type: AWS::IAM::InstanceProfile | |
Condition: DeployServer | |
Properties: | |
Path: / | |
Roles: [!Ref 'EC2Role'] | |
EC2Role: | |
Type: AWS::IAM::Role | |
Condition: DeployServer | |
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'] | |
Resource: '*' | |
JenkinsServer: | |
Type: AWS::EC2::Instance | |
Condition: DeployServer | |
Properties: | |
ImageId: !Ref EcsAmi | |
SecurityGroupIds: [ !Ref JenkinsSecurityGroup ] | |
InstanceType: !Ref 'InstanceType' | |
IamInstanceProfile: !Ref 'EC2InstanceProfile' | |
KeyName: !Ref KeyName | |
SubnetId: !ImportValue 'VPC-PublicSubnet1ID' | |
Tags: | |
- Key: Name | |
Value: Jenkins | |
UserData: | |
Fn::Base64: !Sub | | |
#!/bin/bash -xe | |
echo ECS_CLUSTER=${EcsCluster} >> /etc/ecs/ecs.config | |
yum install -y aws-cfn-bootstrap | |
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region} | |
JenkinsPrivateDomainName: | |
Type: AWS::Route53::RecordSet | |
Condition: DeployServer | |
Properties: | |
HostedZoneId: !ImportValue PrivateHostedZone | |
Name: "jenkins.dev.local" | |
ResourceRecords: | |
- !GetAtt JenkinsServer.PrivateIp | |
Type: "A" | |
TTL: 300 | |
JenkinsService: | |
Type: AWS::ECS::Service | |
Condition: DeployService | |
DependsOn: JenkinsListenerRule # The Target Group must be attached to a load balancer | |
Properties: | |
ServiceName: JenkinsService | |
TaskDefinition: !Ref JenkinsTaskDefinition | |
Cluster: !Ref EcsCluster | |
# DesiredCount: 1 | |
# CapacityProviderStrategy: | |
# - CapacityProvider: !Ref JenkinsCapacityProvider | |
# Base: 1 | |
# Weight: 100 | |
EnableExecuteCommand: true | |
HealthCheckGracePeriodSeconds: 3600 # Allow up to an hour for the service to start; shouldn't take more than a few minutes, but gives time to debug | |
LoadBalancers: | |
- ContainerName: JenkinsServer # Link this to the task definition | |
ContainerPort: 8080 | |
TargetGroupArn: !Ref JenkinsTargetGroup | |
# NetworkConfiguration: | |
# AwsvpcConfiguration: | |
# AssignPublicIp: DISABLED | |
# SecurityGroups: | |
# - !Ref JenkinsSecurityGroup | |
# Subnets: | |
# - !ImportValue "VPC-PublicSubnet1ID" | |
JenkinsAgentIAM: | |
Type: 'AWS::IAM::User' | |
Properties: | |
UserName: 'jenkins' | |
Policies: | |
- PolicyName: 'DepolymentCapabilities' | |
PolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Action: '*' | |
Effect: Allow | |
Resource: "*" # You really should make this a lot more limited | |
# Permissions required to deploy software to AWS (e.g. via CloudFormation) | |
- PolicyName: 'BuildCapablities' | |
PolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Action: '*' | |
Effect: Allow | |
Resource: "*" # You really should make this a lot more limited | |
# Outputs |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment