DevOps on AWS – Building an AMI Bakery

There’s a relatively new concept in the IT world called immutable infrastructure.  This is the idea that once you create a server you should never change it’s running configuration.  The advantages of this approach include: avoidance of configuration drift; no need to patch running systems; and no need for privileged access to running systems.

Configuration drift is where, over time, administrators log on to running systems and make changes.  Unfortunately these changes are often undocumented and in some cases not persisted, so they aren’t applied on reboot.  This leads to lots of unique servers which are impossible to manage at scale.

Everyone should be familiar with the idea of patching running servers.  In my experience performing patching of live systems never goes smoothly, often due to the aforementioned configuration drift.  If we don’t need to change the configuration of a running server, nor to patch it, then we’ve reached the system where there’s no need to log on as root or administrator.  This is great news for tightly regulated organisations who often have to worry about privileged insider threats and spend vast sums of money to build systems that monitor what their administrators are doing.

The way to create immutable infrastructure, and to achieve these benefits, is to create a master image and use this to instantiate all of your servers.  If you want to modify a server, changing it’s configuration or patching it, then you update your master image and redeploy your servers in a rolling upgrade.  This may sound like a lot of work, but by adopting the processes and tooling of DevOps it’s actually quite simple to get up and running.

I’m doing a lot of work with Amazon Web Services (AWS) at the moment and their master images are called Amazon Machine Images (AMI).  AWS also provides a number of DevOps tools that we can use to automate the process of creating AMIs.

Building an AMI with Packer

I started out by creating an AMI manually using the Packer tool from Hashicorp.  Packer is an opensource application written in Go that is design to automate the production of machine images.  The images are generated by taking a base image and then customising it based on a configuration file.  For the purposes of my proof of concept I used the following Packer configuration file:

{
    "builders": [{
        "type": "amazon-ebs",
        "region": "eu-west-1",
        "vpc_id": "vpc-4925e72e",
        "subnet_id": "subnet-4d13d12a",
        "source_ami": "ami-01ccc867",
        "instance_type": "t2.micro",
        "ssh_username": "ec2-user",
        "ami_name": "yum-upgrade {{timestamp}}"
    }],
    "provisioners": [{
        "type": "shell",
        "inline": [
            "sleep 30",
            "sudo yum update -y"
        ]
    }]
}

The first part of the file, the builder, describes how the image will be built.  In this example I am building an “amazon-ebs” image, i.e. an AMI backed with an Elastic Block Storage filesystem.  The other values specify things like the AWS region, VPC, and EC2 instance type that will be used for the build process.  One of the key fields is “source_ami”, this field specifies the base AMI to use, here I am using the latest Amazon Linux AMI available at the time of writing.

The second part of the file, the provisioner, describes how the base image should be customised.  In this example all I am doing is running YUM to apply all of the available package updates using an inline shell provisioner.  There are lots of other provisioners described in the Packer documentation that may be more useful for complex configurations.

The other prerequisite that you need is a set of valid AWS credentials.  Check the AWS documentation on how to set these up.

Once you’ve got your credentials configured you should save the configuration file as packer.json, and you can then check it’s validity by running:

packer validate packer.json

Assuming there’s no syntax errors, building an AMI is as simple as:

packer build packer.json

The build might take a while to run, but once it’s finished you should be able to look at the AMIs section of the EC2 web console and see your newly baked image!

Automating the Process

The source code for my proof of concept AMI bakery is available from my GitHub account.

The automated process works by creating an AWS CodePipeline that is triggered by changes to an AWS CodeCommit Git repository.  The pipeline has two stages: a source stage that monitors the Git repository and a build stage which is an AWS CodeBuild process that runs the Packer command that will produce our new AMI.  For simplicity I’ve written AWS CloudFormation templates to deploy all of these services and their supporting AWS IAM roles.  For the steps to do this, see the README in the GitHub repository.

AWS CodeCommit

AWS CodeCommit is a managed Git service, similar to GitHub.  The service isn’t as feature rich as GitHub, but it has the advantages of being tightly integrated with the other AWS services and of using AWS IAM roles to control access.  AWS CodePipeline supports GitHub Git repositories as well, though there are a couple of extra integration steps needed to setup access.

To create the AWS CodeCommit repository, deploy the codecommit.yaml AWS CloudFormation template using either the AWS web console or the CLI.

AWS CodeBuild

AWS CodeBuild is a fully managed build service that covers all of the steps necessary to create software packages that are ready to be installed – compilation, testing, and packaging.  AWS CodeBuild works by processing a build specification YAML file that describes the build environment and the build steps.  Build environments are supplied as Docker containers, AWS provides a number of pre-built containers for common languages and platforms such as Java, Python, and Ruby.

Unfortunately, Packer is not one of the supplied build containers, fortunately with AWS CodeBuild you can supply your own container.  This is the Dockerfile I put together to run Packer on the AWS CodeBuild service:

FROM ubuntu

RUN apt-get update && apt-get -y install curl unzip jq && \
    curl -o packer.zip https://releases.hashicorp.com/packer/1.0.0/packer_1.0.0_linux_amd64.zip && \
    unzip packer.zip

CMD ["/packer"]

Normally I would have built a minimal Packer container, but AWS CodeBuild requires a bunch of other commands to function and I couldn’t find these listed in the documentation, so I went with the quick solution of copying what Amazon do themselves!

AWS CodeBuild needs to pull the container from a registry.  You can use the Docker Hub container registry, but I chose to use the AWS Elastic Container Registry because it integrates with AWS CodeBuild using IAM roles which makes configuring security simpler.  To create the AWS Elastic Container Registry, deploy the ecr-repository.yaml AWS CloudFormation template using either the AWS web console or the CLI.

With the registry created, building and uploading the Packer container is simple:

docker build --rm -t /packer:latest .
aws ecr get-login --region AWSREGION

Run the docker login command that’s output by aws ecr ..., then:

docker tag /packer:latest AWSACCOUNT.dkr.ecr.AWSREGION.amazonaws.com/packer:latest
docker push AWSACCOUNT.dkr.ecr.AWSREGION.amazonaws.com/packer/latest

The final piece of configuration for AWS CodeBuild is the buildspec.yml file.  Normally, I would just need a single phase, build, which would invoke Packer.  However, there was a bug in the AWS Go SDK which means that you need to manually setup the security credentials for Packer to be able to access EC2.  This bug has been fixed and the next version of Packer should pick this up and the install phase can be removed.

To create the AWS CodeBuild project, deploy the codebuild-role.yaml AWS CloudFormation template and then the codebuild-project.yaml AWS CloudFormation template using either the AWS web console or the CLI.  Note that you will need to edit the codebuild-project.yaml template to reflect your own values for the container image and the source location.

AWS CodePipeline

AWS CodePipeline is the glue that connects the AWS CodeCommit Git repository to the AWS CodeBuild project that invokes Packer to create an AMI.  The pipeline I used has two stages: a source stage and a build stage.  The source stage watches the Git repository for new commits and then invokes the build stage.  The build stage kicks off the AWS CodeBuild project which uses the Packer container I created to build my new AMI.

To create the AWS CodePipeline pipeline, deploy the codepipeline-role.yaml AWS CloudFormation template and then the codepipeline.yaml AWS CloudFormation template using either the AWS web console to the CLI.

Building an AMI

At this point to make the pipeline work all you need to do is to commit the files packer.json and buildspec.yml to the root of the AWS CodeCommit Git repository.  Within a few seconds the source stage of the pipeline will notice the commit, package up the files into an S3 bucket and invoke the build stage to actually create the AMI.

Note that you will need to edit the packer.json file to reflect the AWS Region you are using and the base AMI.  You can omit the “vpc_id” field if the region you are using still has it’s default VPC.  If, like me, you don’t have a default VPC anymore then you can deploy the vpc.yaml AWS Cloudformation template to create a VPC and use the VPC ID of your new VPC in packer.json.

Extra Credit

Once the basic AMI Bakery pipeline is up and running there’s lots of enhancements you could make, here’s some ideas:

  1. If you are creating a VPC just for Packer, you will end up paying for the Internet Gateway.  To avoid this you could create two additional pipeline stages, one to create the VPC and one to tear it down.
  2. Pipelines can be configured to send messages to an AWS SNS topic when they complete.  You could write an AWS Lambda function to listen for these messages and then trigger another pipeline or build project (in a different account) that bakes another AMI based on your newly created AMI.  We’re looking at doing this to allow one team to manage the base operating system AMI that is then used by application teams to build their own AMIs.
  3. You could create extra stages in the pipeline to perform automated testing of your newly baked AMI, to add a manual approval stage, or to perform a rolling upgrade of EC2 instances using older AMIs.
Advertisements

Updates from the world of containers

Containers are very much the technology of the moment and there have been some interesting developments in this space over the last few weeks that I think will influence the direction and use of these technologies through 2017.

Standardisation Efforts

The Open Containers Initiative (OCI) is the standards body working on both the Container Image Standard and the Container Runtime Standard. Both of these documents are approaching their 1.0 release with the Image spec reaching v1.0 rc5 and the Runtime spec also reaching v1.0 rc5.

Also on the standards front we are seeing the start of support from various vendors. As part of the founding of the OCI, Docker Inc. donated their runC component which has become reference implementation of the runtime standard. For the time being runC is pretty much the only runtime in use, though read on for developments regarding CoreOS rkt. If you’re looking for another container runtime you might also consider DC/OS from Mesosphere.

Even more interesting is that Amazon Web Services have announced that their EC2 Container Registry service now supports the container image standard.

Open-sourcing of containerd

Back in December 2016, Docker Inc. announced that they were open sourcing containerd. Containerd is the component that sits immediately above runC in the Docker stack and is responsible for fetching images from a registry, unpacking them on disk, and then invoking runC to instantiate a container based on the downloaded image. The source code for containerd can be found on GitHub.

You’ll notice that the git repository for containerd sits within the Docker organisation on GitHub. At the time of the announcement Docker Inc. stated that they would be looking for a governance home for containerd that could manage the project in a independent manner. This week Docker Inc. announced that it was intending to donate containerd to the Cloud Native Computing Foundation (CNCF). Note that this is only a proposal, and that the CNCF might vote not to accept responsibility for containerd, however I think the donation is very unlikely to be turned down.

If you want a good, technical, overview of containerd and the future plans for the project, this YouTube video from the recent conatinerd mini-conf is worth an hour of your time:

Proposed donation of rkt to the CNCF

At the same time as Docker Inc. were proposing that the CNCF take over stewardship of containerd, CoreOS announced that they were proposing that the CNCF take over governance of their container runtime called rkt (pronounced rocket). Similar to the submission by Docker Inc. of containerd, there’s no guarantee that the CNCF will vote to adopt rkt, but I suspect it will.

What does this mean?

Amazon’s support for the OCI image standard in it’s ECR service is a interesting development. By my reckoning this is the first, major, container registry to support the new standard. Now that the standards are reaching maturity there’s scope for a whole new toolchains for creating container images to be produced.

This is an area that I feel has been neglected over the last four years, pretty much everyone I meet just uses the docker build command and treats it as a blackbox. Additionally, Docker Inc. have, with good reason, largely frozen the Dockerfile specification over the last few years. The result has been that build toolchains have stagnated. I imagine that companies (and open source projects) like Amazon will be looking at adding powerful container build tools into their existing CI/CD toolchains. It wouldn’t surprise me if we saw AWS CodeBuild extended to natively support building OCI complaint containers and pushing them, automatically, to ECR. AWS CodeDeploy or AWS CodePipeline could then use ECS or Blox to perform canary or blue/green deployments.

The open-sourcing of containerd and it’s proposed donation to the CNCF is a indication that Docker Inc. is firming up it’s strategy to monetise the container revolution it launched. The recent rationalisation and clarification of the Docker Inc. production lines into Docker Enterprise Edition and Docker Community Edition also help in this regard. This Hacker News thread, in which Solomon Hykes participates, also shows how their positioning is evolving and is well worth reading.

Finally, if the CNCF accept the donation of rkt from CoreOS we will effectively have two competing container runtimes, both supporting the OCI runtime standard. This should drive further innovation and ensure that we don’t end up with one company dominating the container space and a monoculture similar to that found in enterprise virtualisation.

These announcements herald an exciting future for the adoption of container technologies. Now that the runtime and image standards are nearing completion, and the tool landscape is settling down the large enterprise customers I deal with in my day job can get serous about rolling out containerisation across their environments.

Building a VPC with AWS Cloudformation

One of the advantages of Amazon Web Services is the ability to quickly create complex infrastructures for development and testing, and then, when you’re done, to tear the infrastructure down.  The simplest way to reproducibly provision infrastructure is through the use of Cloudformation Templates.  These templates allow you to describe your infrastructure in JSON or YAML which AWS will then provision for you.

Amazon provides detailed documentation for Cloudformation, unfortunately the documentation is very strong on the reference side, but not so good at worked examples.  There’s also lots of examples on blogs around the Internet, however these tend to be of the variety that state “Here’s our template to do X”, again with little or no explanation about how the templates work.

I’ve been doing a lot of work with distributed systems recently and I wanted to be able to create Virtual Private Cloud containing a number of servers where I could install the software I was experimenting with.  An AWS VPC was the perfect solution but has a larger number of moving parts than you might think, especially if you want to limit the exposure your instances have to the Internet.  This blog post describes the Cloudformation template I created to spin up a VPC in a single availability zone.  Mainly for my own benefit the rest of the post explains how the template works, hopefully this might be of use to other people as well.

VPC Template

I’ve chosen to write my template in YAML.  This is a relatively new feature of Cloudformation, previously templates had to be written in JSON.  YAML has a number of advantages including the ability to have inline comments and a (I think) a cleaner syntax.  I’m also seeing YAML being used in many other projects so it seemed to be a useful thing to learn.

All Cloudformation templates have the following structure:

---
AWSTemplateFormatVersion: "2010-09-09"
Description: My VPC Template

Resources:
    ...

The three hyphens on the first line are part of the YAML specification, indicating the start of a document.  It used to be that the AWSTemplateFormatVersion section was mandatory, but the  latest documentation says that it’s now optional, I tend to include it anyway.  The Description section is also optional, but I would say that it’s best practice to include.

Before we get to the Resources section I should point out that you can do many complex things to make your templates very flexible.  A lot of this flexibility is driven using the Parameters and Mappings sections.  I’ve chosen not to use these two features to keep this template as simple as possible, I may do a follow up post showing how to use these sections to do deployment time customisation.  One other section I’ve not included but I should mention is the Outputs section.  This allows you to output values when AWS is deploying the stack based on your template.  You can use this to get values relating to the created resources such as public IP addresses.

The Resources section is where you specify the items that you want AWS to create when deploying a stack based on the template.  I’ll now go through each of these in turn, explaining the parameters I’ve chosen.  The full template is available from GitHub.

VPC

VPC:
    Type: AWS::EC2::VPC
    Properties:
        CidrBlock: 10.1.0.0/16
        EnableDnsSupport: true
        EnableDnsHostnames: true
        InstanceTenancy: default
        Tags:
          - Key: Name
            Value: Cloudformation Test VPC

All resources have the same basic structure: the logical ID of the resource, a Type, and then a Properties section.  In this example the logical ID of the resource is VPC and the type is AWS::EC2::VPC.  The properties I’m setting are:

  • CidrBlock: The subnet for the VPC.  You need to make sure your VPC has enough IP address space to carve out all the subnets you need.  I’m using an RFC1918 range, though AWS supports any address range you want.
  • EnableDnsSupport: If set to true the AWS DNS server resolves hostnames for instances in the VPC.
  • EnableDnsHostnames: If set to true instances get allocated DNS hostnames, you need to have EnableDnsSupport set to true as well for this to work.
  • InstanceTenancy: You can have your instances run on dedicated hardware assigned to only you if this is set to dedicated.  Understandably setting this to dedicated costs more!

You can optionally set tags on resources to make it easier to manage your AWS account.  Setting the Name tag makes resources identifiable in the AWS web interface.

Internet Gateway

An Internet Gateway is an Amazon managed device that allows resources in your VPC to connect to the Internet.  As I want to connect to my VPC over the Internet and I want instances in my VPC to be able to download from the Internet I need to create an Internet Gateway.

InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
        Tags:
          - Key: Name
            Value: Internet Gateway

You only create a single Internet Gateway per VPC, even if your VPC spans multiple availability zones.  Amazon take care of making the Internet Gateway highly available.  As you can see the Internet Gateway doesn’t need any extra properties.

Creating an Internet Gateway is a two stage operation.  First, as above, you declare the gateway, then you need to attach it to your VPC:

AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
        VpcId:
            Ref: VPC
        InternetGatewayId:
            Ref: InternetGateway

The AttachGateway section has two properties, these are both references to other sections in your Cloudformation template.  Here we are referencing the VPC and the InternetGateway that we have already declared in the template.

Note that these types of attach sections (we’ll see more shortly) don’t have Tag properties.  Adding a Tag section will cause your template to fail.

Bastion Host Subnet

My VPC design has three subnets: one for the bastion host that will allow SSH access to my VPC from the Internet, one for the NAT Gateway that will allow my instances access to the Internet, and one for my worker instances that shouldn’t be reachable directly from the Internet.

We’ll start by creating the subnet for the bastion host:

BastionHostSubnet:
    Type: AWS::EC2::Subnet
    Properties:
        VpcId:
            Ref: VPC
        CidrBlock: 10.1.1.0/28
        MapPublicIpOnLaunch: true
        Tags:
          - Key: Name
            Value: Bastion Host Subnet

As well as the Type, we need to define the following properties for the subnet:

  • VpcId: This is a reference to the VPC which will contain the subnet.
  • CidrBlock: The IP address range for the subnet in CIDR notation.  Note that AWS reserves 5 addresses from the range, that’s why I’ve chosen a /28 subnet mask leaving me with 11 usable addresses.
  • MapPublicIpOnLaunch: With this set to true, instances launched into the subnet will be allocated a public IP address by default.  This means that any instances in this subnet will be reachable from the Internet, subject to Security Groups and Network ACLs.

Once the subnet has been declared we need to configure routing.  By default a VPC is created with a main route table which allows instances to send traffic to each other even if they are in different subnets.  However, we want instances on this subnet to be able to communicate across the Internet so we need to create a subnet specific route table that will route Internet traffic via the Internet Gateway we declared previously.

It’s a three step process to declare and configure the subnet route table.  Step one is declaration of the route table:

BastionHostSubnetRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
        VpcId:
            Ref: VPC
        Tags:
          - Key: Name
            Value: Bastion Host Subnet Route Table

The route table is a very simple object, all it contains is a Type, a reference to the VPC and a tag giving it a name.

Step two is to declare the route entry that will send Internet bound traffic to our Internet Gateway:

BastionHostInternetRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGateway
    Properties:
        DestinationCidrBlock: 0.0.0.0/0
        GatewayId:
            Ref: InternetGateway
        RouteTableId:
            Ref: BastionHostSubnetRouteTable

This section introduces a item: DependsOn which instructs AWS not to create this resource until the InternetGateway has been created.  This is how we ensure that resources are created in the correct order.  The DestinationCidrBlock describes which traffic we want this route to apply to.  A value of 0.0.0.0/0 means all traffic.  It’s important to note that routes operate on a most specific match first and 0.0.0.0/0 is the least specific of all routes.  This means that the default VPC route entry of 10.1.0.0/16 will match first ensuring that traffic does not leak out of the VPC.

The GatewayId reference specifies where traffic matching this route should be sent, in this case it’s to the Internet Gateway we previously declared.  The RouteTableId reference connects this route to the route table.

Finally, step three is to associate the route table with the subnet:

BastionHostSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
    RouteTableId:
        Ref: BastionHostSubnetRouteTable
    SubnetId:
        Ref: BastionHostSubnet

The route table association connects the route table (and route) we’ve just declared with the subnet we declared earlier.  With this in place any instances created within the bastion host subnet should be able to route traffic to and from the Internet (security groups and network ACLs permitting).

Bastion Host Security Group

Before we deploy a bastion host we need to declare a security group.  By default instances are firewalled off from all network traffic so the security group needs to describe what traffic to let in and out of the instance.

BastionHostSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
        GroupDescription: Allow SSH to Bastion Host
        VpcId:
            Ref: VPC
        SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: '22'
            ToPort: '22'
            CidrIp: 0.0.0.0/0
        SecurityGroupEgress:
          - IpProtocol: -1
            CidrIp: 0.0.0.0/0
        Tags:
          - Key: Name
            Value: Bastion Host Security Group

As you can see a security group has a number of properties you can configure:

  • GroupDescription: A free text field that you can use to describe what the security group allows.
  • VpcId: The VPC where we will be using the security group.
  • SecurityGroupIngress: This property describes the traffic we should allow through the security group to the instance.
    • IpProtocol: This should be pretty obvious, we’re interested in TCP traffic.
    • FromPort and ToPort: Combined these describe a range of ports to which traffic should be allowed.
    • CidrIp: The range of IP addresses from which we should allow traffic that matched the IpProtocol and FromPort/ToPort range.
  • SecurityEgressGroup: This property describes traffic we should allow from the instance into the VPC and beyond.
    • IpProtocol: Same as before but this time we are using the value -1 which means all traffic types on all ports.
    • CidrIp: The range of IP addresses to which we should allow traffic that matches the IpProtocol setting (all traffic in this case).

Bastion Host Launch Configuration and Autoscaling

At this stage we could just create our bastion host instance and start using it, however it’s better to expend the extra effort and create a launch configuration and autoscaling group.  By launching our bastion host instance from within an autoscaling group we benefit from the feature that AWS will automatically restart our instance should it die for any reason.

BastionHostLaunchConfig:
    Type: "AWS::AutoScaling::LaunchConfiguration"
    Properties:
        AssociatePublicIpAddress: true
        ImageId: ami-9398d3e0 # Amazon Linux in eu-west-1
        InstanceMonitoring: false
        InstanceType: t2.micro
        KeyName: TestStack
        PlacementTenancy: default
        SecurityGroups:
          - Ref: BastionHostSecurityGroup

Launch configurations have a large number of properties that you can configure, we’re only using a small subset here:

  • AssociatePublicIpAddress: Make sure that instance has a public IP address when it launches, we don’t strictly need this as we configured the subnet to have this feature.
  • ImageId: The AMI to use to create the instance.  Note that I am using the Amazon Linux AMI in the eu-west-1 region.  If you’re in a different region, want to use a different operating system, or Amazon have released an updated version of their Linux, then you’ll need to change this value.
  • InstanceMonitoring: Setting to true enables detailed monitoring for your instance, this costs extra so I don’t use it in throwaway environments.
  • InstanceType: The type (size) of instance you want.  I’m just using the small and cheap t2.micro.
  • KeyName: The SSH key pair to use to access this instance.  You need to have previously created the key pair, obviously your key pair will probably be called something different.
  • PlacementTenancy: Same as for the VPC, we don’t want dedicated hardware for our instance.
  • SecurityGroups: A reference to the security group we declared previously.

After we’ve declared our launch configuration we need to create the autoscaling group:

BastionHostScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
        LaunchConfigurationName:
            Ref: BastionHostLaunchConfig
        MinSize: '1'
        MaxSize: '1'
        VPCZoneIdentifier:
          - Ref: BastionHostSubnet
        Tags:
          - Key: Name
            Value: Bastion Host
            PropagateAtLaunch: true

The autoscaling group references the launch configuration we declared using the LaunchConfigurationName reference.  The other properties are:

  • MinSize and MaxSize: By setting these both to one the autoscaling group will create a single instance and restart it if it fails.
  • VPCZoneIdentifier: A list of references to subnets into which the instances will be launched.  If we had multiple subnets spread across multiple availability zones we could reference them here to create a highly available system.

NAT Gateway, Subnet, and Elastic IP

The bastion host is configured to allow SSH traffic into our VPC from the Internet, however we also want our worker instances to be able to access the internet, mainly so that they can download software updates.  To do this we need create a NAT gateway, a public subnet to host it, and an elastic IP address.

NatGatewaySubnet:
    Type: AWS::EC2::Subnet
    Properties:
        VpcId:
            Ref: VPC
        CidrBlock: 10.1.1.16/28
        MapPublicIpOnLaunch: true
        Tags:
          - Key: Name
            Value: NAT Gateway Host Subnet

NatGatewaySubnetRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
        VpcId:
            Ref: VPC
        Tags:
          - Key: Name
            Value: NAT Gateway Subnet Route Table

NatGatewayInternetRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGateway
    Properties:
        DestinationCidrBlock: 0.0.0.0/0
        GatewayId:
            Ref: InternetGateway
        RouteTableId:
            Ref: NatGatewaySubnetRouteTable

NatGatewaySubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
        RouteTableId:
            Ref: NatGatewaySubnetRouteTable
        SubnetId:
            Ref: NatGatewaySubnet

We declare the subnet for the NAT gateway in exactly the same way we did for the bastion host subnet.  The only differences are in the logical IDs for the resources we declare and the subnet IP address range we are using.

With the subnet declared we can declare the elastic IP address that we’ll assign to the NAT gateway:

NatGatewayEIP:
    Type: AWS::EC2::EIP
    Properties:
        Domain: vpc

The Domain property needs to be set to vpc as we are working within a VPC.

Finally we can create the NAT gateway:

NatGateway:
    Type: AWS::EC2::NatGateway
    DependsOn: AttachGateway
    Properties:
        AllocationId:
            Fn::GetAtt:
              - NatGatewayEIP
              - AllocationId
        SubnetId:
            Ref: NatGatewaySubnet

The AllocationId property is interesting, Fn::GetAtt is an intrinsic function.  Basically what it does is get the AllocationId attribute if the NatGatewayEIP resource that we declared previously.  The SubnetId property is a reference to the subnet where the NAT gateway should be deployed.

Private Subnet

The declarations for the private subnet are much the same as the previous subnets.

PrivateSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
        VpcId:
            Ref: VPC
        CidrBlock: 10.1.2.0/24
        MapPublicIpOnLaunch: false
        Tags:
          - Key: Name
            Value: Private Subnet A

Note that MapPublicIpOnLaunch is set to false so that instances in this subnet don’t get a public IP address.

PrivateSubnetARouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
        VpcId:
            Ref: VPC
        Tags:
          - Key: Name
            Value: Private Subnet A Route Table

PrivateSubnetANatInternetRoute:
    Type: AWS::EC2::Route
    DependsOn: NatGateway
    Properties:
        DestinationCidrBlock: 0.0.0.0/0
        NatGatewayId:
            Ref: NatGateway
        RouteTableId:
            Ref: PrivateSubnetARouteTable

The Internet route for the private subnet is a little different.  We declare the NatGatewayId property as a reference to the Nat gateway we declared previously.

PrivateSubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
        RouteTableId:
            Ref: PrivateSubnetARouteTable
        SubnetId:
            Ref: PrivateSubnetA

Private Subnet Security Group

The private subnet security group is pretty much the same as the bastion host security group:

PrivateSubnetASecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
        GroupDescription: Allow SSH from Bastion Host
        VpcId:
            Ref: VPC
        SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: '22'
            ToPort: '22'
            SourceSecurityGroupId:
                Fn::GetAtt:
                  - BastionHostSecurityGroup
                  - GroupId
        SecurityGroupEgress:
          - IpProtocol: -1
            CidrIp: 0.0.0.0/0
        Tags:
          - Key: Name
            Value: Private Subnet A Security Group

The difference is that in the SecurityGroupIngress property we don’t use the CidrIp property instead we use the SourceSecurityGroupId to link this security group to the bastion host security group allowing SSH traffic.

Private Subnet Launch Configuration and Autoscaling Group

The final part of the puzzle is the launch configuration and autoscaling group for launching worker instances into the private subnet.

PrivateSubnetALaunchConfig:
    Type: "AWS::AutoScaling::LaunchConfiguration"
    Properties:
        AssociatePublicIpAddress: false
        ImageId: ami-9398d3e0 # Amazon Linux in eu-west-1
        InstanceMonitoring: false
        InstanceType: t2.micro
        KeyName: TestStack
        PlacementTenancy: default
        SecurityGroups:
          - Ref: PrivateSubnetASecurityGroup

PrivateSubnetAScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
        LaunchConfigurationName:
            Ref: PrivateSubnetALaunchConfig
        MinSize: '1'
        MaxSize: '1'
        VPCZoneIdentifier:
          - Ref: PrivateSubnetA
        Tags:
          - Key: Name
            Value: Worker Host
            PropagateAtLaunch: true

These are pretty much identical to the bastion host launch configuration and autoscaling group.  The changes are that the AssociatePublicIpAddress is set to false so that instances don’t get public IP addresses (they use the NAT gateway to access the Internet) and the logical IDs for various resources point to those in the private subnet.

Network Access Control Lists

If you’re familiar with AWS VPCs you’ll have noticed that I am not declaring any network access control lists.  By not declaring and ACLs AWS will create a default ACL that allows all inbound and outbound traffic and our subnets will be associated with this ACL.  In a production deployment you might want to use ACLs to limit traffic flows between subnets.  I’ll leave that as an exercise for the reader.

Final Thoughts

Well that turned out far longer than I though (just over 2800 words!).

Cloudformation templates have a huge amount of flexibility, so I’m sure that there are different, and probably better, ways to achieve what I’ve created here.  If you’ve got any tips please feel free to leave a comment.

The full template is available from my GitHub.

Emptying the trash is creating files!

An amusing bug in OS X Yosemite:

Look carefully at the file count.

If you look carefully you will see that the file count is -301, does this mean OS X is creating files while emptying the trash? 🙂

OS X Yosemite and Older iMacs

I had been using VMware Fusion 7 on my iMac running OS X Mavericks with no issues.  However after upgrading to OS X Yosemite my VMs had severe performance problems to the point where the characters I typed in a terminal wouldn’t show up until I waggled the mouse.

A little bit of Google-Fu led me to this thread on VMware’s community forum, however I wasn’t prepared to make the suggested changes to my NVRAM without understanding a little more.  Reading through the thread I was led to similar thread on the Parallels forums suggesting the same fix, but with no more explanation.  The thread did contain a link to a Parallels Knowledge Base article that explained suggested the exact issue I’d seen could be due to interrupt storms.  Even better they linked to another knowledge base article that explained how to confirm this theory.

Having followed the steps on my iMac I confirmed that I was seeing interrupt storms relating to the com.apple.driver.AppleACPIPlatform driver.  This finally led me to a thread on Apple’s support forum that suggested the same fix.

After applying the fix the performance of my system was like night and day!  I hadn’t appreciated quite how badly it had been running even out with VMware Fusion.

If you’ve made it this far you probably want to know what the fix is?  Open a terminal and type the following:

sudo nvram boot-args="debug=0xd4e"

Once you’ve done this restart your iMac and it should be significantly faster!  If this works for you please raise a support issue with Apple so that they address this properly in a future update to OS X Yosemite.

The only troubling thing is that I still don’t know what setting “debug” to “0xd4e” actually does…

Developing for Multiple Platforms with Xcode 6

I’ve been working on an app that targets both iOS and OS X devices, mainly to teach myself to code in Objective C, but also to scratch an itch in the best open source tradition.  In Xcode I’ve had two projects and been syncing code between them using copy and paste.  This is not a great experience so I looked around for more elegant solutions.  The one I settled on was inspired by Session 233: Sharing code between iOS and OS X from the 2014 WWDC.  This involves creating a single project with multiple targets aimed at different platforms and devices.

I was bothered by the fact that despite following what was done in the session I couldn’t get my project to look the same, eventually after much experimenting I came up with the following process.  The caveat is that this works for me, there are quite possibly better ways to achieve the same result, which I’d love to hear!

Create the project

Create a Project

The first thing you need to do is create a new project in Xcode, I usually start from the iOS Single View Application template, but it doesn’t actually matter.

SS 02

On the above screen the key change is in setting the project’s name.  If you chose an iOS application name the project MyApp-iOS, substituting MyApp for your own project name.  If you chose an OS X application  name the project MyApp-OSX.  Click next and finish creating the project by selecting a save location and enabling Git for version control if needed.

Rename the project

SS 03.1 SS 03.2

In the project navigator select the top item, the project itself.  Then move across to the file inspector on the righthand side and rename the project, dropping the suffix, to MyApp.

SS 04.1

The window shown above pops up offering to rename the targets, however as we will be adding targets for other platforms later we want to prevent Xcode from being over helpful by deselecting the targets:

SS 04.2

Clicking on rename might pop up another window where Xcode offers to turn on snapshots, I normally click enable but it doesn’t affect what we are trying to achieve here.

Adding a New Target

SS 06SS 07

The next step is to add a new target to build your application for a different Apple platform.  Click the + button at the bottom of the window, below the existing targets.  In the window that opens select a different platform – in my case that’s an OS X Cocoa Application.  Click next and name the target using the same convention as when you created the project: MyApp-OSX.  Once you’ve added the additional target your project should look something like:

SS 08

Adding Common Code

The whole point of this exercise to create common code that can be used in both targets.  To do this create a group in the Project Navigator by right clicking on the project and selecting New Group.  I normally name this MyApp-Common.  Right clicking on the MyApp-Common group select New File and choose to create a new Objective C class.

SS 09

The two things you need to take care with are: making sure you don’t create a class that inherits from a platform specific class and that you remember to add the class to both targets, as shown in the screenshot above.  You should then end up with a project that looks like this:

SS 10

The final task is to rename the products of the two targets, if you compile and then install or distribute you will end up with applications named MyApp-iOS and MyApp-OSX which isn’t what users expect to see.  Renaming the product is a two step process.  First, for each non-test target, you need to select the target, select Build Settings and search for Product Name.  Once you’ve got the Product Name setting you can change the value to the name you want for your application:

SS 11

Renaming the product in this way has an unexpected side effect – tests will no longer build and run.  This is due to the linker being unable to link the test classes to the application classes as it’s still looking for the old application name (details here).  To fix this you need to change the value for Test Host in the same way as you did for Product Name.  Do this for both test targets, replacing all occurrences of MyApp-iOS and MyApp-OSX with MyApp (or whatever name you gave to your product in the previous step).

SS 12

At this point you should be able to build, install, and run both targets.  As I said at the start, I’m sure there’s either things I’m missing or a better way to do this entirely – I will be looking into creating a framework that builds for both iOS and OSX, and then including the framework in a multi-platform application project.