Skip to content

jasonwalsh/ubuntu-16.04

Repository files navigation

MongoDB Ubuntu 16.04 Amazon Machine Image (AMI)

CircleCI GitHub release

Contents

Purpose

The software automation ecosystem continues to grow and improve daily. Software like Ansible, Chef, and Puppet empowers developers to manage infrastructure lifecycles without human intervention. However, while automation does provide many benefits like mitigating human error, one of the most significant risks is compliance.

compliance (noun): conformity in fulfilling official requirements

Infrastructure is much like software; it has requirements. For example, a web application server may require NGINX, whereas a machine learning environment may require Apache Spark. Requirements define the desired state of the infrastructure.

As infrastructure continues to increase in complexity, ensuring that requirements are satisfied imposes new challenges. For example, if a new release of NGINX is not backward compatible with an older version, then the web application described in the previous section may not be deployable. Therefore, ensuring the desired state of infrastructure is essential and guarantees correctness.

The purpose of this repository is to demonstrate managing the configuration code for provisioning and maintaining the lifecycle of an Amazon Machine Image (AMI) using Ansible and InSpec.

Requirements

Usage

The following environment variables are required to use this repository and are used by the Docker Compose file.

Name Description
AWS_ACCESS_KEY_ID Specifies an AWS access key associated with an IAM user or role
AWS_SECRET_ACCESS_KEY Specifies the secret key associated with the access key
AWS_DEFAULT_REGION Specifies the AWS Region to send the request to

To learn more about Docker and Docker Compose, please visit this documentation.

This repository uses InSpec, a platform-agnostic framework for ensuring the compliance of infrastructure.

The test directory contains InSpec tests that specify compliance requirements.

test
└── integration
    └── default
        ├── controls
        │   └── default.rb
        ├── default.yml
        ├── files
        │   └── packages.yml
        └── inspec.yml

4 directories, 4 files

The default.rb file contains InSpec resources for determining compliance.

AMI Lifecycle

The AMI lifecycle consists of three essential stages: create, verify, and destroy. The create stage is responsible for creating a new EC2 instance using the source AMI defined in the kitchen.yml configuration file. The verify stage is responsible for running compliance tests. The destroy stage is responsible for removing the EC2 instance.

Why are these stages necessary?

Creating and destroying EC2 instances is time-consuming and results in slower feedback. Suppose that building a new Amazon Machine Images takes 30 minutes to complete. This process includes starting a new EC2 instance, waiting for SSH to become available, running any provisioning scripts, running compliance tests, and then destroying the instance. If the provisioning scripts run successfully, but the compliance tests fail, then nearly 30 minutes is wasted to receive feedback. Similar to writing software, building infrastructure requires the ability to make changes quickly and more frequently.

This repository uses Kitchen, an Infrastructure as Code (IaC) framework that manages the lifecycle of the resources that it creates.

This repository includes a Dockerfile and a Docker Compose file for making it easier to write and test changes to the Amazon Machine Image.

To run the entire Kitchen suite of commands, invoke the following command:

$ docker-compose up

The previous command invokes the Kitchen test subcommand, which creates a new instance, runs the Ansible Playbook, runs the compliance tests, and then destroys the instance.

To invoke different subcommands, invoke the following command:

$ docker-compose run --rm target COMMAND

Where target is the name of the Docker Compose service, --rm removes the container after the subcommand finishes execution, and COMMAND is any of the subcommands defined in the kitchen (executable) documentation.

Examples

Suppose Apache2 Web Server is a required dependency for the Amazon Machine Image. Since having Apache2 installed is the desired state, then writing the InSpec test first is trivial.

Append the following InSpec code to the test/integration/default/controls/default.rb file:

describe package('apache2') do
  it { should be_installed }
end

After adding the InSpec code, invoke the following command:

$ docker-compose run --rm target verify

The command above yields the following output:

Profile: Ubuntu 16.04 (ubuntu-16.04)
Version: 0.1.0
Target:  ssh://ubuntu@ec2-18-233-156-37.compute-1.amazonaws.com:22

  File /home/ubuntu/.bashrc
     ✔  should exist
  File /home/ubuntu/.bash_profile
     ✔  should exist
  Directory /etc/sudoers.d
     ✔  should exist
  System Package curl
     ✔  should be installed
  System Package g++
     ✔  should be installed
  System Package git
     ✔  should be installed
  System Package unzip
     ✔  should be installed
  System Package unattended-upgrades
     ✔  should not be installed
  Service apt-daily.service
     ✔  should not be running
  Service apt-daily.timer
     ✔  should not be running
  File /usr/local/bin/packer
     ✔  should exist
     ✔  should be executable
  System Package apache2
     ×  should be installed
     expected that `System Package apache2` is installed

Test Summary: 12 successful, 1 failure, 0 skipped

The InSpec tests fail because apache2 is not installed. To resolve this issue, modify the Ansible task in the common role to include apache2 as a dependency to install via apt:

- name: Install required packages
  apt:
    name:
      - apache2
      - g++
      - git
      - unzip
    state: present
    update_cache: false
  become: true

After updating the task, invoke the following commands:

$ docker-compose run --rm target converge
$ docker-compose run --rm target verify

After invoking the command above, the output should be as follows:

Profile: Ubuntu 16.04 (ubuntu-16.04)
Version: 0.1.0
Target:  ssh://ubuntu@ec2-18-233-156-37.compute-1.amazonaws.com:22

  File /home/ubuntu/.bashrc
     ✔  should exist
  File /home/ubuntu/.bash_profile
     ✔  should exist
  Directory /etc/sudoers.d
     ✔  should exist
  System Package curl
     ✔  should be installed
  System Package g++
     ✔  should be installed
  System Package git
     ✔  should be installed
  System Package unzip
     ✔  should be installed
  System Package unattended-upgrades
     ✔  should not be installed
  Service apt-daily.service
     ✔  should not be running
  Service apt-daily.timer
     ✔  should not be running
  File /usr/local/bin/packer
     ✔  should exist
     ✔  should be executable
  System Package apache2
     ✔  should be installed

Test Summary: 13 successful, 0 failures, 0 skipped

Notice that the previous commands do not destroy the EC2 instance. So if the image requires nginx instead of apache2, then the change can be easily made without having to wait for the EC2 instance to be created, provisioned, and destroyed.

This functionality is possible with Kitchen and provides great value for enabling faster feedback when making changes to infrastructure.

To list the instances managed via Kitchen and their last action, invoke the following command:

$ docker-compose run --rm target list
Instance             Driver  Provisioner      Verifier  Transport  Last Action  Last Error
default-ubuntu-1604  Ec2     AnsiblePlaybook  Inspec    Ssh        Verified     <None>

License

MIT License