Docker overlay network using Flannel

This is the next blog post in the series where I’ll attempt to build a full multi-node kubernetes cluster from scratch. You can find the previous post HERE where I describe bringing up a two-node cluster without using overlay network.

The first thing you need once you start scaling up your containers on different hosts is a consistent networking model, the primary requirement of which is to enable two(or more) containers on different hosts to talk to each other.  Now port forwarding might give you the same result when dealing with less number of containers but this approach gets out of control very quickly and you’re left to wade through port forwarding mess. What we want in situations like these is a network where each container on every hosts gets a unique IP address from a global namespace and all containers can then talk to each other. This is one of the fundamental requirements for kubernetes network implementation as specified HERE.

Two tools that solve the problem and are trending right now are Flannel and Weave. This post will describe how to get the containers on two Docker hosts to talk to each other using Flannel. HERE is the link to the scripts if you prefer reading the code instead of a writeup.

Bootstrapping

As with the previous tutorial, we’ll use two Vagrant hosts for this demo. You might as well choose any cloud provider to do the same, as long as the hosts can talk to each other

Put the following Vagrant file in any folder, say /home/kube/Vagrantfile.

# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  config.vm.define "host1" do |hostone|
    hostone.vm.box = "trusty64"
    hostone.vm.network "private_network", ip: "192.168.33.10"
    hostone.vm.hostname = "host-one"
  end

  config.vm.define "host2" do |hosttwo|
    hosttwo.vm.box = "trusty64"
    hosttwo.vm.network "private_network", ip: "192.168.33.11"
    hosttwo.vm.hostname = "host-two"
  end
end

(gist link)

Following commands will bring up the two hosts and drop you into their respective shell.

[terminal 1] $ cd /home/kube
[terminal-1] $ vagrant box add https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box --name trusty64
[terminal-1] $ vagrant up host-one
[terminal-1] $ vagrant ssh host-one
 
 
[terminal-2] $ vagrant up host-two
[terminal-2] $ vagrant ssh host-two

(gist link)

You need to install Docker on both hosts, the instructions for which are available HERE.

As with the previous post, I’ll put more effort to provide a fully functional script first and then explaining the steps here.

Flannel

Lets begin by setting up Flannel. To quote from Flannel github page

flannel (originally rudder) is an overlay network that gives a subnet to each machine for use with Kubernetes.

Flannel has one additional requirement, etcd, which might make it less attractive in some cases but not when using it with Kubernetes as kubernetes already uses etcd for its data storage.

Installing etcd (host 1): This step can be skipped if etcd is already installed and listening on an available port on any of the hosts.

Install etcd server on _host-one _ONLY using following script. Flannel daemon on host-two will read the configuration settings by connecting to etcd server running on host-one. The etcd server can also be running on any other host. Add this in /etc/init/ folder so that it can be managed by upstart. We don’t need to start etcd manually as it’ll be taken care of by the setup script.

description "Etcd service"
author "@jainvipin"

start on filesystem or runlevel [2345]
stop on runlevel [!2345]

respawn

pre-start script
        # see also https://github.com/jainvipin/kubernetes-ubuntu-start
        ETCD=/opt/bin/$UPSTART_JOB
        if [ -f /etc/default/$UPSTART_JOB ]; then
                . /etc/default/$UPSTART_JOB
        fi
        if [ -f $ETCD ]; then
                exit 0
        fi
    echo "$ETCD binary not found, exiting"
    exit 22
end script

script
        # modify these in /etc/default/$UPSTART_JOB (/etc/default/docker)
        ETCD=/opt/bin/$UPSTART_JOB
        ETCD_OPTS=""
        if [ -f /etc/default/$UPSTART_JOB ]; then
                . /etc/default/$UPSTART_JOB
        fi
        exec "$ETCD" $ETCD_OPTS
end script

(gist link)

Defining the subnet (host 1):

The line HERE uses the _etcdctl _utility to connect to etcd server running on master and inserting the flannel subnet value. Flannel daemons running on each host will connect to this etcd server and read the key (_coreos.com/network/config) _which will determine the subnet the lie in. Since this is a part of setup script, you don’t have to do anything here.

Configuring Flannel (both hosts):

We need to make sure flannel starts before docker does on every host because docker will use the flannel bridge instead of using the default bridge. Place the following file in /etc/init/flanneld.conf

description "Flanneld service"
author "@ric03uec"
 
start on filesystem or runlevel [2345]
stop on runlevel [!2345]
 
respawn
 
pre-start script
        FLANNELD=/usr/bin/$UPSTART_JOB
        if [ -f /etc/default/$UPSTART_JOB ]; then
                . /etc/default/$UPSTART_JOB
        fi
        if [ -f $FLANNELD ]; then
                exit 0
        fi
    echo "$FLANNELD binary not found, exiting"
    exit 22
end script
 
script
        # modify these in /etc/default/$UPSTART_JOB (/etc/default/flanneld)
        FLANNELD=/usr/bin/$UPSTART_JOB
        FLANNELD_OPTS=""
        if [ -f /etc/default/$UPSTART_JOB ]; then
                . /etc/default/$UPSTART_JOB
        fi
        exec "$FLANNELD" $FLANNELD_OPTS
end script

(gist link)

Configuring Docker (both hosts):

As mentioned above, docker daemon should start after flannel daemon. I’ve modified the default docker upstart config to  add dependency on flannel.

– lines 3 and 4 make sure docker starts/stops with flannel daemon.

– lines 44 and 45 import the environment variables exported by flannel and inserts them into _DOCKEROPTS. Specifically, _FLANNELSUBNET variable is used for docker –bip flag and _FLANNELMTU variable is used for docker —mtu flag.

As with the conf files above, the file below replaces /etc/init/docker.conf

description "Docker daemon"

start on started flanneld
stop on stopping flanneld

limit nofile 524288 1048576
limit nproc 524288 1048576

respawn

pre-start script
        # see also https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount
        if grep -v '^#' /etc/fstab | grep -q cgroup \
                || [ ! -e /proc/cgroups ] \
                || [ ! -d /sys/fs/cgroup ]; then
                exit 0
        fi
        if ! mountpoint -q /sys/fs/cgroup; then
                mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup
        fi
        (
                cd /sys/fs/cgroup
                for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do
                        mkdir -p $sys
                        if ! mountpoint -q $sys; then
                                if ! mount -n -t cgroup -o $sys cgroup $sys; then
                                        rmdir $sys || true
                                fi
                        fi
                done
        )
end script
script
        # modify these in /etc/default/$UPSTART_JOB (/etc/default/docker)
        DOCKER=/usr/bin/$UPSTART_JOB
        DOCKER_OPTS=
        if [ -f /etc/default/$UPSTART_JOB ]; then
                . /etc/default/$UPSTART_JOB
        fi

        if [ -f /var/run/flannel/subnet.env ]; then
        ## if flannel subnet env is present, then use it to define
        ## the subnet and MTU values
                . /var/run/flannel/subnet.env
                DOCKER_OPTS="$DOCKER_OPTS --bip=${FLANNEL_SUBNET} --mtu=${FLANNEL_MTU}"
        else
                echo "Flannel subnet not found, exiting..."
                exit 1
        fi

        exec "$DOCKER" -d $DOCKER_OPTS
end script

# Don't emit "started" event until docker.sock is ready.
# See https://github.com/docker/docker/issues/6647
post-start script
        DOCKER_OPTS=
        if [ -f /etc/default/$UPSTART_JOB ]; then
                . /etc/default/$UPSTART_JOB
        fi
        if ! printf "%s" "$DOCKER_OPTS" | grep -qE -e '-H|--host'; then
                while ! [ -e /var/run/docker.sock ]; do
                        initctl status $UPSTART_JOB | grep -q "stop/" && exit 1
                        echo "Waiting for /var/run/docker.sock"
                        sleep 0.1
                done
                echo "/var/run/docker.sock is up"
        fi
end script

(gist link)

 Running the script (both hosts):

Run the following commands on both hosts after editing the _NODEIP and _MASTER_IP _environment variables in setup.sh script

[terminal-1 (host-one)] $ ./setup.sh master
[terminal-1 (host-two)] $ ./setup.sh slave

(gist link)

Testing:

Run the following commands in the specified order to test. This uses the nc utility to test a connection between two containers on different hosts. We run a listener inside a container on host 1 and echo some text to it from a second container inside host 2 using the first containers IP address.

[terminal-1 (host-one)] $ container_id=$(docker run -d -p 5555:5555 ubuntu:14.04 nc -l 5555)
[terminal-1 (host-one)] $ docker inspect $container_id | grep IPA //gives the IP Address of the container
[terminal-1 (host-one)] $ docker logs -f $container_id

[terminal-2 (host-two)] $ docker run -it ubuntu:14.04 /bin/bash
[terminal-2 (host-two)]<container_id> $ echo 'Hello, it works !!' > source_file
[terminal-2 (host-two)]<container_id> $ nc <container1_ip> 5555 < source_file

[terminal-1 (host-one)] $ Hello, it works!!

(gist link)

That’s it !!! Hope the setup works for you. Feel free to comment with suggestions and any bugs you find in the script.

Share Comments
comments powered by Disqus