Onze ervaring met data in etcd Kubernetes cluster direct (zonder K8s API)

Klanten vragen ons steeds vaker om toegang te verlenen tot het Kubernetes-cluster om toegang te krijgen tot diensten binnen het cluster: zodat ze rechtstreeks verbinding kunnen maken met een database of dienst, om een ​​lokale applicatie te verbinden met applicaties binnen het cluster...

Onze ervaring met data in etcd Kubernetes cluster direct (zonder K8s API)

Het is bijvoorbeeld nodig om vanaf uw lokale machine verbinding te maken met een dienst memcached.staging.svc.cluster.local. Wij bieden deze mogelijkheid met behulp van een VPN binnen het cluster waarmee de client verbinding maakt. Om dit te doen, kondigen we subnetten van pods, services aan en pushen we cluster-DNS naar de client. Dus wanneer een client verbinding probeert te maken met de dienst memcached.staging.svc.cluster.local, gaat het verzoek naar de cluster-DNS en ontvangt als reactie het adres van deze service van het clusterservicenetwerk of het pod-adres.

We configureren K8s-clusters met behulp van kubeadm, waar het standaard service-subnet is 192.168.0.0/16, en het netwerk van pods is dat ook 10.244.0.0/16. Meestal werkt alles goed, maar er zijn een paar punten:

  • Subnet 192.168.*.* vaak gebruikt in klantkantoornetwerken, en nog vaker in thuisnetwerken van ontwikkelaars. En dan krijgen we conflicten: thuisrouters werken op dit subnet en de VPN pusht deze subnetten van het cluster naar de client.
  • We beschikken over meerdere clusters (productie-, podium- en/of meerdere dev-clusters). Dan hebben ze standaard allemaal dezelfde subnetten voor pods en services, wat grote problemen oplevert bij het gelijktijdig werken met services in verschillende clusters.

We hebben al lang geleden de gewoonte overgenomen om verschillende subnetten te gebruiken voor services en pods binnen hetzelfde project - in het algemeen, zodat alle clusters verschillende netwerken hebben. Er zijn echter een groot aantal clusters in gebruik die ik niet helemaal opnieuw zou willen overzetten, omdat ze veel services, stateful applicaties, enz. uitvoeren.

En toen vroegen we ons af: hoe kunnen we het subnet in een bestaand cluster veranderen?

Zoeken naar beslissingen

De meest voorkomende praktijk is recreëren alle services met het type ClusterIP. Als een optie, kan adviseren en dit:

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

Maar dit is niet voor iedereen geschikt... Hier zijn meer gedetailleerde introducties voor onze case:

  • Flanel wordt gebruikt;
  • Er zijn clusters zowel in de cloud als op hardware;
  • Ik wil voorkomen dat alle services in het cluster opnieuw worden geïmplementeerd;
  • Er is behoefte om over het algemeen alles te doen met een minimum aan problemen;
  • Kubernetes-versie is 1.16.6 (verdere stappen zullen echter vergelijkbaar zijn voor andere versies);
  • De belangrijkste taak is om ervoor te zorgen dat in een cluster dat wordt geïmplementeerd met behulp van kubeadm met een servicesubnet 192.168.0.0/16, vervang het door 172.24.0.0/16.

En het gebeurde zo dat we al lang geïnteresseerd waren om te zien wat en hoe in Kubernetes wordt opgeslagen in etcd, wat ermee kan worden gedaan... Dus we dachten: “Waarom update je niet gewoon de gegevens in etcd, waarbij je de oude IP-adressen (subnet) vervangt door nieuwe? '

Nadat we naar kant-en-klare tools hadden gezocht voor het werken met gegevens in etcd, hebben we niets gevonden dat het probleem volledig oploste. (Trouwens, als u hulpprogramma's kent om rechtstreeks met gegevens in etcd te werken, zouden we de links op prijs stellen.) Een goed uitgangspunt is echter wel etcdhelper van OpenShift (met dank aan de auteurs!).

Dit hulpprogramma kan verbinding maken met etcd met behulp van certificaten en van daaruit gegevens lezen met behulp van opdrachten ls, get, dump.

Voeg etcdhelper toe

De volgende gedachte is logisch: “Wat houdt je tegen om dit hulpprogramma toe te voegen door de mogelijkheid toe te voegen om gegevens naar etcd te schrijven?”

Het werd een aangepaste versie van etcdhelper met twee nieuwe functies changeServiceCIDR и changePodCIDR. op haar je kunt de code zien hier.

Wat doen de nieuwe functies? Algoritme changeServiceCIDR:

  • maak een deserializer;
  • compileer een reguliere expressie om CIDR te vervangen;
  • we doorlopen alle services met het ClusterIP-type in het cluster:
    • de waarde van etcd decoderen in een Go-object;
    • met behulp van een reguliere expressie vervangen we de eerste twee bytes van het adres;
    • wijs de service een IP-adres toe vanuit het nieuwe subnet;
    • maak een serialisator, converteer het Go-object naar protobuf, schrijf nieuwe gegevens naar etcd.

Functie changePodCIDR in wezen vergelijkbaar changeServiceCIDR - alleen in plaats van de servicespecificatie te bewerken, doen we dit voor het knooppunt en de wijziging .spec.PodCIDR naar een nieuw subnet.

Praktijk

Wijzig service CIDR

Het plan voor het implementeren van de taak is heel eenvoudig, maar brengt downtime met zich mee op het moment dat alle pods in het cluster opnieuw worden gemaakt. Nadat we de belangrijkste stappen hebben beschreven, zullen we ook van gedachten wisselen over hoe deze downtime in theorie kan worden geminimaliseerd.

Voorbereidende stappen:

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

Kort actieplan voor het wijzigen van serviceCIDR:

  • het wijzigen van de apiserver- en controller-manager-manifesten;
  • heruitgave van certificaten;
  • het wijzigen van ClusterIP-services in etcd;
  • opnieuw opstarten van alle peulen in het cluster.

Hieronder volgt een volledige reeks acties in detail.

1. Installeer etcd-client voor datadump:

apt install etcd-client

2. Bouw etcdhelper:

  • Golang installeren:
    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
  • Wij sparen voor onszelf etcdhelper.go, download afhankelijkheden, verzamel:
    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 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 servicesubnet in de Kubernetes-besturingsvlakmanifesten. In bestanden /etc/kubernetes/manifests/kube-apiserver.yaml и /etc/kubernetes/manifests/kube-controller-manager.yaml verander de parameter --service-cluster-ip-range naar een nieuw subnet: 172.24.0.0/16 in plaats van 192.168.0.0/16.

5. Omdat we het servicesubnet wijzigen waarnaar kubeadm certificaten uitgeeft voor apiserver (inclusief), moeten deze opnieuw worden uitgegeven:

  1. Laten we eens kijken voor 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-адрес мастер узла
  3. Laten we de oude crt en sleutel verwijderen, want zonder deze wordt het nieuwe certificaat niet uitgegeven:
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Laten we certificaten voor de API-server opnieuw uitgeven:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. Laten we controleren of het certificaat is uitgegeven voor het nieuwe subnet:
    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. Nadat u het API-servercertificaat opnieuw hebt uitgegeven, start u de container opnieuw op:
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. Laten we de configuratie voor opnieuw genereren admin.conf:
    kubeadm alpha certs renew admin.conf
  8. Laten we de gegevens in etcd bewerken:
    ./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 

    Waarschuwing! Op dit moment werkt de domeinresolutie niet meer in het cluster, omdat dit in bestaande pods gebeurt /etc/resolv.conf het oude CoreDNS-adres (kube-dns) is geregistreerd en kube-proxy verandert de iptables-regels van het oude subnet naar het nieuwe. Verderop in het artikel wordt geschreven over mogelijke opties om downtime te minimaliseren.

  9. Laten we ConfigMap's in de naamruimte repareren kube-system:
    kubectl -n kube-system edit cm kubelet-config-1.16

    - hier vervangen clusterDNS naar 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

    - wij repareren het data.ClusterConfiguration.networking.serviceSubnet naar een nieuw subnet.

  10. Omdat het kube-dns-adres is gewijzigd, is het noodzakelijk om de kubelet-configuratie op alle knooppunten bij te werken:
    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. Het enige dat overblijft is het opnieuw opstarten van alle peulen in het cluster:
    kubectl get pods --no-headers=true --all-namespaces |sed -r 's/(S+)s+(S+).*/kubectl --namespace 1 delete pod 2/e'

Minimaliseer stilstand

Gedachten over hoe u downtime kunt minimaliseren:

  1. Nadat u de manifesten van het besturingsvlak hebt gewijzigd, maakt u bijvoorbeeld een nieuwe kube-dns-service met de naam kube-dns-tmp en nieuw adres 172.24.0.10.
  2. Doen if in etcdhelper, wat de kube-dns-service niet zal wijzigen.
  3. Vervang het adres in alle kubelets ClusterDNS naar een nieuwe, terwijl de oude dienst gelijktijdig met de nieuwe blijft werken.
  4. Wacht tot de peulen met applicaties om natuurlijke redenen vanzelf omrollen of op een afgesproken tijdstip.
  5. Dienst verwijderen kube-dns-tmp en verandering serviceSubnetCIDR voor de kube-dns-service.

Met dit plan kunt u de downtime tot ~een minuut minimaliseren, gedurende de duur van de serviceverwijdering kube-dns-tmp en het wijzigen van het subnet voor de service kube-dns.

Wijziging podNetwerk

Tegelijkertijd besloten we te kijken hoe we podNetwork konden aanpassen met behulp van de resulterende etcdhelper. De volgorde van acties is als volgt:

  • configuraties repareren in kube-system;
  • het kube-controller-manager-manifest repareren;
  • wijzig podCIDR rechtstreeks in etcd;
  • start alle clusterknooppunten opnieuw op.

Nu meer over deze acties:

1. Wijzig ConfigMap's in de naamruimte kube-system:

kubectl -n kube-system edit cm kubeadm-config

- corrigeren data.ClusterConfiguration.networking.podSubnet naar een nieuw subnet 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

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

2. Wijzig het controller-manager-manifest:

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

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

3. Kijk naar de huidige waarden .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addresses voor alle clusterknooppunten:

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. Vervang podCIDR door rechtstreeks wijzigingen aan te brengen 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. Laten we controleren of podCIDR echt is veranderd:

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. Laten we alle clusterknooppunten één voor één opnieuw opstarten.

7. Als u ten minste één knooppunt verlaat oude podCIDR, dan kan kube-controller-manager niet starten en worden pods in het cluster niet gepland.

In feite kan het wijzigen van podCIDR nog eenvoudiger worden gedaan (bijvoorbeeld zo). Maar we wilden rechtstreeks met etcd leren werken, omdat er gevallen zijn waarin Kubernetes-objecten in etcd worden bewerkt - единственный mogelijke variant. (U kunt bijvoorbeeld niet zomaar het veld Service wijzigen zonder downtime spec.clusterIP.)

Totaal

Het artikel bespreekt de mogelijkheid om rechtstreeks met gegevens in etcd te werken, d.w.z. het omzeilen van de Kubernetes API. Soms kun je met deze aanpak ‘lastige dingen’ doen. We hebben de bewerkingen in de tekst getest op echte K8s-clusters. Hun status van gereedheid voor wijdverbreid gebruik is dat echter wel PoC (proof-of-concept). Als u daarom een ​​aangepaste versie van het hulpprogramma etcdhelper op uw clusters wilt gebruiken, doet u dit op eigen risico.

PS

Lees ook op onze blog:

Bron: www.habr.com

Voeg een reactie