Unsere Erfahrungen mit Daten im etcd Kubernetes-Cluster direkt (ohne K8s API)

Zunehmend bitten uns Kunden, Zugriff auf den Kubernetes-Cluster bereitzustellen, um auf Dienste innerhalb des Clusters zugreifen zu können: um eine direkte Verbindung zu einer Datenbank oder einem Dienst herzustellen, um eine lokale Anwendung mit Anwendungen innerhalb des Clusters zu verbinden ...

Unsere Erfahrungen mit Daten im etcd Kubernetes-Cluster direkt (ohne K8s API)

Beispielsweise besteht die Notwendigkeit, von Ihrem lokalen Computer aus eine Verbindung zu einem Dienst herzustellen memcached.staging.svc.cluster.local. Wir stellen diese Funktion mithilfe eines VPN innerhalb des Clusters bereit, mit dem sich der Client verbindet. Dazu geben wir Subnetze von Pods und Diensten bekannt und pushen Cluster-DNS an den Client. Wenn also ein Client versucht, eine Verbindung zum Dienst herzustellen memcached.staging.svc.cluster.local, geht die Anfrage an den Cluster-DNS und erhält als Antwort die Adresse dieses Dienstes vom Cluster-Dienstnetzwerk oder die Pod-Adresse.

Wir konfigurieren K8s-Cluster mit kubeadm, wo sich das Standard-Service-Subnetz befindet 192.168.0.0/16, und das Netzwerk der Pods ist 10.244.0.0/16. Normalerweise funktioniert alles gut, aber es gibt ein paar Punkte:

  • Subnetz 192.168.*.* Wird häufig in Client-Büronetzwerken und noch häufiger in Heimnetzwerken von Entwicklern verwendet. Und dann kommt es zu Konflikten: Heimrouter arbeiten in diesem Subnetz und das VPN schiebt diese Subnetze vom Cluster zum Client.
  • Wir haben mehrere Cluster (Produktions-, Bühnen- und/oder mehrere Entwicklungscluster). Dann verfügen alle standardmäßig über dieselben Subnetze für Pods und Dienste, was die gleichzeitige Arbeit mit Diensten in mehreren Clustern stark erschwert.

Wir haben es uns längst angewöhnt, innerhalb desselben Projekts unterschiedliche Subnetze für Dienste und Pods zu verwenden – im Allgemeinen, sodass alle Cluster über unterschiedliche Netzwerke verfügen. Es sind jedoch eine Vielzahl von Clustern im Einsatz, die ich nicht von Grund auf umsetzen möchte, da auf ihnen viele Dienste, zustandsbehaftete Anwendungen usw. ausgeführt werden.

Und dann haben wir uns gefragt: Wie ändert man das Subnetz in einem bestehenden Cluster?

Suche nach Entscheidungen

Die gängigste Praxis ist die Neuerstellung alle Dienste vom Typ ClusterIP. Als eine Option, kann beraten und solche:

Bei dem folgenden Prozess gibt es ein Problem: Nachdem alles konfiguriert wurde, werden die Pods mit der alten IP als DNS-Nameserver in /etc/resolv.conf angezeigt.
Da ich immer noch keine Lösung gefunden habe, musste ich den gesamten Cluster mit kubeadm reset zurücksetzen und erneut initialisieren.

Aber das ist nicht für jeden geeignet... Hier finden Sie detailliertere Einführungen zu unserem Fall:

  • Flanell wird verwendet;
  • Es gibt Cluster sowohl in den Clouds als auch auf der Hardware;
  • Ich möchte vermeiden, alle Dienste im Cluster erneut bereitzustellen.
  • Es besteht die Notwendigkeit, im Allgemeinen alles mit einem Minimum an Problemen zu erledigen.
  • Die Kubernetes-Version ist 1.16.6 (die weiteren Schritte werden jedoch für andere Versionen ähnlich sein);
  • Die Hauptaufgabe besteht darin, sicherzustellen, dass in einem Cluster mithilfe von kubeadm ein Dienstsubnetz bereitgestellt wird 192.168.0.0/16, ersetzen Sie es durch 172.24.0.0/16.

Und zufällig waren wir schon lange daran interessiert zu sehen, was und wie in Kubernetes in etcd gespeichert ist, was man damit machen kann ... Also dachten wir: „Warum nicht einfach die Daten in etcd aktualisieren und die alten IP-Adressen (Subnetz) durch neue ersetzen?? »

Bei der Suche nach vorgefertigten Tools für die Arbeit mit Daten in etcd haben wir nichts gefunden, was das Problem vollständig löste. (Übrigens, wenn Sie Dienstprogramme kennen, mit denen Sie direkt in etcd mit Daten arbeiten können, würden wir uns über die Links freuen.) Ein guter Ausgangspunkt ist jedoch etcdhelper von OpenShift (Danke an die Autoren!).

Dieses Dienstprogramm kann mithilfe von Zertifikaten eine Verbindung zu etcd herstellen und mithilfe von Befehlen Daten von dort lesen ls, get, dump.

Etcdhelper hinzufügen

Der nächste Gedanke ist logisch: „Was hält Sie davon ab, dieses Dienstprogramm hinzuzufügen, indem Sie die Möglichkeit hinzufügen, Daten in etcd zu schreiben?“

Es wurde eine modifizierte Version von etcdhelper mit zwei neuen Funktionen changeServiceCIDR и changePodCIDR. auf ihr Sie können den Code sehen hier.

Was bewirken die neuen Funktionen? Algorithmus changeServiceCIDR:

  • Erstellen Sie einen Deserialisierer.
  • Kompilieren Sie einen regulären Ausdruck, um CIDR zu ersetzen.
  • Wir gehen alle Dienste mit dem Typ ClusterIP im Cluster durch:
    • dekodieren Sie den Wert von etcd in ein Go-Objekt;
    • Mit einem regulären Ausdruck ersetzen wir die ersten beiden Bytes der Adresse;
    • Weisen Sie dem Dienst eine IP-Adresse aus dem neuen Subnetz zu.
    • Erstellen Sie einen Serializer, konvertieren Sie das Go-Objekt in Protobuf und schreiben Sie neue Daten in etcd.

Funktion changePodCIDR im Wesentlichen ähnlich changeServiceCIDR - Nur anstatt die Servicespezifikation zu bearbeiten, tun wir dies für den Knoten und ändern ihn .spec.PodCIDR in ein neues Subnetz.

Praxis

Dienst-CIDR ändern

Der Plan zur Implementierung der Aufgabe ist sehr einfach, erfordert jedoch Ausfallzeiten zum Zeitpunkt der Neuerstellung aller Pods im Cluster. Nachdem wir die wichtigsten Schritte beschrieben haben, werden wir auch darüber sprechen, wie diese Ausfallzeit theoretisch minimiert werden kann.

Vorbereitende Schritte:

  • Installieren der erforderlichen Software und Zusammenstellen des gepatchten etcdhelper;
  • Backup etcd und /etc/kubernetes.

Kurzer Aktionsplan zur Änderung des DienstesCIDR:

  • Ändern der Apiserver- und Controller-Manager-Manifeste;
  • Neuausstellung von Zertifikaten;
  • ClusterIP-Dienste in etcd ändern;
  • Neustart aller Pods im Cluster.

Im Folgenden finden Sie eine vollständige Abfolge von Aktionen im Detail.

1. Installieren Sie den etcd-Client für den Datendump:

apt install etcd-client

2. Etcdhelper erstellen:

  • Golang installieren:
    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
  • Wir sparen für uns etcdhelper.go, Abhängigkeiten herunterladen, sammeln:
    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. Erstellen Sie ein 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. Ändern Sie das Dienstsubnetz in den Manifesten der Kubernetes-Steuerungsebene. In Akten /etc/kubernetes/manifests/kube-apiserver.yaml и /etc/kubernetes/manifests/kube-controller-manager.yaml Ändern Sie den Parameter --service-cluster-ip-range in ein neues Subnetz: 172.24.0.0/16 statt 192.168.0.0/16.

5. Da wir das Service-Subnetz ändern, an das kubeadm Zertifikate für Apiserver ausstellt (einschließlich), müssen diese neu ausgestellt werden:

  1. Schauen wir uns an, für welche Domänen 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-адрес мастер узла
  3. Löschen wir das alte CRT und den alten Schlüssel, da ohne diese das neue Zertifikat nicht ausgestellt wird:
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Lassen Sie uns Zertifikate für den API-Server erneut ausstellen:
    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 Address:172.24.0.1, IP Address:10.0.0.163, IP Address:192.168.199.100
  6. Nachdem Sie das API-Serverzertifikat erneut ausgestellt haben, starten Sie dessen Container neu:
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. Lassen Sie uns die Konfiguration neu generieren admin.conf:
    kubeadm alpha certs renew admin.conf
  8. Bearbeiten wir 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! Zu diesem Zeitpunkt funktioniert die Domänenauflösung im Cluster nicht mehr, da sie in vorhandenen Pods vorhanden ist /etc/resolv.conf Die alte CoreDNS-Adresse (kube-dns) wird registriert und kube-proxy ändert die iptables-Regeln vom alten Subnetz auf das neue. Im weiteren Verlauf des Artikels wird über mögliche Optionen zur Minimierung von Ausfallzeiten geschrieben.

  9. Lassen Sie uns ConfigMaps im Namespace reparieren kube-system:
    kubectl -n kube-system edit cm kubelet-config-1.16

    - hier ersetzen clusterDNS zur neuen IP-Adresse des kube-dns-Dienstes: kubectl -n kube-system get svc kube-dns.

    kubectl -n kube-system edit cm kubeadm-config

    - Wir werden es reparieren data.ClusterConfiguration.networking.serviceSubnet in ein neues Subnetz.

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

Ausfallzeiten minimieren

Gedanken zur Minimierung von Ausfallzeiten:

  1. Erstellen Sie nach dem Ändern der Manifeste der Steuerungsebene einen neuen kube-dns-Dienst, beispielsweise mit dem Namen kube-dns-tmp und neue Adresse 172.24.0.10.
  2. Tun if in etcdhelper, wodurch der kube-dns-Dienst nicht geändert wird.
  3. Ersetzen Sie die Adresse in allen Kubelets ClusterDNS auf einen neuen, während der alte Dienst weiterhin gleichzeitig mit dem neuen funktioniert.
  4. Warten Sie, bis die Pods mit den Anwendungen entweder aus natürlichen Gründen oder zu einem vereinbarten Zeitpunkt von selbst umkippen.
  5. Dienst löschen kube-dns-tmp und ändern serviceSubnetCIDR für den kube-dns-Dienst.

Mit diesem Plan können Sie die Ausfallzeit für die Dauer der Serviceentfernung auf etwa eine Minute minimieren kube-dns-tmp und Ändern des Subnetzes für den Dienst kube-dns.

Modifikation podNetwork

Gleichzeitig haben wir beschlossen, zu prüfen, wie wir podNetwork mithilfe des resultierenden etcdhelper ändern können. Die Reihenfolge der Aktionen ist wie folgt:

  • Konfigurationen reparieren in kube-system;
  • Korrigieren des Kube-Controller-Manager-Manifests;
  • podCIDR direkt in etcd ändern;
  • Starten Sie alle Clusterknoten neu.

Nun mehr zu diesen Aktionen:

1. Ändern Sie ConfigMaps im Namespace kube-system:

kubectl -n kube-system edit cm kubeadm-config

- Korrigieren data.ClusterConfiguration.networking.podSubnet in ein neues Subnetz 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

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

2. Ändern Sie das Controller-Manager-Manifest:

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

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

3. Sehen Sie sich die aktuellen Werte an .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addresses für alle Clusterknoten:

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. Ersetzen Sie podCIDR, indem Sie Änderungen direkt an 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 sich podCIDR wirklich geändert hat:

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. Starten wir alle Clusterknoten nacheinander neu.

7. Wenn Sie mindestens einen Knoten verlassen altes podCIDR, dann kann kube-controller-manager nicht gestartet werden und Pods im Cluster werden nicht geplant.

Tatsächlich kann die Änderung von podCIDR sogar noch einfacher durchgeführt werden (z. B. so). Aber wir wollten lernen, wie man direkt mit etcd arbeitet, da es Fälle gibt, in denen Kubernetes-Objekte in etcd bearbeitet werden - lediglich mögliche Variante. (Zum Beispiel können Sie das Feld „Service“ nicht einfach ohne Ausfallzeit ändern spec.clusterIP.)

Ergebnis

Der Artikel diskutiert die Möglichkeit, direkt mit Daten in etcd zu arbeiten, d. h. Umgehen der Kubernetes-API. Manchmal ermöglicht Ihnen dieser Ansatz, „knifflige Dinge“ zu tun. Wir haben die im Text angegebenen Operationen an echten K8s-Clustern getestet. Ihr Stand der Bereitschaft zur breiten Nutzung ist jedoch PoC (Proof of Concept). Wenn Sie daher eine modifizierte Version des Dienstprogramms etcdhelper auf Ihren Clustern verwenden möchten, tun Sie dies auf eigenes Risiko.

PS

Lesen Sie auch auf unserem Blog:

Source: habr.com

Kommentar hinzufügen