Introduction
On March 2 (less than a week ago as of this post), Docker announced the release of Docker Enterprise Edition (EE), a new version of the Docker platform optimized for business-critical deployments. As part of the release, Docker also renamed the free Docker products to Docker Community Edition (CE). Both products are adopting a new time-based versioning scheme for both Docker EE and CE. The initial release of Docker CE and EE, the 17.03 release, is the first to use the new scheme.
Along with the release, Docker delivered excellent documentation on installing, configuring, and troubleshooting the new Docker EE and CE. In this post, I will demonstrate how to partially bake an existing Amazon Machine Image (Amazon AMI) with the new Docker CE, preparing it as a base for the creation of Amazon Elastic Compute Cloud (Amazon EC2) compute instances.
Adding Docker and similar tooling to an AMI is referred to as partially baking an AMI, often referred to as a hybrid AMI. According to AWS, ‘hybrid AMIs provide a subset of the software needed to produce a fully functional instance, falling in between the fully baked and JeOS (just enough operating system) options on the AMI design spectrum.’
Installing Docker CE on an AWS AMI should not be confused with Docker’s also recently announced Docker Community Edition (CE) for AWS. Docker for AWS offers multiple CloudFormation templates for Docker EE and CE. According to Docker, Docker for AWS ‘provides a Docker-native solution that avoids operational complexity and adding unneeded additional APIs to the Docker stack.’
Base AMI
Docker provides detailed directions for installing Docker CE and EE onto several major Linux distributions. For this post, we will choose a widely used Linux distro, Ubuntu. According to Docker, currently Docker CE and EE can be installed on three popular Ubuntu releases:
- Yakkety 16.10
- Xenial 16.04 (LTS)
- Trusty 14.04 (LTS)
To provision a small EC2 instance in Amazon’s US East (N. Virginia) Region, I will choose Ubuntu 16.04.2 LTS Xenial Xerus . According to Canonical’s Amazon EC2 AMI Locator website, a Xenial 16.04 LTS AMI is available, ami-09b3691f
, for US East 1, as a t2.micro EC2 instance type.
Packer
HashiCorp Packer will be used to partially bake the base Ubuntu Xenial 16.04 AMI with Docker CE 17.03. HashiCorp describes Packer as ‘a tool for creating machine and container images for multiple platforms from a single source configuration.’ The JSON-format Packer file is as follows:
{ "variables": { "aws_access_key": "{{env `AWS_ACCESS_KEY_ID`}}", "aws_secret_key": "{{env `AWS_SECRET_ACCESS_KEY`}}", "us_east_1_ami": "ami-09b3691f", "name": "aws-docker-ce-base", "us_east_1_name": "ubuntu-xenial-docker-ce-base", "ssh_username": "ubuntu" }, "builders": [ { "name": "{{user `us_east_1_name`}}", "type": "amazon-ebs", "access_key": "{{user `aws_access_key`}}", "secret_key": "{{user `aws_secret_key`}}", "region": "us-east-1", "vpc_id": "", "subnet_id": "", "source_ami": "{{user `us_east_1_ami`}}", "instance_type": "t2.micro", "ssh_username": "{{user `ssh_username`}}", "ssh_timeout": "10m", "ami_name": "{{user `us_east_1_name`}} {{timestamp}}", "ami_description": "{{user `us_east_1_name`}} AMI", "run_tags": { "ami-create": "{{user `us_east_1_name`}}" }, "tags": { "ami": "{{user `us_east_1_name`}}" }, "ssh_private_ip": false, "associate_public_ip_address": true } ], "provisioners": [ { "type": "file", "source": "bootstrap_docker_ce.sh", "destination": "/tmp/bootstrap_docker_ce.sh" }, { "type": "file", "source": "cleanup.sh", "destination": "/tmp/cleanup.sh" }, { "type": "shell", "execute_command": "echo 'packer' | sudo -S sh -c '{{ .Vars }} {{ .Path }}'", "inline": [ "whoami", "cd /tmp", "chmod +x bootstrap_docker_ce.sh", "chmod +x cleanup.sh", "ls -alh /tmp", "./bootstrap_docker_ce.sh", "sleep 10", "./cleanup.sh" ] } ] }
The Packer file uses Packer’s amazon-ebs builder type. This builder is used to create Amazon AMIs backed by Amazon Elastic Block Store (EBS) volumes, for use in EC2.
Bootstrap Script
To install Docker CE on the AMI, the Packer file executes a bootstrap shell script. The bootstrap script and subsequent cleanup script are executed using Packer’s remote shell provisioner. The bootstrap is like the following:
#!/bin/sh sudo apt-get remove docker docker-engine sudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo apt-key fingerprint 0EBFCD88 sudo add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) \ stable" sudo apt-get update sudo apt-get -y upgrade sudo apt-get install -y docker-ce sudo groupadd docker sudo usermod -aG docker ubuntu sudo systemctl enable docker
This script closely follows directions provided by Docker, for installing Docker CE on Ubuntu. After removing any previous copies of Docker, the script installs Docker CE. To ensure sudo
is not required to execute Docker commands on any EC2 instance provisioned from resulting AMI, the script adds the ubuntu
user to the docker
group.
The bootstrap script also uses systemd to start the Docker daemon. Starting with Ubuntu 15.04, Systemd System and Service Manager is used by default instead of the previous init system, Upstart. Systemd ensures Docker will start on boot.
Cleaning Up
It is best good practice to clean up your activities after baking an AMI. I have included a basic clean up script. The cleanup script is as follows:
#!/bin/sh set -e echo 'Cleaning up after bootstrapping...' sudo apt-get -y autoremove sudo apt-get -y clean sudo rm -rf /tmp/* cat /dev/null > ~/.bash_history history -c exit
Partially Baking
Before running Packer to build the Docker CE AMI, I set both my AWS access key and AWS secret access key. The Packer file expects the AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY
environment variables.
Running the packer build ubuntu_docker_ce_ami.json
command builds the AMI. The abridged output should look similar to the following:
$ packer build docker_ami.json ubuntu-xenial-docker-ce-base output will be in this color. ==> ubuntu-xenial-docker-ce-base: Prevalidating AMI Name... ubuntu-xenial-docker-ce-base: Found Image ID: ami-09b3691f ==> ubuntu-xenial-docker-ce-base: Creating temporary keypair: packer_58bc7a49-9e66-7f76-ce8e-391a67d94987 ==> ubuntu-xenial-docker-ce-base: Creating temporary security group for this instance... ==> ubuntu-xenial-docker-ce-base: Authorizing access to port 22 the temporary security group... ==> ubuntu-xenial-docker-ce-base: Launching a source AWS instance... ubuntu-xenial-docker-ce-base: Instance ID: i-0ca883ecba0c28baf ==> ubuntu-xenial-docker-ce-base: Waiting for instance (i-0ca883ecba0c28baf) to become ready... ==> ubuntu-xenial-docker-ce-base: Adding tags to source instance ==> ubuntu-xenial-docker-ce-base: Waiting for SSH to become available... ==> ubuntu-xenial-docker-ce-base: Connected to SSH! ==> ubuntu-xenial-docker-ce-base: Uploading bootstrap_docker_ce.sh => /tmp/bootstrap_docker_ce.sh ==> ubuntu-xenial-docker-ce-base: Uploading cleanup.sh => /tmp/cleanup.sh ==> ubuntu-xenial-docker-ce-base: Provisioning with shell script: /var/folders/kf/637b0qns7xb0wh9p8c4q0r_40000gn/T/packer-shell189662158 ... ubuntu-xenial-docker-ce-base: Reading package lists... ubuntu-xenial-docker-ce-base: Building dependency tree... ubuntu-xenial-docker-ce-base: Reading state information... ubuntu-xenial-docker-ce-base: E: Unable to locate package docker-engine ubuntu-xenial-docker-ce-base: Reading package lists... ubuntu-xenial-docker-ce-base: Building dependency tree... ubuntu-xenial-docker-ce-base: Reading state information... ubuntu-xenial-docker-ce-base: ca-certificates is already the newest version (20160104ubuntu1). ubuntu-xenial-docker-ce-base: apt-transport-https is already the newest version (1.2.19). ubuntu-xenial-docker-ce-base: curl is already the newest version (7.47.0-1ubuntu2.2). ubuntu-xenial-docker-ce-base: software-properties-common is already the newest version (0.96.20.5). ubuntu-xenial-docker-ce-base: 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. ubuntu-xenial-docker-ce-base: OK ubuntu-xenial-docker-ce-base: pub 4096R/0EBFCD88 2017-02-22 ubuntu-xenial-docker-ce-base: Key fingerprint = 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88 ubuntu-xenial-docker-ce-base: uid Docker Release (CE deb) ubuntu-xenial-docker-ce-base: sub 4096R/F273FCD8 2017-02-22 ubuntu-xenial-docker-ce-base: ubuntu-xenial-docker-ce-base: Hit:1 http://us-east-1.ec2.archive.ubuntu.com/ubuntu xenial InRelease ubuntu-xenial-docker-ce-base: Get:2 http://us-east-1.ec2.archive.ubuntu.com/ubuntu xenial-updates InRelease [102 kB] ... ubuntu-xenial-docker-ce-base: Get:27 http://security.ubuntu.com/ubuntu xenial-security/universe amd64 Packages [89.5 kB] ubuntu-xenial-docker-ce-base: Fetched 10.6 MB in 2s (4,065 kB/s) ubuntu-xenial-docker-ce-base: Reading package lists... ubuntu-xenial-docker-ce-base: Reading package lists... ubuntu-xenial-docker-ce-base: Building dependency tree... ubuntu-xenial-docker-ce-base: Reading state information... ubuntu-xenial-docker-ce-base: Calculating upgrade... ubuntu-xenial-docker-ce-base: 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. ubuntu-xenial-docker-ce-base: Reading package lists... ubuntu-xenial-docker-ce-base: Building dependency tree... ubuntu-xenial-docker-ce-base: Reading state information... ubuntu-xenial-docker-ce-base: The following additional packages will be installed: ubuntu-xenial-docker-ce-base: aufs-tools cgroupfs-mount libltdl7 ubuntu-xenial-docker-ce-base: Suggested packages: ubuntu-xenial-docker-ce-base: mountall ubuntu-xenial-docker-ce-base: The following NEW packages will be installed: ubuntu-xenial-docker-ce-base: aufs-tools cgroupfs-mount docker-ce libltdl7 ubuntu-xenial-docker-ce-base: 0 upgraded, 4 newly installed, 0 to remove and 0 not upgraded. ubuntu-xenial-docker-ce-base: Need to get 19.4 MB of archives. ubuntu-xenial-docker-ce-base: After this operation, 89.4 MB of additional disk space will be used. ubuntu-xenial-docker-ce-base: Get:1 http://us-east-1.ec2.archive.ubuntu.com/ubuntu xenial/universe amd64 aufs-tools amd64 1:3.2+20130722-1.1ubuntu1 [92.9 kB] ... ubuntu-xenial-docker-ce-base: Get:4 https://download.docker.com/linux/ubuntu xenial/stable amd64 docker-ce amd64 17.03.0~ce-0~ubuntu-xenial [19.3 MB] ubuntu-xenial-docker-ce-base: debconf: unable to initialize frontend: Dialog ubuntu-xenial-docker-ce-base: debconf: (Dialog frontend will not work on a dumb terminal, an emacs shell buffer, or without a controlling terminal.) ubuntu-xenial-docker-ce-base: debconf: falling back to frontend: Readline ubuntu-xenial-docker-ce-base: debconf: unable to initialize frontend: Readline ubuntu-xenial-docker-ce-base: debconf: (This frontend requires a controlling tty.) ubuntu-xenial-docker-ce-base: debconf: falling back to frontend: Teletype ubuntu-xenial-docker-ce-base: dpkg-preconfigure: unable to re-open stdin: ubuntu-xenial-docker-ce-base: Fetched 19.4 MB in 1s (17.8 MB/s) ubuntu-xenial-docker-ce-base: Selecting previously unselected package aufs-tools. ubuntu-xenial-docker-ce-base: (Reading database ... 53844 files and directories currently installed.) ubuntu-xenial-docker-ce-base: Preparing to unpack .../aufs-tools_1%3a3.2+20130722-1.1ubuntu1_amd64.deb ... ubuntu-xenial-docker-ce-base: Unpacking aufs-tools (1:3.2+20130722-1.1ubuntu1) ... ... ubuntu-xenial-docker-ce-base: Setting up docker-ce (17.03.0~ce-0~ubuntu-xenial) ... ubuntu-xenial-docker-ce-base: Processing triggers for libc-bin (2.23-0ubuntu5) ... ubuntu-xenial-docker-ce-base: Processing triggers for systemd (229-4ubuntu16) ... ubuntu-xenial-docker-ce-base: Processing triggers for ureadahead (0.100.0-19) ... ubuntu-xenial-docker-ce-base: groupadd: group 'docker' already exists ubuntu-xenial-docker-ce-base: Synchronizing state of docker.service with SysV init with /lib/systemd/systemd-sysv-install... ubuntu-xenial-docker-ce-base: Executing /lib/systemd/systemd-sysv-install enable docker ubuntu-xenial-docker-ce-base: Cleanup... ubuntu-xenial-docker-ce-base: Reading package lists... ubuntu-xenial-docker-ce-base: Building dependency tree... ubuntu-xenial-docker-ce-base: Reading state information... ubuntu-xenial-docker-ce-base: 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. ==> ubuntu-xenial-docker-ce-base: Stopping the source instance... ==> ubuntu-xenial-docker-ce-base: Waiting for the instance to stop... ==> ubuntu-xenial-docker-ce-base: Creating the AMI: ubuntu-xenial-docker-ce-base 1288227081 ubuntu-xenial-docker-ce-base: AMI: ami-e9ca6eff ==> ubuntu-xenial-docker-ce-base: Waiting for AMI to become ready... ==> ubuntu-xenial-docker-ce-base: Modifying attributes on AMI (ami-e9ca6eff)... ubuntu-xenial-docker-ce-base: Modifying: description ==> ubuntu-xenial-docker-ce-base: Modifying attributes on snapshot (snap-058a26c0250ee3217)... ==> ubuntu-xenial-docker-ce-base: Adding tags to AMI (ami-e9ca6eff)... ==> ubuntu-xenial-docker-ce-base: Tagging snapshot: snap-043a16c0154ee3217 ==> ubuntu-xenial-docker-ce-base: Creating AMI tags ==> ubuntu-xenial-docker-ce-base: Creating snapshot tags ==> ubuntu-xenial-docker-ce-base: Terminating the source AWS instance... ==> ubuntu-xenial-docker-ce-base: Cleaning up any extra volumes... ==> ubuntu-xenial-docker-ce-base: No volumes to clean up, skipping ==> ubuntu-xenial-docker-ce-base: Deleting temporary security group... ==> ubuntu-xenial-docker-ce-base: Deleting temporary keypair... Build 'ubuntu-xenial-docker-ce-base' finished. ==> Builds finished. The artifacts of successful builds are: --> ubuntu-xenial-docker-ce-base: AMIs were created: us-east-1: ami-e9ca6eff
Results
The result is an Ubuntu 16.04 AMI in US East 1 with Docker CE 17.03 installed. To confirm the new AMI is now available, I will use the AWS CLI to examine the resulting AMI:
aws ec2 describe-images \ --filters Name=tag-key,Values=ami Name=tag-value,Values=ubuntu-xenial-docker-ce-base \ --query 'Images[*].{ID:ImageId}'
Resulting output:
{ "Images": [ { "VirtualizationType": "hvm", "Name": "ubuntu-xenial-docker-ce-base 1488747081", "Tags": [ { "Value": "ubuntu-xenial-docker-ce-base", "Key": "ami" } ], "Hypervisor": "xen", "SriovNetSupport": "simple", "ImageId": "ami-e9ca6eff", "State": "available", "BlockDeviceMappings": [ { "DeviceName": "/dev/sda1", "Ebs": { "DeleteOnTermination": true, "SnapshotId": "snap-048a16c0250ee3227", "VolumeSize": 8, "VolumeType": "gp2", "Encrypted": false } }, { "DeviceName": "/dev/sdb", "VirtualName": "ephemeral0" }, { "DeviceName": "/dev/sdc", "VirtualName": "ephemeral1" } ], "Architecture": "x86_64", "ImageLocation": "931066906971/ubuntu-xenial-docker-ce-base 1488747081", "RootDeviceType": "ebs", "OwnerId": "931066906971", "RootDeviceName": "/dev/sda1", "CreationDate": "2017-03-05T20:53:41.000Z", "Public": false, "ImageType": "machine", "Description": "ubuntu-xenial-docker-ce-base AMI" } ] }
Finally, here is the new AMI as seen in the AWS EC2 Management Console:
Terraform
To confirm Docker CE is installed and running, I can provision a new EC2 instance, using HashiCorp Terraform. This post is too short to detail all the Terraform code required to stand up a complete environment. I’ve included the complete code in the GitHub repo for this post. Not, the Terraform code is only used to testing. No security, including the use of a properly configured security groups, public/private subnets, and a NAT server, is configured.
Below is a greatly abridged version of the Terraform code I used to provision a new EC2 instance, using Terraform’s aws_instance
resource. The resulting EC2 instance should have Docker CE available.
# test-docker-ce instance resource "aws_instance" "test-docker-ce" { connection { user = "ubuntu" private_key = "${file("~/.ssh/test-docker-ce")}" timeout = "${connection_timeout}" } ami = "ami-e9ca6eff" instance_type = "t2.nano" availability_zone = "us-east-1a" count = "1" key_name = "${aws_key_pair.auth.id}" vpc_security_group_ids = ["${aws_security_group.test-docker-ce.id}"] subnet_id = "${aws_subnet.test-docker-ce.id}" tags { Owner = "Gary A. Stafford" Terraform = true Environment = "test-docker-ce" Name = "tf-instance-test-docker-ce" } }
By using the AWS CLI, once again, we can confirm the new EC2 instance was built using the correct AMI:
aws ec2 describe-instances \ --filters Name='tag:Name,Values=tf-instance-test-docker-ce' \ --output text --query 'Reservations[*].Instances[*].ImageId'
Resulting output looks good:
ami-e9ca6eff
Finally, here is the new EC2 as seen in the AWS EC2 Management Console:
SSHing into the new EC2 instance, I should observe that the operating system is Ubuntu 16.04.2 LTS and that Docker version 17.03.0-ce is installed and running:
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-64-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage Get cloud support with Ubuntu Advantage Cloud Guest: http://www.ubuntu.com/business/services/cloud 0 packages can be updated. 0 updates are security updates. Last login: Sun Mar 5 22:06:01 2017 from ubuntu@ip-:~$ docker --version Docker version 17.03.0-ce, build 3a232c8
Conclusion
Docker EE and CE represent a significant step forward in expanding Docker’s enterprise-grade toolkit. Replacing or installing Docker EE or CE on your AWS AMIs is easy, using Docker’s guide along with HashiCorp Packer.
All source code for this post can be found on GitHub.
All opinions in this post are my own and not necessarily the views of my current employer or their clients.
#1 by Ali MEZGANI on July 2, 2017 - 5:01 pm
Reblogged this on Mezgani blog – A Linux System Engineer Blog.