VÄr erfarenhet av att arbeta med data i etcd Kubernetes-klustren direkt (utan K8s API)

Allt fler kunder vĂ€nder sig till oss med önskemĂ„l om att fĂ„ tillgĂ„ng till Kubernetes-klustret för att kunna nĂ„ tjĂ€nster inom klustret: sĂ„ att man direkt kan ansluta till en databas eller tjĂ€nst, för att koppla en lokal applikation till applikationer inom klustret


VÄr erfarenhet av att arbeta med data i etcd Kubernetes-klustren direkt (utan K8s API)

Till exempel uppstÄr behovet av att ansluta frÄn sin lokala maskin till tjÀnsten memcached.staging.svc.cluster.local. Vi erbjuder denna möjlighet via VPN inom klustret, som kunden ansluter till. För detta annonserar vi pod-subnÀt, tjÀnster och pushar kluster-DNS till kunden. PÄ sÄ sÀtt, nÀr kunden försöker ansluta till tjÀnsten memcached.staging.svc.cluster.local, skickas en begÀran till kluster-DNS och fÄr som svar adressen till denna tjÀnst frÄn tjÀnsternas nÀtverk eller adressen till pod'en.

K8s-kluster konfigurerar vi med kubeadm, dÀr standard tjÀnstenÀtverket Àr 192.168.0.0/16, och pod-nÀtverket Àr 10.244.0.0/16. Vanligtvis fungerar allt bra, men det finns ett par saker:

  • SubnĂ€tverket 192.168.*.* anvĂ€nds ofta i kundernas kontorsnĂ€tverk, och Ă€nnu oftare i utvecklares hemmarnĂ€tverk. Och dĂ„ fĂ„r vi konflikter: hemmaroutrar arbetar i detta subnĂ€t och VPN pushar dessa subnĂ€t frĂ„n klustret till kunden.
  • Vi har flera kluster (produktionskluster, stage och/eller flera utvecklingskluster). DĂ„ kommer de alla att ha samma standard-subnĂ€t för poddar och tjĂ€nster, vilket skapar stora problem för samtidig anvĂ€ndning av tjĂ€nster i flera kluster.

Vi har sedan lĂ€nge antagit en praxis att anvĂ€nda olika subnĂ€t för tjĂ€nster och poddar inom ett och samma projekt — för att alla kluster skall ha olika nĂ€tverk. Men det finns ett stort antal kluster i drift, som vi inte vill börja om frĂ„n grunden med, eftersom mĂ„nga tjĂ€nster, stateful-applikationer osv. Ă€r igĂ„ng dĂ€r.

Och dÄ stÀllde vi oss frÄgan: hur skulle man kunna Àndra subnÀtet i ett befintligt kluster?

Sök efter lösningar

Det vanligaste tillvÀgagÄngssÀttet Àr att Äterskapa alla tjÀnster med typ ClusterIP. Som ett alternativ kan de rekommendera och sÄ hÀr:

Den följande processen har ett problem: efter att allt Àr konfigurerat kommer poddarna upp med den gamla IP:n som DNS-nameserver i /etc/resolv.conf.
Eftersom jag fortfarande inte hittade lösningen, var jag tvungen att ÄterstÀlla hela klustret med kubeadm reset och initiera det igen.

Men inte alla passar för detta
 HĂ€r Ă€r mer detaljerad information för vĂ„rt fall:

  • Flannel anvĂ€nds;
  • Det finns kluster bĂ„de i molnet och pĂ„ hĂ„rdvara;
  • Vi vill undvika att behöva deploya alla tjĂ€nster i klustret igen;
  • Det finns ett behov av att lösa allt med minimala problem;
  • Kubernetes-versionen Ă€r 1.16.6 (övriga Ă„tgĂ€rder kommer att vara desamma för andra versioner);
  • Huvuduppgiften Ă€r att i klustret, distribuerat med kubeadm med service-subnĂ€tet, 192.168.0.0/16, ersĂ€tta det med 172.24.0.0/16.

Och det rĂ„kar ocksĂ„ vara sĂ„ att vi lĂ€nge varit intresserade av att se vad och hur Kubernetes lagrar i etcd, vad som gĂ„r att göra med detta
 SĂ„ vi tĂ€nkte: "Varför inte bara uppdatera uppgifterna i etcd genom att ersĂ€tta de gamla IP-adresserna (subnĂ€tet) med nya??»

Efter att ha letat efter fĂ€rdiga verktyg för att arbeta med data i etcd, fann vi inget som helt löste den stĂ€llda uppgiften. (Förresten, om ni kĂ€nner till nĂ„gra verktyg för att arbeta med data direkt i etcd — vi skulle uppskatta lĂ€nkar.) En bra utgĂ„ngspunkt blev etcdhelper frĂ„n OpenShift (tack till dess skapare!).

Detta verktyg kan ansluta till etcd med certifikat och lÀsa data dÀrifrÄn med hjÀlp av kommandon. ls, hÀmta, dump.

Vi lÀgger till etcdhelper

Den nÀsta tanken Àr logisk: "Vad hindrar oss frÄn att utöka detta verktyg genom att lÀgga till möjlighet att skriva data i etcd?"

Detta blev en modifierad version av etcdhelper med tvÄ nya funktioner: changeServiceCIDR och changePodCIDR. Koden för den kan ses hÀr.

Vad gör de nya funktionerna? Algoritmen changeServiceCIDR:

  • skapar en deserialiserare;
  • kompilerar ett reguljĂ€rt uttryck för att ersĂ€tta CIDR;
  • gĂ„r igenom alla tjĂ€nster av typen ClusterIP i klustret:
    • avkodar vĂ€rdet frĂ„n etcd till ett Go-objekt;
    • ersĂ€tter de första tvĂ„ bytes av adressen med hjĂ€lp av det reguljĂ€ra uttrycket;
    • tilldelar tjĂ€nsten en IP-adress frĂ„n det nya subnĂ€tet;
    • skapar en serialiserare, omvandlar Go-objektet till protobuf, skriver de nya uppgifterna i etcd.

Funktionen changePodCIDR Ă€r i grunden liknande changeServiceCIDR — bara istĂ€llet för att redigera specifikationerna för tjĂ€nsterna gör vi detta för noden och Ă€ndrar .spec.PodCIDR till det nya subnĂ€tet.

Praxis

Ändring av serviceCIDR

Planen för att genomföra den stÀllda uppgiften Àr mycket enkel, men innebÀr nedetid under ÄterstÀllandet av alla poddar i klustret. Efter att ha beskrivit de grundlÀggande stegen kommer vi ocksÄ att dela vÄra tankar om hur man i teorin kan minimera denna nedetid.

Förberedande ÄtgÀrder:

  • installera nödvĂ€ndig programvara och bygga det patchade etcdhelper;
  • backup av etcd och /etc/kubernetes.

Kort handlingsplan för att Àndra serviceCIDR:

  • Ă€ndra manifest för apiserver och controller-manager;
  • omutfĂ€rda certifikat;
  • Ă€ndring av ClusterIP-tjĂ€nster i etcd;
  • omstart av alla poddar i klustret.

HÀr presenteras hela sekvensen av ÄtgÀrder i detalj.

1. Installera etcd-klienten för att dumpa data:

apt install etcd-client

2. Bygg etcdhelper:

  • Installera 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
  • Spara etcdhelper.go, ladda ner beroenden, bygg:
    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. Skapa en backup av 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. Ändra tjĂ€nstnĂ€tet i Kubernetes control plane-manifesten. I filerna /etc/kubernetes/manifests/kube-apiserver.yaml och /etc/kubernetes/manifests/kube-controller-manager.yaml Ă€ndra parameter --service-cluster-ip-range till det nya nĂ€tet: 172.24.0.0/16 istĂ€llet för 192.168.0.0/16.

5. Eftersom vi Àndrar tjÀnstnÀtet, för vilket kubeadm utfÀrdar certifikat för apiservern (bland annat), mÄste de utfÀrdas pÄ nytt:

  1. LÄt oss titta pÄ vilka domÀner och IP-adresser det nuvarande certifikatet har utfÀrdats för:
    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. Förbered en minimal konfiguration för kubeadm:
    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-adress för master noden
  3. Ta bort gamla crt och key, annars kommer det nya certifikatet inte att utfÀrdas:
    rm \/etc\/kubernetes\/pki\/apiserver.{key,crt}
  4. UtfÀrda certifikat pÄ nytt för API-servern:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. Kontrollera att certifikatet har utfÀrdats för det nya nÀtet:
    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. Efter att certifikatet för API-servern har utfÀrdats pÄ nytt, starta om dess container:
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. Regenerera konfigurationen för admin.conf:
    kubeadm alpha certs renew admin.conf
  8. Redigera data i 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 

    Varning! I detta ögonblick slutar domÀnupplösningen att fungera i klustret, eftersom de redan existerande poddarna har /etc/resolv.conf den gamla adressen för CoreDNS (kube-dns) instÀlld, och kube-proxy har Àndrat iptables-reglerna frÄn det gamla subnÀtet till det nya. Artikeln beskriver sedan möjliga alternativ för att minimera driftsstopp.

  9. Vi justerar ConfigMap:ar i namnomrÄdet kube-system:
    kubectl -n kube-system edit cm kubelet-config-1.16

    — hĂ€r kommer vi att byta ut clusterDNS mot den nya IP-adressen för kube-dns-tjĂ€nsten: kubectl -n kube-system get svc kube-dns.

    kubectl -n kube-system edit cm kubeadm-config

    — vi kommer att korrigera data.ClusterConfiguration.networking.serviceSubnet till det nya subnĂ€tet.

  10. Eftersom adressen för kube-dns har Àndrats Àr det nödvÀndigt att uppdatera kubelet-konfigurationen pÄ alla noder:
    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. Det ÄterstÄr att starta om alla poddar i klustret:
    kubectl get pods --no-headers=true --all-namespaces | sed -r 's\/(S+)s+(S+).*\/kubectl --namespace \1 delete pod \2\/e'

Minimera driftsstopp

Fundera pÄ hur man kan minimera nedetid:

  1. Efter förÀndringarna av kontrollplansmanifesten, skapa en ny kube-dns-tjÀnst, till exempel med namnet kube-dns-tmp och den nya adressen 172.24.0.10.
  2. Gör if i etcdhelper, som inte kommer att modifiera kube-dns-tjÀnsten.
  3. Byt ut adressen i alla kubeletar ClusterDNS till den nya, medan den gamla tjÀnsten fortsÀtter att fungera samtidigt som den nya.
  4. VÀnta pÄ att poddar med ansökningar ska rullas ut antingen av sig sjÀlva av naturliga skÀl, eller vid en överenskommet tid.
  5. Ta bort tjÀnsten kube-dns-tmp och Àndra serviceSubnetCIDR för kube-dns-tjÀnsten.

Denna plan kommer att minimera nedetiden till ~en minut – tiden det tar att ta bort tjĂ€nsten kube-dns-tmp och byta subnet för tjĂ€nsten Det mest intressanta och nya för mig var sjĂ€lva anledningen till den betydande ökningen av DNS-förfrĂ„gningar. Om detta och vad man ska göra Ă„t det handlar mitt inlĂ€gg..

Modifiering av podNetwork

Samtidigt har vi beslutat att se hur man modifierar podNetwork med hjÀlp av den resulterande etcdhelper. Stegen blir följande:

  • Vi justerar konfigurationerna i kube-system;
  • vi justerar kube-controller-manager-manifestet;
  • vi Ă€ndrar podCIDR direkt i etcd;
  • vi startar om alla noder i klustret.

Nu mer detaljerat om dessa ÄtgÀrder:

1. Modifiera ConfigMap:ar i namnomrÄdet kube-system:

kubectl -n kube-system edit cm kubeadm-config

— vi justerar data.ClusterConfiguration.networking.podSubnet till det nya subnĂ€tet 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

— vi justerar data.config.conf.clusterCIDR: 10.55.0.0/16.

2. Modifiera controller-manager-manifestet:

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

— vi justerar --cluster-cidr=10.55.0.0/16.

3. Kolla aktuella vÀrden .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addresses för alla noder i klustret:

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. Vi byter ut podCIDR genom att göra Àndringar direkt i 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. Vi kontrollerar att podCIDR verkligen har Àndrats:

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. Vi startar om alla noder i klustret en i taget.

7. Om ens en nod lÀmnas med den gamla podCIDR, kan kube-controller-manager inte startas, vilket innebÀr att poddar i klustret inte kommer att schemalÀggas.

Ändringen av podCIDR kan faktiskt göras enklare (till exempel, sĂ„). Men vi ville ju lĂ€ra oss att arbeta direkt med etcd, för det finns tillfĂ€llen nĂ€r det kan vara nödvĂ€ndigt att redigera Kubernetes-objekt i etcd — den enda en möjlig lösning. (Till exempel kan man inte bara Ă€ndra ett Service-fĂ€lt utan att fĂ„ nedtid, spec.clusterIP.)

Resultat

Denna artikel undersöker möjligheten att arbeta med data direkt i etcd, det vill sĂ€ga utan Kubernetes API. Ibland gör detta tillvĂ€gagĂ„ngssĂ€tt det möjligt att göra "smartare grejer". De operationer som presenteras har testats pĂ„ verkliga K8s-kluster. Deras status för bred anvĂ€ndning Ă€r — PoC (proof of concept). DĂ€rför, om du vill anvĂ€nda en modifierad version av verktyget etcdhelper pĂ„ dina kluster, gör det pĂ„ egen risk.

P.S.

LÀs Àven i vÄr blogg:

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster