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
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.
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’.
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
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.
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.
I have been using this solution for a few weeks and it has been working very well. I hope others may find this useful.