2.4 kubeadm: Production-Grade Cluster Bootstrap
Right, so you want a production-grade Kubernetes cluster. Not a toy for your laptop, but the real deal. That means we’re reaching for kubeadm. It’s the official, blessed, “I know what I’m doing” tool for bootstrapping a cluster. It’s not magic; it’s a glorified orchestration tool that runs a series of checks and creates all the necessary components—the API server, etcd, scheduler, the whole gang—as static pods. This is brilliant because it means Kubernetes itself manages its own control plane. If the API server crashes, the kubelet restarts it. Poetic, really.
But let’s be clear: kubeadm init is the beginning of the journey, not the end. Anyone who tells you to just run that and walk away has a cluster running on hope and default settings. We’re not doing that.
The Pre-Flight Checklist (Or, How to Avoid Your Own Hubris)
Before we even think about running kubeadm init, we have to prepare the ground. The kubeadm pre-flight checks are good, but they’re not omniscient. Here’s what you absolutely must do on every node (control plane and workers):
Cripple the Firewall: Or, more accurately, tell it to play nice. You need to allow the correct ports for the control plane nodes (TCP 6443, 2379-2380, 10250, 10259, 10257) and the workers. The
iptablestool must see bridged traffic. This is non-negotiable.# Enable iptables to see bridged traffic on all nodes cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf br_netfilter EOF cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 EOF sudo sysctl --systemMurder swap. This isn’t a suggestion. The kubelet will straight-up refuse to run if swap is enabled. It’s a design choice to ensure predictability (swap kills performance in a noisy way). So, disable it.
# Disable swap immediately sudo swapoff -a # Comment out any swap lines in /etc/fstab to make it permanent sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstabInstall the Holy Trinity: You need
kubeadm,kubelet, andkubectlon all nodes. Use the official Kubernetes repos. Don’t justapt-get installfrom your distro’s default repo; you’ll get geriatric versions.
The kubeadm init That Actually Works
Now for the main event. We’re not running it naked. We’re going to generate a configuration file because we need to change things. The default settings are, frankly, for amateurs. The most important thing? Setting the controlPlaneEndpoint to a stable DNS name or load balancer before you initialize. This is your single biggest “future-proofing” step. It allows you to add more control plane nodes later for high availability. If you just use the node’s IP, you’re locked into a single control plane node forever. Don’t do that.
# Generate a default config file to work from
kubeadm config print init-defaults > kubeadm-init.yaml
Now, crack open kubeadm-init.yaml. You’ll need to change at least these things:
localAPIEndpoint.advertiseAddress: The IP address the API server will advertise.controlPlaneEndpoint: Your stable DNS name or load balancer IP (e.g.,my-k8s-api.example.com:6443).nodeRegistration.name: Change fromnodeto your actual node’s hostname.- Under
apiServer,controllerManager, andscheduler, you’ll findextraArgs. This is where you set critical flags. A big one:apiServer.extraArgs.service-account-issuerandservice-account-jwks-urifor proper OIDC discovery if you ever want to use service account tokens outside the cluster.
Once your config file is polished, run the pre-flight checks with it:
sudo kubeadm init --config kubeadm-init.yaml --dry-run
If that looks good, take a deep breath and run it for real:
sudo kubeadm init --config kubeadm-init.yaml --upload-certs
The --upload-certs flag is a lifesaver if you plan to add other control plane nodes later; it automatically uploads and shares the certificates.
The Aftermath and Joining the Party
Congratulations, your control plane is running. Now, kubeadm will spit out two crucial commands. Copy them to a text file immediately. The first is your standard kubectl setup:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
The second is the kubeadm join command with a token and CA certificate hash. This is what your worker nodes (and future control plane nodes) will run to join the cluster. This token expires after 24 hours. If you need a new one later, generate it with kubeadm token create --print-join-command.
The Most Common “Oh, Come On” Moment
You’ve joined your nodes, but when you run kubectl get nodes, your worker nodes are stuck in NotReady state. 99% of the time, this is because you forgot to install a Container Network Interface (CNI) plugin. Kubernetes is a diva; it won’t consider a node ready until a network pod is running. Let’s fix that with the Canal plugin (which combines Flannel for networking and Calico for network policy):
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/canal.yaml
Wait a minute, then watch your nodes flip to Ready. See? Not so bad. You’ve now got a cluster that’s actually built to last, not just to start.