La nostra esperienza con i dati nel cluster Kubernetes etcd direttamente (senza API K8s).

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…

La nostra esperienza con i dati nel cluster Kubernetes etcd direttamente (senza API K8s).

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, possono consigliare 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/16con 172.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 etcdhelper di OpenShift (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 qui.

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-client

2. 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:

  1. 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
  2. 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
  3. Rimuoveremo i vecchi crt e key, poiché senza di essi non verrà rilasciato il nuovo certificato:
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Rilasceremo nuovamente i certificati per l'API server:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. 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
  6. 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
  7. Rigeneriamo la configurazione per admin.conf:
    kubeadm alpha certs renew admin.conf
  8. 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/16 

    Attenzione! 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.conf Correggeremo i ConfigMap nello spazio dei nomi

  9. kube-system kubectl -n kube-system edit cm kubelet-config-1.16:
    — qui sostituiremo

    clusterDNS con il nuovo indirizzo IP del servizio kube-dns: kubectl -n kube-system get svc kube-dns kubectl -n kube-system edit cm kubeadm-config.

    — correggeremo

    data.ClusterConfiguration.networking.serviceSubnet Poiché l'indirizzo di kube-dns è cambiato, è necessario aggiornare la configurazione di kubelet su tutti i nodi: alla nuova sottorete.

  10. kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
    Rimane da riavviare tutti i pod nel cluster:
  11. 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

  1. kube-dns-tmp e il nuovo indirizzo Creare 172.24.0.10.
  2. if in etcdhelper, che non modificherà il servizio kube-dns. Sostituire in tutti i kubelet l'indirizzo
  3. 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.
  4. Aspettare che i pod con le applicazioni si riavviano da soli o in un momento concordato.
  5. Eliminare il servizio e il nuovo indirizzo e cambiare serviceSubnetCIDR per 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/16

5. 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, così). 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

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster