Docker-machine is tool to create Docker hosts on computer, on cloud providers, and inside data center. It creates Linux based server, and installs and configures docker. It is also capable of configuring docker-swarm nodes.

This blog, will explain stepwise walkthrough for docker host creation using docker-machine.

Installation.

  • If you use Windows or Mac, Docker has already made awesome packaged installer for you Docker-toolbox. Download and Install it.

  • For Linux users, you can download docker-machine binaries from here.

NOTE: Linux users, do remember, you need to install docker client also on your machine.

Creation of docker host with machine.

I will be using VirtualBox in this demo, but you can explore cloud options too.

Docker host can be created with command docker-machine create. Here -d flags specifies driver-name. So command looks like as follows.

$ docker-machine create -d <driver-name> <name-of-machine>

This command does couple of things at backend such as

  • It downloads latest boot2docker image, if not locally available.
  • Create a machine with name-of-machine
  • Creates ssh keys and copies to machine.
  • Installs docker
  • Configures docker daemon at port 2376, so that docker daemon accessible at tcp://<HostIP>:2376

Lets create our first host testhost.

$ docker-machine create -d virtualbox testhost
Running pre-create checks...
Creating machine...
(testhost) Copying C:\Users\kunal\.docker\machine\cache\boot2docker.iso to C:\Users\kunal\.docker\machine\machines\testhost\boot2docker.iso...
(testhost) Creating VirtualBox VM...
(testhost) Creating SSH key...
(testhost) Starting VM...
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect Docker to this machine, run: C:\Program Files\Docker Toolbox\docker-machine.exe env testhost

Here our docker host is ready for use. But wait! host is not same machine as your machine right? It is running inside Virtual Machine. So how docker daemon is accessed remotely?

Docker works on client-server model. Docker daemon is server and it can communicates though REST API’s

NOTE: docker-machine create is most important command of docker-machine. Understanding various flags help you to get best of docker-machine. You must spend some time in understanding all flags

  • To access docker host with docker client binary remotely, we need to export some environment variables. Docker-machine provides a handy command for printing these variables and their values for us as shown below.

    $ docker-machine env testhost
    export DOCKER_TLS_VERIFY="1"
    export DOCKER_HOST="tcp://192.168.99.110:2376"
    export DOCKER_CERT_PATH="C:\Users\kunal\.docker\machine\machines\testhost"
    export DOCKER_MACHINE_NAME="testhost"
    # Run this command to configure your shell:
    # eval $("C:\Program Files\Docker Toolbox\docker-machine.exe" env testhost)
    
  • To export all environment variable at once, simply run

    $ eval $(docker-machine env testhost)
    
  • Now all docker commands will communicates with docker daemon of testhost

    $ docker info
    Containers: 0
    Images: 0
    Server Version: 1.9.1
    Storage Driver: aufs
     Root Dir: /mnt/sda1/var/lib/docker/aufs
     Backing Filesystem: extfs
     Dirs: 0
     Dirperm1 Supported: true
    Execution Driver: native-0.2
    Logging Driver: json-file
    Kernel Version: 4.1.13-boot2docker
    Operating System: Boot2Docker 1.9.1 (TCL 6.4.1); master : cef800b - Fri Nov 20 19:33:59 UTC 2015
    CPUs: 1
    Total Memory: 996.2 MiB
    Name: testhost
    ID: 26MF:TVKB:JI7Q:TL4S:7RXM:U5CD:VHIR:W2NH:CUNZ:RGB3:C6GC:POBS
    Debug mode (server): true
     File Descriptors: 14
     Goroutines: 21
     System Time: 2016-01-07T07:09:51.081394779Z
     EventsListeners: 0
     Init SHA1:
     Init Path: /usr/local/bin/docker
     Docker Root Dir: /mnt/sda1/var/lib/docker
    Labels:
     provider=virtualbox
    

Lets create our first docker container on this host.

$ docker run busybox sh
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
c00ef186408b: Pulling fs layer
ac6a7980c6c2: Pulling fs layer
ac6a7980c6c2: Verifying Checksum
ac6a7980c6c2: Download complete
c00ef186408b: Verifying Checksum
c00ef186408b: Download complete
c00ef186408b: Pull complete
ac6a7980c6c2: Pull complete
Digest: sha256:e4f93f6ed15a0cdd342f5aae387886fba0ab98af0a102da6276eaf24d6e6ade0
Status: Downloaded newer image for busybox:latest

NOTE: If you are one of user, who works behind proxy, then you may face problem like, your newly created docker host may not communicate with docker-hub.

To Fix that, you need to pass few environment flags at creation time as below.

$ docker-machine create -d virtualbox \
--engine-env HTTP_PROXY="http://example.com:8080" \
--engine-env HTTPS_PROXY="http://example.com:8080" \
testhost

So now we learnt how to create a docker host and run docker containers using docker-machine. But Docker-machine not only lets you to create independent hosts, but also can put these host in one cluster!

Isn’t it interesting?

Let’s see how we can build whole cluster of docker-hosts using docker-machine.

Docker Swarm is native clustering solution for docker. Here with native means, Swarm understands and exports all(almost) docker API’s. i.e. API for docker and docker swarm are same. If one product/scripts works with docker, it will automatically work with swarm :)

Docker Swarm

Docker swarm has three components.

  • Discovery Backend : Swarm requires a discovery backend, which is used by each node(agent) to discover the master.
    • Default discovery is provided by Docker Hub. (Not for production usage)
  • Master : Swarm master takes care of all scheduling logic and HA.
  • Agent : These runs on each node and communicates with Swarm master.
    • All agent node must listen to the same network interface (TCP port).
    • Each node runs a node agent that registers the referenced Docker daemon, monitors it, and updates the discovery backend with the node’s status

In this demo, We will create a docker swarm consisting one Master and three agents including master. Also, Consul will be used for discovery backend.

Discovery Backend using consul.

We will create a dedicated machine for consul, but running consul using docker is just one command task.

$ docker-machine create -d virtualbox \
   --engine-env HTTP_PROXY="http://example.com:8080" \
   --engine-env HTTPS_PROXY="http://example.com:8080" \
   swl-consul

$ docker $(docker-machine config swl-consul) run \
   -e "http_proxy=http://example.com:8080" \
   -e "https_proxy=http://example.com:8080" \
   --restart="always" \
   -d -p "8500:8500" \
   -h "consul" \
   progrium/consul -server -bootstrap

Check if consul container is created as expected.

$ docker-machine ls
NAME         ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER   ERRORS
swl-consul   -        virtualbox   Running   tcp://192.168.99.100:2376           v1.9.1

$ eval $(docker-machine env swl-consul)

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                                            NAMES
5357e85abcb5        progrium/consul     "/bin/start -server -"   16 minutes ago      Up 16 minutes       53/tcp, 53/udp, 8300-8302/tcp, 8400/tcp, 8301-8302/udp, 0.0.0.0:8500->8500/tcp   goofy_aryabhata

Creation of Swarm Master.

In cluster, we will have one master and 2 agents. But master will also have agent, so practically we will have 3 agents.

Docker machine helps to setup docker host with swarm in single command.

Few highlights of docker machine provisioned swarm enabled host.

  • Swarm master and agent runs as docker containers.

  • Swarm manager bind on “3376” port, So to communicate with master tcp://<IP:3376>> should be used.

    $ docker-machine create \
    >    -d virtualbox \
    >    --swarm \
    >    --swarm-master \
    >    --swarm-discovery=consul://$(docker-machine ip swl-consul):8500 \
    >    --engine-env HTTP_PROXY=http://example.com:8080 \
    >    --engine-env HTTPS_PROXY=http://example.com:8080 \
    >    --engine-env NO_PROXY=192.168.99.100 \
    >    --engine-opt="cluster-store=consul://$(docker-machine ip swl-consul):8500" \
    >    --engine-opt="cluster-advertise=eth1:3376" \
    >    node1
    Running pre-create checks...
     .
     .
    (node1) Starting VM...
    Waiting for machine to be running, this may take a few minutes...
    Machine is running, waiting for SSH to be available...
    Detecting operating system of created instance...
    Detecting the provisioner...
    Provisioning with boot2docker...
    Copying certs to the local machine directory...
    Copying certs to the remote machine...
    Setting Docker configuration on the remote daemon...
    Configuring swarm...
    Checking connection to Docker...
    Docker is up and running!
    To see how to connect Docker to this machine, run: C:\Program Files\Docker Toolbox\docker-machine.exe env node1
    
    $ eval $(docker-machine env node1)
    
    $ docker ps
    CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
    7e644b71f606        swarm:latest        "/swarm join --advert"   19 seconds ago      Up 16 seconds                           swarm-agent
    58f2a4da438b        swarm:latest        "/swarm manage --tlsv"   22 seconds ago      Up 19 seconds                           swarm-agent-master
    

NOTE: You may have seen NO_PROXY is also set as environment variable. This is to skip proxy for communicating with consul server

Here,

  • --swarm indicates, swarm agent will be configured.
  • --swarm-master indicates, swarm master will be configured. --swarm-discovery sets the backend discovery for swarm.

Here you go, we have our swarm master node up and running. Before communicating to swarm, let me remind you again.

  • Swarm and docker talks in same API signature. But on our host, we have both docker daemon and swarm running.
  • Our docker client can communicate with both, but we need to choose whom it has to talk.
    • To talk with docker daemon use eval $(docker-machine env <host-name>)
    • To talk with swarm use eval $(docker-machine env --swarm <host-name>

So, since we want our docker client should communicate with swarm, we will add --swarm

$ eval $(docker-machine env --swarm node1)

$ docker info
Containers: 2
Images: 1
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 1
 node1: 192.168.99.102:2376
  └ Status: Healthy
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 1.021 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.1.13-boot2docker, operatingsystem=Boot2Docker 1.9.1 (TCL 6.4.1); master : cef800b - Fri Nov 20 19:33:59 UTC 2015, provider=virtualbox, storagedriver=aufs
CPUs: 1
Total Memory: 1.021 GiB
Name: node1

Now add two nodes with only agent.

$ docker-machine create \
>    -d virtualbox \
>    --swarm \
>    --swarm-discovery="consul://$(docker-machine ip swl-consul):8500" \
>    --engine-env HTTP_PROXY="http://example.com:8080" \
>    --engine-env HTTPS_PROXY="http://example.com:8080" \
>    --engine-env NO_PROXY="192.168.99.100" \
>    --engine-opt="cluster-store=consul://$(docker-machine ip swl-consul):8500" \
>    --engine-opt="cluster-advertise=eth1:3376" \  
>    node3
  • Command for adding agent doesn’t requires --swarm-master.
  • cluster-advertise and cluster-store are required for overlay networking also, Will be explained later.

So after adding two more machines with swarm agent, we have 4 machines running as below.

$ docker-machine ls
NAME         ACTIVE   DRIVER       STATE     URL                         SWARM            DOCKER   ERRORS
node1        -        virtualbox   Running   tcp://192.168.99.102:2376   node1 (master)   v1.9.1
node2        -        virtualbox   Running   tcp://192.168.99.103:2376   node1            v1.9.1
node3        -        virtualbox   Running   tcp://192.168.99.104:2376   node1            v1.9.1
swl-consul   -        virtualbox   Running   tcp://192.168.99.100:2376                    v1.9.1

Lets see information of our cluster.

$ eval $(docker-machine env --swarm node1)

$ docker info
Containers: 4
Images: 3
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 3
 node1: 192.168.99.102:2376
  └ Status: Healthy
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 1.021 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.1.13-boot2docker, operatingsystem=Boot2Docker 1.9.1 (TCL 6.4.1); master : cef800b - Fri Nov 20 19:33:59 UTC 2015, provider=virtualbox, storagedriver=aufs
 node2: 192.168.99.103:2376
  └ Status: Healthy
  └ Containers: 1
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 1.021 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.1.13-boot2docker, operatingsystem=Boot2Docker 1.9.1 (TCL 6.4.1); master : cef800b - Fri Nov 20 19:33:59 UTC 2015, provider=virtualbox, storagedriver=aufs
 node3: 192.168.99.104:2376
  └ Status: Healthy
  └ Containers: 1
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 1.021 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.1.13-boot2docker, operatingsystem=Boot2Docker 1.9.1 (TCL 6.4.1); master : cef800b - Fri Nov 20 19:33:59 UTC 2015, provider=virtualbox, storagedriver=aufs
CPUs: 3
Total Memory: 3.064 GiB
Name: node1

Superb! we have three nodes running in our cluster. This completes the swarm setup!!

Understanding few more Swarm functionality.

  1. Strategy

Currently “Spread” strategy is set. So the containers will be spread over all the hosts in cluster.

e.g.

$ docker run -d -P -m 1G -e MYSQL_ROOT_PASSWORD=test123 --name db mysql
57a1af46d72117306b833a28a219d3523e1531c98a19ef25cba00a7bc94c9645

$ docker run -d -P -m 1G --name frontend nginx
f40776d7c9f7583965263340c1542a09eb7cb881c53bcf18a290df0d6ce2fd51

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                         NAMES
f40776d7c9f7        nginx               "nginx -g 'daemon off"   4 seconds ago       Up 4 seconds        192.168.99.103:32770->80/tcp, 192.168.99.103:32769->443/tcp   node2/frontend
57a1af46d721        mysql               "/entrypoint.sh mysql"   3 minutes ago       Up 3 minutes        192.168.99.104:32769->3306/tcp                                node3/db
  • Here node3 have db and node2 have frontend container.
  • If two nodes have the same amount of available RAM and CPUs, the spread strategy prefers the node with least containers.

Other available strategies are BinPack and Random.

  • BinPack tries to pack all possible containers on single node first and so on.
  • If two nodes have the same amount of available RAM and CPUs, the binpack strategy prefers the node with most containers.

Things to Try:

  • Create swarm with BinPack and create multiple containers to see the behavior.

Filters

Filters tell Docker Swarm scheduler which nodes to use when creating and running a container.

Filters are divided into two categories, node filters and container configuration filters.

  • Node filters operate on characteristics of the Docker host or on the configuration of the Docker daemon.

  • Container configuration filters operate on characteristics of containers, or on the availability of images on a host.

  • Each filter has a name that identifies it.

    The node filters are:

    • constraint
    • health

    The container configuration filters are:

    • affinity
    • dependency
    • port

When you start a Swarm manager with the swarm manage command, all the filters are enabled. If you want to limit the filters available to your Swarm, specify a subset of filters by passing the --filter flag and the name:

$ swarm manage --filter=health --filter=dependency

In case of docker machine, while provisioning --swarm-opt can be used to set filters.

Use a constraint filter

Node constraints can refer to Docker’s default tags or to custom labels. Default tags are sourced from docker info. Often, they relate to properties of the Docker host. Currently, the dafult tags include:

  • node to refer to the node by ID or name
  • storagedriver
  • executiondriver
  • kernelversion
  • operatingsystem
  • Custom node labels can be applied while provisioning docker machine.
    • --engine-label can be used for setting custom label.
    • custom label like environment, storage etc are helpful.

Since we have all labels same except node name, we will try creating new container using node-name constraint.

$ docker run -itd -e constraint:node==node3 --name test1 ubuntu
ae4013d014931e43cd5342a625f6b549a8c920cb146b1371a7226359a4bcf014

$ docker run -itd -e constraint:node==node3 --name test2 ubuntu
a6b371773e1a2609122ca68df00d1e03ee274ba3db504d3942c301f8e2c45504

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
a6b371773e1a        ubuntu              "/bin/bash"         3 seconds ago       Up 2 seconds                            node3/test2
ae4013d01493        ubuntu              "/bin/bash"         19 seconds ago      Up 18 seconds                           node3/test1
934501aed30f        ubuntu              "/bin/bash"         19 hours ago        Up 2 hours                              node3/n4
30358292be89        ubuntu              "/bin/bash"         19 hours ago        Up 19 hours                             node1/n3
6a13aa3eca8b        ubuntu              "/bin/bash"         21 hours ago        Up 21 hours                             node2/n1

Similarly other filters can be used. While using filters the syntax to use in docker run is as below.

- ``-e <filter-name>:<key><operator><value>``
- Here operator used are  ``==`` , ``!=`` & ``~``
- For ``==`` & ``!=``, exact match is found.
  - If nothing satisfies the condition, it does schedule container.
- ``~`` is for soft condition. i.e. if nothing matches, it ignores the condition and use default scheduler.
- Here value can contain any valid regex (https://github.com/google/re2/wiki/Syntax)

Hope this article will be helpful in getting started with docker-machine and swarm :)