Onze ervaring met data in de etcd Kubernetes-cluster rechtstreeks (zonder K8s API)

Steeds vaker wenden klanten zich tot ons met het verzoek om toegang tot de Kubernetes-cluster, zodat ze interne services kunnen benaderen: direct verbinding kunnen maken met een database of service, voor de verbinding van een lokale applicatie met applicaties binnen de cluster…

Onze ervaring met data in de etcd Kubernetes-cluster rechtstreeks (zonder K8s API)

Bijvoorbeeld, er is behoefte om vanaf je lokale machine verbinding te maken met de service memcached.staging.svc.cluster.local. Wij bieden deze mogelijkheid middels een VPN binnen de cluster, waar de klant verbinding mee maakt. Hiervoor adverteren we de subnetten van pods, services en pushen we de cluster DNS naar de klant. Zo, wanneer de klant probeert verbinding te maken met de service memcached.staging.svc.cluster.local, wordt het verzoek gestuurd naar de cluster DNS en ontvangt het in ruil de adres van deze service uit het service-netwerk van de cluster of het adres van de pod.

K8s-clusters configureren we met kubeadm, waarbij de standaard service subnet is 192.168.0.0/16, en het pod-netwerk is 10.244.0.0/16. Normaal gesproken werkt alles goed, maar er zijn een paar punten:

  • Het subnet 192.168.*.* wordt vaak gebruikt in de netwerken van klanten, en nog vaker - in de netwerken van ontwikkelaars thuis. En dan krijgen we conflicten: thuisrouters werken in dit subnet en VPN push't deze subnetten vanuit de cluster naar de klant.
  • We hebben verschillende clusters (productie-, stage- en/of verschillende dev-clusters). Dan zullen in al deze clusters standaard dezelfde subnets voor pods en services zijn, wat grote complicaties creëert voor het gelijktijdig werken met services in meerdere clusters.

We hebben al geruime tijd de praktijk aangenomen om verschillende subnetten voor services en pods te gebruiken binnen een project - over het algemeen zodat alle clusters verschillende netwerken hebben. Er zijn echter een groot aantal clusters in gebruik die we niet vanaf nul willen opnieuw opzetten, aangezien hierin veel services, stateful-applicaties, enz. draaien.

En toen stelden we ons de vraag: hoe kunnen we het subnet in een bestaande cluster wijzigen?

Zoektocht naar oplossingen

De meest voorkomende praktijk is om de all services met het type ClusterIP opnieuw aan te maken. Als alternatief, kan men adviseren en zoiets:

Het volgende proces heeft een probleem: nadat alles is geconfigureerd, komen de pods met het oude IP als DNS-nameserver in /etc/resolv.conf.
Aangezien ik de oplossing nog steeds niet heb gevonden, moest ik de hele cluster resetten met kubeadm reset en het opnieuw initialiseren.

Maar niet iedereen vindt dit geschikt… Hier zijn meer details voor onze situatie:

  • Flannel wordt gebruikt;
  • Er zijn clusters zowel in de cloud als op hardware;
  • We zouden graag een herdeploiement van alle services in het cluster willen vermijden.
  • Er is behoefte om alles met een minimum aan problemen te doen.
  • De versie van Kubernetes is 1.16.6 (de verdere stappen zijn eigenlijk vergelijkbaar voor andere versies).
  • De belangrijkste taak is om in het cluster, dat is uitgerold met kubeadm met een service subnet, 192.168.0.0/16, deze te vervangen door 172.24.0.0/16.

En het toeval wil dat we al een tijd geïnteresseerd zijn in wat en hoe Kubernetes in etcd opslaat, en wat we daarmee kunnen doen... Dus dachten we: "Waarom zouden we gewoon de gegevens in etcd niet bijwerken, door de oude IP-adressen (subnet) te vervangen door de nieuwe??»

Na het zoeken naar kant-en-klare hulpmiddelen voor het werken met gegevens in etcd, vonden we niets dat het probleem volledig oploste. (Trouwens, als je kent van enige tools voor het direct werken met gegevens in etcd - we zouden dankbaar zijn voor de links.) Een goede uitgangspunt werd echter etcdhelper van OpenShift (dank aan de auteurs!).

Dit hulpmiddel kan verbinding maken met etcd met behulp van certificaten en gegevens daaruit lezen met behulp van commando's. ls, krijgen, dump.

We breiden etcdhelper uit.

De volgende gedachte is logisch: "Wat houdt ons tegen om deze tool uit te breiden en de mogelijkheid toe te voegen om gegevens in etcd te schrijven?"

Dit resulteerde in een gemodificeerde versie van etcdhelper met twee nieuwe functies. changeServiceCIDR en changePodCIDR. De code kan worden bekeken hier.

Wat doen de nieuwe functies? Het algoritme changeServiceCIDR:

  • maakt een deserializer aan;
  • compileert een reguliere expressie om de CIDR te vervangen;
  • loopt door alle services met het type ClusterIP in het cluster:
    • decodeert de waarde uit etcd naar een Go-object;
    • vervangt de eerste twee bytes van het adres met behulp van de reguliere expressie;
    • wijst het service IP-adres toe uit het nieuwe subnet;
    • maakt een serializer aan, zet het Go-object om naar protobuf, en schrijft de nieuwe gegevens in etcd.

Functie changePodCIDR is in wezen vergelijkbaar changeServiceCIDR — alleen in plaats van het specificeren van services te bewerken, doen we dit voor de knooppunt en veranderen .spec.PodCIDR in het nieuwe subnet.

Praktijk

Verandering van serviceCIDR

Het plan voor de uitvoering van de taak is heel eenvoudig, maar impliceert downtime op het moment van het opnieuw creëren van alle pods in het cluster. Na de beschrijving van de belangrijkste stappen zullen we ook gedachten delen over hoe deze downtime in theorie kan worden geminimaliseerd.

Voorbereidende acties:

  • installatie van benodigde software en het samenstellen van de gepatchte etcdhelper;
  • back-up van etcd en /etc/kubernetes.

Kort plan van aanpak voor het wijzigen van serviceCIDR:

  • wijziging van de manifesten van de apiserver en controller-manager;
  • heruitgave van certificaten;
  • wijziging van de ClusterIP-services in etcd;
  • herstart alle pods in het cluster.

Hieronder wordt de volledige volgorde van acties in detail weergegeven.

1. Installeer de etcd-client voor de gegevensdump:

apt install etcd-client

2. Verzamel etcdhelper:

  • Installeer 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
  • Opslaan als etcdhelper.go, laad afhankelijkheden, bouw:
    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. Maak een back-up van 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. Wijzig het service-netwerk in de Kubernetes control plane manifesten. In de bestanden /etc/kubernetes/manifests/kube-apiserver.yaml en /etc/kubernetes/manifests/kube-controller-manager.yaml wijzig de parameter --service-cluster-ip-range naar het nieuwe netwerk: 172.24.0.0/16 in plaats van 192.168.0.0/16.

5. Aangezien we het service-netwerk wijzigen waarop kubeadm certificaten voor de apiserver uitgeeft (inclusief dit), moeten ze worden heruitgegeven:

  1. Laten we kijken naar welke domeinen en IP-adressen het huidige certificaat is uitgegeven:
    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. Laten we een minimale configuratie voor kubeadm voorbereiden:
    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-adres van de master node
  3. Verwijder de oude crt en key, want zonder deze wordt het nieuwe certificaat niet uitgegeven:
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Heruitgeven van certificaten voor de API-server:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. Controleer of het certificaat is uitgegeven voor het nieuwe netwerk:
    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. Na het heruitgeven van het certificaat van de API-server, herstarten we zijn container:
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. Genereer de configuratie opnieuw voor admin.conf:
    kubeadm alpha certs renew admin.conf
  8. Bewerk de gegevens 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 

    Let op! Op dit moment stopt de domeinnaamresolutie in de cluster, omdat in de reeds bestaande pod's het oude adres van CoreDNS (kube-dns) is ingesteld, terwijl kube-proxy de iptables-regels heeft veranderd van het oude subnet naar het nieuwe. Verder in het artikel worden mogelijkheden beschreven om stilstand te minimaliseren. /etc/resolv.conf Laten we de ConfigMaps in de namespace aanpassen.

  9. kubectl -n kube-system edit cm kubelet-config-1.16 kube-system:
    — hier vervangen we

    clusterDNS door het nieuwe IP-adres van de kube-dns service: kubectl -n kube-system get svc kube-dns kubectl -n kube-system edit cm kubeadm-config.

    — we passen

    data.ClusterConfiguration.networking.serviceSubnet aan, omdat het adres van kube-dns is veranderd, moeten we de kubelet-config op alle knooppunten bijwerken: in het nieuwe subnet.

  10. kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
    Het enige dat overblijft, is alle pod's in de cluster opnieuw opstarten:
  11. kubectl get pods --no-headers=true --all-namespaces |sed -r 's/(S+)s+(S+).*//kubectl --namespace 1 delete pod 2/e'
    Stilstand minimaliseren

Denk na over manieren om de downtime te minimaliseren:

Na wijzigingen aan de manifesten van het control plane, een nieuwe kube-dns service aanmaken, bijvoorbeeld met de naam

  1. kube-dns-tmp en een nieuw adres. Maak 172.24.0.10.
  2. in etcdhelper, dat de kube-dns service niet zal wijzigen. als Vervang in alle kubelets het adres
  3. ClusterDNS door het nieuwe, terwijl de oude service tegelijkertijd blijft werken. Wacht tot de pod's met applicaties opnieuw zijn uitgerold, of vanzelf door natuurlijke oorzaken, of op een afgesproken tijd.
  4. Verwijder de service
  5. en wijzig en een nieuw adres. serviceSubnetCIDR voor de kube-dns service. Dit plan stelt ons in staat om de downtime te minimaliseren tot ~één minuut — de tijd die nodig is voor het verwijderen van de service

en het vervangen van het subnet voor de service. en een nieuw adres. Wijziging van podNetwork kube-dns.

Onderwijl hebben we ook gekeken naar hoe we podNetwork kunnen wijzigen met behulp van de resulterende etcdhelper. De reeks acties is als volgt:

we passen de configuraties aan in

  • we corrigeren het kube-controller-manager manifest; kube-system;
  • we passen podCIDR direct in etcd aan;
  • we herstarten alle knooppunten van de cluster.
  • Nu meer over deze acties:

1. We modificeren de ConfigMaps in de namespace

— we corrigeren kube-system:

— we passen

data.ClusterConfiguration.networking.podSubnet naar het nieuwe subnet kubectl -n kube-system edit cm kube-proxy 10.55.0.0/16.

data.config.conf.clusterCIDR: 10.55.0.0/16

data.ClusterConfiguration.networking.podSubnet 2. We modifieren het controller-manager manifest:.

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

--cluster-cidr=10.55.0.0/16

data.ClusterConfiguration.networking.podSubnet 3. We kijken naar de huidige waarden.

.spec.podCIDR .spec.podCIDRs, .InternalIP, .status.addresses, voor alle knooppunten in de cluster: kubectl get no -o json | jq '[.items[] | {"name": .metadata.name, "podCIDR": .spec.podCIDR, "podCIDRs": .spec.podCIDRs, "InternalIP": (.status.addresses[] | select(.type == "InternalIP") | .address)}]'

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. We will change podCIDR by making adjustments directly 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. We will check that podCIDR has indeed changed:

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. We will sequentially restart all nodes in the cluster.

7. If at least one node retains the old podCIDR, kube-controller-manager will not be able to start, and pods in the cluster will not be scheduled.

In fact, changing the podCIDR can be done more simply (for example, zo). But we wanted to learn how to work with etcd directly, as there are cases where editing Kubernetes objects in etcd is — het enige a viable option. (For instance, you can't just change the field for Service spec.clusterIP.)

Conclusie

This article discusses the possibility of working with data in etcd directly, i.e., bypassing the Kubernetes API. Sometimes this approach allows for ‘clever tricks’. The operations described in the text have been tested on real K8s clusters. However, their readiness status for widespread use is — PoC (proof of concept). Therefore, if you wish to use a modified version of the etcdhelper utility on your clusters, do so at your own risk.

P.S.

Lees ook op onze blog:

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster