kubeadm Creating and Managing Clusters
The complete workflow for using kubeadm to initialize a cluster, add nodes, manage tokens, and install CNI network plugins.
Overview
kubeadm is the official Kubernetes cluster bootstrapping tool for quickly creating best-practice-compliant clusters. It handles complex configurations such as certificates, kubeconfig, and control plane components. kubeadm-related operations are a core part of the CKA exam.
1. kubeadm init -- Initializing the Control Plane
1.1 Basic Initialization
# Minimal initialization
sudo kubeadm init
# Specify the Pod network CIDR (depends on CNI plugin requirements)
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
# Specify the API Server address
sudo kubeadm init --apiserver-advertise-address=192.168.1.10
# Specify the Kubernetes version
sudo kubeadm init --kubernetes-version=v1.31.0
1.2 Parsing Initialization Output
# Example output after successful initialization (each part is important):
Your Kubernetes control-plane has been 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
# A CNI network plugin must be installed
You can now join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.1.10:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
1.3 Post-Initialization Configuration
# Configure kubectl to access the cluster
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# Verify cluster status
kubectl cluster-info
kubectl get nodes
kubectl get pods -n kube-system
1.4 Custom kubeadm-config.yaml
# kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: "192.168.1.10"
bindPort: 6443
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: "v1.31.0"
controlPlaneEndpoint: "192.168.1.10:6443"
networking:
serviceSubnet: "10.96.0.0/12"
podSubnet: "10.244.0.0/16"
dnsDomain: "cluster.local"
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: "systemd"
# Initialize using the configuration
sudo kubeadm init --config=kubeadm-config.yaml
# View default configuration
kubeadm config print init-defaults
kubeadm config print join-defaults
# View cluster configuration
kubectl -n kube-system get configmap kubeadm-config -o yaml
2. kubeadm join -- Adding Worker Nodes
2.1 Basic Usage
# Join using the token from kubeadm init output
sudo kubeadm join 192.168.1.10:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
# Join using a configuration file
sudo kubeadm join --config=join-config.yaml
2.2 join-config.yaml
apiVersion: kubeadm.k8s.io/v1beta4
kind: JoinConfiguration
discovery:
bootstrapToken:
token: "<token>"
apiServerEndpoint: "192.168.1.10:6443"
caCertHashes:
- "sha256:<hash>"
nodeRegistration:
name: "worker-1"
criSocket: "unix:///var/run/containerd/containerd.sock"
2.3 Adding Control Plane Nodes (High Availability)
# Add additional control plane nodes (requires certificate and key first)
sudo kubeadm join 192.168.1.10:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane
# Or use the --certificate-key parameter
sudo kubeadm join 192.168.1.10:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane \
--certificate-key <certificate-key>
3. kubeadm token -- Token Management
3.1 Token Operations
# List all tokens
kubeadm token list
# Create a new token
kubeadm token create
# Create a non-expiring token
kubeadm token create --ttl 0
# Specify token TTL
kubeadm token create --ttl 4h
# Delete a token
kubeadm token delete <token>
# View token details
kubeadm token describe <token>
3.2 Getting the Information Needed to Join a Cluster
# Get tokens
kubeadm token list
# If there are no tokens, create a new one
kubeadm token create
# Get the CA certificate hash
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \
openssl rsa -pubin -outform der 2>/dev/null | \
openssl dgst -sha256 -hex | sed 's/^.* //'
# Or use kubeadm to generate the complete join command
kubeadm token create --print-join-command
# Get the control plane join command
kubeadm init phase upload-certs --upload-certs
kubeadm token create --print-join-command
4. kubeadm reset -- Resetting a Cluster
4.1 Resetting a Node
# Reset a control plane node
sudo kubeadm reset
# Non-interactive reset
sudo kubeadm reset -f
# Clean up network configuration (reset CNI)
sudo kubeadm reset -f
sudo rm -rf /etc/cni/net.d
sudo rm -rf $HOME/.kube
# If using iptables, clean up rules
sudo iptables -F && sudo iptables -t nat -F && sudo iptables -t mangle -F && sudo iptables -X
4.2 Resetting a Worker Node
# On the worker node
sudo kubeadm reset
sudo rm -rf /etc/cni/net.d
sudo systemctl restart containerd
# On the control plane, delete the node
kubectl delete node worker-1
5. TLS Bootstrapping
TLS bootstrapping allows worker nodes to automatically request certificates, simplifying the node joining process.
# View Certificate Signing Requests
kubectl get csr
kubectl describe csr <csr-name>
# Approve a CSR
kubectl certificate approve <csr-name>
# Deny a CSR
kubectl certificate deny <csr-name>
# View the certificates used by the node
ls -la /var/lib/kubelet/pki/
Node Certificate Auto-Renewal Configuration
# /etc/kubernetes/kubelet-config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
rotateCertificates: true
serverTLSBootstrap: true
6. CNI Network Plugin Installation
6.1 Calico
# Install Tigera Operator
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28/manifests/tigera-operator.yaml
# Download custom resources
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.28/manifests/custom-resources.yaml
# Modify Pod CIDR if needed
sed -i 's|192.168.0.0/16|10.244.0.0/16|g' custom-resources.yaml
# Apply configuration
kubectl create -f custom-resources.yaml
# Verify
kubectl get pods -n calico-system
kubectl get nodes
6.2 Flannel
# Install Flannel
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
# Verify
kubectl get pods -n kube-flannel
kubectl get nodes
6.3 Cilium
# Install Cilium using Helm
helm repo add cilium https://helm.cilium.io/
helm install cilium cilium/cilium --namespace kube-system
# Verify Cilium status
cilium status
kubectl get pods -n kube-system | grep cilium
7. Image Management
# View the images needed by kubeadm
kubeadm config images list
# View images for a specific version
kubeadm config images list --kubernetes-version=v1.31.0
# Pull required images
kubeadm config images pull
# Specify an image repository to pull from (useful for certain regions)
kubeadm config images pull --image-repository=registry.aliyuncs.com/google_containers
8. Common Troubleshooting
# Steps for troubleshooting initialization failures
# 1. Check containerd status
sudo systemctl status containerd
# 2. Check containerd configuration (SystemdCgroup)
sudo cat /etc/containerd/config.toml | grep SystemdCgroup
# 3. View kubelet logs
sudo journalctl -u kubelet -n 100 --no-pager
# 4. Check port usage
sudo ss -tulpn | grep -E "6443|2379|2380|10250"
# 5. Check container runtime
sudo crictl info
# 6. Reset and retry
sudo kubeadm reset -f
sudo rm -rf /etc/kubernetes/
sudo rm -rf /var/lib/etcd/
CKA Exam Key Points
--pod-network-cidrmust match the CNI plugin requirements (Calico defaults to192.168.0.0/16, Flannel uses10.244.0.0/16)- To generate a join command, using
kubeadm token create --print-join-commandis the fastest way - Control plane join requires
--control-planeand--certificate-key - Pull images before init:
kubeadm config images pullto avoid timeouts - Clean up after kubeadm reset: manually clean
/etc/kubernetes/and/var/lib/etcd/
🧪 Complete Hands-on Example: Initialize a Single Control Plane Cluster with kubeadm
Scenario Description
On a node with infrastructure already configured, use kubeadm to initialize a single control plane cluster and install the Calico CNI plugin.
Prerequisites
- Node infrastructure has been configured (swap disabled, containerd installed and configured with SystemdCgroup)
- kubeadm, kubelet, kubectl are installed
Steps
Step 1: Pull required images
# Pull images ahead of time to avoid initialization timeouts
sudo kubeadm config images pull
# [config/images] Pulled registry.k8s.io/kube-apiserver:v1.31.0
# [config/images] Pulled registry.k8s.io/kube-controller-manager:v1.31.0
# ...
Step 2: Initialize the control plane
# Use Calico's default Pod CIDR
sudo kubeadm init --pod-network-cidr=192.168.0.0/16
# Output example (key parts):
# Your Kubernetes control-plane has been initialized successfully!
# ...
# mkdir -p $HOME/.kube
# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
# sudo chown $(id -u):$(id -g) $HOME/.kube/config
# ...
# kubeadm join 192.168.1.10:6443 --token <token> \
# --discovery-token-ca-cert-hash sha256:<hash>
Step 3: Configure kubeconfig
# Configure kubectl access after successful installation
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# Verify cluster status
kubectl cluster-info
# Kubernetes control plane is running at https://192.168.1.10:6443
# CoreDNS is running at https://192.168.1.10:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
Step 4: Install Calico CNI
# Install Tigera Operator
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28/manifests/tigera-operator.yaml
# Download and apply custom resources
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.28/manifests/custom-resources.yaml
# If kubeadm init used a different pod CIDR, modify it
# sed -i 's|192.168.0.0/16|10.244.0.0/16|g' custom-resources.yaml
kubectl create -f custom-resources.yaml
# Wait for Calico Pods to become ready
kubectl get pods -n calico-system -w
# NAME READY STATUS RESTARTS AGE
# calico-kube-controllers-xxxxxxxxx-xxxxx 1/1 Running 0 2m
# calico-node-xxxxx 1/1 Running 0 2m
# calico-typha-xxxxx 1/1 Running 0 2m
Step 5: Verify node status
# Wait for the node to become Ready
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# control-plane-1 Ready control-plane 5m v1.31.0
# View system Pods
kubectl get pods -n kube-system
# NAME READY STATUS RESTARTS AGE
# coredns-xxxxxxxxx-xxxxx 1/1 Running 0 5m
# coredns-xxxxxxxxx-xxxxx 1/1 Running 0 5m
# etcd-control-plane-1 1/1 Running 0 5m
# kube-apiserver-control-plane-1 1/1 Running 0 5m
# kube-controller-manager-control-plane-1 1/1 Running 0 5m
# kube-proxy-xxxxx 1/1 Running 0 5m
# kube-scheduler-control-plane-1 1/1 Running 0 5m
Step 6: Generate worker node join command
# Create a new token and print the complete join command
kubeadm token create --print-join-command
# kubeadm join 192.168.1.10:6443 --token <new-token> --discovery-token-ca-cert-hash sha256:<hash>
Verification
kubectl get nodes -o wide
# Confirm node is Ready and version is correct
kubectl get pods --all-namespaces | grep -v Completed
Exam Tips
--pod-network-cidrmust match the CNI plugin requirements: Calico defaults to192.168.0.0/16, Flannel defaults to10.244.0.0/16- Always run
kubeadm config images pullbefore kubeadm init to avoid network timeouts - Save the join command from kubeadm init output, or regenerate with
kubeadm token create --print-join-command - After initialization, configure kubeconfig first before installing CNI, otherwise
kubectl get nodeswill show NotReady