Introduction
There are several easy ways to build a Kubernetes cluster.
For example, k3s can be installed with the following command.
curl -sfL https://get.k3s.io | sh -
With k0s, you can build a single node cluster as follows.
curl -sSf https://get.k0s.sh | sudo sh
sudo k0s install controller --single
sudo k0s start
For minikube, use the following.
minikube start
In this way, you can create a Kubernetes environment fairly easily with lightweight Kubernetes distributions and local development tools.
However, creating so-called plain Kubernetes, that is, a kubeadm-based Kubernetes cluster, is surprisingly troublesome.
When using kubeadm, you can create a Kubernetes cluster with a configuration close to the official one. On the other hand, building one requires several tasks: preparing a container runtime, configuring swap and sysctl, installing kernel modules, installing kubeadm/kubelet/kubectl, running kubeadm init, deploying kubeconfig, joining worker nodes, and installing a CNI plugin.
k3s and k0s bundle most of this. They are very easy to build because they can be handled as a single binary or as an integrated package.
However, in exchange for its convenience, the component configuration is slightly different from a regular kubeadm cluster.
For example, when a runtime such as containerd is integrated into the distribution, it may not be suitable for replacing or verifying Kubernetes itself, the container runtime, CRI, kubelet, kube-proxy, and similar components with the same granularity as in production.
In particular, if you want to verify RuntimeClass and a container runtime such as Kata Containers, a kubeadm-based cluster may be easier to reason about than a lightweight Kubernetes distribution.
I want to use Kubernetes in a configuration close to production. But I also want it to be as easy to build as k3s or minikube.
That’s why I created setup-k8s.
The repository is here. https://github.com/MuNeNiCK/setup-k8s
The docs are here. https://munenick.github.io/setup-k8s/
What is setup-k8s?
setup-k8s is a shell script for building a kubeadm-based Kubernetes cluster with one command.
You can build Kubernetes with the following command.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- init
This initializes the current machine as a Kubernetes control-plane node.
user@ubuntu-24:~$ kubectl get node
NAME STATUS ROLES AGE VERSION
ubuntu-24 NotReady control-plane 8s v1.36.1
user@ubuntu-24:~$ kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-589f44dc88-kf5vj 0/1 Pending 0 1s
kube-system coredns-589f44dc88-w7jnj 0/1 Pending 0 1s
kube-system etcd-ubuntu-24 0/1 Running 0 9s
kube-system kube-apiserver-ubuntu-24 0/1 Running 0 9s
kube-system kube-controller-manager-ubuntu-24 0/1 Running 0 9s
kube-system kube-proxy-v2jhh 1/1 Running 0 2s
kube-system kube-scheduler-ubuntu-24 1/1 Running 0 9s
user@ubuntu-24:~$
One major feature is multi-distribution support.
You can use the same commands to build a Kubernetes cluster on Ubuntu, Debian, Rocky Linux, AlmaLinux, Fedora, openSUSE, Alpine Linux, or Arch Linux.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- init
Internally, it automatically detects the distribution and uses apt, dnf/yum, zypper, apk, pacman, etc.
The Kubernetes setup steps are similar, but there are surprising differences between operating systems.
There are detailed differences between distributions, such as package management, required repositories, container runtime, service manager, cgroups, and iptables/nftables.
setup-k8s absorbs those differences and lets you create a kubeadm-based Kubernetes cluster from the same entry point.
The supported distributions are as follows.
| Distribution | Version |
|---|---|
| Ubuntu | 24.04 LTS, 22.04 LTS |
| Debian | 13 (Trixie), 12 (Bookworm), 11 (Bullseye) |
| CentOS Stream | 10, 9 |
| Rocky Linux | 10, 9, 8 |
| AlmaLinux | 10, 9, 8 |
| Oracle Linux | 9 |
| Fedora | 44, 43 |
| openSUSE | Tumbleweed, Leap 16.0 |
| Alpine Linux | 3.23 |
| Arch Linux | Rolling |
The latest support status is summarized in the docs.
https://munenick.github.io/setup-k8s/supported-distros/
Inside the script, the following happens:
- Installing required packages
- Configuring container runtime
- Disabling swap
- Kernel module and sysctl settings
- Installing kubeadm/kubelet/kubectl
- kubeadm init
- kubeconfig placement
In other words, it uses kubeadm while automating the routine tasks required to create a Kubernetes cluster.
First, create a single node cluster
For testing, the most common setup is a single-node Kubernetes cluster.
Prepare a VM or physical machine and run the following.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- init
The control-plane node is now initialized.
After initialization, install the CNI plugin. For example, when using Calico:
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
If you use it as a single-node cluster, remove the taint so that Pods can run on the control-plane node as well.
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
Check the status.
kubectl get nodes
kubectl get pods -A
You can now use it as a kubeadm-based single-node Kubernetes cluster.
Add worker nodes
If you want to create a multi-node cluster, join worker nodes.
First, check the join command information on the control-plane node.
kubeadm token create --print-join-command
On the worker node, execute join as follows.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
join \
--join-token <token> \
--join-address <address> \
--discovery-token-hash <hash>
On the worker node, the script sets up the necessary packages and container runtime, then executes kubeadm join.
The same script can prepare both control-plane nodes and worker nodes.
Create a multi-node cluster with one command
Setting up each node over SSH manually is also a pain.
Therefore, setup-k8s has a deploy subcommand.
Using deploy, you can connect to each node via SSH from a workstation or CI runner and build a multi-node Kubernetes cluster at once.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sh -s -- \
deploy \
--control-planes [email protected] \
--workers [email protected],[email protected] \
--ssh-key ~/.ssh/id_rsa
You can build the cluster as long as you can connect to the remote nodes over SSH and run sudo on them.
Internally, it transfers a self-contained bundle to each node, initializes the first control-plane node, and then joins the worker nodes.
This usage is recommended when creating a multi-node verification environment.
You can also create an HA control plane
It also supports HA configuration with multiple control-plane nodes.
If you want a VIP using kube-vip, specify --ha-vip.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sh -s -- \
deploy \
--control-planes [email protected],[email protected],[email protected] \
--workers [email protected] \
--ha-vip 192.168.1.100 \
--ssh-key ~/.ssh/id_rsa
Specifying --ha-vip creates an HA control-plane configuration using kube-vip.
You can create a VIP-based kubeadm HA configuration without a separate external load balancer.
You can choose containerd/CRI-O
The container runtime uses containerd by default.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- init
If you want to use CRI-O, specify --cri crio.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
init \
--cri crio
When joining a worker node or additional control-plane node, specify the same CRI as the existing cluster.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
join \
--cri crio \
--join-token <token> \
--join-address <address> \
--discovery-token-hash <hash>
Specify Kubernetes version
The Kubernetes version can be specified with --kubernetes-version.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
init \
--kubernetes-version 1.33.2
You can also specify only the minor version.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
init \
--kubernetes-version 1.33
In a testing environment, you may want to quickly create a specific version of Kubernetes.
For example, you may want to check compatibility with a CNI, CSI, Ingress Controller, Operator, RuntimeClass, or similar component. In that case, specify the version when creating the cluster.
You can choose kube-proxy mode
With setup-k8s, you can select iptables, ipvs, or nftables as the kube-proxy mode.
The default is iptables.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
init \
--proxy-mode iptables
When using IPVS, use the following.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
init \
--proxy-mode ipvs
If you use nftables, use the following.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
init \
--proxy-mode nftables \
--kubernetes-version 1.31
If you are just creating a new cluster, I think the default iptables is fine to start with.
IPVS and nftables are intended for compatibility with existing environments or for verification purposes. In particular, nftables has requirements for both Kubernetes and the kernel, so check the docs before using it.
IPv4 / IPv6 / dual-stack
You can also specify Pod CIDR and Service CIDR.
For IPv4, use the following.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
init \
--pod-network-cidr 192.168.0.0/16 \
--service-cidr 10.96.0.0/12
For dual-stack, specify IPv4 and IPv6 CIDRs separated by commas.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
init \
--pod-network-cidr 10.244.0.0/16,fd00:10:244::/48 \
--service-cidr 10.96.0.0/12,fd00:20::/108
IPv6 single-stack can also be specified.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
init \
--pod-network-cidr fd00:10:244::/48 \
--service-cidr fd00:20::/108
To actually use it, the CNI plugin must also support that address family.
Check in advance with preflight
If you are worried about creating a cluster immediately, you can run preflight.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- preflight
If you want to check for a join operation, specify --mode join.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
preflight \
--mode join
Before running init or join, you can check that the required conditions are met.
Check the status with status
If you want to check the status of your cluster or nodes, use status.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sh -s -- status
Specify --output wide for more details.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sh -s -- status --output wide
It checks the status of kubelet and the container runtime, the versions of Kubernetes components, and the status of nodes and pods if kubectl can be used.
Clean up with cleanup
After verification, you can delete Kubernetes-related settings with cleanup.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- cleanup --force
If you want to remove a worker node from the cluster, first drain and delete it from the control-plane node.
kubectl drain <worker-node-name> --ignore-daemonsets
kubectl delete node <worker-node-name>
After that, run cleanup on the worker node.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- cleanup --force
Running cleanup on a control-plane node is destructive to the cluster itself. Make sure you are targeting the intended node.
You can also upgrade / backup / restore / renew
setup-k8s supports not only cluster creation but also some operational tasks.
However, since the subject of this article is creating a kubeadm cluster with one command, I will only provide an overview here.
Use upgrade when upgrading Kubernetes.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
upgrade \
--kubernetes-version 1.33.2 \
--first-control-plane
Use backup to take an etcd snapshot.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- backup
Use restore when restoring from snapshot.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
restore \
--snapshot-path /path/to/snapshot.db
Use renew to check or update kubeadm certificates.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- \
renew \
--check-only
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- renew
When to use it
setup-k8s is mainly intended for the following situations.
I want to easily create a kubeadm cluster
Use it when you want to create a Kubernetes cluster as easily as k3s, k0s, or minikube, but based on kubeadm.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sudo sh -s -- init
This command is all you need to initialize the control-plane node.
I want to build the same way on different distributions
You can build Kubernetes clusters on Ubuntu, Debian, Rocky Linux, AlmaLinux, Fedora, openSUSE, Alpine Linux, Arch Linux, and more from the same command.
Since package management and prerequisite settings differ by distribution, doing it manually can be quite a hassle.
setup-k8s automatically detects these differences and tries to provide as much of the same experience as possible.
I want a Kubernetes environment close to production
Use it when you want to run kubelet, the container runtime, CNI, and kube-proxy on actual Linux nodes rather than a lightweight Kubernetes distribution.
A kubeadm-based configuration is easier to handle if you want to explicitly choose containerd or CRI-O, try RuntimeClass or Kata Containers, or change the kube-proxy mode.
I want to easily create a multi-node cluster
deploy allows you to build a multi-node cluster together via SSH.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sh -s -- \
deploy \
--control-planes [email protected] \
--workers [email protected],[email protected]
It is easier than joining each node manually.
I want to try HA control plane
You can also create an HA control plane configuration using kube-vip.
curl -fsSL https://github.com/MuNeNiCK/setup-k8s/raw/main/setup-k8s.sh | sh -s -- \
deploy \
--control-planes [email protected],[email protected],[email protected] \
--workers [email protected] \
--ha-vip 192.168.1.100
Points to note
setup-k8s does not provide CNI.
After init, install the CNI plugin separately before using the cluster.
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
Additionally, operations that change nodes, such as init, join, cleanup, upgrade, backup, restore, renew, generally require root privileges.
For remote deploys, root privileges are not required on the workstation, but SSH access and sudo privileges are required on the remote nodes.
Cleanup and restore are destructive operations. Make sure you are targeting the intended node.
Conclusion
There are already easy ways to build Kubernetes.
k3s, k0s, and minikube are very useful. If you’re just trying it out, I think it’s easiest to use them.
However, there are times when I want to create a kubeadm-based Kubernetes cluster with a configuration close to production, without making the setup tedious.
I want to explicitly select the container runtime. I want to use CRI-O. I want to try Kata Containers. I want to change the kube-proxy mode. I want to try the HA control plane with kubeadm. I want to test Kubernetes on multiple distributions.
In such cases, manually running the kubeadm setup procedure every time is tedious.
setup-k8s is a tool that wraps up the tedious parts and makes building a kubeadm-based Kubernetes cluster as easy as possible.
Try it if you are interested.