Created
August 12, 2016 12:34
-
-
Save JosephMaxwell/b1ae9015f8b40638586d6d2b37d1269b to your computer and use it in GitHub Desktop.
CloudFormation Template for Jenkins on EC2
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" : "Launches a Jenkins server.", | |
"Parameters" : { | |
"InstanceType" : { | |
"Description" : "EC2 instance type", | |
"Type" : "String", | |
"Default" : "t2.small", | |
"AllowedValues" : [ "t1.micro","t2.small","m1.small","m1.medium","m1.large","m1.xlarge","m2.xlarge","m2.2xlarge","m2.4xlarge","m3.xlarge","m3.2xlarge","c1.medium","c1.xlarge","cc1.4xlarge","cc2.8xlarge","cg1.4xlarge"], | |
"ConstraintDescription" : "must be a valid EC2 instance type." | |
}, | |
"SshKey" : { | |
"Description" : "Name of an existing EC2 keypair to enable SSH access to the instances", | |
"Default": "SwiftOtter", | |
"Type" : "AWS::EC2::KeyPair::KeyName" | |
}, | |
"DnsPrefix" : { | |
"Description" : "Prefix for Jenkins' DNS record (<prefix>.<zone>)", | |
"Type": "String", | |
"Default": "builds" | |
}, | |
"DnsZone" : { | |
"Description" : "Route53-hosted zone to use for the DNS record (<prefix>.<zone>)", | |
"Type": "String", | |
"Default": "swiftotter.com" | |
}, | |
"S3Bucket" : { | |
"Description" : "Existing S3 bucket to use for Jenkins backups and restores", | |
"Type" : "String", | |
"Default": "swiftotter-jenkins" | |
}, | |
"S3Prefix" : { | |
"Description" : "[Optional] Key prefix to use for Jenkins backups", | |
"Type" : "String", | |
"Default": "" | |
}, | |
"Subnets" : { | |
"Description" : "List of VPC subnet IDs for the cluster", | |
"Type" : "List<AWS::EC2::Subnet::Id>" | |
}, | |
"VpcId" : { | |
"Description" : "VPC associated with the provided subnets", | |
"Type" : "AWS::EC2::VPC::Id" | |
}, | |
"AdminSecurityGroup" : { | |
"Description" : "Existing security group that should be granted administrative access to Jenkins (e.g., 'sg-123456')", | |
"Default": "Primary", | |
"Type": "AWS::EC2::SecurityGroup::Id" | |
} | |
}, | |
"Mappings" : { | |
"RegionMap" : { | |
"us-east-1" : { | |
"AMI" : "ami-6869aa05" | |
}, | |
"us-west-1" : { | |
"AMI" : "ami-7172b611" | |
}, | |
"us-west-2" : { | |
"AMI" : "ami-31490d51" | |
}, | |
"eu-west-1" : { | |
"AMI" : "ami-f9dd458a" | |
} | |
} | |
}, | |
"Resources" : { | |
"CloudFormationLogs": { | |
"Type": "AWS::Logs::LogGroup", | |
"Properties": { | |
"RetentionInDays": 7 | |
} | |
}, | |
"SwiftOtterJenkins" : { | |
"Type" : "AWS::IAM::User", | |
"Properties" : { | |
"Policies" : [{ | |
"PolicyName" : "S3Access", | |
"PolicyDocument" : { | |
"Statement": [{ | |
"Effect" : "Allow", | |
"Action" : "s3:*", | |
"Resource" : { "Fn::Join" : ["", ["arn:aws:s3:::", {"Ref" : "S3Bucket"} , "/*"]]} | |
}] | |
} | |
}, | |
{ | |
"PolicyName" : "IAMAccess", | |
"PolicyDocument" : { | |
"Statement" : [{ | |
"Effect" : "Allow", | |
"NotAction" : "iam:*", | |
"Resource" : "*" | |
}] | |
} | |
}, | |
{ | |
"PolicyName" : "EC2Access", | |
"PolicyDocument" : { | |
"Statement" : [{ | |
"Effect" : "Allow", | |
"Action" : "ec2:*", | |
"Resource" : "*" | |
}] | |
} | |
}] | |
} | |
}, | |
"BuildRole": { | |
"Type": "AWS::IAM::Role", | |
"Properties": { | |
"AssumeRolePolicyDocument": { | |
"Version" : "2012-10-17", | |
"Statement": [{ | |
"Effect": "Allow", | |
"Principal": { | |
"Service": [ "ec2.amazonaws.com" ] | |
}, | |
"Action": [ "sts:AssumeRole" ] | |
}] | |
}, | |
"Policies": [ { | |
"PolicyName": "root", | |
"PolicyDocument": { | |
"Version" : "2012-10-17", | |
"Statement": [ { | |
"Effect": "Allow", | |
"Action": "*", | |
"Resource": "*" | |
} ] | |
} | |
} ] | |
} | |
}, | |
"RolePolicies": { | |
"Type": "AWS::IAM::Policy", | |
"Properties": { | |
"PolicyName": "root", | |
"PolicyDocument": { | |
"Version": "2012-10-17", | |
"Statement": [ | |
{ | |
"Action": "ec2:*", | |
"Effect": "Allow", | |
"Resource": "*" | |
}, | |
{ | |
"Effect": "Allow", | |
"Action": "elasticloadbalancing:*", | |
"Resource": "*" | |
}, | |
{ | |
"Effect": "Allow", | |
"Action": "cloudwatch:*", | |
"Resource": "*" | |
}, | |
{ | |
"Effect": "Allow", | |
"Action": "autoscaling:*", | |
"Resource": "*" | |
}, | |
{ | |
"Effect": "Allow", | |
"Action": "s3:*", | |
"Resource": "*" | |
}, | |
{ | |
"Effect":"Allow", | |
"Action": ["iam:PassRole", "iam:ListInstanceProfiles", "ec2:*"], | |
"Resource": "*" | |
} | |
] | |
}, | |
"Roles": [ { "Ref": "BuildRole" } ] | |
} | |
}, | |
"BuildInstanceProfile": { | |
"Type": "AWS::IAM::InstanceProfile", | |
"Properties" : { | |
"Path": "/", | |
"Roles": [ { "Ref": "BuildRole" }] | |
} | |
}, | |
"HostKeys" : { | |
"Type" : "AWS::IAM::AccessKey", | |
"Properties" : { | |
"UserName" : { "Ref" : "SwiftOtterJenkins" } | |
} | |
}, | |
"ServerGroup" : { | |
"Type" : "AWS::AutoScaling::AutoScalingGroup", | |
"Properties" : { | |
"AvailabilityZones" : { "Fn::GetAZs" : "" }, | |
"LaunchConfigurationName" : { "Ref" : "LaunchConfig" }, | |
"MinSize" : "1", | |
"MaxSize" : "1", | |
"DesiredCapacity" : "1", | |
"LoadBalancerNames" : [ { "Ref" : "ElasticLoadBalancer" } ] | |
} | |
}, | |
"LaunchConfig" : { | |
"Type" : "AWS::AutoScaling::LaunchConfiguration", | |
"Metadata" : { | |
"AWS::CloudFormation::Init" : { | |
"configSets" : { | |
"install" : [ "installConfig", "installApp", "installLogs" ] | |
}, | |
"installConfig" : { | |
"files" : { | |
"/etc/cfn/cfn-hup.conf" : { | |
"content" : { "Fn::Join" : ["", [ | |
"[main]\n", | |
"stack=", { "Ref" : "AWS::StackId" }, "\n", | |
"region=", { "Ref" : "AWS::Region" }, "\n" | |
]]}, | |
"mode" : "000400", | |
"owner" : "root", | |
"group" : "root" | |
}, | |
"/etc/cfn/hooks.d/cfn-auto-reloader.conf" : { | |
"content": { "Fn::Join" : ["", [ | |
"[cfn-auto-reloader-hook]\n", | |
"triggers=post.update\n", | |
"path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init\n", | |
"action=/opt/aws/bin/cfn-init -v ", | |
" --stack ", { "Ref" : "AWS::StackName" }, | |
" --resource WebServerInstance ", | |
" --configsets install_all ", | |
" --region ", { "Ref" : "AWS::Region" }, "\n", | |
"runas=root\n" | |
]]} | |
} | |
}, | |
"services" : { | |
"sysvinit" : { | |
"cfn-hup" : { "enabled" : "true", "ensureRunning" : "true", | |
"files" : ["/etc/cfn/cfn-hup.conf", "/etc/cfn/hooks.d/cfn-auto-reloader.conf"]} | |
} | |
} | |
}, | |
"installLogs": { | |
"packages": { | |
"yum": { | |
"awslogs": [] | |
} | |
}, | |
"commands" : { | |
"01_create_state_directory" : { | |
"command" : "mkdir -p /var/awslogs/state" | |
} | |
}, | |
"services" : { | |
"sysvinit" : { | |
"awslogs" : { "enabled" : "true", "ensureRunning" : "true", | |
"files" : [ "/etc/awslogs/awslogs.conf" ] } | |
} | |
}, | |
"files" : { | |
"/etc/awslogs/awslogs.conf": { | |
"content": { "Fn::Join": [ "", [ | |
"[general]\n", | |
"state_file= /var/awslogs/state/agent-state\n", | |
"[/var/log/cloud-init.log]\n", | |
"file = /var/log/cloud-init.log\n", | |
"log_group_name = ", { "Ref": "CloudFormationLogs" }, "\n", | |
"log_stream_name = {instance_id}/cloud-init.log\n", | |
"datetime_format = \n", | |
"[/var/log/cloud-init-output.log]\n", | |
"file = /var/log/cloud-init-output.log\n", | |
"log_group_name = ", { "Ref": "CloudFormationLogs" }, "\n", | |
"log_stream_name = {instance_id}/cloud-init-output.log\n", | |
"datetime_format = \n", | |
"[/var/log/cfn-init.log]\n", | |
"file = /var/log/cfn-init.log\n", | |
"log_group_name = ", { "Ref": "CloudFormationLogs" }, "\n", | |
"log_stream_name = {instance_id}/cfn-init.log\n", | |
"datetime_format = \n", | |
"[/var/log/cfn-hup.log]\n", | |
"file = /var/log/cfn-hup.log\n", | |
"log_group_name = ", { "Ref": "CloudFormationLogs" }, "\n", | |
"log_stream_name = {instance_id}/cfn-hup.log\n", | |
"datetime_format = \n", | |
"[/var/log/cfn-wire.log]\n", | |
"file = /var/log/cfn-wire.log\n", | |
"log_group_name = ", { "Ref": "CloudFormationLogs" }, "\n", | |
"log_stream_name = {instance_id}/cfn-wire.log\n", | |
"datetime_format = \n", | |
"[/var/log/httpd]\n", | |
"file = /var/log/httpd/*\n", | |
"log_group_name = ", { "Ref": "CloudFormationLogs" }, "\n", | |
"log_stream_name = {instance_id}/httpd\n", | |
"datetime_format = %d/%b/%Y:%H:%M:%S\n" | |
] ] }, | |
"mode": "000444", | |
"owner": "root", | |
"group": "root" | |
}, | |
"/etc/awslogs/awscli.conf": { | |
"content": { "Fn::Join": [ "", [ | |
"[plugins]\n", | |
"cwlogs = cwlogs\n", | |
"[default]\n", | |
"region = ", { "Ref" : "AWS::Region" }, "\n" | |
] ] }, | |
"mode": "000444", | |
"owner": "root", | |
"group": "root" | |
} | |
} | |
}, | |
"installApp": { | |
"packages" : { | |
"python" : { | |
"awscli":[] | |
}, | |
"yum" : { | |
"git-all":[] | |
} | |
}, | |
"files" : { | |
"/etc/aws.conf" : { | |
"content" : { "Fn::Join" : ["\n", [ | |
"[default]", | |
"aws_access_key_id={{access_key}}", | |
"aws_secret_access_key={{secret_key}}" | |
]]}, | |
"context" : { | |
"access_key" : { "Ref" : "HostKeys" }, | |
"secret_key" : { "Fn::GetAtt" : ["HostKeys", "SecretAccessKey"]} | |
}, | |
"mode" : "000700", | |
"owner" : "root", | |
"group" : "root" | |
}, | |
"/usr/local/bin/jenkins-restore" : { | |
"content" : { "Fn::Join" : ["\n", [ | |
"#!/bin/bash -e", | |
"", | |
"USAGE=\"Usage: $0 S3_TARGET JENKINS_HOME\\n", | |
"\\n", | |
"Example:\\n", | |
"$0 s3://mybucket/jenkins/jenkins-201405011901.tar.gz /var/lib/jenkins\\n", | |
"\\n", | |
"If S3_TARGET is a directory, restore from the newest file. Make sure to include the trailing slash:\\n", | |
"$0 s3://mybucket/jenkins/ /var/lib/jenkins\"", | |
"", | |
"S3_TARGET=$1", | |
"JENKINS_HOME=$2", | |
"if [[ -z \"`echo $S3_TARGET|grep '^s3://'`\" ]]; then", | |
" echo -e $USAGE", | |
" exit 1", | |
"fi", | |
"", | |
"if [[ \"$S3_TARGET\" == */ ]]; then", | |
" S3_TARGET=$S3_TARGET`aws s3 ls $S3_TARGET|tail -1|awk '{print $NF}'`", | |
"fi", | |
"", | |
"LOCAL_BACKUP=/tmp/`basename $S3_TARGET`", | |
"aws s3 cp $S3_TARGET $LOCAL_BACKUP", | |
"", | |
"rm -rf $JENKINS_HOME", | |
"#if [[ -d \"$JENKINS_HOME\" ]]; then", | |
"# read -p \"Delete existing $JENKINS_HOME? (y/n) \" -n 1 -r", | |
"# echo", | |
"# if [[ $REPLY =~ ^[Yy]$ ]]; then", | |
"# rm -rf $JENKINS_HOME", | |
"# else", | |
"# echo \"Bailing out\"", | |
"# exit 1", | |
"# fi", | |
"#fi", | |
"", | |
"mkdir -p $JENKINS_HOME", | |
"tar zxf $LOCAL_BACKUP -C $JENKINS_HOME", | |
"rm -f $LOCAL_BACKUP" | |
]]}, | |
"mode" : "000755", | |
"owner" : "root", | |
"group" : "root" | |
}, | |
"/usr/local/bin/jenkins-backup" : { | |
"content" : { "Fn::Join" : ["\n", [ | |
"#!/bin/bash -e", | |
"", | |
"USAGE=\"Usage: $0 JENKINS_HOME S3_TARGET\\n", | |
"\\n", | |
"Examples:\\n", | |
"$0 /var/lib/jenkins s3://mybucket/jenkins/jenkins-201405011901.tar.gz\"", | |
"", | |
"JENKINS_HOME=$1", | |
"S3_TARGET=$2", | |
"if [[ -z \"`echo $S3_TARGET|grep '^s3://'`\" || ! -d \"$JENKINS_HOME\" ]]; then", | |
" echo -e $USAGE", | |
" exit 1", | |
"fi", | |
"", | |
"LOCAL_BACKUP=/tmp/`basename $S3_TARGET`", | |
"", | |
"tar -C $JENKINS_HOME -zcf $LOCAL_BACKUP .\\", | |
" --exclude \"config-history/\" \\", | |
" --exclude \"config-history/*\" \\", | |
" --exclude \"jobs/*/workspace*\" \\", | |
" --exclude \"jobs/*/builds/*/archive\" \\", | |
" --exclude \"plugins/*/*\" \\", | |
" --exclude \"plugins/*.bak\" \\", | |
" --exclude \"war\" \\", | |
" --exclude \"cache\"", | |
"", | |
"aws s3 cp $LOCAL_BACKUP $S3_TARGET", | |
"rm -f $LOCAL_BACKUP" | |
]]}, | |
"mode" : "000755", | |
"owner" : "root", | |
"group" : "root" | |
}, | |
"/etc/cron.daily/jenkins" : { | |
"content" : { "Fn::Join" : ["\n", [ | |
"#!/bin/bash\n", | |
"AWS_CONFIG_FILE=/etc/aws.conf\n", | |
"PATH=/bin:/usr/bin::/usr/local/bin\n", | |
"source /usr/local/bin/jenkins-backup /var/lib/jenkins s3://{{s3_bucket}}/{{s3_prefix}}jenkins-`date +\\%Y\\%m\\%d\\%H\\%M.tar.gz` >> /var/log/jenkins-backup.log 2>&1\n", | |
"echo \"ec2-terminate-instances $(curl -s http://169.254.169.254/latest/meta-data/instance-id)\" | at now + 15 min" | |
]]}, | |
"context" : { | |
"s3_bucket" : { "Ref" : "S3Bucket"}, | |
"s3_prefix" : { "Ref" : "S3Prefix"} | |
}, | |
"mode" : "000755", | |
"owner" : "root", | |
"group" : "root" | |
} | |
} | |
} | |
} | |
}, | |
"Properties" : { | |
"KeyName" : { "Ref" : "SshKey" }, | |
"IamInstanceProfile": { "Ref" : "BuildInstanceProfile" }, | |
"ImageId" : { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "AMI"] }, | |
"SecurityGroups" : [ { "Ref" : "ServerSecurityGroup" }, { "Ref": "AdminSecurityGroup" } ], | |
"InstanceType" : { "Ref" : "InstanceType" }, | |
"UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ | |
"#!/bin/bash -xe\n", | |
"# Helper function\n", | |
"function error_exit\n", | |
"{\n", | |
" cfn-signal -e 1 -r \"$1\" '", { "Ref" : "WaitHandle" }, "'\n", | |
" exit 1\n", | |
"}\n", | |
"/opt/aws/bin/cfn-init --stack ", { "Ref" : "AWS::StackName" }, | |
" --resource LaunchConfig", | |
" --configsets install", | |
" --access-key ", { "Ref" : "HostKeys" }, | |
" --secret-key ", {"Fn::GetAtt": ["HostKeys", "SecretAccessKey"]}, | |
" --region ", { "Ref" : "AWS::Region" }, " || error_exit 'Failed to run cfn-init'\n", | |
"# Post-cfn work\n", | |
"sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins.io/redhat-stable/jenkins.repo\n", | |
"sudo rpm --import http://pkg.jenkins.io/redhat-stable/jenkins.io.key\n", | |
"yum install -y jenkins\n", | |
"# Handle case where cron doesn't detect the new /etc/cron.d file\n", | |
"#service cron restart\n", | |
"# Attempt to restore from backup\n", | |
"export AWS_CONFIG_FILE=/etc/aws.conf\n", | |
"sudo /usr/local/bin/jenkins-restore s3://",{ "Ref": "S3Bucket" },"/",{ "Ref": "S3Prefix" }," /var/lib/jenkins || true # ignore errors\n", | |
"sudo /etc/init.d/jenkins start\n", | |
"sudo chkconfig jenkins on\n", | |
"# Start Jenkins\n", | |
"# All is well, signal success\n", | |
"cfn-signal -e 0 -r \"Stack setup complete\" '", { "Ref" : "WaitHandle" }, "'\n", | |
"#EOF" | |
]]}} | |
} | |
}, | |
"LbSecurityGroup" : { | |
"Type" : "AWS::EC2::SecurityGroup", | |
"Properties" : { | |
"GroupDescription" : "Jenkins LBs", | |
"VpcId" : { "Ref" : "VpcId" }, | |
"SecurityGroupIngress" : | |
[ { "IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0" }] | |
} | |
}, | |
"ServerSecurityGroup" : { | |
"Type" : "AWS::EC2::SecurityGroup", | |
"Properties" : { | |
"GroupDescription" : "Jenkins servers", | |
"VpcId" : { "Ref" : "VpcId" }, | |
"SecurityGroupIngress" : | |
[ { "IpProtocol" : "tcp", "FromPort" : "8080", "ToPort" : "8080", "CidrIp" : "0.0.0.0/0" }] | |
} | |
}, | |
"ElasticLoadBalancer" : { | |
"Type" : "AWS::ElasticLoadBalancing::LoadBalancer", | |
"Properties" : { | |
"SecurityGroups": [{ "Ref": "LbSecurityGroup" }, { "Ref": "AdminSecurityGroup" }], | |
"Subnets": { "Ref": "Subnets" }, | |
"Listeners" : [ { | |
"LoadBalancerPort" : "80", | |
"InstancePort" : "8080", | |
"Protocol" : "HTTP" | |
} ], | |
"HealthCheck" : { | |
"Target" : "TCP:8080", | |
"HealthyThreshold" : "3", | |
"UnhealthyThreshold" : "5", | |
"Interval" : "30", | |
"Timeout" : "5" | |
} | |
} | |
}, | |
"DnsRecord" : { | |
"Type" : "AWS::Route53::RecordSet", | |
"Properties" : { | |
"HostedZoneName" : { "Fn::Join" : [ "", [{"Ref" : "DnsZone"}, "." ]]}, | |
"Name" : { "Fn::Join" : [ "", [{"Ref" : "DnsPrefix"}, ".", {"Ref" : "DnsZone"}, "."]]}, | |
"Type" : "CNAME", | |
"TTL" : "900", | |
"ResourceRecords" : [ { "Fn::GetAtt" : [ "ElasticLoadBalancer", "DNSName" ] } ] | |
} | |
}, | |
"WaitHandle" : { | |
"Type" : "AWS::CloudFormation::WaitConditionHandle" | |
} | |
}, | |
"Outputs" : { | |
"DnsAddress" : { | |
"Description" : "Jenkins URL", | |
"Value" : { "Fn::Join" : ["", [ | |
"http://", { "Ref" : "DnsRecord" } | |
]]} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment