Kubernetes Cluster on Bare Metal System Made Possible using MetalLB

Estimated Reading Time: 11 minutes

If you try to setup Kubernetes cluster on bare metal system, you will notice that Load-Balancer always remain in the “pending” state indefinitely when created. This is expected because Kubernetes, by default does not offer an implementation of network load-balancer for bare metal cluster.

In a cloud-enabled Kubernetes cluster, you request a load-balancer, and your cloud platform assigns an IP address to you. In a bare metal cluster, you need an external Load-Balancer implementation which has capability to perform an IP allocation.

Missing Load-Balancer in Bare Metal System

Enter MetalLB…

MetalLB is a load-balancer implementation for bare metal Kubernetes clusters, using standard routing protocols. MetalLB provides a network load-balancer implementation for Kubernetes clusters that do not run on a supported cloud provider, effectively allowing the usage of LoadBalancer Services within any cluster. It aims to redress this imbalance by offering a Network LB implementation that integrates with standard network equipment, so that external services on bare metal clusters also “just work” as much as possible.

Why can’t Ingress help me out here?

Yes, Ingress could be one of the best option if you deployed Kubernetes cluster on bare metal. Ingress lets you configure internal load balancing of HTTP or HTTPS traffic to your deployed services using software load balancers like NGINX or HAProxy deployed as pods in your cluster. Ingress makes use of Layer 7 routing of your applications as well. The problem with this is that it doesn’t easily route TCP or UDP traffic. The best way to do this was using a LoadBalancer type of service. However, if you deployed your Kubernetes cluster to bare metal you didn’t have the option of using a LoadBalancer.

How does MetalLB work?

MetalLB hooks into your Kubernetes cluster, and provides a network load-balancer implementation. In short, it allows you to create Kubernetes services of type “LoadBalancer” in clusters that don’t run on a cloud provider, and thus cannot simply hook into paid products to provide load-balancers.

It is important to note that MetalLB cannot create IP addresses out of thin air, so you do have to give it pools of IP addresses that it can use. It will then take care of assigning and unassigning individual addresses as services come and go, but it will only ever hand out IPs that are part of its configured pools.

Okay wait.. How will I get IP Address pool for MetalLB?

How you get IP address pools for MetalLB depends on your environment. If you’re running a bare metal cluster in a colocation facility, your hosting provider probably offers IP addresses for lease. In that case, you would lease, say, a /26 of IP space (64 addresses), and provide that range to MetalLB for cluster services.

Under this blog post, I will showcase how to setup 3-Node Kubernetes cluster using MetalLB. The below steps have also been tested for ESXi Virtual Machines and works flawlessly.

Preparing the Infrastructure

[rml_read_more]

  • Machine #1(Master): 10.94.214.206
  • Machine #2(Worker Node1): 10.94.214.210
  • Machine #3(Worker Node2): 10.94.214.213

Assign hostname to each of these systems:

~$ cat /etc/hosts
127.0.0.1       localhost
127.0.1.1       ubuntu1804-1
10.94.214.206    kubemaster.dell.com
10.94.214.210   node1.dell.com
10.94.214.213   node2.dell.com

Installing curl package

$ sudo apt install curl
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
  libcurl4
The following NEW packages will be installed:
  curl libcurl4
0 upgraded, 2 newly installed, 0 to remove and 472 not upgraded.
Need to get 373 kB of archives.
After this operation, 1,036 kB of additional disk space will be used.
Do you want to continue? [Y/n] Y
Get:1 http://us.archive.ubuntu.com/ubuntu bionic-updates/main amd64 libcurl4 amd64 7.58.0-2ubuntu3.7 [214 kB]
Get:2 http://us.archive.ubuntu.com/ubuntu bionic-updates/main amd64 curl amd64 7.58.0-2ubuntu3.7 [159 kB]
Fetched 373 kB in 2s (164 kB/s)
Selecting previously unselected package libcurl4:amd64.
(Reading database ... 128791 files and directories currently installed.)
Preparing to unpack .../libcurl4_7.58.0-2ubuntu3.7_amd64.deb ...
Unpacking libcurl4:amd64 (7.58.0-2ubuntu3.7) ...
Selecting previously unselected package curl.
Preparing to unpack .../curl_7.58.0-2ubuntu3.7_amd64.deb ...
Unpacking curl (7.58.0-2ubuntu3.7) ...
Setting up libcurl4:amd64 (7.58.0-2ubuntu3.7) ...
Processing triggers for libc-bin (2.27-3ubuntu1) ...
Processing triggers for man-db (2.8.3-2) ...
Setting up curl (7.58.0-2ubuntu3.7) ...

Installing Docker

$ sudo curl -sSL https://get.docker.com/ | sh
# Executing docker install script, commit: 2f4ae48
+ sudo -E sh -c apt-get update -qq >/dev/null
+ sudo -E sh -c apt-get install -y -qq apt-transport-https ca-certificates curl >/dev/null
+ sudo -E sh -c curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" | apt-key add -qq - >/dev/null
Warning: apt-key output should not be parsed (stdout is not a terminal)
+ sudo -E sh -c echo "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable" > /etc/apt/sources.list.d/docker.list
+ sudo -E sh -c apt-get update -qq >/dev/null
+ [ -n  ]
+ sudo -E sh -c apt-get install -y -qq --no-install-recommends docker-ce >/dev/null
+ sudo -E sh -c docker version
Client:
 Version:           18.09.7
 API version:       1.39
 Go version:        go1.10.8
 Git commit:        2d0083d
 Built:             Thu Jun 27 17:56:23 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.7
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.8
  Git commit:       2d0083d
  Built:            Thu Jun 27 17:23:02 2019
  OS/Arch:          linux/amd64
  Experimental:     false
If you would like to use Docker as a non-root user, you should now consider
adding your user to the "docker" group with something like:

  sudo usermod -aG docker cse

Remember that you will have to log out and back in for this to take effect!

WARNING: Adding a user to the "docker" group will grant the ability to run
         containers which can be used to obtain root privileges on the
         docker host.
         Refer to https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface
         for more information.
cse@kubemaster:~$
~$ sudo docker version
Client:
 Version:           18.09.7
 API version:       1.39
 Go version:        go1.10.8
 Git commit:        2d0083d
 Built:             Thu Jun 27 17:56:23 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.7
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.8
  Git commit:       2d0083d
  Built:            Thu Jun 27 17:23:02 2019
  OS/Arch:          linux/amd64
  Experimental:     false

Add the Kubernetes signing key on both the nodes

$ sudo curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add
OK

Adding Xenial Kubernetes Repository on both the nodes

sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"

Installing Kubeadm

sudo apt install kubeadm

Verifying Kubeadm installation

$ sudo kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.0", GitCommit:"e8462b5b5dc2584fdcd18e6bcfe9f1e4d970a529", GitTreeState:"clean", BuildDate:"2019-06-19T16:37:41Z", GoVersion:"go1.12.5", Compiler:"gc", Platform:"linux/amd64"}

Disable swap memory (if running) on both the nodes

sudo swapoff -a

Steps to setup K8s Cluster

sudo kubeadm init --apiserver-advertise-address $(hostname -i)
mkdir -p $HOME/.kube
chown $(id -u):$(id -g) $HOME/.kube/config
kubectl apply -n kube-system -f \
    "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 |tr -d '\n')"

In case you face any issue, just run the below command to see the logs:

journalctl -xeu kubelet

Adding Worker Node

cse@ubuntu1804-1:~$ sudo swapoff -a
cse@ubuntu1804-1:~$ sudo kubeadm join 10.94.214.210:6443 --token aju7kd.5mlhmmo1wlf8d5un     --discovery-token-ca-cert-hash sha256:89541bb9bbe5ee1efafe17b20eab77e6b756bd4ae023d2ff7c67ce73e3e8c7bb
cse@ubuntu1804-1:~$ sudo swapoff -a
cse@ubuntu1804-1:~$ sudo kubeadm join 10.94.214.210:6443 --token aju7kd.5mlhmmo1wlf8d5un     --discovery-token-ca-cert-hash sha256:89541bb9bbe5ee1efafe17b20eab77e6b756bd4ae023d2ff7c67ce73e3e8c7bb
[preflight] Running pre-flight checks
        [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.15" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Activating the kubelet service
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

cse@ubuntu1804-1:~$

Listing the Nodes

cse@kubemaster:~$ sudo kubectl get nodes
NAME               STATUS   ROLES    AGE     VERSION
kubemaster         Ready    master   8m17s   v1.15.0
worker1.dell.com   Ready    <none>   5m22s   v1.15.0
cse@kubemaster:~$
cse@kubemaster:~$ sudo kubectl describe node worker1.dell.com
Name:               worker1.dell.com
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=worker1.dell.com
                    kubernetes.io/os=linux
Annotations:        kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Fri, 05 Jul 2019 16:10:33 -0400
Taints:             <none>
Unschedulable:      false
Conditions:
  Type                 Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
  ----                 ------  -----------------                 ------------------                ------                       -------
  NetworkUnavailable   False   Fri, 05 Jul 2019 16:10:55 -0400   Fri, 05 Jul 2019 16:10:55 -0400   WeaveIsUp                    Weave pod has set this
  MemoryPressure       False   Fri, 05 Jul 2019 16:15:33 -0400   Fri, 05 Jul 2019 16:10:33 -0400   KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure         False   Fri, 05 Jul 2019 16:15:33 -0400   Fri, 05 Jul 2019 16:10:33 -0400   KubeletHasNoDiskPressure     kubelet has no disk pressure
  PIDPressure          False   Fri, 05 Jul 2019 16:15:33 -0400   Fri, 05 Jul 2019 16:10:33 -0400   KubeletHasSufficientPID      kubelet has sufficient PID available
  Ready                True    Fri, 05 Jul 2019 16:15:33 -0400   Fri, 05 Jul 2019 16:11:03 -0400   KubeletReady                 kubelet is posting ready status. AppArmor enabled
Addresses:
  InternalIP:  10.94.214.213
  Hostname:    worker1.dell.com
Capacity:
 cpu:                2
 ephemeral-storage:  102685624Ki
 hugepages-1Gi:      0
 hugepages-2Mi:      0
 memory:             4040016Ki
 pods:               110
Allocatable:
 cpu:                2
 ephemeral-storage:  94635070922
 hugepages-1Gi:      0
 hugepages-2Mi:      0
 memory:             3937616Ki
 pods:               110
System Info:
 Machine ID:                 e7573bb6bf1e4cf5b9249413950f0a3d
 System UUID:                2FD93F42-FA94-0C27-83A3-A1F9276469CF
 Boot ID:                    782d6cfc-08a2-4586-82b6-7149389b1f4f
 Kernel Version:             4.15.0-29-generic
 OS Image:                   Ubuntu 18.04.1 LTS
 Operating System:           linux
 Architecture:               amd64
 Container Runtime Version:  docker://18.9.7
 Kubelet Version:            v1.15.0
 Kube-Proxy Version:         v1.15.0
Non-terminated Pods:         (4 in total)
  Namespace                  Name                         CPU Requests  CPU Limits  Memory Requests  Memory Limits  AGE
  ---------                  ----                         ------------  ----------  ---------------  -------------  ---
  default                    my-nginx-68459bd9bb-55wk7    0 (0%)        0 (0%)      0 (0%)           0 (0%)         4m8s
  default                    my-nginx-68459bd9bb-z5r45    0 (0%)        0 (0%)      0 (0%)           0 (0%)         4m8s
  kube-system                kube-proxy-jt4bs             0 (0%)        0 (0%)      0 (0%)           0 (0%)         5m51s
  kube-system                weave-net-kw9gg              20m (1%)      0 (0%)      0 (0%)           0 (0%)         5m51s
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests  Limits
  --------           --------  ------
  cpu                20m (1%)  0 (0%)
  memory             0 (0%)    0 (0%)
  ephemeral-storage  0 (0%)    0 (0%)
Events:
  Type    Reason                   Age                    From                          Message
  ----    ------                   ----                   ----                          -------
  Normal  Starting                 5m51s                  kubelet, worker1.dell.com     Starting kubelet.
  Normal  NodeHasSufficientMemory  5m51s (x2 over 5m51s)  kubelet, worker1.dell.com     Node worker1.dell.com status is now: NodeHasSufficientMemory
  Normal  NodeHasNoDiskPressure    5m51s (x2 over 5m51s)  kubelet, worker1.dell.com     Node worker1.dell.com status is now: NodeHasNoDiskPressure
  Normal  NodeHasSufficientPID     5m51s (x2 over 5m51s)  kubelet, worker1.dell.com     Node worker1.dell.com status is now: NodeHasSufficientPID
  Normal  NodeAllocatableEnforced  5m51s                  kubelet, worker1.dell.com     Updated Node Allocatable limit across pods
  Normal  Starting                 5m48s                  kube-proxy, worker1.dell.com  Starting kube-proxy.
  Normal  NodeReady                5m21s                  kubelet, worker1.dell.com     Node worker1.dell.com status is now: NodeReady
cse@kubemaster:~$
$ sudo kubectl run nginx --image nginx
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/nginx created
~$

Configuring Metal LoadBalancer

kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.7.3/manifests/metallb.yaml
~$ sudo kubectl get ns
NAME              STATUS   AGE
default           Active   23h
kube-node-lease   Active   23h
kube-public       Active   23h
kube-system       Active   23h
metallb-system    Active   13m
$ kubectl get all -n metallb-system
NAME                              READY   STATUS    RESTARTS   AGE
pod/controller-547d466688-m9xlt   1/1     Running   0          13m
pod/speaker-tb9d7                 1/1     Running   0          13m



NAME                     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset.apps/speaker   1         1         1       1            1           <none>          13m

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/controller   1/1     1            1           13m

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/controller-547d466688   1         1         1       13m

There are 2 components :

  • Controller – Assigns the IP address to the LB
  • Speaker – Ensure that you can reach service through LB

Controller component is deployed as deplyment and speaker as daemonset which is running on all worker nodes

Next, we need to look at config files.

To configure MetalLB, write a config map to metallb-system/config

Link: https://metallb.universe.tf/configuration/

Layer 2 mode is the simplest to configure: in many cases, you don’t need any protocol-specific configuration, only IP addresses.

 sudo kubectl get nodes -o wide
NAME               STATUS   ROLES    AGE   VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
kubemaster         Ready    master   23h   v1.15.0   10.94.214.210   <none>        Ubuntu 18.04.1 LTS   4.15.0-29-generic   docker://18.9.7
worker1.dell.com   Ready    <none>   23h   v1.15.0   10.94.214.213   <none>        Ubuntu 18.04.1 LTS   4.15.0-29-generic   docker://18.9.7

We need to pay attention to the above Internal IP. We need to use this range only.

$ sudo cat <<EOF | kubectl create -f -
> apiVersion: v1
> kind: ConfigMap
> metadata:
>   namespace: metallb-system
>   name: config
> data:
>   config: |
>     address-pools:
>     - name: default
>       protocol: layer2
>       addresses:
>       - 10.94.214.200-10.94.214.255
>
> EOF
configmap/config created
cse@kubemaster:~$ kubectl describe configmap config -n metallb-system
Name:         config
Namespace:    metallb-system
Labels:       <none>
Annotations:  <none>

Data
====
config:
----
address-pools:
- name: default
  protocol: layer2
  addresses:
  - 10.94.26.214-10.94.214.255

Events:  <none>
kubectl get all
$ kubectl expose deploy nginx --port 80 --type LoadBalancer
service/nginx exposed

Every 2.0s: kubectl get all             kubemaster: Sat Jul  6 15:33:30 2019

NAME                         READY   STATUS    RESTARTS   AGE
pod/nginx-7bb7cd8db5-rc8c4   1/1     Running   0          18m


NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)
        AGE
service/kubernetes   ClusterIP      10.96.0.1        <none>          443/TCP
        23h
service/nginx        LoadBalancer   10.105.157.210   10.94.214.200   80:3063
1/TCP   34s


NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx   1/1     1            1           18m

NAME                               DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-7bb7cd8db5   1         1         1       18m


By now, you should be able to browser NGINX Page underhttp://10.94.214.210

Hurray !!!

Let’s run another nginx service:

~$ kubectl run nginx2 --image nginx
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/nginx2 created
Every 2.0s: kubectl get all             kubemaster: Sat Jul  6 15:37:21 2019

NAME                          READY   STATUS    RESTARTS   AGE
pod/nginx-7bb7cd8db5-rc8c4    1/1     Running   0          21m
pod/nginx2-5746fc444c-4tsls   1/1     Running   0          42s


NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)
        AGE
service/kubernetes   ClusterIP      10.96.0.1        <none>          443/TCP
        23h
service/nginx        LoadBalancer   10.105.157.210   10.94.214.200   80:3063
1/TCP   4m24s


NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx    1/1     1            1           21m
deployment.apps/nginx2   1/1     1            1           42s

NAME                                DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-7bb7cd8db5    1         1         1       21m
replicaset.apps/nginx2-5746fc444c   1         1         1       42s
cse@kubemaster:~$ kubectl expose deploy  nginx2 --port 80 --type LoadBalancer
service/nginx2 exposed
cse@kubemaster:~$
Every 2.0s: kubectl get all             kubemaster: Sat Jul  6 15:38:49 2019

NAME                          READY   STATUS    RESTARTS   AGE
pod/nginx-7bb7cd8db5-rc8c4    1/1     Running   0          23m
pod/nginx2-5746fc444c-4tsls   1/1     Running   0          2m10s


NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)
        AGE
service/kubernetes   ClusterIP      10.96.0.1        <none>          443/TCP
        23h
service/nginx        LoadBalancer   10.105.157.210   10.94.214.200   80:3063
1/TCP   5m52s
service/nginx2       LoadBalancer   10.107.32.195    10.94.214.201   80:3139
0/TCP   15s


NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx    1/1     1            1           23m
deployment.apps/nginx2   1/1     1            1           2m10s

NAME                                DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-7bb7cd8db5    1         1         1       23m
replicaset.apps/nginx2-5746fc444c   1         1         1       2m10s

Let’s run hellowhale example

cse@kubemaster:~$ sudo kubectl run hellowhale --image ajeetraina/hellowhale
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/hellowhale created
cse@kubemaster:~$
cse@kubemaster:~$ sudo kubectl expose deploy hellowhale --port 89 --type LoadBalancer
service/hellowhale exposed
cse@kubemaster:~$
cse@kubemaster:~$ sudo kubectl get all
NAME                              READY   STATUS    RESTARTS   AGE
pod/hellowhale-64ff675cb5-c95qf   1/1     Running   0          99s
pod/nginx-7bb7cd8db5-rc8c4        1/1     Running   0          2d9h
pod/nginx2-5746fc444c-4tsls       1/1     Running   0          2d8h


NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)        AGE
service/hellowhale   LoadBalancer   10.100.239.246   10.94.214.203   89:30385/TCP   29s
service/kubernetes   ClusterIP      10.96.0.1        <none>          443/TCP        3d8h
service/nginx        LoadBalancer   10.105.157.210   10.94.214.200   80:30631/TCP   2d8h
service/nginx2       LoadBalancer   10.107.32.195    10.94.24.201   80:31390/TCP   2d8h


NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/hellowhale   1/1     1            1           99s
deployment.apps/nginx        1/1     1            1           2d9h
deployment.apps/nginx2       1/1     1            1           2d8h

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/hellowhale-64ff675cb5   1         1         1       99s
replicaset.apps/nginx-7bb7cd8db5        1         1         1       2d9h
replicaset.apps/nginx2-5746fc444c       1         1         1       2d8h

Hence, you saw that it’s so easy to deploy Kubernetes on bare metal using various popular operating systems like Ubuntu Linux, CentOS, SLES or Red Hat Enterprise Linux. Bare Metal Kubernetes deployments are no longer second-class deployments. Now, you, too, can use LoadBalancer resources with Kubernetes Metallb.

Do you have any queries around Docker, Kubernetes & Cloud? Here’s your chance to meet 850+ Community members via Slack https://tinyurl.com/y973wcq8

In case you’re new and want to start with Docker & Kubernetes, don’t miss out https://dockerlabs.collabnix.com

Top 5 Cool Projects around Docker, Raspberry Pi & Blinkt! ~ Monitoring Docker Swarm using LEDs – Part I

Estimated Reading Time: 5 minutes

Two week back, I travelled to Jaipur, around 1000+ miles from Bangalore for delivering one of Docker Session. I was invited as a Guest Speaker for “IIEC Connect” event conducted by LinuxWorld Inc. held in GD Badaya Auditorium, Jaipur which accommodated around 500-600+ engineering students.

It was an amazing experience with dozens of questions at the end of the session. The session lasted for 3 hours and I was just amazed when 90% hands got raised when I asked “How many of you know about Docker?”. I compiled 120+ slides for this session but skipped immediately to advanced talk so as to keep the audience intact. I talked about how industry is using Docker with some real time in-house projects like Pico, OpenUSM & Docker in the data centre.

During the end of the session, I showcased an interesting demonstration around monitoring Docker Swarm cluster using Blinkt! Pironomi LED. It was a great opportunity for me to excite the students showcasing such a cool project around Docker containers running on Raspberry Pi. Under this blog post, I will talk about how to achieve it in a detailed way.

Pre-requisite:

[rml_read_more]

ItemsLink Cost
Raspberry Pi 3 Model BBuy2849 INR
Raspberry Pi 3 Model B 4-layer Dog Bone
Stack Clear Case Box Enclosure
Buy4772 INR
Pimoroni Blinkt!Buy749 INR
Raspberry Pi 3 Heat Sink Set
Buy159 INR
  • A Raspberry Pi Node Cluster Stack
  • Blinkt

Blinkt! is an eight super-bright RGB LED indicators that are ideal for adding visual notifications to your Raspberry Pi. Inspired by OpenFaas founder ~ Alex Ellis’ work with his Raspberry Pi Zero Docker Cluster, Pironomi developed these boards for him to use as status indicators. Blinkt! offers eight APA102 pixels in the smallest (and cheapest) form factor to plug straight onto your Raspberry Pi.

Features

  • Eight APA102 RGB LEDs
  • Individually controllable pixels
  • Sits directly on top of your Pi in a tiny footprint
  • Fits inside most Pi cases
  • Doesn’t interfere with PWM audio
  • Blinkt! pinout
  • Compatible with Raspberry Pi 3B+, 3, 2, B+, A+, Zero, and Zero W
  • Python library
  • Comes fully assembled

Installing Docker on all 3 Nodes – 1 Manager and 2 worker nodes

Follow link to install Docker 18.09 on all the Raspberry Node cluster

root@raspberrypi:/home/pi# docker version
Client:
 Version:           18.09.0
 API version:       1.39
 Go version:        go1.10.4
 Git commit:        4d60db4
 Built:             Wed Nov  7 00:57:21 2018
 OS/Arch:           linux/arm
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.0
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.4
  Git commit:       4d60db4
  Built:            Wed Nov  7 00:17:57 2018
  OS/Arch:          linux/arm
  Experimental:     false
root@raspberrypi:/home/pi# 

Setting up Swarm Manager Node

root@raspberrypi:/home/pi# docker swarm init --advertise-addr 192.168.43.134 --listen-addr 192.168.43.134:2377
Swarm initialized: current node (j7i394an31gsevxt3fndzvum5) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-1zbsutds2u5gk5qwx0qbf95uccogrjx1ukszxxxxx-bcptng4inxxxldvvx17tn2l 192.168.43.134:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

root@raspberrypi:/home/pi# 
pi@raspberrypi:~ $ sudo docker swarm join --token SWMTKN-1-1zbsutds2u5gk5qwx0qbf95uccogrjx1ukszysmxxxbcptng4invy1abldvvx17tn2l 192.168.43.134:2377
This node joined a swarm as a worker.
pi@raspberrypi:~ $ 

Listing the Nodes

root@raspberrypi:/home/pi# docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
ijnqkk7vybzts7ohgt63fteoo     raspberrypi         Ready               Active                                  18.09.0
j7i394an31gsevxt3fndzvum5 *   raspberrypi         Ready               Active              Leader              18.09.0
let43cp6uoankngeg5lmd91mn     raspberrypi         Ready               Active                                  18.09.0
root@raspberrypi:/home/pi# 

Running Monitor Service

A special credit to Docker Captain Stefan Schere and his work for building these piece of Docker container.

root@raspberrypi:/home/pi# docker service create --name monitor --mode global --restart-condition any --mount type=bind,src=/sys,dst=/sys --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock stefanscherer/monitor:1.1.0
kvgvohexsc2e8yapol0ulwq5q
overall progress: 3 out of 3 tasks 
ijnqkk7vybzt: running   [==================================================>] 
let43cp6uoan: running   [==================================================>] 
j7i394an31gs: running   [==================================================>] 
verify: Service converged 
root@raspberrypi:/home/pi# 
root@raspberrypi:/home/pi# docker service create --name whoami stefanscherer/whoami:1.1.0
jd5e5hlswu8ruxgfhgbwtww84
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

Scaling the Service to 3

root@raspberrypi:/home/pi# docker service scale whoami=3
whoami scaled to 3
overall progress: 3 out of 3 tasks 
1/4: running   [==================================================>] 
2/4: running   [==================================================>] 
3/4: running   [==================================================>] 
verify: Service converged 

Scaling the service to 16

root@raspberrypi:/home/pi# docker service scale whoami=16
whoami scaled to 16
overall progress: 16 out of 16 tasks 
1/16: running   [==================================================>] 
2/16: running   [==================================================>] 
3/16: running   [==================================================>] 
4/16: running   [==================================================>] 
5/16: running   [==================================================>] 
6/16: running   [==================================================>] 
7/16: running   [==================================================>] 
8/16: running   [==================================================>] 
9/16: running   [==================================================>] 
10/16: running   [==================================================>] 
11/16: running   [==================================================>] 
12/16: running   [==================================================>] 
13/16: running   [==================================================>] 
14/16: running   [==================================================>] 
15/16: running   [==================================================>] 
16/16: running   [==================================================>] 
verify: Service converged 

Scaling the Service to 32

root@raspberrypi:/home/pi# docker service scale whoami=32
whoami scaled to 32
overall progress: 32 out of 32 tasks 
verify: Service converged 

Scaling the Service back to 4

root@raspberrypi:/home/pi# docker service scale whoami=4
whoami scaled to 4
overall progress: 4 out of 4 tasks 
1/4: running   [==================================================>] 
2/4: running   [==================================================>] 
3/4: running   [==================================================>] 
4/4: running   [==================================================>] 
verify: Service converged 




Listing out the service

root@raspberrypi:/home/pi# docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                         PORTS
h7ap83sidbw8        monitor             global              2/2                 stefanscherer/monitor:1.1.0   
root@raspberrypi:/home/pi# 

Listing the Nodes

root@raspberrypi:/home/pi# docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
ijnqkk7vybzts7ohgt63fteoo     raspberrypi         Ready               Active                                  18.09.0
j7i394an31gsevxt3fndzvum5 *   raspberrypi         Ready               Active              Leader              18.09.0
let43cp6uoankngeg5lmd91mn     raspberrypi         Down                Active                                  18.09.0
root@raspberrypi:/home/pi# 

Rolling Updates

root@raspberrypi:/home/pi# docker service update --image stefanscherer/whoami:1.2.0 \
>   --update-parallelism 4  --update-delay 2s whoami
whoami
overall progress: 2 out of 4 tasks 
1/4: preparing [=================================>                 ] 
2/4: running   [==================================================>] 
3/4: preparing [=================================>                 ] 
4/4: running   [==================================================>] 
root@raspberrypi:/home/pi# 

Overall, it was an amazing experience to showcase the power of Raspberry Pi to monitor Docker Swarm Cluster using Pironomi Blinkt! LEDs. In my future post, I will bring another interesting use cases around Docker & Raspberry Pi.

References:

Docker Enterprise 3.0: Now with New Built-in Docker cluster CLI Plugin

Estimated Reading Time: 8 minutes

Last Dockercon, dozens of new Docker CLI Plugin were introduced. All of these CLI plugins will be available in upcoming Docker Enterprise 3.0 GA release this year. Docker Desktop Enterprise 3.0 Public Beta was made available soon after Dockercon event during 2nd week of May 2019. This public beta consists of Desktop Enterprise 2.0.0.4-ent, Universal Control Plane 3.2, Docker Trusted Registry 2.7, and Engine Enterprise 19.03.0. Similar to previous deployments, Docker Enterprise components except Docker Engine are deployed as containers. Please note that only a limited subset of operating systems have been tested for the current beta release, including RHEL 7.6, and Ubuntu 16.04 and 18.04, and Windows Server 2019.

What is DCI all about?

One of the primary focus of this public beta is enhancement around expanding choices. Docker Certified Infrastructure(DCI) is Docker’s prescriptive approach to deploying Docker Enterprise Edition on a range of infrastructures. DCI is designed to automate and reliably deliver a secure, enterprise-ready container platform, integrated with your existing management and infrastructure tools.

Is DCI targeted only for Enterprise customers?

The short answer is “Yes”. DCI is installed in Docker Engine – Enterprise and Desktop Enterprise by default. DCI provides a declarative way to build and manage Docker clusters. It implements a Docker CLI plugin that exposes a `docker cluster` top-level command, and lets you define a cluster in a YAML file.

How does it work?

At a high-level, you define a cluster in a YAML file and instantiate it with `docker cluster create`. The DCI back-end then performs the hard work of building the cluster.

What Platform does it support?

DCI currently supports building and managing clusters on AWS during the Public beta with upcoming support Azure, and VMware vSphere by General Availability.

In my last blog, I talked about “What’s New in Docker Desktop Enterprise 3.0” which introduced a new way to build, share and run multi-service apps on any infrastructure with Docker Applications. Under this blog post, I will showcase how to get started with docker clusterCLI plugin

Pre-requisite:

[Captains-Bay]🚩 >  aws --version
aws-cli/1.11.107 Python/2.7.10 Darwin/17.7.0 botocore/1.5.70
[Captains-Bay]🚩 >  
  • AWS Access Keys

If you already have an `~/.aws/credentials` file, you can skip this step.  Use the `aws configure` command to specify your AWS credentials.

You will require a Docker ID with access to a Docker UCP subscription either:

  • Docker Enterprise 3.0 Beta License for Docker Enterprise 3.0 Beta
  • An active Docker Enterprise license (paid or trial) to install generally available Docker Enterprise version

Also, An AWS account with security credentials you will need AWS credentials with the following IAM policies:

  • AmazonEC2FullAccess
  • AmazonElasticFileSystemFullAccess
  • AmazonRoute53DomainsFullAccess
  • AmazonS3FullAccess
  • IAMFullAccess (for creating instance profiles with roles and policies)

  • Under Docker Beta registration page, sign in with your DockerID
  • Once you complete your registration, you will see the links for Docker Desktop Enterprise for Mac and Windows. Download your preferred software based on your desktop OS.

Installing Docker Desktop Enterprise

[rml_read_more]

You can directly download Desktop Enterprise for Mac too with the below link:

https://download.docker.com/mac/enterprise/Docker.pkg

To install double click the .pkg file. For Mac administrators, the following command line options support fine tuning and mass installation, after which Docker Desktop Enterprise can be run from the Applications folder on each individual machine.

sudo installer -pkg Docker.pkg -target /

The license file must then be either installed in the following location:

~/Library/Group Containers/group.com.docker/docker_subscription.lic


Or can be provided in the UI when starting the application for the first time (/Applications/Docker.app).

Click on “whale” icon which appear at the right top of the screen to verify if Docker Desktop Enterprise comes up well.

[Captains-Bay]🚩 >  docker version
Client: Docker Engine - Enterprise
 Version:           19.03.0-beta4
 API version:       1.40
 Go version:        go1.12.5
 Git commit:        d9934ea
 Built:             Tue May 14 06:40:00 2019
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Enterprise
 Engine:
  Version:          19.03.0-beta4
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.5
  Git commit:       d9934ea
  Built:            Tue May 14 06:46:25 2019
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          v1.2.6
  GitCommit:        894b81a4b802e4eb2a91d1ce216b8817763c29fb
 runc:
  Version:          1.0.0-rc8
  GitCommit:        425e105d5a03fabd737a126ad93d62a9eeede87f
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

Login to Docker Hub

Login to Docker Hub with a Docker ID that has access to a Docker EE/UCP repository.

[Captains-Bay]🚩 >  docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: ajeetraina
Password: 
Login Succeeded

Testing the inbuilt docker cluster CLI Plugin

[Captains-Bay]🚩 >  docker cluster version
Version:  v0.3.0
Commit:   dc3d07a
Build:    Plugin

Cluster Declaration

It’s time to declare our cluster. We’ll use the following YAML file to deploy a new cluster to AWS. By default, `docker cluster create` will look for a cluster.yml file in the current working directory.  Alternatively, you can give the file any name you choose. Let’s create a cluster.yml file with the following contents of a simple cluster definition.  The below YAML will allow you to install Docker Enterprise 3.0 beta on 1 manager and 1 DTR node.

variable:
  region: us-east-1
  subscription_url: https://storebits.docker.com/ee/m/sub-zxxxx/  ## Don't forget to add / at the end as shown 
  ucp_password:
    type: "prompt"

provider:
  aws:
    region: ${region}

cluster:
  engine:
    url: ${subscription_url}
    version: "ee-test-19.03"
  ucp:
    version: "docker/ucp:3.2.0-beta4"
    username: "admin"
    password: ${ucp_password}
  dtr:
    version: "docker/dtr:2.7.0-beta4"
 
resource:
  aws_instance:
    managers:
      quantity: 1
    registry:
      quantity: 1

Let us go through each of the below section one by one –

The YAML has four top-level resources:

– variable- provider
– cluster
– resource

The `variable` section declares variables that will be used in the cluster declaration.  The ucp_password uses type “prompt” to indicate that `docker cluster` will request a value at cluster creation.

The `provider` section declares that this cluster will be deployed in AWS, and references the region parameter.

The `cluster` section defines the Docker Engine and UCP versions to deploy. It also specifies the UCP admin credentials to apply to the cluster.

The `resource` section requests a single AWS instance to be configured as a UCP manager.

Spinning up Docker Enterprise 3.0 on AWS Platform

[Captains-Bay]🚩 >  docker cluster create -f cluster.yml --log-level debug
Please provide a value for ucp_password
DEBU[0009] Image Ref: sha256:ea8a7a832f839d48f478e37602cb7f67207be6f612c3a00aeafa42ca9f155214 
DEBU[0009] Generating public/private rsa key pair.      
DEBU[0010] Your identification has been saved in /data/keys/ssh/id_rsa. 
DEBU[0010] Your public key has been saved in /data/keys/ssh/id_rsa.pub. 
DEBU[0010] The key fingerprint is:                      
DEBU[0010] SHA256:CnQ4M5/f+2AOXj+azUVReBXXXXX cluster@a1f8091cbb6a 
DEBU[0010] The key's randomart image is:                
DEBU[0010] +---[RSA 2048]----+                          
DEBU[0010] |       ..      +o|                          
DEBU[0010] |     .  ..    o.+|                          
DEBU[0010] |    * .o.     .o.|                          
DEBU[0010] |   . *+.oo    o. |                          
DEBU[0010] |    ..o=S    ... |                          
DEBU[0010] |     .oo o  o.   |                          
DEBU[0010] |      ..+.Oo  .  |                          
DEBU[0010] |      o+.E.B..   |                          
DEBU[0010] |     o+oo =o=.   |                          
DEBU[0010] +----[SHA256]-----+                          
DEBU[0010] Planning cluster on aws       

Sit back & Relax ! This is going to take couple of minutes to bring up your Docker Enterprise 3.0

Troubleshooting Tips:

In case you encounter issue around unable to pull dockereng/cluster:v0.3.0

there is a quick workaround. Reason – The dockereng/cluster:v0.3.0 is a private Docker image which would fail to get pulled from Dockerhub. You might need to follow the below steps:

[Captains-Bay]🚩 >  docker pull docker/cluster:v0.3.0
v0.3.0: Pulling from docker/cluster
bdf0201b3a05: Pull complete 
227965e0be77: Pull complete 
656c27da0276: Downloading  10.18MB/98.87MB
6bc49ae6e7fa: Download complete 
ddbd7883b3bf: Download complete 
90dd03face76: Download complete 
cb5cae322035: Download complete 
c0c9485136e8: Download complete 
a5ab55def61b: Download complete 
ddbd7b624dc0: Download complete

Now you need to tag it to dickering/cluster:v0.3.0so as to let CLI plugin consider it locally and pick it up for building the cluster.

docker tag docker/cluster:v0.3.0 dockereng/cluster:v0.3.0

Please note that this issue has been fixed under cluster CLI version 0.3.3. By now, you should be able to see the below window while accessing it over the browser.


Once you upload License, you should be able to access Docker Enterprise 3.0 UI as shown below:

Inspecting the cluster

You can use docker cluster ls to list out the cluster. Even you can inspect the cluster using the below command:

[Captains-Bay]🚩 >  docker cluster inspect fervent_taussig
name: fervent_taussig
shortid: 67fb8cb05043
variable:
  region: us-east-1
  subscription_url: https://storebits.docker.com/ee/m/sub-a3dd83ed-d9db-440f-a175-e11347fb1037/
  ucp_password: Oracle9ias
provider:
  aws:
    region: us-east-1
    tags:
      pet: "true"
      project: CSG-DCI
    version: ~> 1.0
cluster:
  dtr:
    version: docker/dtr:2.7.0-beta4
  engine:
    storage_volume: /dev/xvdb
    url: https://storebits.docker.com/ee/m/sub-a3dd83ed-d9db-440f-a175-e11347fb1037/
    version: ee-test-19.03
  registry:
    url: https://index.docker.io/v1/
    username: ajeetraina
  ucp:
    username: admin
    version: docker/ucp:3.2.0-beta4
resource:
  aws_instance:
    managers:
      _running:
        managers_ids:
        - i-088036137bdf5564a
        managers_ips:
        - 35.170.33.58
      instance_type: t2.xlarge
      os: Ubuntu 16.04
      quantity: 1
      role: manager
    registry:
      _running:
        registry_ids:
        - i-016770ea989a55a0a
        registry_ips:
        - 18.208.208.51
      instance_type: t2.xlarge
      os: Ubuntu 16.04
      quantity: 1
      role: dtr

Using context switching to switch from Docker Desktop to remote AWS cluster

[Captains-Bay]🚩 >  docker context ls
NAME                DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT                ORCHESTRATOR
default *           Current DOCKER_HOST based configuration   unix:///var/run/docker.sock   https://localhost:6443 (default)   swarm
fervent_taussig     fervent_taussig                           tcp://35.170.33.58:443                                           
[Captains-Bay]🚩 >  docker context use fervent_taussig
fervent_taussig
Current context is now "fervent_taussig"
[Captains-Bay]🚩 >  docker node ls
ID                            HOSTNAME                       STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
fbe5k12hyoit5qcdtatamz907 *   ip-172-31-9-19.ec2.internal    Ready               Active              Leader              19.03.0-beta4
hs8jz9vnuwqjjjukzh9s2rejc     ip-172-31-10-73.ec2.internal   Ready               Active                                  19.03.0-beta4
[Captains-Bay]🚩 >

As you can see, it shows up UCP nodes cluster running on remote AWS Cloud Platform.

Open up the browser and you shall be able to access Docker Enterprise v3.2.0-beta4 Release.

In my next blog post, I will talk around docker registry as well as docker gmsa CLI Plugin. Stay tuned !