Skip to main content
This guide demonstrates how to bootstrap a single-control-plane Kubernetes cluster with kubeadm. The example environment uses three Vagrant virtual machines: We use the enp0s8 interface for inter-node cluster traffic. Example condensed outputs for the enp0s8 interface (shown here for reference):
Before you begin, consult the official kubeadm documentation and choose a Kubernetes version (the examples below use v1.31):
A screenshot of the Kubernetes documentation page for "Installing kubeadm," showing the left navigation menu and the main content with prerequisites and version links. The top banner advertises KubeCon + CloudNativeCon 2024.
A screenshot of the Kubernetes documentation webpage titled "Creating a cluster with kubeadm," showing navigation links on the left and detailed instructions in the main content area. A blue header promotes KubeCon + CloudNativeCon 2024 at the top.
High-level flow:
  1. Install kubeadm, kubelet, and kubectl on all nodes.
  2. Install and configure a container runtime (containerd in this demo).
  3. Ensure cgroup driver alignment between kubelet and container runtime.
  4. Initialize the control plane on the master with kubeadm.
  5. Install a Pod network (CNI) — Flannel in this example.
  6. Join worker nodes to the cluster.
  7. Validate cluster and run a test pod.
Step-by-step instructions with commands:

1) Install kubeadm, kubelet and kubectl on all nodes

Run the following on all three machines (control plane + both workers). This example targets Kubernetes v1.31:
kubeadm requires swap to be disabled. Disable swap immediately and make the change persistent by removing or commenting out swap entries in /etc/fstab before running kubeadm init or kubeadm join.

2) Install and configure a container runtime (containerd)

Kubernetes needs a CRI-compatible container runtime. This demo uses containerd.
A screenshot of the Kubernetes documentation page titled "Container Runtimes," showing the main article text and links, a left-hand navigation menu, and a blue KubeCon banner across the top.
Install and enable containerd, and configure networking sysctl values required by Kubernetes:

3) Ensure cgroup driver alignment

For modern Debian/Ubuntu systems using systemd, kubelet and containerd should both use the systemd cgroup driver. Check the init system and configure containerd accordingly.
A screenshot of the Kubernetes documentation site showing a page about cgroup drivers, with the site navigation on the left and content describing cgroupfs and systemd drivers on the right. The Kubernetes logo and header are visible at the top.
Verify the init system:
Generate containerd config and enable systemd cgroups:

4) Initialize the control plane with kubeadm

On the master node, run kubeadm init. Choose the API server advertise address (the master IP on the cluster network) and a pod network CIDR that matches your chosen CNI (Flannel typically uses 10.244.0.0/16). Example:
kubeadm will perform preflight checks, fetch images, create certificates and static pod manifests, and print next steps including:
  • How to configure your local kubeconfig.
  • The kubeadm join command for worker nodes (with token and discovery hash).
  • (If --upload-certs was used) a certificate key for sharing control-plane certs.
After init completes, configure local kubeconfig:
Example important excerpt printed by kubeadm (condensed):

5) Install a Pod network (CNI) — Flannel example

Pick a CNI that supports your pod CIDR. For a default Flannel setup with 10.244.0.0/16:
Applying the manifest creates namespace, RBAC, configmap and a DaemonSet:
Verify CNI and system pods, and wait for nodes to become Ready:
Expected result (example):

6) Join worker nodes to the cluster

On each worker node, run the kubeadm join command that was printed by kubeadm init. If you lost it, you can create a new token or retrieve join info from the master:
If you need to create a new token on the master:
After joining, verify on the master:
You should see flannel pods scheduled on the worker nodes:

7) Test the cluster by running a simple Pod

Run an example Pod to confirm workload scheduling and networking:
The pod should reach the Running state if networking and node readiness are correct.

Quick reference table: common commands

Tips:
  • If you plan to migrate from a single-control-plane to an HA setup later, pass --control-plane-endpoint to kubeadm init to reference a stable virtual IP or load balancer.
  • For K8s >= 1.22 kubeadm defaults to systemd cgroup driver; ensure your container runtime is configured accordingly (e.g., containerd with SystemdCgroup = true).
  • If kubeadm cannot find your CRI socket, use --cri-socket (e.g., unix:///var/run/containerd/containerd.sock) to point explicitly to the runtime.
This completes the kubeadm bootstrap flow: you now have a single-control-plane Kubernetes cluster with two worker nodes running Flannel as the CNI. For production or HA setups, consult the kubeadm HA guide and the Kubernetes official docs linked above.

Watch Video

Practice Lab