Step-by-Step Walkthrough¶
Use this step-by-step guide as a comprehensive walkthrough of stacks. The goal of this guide is to build a simple deployment and then gradually create a more complex deployment using features of stacks that you will probably reuse in your own deployments.
This guide uses a Linux command-line tool and assumes you are familiar with YAML syntax and are comfortable running commands on a Linux command-line window.
Note
If this is your first time using stacks, try the Getting Sarted Guide first.
Installation and Setup¶
Follow the setup steps to prepare your local environment to use stacks.
Configure Authentication¶
Stacks supports standard AWS SDK configuration, which means you can interact with multiple AWS accounts via profiles.
Be sure to replace access keys with your own ones.
AWS credentials file¶
$ install -d -m 0700 ~/.aws
$ vim ~/.aws/credentials
[project1]
aws_access_key_id = AKIAIqH2jHP2BGMKPikN
aws_secret_access_key = HFq+nQfq59yStF+qLl6/rW0AHFq+nQfq59yStF+q
output = json
region = us-east-1
[project2]
aws_access_key_id = AKIAIj2UPlaLJgMmNejS
aws_secret_access_key = UZSYq2dzZqLzc1s+sXENnzEmUZSYq2dzZqLzc1s+
output = json
As you can see project2
profile does not have region specified, in this
case, the region will have to either be provided via AWS_DEFAULT_REGION
environment variable or command line argument stacks --region us-west-1
.
Environment variables¶
export AWS_ACCESS_KEY_ID="AKIAIqH2jHP2BGMKPikN"
export AWS_SECRET_ACCESS_KEY="HFq+nQfq59yStF+qLl6/rW0AHFq+nQfq59yStF+q"
export AWS_DEFAULT_REGION="us-east-1"
Now that you have set up your environment, you can begin constructing your deployment.
Create a Deployment¶
For this guide, let’s define a set of templates that will create a new multi-AZ VPC and an autoscaling group to launch an instance in the new VPC.
If you were working with native CloudFormation JSON templates, you would have to define all your AWS resources for this deployment in a single JSON template, because CloudFormation JSON templates are static - there is no way to reference resources created by different CloudFormation templates.
Stacks aims to solve this problem, by allowing you to logically separate out AWS resources. As your infrastructure grows, it makes sense to have your VPC defined in a separate template from S3 buckets or RDS instances.
First of all, we need to create a local directory structure, it will help keep all the files you create in this guide organized:
$ mkdir -p step-by-step/{templates/snippets,config.d}
$ cd step-by-step
Create a properties file¶
If you remember from Getting Sarted Guide guide, we used command
line arguments to specify template properties. In this guide we are going to
create properties configuration file config.yaml
. A properties file allows
us to set key/value pairs and reference values from within templates.
$ vim config.yaml
---
vpc_name: MyVPC
vpc_cidr: 10.10.0.0/16
vpc_subnets:
- 10.10.0.0/24
- 10.10.1.0/24
- 10.10.2.0/24
ami_name: CoreOS-beta-877.1.0-hvm
instance_type: t3.micro
Define a VPC template¶
From this point, we assume that you are familiar with CloudFormation templates. Let’s jump right in and start by creating a VPC template.
$ vim templates/vpc.yaml
---
name: {{ env }}-vpc
disable_rollback: true
tags:
- key: Project
value: step-by-step
---
AWSTemplateFormatVersion: '2010-09-09'
Description: VPC stack in {{ env }} environment
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: {{ vpc_cidr }}
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: {{ vpc_name }}
- Key: Env
Value: {{ env }}
DefaultSG:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId:
Ref: VPC
GroupDescription: {{ env }}-vpc Default SG
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
FromPort: -1
ToPort: -1
Tags:
- Key: Name
Value: {{ env }}-default
- Key: Env
Value: {{ env }}
# Allow traffic within subnets
AllTrafficSGIn:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId:
Ref: DefaultSG
IpProtocol: -1
SourceSecurityGroupId:
Ref: DefaultSG
FromPort: -1
ToPort: -1
# Create a subnet in each AZ
{% for n in vpc_subnets %}
Subnet{{ loop.index0 }}:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- '{{ loop.index0 }}'
- Fn::GetAZs: ''
VpcId:
Ref: VPC
CidrBlock: {{ n }}
Tags:
- Key: Name
Value: {{ env }}-az{{ loop.index0 }}
- Key: Env
Value: {{ env }}
SubnetRouteTableAssociation{{ loop.index0 }}:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: Subnet{{ loop.index0 }}
RouteTableId:
Ref: RouteTable
{%- endfor %}
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: {{ env }}-igw
- Key: Env
Value: {{ env }}
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: VPC
InternetGatewayId:
Ref: InternetGateway
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPC
Tags:
- Key: Name
Value: {{ env }}-default-routetable
- Key: Env
Value: {{ env }}
Route:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId:
Ref: RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: InternetGateway
There are two YAML documents in the above template. First one is a stack metadata document and the second one is where we define AWS resources. Both documents can be templated using Jinja2 syntax. In fact, the metadata document says that a stack name starts with an environment name, which makes it easy to reuse this template for different environments.
We defined the following AWS resources in the VPC template:
VPC
vpc_cidr
andvpc_name
properties are being referenced from the properties file. Yes, we could hard-code them into the template, but by not doing so, we made our template more flexible.DefaultSG
A default security group with a single rule allowing all egress traffic.
AllTrafficSGIn
Adds an ingress rule to
DefaultSG
to allow all traffic between instances with the same security group.SubnetN
We iterate over
vpc_subnets
and create a subnet in each availability zone.
The rest should be self-explanatory.
Now that we have our VPC template defined, it’s time to create another template to define our auto scaling group.
Define an AutoScaling Group Template¶
$ vim templates/coreos-asg.yaml
---
name: {{ env }}-coreos-asg
tags:
- key: Project
value: step-by-step
---
AWSTemplateFormatVersion: '2010-09-09'
Description: CoreOS ASG in {{ env }} environment
Resources:
ASG:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AvailabilityZones:
{% for n in range(vpc_subnets|count) %}
- Fn::Select:
- '{{ n }}'
- Fn::GetAZs: ''
{% endfor -%}
LaunchConfigurationName:
Ref: LaunchConfiguration
VPCZoneIdentifier:
{% for n in range(vpc_subnets|count) %}
- {{ get_stack_resource(cf_conn, env + '-vpc', 'Subnet' + n|string) }}
{% endfor -%}
TerminationPolicies:
- 'OldestInstance'
- 'Default'
MaxSize: 1
MinSize: 1
Tags:
- Key: Name
Value: {{ env }}-coreos
PropagateAtLaunch: true
- Key: Env
Value: {{ env }}
PropagateAtLaunch: true
UpdatePolicy:
AutoScalingRollingUpdate:
PauseTime: PT1S
LaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
AssociatePublicIpAddress: true
ImageId: {{ get_ami_id(ec2_conn, ami_name) }}
InstanceMonitoring: false
InstanceType: {{ instance_type }}
SecurityGroups:
- {{ get_stack_resource(cf_conn, env + '-vpc', 'DefaultSG') }}
This template demonstrates the power of cross-stack referencing.
In our VPC stack we created 3 subnets, one in each availability zone. An
autoscaling group can dynamically spread instances across availability zones,
but for that to work, it needs to be given a list of availability zones and a
list of VPC subnets corresponding to each particular availability zone. Using
stacks we can simply iterate over a number of vpc_subnets
and get each
subnet’s physical id using get_stack_resource()
helper function.
If you look at LaunchConfiguration
resource definition, you notice that
instead of specifying an AMI id for the CoreOS instances, we used
get_ami_id()
function to get the correct AMI id, by providing just
ami_name
, which we set in our properties file, see
Create a properties file.
This allows us to reuse this template in different regions, because otherwise you would have to create some sort of AMI to region mapping, because AMIs are region specific.
Now that we have both templates defined, let’s deploy them.
Deploy templates¶
We are going to be deploying stacks using project1
profile. If you prefer
using environment variables for configuring AWS authentication, then you
don’t have to specify the profile.
Sample templates requires you to provide an environment name, for this guide, let’s call
it learning
. Properties file is environment-aware and can have a common set
of properties as well as per-environment ones. See Configuration.
There are two ways to set an environment:
STACKS_ENV
environment variable--env
or-e
command line argument to the subcommands
Create the VPC stack:
$ stacks -p project1 create -e learning -t templates/vpc.yaml -f
Wait for the VPC stack to finish creating, because we know that the coreos-asg stack depends on the VPC and other infrastructure resources being there.
$ stacks -p project1 create -e learning -t templates/coreos-asg.yaml -f
Check the status:
$ stacks -p project1 list
learning-coreos-asg CREATE_COMPLETE
learning-vpc CREATE_COMPLETE
Deployment update¶
After you have successfully launched your stacks, you might want to update or change the deployment as your application evolves. Stacks allows you to change a stack by:
- Adding or removing resources to the deployment
- Updating some properties of existing resources in your stack
Remember that stacks uses CloudFormation, which uses the underlying APIs of AWS services to manage resources. If an API doesn’t support a certain action, stacks cannot perform that action either. For example, CloudFormation can only update an existing resource if there is an update method in the corresponding API. Some resources have to be replaced instead.
Let’s change the version CoreOS in config.yaml
, so that it now looks like
this:
---
vpc_name: MyVPC
vpc_cidr: 10.10.0.0/16
vpc_subnets:
- 10.10.0.0/24
- 10.10.1.0/24
- 10.10.2.0/24
ami_name: CoreOS-alpha-899.1.0-hvm
instance_type: t3.micro
Since ami_name
is not used anywhere in VPC stack, we only need to update
the autoscaling group stack:
$ stacks -p project1 update -e learning -t templates/coreos-asg.yaml
This time we didn’t set -f
command line flag to follow events, which means
that stacks fires an API call and exits. But luckily, there is a way to get
events from an existing stack:
$ stacks -p project1 list
learning-coreos-asg UPDATE_IN_PROGRESS
learning-vpc CREATE_COMPLETE
$ stacks -p project1 events -f learning-coreos-asg
Wait until the update has finished, and check the status:
$ stacks --profile project1 list
learning-coreos-asg UPDATE_COMPLETE
learning-vpc CREATE_COMPLETE
Clean up¶
AWS resources incur charges, so you should delete this deployment. Deleting this deployment takes care of deleting all the resources created by both templates.
To delete this deployment:
$ stacks -p project1 delete learning-coreos-asg -f --yes
$ stacks -p project1 delete learning-vpc -f --yes