Sempre più spesso i clienti ci contattano chiedendo di garantire l'accesso al cluster Kubernetes per poter interagire con i servizi all'interno del cluster: per connettersi direttamente a un database o a un servizio, per collegare un'applicazione locale alle applicazioni all'interno del cluster…

Ad esempio, potrebbe sorgere la necessità di collegarsi dal proprio computer locale al servizio memcached.staging.svc.cluster.local. Offriamo questa possibilità tramite VPN all'interno del cluster a cui si connette il cliente. Per questo annunciamo le sotto-reti dei pod, dei servizi e inoltriamo il DNS del cluster al cliente. In questo modo, quando il cliente tenta di connettersi al 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.
Configuramos i cluster K8s utilizzando kubeadm, dove per impostazione predefinita la sotto-rete dei servizi è 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:
- La sotto-rete
192.168.*.*è spesso utilizzata nelle reti aziendali dei clienti, e ancor più spesso nelle reti domestiche degli sviluppatori. E quindi ci troviamo di fronte a conflitti: i router domestici operano in questa sotto-rete e la VPN inoltra queste sotto-reti dal cluster al cliente. - Abbiamo diversi cluster (cluster di produzione, staging e/o diversi cluster di sviluppo). Quindi in tutti loro, per impostazione predefinita, ci saranno sottoreti identiche per i pod e i servizi, il che crea grandi difficoltà per lavorare contemporaneamente con i servizi in più cluster.
Da tempo abbiamo adottato la prassi di utilizzare sottoreti diverse per i servizi e i pod all'interno di un progetto — in generale, affinché tutti i cluster siano con reti diverse. Tuttavia, ci sono un gran numero di cluster in produzione che non vogliamo ricreare da zero, poiché in essi sono attivati molti servizi, applicazioni stateful, ecc.
E allora ci siamo posti la domanda: come possiamo cambiare la sotto-rete in un cluster esistente?
Ricerca di soluzioni
La prassi più comune è quella di ricreare tutti servizi di tipo ClusterIP. Come alternativa, e così via:
Il seguente processo presenta un problema: dopo che tutto è stato configurato, i pod si avviano con l'IP precedente come nameserver DNS in /etc/resolv.conf.
Poiché non ho ancora trovato la soluzione, ho dovuto resettare l'intero cluster con kubeadm reset e inizializzarlo di nuovo.
Ma non è adatto a tutti… Ecco ulteriori dettagli per il nostro caso:
- Si utilizza Flannel;
- Ci sono cluster sia nel cloud che su hardware;
- Ci piacerebbe evitare il ridispiegamento di tutti i servizi nel cluster;
- C'è la necessità di fare tutto con il minor numero possibile di problemi;
- La versione di Kubernetes è 1.16.6 (tuttavia, le azioni successive saranno analoghe anche per altre versioni);
- Il compito principale è quello di sostituire nella rete di servizio del cluster, distribuito con kubeadm
192.168.0.0/16, con172.24.0.0/16.
E così è accaduto che eravamo da tempo interessati a vedere cosa e come viene memorizzato in Kubernetes in etcd, e cosa si può fare con questo... Abbiamo pensato: "Perché non aggiornare semplicemente i dati in etcd, sostituendo i vecchi indirizzi IP (rete) con i nuovi??»
Cercando strumenti pronti per lavorare con i dati in etcd, non abbiamo trovato nulla che risolvesse completamente il compito porposto. (A proposito, se conoscete qualsiasi strumento per lavorare direttamente con i dati in etcd, saremo grati se ci inviate i link.) Tuttavia, un buon punto di partenza è stato (grazie ai suoi autori!).
Questo strumento è in grado di connettersi a etcd utilizzando certificati e di leggere i dati da lì utilizzando i comandi ls, get, dump.
Aggiungiamo etcdhelper
Il pensiero successivo è naturale: "Cosa impedisce di aggiungere a questo strumento la possibilità di scrivere dati in etcd?"
Questo si è concretizzato in una versione modificata di etcdhelper con due nuove funzioni changeServiceCIDR e changePodCIDR. Puoi vedere il suo codice .
Cosa fanno le nuove funzioni? L'algoritmo changeServiceCIDR:
- crea un deserializzatore;
- compila un'espressione regolare per sostituire CIDR;
- scorre tutti i servizi con 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 al servizio un indirizzo IP della nuova rete;
- crea un serializzatore, trasforma l'oggetto Go in protobuf, scrive i nuovi dati in etcd.
La funzione changePodCIDR è sostanzialmente analoga changeServiceCIDR – solo invece di modificare le specifiche dei servizi lo facciamo per il nodo e cambiamo .spec.PodCIDR in una nuova rete.
Pratica
Cambio di serviceCIDR
Il piano per l'attuazione del compito proposto è molto semplice, ma implica un downtime al momento della ricreazione di tutti i pod nel cluster. Dopo aver descritto i passaggi principali, condivideremo anche le riflessioni su come in teoria sia possibile ridurre al minimo questo downtime.
Azioni preliminari:
- installazione del software necessario e costruzione del etcdhelper patchato;
- backup di etcd e
/etc/kubernetes.
Piano d'azione riassuntivo per cambiare 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 etcd-client 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. Facciamo un 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. Modifichiamo la rete dei servizi nei manifesti del controllo del piano 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 rete: 172.24.0.0/16 anziché 192.168.0.0/16.
5. Poiché stiamo cambiando la rete dei servizi, per cui kubeadm emette certificati per l'apiserver (compreso), è necessario riemetterli:
- Controlliamo a quali domini e indirizzi IP è emesso l'attuale certificato:
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 - Eliminiamo i vecchi crt e key, poiché senza questo il nuovo certificato non verrà emesso:
rm /etc/kubernetes/pki/apiserver.{key,crt} - Riemettiamo 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 rete:
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 la riemissione del certificato API server, riavviamo il suo container:
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, nel cluster smette di funzionare la risoluzione dei nomi, poiché nei pod esistenti è
/etc/resolv.confregistrato il vecchio indirizzo di CoreDNS (kube-dns), mentre kube-proxy ha modificato le regole iptables dalla vecchia sottorete a quella nuova. L'articolo discute ora delle possibili opzioni per minimizzare i tempi di inattività. - Modifichiamo i ConfigMap nello spazio dei nomi
kube-system:kubectl -n kube-system edit cm kubelet-config-1.16— qui sostituiamo
clusterDNScon il nuovo indirizzo IP del servizio kube-dns:kubectl -n kube-system get svc kube-dns.kubectl -n kube-system edit cm kubeadm-config— modifichiamo
data.ClusterConfiguration.networking.serviceSubnetin una nuova rete. - Poiché è cambiato l'indirizzo di kube-dns, è necessario aggiornare la configurazione di kubelet su tutti i nodi:
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'
Minimizzazione dei tempi di inattività
Riflessioni su come minimizzare i downtime:
- Dopo le modifiche ai manifesti del control plane, creare un nuovo servizio kube-dns, ad esempio, chiamato
kube-dns-tmpe con un nuovo indirizzo172.24.0.10. - Creare
ifin etcdhelper, che non modificherà il servizio kube-dns. - Sostituire in tutti i kubelet l'indirizzo
ClusterDNScon il nuovo, mentre il vecchio servizio continuerà a funzionare contemporaneamente al nuovo. - Aspettare che i pod con le applicazioni si aggiorni o per motivi naturali o in un momento concordato.
- Eliminare il servizio
kube-dns-tmpe cambiareserviceSubnetCIDRper il servizio kube-dns.
Questo piano permetterà di ridurre al minimo il downtime a ~un minuto — durante l'eliminazione del servizio kube-dns-tmp e la sostituzione della sottorete per il servizio dnsmasq.
Modifica di podNetwork
Nel frattempo, abbiamo deciso di vedere come modificare podNetwork utilizzando il nuovo etcdhelper. L'ordine delle operazioni è il seguente:
- modificare le configurazioni in
kube-system; - modificare il manifesto di kube-controller-manager;
- cambiare podCIDR direttamente in etcd;
- riavviare tutti i nodi del cluster.
Ora più in dettaglio su queste operazioni:
1. Modifichiamo i ConfigMap nello spazio dei nomi kube-system:
kubectl -n kube-system edit cm kubeadm-config — modifichiamo data.ClusterConfiguration.networking.podSubnet con la nuova sottorete 10.55.0.0/16.
kubectl -n kube-system edit cm kube-proxy — modifichiamo data.config.conf.clusterCIDR: 10.55.0.0/16.
2. Modifichiamo il manifesto di controller-manager:
vim /etc/kubernetes/manifests/kube-controller-manager.yaml — modifichiamo --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. Sostituiremo 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. Controlliamo che il podCIDR sia effettivamente cambiato:
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 uno dopo l'altro tutti i nodi del cluster.
7. Se anche solo un nodo rimane con il vecchio podCIDR, kube-controller-manager non riuscirà a partire e i pod nel cluster non verranno pianificati.
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 modificare gli oggetti Kubernetes in etcd è l'unica opzione possibile. (Ad esempio, non si può cambiare semplicemente il campo di Service spec.clusterIP.)
Risultato
L'articolo esamina la possibilità di lavorare con i dati in etcd direttamente, cioè bypassando l'API Kubernetes. A volte questo approccio consente di fare "trucchi". 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 dello strumento etcdhelper sui tuoi cluster, fallo a tuo rischio e pericolo.
P.S.
Leggi anche nel nostro blog:
- «»;
- «»;
- «»;
- «».
Fonte: habr.com
