Setup Kubernetes Cluster Using Kubeadm: Difference between revisions
No edit summary |
No edit summary |
||
| Line 7: | Line 7: | ||
# '''10.X.X.X/X''' network range with static IPs for master and worker nodes. We will be using the '''192.x.x.x''' series as the pod network range that will be used by the Calico network plugin. Make sure the Node IP range and pod IP range don't overlap. | # '''10.X.X.X/X''' network range with static IPs for master and worker nodes. We will be using the '''192.x.x.x''' series as the pod network range that will be used by the Calico network plugin. Make sure the Node IP range and pod IP range don't overlap. | ||
On cp1:<pre> | |||
sudo hostnamectl set-hostname cp1 | sudo hostnamectl set-hostname cp1 | ||
</pre> | </pre>On worker1:<pre> | ||
sudo hostnamectl set-hostname w1 | sudo hostnamectl set-hostname w1 | ||
</pre> | </pre>On worker2:<pre>sudo hostnamectl set-hostname w2</pre>Add entries to /etc/hosts on all nodes:<pre> | ||
sudo nano /etc/hosts | sudo nano /etc/hosts | ||
</pre> | </pre>Add the following to the end of the file, replacing with your IP addresses:<pre> | ||
10.0.0.10 cp1 | 10.0.0.10 cp1 | ||
10.0.0.11 w1 | 10.0.0.11 w1 | ||
10.0.0.12 w2 | 10.0.0.12 w2 | ||
</pre> | </pre>This allows nodes to address each other by name rather than by IP address. | ||
'''1. | '''1. General system preparation (all nodes)''' | ||
'''1.1. | '''1.1. Updating packages'''<pre> | ||
sudo apt update | sudo apt update | ||
sudo apt upgrade -y | sudo apt upgrade -y | ||
</pre> | </pre>We are simply updating the system to avoid bugs caused by old packages. | ||
'''1.2. | '''1.2. Disable swap (required for kubeadm)''' | ||
On all nodes:<pre> | |||
sudo swapoff -a | sudo swapoff -a | ||
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab | sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab | ||
</pre> | </pre>Verification:<pre> | ||
free -h | free -h | ||
</pre>'''1.3. | </pre>'''1.3. Kernel modules and sysctl (for the grid)↵↵On each node:'''<pre> | ||
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf | cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf | ||
overlay | overlay | ||
| Line 43: | Line 41: | ||
sudo modprobe overlay | sudo modprobe overlay | ||
sudo modprobe br_netfilter | sudo modprobe br_netfilter | ||
</pre> | </pre>Now sysctl:<pre> | ||
cat <<EOF | sudo tee /etc/sysctl.d/kubernetes.conf | cat <<EOF | sudo tee /etc/sysctl.d/kubernetes.conf | ||
net.bridge.bridge-nf-call-ip6tables = 1 | net.bridge.bridge-nf-call-ip6tables = 1 | ||
| Line 51: | Line 49: | ||
sudo sysctl --system | sudo sysctl --system | ||
</pre> | </pre>This includes correct traffic handling through the Linux bridge and forwarding — mandatory for CNI (Calico). | ||
'''1.4. ( | '''1.4. (For lab) Disable UFW''' | ||
If UFW is enabled and this is a clean lab:<pre> | |||
sudo ufw disable | sudo ufw disable | ||
</pre> | </pre>In Prod, of course, it is better to open the necessary ports, but for the first build it is easier without a firewall. | ||
'''2. | '''2. Install containerd (all nodes)''' | ||
At each node:<pre> | |||
sudo apt install -y containerd | sudo apt install -y containerd | ||
</pre> | </pre>Generate default configuration:<pre> | ||
sudo mkdir -p /etc/containerd | sudo mkdir -p /etc/containerd | ||
sudo containerd config default | sudo tee /etc/containerd/config.toml > /dev/null | sudo containerd config default | sudo tee /etc/containerd/config.toml > /dev/null | ||
</pre> | </pre>Restart and switch on:<pre> | ||
sudo systemctl restart containerd | sudo systemctl restart containerd | ||
sudo systemctl enable containerd | sudo systemctl enable containerd | ||
sudo systemctl status containerd | sudo systemctl status containerd | ||
</pre | </pre>SystemdCgroup = true → kubelet and containerd use the same cgroup driver (systemd). This is now considered best practice. | ||
'''3. | '''3. Install kubeadm/kubelet/kubectl 1.34 (all nodes)''' | ||
According to the official documentation for v1.34: | |||
'''3.1. | '''3.1. Repo packages''' | ||
At each node:<pre> | |||
sudo apt-get update | sudo apt-get update | ||
sudo apt-get install -y apt-transport-https ca-certificates curl gpg | sudo apt-get install -y apt-transport-https ca-certificates curl gpg | ||
</pre>'''3.2. GPG | </pre>'''3.2. GPG key and pkgs.k8s.io repository (v1.34)↵↵Ubuntu 22.04 already has /etc/apt/keyrings, but if it doesn't:'''<pre> | ||
sudo mkdir -p -m 755 /etc/apt/keyrings | sudo mkdir -p -m 755 /etc/apt/keyrings | ||
</pre> | </pre>Repository key:<pre> | ||
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.34/deb/Release.key \ | curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.34/deb/Release.key \ | ||
| sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg | | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg | ||
</pre> | </pre>Add repo specifically for Kubernetes 1.34:<pre> | ||
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.34/deb/ /" \ | echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.34/deb/ /" \ | ||
| sudo tee /etc/apt/sources.list.d/kubernetes.list | | sudo tee /etc/apt/sources.list.d/kubernetes.list | ||
</pre>'''3.3. | </pre>'''3.3. Installing kubeadm/kubelet/kubectl'''<pre> | ||
sudo apt-get update | sudo apt-get update | ||
sudo apt-get install -y kubelet kubeadm kubectl | sudo apt-get install -y kubelet kubeadm kubectl | ||
sudo apt-mark hold kubelet kubeadm kubectl | sudo apt-mark hold kubelet kubeadm kubectl | ||
</pre><blockquote> | </pre><blockquote>Repository v1.34 ensures that branch 1.34.x is installed. apt-mark hold will prevent accidental updates to other minor versions during apt upgrade.</blockquote>(Not required, but you can enable kubelet immediately; it will run and wait for kubeadm init / join):<pre> | ||
sudo systemctl enable --now kubelet | sudo systemctl enable --now kubelet | ||
</pre>'''5. | </pre>'''5. Control-plane initialisation (cp1 only)''' | ||
Now we create a cluster on <code>cp1</code>. | |||
'''5.1. kubeadm init | '''5.1. kubeadm init with pod-CIDR for Calico''' | ||
Calico | Calico uses 192.168.0.0/16 by default. For convenience, we will use the same: | ||
On <code>cp1</code>:<pre> | |||
sudo kubeadm init \ | sudo kubeadm init \ | ||
--apiserver-advertise-address=10.0.0.10 \ | --apiserver-advertise-address=10.0.0.10 \ | ||
| Line 111: | Line 107: | ||
</pre> | </pre> | ||
* <code>--apiserver-advertise-address</code> — IP cp1, | * <code>--apiserver-advertise-address</code> — IP cp1, which everyone will use to access the API. | ||
* <code>--pod-network-cidr</code> — | * <code>--pod-network-cidr</code> — The IP range for pods that Calico will use. | ||
At the end, kubeadm will output: | |||
* | * message about successful initialisation; | ||
* | * The command kubeadm join ... — be sure to copy it somewhere (we will use it on the workers). | ||
'''5.2. | '''5.2. Configuring kubectl for the sadmin user (cp1)''' | ||
Currently, kubeconfig is located in /etc/kubernetes/admin.conf and belongs to root. | |||
On cp1 under sadmin:<pre> | |||
mkdir -p $HOME/.kube | mkdir -p $HOME/.kube | ||
sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config | sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config | ||
sudo chown $(id -u):$(id -g) $HOME/.kube/config | sudo chown $(id -u):$(id -g) $HOME/.kube/config | ||
</pre> | </pre>Verification:<pre> | ||
kubectl get nodes | kubectl get nodes | ||
</pre> | </pre>Until the network is up, the node may be NotReady — this is normal. | ||
'''6. | '''6. Install Calico 3.31 (cp1 only)''' | ||
We take the latest Calico 3.31.1 from the official on-prem instructions. | |||
'''6.1. Tigera Operator + CRD''' | '''6.1. Tigera Operator + CRD''' | ||
On <code>cp1</code>:<pre> | |||
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.31.1/manifests/operator-crds.yaml | kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.31.1/manifests/operator-crds.yaml | ||
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.31.1/manifests/tigera-operator.yaml | kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.31.1/manifests/tigera-operator.yaml | ||
</pre><blockquote> | </pre><blockquote>This sets the Calico operator, which then rolls out the necessary components across the nodes itself.</blockquote>'''6.2. Calico configuration (custom-resources)''' | ||
By default, we use iptables-dataplane (without eBPF, so as not to complicate things for now). | |||
On <code>cp1</code>:<pre> | |||
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.31.1/manifests/custom-resources.yaml | curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.31.1/manifests/custom-resources.yaml | ||
kubectl create -f custom-resources.yaml | kubectl create -f custom-resources.yaml | ||
</pre>( | </pre>(If you want eBPF in the future, there is also ''custom-resources-bpf.yaml.'') | ||
'''6.3. | '''6.3. Verify that Calico has started''' | ||
On <code>cp1</code>:<pre> | |||
watch kubectl get tigerastatus | watch kubectl get tigerastatus | ||
</pre> | </pre>We are waiting until everything is ''AVAILABLE=True:''<pre> | ||
NAME AVAILABLE PROGRESSING DEGRADED SINCE | NAME AVAILABLE PROGRESSING DEGRADED SINCE | ||
calico True False False ... | calico True False False ... | ||
ippools True False False ... | ippools True False False ... | ||
... | ... | ||
</pre> | </pre>And let's look at the pods:<pre> | ||
kubectl get pods -n calico-system | kubectl get pods -n calico-system | ||
kubectl get pods -n kube-system | kubectl get pods -n kube-system | ||
kubectl get nodes | kubectl get nodes | ||
</pre> | </pre><code>cp1</code> should transition to Ready status. | ||
'''7. | '''7. Make the control plane "clean" (without workload pods)''' | ||
By default, in 1.34, the control plane can accept normal pods (taint is not set automatically). | |||
You don't need this, so let's set taint right away: | |||
On <code>cp1</code>:<pre> | |||
kubectl taint nodes cp1 node-role.kubernetes.io/control-plane=:NoSchedule | kubectl taint nodes cp1 node-role.kubernetes.io/control-plane=:NoSchedule | ||
</pre> | </pre>Verification:<pre> | ||
kubectl describe node cp1 | grep -i Taint | kubectl describe node cp1 | grep -i Taint | ||
</pre> | </pre>It should be something like:<pre> | ||
Taints: node-role.kubernetes.io/control-plane:NoSchedule | Taints: node-role.kubernetes.io/control-plane:NoSchedule | ||
</pre><blockquote> | </pre><blockquote>Now the scheduler will NOT place regular pods on cp1. Only system components will remain there (kube-apiserver, etcd, kube-controller-manager, scheduler, Calico DaemonSet, etc.).</blockquote>'''8. Connect worker1 and worker2''' | ||
'''8.1. | '''8.1. Obtain the join command (if lost)''' | ||
If you did not save the output of kubeadm init, on cp1:<pre> | |||
sudo kubeadm token create --print-join-command | sudo kubeadm token create --print-join-command | ||
</pre> | </pre>Example:<pre> | ||
kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \ | kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \ | ||
--discovery-token-ca-cert-hash sha256:xxxxxxxx... | --discovery-token-ca-cert-hash sha256:xxxxxxxx... | ||
</pre> | </pre>8.2. Perform a join on worker1 / worker2↵↵On worker1:<pre> | ||
sudo kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \ | sudo kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \ | ||
--discovery-token-ca-cert-hash sha256:xxxxxxxx... | --discovery-token-ca-cert-hash sha256:xxxxxxxx... | ||
</pre> | </pre>On <code>worker2</code>:<pre> | ||
sudo kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \ | sudo kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \ | ||
--discovery-token-ca-cert-hash sha256:xxxxxxxx... | --discovery-token-ca-cert-hash sha256:xxxxxxxx... | ||
</pre><blockquote> | </pre><blockquote>Here, a token and CA hash are used to enable workers to securely join the cluster.</blockquote>After that, on <code>cp1</code>:<pre> | ||
kubectl get nodes | kubectl get nodes | ||
</pre> | </pre>Expected result:<pre> | ||
NAME STATUS ROLES AGE VERSION | NAME STATUS ROLES AGE VERSION | ||
cp1 Ready control-plane 20m v1.34.x | cp1 Ready control-plane 20m v1.34.x | ||
worker1 Ready <none> 5m v1.34.x | worker1 Ready <none> 5m v1.34.x | ||
worker2 Ready <none> 3m v1.34.x | worker2 Ready <none> 3m v1.34.x | ||
</pre> | </pre>If workers are NotReady for some time, wait until kube-proxy + Calico catch up and start. | ||
'''9. | '''9. Verification: only on worker nodes''' | ||
Let's create a test deployment: | |||
On <code>cp1</code>:<pre> | |||
kubectl create deployment nginx --image=nginx --replicas=3 | kubectl create deployment nginx --image=nginx --replicas=3 | ||
kubectl expose deployment nginx --port=80 --type=NodePort | kubectl expose deployment nginx --port=80 --type=NodePort | ||
</pre> | </pre>Let's see:<pre> | ||
kubectl get pods -o wide | kubectl get pods -o wide | ||
</pre>Все pod’ы <code>nginx</code> должны сидеть на <code>worker1</code> и <code>worker2</code>, '''но не на cp1'''. | </pre>Все pod’ы <code>nginx</code> должны сидеть на <code>worker1</code> и <code>worker2</code>, '''но не на cp1'''. | ||
Revision as of 08:06, 19 November 2025
Kubeadm Setup Prerequisites
Following are the prerequisites for Kubeadm Kubernetes cluster setup.
- Minimum two Ubuntu nodes [One master and one worker node]. You can have more worker nodes as per your requirement.
- The master node should have a minimum of 2 vCPU and 2GB RAM.
- For the worker nodes, a minimum of 1vCPU and 2 GB RAM is recommended.
- 10.X.X.X/X network range with static IPs for master and worker nodes. We will be using the 192.x.x.x series as the pod network range that will be used by the Calico network plugin. Make sure the Node IP range and pod IP range don't overlap.
On cp1:
sudo hostnamectl set-hostname cp1
On worker1:
sudo hostnamectl set-hostname w1
On worker2:
sudo hostnamectl set-hostname w2
Add entries to /etc/hosts on all nodes:
sudo nano /etc/hosts
Add the following to the end of the file, replacing with your IP addresses:
10.0.0.10 cp1 10.0.0.11 w1 10.0.0.12 w2
This allows nodes to address each other by name rather than by IP address.
1. General system preparation (all nodes)
1.1. Updating packages
sudo apt update sudo apt upgrade -y
We are simply updating the system to avoid bugs caused by old packages.
1.2. Disable swap (required for kubeadm)
On all nodes:
sudo swapoff -a sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
Verification:
free -h
1.3. Kernel modules and sysctl (for the grid)↵↵On each node:
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf overlay br_netfilter EOF
sudo modprobe overlay sudo modprobe br_netfilter
Now sysctl:
cat <<EOF | sudo tee /etc/sysctl.d/kubernetes.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 EOF
sudo sysctl --system
This includes correct traffic handling through the Linux bridge and forwarding — mandatory for CNI (Calico).
1.4. (For lab) Disable UFW
If UFW is enabled and this is a clean lab:
sudo ufw disable
In Prod, of course, it is better to open the necessary ports, but for the first build it is easier without a firewall.
2. Install containerd (all nodes)
At each node:
sudo apt install -y containerd
Generate default configuration:
sudo mkdir -p /etc/containerd sudo containerd config default | sudo tee /etc/containerd/config.toml > /dev/null
Restart and switch on:
sudo systemctl restart containerd sudo systemctl enable containerd sudo systemctl status containerd
SystemdCgroup = true → kubelet and containerd use the same cgroup driver (systemd). This is now considered best practice.
3. Install kubeadm/kubelet/kubectl 1.34 (all nodes)
According to the official documentation for v1.34:
3.1. Repo packages
At each node:
sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl gpg
3.2. GPG key and pkgs.k8s.io repository (v1.34)↵↵Ubuntu 22.04 already has /etc/apt/keyrings, but if it doesn't:
sudo mkdir -p -m 755 /etc/apt/keyrings
Repository key:
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.34/deb/Release.key \
| sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
Add repo specifically for Kubernetes 1.34:
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.34/deb/ /" \
| sudo tee /etc/apt/sources.list.d/kubernetes.list
3.3. Installing kubeadm/kubelet/kubectl
sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl
Repository v1.34 ensures that branch 1.34.x is installed. apt-mark hold will prevent accidental updates to other minor versions during apt upgrade.
(Not required, but you can enable kubelet immediately; it will run and wait for kubeadm init / join):
sudo systemctl enable --now kubelet
5. Control-plane initialisation (cp1 only)
Now we create a cluster on cp1.
5.1. kubeadm init with pod-CIDR for Calico
Calico uses 192.168.0.0/16 by default. For convenience, we will use the same:
On cp1:
sudo kubeadm init \ --apiserver-advertise-address=10.0.0.10 \ --pod-network-cidr=192.168.0.0/16
--apiserver-advertise-address— IP cp1, which everyone will use to access the API.--pod-network-cidr— The IP range for pods that Calico will use.
At the end, kubeadm will output:
- message about successful initialisation;
- The command kubeadm join ... — be sure to copy it somewhere (we will use it on the workers).
5.2. Configuring kubectl for the sadmin user (cp1)
Currently, kubeconfig is located in /etc/kubernetes/admin.conf and belongs to root.
On cp1 under sadmin:
mkdir -p $HOME/.kube sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config
Verification:
kubectl get nodes
Until the network is up, the node may be NotReady — this is normal.
6. Install Calico 3.31 (cp1 only)
We take the latest Calico 3.31.1 from the official on-prem instructions.
6.1. Tigera Operator + CRD
On cp1:
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.31.1/manifests/operator-crds.yaml kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.31.1/manifests/tigera-operator.yaml
This sets the Calico operator, which then rolls out the necessary components across the nodes itself.
6.2. Calico configuration (custom-resources)
By default, we use iptables-dataplane (without eBPF, so as not to complicate things for now).
On cp1:
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.31.1/manifests/custom-resources.yaml kubectl create -f custom-resources.yaml
(If you want eBPF in the future, there is also custom-resources-bpf.yaml.)
6.3. Verify that Calico has started
On cp1:
watch kubectl get tigerastatus
We are waiting until everything is AVAILABLE=True:
NAME AVAILABLE PROGRESSING DEGRADED SINCE calico True False False ... ippools True False False ... ...
And let's look at the pods:
kubectl get pods -n calico-system kubectl get pods -n kube-system kubectl get nodes
cp1 should transition to Ready status.
7. Make the control plane "clean" (without workload pods)
By default, in 1.34, the control plane can accept normal pods (taint is not set automatically).
You don't need this, so let's set taint right away:
On cp1:
kubectl taint nodes cp1 node-role.kubernetes.io/control-plane=:NoSchedule
Verification:
kubectl describe node cp1 | grep -i Taint
It should be something like:
Taints: node-role.kubernetes.io/control-plane:NoSchedule
Now the scheduler will NOT place regular pods on cp1. Only system components will remain there (kube-apiserver, etcd, kube-controller-manager, scheduler, Calico DaemonSet, etc.).
8. Connect worker1 and worker2
8.1. Obtain the join command (if lost)
If you did not save the output of kubeadm init, on cp1:
sudo kubeadm token create --print-join-command
Example:
kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:xxxxxxxx...
8.2. Perform a join on worker1 / worker2↵↵On worker1:
sudo kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:xxxxxxxx...
On worker2:
sudo kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:xxxxxxxx...
Here, a token and CA hash are used to enable workers to securely join the cluster.
After that, on cp1:
kubectl get nodes
Expected result:
NAME STATUS ROLES AGE VERSION cp1 Ready control-plane 20m v1.34.x worker1 Ready <none> 5m v1.34.x worker2 Ready <none> 3m v1.34.x
If workers are NotReady for some time, wait until kube-proxy + Calico catch up and start.
9. Verification: only on worker nodes
Let's create a test deployment:
On cp1:
kubectl create deployment nginx --image=nginx --replicas=3 kubectl expose deployment nginx --port=80 --type=NodePort
Let's see:
kubectl get pods -o wide
Все pod’ы nginx должны сидеть на worker1 и worker2, но не на cp1.
Если вдруг увидишь под на cp1, значит taint не применился → ещё раз:
kubectl taint nodes cp1 node-role.kubernetes.io/control-plane=:NoSchedule --overwrite kubectl rollout restart deploy nginx
✅ 1. Как удалить эти pod’ы (Deployment + Service)
Ты создал:
- Deployment: nginx
- Service: nginx
Чтобы удалить:
kubectl delete service nginx
Удалить Deployment:
kubectl delete deployment nginx
Проверяем:
kubectl get pods kubectl get svc
📌 Если хочешь удалить ВСЁ, что относится к nginx, одной командой:
kubectl delete deploy,svc nginx