Skip to content

Instantly share code, notes, and snippets.

@twasink
Last active June 25, 2021 06:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save twasink/9f8262a7229fb5e7e1801e686626fc40 to your computer and use it in GitHub Desktop.
Save twasink/9f8262a7229fb5e7e1801e686626fc40 to your computer and use it in GitHub Desktop.
Jenkins ECS CloudFormation
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