I've just seen a talk on YouTube where running self managed Kubernetes cluster was described as ninja technique. I strongly disagree with this and actually want to disapprove it in this article. Running self managed K8S can also be inexpensive if you use a provider such as Hetzner Cloud.

As a prerequisite to complete the following step, you need to install Go first. Now let's install hcloud command line utility:

export PATH=~/go/bin:/usr/local/go/bin:$PATH
go get -u github.com/hetznercloud/cli/cmd/hcloud

In order to be able to connect to your Hetzner Cloud account using hcloud command, you will need to get your API key on their web console and export it:

export HCLOUD_TOKEN=your_key

We are ready to do the real stuff now, so let's configure the SSH key to be used and create the VM instance:

hcloud ssh-key create --name your_ssh_key_name --public-key-from-file ~/.ssh/id_rsa.pub
hcloud server create --name server_name --image ubuntu-16.04 --location nbg1 --ssh-key your_ssh_key_name --type cx41

You should see the public IP of the freshly created instance in the output or just check it out at the web console. You may want to configure DNS here, but this is out of the scope of this article.  It is always a good idea to fully update your newly created Ubuntu box:

ssh root@your_ip_address
apt-get update
apt-get upgrade
apt-get dist-upgrade
reboot

Now let's install CRI (Container Runtime Interface). We will use Docker:

apt-get install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository \
  "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) \
  stable"
apt-get update
apt-get install docker-ce
cat > /etc/docker/daemon.json <<EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF
mkdir -p /etc/systemd/system/docker.service.d
systemctl daemon-reload
systemctl restart docker

Kubernetes requires some capabilities which are not enabled by default on Ubuntu 16.04, so edit the /etc/ufw/sysctl.conf file and add this snippet at the bottom:

# Kubernetes & Flannel
net/bridge/bridge-nf-call-ip6tables=1
net/bridge/bridge-nf-call-iptables=1
net/bridge/bridge-nf-call-arptables=1

Reboot your VM and finally install the latest stable version of Kubernetes:

curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add
apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
apt-get update
apt-get install -y kubelet kubeadm kubectl
kubeadm init --pod-network-cidr=10.244.0.0/16

We can now continue configuring K8S using kubectl command, but we need to configure kubectl beforehand:

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

Kubernetes also needs some CNI (Container Networking Interface). We will use Flannel:

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

At this point you may want to create more nodes and join to the cluster, but we will continue with the single node "cluster". I may write another article on this topic, but let's keep it simple this time. In order to run your applications (pods) on master node, we need to taint it:

kubectl taint nodes --all node-role.kubernetes.io/master-

And do some security (firewall) stuff:

ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 6443/tcp
ufw allow 8285/udp # Flannel
ufw allow 8472/udp # Flannel
ufw enable

We are now ready to run our applications, but if you want to use automatic storage provisioning on Hetzner Cloud, you will need to install CSI (Container Storage Interface). Let's first create secret with Hetzner Cloud API token:

apiVersion: v1
kind: Secret
metadata:
  name: hcloud-csi
  namespace: kube-system
stringData:
  token: PUT_YOUR_TOKEN_HERE

Save this to secret-hcloud-csi.yaml and run the following commands to install CSI:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/csi-api/release-1.14/pkg/crd/manifests/csidriver.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/csi-api/release-1.14/pkg/crd/manifests/csinodeinfo.yaml
kubectl apply -f secret-hcloud-csi.yaml
kubectl apply -f https://raw.githubusercontent.com/hetznercloud/csi-driver/master/deploy/kubernetes/hcloud-csi.yml

And voila, that's all. We are finished setting up the latest Kubernetes version on Ubuntu 16.04 which is still the latest officially supported version. If you want to use Ubuntu 18.04, you can still use these instructions except you may need to modify or skip the part about configuring /etc/ufw/sysctl.conf. Have fun with K8S.