Docker quick start guide

I have avoided the whole Docker thing for some time now, mainly cause I didn’t understand why the world needed another kernel level virtualization product. I have been using OpenVZ and FreeBSD Jails for years, and just never saw the need to add another product to the mix.

But after spending some time with it, little did I realize that Docker is nothing more than a wrapper script essentially for using LXC on Linux. So if your already familiar with OpenVZ, LXC, FreeBSD Jails, or Solaris Zones, then Docker it actually really simple to pick up.

I am not going to go into the details of what container virtualization is and the pros and cons since there are literally hundreds of good sites online that dive into that.

Instead so you can see it in action, since many people learn what questions to ask by actually doing it, I’ll post a basic example here so you can get started quickly on CentOS 7 running Docker.

Our end goal for this quick start guide will simply illustrate how you can use Docker on a single CentOS 7 server to spin up multiple containers for a test environment. The Docker host can be a VM, cloud server, vagrant image, or a dedicated server.

So to get started, first configure the basics on your CentOS 7 server, such as setting the hostname, updating the system, installing NTP and sysstat, and rebooting so your running off the latest kernel:

[root@localhost ~]# hostnamectl set-hostname docker01.example.com
[root@docker01 ~]# yum -y update
[root@docker01 ~]# yum install ntp sysstat
[root@docker01 ~]# chkconfig ntpd on
[root@docker01 ~]# service ntpd start
[root@docker01 ~]# reboot

Now setup the official Docker repo on your server:

[root@docker01 ~]# vim /etc/yum.repos.d/docker.repo
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/
enable=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg

Then install Docker:

[root@docker01 ~]# yum install docker-engine
[root@docker01 ~]# systemctl enable docker
[root@docker01 ~]# systemctl start docker

Since there is really no reason to run Docker as root, we are going to setup a unprivileged user to run Docker:

[root@docker01 ~]# useradd dockeradmin
[root@docker01 ~]# usermod dockeradmin -G docker

Now log in as user dockeradmin, and confirm Docker is working:

[root@docker01 ~]# su - dockeradmin
[dockeradmin@docker01 ~]# docker ps
[dockeradmin@docker01 ~]# docker images

If no errors are returned, then Docker is working! So lets pull down some OS images I use often. This is not a requirement, but it just makes deploying new containers a bit faster:

[dockeradmin@docker01 ~]# docker pull centos:centos6
[dockeradmin@docker01 ~]# docker pull centos:centos7
[dockeradmin@docker01 ~]# docker pull ubuntu:precise
[dockeradmin@docker01 ~]# docker pull ubuntu:trusty
[dockeradmin@docker01 ~]# docker pull ubuntu:xenial

Lets spin up our first container:

[dockeradmin@docker01 ~]# docker run --name centos6-test01 -id --restart unless-stopped centos:centos6 /bin/bash

Once its running, you can get to a console by running:

[dockeradmin@docker01 ~]# docker exec -it centos6-test01 /bin/bash

From there, you will be able to configure and use the container as you see fit!

Some other basic commands you will need to know for how to interact with Docker are below.

To see your running containers:

[dockeradmin@docker01 ~]# docker ps

To see both your running and stopped containers:

[dockeradmin@docker01 ~]# docker ps -a

To stop a running container:

[dockeradmin@docker01 ~]# docker stop your_container_name

To start up a stopped container:

[dockeradmin@docker01 ~]# docker start your_container_name

To create an image of a running container that you can use to deploy new containers from:

[dockeradmin@docker01 ~]# docker commit -m "your_commit_message" -a dockeradmin your_container_name dockeradmin/your_new_image_name:v1

To see what images you have setup:

[dockeradmin@docker01 ~]# docker images

To remove an image:

[dockeradmin@docker01 ~]# docker rmi your_image_id

If you would like you view the stats of your containers:

[dockeradmin@docker01 ~]# docker stats

To delete a container:

[dockeradmin@docker01 ~]# docker stop your_container_name
[dockeradmin@docker01 ~]# docker rm your_container_name

If you want to stop all running containers and delete them from the system so you can start fresh:

[dockeradmin@docker01 ~]# docker stop `docker ps -a -q`
[dockeradmin@docker01 ~]# docker rm `docker ps -a -q`

This should get you started into the world of Docker. Once you get a feel for it, treating the containers as their own “vm’s”, you will start to recognize some of the benefits of Docker.

From there, you will be able to start diving deeper and instead of using containers as a “vm”, you can instead start thinking about how to break up your application into individual containers to give you a finer degree of control and portability over your application.

Determine how sites react under increased requests with curl

There are times when you test out your site using curl, and it loads fine. But yet when multiple users or a search bot is going through your site, you notice the page request times skyrocket. There could be many causes, though usually it revolves around a lack of caching since repeated requests for the same page need to first check in with the database, then get passed over to PHP to finish processing the request.

Okay great, cache is king. We know this. But how can we actually confirm this is a problem? Well, this is where curl comes to the rescue.

First, establish a baseline by sending a single request to each website:

[user@workstation01 ~]# for i in www.example1.com www.example2.com www.example3.com; do echo -n "$i "; (time curl -IL $i -XGET) 2>&1 | grep -E "real|HTTP"; echo; done

www.example1.com HTTP/1.1 200 OK
real	0m0.642s

www.example2.com HTTP/1.1 200 OK
real	0m2.234s

www.example3.com HTTP/1.1 200 OK
real	0m0.421s

So based off those results, we established that 2 of the sites take about half a second to load, and the other one (www.example2.com) takes about 2 seconds to load when hit with a single request.

As www.example2.com takes 2 seconds to load with a single request, lets see what happens during increased traffic. This could be from valid users, search bots, or something else. How much will the load times increase for the site? Here is an example sending over 25 requests:

[user@workstation01 ~]# for i in {1..25}; do (time curl -sIL http://www.example2.com -X GET &) 2>&1 | grep -E "real|HTTP" & done

HTTP/1.1 200 OK
real	0m11.297s
HTTP/1.1 200 OK
real	0m11.395s
HTTP/1.1 200 OK
real	0m11.906s
HTTP/1.1 200 OK
real	0m12.079s
...
HTTP/1.1 200 OK
real	0m11.297s

So with the increased requests, the page load times increase from 2 seconds all the way up to 11-12 seconds!

Determining why this happens will involve investigation outside the scope of this article. However at least now we know which site doesn’t perform well under increased requests.

While every site is different, lets say www.example2.com was a WordPress site. Try installing the WordPress W3 Total Cache (W3TC) plugin. Basic items to enable/disable within W3TC would be:

- Enable Page Cache
- Disable Minify options
- Enable Browser Cache

Next, sign up with a free CloudFlare account, then add the following PageRules so CloudFlare actually starts caching your WordPress site:

1. *example2.com/wp-admin* : Cache Level (Bypass)
2. *example2.com/wp-login.php* : Cache Level (Bypass)
3. *example2.com/* : Cache Level (Cache Everything)
* The order is extremely critical here.  Make sure the pages you do not wish to cache are above the last line!
** Every site is different.  These rules may or may not work for you.

Then retest to see if your changes helped improve the times.

[user@workstation01 ~]# for i in {1..25}; do (time curl -sIL http://www.example2.com -X GET &) 2>&1 | grep -E "real|HTTP" & done

HTTP/1.1 200 OK
real	0m1.347s
HTTP/1.1 200 OK
real	0m1.342s
HTTP/1.1 200 OK
real	0m1.237s
HTTP/1.1 200 OK
real	0m1.021s
...
HTTP/1.1 200 OK
real	0m1.532s

Vagrant Introduction

What is Vagrant? Taken directly from the vendors website:

Vagrant provides easy to configure, reproducible, and portable work environments built on top of industry-standard technology and controlled by a single consistent workflow to help maximize the productivity and flexibility of you and your team.

For more details about how Vagrant can be beneficial, I strongly encourage you to read the vendors website at:
https://www.vagrantup.com/docs/why-vagrant

Vagrant can be somewhat difficult to understand without seeing it in action. But in summary, building test servers can take a long time. With vagrant, you can run your test servers with a few simple commands so you can begin performing the tests that you wanted to get done without the wait. The environments are portable, so its very easy to share with colleagues.

All Vagrant does is communicate with providers of your choice such as VirtualBox, AWS, Rackspace Cloud, etc, and spins up the boxes using the respective providers API’s.

Installation

Installing Vagrant with VirtualBox is simple and easy on most desktop OS’s running Windows, Mac OSX, and Linux. Simply use the links below to download and install both VirtualBox and Vargrant on your desktop:

Known working versions
https://www.virtualbox.org/wiki/Download_Old_Builds_5_0
https://releases.hashicorp.com/vagrant/1.8.4/ **

** Vagrant v1.8.5 currently has issues with CentOS. Therefore, use v1.8.4 for now. Once v1.8.6 comes out, the bug should be addressed.

Once you have Vagrant and VirtualBox installed on your desktop, you are ready to begin deploying your test servers.

Getting familiar with Vagrant

Some quick terms you need to know:
Vagrantfile: This is the main configuration file for your test server.
Provider: Which API are we using? AWS, Rackspace Cloud, VirtualBox, etc.
Vagrant Boxes: This is the Vagrant base image. Think of it as your golden template that is used to deploy your test servers.

To manage your individual environments or test servers, you simply create the directory on your desktop, and tell Vagrant to deploy the OS to that individual directory.

An example of some of the test environments on my workstation are:

/home/user/vagrant/centos6
/home/user/vagrant/ubuntu1404
/home/user/vagrant/centos6-lamp-environment

Most of the common distro’s used today have official boxes available. I included puppetlabs as they have images that are up to date with base images as well as boxes with Puppet Enterprise already installed:
https://atlas.hashicorp.com/centos
https://atlas.hashicorp.com/ubuntu
https://atlas.hashicorp.com/debian
https://atlas.hashicorp.com/freebsd
https://atlas.hashicorp.com/puppetlabs

Quick start to see Vagrant in action

This is just a quick way to see Vagrant in action. It is important to remember that all the vagrant commands apply to whatever directory you are in on your desktop!

First, create the directory on your desktop for your test server:

[user@workstation ~]# mkdir -p vagrant/centos6-test
[user@workstation ~]# cd vagrant/centos6-test

Now initialize a Vagrantfile, which tells Vagrant what image to use, and how to configure it:

[user@workstation ~]# vagrant init centos/6

Then startup the test server:

[user@workstation ~]# vagrant up

And thats it! You can now log into your test server and get to work by running:

[user@workstation ~]# vagrant ssh

Once your done with your testing, you can remove the test server by running:

[user@workstation ~]# vagrant destroy

Command commands

These commands are to be ran from inside the directory of your project.

To check the status of a single test server:

[user@workstation ~]# vagrant status

If you made changes to your Vagrantfile and need to reload your settings:

[user@workstation ~]# vagrant reload

If you want to shutdown a test server, but you don’t want to destroy it:

[user@workstation ~]# vagrant halt

To check the status of all your test servers:

[user@workstation ~]# vagrant global-status

How to customize your test server

What makes Vagrant powerful is the Vagrantfile that resides in each directory for your test servers. This file allows you to specify IP’s, set users and passwords, run bootstrap scripts, or even spin up multiple servers from one file. This greatly speeds up the provisioning time so you can focus on testing what you truly needed to test.

The examples below are full examples of the Vagrantfile you can copy in your projects directory to spin up test servers.

Here is an example for spinning up a Ubuntu 12.04 server, and having Apache already installed:

[user@workstation ~]# mkdir vagrant/ubuntu1204-test01
[user@workstation ~]# cd vagrant/ubuntu1204-test01
[user@workstation ~]# vim Vagrantfile
  config.vm.box = "hashicorp/precise32"
  config.vm.provision "shell", inline: <<-SHELL
    sudo apt-get update
    sudo apt-get install -y apache2
    SHELL
end

[user@workstation ~]# vagrant up

Below is an example for spinning up a CentOS 6 server with the private IP address of 172.16.0.2, accessible only to your workstation:

[user@workstation ~]# mkdir vagrant/ubuntu1204-test01
[user@workstation ~]# cd vagrant/ubuntu1204-test01
[user@workstation ~]# vim Vagrantfile
Vagrant.configure(2) do |config|
  config.vm.box = "centos/6"
  config.vm.network "private_network", ip: "172.16.0.2"
end

[user@workstation ~]# vagrant up

Below is an example for spinning up a CentOS 6 server with the public IP address of 123.123.123.123, accessible by anyone:

[user@workstation ~]# mkdir vagrant/ubuntu1204-test01
[user@workstation ~]# cd vagrant/ubuntu1204-test01
[user@workstation ~]# vim Vagrantfile
Vagrant.configure(2) do |config|
  config.vm.box = "centos/6"
  config.vm.network "public_network", ip: "123.123.123.123"
end

[user@workstation ~]# vagrant up

Below is an example for spinning up a CentOS 6 server with Puppet Enterprise, with the private IP address of 10.1.0.3, and adding a bootstrap.sh file that we'll use for automatically our build process to install and configure Nginx:

[user@workstation ~]# mkdir vagrant/centos6-puppet-server01
[user@workstation ~]# cd vagrant/centos6-puppet-server01
[user@workstation ~]# vim Vagrantfile
Vagrant.configure(2) do |config|
  config.vm.box = "puppetlabs/centos-6.6-64-puppet"
  config.vm.network "private_network", ip: "10.1.0.3"
  config.vm.network "forwarded_port", guest: 80, host: 80
  config.vm.provision :shell, path: "bootstrap.sh"
end

[user@workstation ~]# vim bootstrap.sh
#!/usr/bin/env bash

# Sanity checks
if [ ! `whoami` = root ]; then
        echo "This script must be ran by the user:  root"
        exit 1
fi

# CentOS specific tasks:
if [ -f /etc/redhat-release ]; then
	selinux=`getenforce`
	if [ $selinux = "Enforcing" ]; then
		setenforce 0
	fi
	
	if [ `cat /etc/redhat-release | grep "Linux release 7" | wc -l` = 1 ]; then
		yum -y install iptables-services
	fi
fi

# Install required Puppet modules from the forge
puppet module install puppetlabs-stdlib
puppet module install jfryman-nginx
puppet module install puppetlabs-firewall

# General default.pp for Puppet
cat << EOF > /root/default.pp

class { 'nginx': }

nginx::resource::vhost { 'example.com':
  www_root    => '/var/www/example.com',
}

package { 'git':
  ensure => present,
}

firewall { '000 accept all icmp':
  proto  => 'icmp',
  action => 'accept',
}

firewall { '100 allow SSH':
  port   => [22],
  proto  => tcp,
  action => accept,
}

firewall { '101 allow http over port 80':
  port   => [80],
  proto  => tcp,
  action => accept,
}

EOF

# Execute first run of default.pp
puppet apply /root/default.pp

[user@workstation ~]# vagrant up

Below is an example for spinning up 2x CentOS 6 server with private IP addresses, accessible only to your workstation, both with 1G of memory, and installing MySQL on the db server, and Apache/PHP on the web server:

Vagrant.configure(2) do |config|
  config.vm.define "db01" do |db01|
    db01.vm.box = "centos/6"
    db01.vm.hostname = "web01.example.com"
    db01.vm.network "private_network", ip: "192.168.33.100"
    db01.vm.provision "shell", inline: <<-SHELL
        sudo yum update
        sudo yum install -y mysql
    SHELL
    db01.vm.provider "virtualbox" do |vb|
      vb.gui = false
      vb.memory = "1024"
    end
  end
  config.vm.define "web01" do |web01|
    web01.vm.box = "centos/6"
    web01.vm.hostname = "web02.example.com"
    web01.vm.network "private_network", ip: "192.168.33.101"
    web01.vm.provider "virtualbox" do |vb|
      vb.gui = false
      vb.memory = "1024"
    end
   web01.vm.provision "shell", inline: <<-SHELL
      sudo yum update
      sudo yum install -y httpd php
   SHELL
  end
end

[user@workstation ~]# vagrant up