HA Kubernetes cluster with containerd
Prerequisites:
- 2 Ubuntu20.04 LoadBalancer node’s
- 3 Ubuntu20.04 Kubernetes master node’s
- 2 Ubuntu20.04 Kubernetes worker node’s
Generate TLS certificates:
SSH into one of the master-node and run:
1
2
3
4
5
6
7
8
9
| {
mkdir certs
cd certs
wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
chmod +x cfssl*
sudo mv cfssl_linux-amd64 /usr/local/bin/cfssl
sudo mv cfssljson_linux-amd64 /usr/local/bin/cfssljson
}
|
Create a Certificate Authority (CA):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| {
cat > ca-config.json <<EOF
{
"signing": {
"default": {
"expiry": "8766h"
},
"profiles": {
"kubernetes": {
"expiry": "8766h",
"usages": ["signing","key encipherment","server auth","client auth"]
}
}
}
}
EOF
cat > ca-csr.json <<EOF
{
"CN": "Kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "IE",
"L": "Cork",
"O": "Kubernetes",
"OU": "CA",
"ST": "Cork Co."
}
]
}
EOF
cat > kubernetes-csr.json <<EOF
{
"CN": "Kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "IE",
"L": "Cork",
"O": "Kubernetes",
"OU": "CA",
"ST": "Cork Co."
}
]
}
EOF
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
}
|
Create TLS certificates:
Set <load-balancer-ips>
,<master-node-ips>
as per your configuration:
1
2
3
4
5
6
7
8
9
| {
cfssl gencert \
-ca=ca.pem \
-ca-key=ca-key.pem \
-config=ca-config.json \
-hostname=<load-balancer-vip>,<master01-ip>,<master02-ip>,<master03-ip>,<load01-balancer-ip>,<load02-balancer-ip>,127.0.0.1,kubernetes.default \
-profile=kubernetes kubernetes-csr.json | \
cfssljson -bare kubernetes
}
|
Setup kubernetes master & worker nodes:
Run on all master & worker nodes:
Load Kerenel Modules
1
2
3
4
5
6
7
8
9
| {
cat << EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
}
|
Add Kernel Settings
1
2
3
4
5
6
7
8
9
| {
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sysctl --system
}
|
Install runtime: containerd
1
2
3
4
5
6
7
| {
wget https://github.com/containerd/containerd/releases/download/v1.7.2/containerd-1.7.2-linux-amd64.tar.gz
tar Cxzvf /usr/local containerd-1.7.2-linux-amd64.tar.gz
rm containerd-1.7.2-linux-amd64.tar.gz
mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
}
|
1
2
3
4
| {
mkdir -pv /usr/local/lib/systemd/system/
wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service -O /usr/local/lib/systemd/system/containerd.service
}
|
1
2
3
4
5
6
| {
systemctl daemon-reload
systemctl enable --now containerd
systemctl start containerd
systemctl status containerd
}
|
1
2
3
4
| #Set the data path for worker nodes only if your using a different data disk
vim /etc/containerd/config.toml
root = "/data/containerd"
:wq!
|
Disable swap
1
| swapoff -a; sed -i '/swap/d' /etc/fstab
|
Install runc
1
2
3
4
| {
wget https://github.com/opencontainers/runc/releases/download/v1.1.8/runc.amd64
install -m 755 runc.amd64 /usr/local/sbin/runc
}
|
Installing CNI plugins
1
2
3
4
5
| {
wget https://github.com/containernetworking/plugins/releases/download/v1.3.0/cni-plugins-linux-amd64-v1.3.0.tgz
mkdir -p /opt/cni/bin
tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.3.0.tgz
}
|
Install cricctl
1
2
3
4
5
6
| {
VERSION="v1.30.0" # change this based on your k8s version
curl -L https://github.com/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-${VERSION}-linux-amd64.tar.gz --output crictl-${VERSION}-linux-amd64.tar.gz
sudo tar zxvf crictl-$VERSION-linux-amd64.tar.gz -C /usr/bin
rm -f crictl-$VERSION-linux-amd64.tar.gz
}
|
Set containerd as default runtime for crictl
1
2
3
4
5
6
7
8
9
| {
cat << EOF | sudo tee /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 2
debug: false
pull-image-on-create: false
EOF
}
|
Add Apt repository & Install Kubernetes components:
1
2
3
4
5
6
7
8
| {
apt-get install -y apt-transport-https ca-certificates curl gpg
mkdir /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
cat << EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /
EOF
}
|
1
2
3
4
5
| {
apt-get update
apt-get install -y kubelet=1.30.6-1.1 kubeadm=1.30.6-1.1 kubectl=1.30.6-1.1
}
|
ETCD cluster creation[on all etcd nodes]
:
Note: Copy ca.pem, kubernetes.pem, kubernetes-key.pem
to other master nodes.
1
2
3
4
5
6
7
| {
declare -a NODES=(192.168.56.101 192.168.56.102 192.168.56.103)
for node in ${NODES[@]}; do
scp ca.pem kubernetes.pem kubernetes-key.pem root@$node:
done
}
|
1
2
3
4
5
| {
mkdir -pv /etc/etcd /data/etcd
mv ca.pem kubernetes.pem kubernetes-key.pem /etc/etcd/
ll /etc/etcd/
}
|
Download etcd & etcdctl binaries from Github:
1
2
3
4
5
6
7
| {
ETCD_VER=v3.5.9
wget -q --show-progress "https://github.com/etcd-io/etcd/releases/download/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz"
tar zxf etcd-${ETCD_VER}-linux-amd64.tar.gz
mv etcd-${ETCD_VER}-linux-amd64/etcd* /usr/local/bin/
rm -rf etcd*
}
|
Create systemd unit file for etcd service:
Set NODE_IP to the correct IP of the machine where you are running this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| {
NODE_IP="k8s-master-1"
ETCD1_IP="k8s-master-1"
ETCD2_IP="k8s-master-2"
ETCD3_IP="k8s-master-3"
cat <<EOF >/etc/systemd/system/etcd.service
[Unit]
Description=etcd
[Service]
Type=notify
ExecStart=/usr/local/bin/etcd \\
--name ${NODE_IP} \\
--cert-file=/etc/etcd/kubernetes.pem \\
--key-file=/etc/etcd/kubernetes-key.pem \\
--peer-cert-file=/etc/etcd/kubernetes.pem \\
--peer-key-file=/etc/etcd/kubernetes-key.pem \\
--trusted-ca-file=/etc/etcd/ca.pem \\
--peer-trusted-ca-file=/etc/etcd/ca.pem \\
--peer-client-cert-auth \\
--client-cert-auth \\
--initial-advertise-peer-urls https://${NODE_IP}:2380 \\
--listen-peer-urls https://${NODE_IP}:2380 \\
--advertise-client-urls https://${NODE_IP}:2379 \\
--listen-client-urls https://${NODE_IP}:2379,https://127.0.0.1:2379 \\
--initial-cluster-token etcd-cluster-0 \\
--initial-cluster ${ETCD1_IP}=https://${ETCD1_IP}:2380,${ETCD2_IP}=https://${ETCD2_IP}:2380,${ETCD3_IP}=https://${ETCD3_IP}:2380 \\
--initial-cluster-state new \\
--data-dir=/data/etcd
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
}
|
Enable and Start etcd service:
1
2
3
4
5
6
| {
systemctl daemon-reload
systemctl enable --now etcd
systemctl start etcd
systemctl status etcd
}
|
Verify Etcd cluster status:
1
2
3
4
5
6
7
| {
ETCDCTL_API=3 etcdctl member list \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/etcd/kubernetes.pem \
--cert=/etc/etcd/kubernetes.pem \
--key=/etc/etcd/kubernetes-key.pem
}
|
Example output:
1
2
3
| 25ef0cb30a2929f1, started, k8s-master-1, https://k8s-master-1:2380, https://k8s-master-1:2379, false
5818a496a39840ca, started, k8s-master-2, https://k8s-master-2:2380, https://k8s-master-2:2379, false
c669cf505c64b0e8, started, k8s-master-3, https://k8s-master-3:2380, https://k8s-master-3:2379, false
|
Repeat the above spets on other master nodes by replacing the ip address with respect to node ip address.
Initializing Master Node 1:
Create configuration file :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| {
HAPROXY_IP="haproxy-ip"
MASTER01_IP="k8s-master-1"
MASTER02_IP="k8s-master-2"
MASTER03_IP="k8s-master-3"
cat <<EOF > config.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: stable
apiServer:
certSANs:
- ${HAPROXY_IP}
ExtraArgs:
apiserver-count: "3"
controlPlaneEndpoint: "${HAPROXY_IP}:6443"
etcd:
external:
endpoints:
- https://${MASTER01_IP}:2379
- https://${MASTER02_IP}:2379
- https://${MASTER03_IP}:2379
caFile: /etc/etcd/ca.pem
certFile: /etc/etcd/kubernetes.pem
keyFile: /etc/etcd/kubernetes-key.pem
networking:
podSubnet: 10.244.0.0/16
apiServerExtraArgs:
apiserver-count: "3"
EOF
}
|
Initializing the node:
1
| kubeadm init --config=config.yaml
|
In order to make kubectl work for non-root users execute the following on master01:
1
2
3
4
5
| {
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
}
|
Option 1: Activate the flannel CNI plugin:
1
2
3
| kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
*If you use custom podCIDR (not 10.244.0.0/16) you first need to download the above manifest and modify the network to match your one*
|
Option 2: Activate the calico CNI plugin:
1
2
3
4
5
6
7
8
9
10
11
12
| #Download the custom resources necessary to configure Calico
curl https://raw.githubusercontent.com/projectcalico/calico/v3.25.0/manifests/calico.yaml -O
#Update the CALICO_IPV4POOL_CIDR block in calico.yaml
- name: CALICO_IPV4POOL_CIDR
value: '10.244.0.0/16'
# Install calico
kubectl create -f calico.yaml
# Confirm that all of the pods are running with the following command.
watch kubectl get pods -n kube-system
|
Note: create a directory and store the custom-resources.yaml file for future reference
Option 3: Activate the Cilium CNI plugin:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| #install Cilium cli
{
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
CLI_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
}
#Install cilium
cilium install --version 1.14.0
#validate the installation
cilium status --wait
#connectivity test
cilium connectivity test
|
Initializing Master Node 2:
- Copy the kubernetets certs
(/etc/kubernetes/pki/*)
from the master node 1 - Copy the
config.yaml
from master node01
1
2
3
4
5
| {
mkdir -p /etc/kubernetes/pki
scp /etc/kubernetes/pki/* root@k8smaster2:/etc/kubernetes/pki/
scp /etc/kubernetes/pki/* root@k8smaster3:/etc/kubernetes/pki/
}
|
1
| kubeadm init --config=config.yaml
|
Adding Worker nodes:
Get the join command from master node and run it on worker nodes to join the cluster:
1
| kubeadm token create --print-join-command
|
When you run kubeadm init you will get the join command as follows , you need to run this on all worker nodes.
1
| kubeadm join 192.168.X.X:6443 --token 85iw5v.ymo1wqcs9mrqmnnf --discovery-token-ca-cert-hash sha256:4710a65a4f0a2be37c03249c83ca9df2377b4433c6564db4e61e9c07f5a213dd
|
Use the below command to label the worker nodes:
1
| kubectl label nodes k8sworker1 kubernetes.io/role=k8sworker1
|
Reference Links: