Ethernet Research

Actionable information about cloud technologies

Kubernetes Tutorial - How to Install Kubernetes on Ubuntu Baremetal

The primary goal of this tutorial is teach how to install Kubernetes on Ubuntu and configure a Kubernetes Cluster using a Master and Worker node.

[This is the the first lab in the Kubernetes Tutorial.]

The Ubuntu server can be a baremetal Ubuntu server or an Ubuntu VM.  The steps involved are exactly the same.

Here is the agenda for in this lab.

  1. Bring up two Ubuntu 16.4 VMs.
  2. Install and bring up Kubernetes cluster master node,
  3. Install and bring up Kubernetes cluster worker node,
  4. Install and bring up Calico CNI.
  5. Download and run NGINX container.
  6. Verify NGINX pod runs in the cluster.

Google Cloud Platform(GCP):

I signed up for Google Cloud Platform as it provides a $300 worth of cloud usage for 1 year free.  I you prefer you can also sign up and run this tutorial in GCP.

Alternatively, you could run the VMs on your laptop.

I spawned two Ubuntu 16.4 VM’s.

[Please ensure that you use Ubuntu 16.4 for this tutorial.  I tried on 18.4 but I faced several challenges and open bugs in Kubernetes.]{style=“text-decoration: underline; color: #ff0000;"}

[Please ensure that each VM has at least 2 Cores and 4GB RAM.]{style=“text-decoration: underline; color: #ff0000;"}

Installing Kubernetes on Ubuntu:

The installation procedure is common for both master and worker nodes in a Kubernetes cluster.  Follow the procedure below to install Kubernetes in both the Ubuntu VM’s you have spawned.

Welcome to Ubuntu 16.04.4 LTS (GNU/Linux 4.13.0-1019-gcp x86_64)

* Documentation:
 * Management:
 * Support:

Get cloud support with Ubuntu Advantage Cloud Guest:

0 packages can be updated.
0 updates are security updates.

Last login: Sun Jul 8 04:42:10 2018 from
master:~$ ifconfig
ens4 Link encap:Ethernet HWaddr 42:01:0a:a0:00:02 
 inet addr: Bcast: Mask:
 inet6 addr: fe80::4001:aff:fea0:2/64 Scope:Link
 RX packets:547 errors:0 dropped:0 overruns:0 frame:0
 TX packets:499 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:1000 
 RX bytes:413630 (413.6 KB) TX bytes:61585 (61.5 KB)

lo Link encap:Local Loopback 
 inet addr: Mask:
 inet6 addr: ::1/128 Scope:Host
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:1000 
 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

Check if the worker node can be pinged from the master node.  This is key to forming the Kubernetes cluster.

master:~$ ping
PING ( 56(84) bytes of data.
64 bytes from icmp_seq=1 ttl=64 time=1.48 ms
64 bytes from icmp_seq=2 ttl=64 time=0.273 ms
--- ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.273/0.877/1.481/0.604 ms

Docker Installation:

We need to install Docker daemon if we need to use Docker images to run Docker containers.  Remember from previous tutorials that Kubernetes is just an orchestration platform.  It manages Docker and LXC containers.

master:~$ sudo apt-get update   

&& sudo apt-get install -qy

Setup Kubernetes apt repository:

First we need to download the Kubernetes apt packages from the Kubernetes web site to a local repository.

master:~$ sudo apt-get update   

&& sudo apt-get install -y apt-transport-https
&& curl -s | sudo apt-key add -

master-1:~$ echo "deb kubernetes-xenial main"   

| sudo tee -a /etc/apt/sources.list.d/kubernetes.list
&& sudo apt-get update

Install Kubernetes

Next install the required Kubernetes packages from the local repo.

master:~$ sudo apt-get update   

&& sudo apt-get install -y

This completes the installation of all the softwares required to setup Kubernetes Cluster and run Docker containers in Kubernetes.

Simple Installation Script

For ease of use, I have captured all the commands used so far in a shell script file named  All you need to do is simply download the shell script and execute “sh” as a non-root user.  It will install all the software for you in one shot, without requiring any user intervention.

Please make sure that the Linux swap is turn off.  Kubernetes requires that all pods/containers always stays in memory and not swapped to the disk.

master:~$ sudo apt install mount   #install swapoff in mount pkg
master:~$ sudo swapoff --all
master:~$ cat /proc/swaps
Filename Type Size Used Priority

I hope you followed all the above set of steps to install Docker and Kubernetes on the worker node also.  If not, please complete the Kubernetes installation in the worker node before we move on the the next steps.

Inialize the master node:

From now on, we execute and manage the clusters and pods from the master node only.  Worker nodes don’t can’t be used for cluster and pod management.

Initialalize the master node with the following command.  The default subnet for Calico CNI is and Flannel CNI is  We’ll be using Calico CNI plugin for this example.  Hence we’ll set “—pod-network-cidr” to the Calico subnet.

Set the “—apiserver-advertise-address” with the ip address of the master node.

master:~$ sudo kubeadm init —pod-network-cidr= —apiserver-advertise-address=


Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

mkdir -p $HOME/.kube
 sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
 sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:

You can now join any number of machines by running the following on each node
as root:

kubeadm join --token dg6zhb.naxuxb5wd78tdsqz --discovery-token-ca-cert-hash sha256:af9e87508dc6c2c52b6f51dd88635f2dc9f061dce27368f3ac9913b52522cac3

Setup Container Networking Interface(CNI)

As the init logs above says, we need to install and setup the CNI. We’ll use the Calico CNI plugin in this example.  We should provide the calico.yaml file from the calico’s project website.

master:~$ kubectl apply -f

Usually Pods or Containers are not run in the master node. because master nodes are used for controlling purposes and not for executing the jobs.  But, there is an option to override this to run a single node cluster with just the master node.  The pods will run in the master node itself.   Here is the command to make master node to run the Pods.

$ kubectl taint nodes --all

However, if you want to build a real world cluster, then skip the above command and go add the worker nodes as explained below.

Configure the Worker:

We need to use the token generated by the master during “kubeadm init” to join from the worker node.

In case you lost the token generated by the master node, execute the below command in the master node to make it print again.

master $ kubeadm token create --print-join-command
kubeadm join --token wdyfoz.k1wr3dcdc7l9bd2n --discovery-token-ca-cert-hash s

Now join the worker node to the cluster.

worker:~$ sudo kubeadm join --token dg6zhb.naxuxb5wd78tdsqz --discovery-token-ca-cert-hash sha256:af9e87508dc6c2c52b6f51dd88635f2dc9f061dce27368f3ac9913b52522cac3
[preflight] running pre-flight checks
I0708 05:23:27.564762 18132 kernel_validator.go:81] Validating kernel version
I0708 05:23:27.564859 18132 kernel_validator.go:96] Validating kernel config
[discovery] Trying to connect to API Server ""
[discovery] Created cluster-info discovery client, requesting info from ""
[discovery] Requesting info from "" again to validate TLS against the pinned public key
[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server ""
[discovery] Successfully established connection with API Server ""
[kubelet] Downloading configuration for the kubelet from the "kubelet-config-1.11" ConfigMap in the kube-system namespace
[kubelet] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[preflight] Activating the kubelet service
[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap...
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "instance-2" as an annotation

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

Run 'kubectl get nodes' on the master to see this node join the cluster.

Sometime when you run the above command, it may complain that the “ip_vs” kernel module is not loaded.  To solve this issue, you may need to do download and the missing kernel modules.

worker:~$ sudo apt install ip_vs
worker:~$ modprobe ip_vs

Also, as mentioned earlier, turn off the Linux swap service before running the above join command, using “swapoff -a” command.

Verify if the cluster nodes are up and running:

Use the “kubectl get nodes” command to check if worker node was able to talk to the master node and join the cluster.  Verify if both master and worker are ready to run pods.

master:~$ kubectl get nodes
instance-1 Ready master 14m v1.11.0
instance-2 NotReady  47s v1.11.0
master:~$ kubectl get nodes
instance-1 Ready master 15m v1.11.0
instance-2 Ready  1m v1.11.0

That’s all folks.  We are done with installing Kubernetes on a bare metal server.

Spin an NGINX pod in the cluster

We should use the master node to execute any cluster/pod management commands.

master:~$ kubectl run nginx --image=nginx --port=80
deployment.apps/nginx created

Verify that the pod is up and running.

master:~$ kubectl get pods
nginx-6f858d4d45-b7nk5 0/1 ContainerCreating 0 7s
master:~$ kubectl get pods
nginx-6f858d4d45-b7nk5 1/1 Running 0 31s

To get more details about the pod, use the ‘kubectl describe’ command.

master:~$ kubectl describe pods
Name: nginx-6f858d4d45-b7nk5
Namespace: default
Node: instance-2/
Start Time: Sun, 08 Jul 2018 05:25:21 +0000
Labels: pod-template-hash=2941480801

Status: Running
Controlled By: ReplicaSet/nginx-6f858d4d45
 Container ID: docker://90431cb74d4f3e3abf77f3344277ba5679137cfad54a473f56d381d1ad0f3a6d
 Image: nginx
 Image ID: docker-pullable://nginx@sha256:a65beb8c90a08b22a9ff6a219c2f363e16c477b6d610da28fe9cba37c2c3a2ac
 Port: 80/TCP
 Host Port: 0/TCP
 State: Running
 Started: Sun, 08 Jul 2018 05:25:35 +0000
 Ready: True
 Restart Count: 0
 /var/run/secrets/ from default-token-n8l4x (ro)
 Type Status
 Initialized True 
 Ready True 
 ContainersReady True 
 PodScheduled True 
 Type: Secret (a volume populated by a Secret)
 SecretName: default-token-n8l4x
 Optional: false
QoS Class: BestEffort
Tolerations: for 300s for 300s
 Type Reason Age From Message
 ---- ------ ---- ---- -------
 Normal Scheduled 38s default-scheduler Successfully assigned default/nginx-6f858d4d45-b7nk5 to instance-2
 Normal Pulling 37s kubelet, instance-2 pulling image "nginx"
 Normal Pulled 25s kubelet, instance-2 Successfully pulled image "nginx"
 Normal Created 24s kubelet, instance-2 Created container
 Normal Started 24s kubelet, instance-2 Started container