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

Sempre più spesso i clienti ci chiedono di fornire l'accesso al cluster Kubernetes per poter accedere ai servizi all'interno del cluster: potersi connettere direttamente a qualche database o servizio, connettere un'applicazione locale con applicazioni all'interno del cluster...

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

Ad esempio, è necessario connettersi dal computer locale a un servizio memcached.staging.svc.cluster.local. Forniamo questa funzionalità utilizzando una VPN all'interno del cluster a cui si connette il client. Per fare ciò, annunciamo sottoreti di pod, servizi e inviamo il DNS del cluster al client. Pertanto, quando un client tenta di connettersi al servizio memcached.staging.svc.cluster.local, la richiesta va al DNS del cluster e in risposta riceve l'indirizzo di questo servizio dalla rete del servizio cluster o dall'indirizzo del pod.

Configuriamo i cluster K8 utilizzando kubeadm, dove si trova la sottorete del servizio predefinita 192.168.0.0/16e la rete di pod lo è 10.244.0.0/16. Di solito tutto funziona bene, ma ci sono un paio di punti:

  • Sottorete 192.168.*.* spesso utilizzato nelle reti degli uffici dei clienti e ancora più spesso nelle reti domestiche degli sviluppatori. E poi si verificano conflitti: i router domestici lavorano su questa sottorete e la VPN spinge queste sottoreti dal cluster al client.
  • Abbiamo diversi cluster (produzione, stage e/o diversi cluster di sviluppo). Quindi, per impostazione predefinita, tutti avranno le stesse sottoreti per pod e servizi, il che crea grandi difficoltà per il lavoro simultaneo con servizi in diversi cluster.

Abbiamo da tempo adottato la pratica di utilizzare sottoreti diverse per servizi e pod all'interno dello stesso progetto, in generale, in modo che tutti i cluster abbiano reti diverse. Tuttavia, esiste un gran numero di cluster in funzione che non vorrei ripristinare da zero, poiché eseguono molti servizi, applicazioni stateful, ecc.

E allora ci siamo chiesti: come cambiare la sottorete in un cluster esistente?

Cerca soluzioni

La pratica più comune è ricreare tutti servizi con tipo ClusterIP. Come opzione, può consigliare e simili:

Il seguente processo presenta un problema: dopo aver configurato tutto, i pod presentano il vecchio IP come server dei nomi DNS in /etc/resolv.conf.
Dato che non ho ancora trovato la soluzione, ho dovuto reimpostare l'intero cluster con kubeadm reset e riavviarlo.

Ma questo non è adatto a tutti... Ecco le presentazioni più dettagliate per il nostro caso:

  • Viene utilizzata la flanella;
  • Esistono cluster sia nei cloud che sull'hardware;
  • Vorrei evitare di ridistribuire tutti i servizi nel cluster;
  • C'è bisogno di fare tutto in generale con un numero minimo di problemi;
  • La versione di Kubernetes è la 1.16.6 (tuttavia, i passaggi successivi saranno simili per le altre versioni);
  • Il compito principale è garantire che in un cluster distribuito utilizzando kubeadm con una sottorete del servizio 192.168.0.0/16, sostituiscilo con 172.24.0.0/16.

E si è scoperto che da tempo eravamo interessati a vedere cosa e come in Kubernetes viene archiviato in etcd, cosa si può fare con esso... Quindi abbiamo pensato: "Perché non aggiornare semplicemente i dati in etcd, sostituendo i vecchi indirizzi IP (sottorete) con quelli nuovi? »

Dopo aver cercato strumenti già pronti per lavorare con i dati in etcd, non abbiamo trovato nulla che risolvesse completamente il problema. (A proposito, se conosci qualche utilità per lavorare con i dati direttamente in etcd, apprezzeremmo i collegamenti.) Tuttavia, un buon punto di partenza è etcdhelper da OpenShift (grazie ai suoi autori!).

Questa utility può connettersi a etcd utilizzando i certificati e leggere i dati da lì utilizzando i comandi ls, get, dump.

Aggiungi etcdhelper

Il pensiero successivo è logico: "Cosa ti impedisce di aggiungere questa utility aggiungendo la possibilità di scrivere dati su etcd?"

È diventata una versione modificata di etcdhelper con due nuove funzioni changeServiceCIDR и changePodCIDR. su di lei puoi vedere il codice qui.

Cosa fanno le nuove funzionalità? Algoritmo changeServiceCIDR:

  • creare un deserializzatore;
  • compilare un'espressione regolare per sostituire CIDR;
  • esaminiamo tutti i servizi con il tipo ClusterIP nel cluster:
    • decodificare il valore da etcd in un oggetto Go;
    • utilizzando un'espressione regolare sostituiamo i primi due byte dell'indirizzo;
    • assegnare al servizio un indirizzo IP della nuova sottorete;
    • crea un serializzatore, converti l'oggetto Go in protobuf, scrivi nuovi dati su etcd.

Funzione changePodCIDR sostanzialmente simili changeServiceCIDR - solo che invece di modificare le specifiche del servizio, lo facciamo per il nodo e cambiamo .spec.PodCIDR a una nuova sottorete.

Pratica

Modificare il CIDR del servizio

Il piano per l'implementazione dell'attività è molto semplice, ma comporta tempi di inattività durante la ricreazione di tutti i pod nel cluster. Dopo aver descritto i passaggi principali, condivideremo anche le nostre riflessioni su come, in teoria, questi tempi di inattività possano essere ridotti al minimo.

Fasi preparatorie:

  • installare il software necessario e assemblare l'etcdhelper con patch;
  • backup ecc. e /etc/kubernetes.

Breve piano d'azione per la modifica del servizioCIDR:

  • modifica dei manifest dell'apiserver e del controller-manager;
  • riemissione di certificati;
  • modifica dei servizi ClusterIP in etcd;
  • riavvio di tutti i pod nel cluster.

Quella che segue è una sequenza completa di azioni in dettaglio.

1. Installa etcd-client per il dump dei dati:

apt install etcd-client

2. Costruisci etcdhelper:

  • Installa 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
  • Risparmiamo per noi stessi etcdhelper.go, scarica le dipendenze, raccogli:
    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. Effettua un backup 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. Modificare la sottorete del servizio nei manifest del piano di controllo Kubernetes. Nei file /etc/kubernetes/manifests/kube-apiserver.yaml и /etc/kubernetes/manifests/kube-controller-manager.yaml modificare il parametro --service-cluster-ip-range a una nuova sottorete: 172.24.0.0/16 invece di 192.168.0.0/16.

5. Poiché stiamo modificando la sottorete del servizio a cui kubeadm emette certificati per apiserver (incluso), è necessario emetterli nuovamente:

  1. Vediamo per quali domini e indirizzi IP è stato rilasciato 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
  2. Prepariamo 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" # IP-адрес мастер узла
  3. Eliminiamo il vecchio crt e la chiave, poiché senza di questa il nuovo certificato non verrà emesso:
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Riemettiamo i certificati per il server API:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. Verifichiamo che il certificato sia stato emesso per la nuova sottorete:
    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 riemesso il certificato del server API, riavvia 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 del dominio smette di funzionare nel cluster, poiché nei pod esistenti in /etc/resolv.conf il vecchio indirizzo CoreDNS (kube-dns) viene registrato e kube-proxy modifica le regole iptables dalla vecchia sottorete a quella nuova. Più avanti nell'articolo vengono scritte le possibili opzioni per ridurre al minimo i tempi di inattività.

  9. Correggiamo i ConfigMap nello spazio dei nomi kube-system:
    kubectl -n kube-system edit cm kubelet-config-1.16

    - sostituisci qui clusterDNS al nuovo indirizzo IP del servizio kube-dns: kubectl -n kube-system get svc kube-dns.

    kubectl -n kube-system edit cm kubeadm-config

    - lo sistemeremo data.ClusterConfiguration.networking.serviceSubnet a una nuova sottorete.

  10. Poiché l'indirizzo kube-dns è cambiato, è necessario aggiornare la configurazione kubelet su tutti i nodi:
    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. Non resta che 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'

Ridurre al minimo i tempi di inattività

Considerazioni su come ridurre al minimo i tempi di inattività:

  1. Dopo aver modificato i manifest del piano di controllo, crea un nuovo servizio kube-dns, ad esempio, con il nome kube-dns-tmp e nuovo indirizzo 172.24.0.10.
  2. Fare if in etcdhelper, che non modificherà il servizio kube-dns.
  3. Sostituisci l'indirizzo in tutti i kubelet ClusterDNS a uno nuovo, mentre il vecchio servizio continuerà a funzionare contemporaneamente a quello nuovo.
  4. Attendere fino a quando i pod con le applicazioni si riavvolgono da soli per motivi naturali o in un momento concordato.
  5. Elimina servizio kube-dns-tmp e cambiare serviceSubnetCIDR per il servizio kube-dns.

Questo piano ti consentirà di ridurre al minimo i tempi di inattività a circa un minuto, per la durata della rimozione del servizio kube-dns-tmp e modificando la sottorete per il servizio kube-dns.

PodNetwork di modifica

Allo stesso tempo, abbiamo deciso di esaminare come modificare podNetwork utilizzando il risultante etcdhelper. La sequenza delle azioni è la seguente:

  • correzione delle configurazioni in kube-system;
  • correzione del manifest kube-controller-manager;
  • cambia podCIDR direttamente in etcd;
  • riavviare tutti i nodi del cluster.

Ora maggiori informazioni su queste azioni:

1. Modifica ConfigMap nello spazio dei nomi kube-system:

kubectl -n kube-system edit cm kubeadm-config

- correggere data.ClusterConfiguration.networking.podSubnet a una nuova sottorete 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

- correggere data.config.conf.clusterCIDR: 10.55.0.0/16.

2. Modificare il manifest del controller-manager:

vim /etc/kubernetes/manifests/kube-controller-manager.yaml

- correggere --cluster-cidr=10.55.0.0/16.

3. Guarda 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. Sostituisci podCIDR apportando modifiche direttamente a 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. Controlliamo che podCIDR sia davvero 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. Riavviamo tutti i nodi del cluster uno per uno.

7. Se lasci almeno un nodo vecchio podCIDR, kube-controller-manager non potrà essere avviato e i pod nel cluster non verranno pianificati.

In effetti, cambiare podCIDR può essere fatto in modo ancora più semplice (ad esempio, così). Ma volevamo imparare come lavorare direttamente con etcd, perché ci sono casi in cui si modificano oggetti Kubernetes in etcd - единственный possibile variante. (Ad esempio, non puoi semplicemente modificare il campo Servizio senza tempi di inattività spec.clusterIP.)

risultato

L'articolo discute la possibilità di lavorare direttamente con i dati in etcd, ad es. bypassando l'API Kubernetes. A volte questo approccio ti consente di fare “cose ​​complicate”. Abbiamo testato le operazioni indicate nel testo su veri cluster K8. Tuttavia, il loro stato di preparazione per un uso diffuso lo è PoC (prova di concetto). Pertanto, se desideri utilizzare una versione modificata dell'utilità etcdhelper sui tuoi cluster, fallo a tuo rischio e pericolo.

PS

Leggi anche sul nostro blog:

Fonte: habr.com

Aggiungi un commento