Unsere Erfahrung mit Daten in etcd Kubernetes-Cluster direkt zu arbeiten (ohne K8s API)

Immer häufiger wenden sich Kunden mit der Bitte an uns, Zugriff auf den Kubernetes-Cluster zu gewähren, um auf Dienste innerhalb des Clusters zugreifen zu können: Um eine direkte Verbindung zu einer Datenbank oder einem Dienst herstellen zu können, um die lokale Anwendung mit Anwendungen innerhalb des Clusters zu verbinden ...



Beispielsweise muss eine Verbindung hergestellt werden von Ihrem lokalen Computer zum Service memcached.staging.svc.cluster.local. Wir bieten diese Möglichkeit mit einem VPN innerhalb des Clusters, mit dem der Client eine Verbindung herstellt. Zu diesem Zweck kündigen wir die Subnetze von Pods, Diensten und Push-Cluster-DNS an den Client an. Wenn der Client versucht, eine Verbindung zum Dienst herzustellen memcached.staging.svc.cluster.local, wird die Anforderung an das Cluster-DNS gesendet und erhält als Antwort die Adresse dieses Dienstes vom Clusterdienstnetzwerk oder die Pod-Adresse.

Wir konfigurieren K8s-Cluster mit kubeadm, wobei das Service-Subnetz standardmäßig 192.168.0.0/16und das Pod-Netzwerk aktiviert sind 10.244.0.0/16. Normalerweise funktioniert alles gut, aber es gibt ein paar Punkte:

  • Das Subnetz wird 192.168.*.*häufig in Büronetzwerken von Clients und noch häufiger in Heimnetzwerken von Entwicklern verwendet. Und dann kommt es zu Konflikten: Heimrouter arbeiten in diesem Subnetz und das VPN überträgt diese Subnetze vom Cluster zum Client.
  • Wir haben mehrere Cluster (Produktions-, Stage- und / oder mehrere Entwicklungscluster). Dann gibt es in allen standardmäßig dieselben Subnetze für Pods und Dienste, was große Schwierigkeiten bei der gleichzeitigen Arbeit mit Diensten in mehreren Clustern verursacht.

Seit geraumer Zeit verwenden wir die Praxis, unterschiedliche Subnetze für Dienste und Pods innerhalb desselben Projekts zu verwenden - im Allgemeinen, sodass sich alle Cluster in unterschiedlichen Netzwerken befinden. Es gibt jedoch eine große Anzahl von Clustern in Betrieb, die ich nicht von Grund auf neu erstellen möchte, da auf ihnen viele Dienste, Stateful-Anwendungen usw. ausgeführt werden.

Und dann haben wir uns gefragt: Wie würde ich das Subnetz in einem vorhandenen Cluster ändern?

Suche nach Entscheidungen


Am häufigsten werden alle Dienste mit dem ClusterIP-Typ neu erstellt. Alternativ können sie dies auch empfehlen :

Der folgende Prozess hat ein Problem: Nachdem alles konfiguriert wurde, wird in den Pods die alte IP als DNS-Nameserver in /etc/resolv.conf angezeigt.
Da ich die Lösung immer noch nicht gefunden habe, musste ich den gesamten Cluster mit kubeadm reset zurücksetzen und erneut starten.

Aber das passt nicht jedem ... Hier sind detailliertere einleitende Anmerkungen zu unserem Fall:

  • Wird von Flanell verwendet;
  • Es gibt Cluster sowohl in den Wolken als auch auf dem Eisen;
  • Ich möchte die wiederholte Bereitstellung aller Dienste im Cluster vermeiden.
  • Es ist notwendig, alles mit einem Minimum an Problemen zu tun.
  • Die Kubernetes-Version ist 1.16.6 (weitere Aktionen sind jedoch für andere Versionen ähnlich).
  • Die Hauptaufgabe besteht darin 192.168.0.0/16, es durch einen Cluster zu ersetzen , der mithilfe von kubeadm mit einem Service-Subnetz bereitgestellt wird 172.24.0.0/16.

Und es ist einfach so passiert, dass es für uns lange Zeit interessant war zu sehen, was und wie in Kubernetes in etcd gespeichert ist, was damit überhaupt gemacht werden kann ... Also dachten wir: „ Warum nicht einfach die Daten in etcd aktualisieren, indem wir die alten IP-Adressen (Subnetz) ersetzen? zu neuen ? "

Auf der Suche nach vorgefertigten Tools für die Arbeit mit Daten in etcd haben wir nichts gefunden, was die Aufgabe vollständig löst. (Übrigens, wenn Sie über Dienstprogramme für die Arbeit mit Daten direkt in etcd Bescheid wissen, sind wir für die Links dankbar.) OpenShift etcdhelper wurde jedoch zu einem guten Ausgangspunkt (dank seiner Autoren!) .

Dieses Dienstprogramm ist in der Lage zu verbinden Zertifikate ETCD verwenden und die ausgelesenen Daten mit den Befehlen ls, get, dump.

Fügen Sie etcdhelper hinzu


Der folgende Gedanke ist logisch: "Was verhindert das Hinzufügen dieses Dienstprogramms und das Hinzufügen der Fähigkeit, Daten in etcd zu schreiben?"

Es wurde in eine modifizierte Version von etcdhelper mit zwei neuen übersetzt changeServiceCIDRund Funktionen changePodCIDR. Sie können ihren Code hier sehen .

Was machen die neuen Funktionen? Algorithmus changeServiceCIDR:

  • einen Deserializer erstellen;
  • Kompilieren Sie einen regulären Ausdruck, um CIDR zu ersetzen.
  • Wir durchlaufen alle Dienste mit dem ClusterIP-Typ im Cluster:

    • Dekodieren Sie den Wert von etcd in das Go-Objekt.
    • Ersetzen Sie mit einem regulären Ausdruck die ersten beiden Bytes der Adresse.
    • Weisen Sie dem Dienst eine IP-Adresse aus einem neuen Subnetz zu.
    • Erstellen Sie einen Serializer, konvertieren Sie das Go-Objekt in Protobuf, schreiben Sie neue Daten in etcd.

Die Funktion changePodCIDRist im Wesentlichen dieselbe changeServiceCIDR- nur anstatt die Dienstspezifikation zu bearbeiten, tun wir dies für den Knoten und ändern sie .spec.PodCIDRin ein neues Subnetz.

Trainieren


Ändern Sie serviceCIDR


Der Plan für die Implementierung der Aufgabe ist sehr einfach, beinhaltet jedoch Ausfallzeiten zum Zeitpunkt der Neuerstellung aller Pods im Cluster. Nachdem wir die Hauptschritte beschrieben haben, werden wir auch unsere Gedanken darüber teilen, wie dieser einfache theoretisch minimiert werden kann.

Vorbereitende Maßnahmen:

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

Kurzer Aktionsplan zur Änderung von serviceCIDR:

  • Ändern von Manifesten für Apiserver und Controller-Manager
  • Neuausstellung von Zertifikaten;
  • Änderung der ClusterIP-Dienste in etcd;
  • Starten Sie alle Pods in einem Cluster neu.

Das Folgende ist eine vollständige Abfolge von Aktionen im Detail.

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

apt install etcd-client

2. Wir sammeln etcdhelper:

  • Wir setzen 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
  • Wir retten uns etcdhelper.go, laden die Abhängigkeiten, 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. Backup machen 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 Service-Subnetz in den Manifesten der Kubernetes-Steuerebene. In Dateien /etc/kubernetes/manifests/kube-apiserver.yamlund /etc/kubernetes/manifests/kube-controller-manager.yamländern Sie den Parameter --service-cluster-ip-rangein ein neues Subnetz: 172.24.0.0/16stattdessen 192.168.0.0/16.

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

  1. Schauen wir uns an, für welche Domänen und IP-Adressen das aktuelle Zertifikat ausgestellt wird:

    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 Sie die Mindestkonfiguration 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 die alte CRT und den alten Schlüssel, da ohne diese kein neues Zertifikat ausgestellt wird:

    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Stellen Sie Zertifikate für den API-Server erneut aus:

    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. , :

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

    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. admin.conf:

    kubeadm alpha certs renew admin.conf
  8. 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 

    ! , pod' /etc/resolv.conf CoreDNS (kube-dns), kube-proxy iptables . .
  9. ConfigMap' kube-system:

    kubectl -n kube-system edit cm kubelet-config-1.16

    clusterDNS IP- kube-dns: kubectl -n kube-system get svc kube-dns.

    kubectl -n kube-system edit cm kubeadm-config

    data.ClusterConfiguration.networking.serviceSubnet .
  10. kube-dns, kubelet :

    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'

Minimierte Ausfallzeiten


Gedanken zur Minimierung von Ausfallzeiten:

  1. Erstellen Sie nach dem Ändern der Manifestationen der Steuerebene einen neuen kube-dns-Dienst, z. B. mit einem Namen kube-dns-tmpund einer neuen Adresse 172.24.0.10.
  2. Make ifin etcdhelper, wodurch der kube-dns-Dienst nicht geändert wird.
  3. Ersetzen Sie die Adresse in allen Kubelets ClusterDNSdurch eine neue, während der alte Dienst gleichzeitig mit dem neuen Dienst weiterarbeitet.
  4. Warten Sie, bis die Pods mit den Anwendungen aus natürlichen Gründen oder zu einem vereinbarten Zeitpunkt von selbst rollen.
  5. Löschen Sie den Dienst kube-dns-tmpund ändern Sie ihn serviceSubnetCIDRfür den kube-dns-Dienst.

Dieser Plan minimiert Ausfallzeiten von bis zu ~ einer Minute - für die Zeit, in der der Dienst entfernt kube-dns-tmpund das Subnetz für den Dienst ersetzt wird kube-dns.

Modifikation podNetwork


Gleichzeitig haben wir uns entschlossen zu sehen, wie podNetwork mithilfe des resultierenden etcdhelper geändert werden kann. Die Reihenfolge der Aktionen ist wie folgt:

  • wir reparieren Konfigurationen in kube-system;
  • Wir korrigieren das Manifest von kube-controller-manager.
  • wir ändern podCIDR direkt in etcd;
  • Starten Sie alle Knoten des Clusters neu.

Weitere

Informationen zu diesen Aktionen: 1. Ändern Sie ConfigMap im Namespace kube-system:

kubectl -n kube-system edit cm kubeadm-config

- data.ClusterConfiguration.networking.podSubnetin einem neuen Subnetz behoben 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

- Wir korrigieren 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 korrigieren --cluster-cidr=10.55.0.0/16.

3. Schauen Sie sich die aktuellen Werte .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addressesfü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. 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 Sie, 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. Im Gegenzug starten wir alle Knoten des Clusters neu.

7. Wenn mindestens ein Knoten den alten podCIDR verlässt, kann der kube-controller-manager nicht gestartet werden und Pods im Cluster werden nicht geplant.

Tatsächlich kann das Ändern von podCIDR vereinfacht werden (z. B. so ). Wir wollten jedoch lernen, wie man direkt mit etcd arbeitet, da es Fälle gibt, in denen das Bearbeiten von Kubernetes-Objekten in etcd die einzig mögliche Option ist. (Sie können beispielsweise nicht einfach das Feld eines Dienstes ohne Ausfallzeit ändern spec.clusterIP.)

Gesamt


Der Artikel betrachtet die Möglichkeit, direkt mit Daten in etcd zu arbeiten, d. H. Umgehen der Kubernetes-API. Manchmal können Sie mit diesem Ansatz "knifflige Dinge" tun. Die im Text beschriebenen Operationen wurden an realen K8-Clustern getestet. Ihr Status der Bereitschaft zur weit verbreiteten Verwendung ist jedoch PoC (Proof of Concept) . Wenn Sie daher eine modifizierte Version des Dienstprogramms etcdhelper in Ihren Clustern verwenden möchten, tun Sie dies auf eigene Gefahr und Gefahr.

PS


Lesen Sie auch in unserem Blog:


All Articles