Sempre più spesso i clienti si rivolgono a noi richiedendo l'accesso al cluster Kubernetes per poter accedere ai servizi all'interno del cluster: così da poter collegarsi direttamente a un database o a un servizio, per connettere un'applicazione locale con applicazioni all'interno del cluster…

Ad esempio, può sorgere la necessità di collegarsi dalla propria macchina locale a un servizio memcached.staging.svc.cluster.local. Offriamo questa possibilità tramite una VPN all'interno del cluster, a cui si collega il cliente. A questo scopo, annunciamo le sottoreti dei pod, dei servizi e inviamo il DNS del cluster al cliente. Così, quando il cliente cerca di collegarsi a un servizio memcached.staging.svc.cluster.local, la richiesta va al DNS del cluster e in risposta riceve l'indirizzo di quel servizio dalla rete dei servizi del cluster o l'indirizzo di un pod.
Configurando i cluster K8s con kubeadm, dove per default la sottorete del servizio è 192.168.0.0/16, e la rete dei pod è 10.244.0.0/16. Di solito tutto funziona bene, ma ci sono un paio di aspetti da considerare:
- La sottorete
192.168.*.*è spesso utilizzata nelle reti aziendali dei clienti, e ancora più spesso nelle reti domestiche degli sviluppatori. E lì possono sorgere conflitti: i router domestici operano in questa sottorete e la VPN spinge queste sottoreti dal cluster al cliente. - Abbiamo diversi cluster (cluster di produzione, stage e/o diversi cluster di sviluppo). Così, in tutti questi, per impostazione predefinita, ci saranno le stesse subnet per i pod e i servizi, il che crea grandi difficoltà nel gestire simultaneamente servizi in più cluster.
Da tempo abbiamo adottato la pratica di utilizzare subnet diverse per i servizi e i pod all'interno di un progetto — in generale, affinché tutti i cluster abbiano reti diverse. Tuttavia, ci sono molti cluster in uso che non vorremmo ricreare da zero, poiché in essi sono in esecuzione molti servizi, applicazioni stateful, e così via.
E quindi ci siamo chiesti: come si può cambiare la subnet in un cluster esistente?
Ricerca di soluzioni
La pratica più comune è ricreare tutti servizi di tipo ClusterIP. Come alternativa, anche questo:
Il seguente processo ha un problema: dopo che tutto è stato configurato, i pod si avviano con il vecchio IP come server DNS in /etc/resolv.conf.
Poiché non ho ancora trovato la soluzione, ho dovuto ripristinare l'intero cluster con kubeadm reset e inizializzarlo di nuovo.
Ma non è adatto a tutti... Ecco informazioni più dettagliate per il nostro caso:
- Si utilizza Flannel;
- Ci sono cluster sia in cloud che su hardware;
- Vorremmo evitare di ridistribuire tutti i servizi nel cluster;
- C'è la necessità di fare tutto con il minimo di problemi possibile;
- Versione di Kubernetes — 1.16.6 (anche se i passaggi successivi saranno analoghi per altre versioni);
- Il compito principale è quello di sostituire la sottorete di servizio nel cluster distribuito tramite kubeadm
192.168.0.0/16con172.24.0.0/16.
E così è successo che da tempo volevamo vedere cosa e come vengono memorizzati i dati in Kubernetes in etcd, e cosa si può fare con essi... E abbiamo pensato: «Perché non aggiornare semplicemente i dati in etcd, sostituendo i vecchi indirizzi IP (sottorete) con i nuovi??»
Cercando strumenti già pronti per lavorare con i dati in etcd, non abbiamo trovato nulla che risolvesse completamente la questione. (A proposito, se conoscete strumenti per lavorare con i dati direttamente in etcd — saremo grati per i link.) Tuttavia, un buon punto di partenza è stato (grazie ai suoi autori!).
Questo strumento è in grado di connettersi a etcd tramite certificati e leggere i dati da lì utilizzando i comandi ls, get, dump.
Aggiungiamo etcdhelper
Il pensiero successivo è logico: «Cosa ci impedisce di aggiungere questa funzionalità all'utility, consentendo la scrittura di dati in etcd?»
Si è trasformata in una versione modificata di etcdhelper con due nuove funzionalità changeServiceCIDR e changePodCIDR. Nel suo il codice può essere visualizzato .
Cosa fanno le nuove funzionalità? L'algoritmo changeServiceCIDR:
- crea un deserializzatore;
- compila un'espressione regolare per sostituire il CIDR;
- scorre tutti i servizi di tipo ClusterIP nel cluster:
- decodifica il valore da etcd in un oggetto Go;
- utilizza un'espressione regolare per sostituire i primi due byte dell'indirizzo;
- assegna all'indirizzo del servizio un IP dalla nuova sottorete;
- crea un serializzatore, trasforma l'oggetto Go in protobuf, scrive i nuovi dati in etcd.
Funzione changePodCIDR è sostanzialmente analoga changeServiceCIDR — solo che invece di modificare le specifiche dei servizi lo facciamo per il nodo e cambiamo .spec.PodCIDR alla nuova sottorete.
Pratica
Cambio di serviceCIDR
Il piano di attuazione del compito assegnato è molto semplice, ma comporta un downtime nel momento della ricreazione di tutti i pod nel cluster. Dopo aver descritto i passaggi fondamentali, condivideremo anche alcune idee su come minimizzare in teoria questo downtime.
Azioni preparatorie:
- installazione del software necessario e compilazione di etcdhelper patchato;
- backup di etcd e
/etc/kubernetes.
Breve piano d'azione per cambiare il serviceCIDR:
- modifica dei manifesti dell'apiserver e del controller-manager;
- riemissione dei certificati;
- modifica dei servizi ClusterIP in etcd;
- riavvio di tutti i pod nel cluster.
Di seguito è riportata la sequenza completa delle azioni in dettaglio.
1. Installiamo il client etcd per il dump dei dati:
apt install etcd-client2. Compiliamo etcdhelper:
- Installiamo golang:
GOPATH=/root/golang mkdir -p $GOPATH/local curl -sSL https://dl.google.com/go/go1.14.1.linux-amd64.tar.gz | tar -xzvC $GOPATH/local echo "export GOPATH="$GOPATH"" >> ~/.bashrc echo 'export GOROOT="$GOPATH/local/go"' >> ~/.bashrc echo 'export PATH="$PATH:$GOPATH/local/go/bin"' >> ~/.bashrc - Salviamo
etcdhelper.go, carichiamo le dipendenze, compiliamo:wget https://raw.githubusercontent.com/flant/examples/master/2020/04-etcdhelper/etcdhelper.go go get go.etcd.io/etcd/clientv3 k8s.io/kubectl/pkg/scheme k8s.io/apimachinery/pkg/runtime go build -o etcdhelper etcdhelper.go
3. Eseguiamo il backup di etcd:
backup_dir=/root/backup
mkdir ${backup_dir}
cp -rL /etc/kubernetes ${backup_dir}
ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.crt --key=/etc/kubernetes/pki/etcd/server.key --cert=/etc/kubernetes/pki/etcd/server.crt --endpoints https://192.168.199.100:2379 snapshot save ${backup_dir}/etcd.snapshot 4. Cambiamo la sottorete del servizio nei manifesti del piano di controllo di Kubernetes. Nei file /etc/kubernetes/manifests/kube-apiserver.yaml e /etc/kubernetes/manifests/kube-controller-manager.yaml modifichiamo il parametro --service-cluster-ip-range alla nuova sottorete: 172.24.0.0/16 invece di 192.168.0.0/16.
5. Poiché stiamo cambiando la sottorete del servizio, per la quale kubeadm rilascia certificati per l'apiserver (tra l'altro), è necessario riemetterli:
- Controlliamo a quali domini e indirizzi IP è stato rilasciato il certificato attuale:
openssl x509 -noout -ext subjectAltName </etc/kubernetes/pki/apiserver.crt X509v3 Subject Alternative Name: DNS:dev-1-master, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:apiserver, IP Address:192.168.0.1, IP Address:10.0.0.163, IP Address:192.168.199.100 - Prepareremo una configurazione minima per kubeadm:
cat kubeadm-config.yaml apiVersion: kubeadm.k8s.io/v1beta1 kind: ClusterConfiguration networking: podSubnet: "10.244.0.0/16" serviceSubnet: "172.24.0.0/16" apiServer: certSANs: - "192.168.199.100" # Indirizzo IP del nodo master - Rimuoveremo i vecchi crt e key, poiché senza di essi non verrà rilasciato il nuovo certificato:
rm /etc/kubernetes/pki/apiserver.{key,crt} - Rilasceremo nuovamente i certificati per l'API server:
kubeadm init phase certs apiserver --config=kubeadm-config.yaml - Verifichiamo che il certificato sia stato emesso per la nuova subnet:
openssl x509 -noout -ext subjectAltName </etc/kubernetes/pki/apiserver.crt X509v3 Subject Alternative Name: DNS:kube-2-master, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, IP Address:172.24.0.1, IP Address:10.0.0.163, IP Address:192.168.199.100 - Dopo aver rilasciato nuovamente il certificato dell'API server, riavvieremo il suo contenitore:
docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart - Rigeneriamo la configurazione per
admin.conf:kubeadm alpha certs renew admin.conf - Modifichiamo i dati in etcd:
./etcdhelper -cacert /etc/kubernetes/pki/etcd/ca.crt -cert /etc/kubernetes/pki/etcd/server.crt -key /etc/kubernetes/pki/etcd/server.key -endpoint https://127.0.0.1:2379 change-service-cidr 172.24.0.0/16Attenzione! In questo momento, la risoluzione dei nomi nel cluster smette di funzionare, poiché negli pod esistenti è registrato il vecchio indirizzo di CoreDNS (kube-dns), e kube-proxy ha modificato le regole iptables dalla vecchia sottorete a quella nuova. Di seguito nell'articolo sono descritti i possibili modi per ridurre al minimo il downtime.
/etc/resolv.confCorreggeremo i ConfigMap nello spazio dei nomi - kube-system
kubectl -n kube-system edit cm kubelet-config-1.16:— qui sostituiremoclusterDNS
con il nuovo indirizzo IP del servizio kube-dns:kubectl -n kube-system get svc kube-dnskubectl -n kube-system edit cm kubeadm-config.— correggeremodata.ClusterConfiguration.networking.serviceSubnet
Poiché l'indirizzo di kube-dns è cambiato, è necessario aggiornare la configurazione di kubelet su tutti i nodi:alla nuova sottorete. - kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
Rimane da riavviare tutti i pod nel cluster: - kubectl get pods --no-headers=true --all-namespaces |sed -r 's/(S+)s+(S+).*/kubectl --namespace 1 delete pod 2/e'
Riduzione del downtime
Idee su come minimizzare il downtime:
Dopo le modifiche ai manifest del control plane, creare un nuovo servizio kube-dns, ad esempio, con il nome
- kube-dns-tmp
e il nuovo indirizzoCreare172.24.0.10. - if
in etcdhelper, che non modificherà il servizio kube-dns.Sostituire in tutti i kubelet l'indirizzo - ClusterDNS
con il nuovo, mentre il vecchio servizio continuerà a funzionare insieme al nuovo.su uno nuovo, mentre il vecchio servizio continuerà a funzionare contemporaneamente al nuovo. - Aspettare che i pod con le applicazioni si riavviano da soli o in un momento concordato.
- Eliminare il servizio
e il nuovo indirizzoe cambiareserviceSubnetCIDRper il servizio kube-dns.
Questo piano permetterà di minimizzare il downtime a ~un minuto — durante l'eliminazione del servizio e il nuovo indirizzo e la sostituzione della subnet per il servizio kube-dns.
Modifica podNetwork
Allo stesso tempo, abbiamo deciso di vedere come modificare il podNetwork utilizzando il nostro etcdhelper. La sequenza di azioni è la seguente:
- Correggiamo le configurazioni in
kubectl -n kube-system edit cm kubelet-config-1.16; - Correggiamo il manifesto del kube-controller-manager;
- Modifichiamo podCIDR direttamente in etcd;
- Riavviamo tutti i nodi del cluster.
Ora in dettaglio su queste azioni:
1. Modifichiamo i ConfigMap nello spazio dei nomi kubectl -n kube-system edit cm kubelet-config-1.16:
— correggeremo — Correggiamo data.ClusterConfiguration.networking.podSubnet alla nuova subnet 10.55.0.0/16.
kubectl -n kube-system edit cm kube-proxy — Correggiamo data.config.conf.clusterCIDR: 10.55.0.0/16.
2. Modifichiamo il manifesto del controller-manager:
vim /etc/kubernetes/manifests/kube-controller-manager.yaml — Correggiamo --cluster-cidr=10.55.0.0/16.
3. Controlliamo i valori attuali .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addresses per tutti i nodi del cluster:
kubectl get no -o json | jq '[.items[] | {"name": .metadata.name, "podCIDR": .spec.podCIDR, "podCIDRs": .spec.podCIDRs, "InternalIP": (.status.addresses[] | select(.type == "InternalIP") | .address)}]'[
{
"name": "kube-2-master",
"podCIDR": "10.244.0.0/24",
"podCIDRs": [
"10.244.0.0/24"
],
"InternalIP": "192.168.199.2"
},
{
"name": "kube-2-master",
"podCIDR": "10.244.0.0/24",
"podCIDRs": [
"10.244.0.0/24"
],
"InternalIP": "10.0.1.239"
},
{
"name": "kube-2-worker-01f438cf-579f9fd987-5l657",
"podCIDR": "10.244.1.0/24",
"podCIDRs": [
"10.244.1.0/24"
],
"InternalIP": "192.168.199.222"
},
{
"name": "kube-2-worker-01f438cf-579f9fd987-5l657",
"podCIDR": "10.244.1.0/24",
"podCIDRs": [
"10.244.1.0/24"
],
"InternalIP": "10.0.4.73"
}
]4. Sostituiamo il podCIDR, apportando modifiche direttamente in etcd:
./etcdhelper -cacert /etc/kubernetes/pki/etcd/ca.crt -cert /etc/kubernetes/pki/etcd/server.crt -key /etc/kubernetes/pki/etcd/server.key -endpoint https://127.0.0.1:2379 change-pod-cidr 10.55.0.0/165. Verifichiamo che il podCIDR sia stato effettivamente modificato:
kubectl get no -o json | jq '[.items[] | {"name": .metadata.name, "podCIDR": .spec.podCIDR, "podCIDRs": .spec.podCIDRs, "InternalIP": (.status.addresses[] | select(.type == "InternalIP") | .address)}]'[
{
"name": "kube-2-master",
"podCIDR": "10.55.0.0/24",
"podCIDRs": [
"10.55.0.0/24"
],
"InternalIP": "192.168.199.2"
},
{
"name": "kube-2-master",
"podCIDR": "10.55.0.0/24",
"podCIDRs": [
"10.55.0.0/24"
],
"InternalIP": "10.0.1.239"
},
{
"name": "kube-2-worker-01f438cf-579f9fd987-5l657",
"podCIDR": "10.55.1.0/24",
"podCIDRs": [
"10.55.1.0/24"
],
"InternalIP": "192.168.199.222"
},
{
"name": "kube-2-worker-01f438cf-579f9fd987-5l657",
"podCIDR": "10.55.1.0/24",
"podCIDRs": [
"10.55.1.0/24"
],
"InternalIP": "10.0.4.73"
}
]6. Riavvieremo ciascun nodo del cluster a turno.
7. Se anche solo un nodo mantiene il vecchio podCIDR, kube-controller-manager non riuscirà ad avviarsi e i pod nel cluster non verranno programmati.
In realtà, è possibile modificare il podCIDR anche in modo più semplice (ad esempio, ). Ma volevamo imparare a lavorare direttamente con etcd, perché ci sono casi in cui la modifica degli oggetti Kubernetes in etcd è l'unica opzione possibile. (Ad esempio, non puoi semplicemente modificare il campo spec.clusterIP.)
Risultato
senza downtime). Questo articolo esplora la possibilità di lavorare direttamente con i dati in etcd, bypassando l'API di Kubernetes. A volte, questo approccio consente di fare „cose astute“. Le operazioni descritte nel testo sono state testate su reali cluster K8s. Tuttavia, il loro stato di prontezza per un uso generalizzato è PoC (proof of concept). Pertanto, se desideri utilizzare una versione modificata dell'utility etcdhelper sui tuoi cluster, fallo a tuo rischio e pericolo.
P.S.
Leggete anche nel nostro blog:
- «»;
- «»;
- «»;
- «».
Fonte: habr.com
