Unsere Erfahrung im Umgang mit Daten in etcd des Kubernetes-Clusters direkt (ohne K8s API)

Immer mehr Kunden wenden sich an uns mit der Bitte, Zugang zum Kubernetes-Cluster zu gewĂ€hren, um auf die Dienste innerhalb des Clusters zugreifen zu können: damit man sich direkt mit einer bestimmten Datenbank oder einem Dienst verbinden kann, um eine lokale Anwendung mit Anwendungen innerhalb des Clusters zu verbinden


Unsere Erfahrung im Umgang mit Daten in etcd des Kubernetes-Clusters direkt (ohne K8s API)

Beispielsweise besteht die Notwendigkeit, sich von seinem lokalen Rechner mit dem Dienst memcached.staging.svc.cluster.localzu verbinden. Wir bieten diese Möglichkeit ĂŒber ein VPN innerhalb des Clusters an, mit dem sich der Kunde verbindet. Dazu kĂŒndigen wir die Subnetze der Pods, der Dienste an und pushen die Cluster-DNS an den Kunden. Wenn der Kunde also versucht, sich mit dem Dienst memcached.staging.svc.cluster.localzu verbinden, geht die Anfrage an das Cluster-DNS und erhĂ€lt die Adresse dieses Dienstes aus dem Servicenetz des Clusters oder die Adresse des Pods zurĂŒck.

Die K8s-Cluster richten wir mit kubeadm ein, wobei das Standard-Servicenetz – 192.168.0.0/16und das Pod-Netz – 10.244.0.0/16ist. Normalerweise funktioniert alles gut, aber es gibt ein paar Punkte:

  • Das Subnetz 192.168.*.* wird hĂ€ufig in den BĂŒros von Kunden eingesetzt, aber noch hĂ€ufiger in den Heimnetzwerken von Entwicklern. Daraus ergeben sich Konflikte: Heimrouter arbeiten in diesem Subnetz, und das VPN ĂŒbertrĂ€gt diese Subnetze aus dem Cluster an den Kunden.
  • Wir haben mehrere Cluster (Produktionscluster, Stage-Cluster und/oder mehrere Entwicklungscluster). Dadurch haben alle standardmĂ€ĂŸig dieselben Subnetze fĂŒr Pods und Dienste, was erhebliche Schwierigkeiten verursacht, wenn man gleichzeitig mit Diensten in mehreren Clustern arbeiten möchte.

Bereits seit geraumer Zeit haben wir die Praxis angenommen, verschiedene Subnetze fĂŒr Dienste und Pods innerhalb eines Projekts zu verwenden – insgesamt, um sicherzustellen, dass alle Cluster unterschiedliche Netzwerke haben. Es gibt jedoch eine betrĂ€chtliche Anzahl von Clustern in Betrieb, die man nicht von Grund auf neu aufsetzen möchte, da viele Dienste, stateful-Anwendungen usw. darin laufen.

Und so haben wir uns die Frage gestellt: Wie können wir das Subnetz in einem bestehenden Cluster Àndern?

Suche nach Lösungen

Die gÀngigste Praxis besteht darin, alle Dienste vom Typ ClusterIP neu zu erstellen. Alternativ können auch Folgendes empfohlen werden: und das:

Der folgende Prozess hat ein Problem: Nachdem alles konfiguriert ist, starten die Pods mit der alten IP als DNS-Namensserver in /etc/resolv.conf.
Da ich die Lösung noch nicht gefunden habe, musste ich das gesamte Cluster mit kubeadm zurĂŒcksetzen und neu initialisieren.

Aber das passt nicht fĂŒr alle
 Hier sind genauere Informationen fĂŒr unseren Fall:

  • Flannel wird verwendet;
  • Es gibt Cluster sowohl in der Cloud als auch auf physischer Hardware;
  • Ich möchte vermeiden, alle Dienste im Cluster erneut bereitzustellen;
  • Es besteht die Notwendigkeit, alles mit minimalen Problemen zu erledigen;
  • Die Kubernetes-Version ist 1.16.6 (ĂŒbrigens werden die nĂ€chsten Schritte auch fĂŒr andere Versionen Ă€hnlich sein);
  • Die Hauptaufgabe besteht darin, im Cluster, der mit kubeadm und einem Dienstnetzwerk bereitgestellt wurde, 192.168.0.0/16dieses auf 172.24.0.0/16.

Und zufĂ€llig war es so, dass wir schon lange interessiert waren, was und wie in Kubernetes in etcd gespeichert wird, und was man damit ĂŒberhaupt machen kann
 Und daher dachten wir: "Warum die Daten in etcd nicht einfach aktualisieren, indem wir die alten IP-Adressen (Subnetz) durch neue ersetzen??»

Nachdem wir nach vorhandenen Werkzeugen fĂŒr die Arbeit mit Daten in etcd gesucht hatten, fanden wir nichts, das die gestellte Aufgabe vollstĂ€ndig löst. (Übrigens, wenn Sie von irgendwelchen Tools wissen, die direkt mit Daten in etcd arbeiten – wir wĂ€ren dankbar fĂŒr Links.) Eine gute Ausgangsbasis war jedoch etcdhelper von OpenShift (Danke an die Autoren!).

Dieses Tool kann sich mit etcd ĂŒber Zertifikate verbinden und Daten dort mit Kommandos lesen. ls, get, dump.

Wir erweitern etcdhelper.

Der nĂ€chste Gedanke ist naheliegend: "Was hindert uns daran, dieses Tool zu erweitern und die Möglichkeit hinzuzufĂŒgen, Daten in etcd zu schreiben?"

Das wurde in einer modifizierten Version von etcdhelper mit zwei neuen Funktionen umgesetzt. changeServiceCIDR und changePodCIDR. Ihr Code kann eingesehen werden. hier.

Was machen die neuen Funktionen? Algorithmen changeServiceCIDR:

  • wir erstellen einen Deserialisierer;
  • wir kompilieren einen regulĂ€ren Ausdruck zur Änderung von CIDR;
  • wir durchlaufen alle ClusterIP-Services im Cluster:
    • wir dekodieren den Wert aus etcd in ein Go-Objekt;
    • wir ersetzen mit dem regulĂ€ren Ausdruck die ersten beiden Bytes der Adresse;
    • wir weisen dem Service eine IP-Adresse aus dem neuen Subnetz zu;
    • wir erstellen einen Serializer, wandeln das Go-Objekt in Protobuf um und schreiben die neuen Daten in etcd.

Die Funktion changePodCIDR ist im Grunde genommen Ă€hnlich changeServiceCIDR — nur anstatt die Spezifikation der Services zu bearbeiten, tun wir dies fĂŒr den Knoten und Ă€ndern .spec.PodCIDR in das neue Subnetz.

Praxis

Änderung von serviceCIDR.

Der Plan zur Umsetzung der gestellten Aufgabe ist sehr einfach, beinhaltet jedoch Downtime wÀhrend der Neuerstellung aller Pods im Cluster. Nach der Beschreibung der wesentlichen Schritte werden wir auch Gedanken teilen, wie man in der Theorie diese Downtime minimieren kann.

Vorbereitende Maßnahmen:

  • Installation der erforderlichen Software und Kompilierung des gepatchten etcdhelper;
  • Backup von etcd und /etc/kubernetes.

Ein kurzer Aktionsplan zur Änderung des serviceCIDR:

  • Änderung der Manifeste von apiserver und controller-manager;
  • Neuausstellung von Zertifikaten;
  • Änderung der ClusterIP-Services in etcd;
  • Neustart aller Pods im Cluster.

Im Folgenden wird die vollstÀndige Abfolge der Schritte im Detail dargestellt.

1. Installieren Sie den etcd-Client fĂŒr das Dumpen von Daten:

apt install etcd-client

2. Kompilieren Sie etcdhelper:

  • Installieren Sie 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
  • Speichern Sie sich etcdhelper.go, laden Sie die AbhĂ€ngigkeiten herunter und kompilieren Sie:
    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. Machen Sie ein Backup von 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. Ändern Sie das Dienst-Subnetz in den Manifesten des Kubernetes-Controlplanes. In den Dateien /etc/kubernetes/manifests/kube-apiserver.yaml und /etc/kubernetes/manifests/kube-controller-manager.yaml Ă€ndern Sie den Parameter --service-cluster-ip-range in das neue Subnetz: 172.24.0.0/16 statt 192.168.0.0/16.

5. Da wir das Dienst-Subnetz Ă€ndern, fĂŒr das kubeadm Zertifikate fĂŒr den API-Server (unter anderem) ausstellt, mĂŒssen diese neu ausgestellt werden:

  1. Sehen wir uns an, fĂŒr welche Domains und IP-Adressen das aktuelle Zertifikat ausgestellt wurde:
    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. Bereiten wir eine minimale Konfiguration fĂŒr kubeadm vor:
    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-Adresse des Master-Knotens
  3. Löschen Sie die alten crt und key, da ohne dies das neue Zertifikat nicht ausgestellt werden kann:
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Stellen Sie die Zertifikate fĂŒr den API-Server neu aus:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. ÜberprĂŒfen wir, ob das Zertifikat fĂŒr das neue Subnetz ausgestellt wurde:
    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-Adresse:172.24.0.1, IP-Adresse:10.0.0.163, IP-Adresse:192.168.199.100
  6. Nach der Neuausstellung des API-Server-Zertifikats starten wir den Container neu:
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. Wir regenerieren die Konfiguration fĂŒr admin.conf:
    kubeadm alpha certs renew admin.conf
  8. Wir bearbeiten die Daten 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 

    Achtung! In diesem Moment funktioniert die Namensauflösung im Cluster nicht mehr, da in den bereits vorhandenen Pods die alte Adresse von CoreDNS (kube-dns) hinterlegt ist und kube-proxy die iptables-Regeln von der alten Subnetz auf das neue geÀndert hat. Im weiteren Verlauf des Artikels wird erlÀutert, wie man die Ausfallzeiten minimieren kann. /etc/resolv.conf Die alte CoreDNS-Adresse (kube-dns) ist konfiguriert, und kube-proxy hat die iptables-Regeln von dem alten Subnetz auf das neue geÀndert. Im Folgenden werden mögliche Optionen beschrieben, um Ausfallzeiten zu minimieren.

  9. Wir korrigieren die ConfigMaps im Namespace kube-system:
    kubectl -n kube-system edit cm kubelet-config-1.16

    — hier ersetzen wir clusterDNS durch die neue IP-Adresse des kube-dns-Services: kubectl -n kube-system get svc kube-dns.

    kubectl -n kube-system edit cm kubeadm-config

    — wir korrigieren data.ClusterConfiguration.networking.serviceSubnet in das neue Subnetz.

  10. Da sich die Adresse von kube-dns geÀndert hat, muss die Konfiguration von kubelet auf allen Knoten aktualisiert werden:
    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. Es bleibt, alle Pods im Cluster neu zu starten:
    kubectl get pods --no-headers=true --all-namespaces | sed -r 's/(S+)s+(S+).*/kubectl --namespace 1 delete pod 2/e'

Minimierung der Ausfallzeit

Überlege, wie du den Downtime minimieren kannst:

  1. Nach Änderungen der Manifeste des Steuerungsplans, erstelle einen neuen Dienst kube-dns, zum Beispiel mit dem Namen kube-dns-tmp und einer neuen Adresse 172.24.0.10.
  2. Erstelle wenn in etcdhelper, der den Dienst kube-dns nicht modifizieren wird.
  3. Ersetze in allen Kubelets die Adresse ClusterDNS durch die neue, wÀhrend der alte Dienst weiterhin gleichzeitig mit dem neuen lÀuft.
  4. Warte, bis die Pods mit den Anwendungen entweder von selbst aus natĂŒrlichen GrĂŒnden oder zu einem vereinbarten Zeitpunkt umgeschaltet werden.
  5. Lösche den Dienst kube-dns-tmp und Ă€ndere serviceSubnetCIDR fĂŒr den Dienst kube-dns.

Dieser Plan ermöglicht es, die Ausfallzeit auf etwa eine Minute zu minimieren – wĂ€hrend der Löschung des Dienstes kube-dns-tmp und der Änderung des Subnetzes fĂŒr den Dienst kube-dns.

Modifikation des podNetwork

Gleichzeitig haben wir entschieden, zu zeigen, wie man das podNetwork mit dem resultierenden etcdhelper modifizieren kann. Der Ablauf sieht wie folgt aus:

  • wir korrigieren die Konfigurationen in kube-system;
  • wir korrigieren das Manifest des kube-controller-managers;
  • wir Ă€ndern podCIDR direkt in etcd;
  • wir starten alle Knoten im Cluster neu.

Jetzt im Detail zu diesen Aktionen:

1. Modifizieren Sie die ConfigMaps im Namespace kube-system:

kubectl -n kube-system edit cm kubeadm-config

— wir beheben data.ClusterConfiguration.networking.podSubnet auf das neue Subnetz 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

— wir beheben data.config.conf.clusterCIDR: 10.55.0.0/16.

2. Ändern Sie das Manifest des Controller-Managers:

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

— wir beheben --cluster-cidr=10.55.0.0/16.

3. ÜberprĂŒfen Sie die aktuellen Werte .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addresses fĂŒr alle Knoten im 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. Wir ersetzen podCIDR, indem wir Änderungen direkt in etcd vornehmen:

./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. ÜberprĂŒfen wir, ob podCIDR tatsĂ€chlich geĂ€ndert wurde:

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. Lassen Sie nacheinander alle Knoten des Clusters neu starten.

7. Wenn bei mindestens einem Knoten der alte podCIDR, kann kube-controller-manager nicht gestartet werden, und die Pods im Cluster werden nicht geplant.

In der Tat kann die Änderung des podCIDR auch einfacher durchgefĂŒhrt werden (zum Beispiel, so). Aber wir wollten lernen, direkt mit etcd zu arbeiten, denn es gibt FĂ€lle, in denen das Bearbeiten von Kubernetes-Objekten in etcd — die einzige Möglichkeit ist. (Zum Beispiel kann man das Feld spec.clusterIP.)

Zusammenfassung

bei einem Service nicht einfach ohne Stillstand Ă€ndern. Der Artikel behandelt die Möglichkeit, direkt mit Daten in etcd zu arbeiten, das heißt, den Kubernetes API zu umgehen. Manchmal ermöglicht dieser Ansatz, "clevere Tricks" zu machen. Die im Text aufgefĂŒhrten Operationen haben wir in realen K8s-Clustern getestet. Ihr Status in Bezug auf die breite Anwendbarkeit ist jedoch — PoC (Proof of Concept). Daher, wenn Sie eine modifizierte Version des Dienstprogramms etcdhelper in Ihren Clustern verwenden möchten, tun Sie dies auf eigene Gefahr.

P.S.

Lesen Sie auch in unserem Blog:

Quelle: habr.com

ZuverlĂ€ssiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen đŸ”„ ZuverlĂ€ssiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster