Shared Chef Code For Amazon Machine Images and Docker Images

If you are building Docker images you might be using a Dockerfile to define the state of the image. For example:

FROM centos:7

RUN yum install -y httpd
RUN mkdir -p /var/www/html/images
COPY images/foo.png /var/www/html/images/foo.png
RUN chown apache: /var/www/html/images/foo.png
EXPOSE 80

If you have been creating Amazon Machine Images (AMIs) you might be using Chef or some other configuration management tool.

A problem arises when you want to avoid duplicating code across the two. Let’s say you want to create a “baseline” image for your development teams to use. It will likely share the same configuration in terms of what packages you need to install and what security configurations are required. How do you avoid writing code in your Dockerfile and duplicating that for your AMIs?

One solution is to use Chef to define your configuration for both images. In this article I will show how you can develop AMIs and Docker images using Chef and Kitchen and build the images using Jenkins and Packer.

Workstation Setup

First, let’s setup our workstation. I highly recommend installing Windows Subsystem for Linux 2 (WSL 2). To do so, install Windows 10 build 18932 or later. Then from PowerShell run:

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform




Install the Ubuntu app from the Microsoft Store and from PowerShell run:

wsl --set-version Ubuntu 2

Install Docker Desktop, open the ‘WSL 2 Tech Preview’ section of Docker Desktop and click ‘Start’.

From your Ubuntu app install Chef Workstation which in addition to Chef provides the Kitchen tool and the ec2 and dokken drivers.

wget "https://packages.chef.io/files/stable/chef-workstation/0.9.42/ubuntu/18.04/chef-workstation_0.9.42-1_amd64.deb" -O /tmp/chef.deb
sudo dpkg -i /tmp/chef.deb
rm -f /tmp/chef.deb

Kitchen

If you clone https://gitlab.com/colmmg/chef/baseline and inspect the kitchen.yml file you can see how Docker images and AMIs can be developed with the same Chef code.

---
driver:
  name: <%= ENV['KITCHEN_DRIVER'] || 'dokken' %>
  <% if ENV['KITCHEN_DRIVER'] == "dokken" %>
  chef_version: 15.3.14
  <% else %>
  associate_public_ip: false
  availability_zone: us-east-1a
  iam_profile_name: kitchen-instance
  instance_type: t3.micro
  interface: private
  region: us-east-1
  security_group_filter:
    tag:   'Name'
    value: 'kitchen-sg'
  subnet_filter:
    tag:   'Name'
    value: 'kitchen-subnet'
  tags:
    Name: baseline-testkitchen
  <% end %>

transport:
  <% if ENV['KITCHEN_DRIVER'] == "dokken" %>
  name: dokken
  <% else %>
  connection_retries: 5
  connection_timeout: 10
  name: ssh
  username: ec2-user
  <% end %>

provisioner:
  <% if ENV['KITCHEN_DRIVER'] == "dokken" %>
  name: dokken
  <% else %>
  client_rb:
    environment: build
  environments_path: environments
  name: chef_zero
  product_name: chef
  product_version: 15.3.14
  <% end %>

platforms:
  - name: amazon
    driver:
      <% if ENV['KITCHEN_DRIVER'] == "dokken" %>
      image: amazonlinux:2
      <% else %>
      image_id: ami-0ce71448843cb18a1
      block_device_mappings:
        - device_name: /dev/xvda
          ebs:
            volume_type: gp2
            volume_size: 8
            delete_on_termination: true
      <% end %>

suites:
  - name: default
    run_list:
      - recipe[baseline]
    attributes:
      colmmg:
        docker: <% if ENV['KITCHEN_DRIVER'] == "dokken" %>true<% else %>false<% end %>

verifier:
  name: inspec
  inspec_tests:
    - path: test/default
    - path: test/<%= ENV['KITCHEN_DRIVER'] %>




We can use the KITCHEN_DRIVER environment variable to control whether we want to use the ec2 or dokken driver.

Rapid development of Docker images is possible here because Kitchen creates a running container and you may iteratively run ‘kitchen converge’ as you develop your cookbooks.

Packer

If you clone https://gitlab.com/colmmg/aws-scripts/packer you will see a Jenkins pipeline file (Jenkinsfile). This pipeline has a ‘berks’ stage to package the cookbooks and then parallel stages for the Docker build and the AMI build. Both images are built with Packer…

./packer build docker.json
./packer build ec2.json

The configuration values for these ‘docker.json’ and ‘ec2.json’ Packer files are extracted from the kitchen.yml of the cloned cookbook. The benefit of this is that your kitchen.yml contains all of the information needed to develop Docker images and AMIs in Kitchen and to build the images using Packer.

Final Comments

I have been using this solution for a few weeks and it has been working very well. I hope others may find this useful.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s