In Part 1 of this blog series I investigated the varying cloud providers and associated costs involved with building a cloud hosted Kubernetes cluster. In this post I will demonstrating how to build the cluster using Hetzner Cloud.
The steps performed here have been culminated from various articles and provide a complete example of setting up a cluster for Hetzner’s Cloud Platform. I have yet to find a complete example of how to get a Kubernetes cluster fully setup and working on the platform. Links to the source articles can be found in the resources section at the end of this post.
Create Resources
Throughout this tutorial I will be using the following setup for the cluster. This provides a reasonable sized cluster with a single master node and a single worker node. It is entirely up to you as to how many worker nodes you add into your cluster.
Component | Hetzner Size Code | Details | Price (Inc VAT) | Quantity Required | Total |
Master Node | CX21 | 4GB RAM, 2 vCPU, 40GB Local Storage | €5.88 | 1 | €5.88 |
Worker Node | CX41 | 16GB RAM, 4 vCPU, 160GB Local Storage | €19.08 | 1 | €19.08 |
Once you have setup an account on Hetzner and signed in, the first item to create is a project. This will act as a container for all of our resources. I have very simply called my project “K8S Cluster Test”.
You will now need to create a network that the servers will belong to. I have created mine using the following settings:
Next we can add the servers into the project. I have used the following settings when creating the servers:
- Image – Ubuntu 20.04
- Network – Select the network created above
- Additional Features / Placement Group – I created a new placement group for the servers. This is an optional step but one I would recommend. Servers within a placement group will be located on different physical hosts to one another, providing a more resilient architecture.
- SSH Key – I added my SSH key at the point of creating the first server. This will allow me to SSH into the server once it is created. Once your key has been added the first time, you can simply select it for any additional servers.
- Name – Give each server a unique name. I opted for setup-example-master-01 and setup-example-worker-01.
Prepare Servers
Once your servers have been created and are ready, connect via SSH and run the following commands. These commands need to be run on all servers.
Install required packages
apt update
apt install -y apt-transport-https
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt update
apt install -y kubelet kubeadm kubectl containerd
Configure ContainerD
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
sudo systemctl restart containerd
Disable Swap
swapoff -a
Add a User Account
adduser --gecos "" --disabled-password k8s
chpasswd <<<"k8s:YourPassword"
adduser k8s sudo
Prepare the Cloud Controller Manager
This step is important is it will ensure the cluster is installed with the correct arguments for Hetzner’s Cloud Controller Manager module.
touch /etc/systemd/system/kubelet.service.d/20-hetzner-cloud.conf
cat <<EOF | sudo tee /etc/systemd/system/kubelet.service.d/20-hetzner-cloud.conf
[Service]
Environment="KUBELET_EXTRA_ARGS=--cloud-provider=external"
EOF
Setup Master Node
The following steps must be run on ONLY the master node. It is advisable not to run the installation as the root user. Use the k8s
user account that we created previously.
Install Kubernetes
kubeadm config images pull
kubeadm init --pod-network-cidr=10.244.0.0/16 --cri-socket /run/containerd/containerd.sock --upload-certs --apiserver-cert-extra-sans 10.0.0.1
mkdir -p /root/.kube
cp -i /etc/kubernetes/admin.conf /root/.kube/config
It is important to ensure your pod network does not clash with your server network. I have highlighted this setting in bold above. If you recall, our server network was defined as 10.0.0.0/24 in an earlier step.
The above steps will take between 5 and 10 minutes to complete. If you have followed all steps up until now exactly as they are written, the installation should be without any error. Once the installation is complete a command for joining nodes to the cluster will have been output. Take a copy of this statement to be used later.
Setup Hetzner Cloud Controller Manager
To enable the cluster to create resources on the Hetzner Cloud platform we must install their cloud controller manager. There are various ways to deploy this component, I have opted for installing with networks support. You can read about the different installation options on the github page linked in the resources section below. The following steps need to be performed:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: hcloud
namespace: kube-system
stringData:
token: "$hetznerApiToken"
network: "$hetznerNetworkId"
---
apiVersion: v1
kind: Secret
metadata:
name: hcloud-csi
namespace: kube-system
stringData:
token: "$hetznerApiToken"
EOF
kubectl apply -f https://raw.githubusercontent.com/hetznercloud/hcloud-cloud-controller-manager/master/deploy/ccm-networks.yaml
Indicated in bold are two values that you must either set in environment variables or replace in the code. The Hetzner API Token can be created within the security section of the Hetzner Cloud portal. For the network ID, you can simply pass the name you gave to the network in an earlier step. My example was “K8S-Example-Network”.
Install Cluster Networking
Cluster networking needs to be setup. This is a broad topic and there are many different providers available such as Flannel, Calico, Weave, just to mention a few. You can read about the different providers in the Kubernetes documentation, a link is provided below. I have opted for Flannel as it is very simple to setup and use.
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
Once flannel is installed, the flannel deployment needs to be patched to tolerate the uninitialized
taint:
kubectl -n kube-system patch ds kube-flannel-ds --type json -p '[{"op":"add","path":"/spec/template/spec/tolerations/-","value":{"key":"node.cloudprovider.kubernetes.io/uninitialized","value":"true","effect":"NoSchedule"}}]'
kubectl -n kube-system patch deployment coredns --type json -p '[{"op":"add","path":"/spec/template/spec/tolerations/-","value":{"key":"node.cloudprovider.kubernetes.io/uninitialized","value":"true","effect":"NoSchedule"}}]'
At this point you should have the basis of your cluster created. Running the command kubectl get po -A
should list out the running pods on the cluster. All pods should be running with no errors:
Join Worker Nodes to Cluster
Using the join command that you noted previously, SSH onto each worker node and run the command. This will join the nodes to the cluster. Once you have done this, you can run the command kubectl get nodes -o wide
to validate that the nodes are joined successfully:
You now have a fully functional Kubernetes cluster running on the Hetzner Cloud platform. In Part 3 of this series I will be demonstrating the following features:
- LinkerD Service Mesh – This provides TLS communication and traffic management within the pod network.
- Cert-Manager – We can use Cert-Manager in conjunction with Hetzner Cloud to generate free HTTPS certificates for our public facing components.
- Kong Ingress Gateway – We need a way to ingress traffic into our cluster. I will be using Kong which will handle the ingress and work together with the Hetzner Cloud Load Balancer.
Resources
HCloud Controller Manager – Deployment with Networks Support
Deploy Kubernetes Cluster with ContainerD