Naše zkušenosti s daty v etcd Kubernetes clusteru přímo (bez K8s API)

Stále častěji se na nás obracejí klienti s žádostí o zajištění přístupu do klastru Kubernetes, aby bylo možné se připojit k službám uvnitř klastru: aby bylo možné se přímo připojit k nějaké databázi nebo službě a pro spojení místní aplikace s aplikacemi uvnitř klastru...

Naše zkušenosti s daty v etcd Kubernetes clusteru přímo (bez K8s API)

Například vzniká potřeba připojit se ze své lokální mašiny k službě memcached.staging.svc.cluster.local. Tuto možnost poskytujeme prostřednictvím VPN uvnitř klastru, ke kterému se klient připojuje. K tomu oznamujeme podsítě podů, služeb a pushujeme klastrové DNS klientovi. Tímto způsobem, když se klient pokusí připojit k službě memcached.staging.svc.cluster.local, dotaz odejde do DNS klastru a v odpovědi obdrží adresu dané služby ze služební sítě klastru nebo adresu podu.

Klastry K8s nastavujeme pomocí kubeadm, kde je výchozí služební podsíť - 192.168.0.0/16, a síť podů - 10.244.0.0/16. Obvykle vše dobře funguje, ale existuje několik bodů:

  • Podsíť 192.168.*.* se často používá v kancelářských sítích klientů a ještě častěji - v domácích sítích vývojářů. A pak dochází ke konfliktům: domácí směrovače fungují v této podsíti a VPN pushuje tyto podsítě z klastru klientovi.
  • Máme několik klastrů (produkční, stage a/nebo několik dev-klastrů). Pak ve všech nich budou výchozí podsítě pro podí a služby stejné, což vytváří velké problémy pro současnou práci se službami v několika klastrech.

Už nějakou dobu praktikujeme používání různých podsítí pro služby a pady v rámci jednoho projektu - obecně, aby všechny klastry měly různé sítě. Nicméně existuje velké množství klastrů v provozu, které bychom nechtěli měnit od nuly, protože v nich běží mnohé služby, stateful aplikace atd.

A tak jsme si položili otázku: jak bychom mohli změnit podsíť v existujícím klastru?

Hledání řešení

Nejrozšířenější praxí je znovu vytvořit všechny služby typu ClusterIP. Jako alternativa, může se doporučit a takto:

Následující proces má problém: po nastavení všeho se pody spouštějí se starou IP jako DNS nameserver v /etc/resolv.conf.
Protože jsem stále nenašel řešení, musel jsem restartovat celý klastr pomocí kubeadm reset a znovu ho inicializovat.

Ale to nevyhovuje každému… Zde jsou podrobnější vstupy pro náš případ:

  • Používá se Flannel;
  • Existují klastry jak v cloudech, tak na hardwaru;
  • Chtěli bychom se vyhnout opětovnému nasazení všech služeb v clusteru;
  • Je potřeba udělat všechno s minimálním množstvím problémů;
  • Verze Kubernetes – 1.16.6 (nicméně další kroky budou podobné i pro jiné verze);
  • Hlavním úkolem je v clusteru nasazeném pomocí kubeadm se služební podsítí 192.168.0.0/16, nahradit ji za 172.24.0.0/16.

A tak se shodovalo, že nás už dlouho zajímalo podívat se, co a jak se v Kubernetes uchovává v etcd, co s tím vůbec může udělat... Tak jsme si řekli: "Proč prostě neaktualizovat data v etcd, nahradit staré IP adresy (podsítí) novými?»

Při hledání hotových nástrojů pro práci s daty v etcd jsme nenašli nic, co by zcela vyřešilo stanovený úkol. (Mimochodem, pokud znáte nějaké utility pro práci s daty přímo v etcd – budeme vděční za odkazy.) Dobrou výchozí bodou se stal etcdhelper od OpenShift (děkujeme jeho autorům!).

Tento nástroj umí připojit k etcd pomocí certifikátů a číst z něj data pomocí příkazů ls, získat, dump.

Přidáváme etcdhelper

Další myšlenka je logická: "Co nám brání doplnit tuto utilitu a přidat možnost zápisu dat do etcd?"

Ta se zhmotnila v modifikované verzi etcdhelper s dvěma novými funkcemi changeServiceCIDR a changePodCIDR. Její kód lze vidět zde.

Co nové funkce dělají? Algoritmus changeServiceCIDR:

  • vytvoříme deserializátor;
  • kompilujeme regulární výraz pro výměnu CIDR;
  • procházíme všechny služby typu ClusterIP v clusteru:
    • dekódujeme hodnotu z etcd do objektu Go;
    • pomocí regulárního výrazu nahradíme první dva bajty adresy;
    • přiřazujeme službě IP adresu z nové podsítě;
    • vytvoříme serializátor, převedeme objekt Go na protobuf, zapíšeme nová data do etcd.

Funkce changePodCIDR je v podstatě analogická changeServiceCIDR – pouze místo úpravy specifikace služeb to děláme pro uzel a měníme .spec.PodCIDR na novou podsíť.

Praxe

Změna serviceCIDR

Plán pro realizaci stanoveného úkolu je velmi jednoduchý, ale vyžaduje výpadek v okamžiku přestavění všech podů v clusteru. Po popisu základních kroků se také podělíme o úvahy, jak teoreticky lze tento výpadek minimalizovat.

Přípravné úkony:

  • nainstalování potřebného softwaru a sestavení opraveného etcdhelper;
  • záloha etcd a /etc/kubernetes.

Krátký plán akcí pro změnu serviceCIDR:

  • změna manifestů apiserveru a controller-manageru;
  • Opětovné vydání certifikátů;
  • změna služeb ClusterIP v etcd;
  • restart všech podů v klastru.

Následuje podrobný postup.

1. Nainstalujte etcd-client pro export dat:

apt install etcd-client

2. Sestavte etcdhelper:

  • Nainstalujeme 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, stáhneme závislosti, sestaví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. Udělejme 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. Změňte služební podsíť v manifestech Kubernetes control plane. V souborech /etc/kubernetes/manifests/kube-apiserver.yaml a /etc/kubernetes/manifests/kube-controller-manager.yaml změňte parametr --service-cluster-ip-range na novou podsíť: 172.24.0.0/16 místo 192.168.0.0/16.

5. Vzhledem k tomu, že měníme služební podsíť, pro kterou kubeadm vydává certifikáty pro apiserver (včetně), je nutné je znovu vydat:

  1. Podíváme se, na které domény a IP adresy byl aktuální certifikát vydán:
    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. Připravíme minimální konfiguraci pro 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 uzlu
  3. Odstraníme staré crt a key, protože bez toho nebude nový certifikát vydán:
    rm \/etc\/kubernetes\/pki\/apiserver.{key,crt}
  4. Znovu vydáme certifikáty pro API server:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. Ověříme, že certifikát byl vydán pro novou podsíť:
    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ětovném vydání certifikátu API serveru restartujeme jeho kontejner:
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. Znovu vygenerujeme konfiguraci pro admin.conf:
    kubeadm alpha certs renew admin.conf
  8. Upravíme údaje 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 tuto chvíli přestává v clusteru fungovat rozlišení domén, protože ve stávajících podech je /etc/resolv.conf zapsána stará adresa CoreDNS (kube-dns) a kube-proxy změnil pravidla iptables ze staré podsítě na novou. Dále se v článku píše o možných variantách, jak minimalizovat prostoje.

  9. Opraviť ConfigMapy v prostoru názvů kube-system:
    kubectl -n kube-system edit cm kubelet-config-1.16

    — zde nahradíme clusterDNS za novou IP adresu 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 novou podsíť.

  10. Jelikož se změnila adresa kube-dns, je nutné aktualizovat konfiguraci kubeletu na všech uzlech:
    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. Zbývá restartovat všechny podech v clusteru:
    kubectl get pods --no-headers=true --all-namespaces | sed -r 's/(S+)s+(S+).*/kubectl --namespace 1 delete pod 2/e'

Minimalizace prostoje

Myšlenky, jak minimalizovat prostoje:

  1. Po změnách manifestů control plane vytvořte novou službu kube-dns, například s názvem kube-dns-tmp a s novou adresou 172.24.0.10.
  2. Udělat if v etcdhelper, který nebude modifikovat službu kube-dns.
  3. Nahradit ve všech kubeletech adresu ClusterDNS novým, přičemž stará služba bude i nadále fungovat souběžně s novou.
  4. Čekat, až se podech s aplikacemi překlopí buď samy přirozeně, nebo v dohodnutém čase.
  5. Smazat službu kube-dns-tmp a změnit serviceSubnetCIDR pro službu kube-dns.

Tento plán umožní minimalizovat prostoje na ~minutu — na dobu smazání služby kube-dns-tmp a změny podsítě pro službu kube-dns.

Modifikace podNetwork

Zároveň jsme se rozhodli podívat, jak modifikovat podNetwork pomocí vzniklého etcdhelpera. Postup je následující:

  • opravujeme konfigurace v kube-system;
  • opravujeme manifest kube-controller-managera;
  • měníme podCIDR přímo v etcd;
  • restartujeme všechny uzly clusteru.

Nyní podrobněji o těchto akcích:

1. Modifikujeme ConfigMapy v prostoru názvů kube-system:

kubectl -n kube-system edit cm kubeadm-config

— opravujeme data.ClusterConfiguration.networking.podSubnet na novou podsíť 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-managera:

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

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

3. Sledujeme aktuální hodnoty .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addresses pro všechny uzly clusteru:

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. Změníme podCIDR přímo 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. Ověříme, že se podCIDR skutečně změnil:

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. Postupně restartujeme všechny uzly clustera.

7. Pokud alespoň jeden uzel zanechá starý podCIDR, kube-controller-manager se nebude moci spustit a pod'y v clusteru nebudou plánovány.

Ve skutečnosti lze změnit podCIDR i jednodušeji (například, takto). Ale chtěli jsme se naučit pracovat s etcd přímo, protože existují případy, kdy úprava objektů Kubernetes v etcd je jedinou možností. (Například, nelze jednoduše změnit pole spec.clusterIP.)

Výsledek

V článku se zkoumá možnost práce s daty v etcd přímo, tedy mimo Kubernetes API. Tento přístup občas umožňuje provádět 'chytré věci'. Operace popsané v textu jsme testovali na skutečných K8s clusterech. Jejich stav připravenosti k širokému použití je PoC (proof of concept). Proto, pokud chcete použít modifikovanou verzi nástroje etcdhelper ve svých clusterech, dělejte to na vlastní nebezpečí.

P.S.

Přečtěte si také náš blog:

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster