In this guide, I will cover the installation of a single-node Kubernetes cluster on an Ubuntu host through kubeadm. To keep it as straight-forward as possible, I chose the tools that are the most popular and easy to use. As such, the stack will consist of Ubuntu, Docker, vanilla Kubernetes and Weave Net. At the end of this guide, you will have a basic but ready-to-use Kubernetes cluster.
If you're wondering why I chose to install Kubernetes "the hard way", this article provides an overview and comparison of the different options. In essence, I chose kubeadm so that I can have a Kubernetes cluster that comes as close to a real production environment as possible, where I have every feature at my disposal.
As the first step, we need to install a container runtime for Kubernetes to use. Docker is the most popular choice, for which you can find the installation commands below. Note that we only install the Docker Engine here, not Docker Desktop.
sudo apt-get install ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo \ "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
After executing these commands, you should have Docker up and running. You can verify this by issuing the sudo docker run hello-world command. With the current configuration, you always have to be root to use the docker command. Add your user to the appropriate group by running the command below to use docker without elevated privileges.
sudo usermod -aG docker $USER
The kubelet is the primary agent that runs on every node of the cluster. It uses the configured Container Runtime Interface (CRI) to start Pods and containers. The Docker Engine is such a runtime, but the kubelet cannot use it out of the box because it does not conform to the CRI. In order to close that gap, we need to make the Docker Engine CRI-conformant by installing the cri-dockerd adapter. The easiest way to do this, is by downloading the latest release (Debian package) and installing it manually with the commands below.
curl -L https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.7/cri-dockerd_0.3.7.3-0.ubuntu-jammy_amd64.deb -o cri-dockerd_0.3.7.3-0.ubuntu-jammy_amd64.deb sudo dpkg -i cri-dockerd_0.3.7.3-0.ubuntu-jammy_amd64.deb
One this is done, we can install the binaries required for the Kubernetes installation.
sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl gpg curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl
At last, it's time to install the actual cluster, which we can do in a single command. Note that I gave my Ubuntu host a static IP address, which I pass to the kubeadm command as part of the apiserver-advertise-address argument. If you don't do this, any future IP address changes will require new certificates and configuration. We also explicitly point kubeadm to the Docker Engine CRI adapter.
sudo kubeadm init --apiserver-advertise-address=192.168.149.100 --cri-socket=/var/run/cri-dockerd.sock
Give kubeadm a minute to perform all of its actions. Then, run the commands it suggests.
mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config
All Kubernetes core services should now be operational, except for CoreDNS, which is waiting for a network addon. You can verify this with the kubectl get pods --all-namespaces command.
NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-5dd5756b68-8knbn 0/1 Pending 0 42s kube-system coredns-5dd5756b68-vwtsp 0/1 Pending 0 42s kube-system etcd-jelle-virtualbox 1/1 Running 4 47s kube-system kube-apiserver-jelle-virtualbox 1/1 Running 4 47s kube-system kube-controller-manager-jelle-virtualbox 1/1 Running 28 47s kube-system kube-proxy-l4xg7 1/1 Running 0 42s kube-system kube-scheduler-jelle-virtualbox 1/1 Running 28 47s
The final step is installing a network addon that will make the cluster fully operational. There's a plethora of options available, but I chose Weave Net because of its simplicity. Installing it only requires one kubectl apply command.
kubectl apply -f https://github.com/weaveworks/weave/releases/download/v2.8.1/weave-daemonset-k8s.yaml
serviceaccount/weave-net created clusterrole.rbac.authorization.k8s.io/weave-net created clusterrolebinding.rbac.authorization.k8s.io/weave-net created role.rbac.authorization.k8s.io/weave-net created rolebinding.rbac.authorization.k8s.io/weave-net created daemonset.apps/weave-net created
After waiting for a couple of minutes, you should see that all Pods are in the "Running" status, which indicates that your cluster is ready to be used.
kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-5dd5756b68-8knbn 1/1 Running 0 2m37s kube-system coredns-5dd5756b68-vwtsp 1/1 Running 0 2m37s kube-system etcd-jelle-virtualbox 1/1 Running 4 2m42s kube-system kube-apiserver-jelle-virtualbox 1/1 Running 4 2m42s kube-system kube-controller-manager-jelle-virtualbox 1/1 Running 28 2m42s kube-system kube-proxy-l4xg7 1/1 Running 0 2m37s kube-system kube-scheduler-jelle-virtualbox 1/1 Running 28 2m42s kube-system weave-net-4sz5m 2/2 Running 0 28s