Kubernetes è un sistema open source di orchestrazione di containers che permette l’automazione del deploy, dello scaling e della gestione del lifecycle.
Un cluster K8s è formato da diversi componenti, tra i principali ci sono i nodi “Control Plane” che si occupano del controllo del cluster e della gestione di “Etcd” (uno “key-value data store” condiviso) e i “Working Nodes” dove vengono eseguiti i pods.
In piccoli cluster è possibile utilizzare i nodi “Control Plane” come nodi di carico (Working Nodes) ed eseguire i pods su di essi.
I pods sono le istanze dei containers, ad esempio è possibile istanziare uno o più pods partendo da un’immagine Docker.
Il Container Runtime è il componente repsonsabile del lifecycle dei container e tipicamente viene utilizzato CRI-O (Docker è sconsigliato).
Un’altro componente fondamentale è la CNI (Container Network Interface) poiché ha in carico la gestione e la segmentazione della rete dei pods.
La CNI viene installata in seguito alla inizializzazione del cluster Kubernetes ed è possibile scegliere tra diverse opzioni.
In questo articolo verrà mostrato come installare un cluster K8s a tre nodi (Control Plane + Working Node) su Debian 12 e verrà utilizzato Calico come CNI.
PREREQUISTI
Per garantire l’alta affidabilità del servizio API dei nodi Control Plane è necessario un reverse proxy, in questa guida verranno mostrate le configurazioni per HAProxy.
Calico utilizza il BGP per diffondere le rotte tra i nodi del cluster ed opzionalmente con dei routers esterni, perciò se si vuole contattare il cluster K8s dall’esterno è necessario utilizzare un firewall compatibile con il protocollo BGP.
PREREQUISTI DEI NODI
Aggiornare il sistema:
0
1
|
apt update
apt upgrade -y
|
Installare alcuni pacchetti necessari:
0 |
apt install -y apt-transport-https ca-certificates curl gpg ifupdown2
|
Disabilitare lo Swap:
0 |
swapoff -a
|
Commentare nel file /etc/fstab la riga relativa allo swap:
0 |
#/dev/mapper/k8s01--vg-swap_1 none swap sw 0 0 |
Ricaricare il file “fstab”:
0 |
systemctl daemon-reload
|
CONFIGURAZIONE DELLA RETE
Disabilitare IPv6 e abilitare IPv4 forwarding:
0
1
2
|
echo "net.ipv6.conf.all.disable_ipv6=1" >> /etc/sysctl.conf
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -p
|
Abilitare il modulo del kernel br_netfilter:
0
1
|
modprobe br_netfilter
echo "br_netfilter" >> /etc/modules
|
Modificare il file /etc/network/interfaces (modificare i nomi delle interfacce di rete ed assegnare IP univoci ai nodi):
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
source /etc/network/interfaces.d/*
# The loopback network interface
auto lo
iface lo inet loopback
# The primary network interface
auto ens18
iface ens18 inet static
address 10.10.150.181/24
gateway 10.10.150.1
mtu 1500
# BGP Network
auto ens20
iface ens20 inet static
address 10.10.170.181/24
mtu 1500
|
Ricaricare la rete:
0 |
ifreload -a
|
VERIFICA DELL’UNIVOCITÁ DEGLI ID DEI NODI
Eseguire i seguenti comandi su tutti i nodi e verificare che gli identificativi siano univoci.
Verificare che i MAC addresses dei nodi siano univoci:
0 |
ip link
|
Verificare che gli UUIDs dei nodi siano univoci:
0 |
cat /sys/class/dmi/id/product_uuid
|
CONFIGURAZIONE NTP
Affinché il cluster K8s possa funzionare è necessario che l’orologio di sistema dei nodi sia sincronizzato.
Per mantenere l’orologio sincronizzato è sufficiente installare un servizio NTP e configurare il medesimo peer (ad esempio il firewall) su tutti i nodi.
Installre il pacchetto “chrony”:
0 |
apt install chrony -y
|
Commentare nel file /etc/chrony/chrony.conf:
0 |
#pool 2.debian.pool.ntp.org iburst |
Aggiungere nel file /etc/chrony/chrony.conf (sostituire l’IP con l’IP del proprio peer):
0
1
|
# Use custom NTP pool
pool 10.10.150.1
|
Riavviare il servizio:
0 |
systemctl restart chrony.service
|
GENERAZIONE DELLE CHIAVI SSH
Può essere utile collegarsi in SSH tra un nodo e l’altro per la gestione o per lo scambio di files (SFTP), per l’autenticazione è consigliato l’utilizzo di chiavi SSH.
Generare una coppia di chiavi SSH (da eseguire su tutti i nodi):
0 |
ssh-keygen
|
Aggiungere al file /root/.ssh/authorized_keys di tutti i nodi le chiavi degli altri nodi.
HARDENING SSH
Questo step è opzionale e serve a limitare l’accesso in SSH dalla rete BGP.
Dal momento che viene aggiunta la direttiva ListenAddress nella configurazione del demone sshd, è necessario che il servizio venga avviato dopo che configurazione degli IP sia stata completata.
Un’alternativa consiste nel modificare un parametro del kernel per consentire il bind su IP non configurati su nessuna interfaccia, ma questo approcio non verrà preso in considerazione in questo articolo.
Creare la directory per le configurazioni aggiuntive del servizio ssh.service:
0 |
mkdir -p /etc/systemd/system/ssh.service.d
|
Creare il file “/etc/systemd/system/ssh.service.d/override.conf” con il seguente contenuto:
0
1
|
[Unit]
After=network-online.target auditd.service
|
Modificare il file /lib/systemd/system/systemd-networkd-wait-online.service:
0 |
ExecStart=/lib/systemd/systemd-networkd-wait-online --interface=ens18 --interface=ens19 --interface=ens20
|
Abilitare il servizio systemd-networkd-wait-online:
0 |
systemctl enable systemd-networkd-wait-online
|
Ricaricare i demoni di systemd:
0 |
systemctl daemon-reload
|
Aggiungere la direttiva necessaria per limitare l’accesso:
0 |
echo "ListenAddress $(ip a | grep -v 127. | grep inet | head -n1 | tr -s ' ' | cut -d ' ' -f3 | cut -d '/' -f1)" > /etc/ssh/sshd_config.d/hardening.conf
|
Riavviare il servizio ssh:
0 |
systemctl restart ssh.service
|
CONFIGURAZIONE DELLE REPOSITORIES DI KUBERNETES E CRI-O
Aggiungere le repositories di Kubernetes:
0
1
|
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /" | tee /etc/apt/sources.list.d/kubernetes.list
|
Aggiungere le repositories di CRI-O
0
1
|
curl -fsSL https://pkgs.k8s.io/addons:/cri-o:/prerelease:/main/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/cri-o-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/cri-o-apt-keyring.gpg] https://pkgs.k8s.io/addons:/cri-o:/prerelease:/main/deb/ /" | tee /etc/apt/sources.list.d/cri-o.list
|
Aggiornare le repositories:
0 |
apt update
|
INSTALLAZIONE DEL CONTAINER RUNTIME (CRI-O)
Installare il pacchetto cri-o:
0 |
apt install cri-o -y
|
Marcare come hold il pacchetto di CRI-O per evitare che venga aggiornato:
0 |
apt-mark hold cri-o
|
Abilitare il servizio:
0 |
systemctl start crio.service
|
INSTALLAZIONE DI KUBERNETES
Installare i pacchetti necessari:
0 |
apt install kubelet kubeadm kubectl socat -y
|
Marcare come hold di pacchetti di Kubernetes per evitare che vengano aggiornati:
0 |
apt-mark hold kubelet kubeadm kubectl etcd-client
|
Abilitare il servizio:
0 |
systemctl enable --now kubelet
|
CONFIGURAZIONE DELL’AUTOCOMPLETAMENTO PER BASH
Può essere utile utilizzare l’autocomplemento quando si utilizza il comando kubectl, per abilitarlo eseguire i seguenti comandi:
0
1
2
3
4
5
6
7
8
9
|
mkdir -p ~/.kube
source <(kubectl completion bash)
kubectl completion bash > ~/.kube/completion.bash.inc
printf "
# kubectl shell completion
source '$HOME/.kube/completion.bash.inc'
# kubectl admin.conf
export KUBECONFIG=/etc/kubernetes/admin.conf
" >> $HOME/.bash_profile
source $HOME/.bash_profile
|
CONFIGURAZIONE DI HAPROXY
Per ottenere l’alta affidabilità del controller Kubernetes è necessario configurare un load balancer, come ad esempio HAProxy, che gestisca le richieste alle API.
Esempio di configurazione di HAProxy per il controller Kubernet (nel file /etc/haproxy/haproxy.cfg):
0
1
2
3
4
5
6
7
8
9
10
11
12
|
frontend fe_k8s_control
mode tcp
option tcplog
bind :6443 name k8s_control
default_backend be_k8s_control
backend be_k8s_control
mode tcp
balance roundrobin
option log-health-checks
server k8s01 10.10.150.181:6443 check
server k8s02 10.10.150.182:6443 check
server k8s03 10.10.150.183:6443 check
|
Per verificare la configurazione:
0 |
haproxy -c -f /etc/haproxy/haproxy.cfg
|
Per applicare la configurazione:
0 |
systemctl reload haproxy.service
|
CREAZIONE DEL CLUSTER K8S
ATTENZIONE
: Le configurazioni effettuate tramite il comando kubeadm init sono difficilmente modificabili dopo la creazione del cluster, perciò si consiglia una particolare attenzione nella scelta di quest’ultime.
Configurazioni iniziali:
0
1
2
3
|
control-plane-endpoint (es. k8s-control.pizza.local:6443): L'FQDN e la porta su cui contattare il frontend di HAProxy per il controller K8s
pod-network-cidr (es. 10.190.0.0/16): La rete che tramite la CNI sarà possibiile segmentare ed utilizzerla per i pods
service-cidr (es. 10.190.0.0/16): La rete che verrà utilizzata per i servizi interni del cluster (è sconsigliato renderla accessibile dall'esterno)
service-dns-domain (es. pizza.local): Il dominio del cluster, di default è: cluster.local
|
Creare il cluster eseguendo questo comando su un solo nodo:
0 |
kubeadm init --control-plane-endpoint "k8s-control.pizza.local:6443" --upload-certs --pod-network-cidr 10.190.0.0/16 --service-cidr 10.180.0.0/16 --service-dns-domain pizza.k8s
|
Sugli altri nodi effettuare il join tramite le istruzioni mostrate in fase di creazione del cluster:
0 |
kubeadm join k8s-control.pizza.local:6443 --token TOKEN-HERE --discovery-token-ca-cert-hash sha256:SHA256-HERE --control-plane --certificate-key KEY-HERE
|
Se il comando non dovesse funzionare è possibile che il token per il join sia scaduto, per elencare i token validi eseguire (su un nodo già appartenente al cluster):
0 |
kubeadm token list
|
Se l’output risulta vuoto, allora è necessario rigenerare il token con il seguente comando:
0 |
kubeadm token create
|
Se invece si ottiene un errore durante il download dei ceritificati, è necessario rigenerarli e sostituire la certificate key nel comando di join.
Eseguire su un nodo già appartenente al cluster:
0 |
kubeadm init phase upload-certs --upload-certs
|
Per autenticarsi con le API di Kubernetes quando si utilizza l’utente root è necessario configurare la variabile d’ambiente KUBECONFIG:
0
1
|
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> /root/.profile
export KUBECONFIG=/etc/kubernetes/admin.conf
|
DISABILITARE L’ISOLAMENTO
Nel caso di piccoli cluster K8s è possibile disabilitare l’isolamento dei nodi control plane e permettere di esseguire i Pod su quest’ultimi.
Dopo aver aggiunto tutti i nodi, eseguire su un solo nodo:
0 |
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
|
CONFIGURAZIONE DI COREDNS
CoreDNS
è il servizio DNS del cluster Kubernetes.
Per modificare la configurazione è sufficiente modificare la sua ConfigMap e ricreare i pods.
Creare il file coredns_cm.yaml:
0
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
|
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes pizza.k8s in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
policy sequential
}
cache 30
loop
reload
loadbalance
}
|
Modificare il dominio pizza.k8s con il dominio del cluster.
Tramite la seguente configurazione è possibile gestire il comportamento di CoreDNS quando deve risolvere domini esterni al cluster:
0
1
2
3
|
forward . /etc/resolv.conf {
max_concurrent 1000
policy sequential
}
|
Con questa configurazione i nomi DNS vengono risolti utilizzando i servers indicati nel file /etc/hosts degli hosts scelti in modo sequenziale.
Se non viene specificata una policy, i servers vengono scelti in modo randomico.
CONFIGURAZIONE DELLE ROTTE STATICHE
Per evitare situazioni di traffico assimmetrico è necessario configurare che il traffico proveniente dai pods utilizzi come gateway il router sulla rete BGP.
Nei seguenti comandi e nello script sostituire gli IP e le reti in base alle proprie esigenze considerando che negli esempi essi corrispondono a:
- 10.10.150.181, 10.10.150.182, 10.10.150.183: sono gli IP che utilizzo per la gestione del cluster e l’accesso ai nodi in SSH
- 10.10.170.181, 10.10.170.182, 10.10.170.183: sono gli IP sulla rete BGP
- 10.10.170.1: è l’IP del gateway sulla rete BGP che utlizzo anche come peer BGP
- 10.190.0.0/16: è la rete pods del mio cluster
Aggiugere una routing table dedicata alle rotte necessarie per Kubernetes:
0
1
|
echo -e "#\n# Custom tables\n#" >> /etc/iproute2/rt_tables
echo -e "77\tk8s" >> /etc/iproute2/rt_tables
|
Eseguire i seguenti comandi per aggiungere le rotte necessarie (sostituire ens20 con il nome dell’interfaccia sulla rete BGP):
0
1
2
3
4
5
6
7
8
9
10
11
12
|
# Routes
ip route add default via 10.10.170.1 dev ens20 table k8s
# LAN Network
ip rule add to 10.10.150.181 lookup main priority 100
ip rule add to 10.10.150.182 lookup main priority 100
ip rule add to 10.10.150.183 lookup main priority 100
# BGP Network
ip rule add to 10.10.170.181 lookup main priority 100
ip rule add to 10.10.170.182 lookup main priority 100
ip rule add to 10.10.170.183 lookup main priority 100
# K8s Network
ip rule add to 10.190.0.0/16 lookup main priority 100
ip rule add from 10.190.0.0/16 lookup k8s priority 200
|
Aggiungere il file /etc/network/if-up.d/k8s-up (sostituire ens20 con il nome dell’interfaccia sulla rete BGP):
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#!/bin/sh
# filename: k8s-up
if [ "$IFACE" = ens20 ]; then
# Routes
ip route add default via 10.10.170.1 dev ens20 table k8s
# LAN Network
ip rule add to 10.10.150.181 lookup main priority 100
ip rule add to 10.10.150.182 lookup main priority 100
ip rule add to 10.10.150.183 lookup main priority 100
# BGP Network
ip rule add to 10.10.170.181 lookup main priority 100
ip rule add to 10.10.170.182 lookup main priority 100
ip rule add to 10.10.170.183 lookup main priority 100
# K8s Network
ip rule add to 10.190.0.0/16 lookup main priority 100
ip rule add from 10.190.0.0/16 lookup k8s priority 200
fi
|
Aggiungere il file /etc/network/if-down.d/k8s-down (sostituire ens20 con il nome dell’interfaccia sulla rete BGP):
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#!/bin/sh
# filename: k8s-down
if [ "$IFACE" = ens20 ]; then
# LAN Network
ip rule del to 10.10.150.181 lookup main priority 100
ip rule del to 10.10.150.182 lookup main priority 100
ip rule del to 10.10.150.183 lookup main priority 100
# BGP Network
ip rule del to 10.10.170.181 lookup main priority 100
ip rule del to 10.10.170.182 lookup main priority 100
ip rule del to 10.10.170.183 lookup main priority 100
# K8s Network
ip rule del to 10.190.0.0/16 lookup main priority 100
ip rule del from 10.190.0.0/16 lookup k8s priority 200
fi
|
Rendere i file eseguibili:
0 |
chmod +x /etc/network/if-up.d/k8s-up /etc/network/if-down.d/k8s-down
|
CONFIGURAZIONE DI CALICO
ATTENZIONE
: Quando si applica un file YAML tramite “kubectl apply” o “kubectl create”, esso viene applicato a livello di cluster, perciò gli step mostrati in questo paragrafo vanno eseguiti solamente su un nodo.
Creare le risorse necessarie all’operatore per Calico (Tigera Operator):
0 |
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/tigera-operator.yaml
|
Effettuare il download del file custom-resources.yaml da GitHub:
0 |
curl https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/custom-resources.yaml -O
|
Modificare la opzioni della rete di default per i pods nel file custom-resources.yaml:
0
1
2
3
4
5
6
7
8
9
|
calicoNetwork:
bgp: Enabled
ipPools:
- name: default-ipv4-ippool
blockSize: 25
cidr: 10.190.1.0/22
encapsulation: None
natOutgoing: Disabled
disableBGPExport: false
nodeSelector: all()
|
La rete indicata con il parametro cidr deve essere una subnet della pod network configurata in fase di inizializzazione del cluter.
Il NAT può essere disabilitato tramite il parametro natOutgoing, poiché viene utilizzato il BGP per esportare le rotte.
Il parametro disableBGPExport viene mantenuto false per effettuare la configurazione manuale del BGP.
Applicare le risorse:
0 |
kubectl apply -f custom-resources.yaml
|
Creare il file bgp.yaml con le configurazioni relative al BGP:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
---
apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
name: default
spec:
logSeverityScreen: Info
nodeToNodeMeshEnabled: true
asNumber: 65000
listenPort: 178
bindMode: NodeIP
---
apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
name: calico-bgppeer-fw01
spec:
peerIP: 10.17.170.1
asNumber: 65000
|
Sostituire i due parametri asNumber con il numero AS della propria rete locale.
Sostituire l’IP indicato nel parametro peerIP con l’IP del router BGP (il firewall); è possibile aggiungere più routers indicando più BGPPeer.
Applicare le risorse:
0 |
kubectl apply -f bgp.yaml
|
Creare il file podnetpools.yaml con le reti dei pods, in seguito un esempio di una rete:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
---
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: custom-pod-network
spec:
cidr: 10.190.4.0/24
blockSize: 27
ipipMode: Never
vxlanMode: Never
natOutgoing: false
disableBGPExport: false
disabled: false
nodeSelector: all()
allowedUses:
- Workload
- Tunnel
|
Sostituire custom-pod-network con il nome della rete e modificare il valore del parametro cidr indicando la rete in formato CIDR.
La rete deve essere una subnet della pod network configurata in fase di inizializzazione del cluter.
Applicare le risorse:
0 |
kubectl apply -f podnetpools.yaml
|
A questo punto le rotte BGP dovrebbero venir diffuse verso i routers indicati come peers.
Assicurarsi di aver configurato il firewall ed eventuali altri routers per ricevere le rotte dai nodi K8s.
INSTALLAZIONE DELL’UTILITY CALICOCTL
Per sfruttare a pieno le funzionalità di Calico è possibile scaricare l’eseguibile calicoctl:
0 |
curl -L https://github.com/projectcalico/calico/releases/download/v3.28.2/calicoctl-linux-amd64 -o /usr/local/bin/calicoctl
|
Rendere il file eseguibile:
0 |
chmod +x /usr/local/bin/calicoctl
|
Verificare il funzionamento dell’utility:
0
1
2
3
4
|
# calicoctl version
Client Version: v3.28.2
Git commit: 9a96ee39f
Cluster Version: v3.28.2
Cluster Type: typha,kdd,k8s,operator,bgp,kubeadm
|
ASSEGNARE GLI IP POOLS AI NAMESPACES
Tramite Calico è possibile segmentare la rete in modo che i pods di namespaces diversi appartengano a reti diverse.
Tramite le Network Policies è possibile evitare che le diverse reti comunichino tra di loro e consentire solo i flussi richiesti, però è al di fuori dallo scopo di questo articolo.
Per assegnare al namespace default l’IPPool chiamato default-ipv4-ippool eseguire:
0 |
kubectl annotate namespace default "cni.projectcalico.org/ipv4pools"='["default-ipv4-ippool"]'
|
Analogamente è possibile configurare un IPPool specifico per ogni namespace.
Per applicare le modifiche della rete è possibile rimuovere i pods tramite “kubectl delete” ed attendere che Kubernetes li ricrei.
DEPLOY DI NGINX
Per verificare il funzionamento del cluster K8s è possibile inizializzare un deployment di Nginx.
Creare il deployment utilizzando l’immagine nginx:
0 |
kubectl create deployment nginx-test --image=nginx
|
Modificare il deployment appena creato:
0 |
kubectl edit deployment nginx-test
|
Inserire l’annotazione utilizzata da Calico per assegnare il deployment ad una specifico IP Pool (sostituire custom-pod-network con il nome del pool):
0
1
2
3
4
5
6
7
8
9
10
|
template:
metadata:
labels:
app: my-app
annotations:
# Assign the pod to a specific Calico IPPool
"cni.projectcalico.org/ipv4pools": "[\"custom-pod-network\"]"
spec:
containers:
- name: my-container
image: nginx
|
Per applicare le modifiche alla rete è possibile modificare il numero di replicas prima a 0 e poi al numero desiderato:
0
1
|
kubectl scale deployment nginx-test --replicas=0
kubectl scale deployment nginx-test --replicas=3
|
Tramite il seguente comando è possibile visualizzare i pods nel namespace default ed il loro IP:
0 |
kubectl get pods -o wide -n default
|
0 commenti