Learning CloudFormation
by Vikas Srivastava
Opinions expressed are solely my own and do not express the views or opinions of my employer.
1:33 Speed / kcfnuser / D
Good link for CFN IDE Tips https://hodgkins.io/up-your-cloudformation-game-with-vscode
A wordpress stack typically consists of the following:
JSON
- JSON stands for “JavaScript Object Notation”
- Used heavility within the AWS Product set
- JSON is constructd of name/value pair
- Data is seperated by commas
- Value can be strin,number,boolean,object,array or null
[]
Square brackets holds arrays- Cury Braces {} Holds JSON
Null
in its purest form is not used in CloudFormation.
In the example below notice that the
ProductID
variable is used twice , which is fine since it is under two different JSON objects (Curly Brackets) Same variable cannot be used at the same JSON depth
{
"OrderID": 12345,
"ShopperName" : "Reyansh",
"Email": "email@gmail.com",
"OrderData" : [
{
"ProductID": "12",
"Quantity": "1"
},
{
"ProductID":"34",
"Quantity": 2
}
]
}
Below is an example of a YAML CloudFormation Template
Notice that under
Listeners
we have 2-
; which means start of two different lists
myELBname:
Type: AWS::ElasticLoadBalancing::LoadBalancer
Properties:
Scheme: internal
SecurityGroups:
- sg-id
Subnets:
- subnet-id
Instances:
- instance-id
Listeners:
- LoadBalancerPort:
InstancePort:
Protocol: HTTP|HTTPS|TCP|SSL
InstanceProtocol: HTTP|HTTPS|TCP|SSL
- LoadBalancerPort: '443'
InstancePort: '443'
Protocol: HTTPS
InstanceProtocol: HTTPS
SSLCertificateId:
Lets start our first cloudformation template with the following :
Resources:
DB:
Type: "AWS::RDS::DBInstance"
Properties:
AllocatedStorage: 2
StorageType: gp2
DBInstanceClass: db.t2.micro
DBName: wordpress
Engine: MySQL
MasterUsername: root
MasterUserPassword: redhat123
EC2:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-343243423434
InstanceType: t2.micro
S3:
Type: "AWS::S3::Bucket"
Properties:
BucketName: wp-vikassri12312312313
Thats a good start up template but lets look at the challenges in re-use
of the template .
- Everytime this template is used ,
DBInstanceClass
andInstanceType
has hardcoded values , which as per the customer requirement should be Small ,Medium , Large ; which cin this case is static.- The instance type is specific to a region and the CloudFormation template is not portable across regions.
- Also the S3 bucket is static , so the name would conflict.
The way we fix the above issues is with the following script :
Parameters:
EnvironmentSize:
Type: String
Default: SMALL
AllowedValues:
- SMALL
- MEDIUM
- LARGE
Description: Select Environment Size (S,M,L)
Mappings:
RegionMap:
us-east-1:
"AMALINUX" : "ami-c481fad3"
us-east-2:
"AMALINUX" : "ami-71ca9114"
us-west-1:
"AMALINUX" : "ami-de347abe"
us-west-2:
"AMALINUX" : "ami-b04e92d0"
InstanceSize:
SMALL:
"EC2" : "t2.micro"
"DB" : "db.t2.micro"
MEDIUM:
"EC2" : "t2.small"
"DB" : "db.t2.small"
LARGE:
"EC2" : "t2.medium"
"DB" : "db.t2.medium"
Resources:
DB:
Type: "AWS::RDS::DBInstance"
Properties:
AllocatedStorage: 5
StorageType: gp2
DBInstanceClass: !FindInMap [InstanceSize, !Ref EnvironmentSize, DB]
DBName: wordpress
Engine: MySQL
MasterUsername: wordpress
MasterUserPassword: w0rdpr355
EC2:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMALINUX]
InstanceType: !FindInMap [InstanceSize, !Ref EnvironmentSize, EC2]
S3:
Type: "AWS::S3::Bucket"
- In the above Code Template we used
Mapping
tables to reuse variables and make the template more dynamic - The
!FindInMap
references the Table and looks for the value there in the table
In the code example below , take a look at the following :
Fn::Base64"
: Allows to put incloud-init
dataSub
: Function for string substitution$Variables
: Used below in thesed
line to make thecloud-init
dynamic.
Keypair below "AdvancedCFN" is used to reference an already created SSH key.
EC2:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMALINUX] # Dynamic mapping + Pseudo Parameter
InstanceType: !FindInMap [InstanceSize, !Ref EnvironmentSize, EC2]
KeyName: AdvancedCFN
UserData:
"Fn::Base64":
!Sub |
#!/bin/bash
yum install httpd php mysql php-mysql -y
yum update -y
chkconfig httpd on
service httpd start
cd /var/www/html
wget https://wordpress.org/latest.tar.gz
tar -zxvf latest.tar.gz --strip 1
rm latest.tar.gz
cp wp-config-sample.php wp-config.php
sed -i 's/database_name_here/${DatabaseName}/g' wp-config.php
sed -i 's/localhost/${DB.Endpoint.Address}/g' wp-config.php
sed -i 's/username_here/${DatabaseUser}/g' wp-config.php
sed -i 's/password_here/${DatabasePassword}/g' wp-config.php
What the above means in simple terms
Note that in above example we have the
DB.Endpoint.Address
is required to be createdbefore
theEC2
instance can be created. Hence CloudFormation creates the DB first and then the EC2
The above is done automatically by the AWS systems
As a best practice , we should declare the dependency manually using the DependsOn
attribute. Notice in the example below under EC2 , we say DependsOn: DB
which basically means that EC2 would have to wait for the DB to done first before the EC2 is created.
Resources:
DB:
Type: "AWS::RDS::DBInstance"
Properties:
AllocatedStorage: 5
StorageType: gp2
DBName: wordpress
EC2:
Type: "AWS::EC2::Instance"
Properties:
DependsOn: DB
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMALINUX]
InstanceType: !FindInMap [InstanceSize, !Ref EnvironmentSize, EC2]
CloudFormationInit
Benefits
cfn-init
is OS independentcfn-init
is a desired state engine. Like Ansible you define that you needhttp
server ; you dont have to install , start the service etc . !!
Example CloudFormation Init Template
Resources:
MyInstance:
Type: AWS::EC2::Instance
Metadata:
AWS::CloudFormation::Init:
config:
packages:
:
groups:
:
users:
:
sources:
:
files:
:
commands:
:
services:
- The
Metadata
tag is at the same level as the Instance Type tag - The CloudFormation::Init starts by default with
config
if notconfigsets
are defined packages
: Define the packages to be installedgroups
: user groups be presentusers
: users to be presentsources
: Ensures to allow certain files stored on the instancefile
: Create , Modify , delete and control file content.commands
: Allows you to run commands .services
: Enables service declarations. service restart stop etc.
EC2:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMALINUX] # Dynamic mapping + Pseudo Parameter
InstanceType: !FindInMap [InstanceSize, !Ref EnvironmentSize, EC2]
KeyName: AdvancedCFN
UserData:
"Fn::Base64":
!Sub |
#!/bin/bash
yum update -y aws-cfn-bootstrap # good practice - always do this.
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource EC2 --configsets wordpress --region ${AWS::Region}
yum -y update
CFN Hup is a sister utility for CFN Init. It listens for a change on the meta data for the instance. When the metadat changes , CFN Hup can update the instances based on the updated Metadata.
Subscribe via RSS