Skip to content

Instantly share code, notes, and snippets.

@chipx86
Last active May 3, 2018 09:06
Show Gist options
  • Save chipx86/81b8e77bd62cd626de97 to your computer and use it in GitHub Desktop.
Save chipx86/81b8e77bd62cd626de97 to your computer and use it in GitHub Desktop.
Some samples showing our new CloudFormation compilation tool, with includes, macros, variables, functions, and more.
#
# This file contains a list of AMIs that we'll reference throughout our
# templates.
#
# We keep track of the baseline AMIs (Amazon Linux) we use to build clean
# images for our servers. We also keep track of "delta" images (AMIs last
# generated for a particular server image), which we'll bootstrap new
# deployments from.
#
# Note how we're using variable references within the file to avoid repeating
# ourselves. This is indicated by the $${....}.
#
# When we run the bundled create-ami tool for a particular template, the
# template will be compiled with some special properties and outputs. A new
# stack will then be created, and AMIs automatically generated against
# the clean images.
#
# Once this is done, the create-ami tool will automatically find the old
# AMI ID for that server type and replace it with the new one (if passed
# the --update-amis-file option). This makes management of your fleet of
# AMIs much easier.
#
--- !vars
amis:
amazonLinux:
us_east_1:
hvm: ami-146e2a7c
paravirtual: ami-8e682ce6
myServer:
us_east_1:
clean: $${amis.amazonLinux.us_east_1.hvm}
delta: ami-abc1234
#
# In this file, we're defining a set of common variables and macros we'll
# be using in our other templates. A lot of the bulk in our infrastructure
# lives in this file, which is nice, because it means we're not adding that
# bulk to all our other templates.
#
# We're defining some common variables (SNS topics, S3 buckets and paths),
# common template parameters and outputs, common bootstrapping tasks, and
# more.
#
--- !vars
snsTopics:
pagerDuty: 'arn:aws:sns:us-east-1:1234567890:PagerDuty'
s3:
buckets:
deploy: 'arn:aws:s3:::mybucket'
paths:
deploy:
build_tools: "$${s3.buckets.deploy}/build-tools"
config: "$${s3.buckets.deploy}/config"
--- !macros
#
# Common input parameters to templates.
#
# Note how we can define some defaults for these macros. The caller can
# specify their own values.
#
params:
AvailabilityZone:
defaultParams:
default: us-east-1a
content:
AvailabilityZone:
Type: String
Default: $$default
Description: "The availability zone for the server."
Environment:
defaultParams:
default: production
content:
Environment:
Type: String
Default: $$default
Description: "The environment of the server instance."
ConstraintDescription: "must be a valid environment type."
AllowedValues:
- production
- testing
- staging
ImageId:
content:
ImageId:
Type: String
Default: $$default
Description: "The AMI ID to use for the instance. Defaults to
the base AMI for this type of server."
KeyName:
defaultParams:
default: mykey
content:
KeyName:
Type: AWS::EC2::KeyPair::KeyName
Default: $$default
Description:
"The EC2 Key Pair to allow SSH access to the gateway."
ConstraintDescription:
"must be the name of an existing EC2 KeyPair."
MinLength: 1
MaxLength: 255
AllowedPattern: '[\x20-\x7E]*'
ServerName:
content:
ServerName:
Type: String
Default: $$default
Description: "The name of the server instance."
#
# Alarms that will notify us on PagerDuty when bad things happen.
#
alarms:
ec2:
# Notifies when the CPU usage is above 80%.
HighCPUAlarm:
defaultParams:
serverName: "@@ServerName"
serverRef: "@@WebServer"
period: 300
evaluationPeriods: 1
threshold: 80
notifyARNs:
- $${snsTopics.pagerDuty}
content:
HighCPUAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmDescription: "[$$serverName] Alert if CPU > 80% for 5 minutes"
MetricName: CPUUtilization
Namespace: AWS/EC2
Statistic: Average
Period: $$period
EvaluationPeriods: $$evaluationPeriods
Threshold: $$threshold
ComparisonOperator: GreaterThanOrEqualToThreshold
AlarmActions: $$notifyARNs
InsufficientDataActions: $$notifyARNs
Dimensions:
- Name: InstanceId
Value: $$serverRef
#
# Common IAM setup rules.
#
# A neat trick below: Note how we can use a variable for a key in a macro.
# This allows us to pass in custom names for resources.
#
iam:
ec2Setup:
defaultParams:
roleName: null
instanceProfileName: null
policyName: null
statements: null
content:
$$roleName:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- "sts:AssumeRole"
Path: /
Policies:
- PolicyName: $$policyName
PolicyDocument:
Version: "2012-10-17"
Statement: $$statements
$$instanceProfileName:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- Ref: $$roleName
#
# Common macro outputs.
#
# Note below in publicAddress, how we're calling the GetAtt function against
# the resource name provided to the template (or "Server", if not provided).
#
outputs:
instanceID:
defaultParams:
outputName: InstanceID
server: "@@Server"
content:
$$outputName:
Description: The ID of the server instance.
Value: $$server
publicAddress:
defaultParams:
outputName: PublicAddress
serverResourceName: Server
content:
$$outputName:
Description: The public address of the server instance.
Value: <% GetAtt($$serverResourceName, "PublicDnsName") %>
#
# Macros for handling the bootstrapping of an instance.
#
# We make use of the !cloud-init directive to easily upload a MIME archive
# containing both cloud-init configuration and a bootstrapper shell script.
#
# We're also making use of flexible expressions below to determine if
# swap-related rules should be set. These will be compiled into registered
# Conditions, and referenced in the appropriate place.
#
bootstrapper:
resources:
defaultParams:
timeout: 2400
dependsOn: Server
content:
WaitHandle:
Type: AWS::CloudFormation::WaitConditionHandle
WaitCondition:
Type: AWS::CloudFormation::WaitCondition
DependsOn: $$dependsOn
Properties:
Handle: '@@WaitHandle'
Timeout: $$timeout
script:
defaultParams:
hostname: "@@ServerName"
useSwap: true
content: !cloud-init
config: |
#cloud-config
<% If ($$useSwap == true) { %>
mounts:
- [ ephemeral0, none, swap, sw, 0, 0 ]
bootcmd:
- mkswap /dev/xvdb
- swapon /dev/xvdb
<% } %>
script: |
#!/bin/bash -v
yum update -y
# XXX Do some stuff here for bootstrapping the server.
/opt/aws/bin/cfn-signal -e 0 -r "Bootstrap complete." \
'@@WaitHandle'
#
# A template for a server.
#
# This will start out by importing variables and macros from our amis.yaml
# and defs.yaml files. We can then make use of them below.
#
# Note also that while this mostly follows the format of CloudFormation JSON
# files, we do have a special Meta section that contains version information.
# Soon, it will also contain destination S3 bucket/path information, to make
# it easy to upload compiled templates. Anything can really go in this section.
#
__imports__:
!import amis.yaml
defs.yaml
Meta:
Description: My Server Instance
Version: 1.0
Parameters:
# Embed some parameters from defs.yaml. The "<" will replace the entire
# !call-macro key/value pair with the contents of the macro.
<: !call-macro
macro: params.KeyName
<: !call-macro
macro: params.AvailabilityZone
<: !call-macro
macro: params.ServerName
default: myserver.example.com
<: !call-macro
macro: params.Environment
Mappings:
RegionMap:
us-east-1:
<: $${amis.myServer.us_east_1}
Resources:
<: !call-macro
macro: iam.ec2Setup
roleName: MyRole
instanceProfileName: MyInstanceProfile
policyName: my_policy_name
statements:
- Effect: Allow
Action:
- 's3:ListBucket'
Resource: $${s3.buckets.deploy}
- Effect: Allow
Action:
- 's3:GetObjects'
- 's3:ListObjects'
Resource:
- $${s3.paths.deploy.build_tools}/*
- $${s3.paths.deploy.config}/*
- Effect: Allow
Action:
- 's3:ListObjects'
Resource:
- $${s3.paths.deploy.build_tools}/
- $${s3.paths.deploy.config}/
MyServer:
Type: AWS::EC2::Instance
Properties:
AvailabilityZone: "@@AvailabilityZone"
ImageId: <% FindInMap(RegionMap, @@AWS::Region, delta) %>
IamInstanceProfile: my_profile_name
InstanceType: m3.medium
KeyName: "@@KeyName"
SecurityGroupIds:
- Secret Group
- Super Secret Group
Tags: !tags
Name: "@@ServerName"
Environment: "@@Environment"
FavoriteColor: blue
UserData: !call-macro
macro: bootstrapper.script
useSwap: false
<: !call-macro
macro: alarms.ec2.HighCPUAlarm
serverRef: "@@MyServer"
<: !call-macro
macro: bootstrapper.resources
timeout: 2400
dependsOn: MyServer
Outputs:
<: !call-macro
macro: outputs.instanceID
server: "@@MyServer"
<: !call-macro
macro: outputs.publicAddress
serverResourceName: MyServer
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "My Server Instance [v1.0]",
"Parameters": {
"KeyName": {
"Type": "AWS::EC2::KeyPair::KeyName",
"Default": "mykey",
"Description": "The EC2 Key Pair to allow SSH access to the gateway.",
"ConstraintDescription": "must be the name of an existing EC2 KeyPair.",
"MinLength": "1",
"MaxLength": "255",
"AllowedPattern": "[\\x20-\\x7E]*"
},
"AvailabilityZone": {
"Type": "String",
"Default": "us-east-1a",
"Description": "The availability zone for the server."
},
"ServerName": {
"Type": "String",
"Default": "myserver.example.com",
"Description": "The name of the server instance."
},
"Environment": {
"Type": "String",
"Default": "production",
"Description": "The environment of the server instance.",
"ConstraintDescription": "must be a valid environment type.",
"AllowedValues": [
"production",
"testing",
"staging"
]
}
},
"Mappings": {
"RegionMap": {
"us-east-1": {
"hvm": "ami-146e2a7c"
}
}
},
"Conditions": {
"IfCondition1": {
"Fn::Equals": [
"false",
"true"
]
}
},
"Resources": {
"MyRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"ec2.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"Path": "/",
"Policies": [
{
"PolicyName": "my_policy_name",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::mybucket"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObjects",
"s3:ListObjects"
],
"Resource": [
"arn:aws:s3:::mybucket/build-tools/*",
"arn:aws:s3:::mybucket/config/*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:ListObjects"
],
"Resource": [
"arn:aws:s3:::mybucket/build-tools/",
"arn:aws:s3:::mybucket/config/"
]
}
]
}
}
]
}
},
"MyInstanceProfile": {
"Type": "AWS::IAM::InstanceProfile",
"Properties": {
"Path": "/",
"Roles": [
{
"Ref": "MyRole"
}
]
}
},
"MyServer": {
"Type": "AWS::EC2::Instance",
"Properties": {
"AvailabilityZone": {
"Ref": "AvailabilityZone"
},
"ImageId": {
"Fn::FindInMap": [
"RegionMap",
{
"Ref": "AWS::Region"
},
"hvm"
]
},
"IamInstanceProfile": "my_profile_name",
"InstanceType": "m3.medium",
"KeyName": {
"Ref": "KeyName"
},
"SecurityGroupIds": [
"Secret Group",
"Super Secret Group"
],
"Tags": [
{
"Key": "Environment",
"Value": {
"Ref": "Environment"
}
},
{
"Key": "FavoriteColor",
"Value": "blue"
},
{
"Key": "Name",
"Value": {
"Ref": "ServerName"
}
}
],
"UserData": {
"Fn::Base64": {
"Fn::Join": [
"",
[
"Content-Type: multipart/mixed; boundary=\"===============2834702784899265975==\"\n",
"MIME-Version: 1.0\n",
"\n",
"--===============2834702784899265975==\n",
"Content-Type: text/cloud-config\n",
"MIME-Version: 1.0\n",
"Content-Disposition: attachment; filename=\"cloud.cfg\"\n",
"\n",
"#cloud-config\n",
"\n",
{
"Fn::If": [
"IfCondition1",
{
"Fn::Join": [
"",
[
"mounts:\n",
" - [ ephemeral0, none, swap, sw, 0, 0 ]\n",
"\n",
"bootcmd:\n",
" - mkswap /dev/xvdb\n",
" - swapon /dev/xvdb\n"
]
]
},
{
"Ref": "AWS::NoValue"
}
]
},
"--===============2834702784899265975==\n",
"Content-Type: text/x-shellscript\n",
"MIME-Version: 1.0\n",
"Content-Disposition: attachment; filename=\"script.sh\"\n",
"\n",
"#!/bin/bash -v\n",
"yum update -y\n",
"\n",
"function error_exit {\n",
" /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '",
{
"Ref": "WaitHandle"
},
"'\n",
" exit 1\n",
"}\n",
"\n",
"# XXX Do some stuff here for bootstrapping the server.\n",
"\n",
"/opt/aws/bin/cfn-signal -e 0 -r \"Bootstrap complete.\" '",
{
"Ref": "WaitHandle"
},
"'\n",
"--===============2834702784899265975==--\n"
]
]
}
}
}
},
"HighCPUAlarm": {
"Type": "AWS::CloudWatch::Alarm",
"Properties": {
"AlarmDescription": {
"Fn::Join": [
"",
[
"[",
{
"Ref": "ServerName"
},
"] Alert if CPU > 80% for 5 minutes"
]
]
},
"MetricName": "CPUUtilization",
"Namespace": "AWS/EC2",
"Statistic": "Average",
"Period": "300",
"EvaluationPeriods": "1",
"Threshold": "80",
"ComparisonOperator": "GreaterThanOrEqualToThreshold",
"AlarmActions": [
"arn:aws:sns:us-east-1:1234567890:PagerDuty"
],
"InsufficientDataActions": [
"arn:aws:sns:us-east-1:1234567890:PagerDuty"
],
"Dimensions": [
{
"Name": "InstanceId",
"Value": {
"Ref": "MyServer"
}
}
]
}
},
"WaitHandle": {
"Type": "AWS::CloudFormation::WaitConditionHandle"
},
"WaitCondition": {
"Type": "AWS::CloudFormation::WaitCondition",
"DependsOn": "MyServer",
"Properties": {
"Handle": {
"Ref": "WaitHandle"
},
"Timeout": "2400"
}
}
},
"Outputs": {
"InstanceID": {
"Description": "The ID of the server instance.",
"Value": {
"Ref": "MyServer"
}
},
"PublicAddress": {
"Description": "The public address of the server instance.",
"Value": {
"Fn::GetAtt": [
"MyServer",
"PublicDnsName"
]
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment