Naša skúsenosť s prácou s údajmi v etcd klastre Kubernetes priamo (bez K8s API)

Čoraz častejšie sa na nás obracajú klienti s požiadavkou zabezpečiť prístup do Kubernetes klasteru, aby mohli komunikovať so službami v rámci klasteru: aby bolo možné priamo sa pripojiť k nejakej databáze alebo službe, pre spojenie lokálnej aplikácie s aplikáciami vo vnútri klasteru…

Naša skúsenosť s prácou s údajmi v etcd klastre Kubernetes priamo (bez K8s API)

Napríklad sa objaví potreba pripojiť sa so svojho lokálneho počítača k službe memcached.staging.svc.cluster.local. Zabezpečujeme túto možnosť pomocou VPN v rámci klasteru, ku ktorému sa klient pripája. Na tento účel oznámime podsiete podov, služieb a posunieme klientovi klasterové DNS. Takže, keď sa klient pokúsi pripojiť k službe memcached.staging.svc.cluster.local, dopyt prechádza do DNS klasteru a v odpovedi dostáva adresu danej služby zo služobnej siete klasteru alebo adresu podu.

K8s klastry nastavujeme pomocou kubeadm, kde je predvolená služobná podsieť — 192.168.0.0/16, a sieť podov — 10.244.0.0/16. Zvyčajne všetko dobre funguje, ale sú tu niektoré momenty:

  • Podsieť 192.168.*.* sa často používa v kancelárskych sieťach klientov, a ešte častejšie — v domácich sieťach vývojárov. A potom vznikajú konflikty: domáce routery fungujú v tejto podsieti a VPN posúva tieto podsiete z klasteru klientovi.
  • Máme niekoľko klastrov (produkčné, stage a/alebo niekoľko dev klastrov). Potom budú vo všetkých z nich predvolene rovnaké podsiete pre podov a služby, čo spôsobuje veľké ťažkosti pre súčasnú prácu so službami vo viacerých klastroch.

Už dlhší čas sme prijali prax používania rôznych podsietí pre služby a pod v rámci jedného projektu — aby mali všetky klastre rôzne siete. Avšak existuje veľké množstvo klastrov v prevádzke, ktoré by sme nechceli preinštalovať od nuly, pretože v nich beží množstvo služieb, stavové aplikácie a pod.

A tak sme si položili otázku: ako zmeniť podsieť v existujúcom klasteri?

Hľadanie riešení

Najbežnejšia prax — znovu vytvoriť všetky služby typu ClusterIP. Ako alternatívu, môžu odporučiť a aj toto:

Nasledujúci proces má problém: po nakonfigurovaní všetkého sa pady spúšťajú so starou IP ako DNS menný server v /etc/resolv.conf.
Keďže som ešte nenašiel riešenie, musel som resetovať celý klaster pomocou kubeadm reset a znova ho nastaviť.

Ale nie všetkým to vyhovuje… Tu sú podrobnejšie vstupy pre náš prípad:

  • Používa sa Flannel;
  • Existujú klastre ako v cloude, tak aj na železe;
  • Chceli by sme sa vyhnúť opätovnému nasadeniu všetkých služieb v klustre;
  • Existuje potreba spraviť všetko s minimálnym počtom problémov;
  • Verzia Kubernetes — 1.16.6 (však, ďalšie kroky budú podobné aj pre iné verzie);
  • Hlavná úloha spočíva v tom, aby sa v klustre, nasadenom pomocou kubeadm so servisnou podsieťou 192.168.0.0/16, nahradila na 172.24.0.0/16.

A tak sa stalo, že nás už dlho zaujímalo, čo a ako sa v Kubernetes ukladá v etcd, čo s tým vôbec môžeme urobiť... Tak sme si povedali: „Prečo jednoducho neaktualizovať údaje v etcd, nahradením starých IP adries (podsieť) na nové?»

Hľadali sme hotové nástroje na prácu s údajmi v etcd, nič úplne riešiace danú úlohu sme nenašli. (Mimochodom, ak poznáte akékoľvek utilitky na prácu s údajmi priamo v etcd — budeme vďační za odkazy.) Avšak dobrým východiskovým bodom bol etcdhelper od OpenShift (ďakujeme jeho autorom!).

Tento nástroj dokáže pripojiť k etcd pomocou certifikátov a prečítať z neho údaje pomocou príkazov ls, get, dump.

Dopíšeme etcdhelper

Ďalšia myšlienka je logická: „Čo bráni dopísať tento nástroj, pridaním možnosti zápisu údajov do etcd?“

Bola realizovaná v upravenej verzii etcdhelper s dvoma novými funkciami changeServiceCIDR a changePodCIDR. Jej kód si môžete pozrieť tu.

Čo robia nové funkcie? Algoritmus changeServiceCIDR:

  • vytvárame deserializátor;
  • kompilujeme regulárny výraz na výmenu CIDR;
  • prechádzame všetky služby s typom ClusterIP v klustre:
    • dekódujeme hodnotu z etcd do Go-objektu;
    • s pomocou regulárneho výrazu nahrádzame prvé dva bajty adresy;
    • priraďujeme službe IP adresu z novej podsiete;
    • vytvárame serializátor, prevádzame Go-objekt na protobuf, zapisujeme nové údaje do etcd.

Funkcia changePodCIDR v podstate je to podobné changeServiceCIDR — len namiesto úpravy špecifikácií služieb to robíme pre uzol a meníme .spec.PodCIDR na novú podsieť.

Praktika

Zmena serviceCIDR

Plán na realizáciu stanoveného cieľa je veľmi jednoduchý, ale predpokladá prestoje v okamihu opätovného vytvorenia všetkých podov v klustre. Po opise hlavných krokov sa tiež podelíme o myšlienky, ako teoreticky minimalizovať tento prestoj.

Prípravné kroky:

  • inštalácia potrebného softvéru a zostavenie opraveného etcdhelper;
  • záloha etcd a /etc/kubernetes.

Stručný plán krokov na zmenu serviceCIDR:

  • úprava manifestov apiserver'a a controller-manager'a;
  • nové vydanie certifikátov;
  • zmena ClusterIP služieb v etcd;
  • reštart všetkých pod’ov v klasteri.

Nasleduje úplná postupnosť krokov v detailoch.

1. Nainštalujeme etcd-client pre dump dát:

apt install etcd-client

2. Zostavíme etcdhelper:

  • Nainštalujeme 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
  • Uložíme si etcdhelper.go, stiahneme závislosti, zostavíme:
    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. Urobíme zálohu 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. Zmienia sa sieťová podsieť služieb v manifestoch Kubernetes control plane. V súboroch /etc/kubernetes/manifests/kube-apiserver.yaml a /etc/kubernetes/manifests/kube-controller-manager.yaml meníme parameter --service-cluster-ip-range na novú podsieť: 172.24.0.0/16 namiesto 192.168.0.0/16.

5. Keďže meníme sieťovú podsieť, na ktorú kubeadm vydáva certifikáty pre apiserver (vrátane), musíme ich opätovne vydať:

  1. Pozrieme sa, na aké domény a IP adresy bol aktuálny certifikát vydaný:
    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. Pripravíme minimálny config pre 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 adresa master uzla
  3. Odstránime staré crt a key, pretože bez toho nový certifikát nebude vydaný:
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Opätovne vydáme certifikáty pre API server:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. Skontrolujeme, či bol certifikát vydaný pre novú podsieť:
    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. Po opätovnom vydaní certifikátu API servera reštartujeme jeho kontajner:
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. Opätovne generujeme config pre admin.conf:
    kubeadm alpha certs renew admin.conf
  8. Upravíme dáta v 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 

    Pozor! V tejto chvíli v klastri prestáva fungovať resolvovanie domén, pretože v už existujúcich pod’och je /etc/resolv.conf zadaná stará adresa CoreDNS (kube-dns), a kube-proxy zmenil pravidlá iptables zo starej podsiete na novú. Ďalej v článku sú uvedené možnosti, ako minimalizovať prestoj.

  9. Upravíme ConfigMapy v priestore názvov kube-system:
    kubectl -n kube-system edit cm kubelet-config-1.16

    — tu nahradíme clusterDNS novou IP adresou služby kube-dns: kubectl -n kube-system get svc kube-dns.

    kubectl -n kube-system edit cm kubeadm-config

    — opravíme data.ClusterConfiguration.networking.serviceSubnet na novú podsieť.

  10. Keďže sa adresa kube-dns zmenila, je potrebné aktualizovať konfiguračný súbor kubelet na všetkých uzloch:
    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. Zostáva reštartovať všetky pod’y v klastri:
    kubectl get pods --no-headers=true --all-namespaces | sed -r 's\/(S+)s+(S+).*\/kubectl --namespace 1 delete pod 2\/e'

Minimalizácia prestoja

Myšlienky, ako minimalizovať downtime:

  1. Po zmenách manifestov control plane vytvorte novú službu kube-dns, napríklad s názvom kube-dns-tmp a novou adresou 172.24.0.10.
  2. Urobiť ak v etcdhelper, ktorý nebude modifikovať službu kube-dns.
  3. Nahradiť vo všetkých kubelet’och adresu ClusterDNS novou, pričom stará služba bude pokračovať v prevádzke súčasne s novou.
  4. Čakať, kým sa pod’y s aplikáciami presunú buď samy prirodzene, alebo v dohodnutom čase.
  5. Odstrániť službu kube-dns-tmp a zmeniť serviceSubnetCIDR pre službu kube-dns.

Tento plán umožní minimalizovať downtime na približne minútu — počas odstránenia služby kube-dns-tmp a zmeny podsiete pre službu Najzaujímavejšou a najnovšou vecou pre mňa bola sama príčina významného nárastu prevádzky DNS dopytov. O tom a o tom, čo s tým robiť, píšem v tomto príspevku..

Modifikácia podNetwork

Zároveň sme sa rozhodli pozrieť, ako modifikovať podNetwork pomocou získaného etcdhelpera. Postupnosť akcií je nasledujúca:

  • opraviť konfigurácie v kube-system;
  • opraviť manifest kube-controller-manager’a;
  • zmeniť podCIDR priamo v etcd;
  • reštartovať všetky uzly klastra.

Teraz podrobnejšie o týchto akciách:

1. Modifikujeme ConfigMapy v priestore názvov kube-system:

kubectl -n kube-system edit cm kubeadm-config

— opravujeme data.ClusterConfiguration.networking.podSubnet na novú podsieť 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

— opravujeme data.config.conf.clusterCIDR: 10.55.0.0/16.

2. Modifikujeme manifest controller-manager’a:

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

— opravujeme --cluster-cidr=10.55.0.0/16.

3. Pozrieme sa na aktuálne hodnoty .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addresses pre všetky uzly klastra:

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. Nahradíme podCIDR úpravou priamo v 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. Skontrolujeme, či sa podCIDR naozaj zmenil:

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. Jednotlivo reštartujeme všetky uzly klastra.

7. Ak aspoň na jednom uzle zostane starý podCIDR, tak kube-controller-manager sa nebude môcť spustiť a pod'y v klastri nebudú naplánované.

V skutočnosti je možné zmeniť podCIDR aj jednoduchšie (napríklad, tak). Ale chceli sme sa naučiť pracovať s etcd priamo, pretože existujú prípady, keď úprava objektov Kubernetes v etcd je jedinou možnou alternatívou. (Napríklad, nie je možné len tak bez prestojov zmeniť pole služby spec.clusterIP.)

Záver

V článku sa zaoberáme možnosťou práce s dátami v etcd priamo, teda mimo Kubernetes API. Niekedy tento prístup umožňuje robiť 'vychytralé veci'. Operácie uvedené v texte sme testovali na reálnych K8s klastra. Avšak ich stav pripravenosti na široké využitie je PoC (dôkaz konceptu). Preto, ak chcete používať modifikovanú verziu nástroja etcdhelper vo svojich klastroch, robte to na vlastné riziko.

P.S.

Prečítajte si tiež na našom blogu:

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster