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...

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, 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 za172.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 (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 .
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-client2. 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:
- 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 - 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 - Odstraníme staré crt a key, protože bez toho nebude nový certifikát vydán:
rm \/etc\/kubernetes\/pki\/apiserver.{key,crt} - Znovu vydáme certifikáty pro API server:
kubeadm init phase certs apiserver --config=kubeadm-config.yaml - 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 - 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 - Znovu vygenerujeme konfiguraci pro
admin.conf:kubeadm alpha certs renew admin.conf - 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\/16Pozor! 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.confzapsá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. - Opraviť ConfigMapy v prostoru názvů
kube-system:kubectl -n kube-system edit cm kubelet-config-1.16— zde nahradíme
clusterDNSza 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.serviceSubnetna novou podsíť. - 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 - 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:
- Po změnách manifestů control plane vytvořte novou službu kube-dns, například s názvem
kube-dns-tmpa s novou adresou172.24.0.10. - Udělat
ifv etcdhelper, který nebude modifikovat službu kube-dns. - Nahradit ve všech kubeletech adresu
ClusterDNSnovým, přičemž stará služba bude i nadále fungovat souběžně s novou. - Čekat, až se podech s aplikacemi překlopí buď samy přirozeně, nebo v dohodnutém čase.
- Smazat službu
kube-dns-tmpa změnitserviceSubnetCIDRpro 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/165. 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, ). 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
