Eksperienca jonë me të dhënat në etcd të klasterit Kubernetes direkt (pa API K8s)

Po na drejtohen gjithnjë e më shpesh klientët me kërkesën për të siguruar akses në klasterin Kubernetes për të pasur mundësi për të kontaktuar shërbimet brenda klasterit: për t'u lidhur direkt me ndonjë bazë të dhënash ose shërbim, për lidhjen e aplikacioneve lokale me aplikacionet brenda klasterit…

Eksperienca jonë me të dhënat në etcd të klasterit Kubernetes direkt (pa API K8s)

Për shembull, krijohet nevoja për t'u lidhur nga makina juaj lokale me shërbimin memcached.staging.svc.cluster.local. Ne ofrojmë një mundësi të tillë përmes VPN brenda klasterit, me të cilin lidhet klienti. Për këtë, njoftojmë subnetet e pod-ëve, shërbimeve dhe dërgojmë DNS-in e klasterit te klienti. Kështu, kur klienti përpiqet të lidhet me shërbimin memcached.staging.svc.cluster.local, kërkesa shkon në DNS-in e klasterit dhe në përgjigje merr adresën e këtij shërbimi nga rrjeti shërbimesh të klasterit ose adresën e pod-it.

Ne konfigurojmë klasterët K8s me kubeadm, ku nënkuptohet që subneti i shërbimit është 192.168.0.0/16, dhe rrjeti i pod-ëve është 10.244.0.0/16. Zakonisht gjithçka funksionon mirë, por ka disa momente:

  • Subneta 192.168.*.* shpërdoret shpesh në rrjetet e zyrave të klientëve, dhe më shpesh ende — në rrjetet shtëpiake të zhvilluesve. Dhe kështu kemi konflikte: routerat shtëpiakë punojnë në këtë nënrrjetë dhe VPN e shtyn këto nënrrjeta nga klasteri te klienti.
  • Kemi disa klasterë (klasterë production, stage dhe/ose disa klasterë dev). Atëherë në të gjithë klasterët do të kenë nënrrjeta të njëjta për podët dhe shërbimet, gjë që krijon vështirësi të mëdha për të punuar njëkohësisht me shërbimet në disa klasterë.

Qysh prej një kohe të gjatë kemi pranuar praktikën e përdorimit të nënrrjetave të ndryshme për shërbimet dhe podët brenda një projekti — në përgjithësi, për të siguruar që të gjithë klasterët të kenë rrjete të ndryshme. Megjithatë, ka një numër të madh klasterësh në punë, të cilët nuk do të dëshironim t'i rikthinim nga e para, pasi në to janë aktivizuar shumë shërbime, aplikacione stateful, etj.

Dhe kështu na erdhi në mendje pyetja: si ta ndryshojmë nënrrjetën në një klaster ekzistues?

Kërkimi i zgjidhjeve

Praktika më e zakonshme — për ta krijuar përsëri të gjitha shërbimet me tipin ClusterIP. Si një mundësi, mund të këshillojnë dhe diçka të tillë:

Proçesi në vazhdim ka një problem: pas konfigurimit të gjithçkaje, podët dalin me IP-në e vjetër si një server DNS në /etc/resolv.conf.
Deri sa nuk e gjeta zgjidhjen, më duhej të rivendosja tërë klasterin me kubeadm reset dhe ta filloja atë përsëri.

Por, nuk është e përshtatshme për të gjithë... Këtu janë disa informacione më të detajuara për rastin tonë:

  • Përdoret Flannel;
  • Ka klastera si në re ashtu edhe në hardware;
  • Do të doja të evitonim ri-deploy të të gjitha shërbimeve në klaster;
  • Ka një nevojë për të bërë gjithçka me sa më pak probleme të jetë e mundur;
  • Versioni i Kubernetes është 1.16.6 (megjithatë, veprimet e mëtejshme do të jenë të ngjashme edhe për versione të tjera);
  • Detyra kryesore përfshin që në klasterin e ndërtuar me kubeadm me nënrrjetë shërbimi 192.168.0.0/16, ta zëvendësojmë atë me 172.24.0.0/16.

Dhe ndodhi që na kishte interesuar prej një kohë të shihnim se çfarë dhe si ruhet në Kubernetes në etcd, çfarë mund të bëjmë me të... Prandaj menduam: "Pse të mos përditësojmë thjesht të dhënat në etcd, duke zëvendësuar IP-të e vjetra (nënrrjetë) me të rejat?

Pasi kërkuam mjete të gatshme për të punuar me të dhënat në etcd, nuk gjetëm asgjë që të zgjidhte plotësisht detyrën. (P.S. nëse dini për ndonjë utilitare për të punuar drejtpërdrejt me të dhënat në etcd — do jemi mirënjohës për lidhjet.) Megjithatë, një pikë e mirë prej nga filluam ishte etcdhelper nga OpenShift (faleminderit autorëve të tij!).

Ky utilitar ka aftësinë të lidhë me etcd përmes certifikateve dhe të lexojë të dhëna nga aty me anë të komandave ls, merr, dump.

Shtojmë etcdhelper

Mendimi tjetër është i dukshëm: «Çfarë e pengon të shtojmë këtë utilitar duke shtuar mundësinë për të shkruar të dhëna në etcd?»

Ajo është realizuar në një version të modifikuar të etcdhelper me dy funksione të reja changeServiceCIDR dhe changePodCIDR. Në kodin e saj mund të shihni këtu.

Çfarë bëjnë funksionet e reja? Algoritmi changeServiceCIDR:

  • krijon një deserializues;
  • kompilon një shprehje të rregullt për të zëvendësuar CIDR;
  • kalon nëpër të gjitha shërbimet me llojin ClusterIP në klaster:
    • dekodifikon vlerën nga etcd në objektin Go;
    • me anë të shprehjes së rregullt zëvendëson dy bajtat e para të adresës;
    • i jep shërbimit një adresë IP nga subneti i ri;
    • krijon një serializues, transformon objektin Go në protobuf, shkruan të dhënat e reja në etcd.

Funksioni changePodCIDR në thelb ështëanalogjike changeServiceCIDR — vetëm se në vend të redaktimit të specifikimeve të shërbimeve ne e bëjmë këtë për nyjën dhe ndryshojmë .spec.PodCIDR në subnetin e ri.

Praktika

Ndryshimi i serviceCIDR

Plani për realizimin e detyrës së caktuar është shumë i thjeshtë, por përfshin ndalesa gjatë ripërtëritjes së të gjithë pod’ave në klaster. Pas përshkrimit të hapat kryesorë, do të ndajmë gjithashtu mendimet tona se si, në teori, mund të minimalizohet kjo ndalesë.

Veprimet përgatitore:

  • instalimi i softuerit të nevojshëm dhe ndërtimi i etcdhelper të patch-it;
  • backup i etcd dhe /etc/kubernetes.

Një plan i shkurtër veprimi për ndryshimin e serviceCIDR:

  • ndryshimi i manifestëve të apiserver-it dhe controller-manager-it;
  • rishpërndarja e certifikatave;
  • ndryshimi i ClusterIP të shërbimeve në etcd;
  • ri-ngritja e të gjithë pod’ave në klaster.

Më poshtë është e paraqitur sekuenca e plotë e veprimeve në detaje.

1. Instaloni etcd-client për dump të të dhënave:

apt install etcd-client

2. Ndërtojmë etcdhelper:

  • Instaloni 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
  • Ruani etcdhelper.go, shkarkoni varësitë, ndërtoni:
    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. Bëni një backup të 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. Ndryshojmë subnetin e shërbimeve në manifestet e Kubernetes control plane. Në skedarët /etc/kubernetes/manifests/kube-apiserver.yaml dhe /etc/kubernetes/manifests/kube-controller-manager.yaml ndryshojmë parametrin --service-cluster-ip-range në subnetin e ri: 172.24.0.0/16 në vend të 192.168.0.0/16.

5. Pasi po ndryshojmë subnetin e shërbimeve, për të cilin kubeadm lëshon certifikatat për apiserver’in (përfshirë), është e nevojshme të ripublikojmë:

  1. Le të shikojmë për çfarë domenesh dhe IP adresash është lëshuar certifikati aktual:
    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. Të përgatisim një konfigurim minimal pë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 adresa e nodit master
  3. Do të fshijmë crt dhe key-të e vjetra, pasi pa këtë certifikati i ri nuk do të lëshohet:
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Do të ripublikojmë certifikatat për API-serverin:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. Do të kontrollojmë që certifikati është lëshuar për subnetin e ri:
    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. Pas rivendosjes së certifikatës së API-serverit, do të rindiznim kontejnerin e tij:
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. Rregullojmë konfigurimin për admin.conf:
    kubeadm alpha certs renew admin.conf
  8. Modifikojmë të dhënat në 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 

    Kujdes! Në këtë moment, rezolvimi i domain-eve në klaster pushon së funksionuari, pasi në pod-at ekzistues ka ende adresën e vjetër të CoreDNS (kube-dns), ndërsa kube-proxy e ka ndryshuar rregulloren e iptables nga subneti i vjetër në të riun. Më tej në artikull diskutohet për opsionet për të minimizuar pezullimin. /etc/resolv.conf Korrigjojmë ConfigMap-at në hapësirën emri

  9. kube-system kubectl -n kube-system edit cm kubelet-config-1.16:
    — këtu do të zëvendësojmë

    clusterDNS me adresën e re IP të shërbimit kube-dns: kubectl -n kube-system get svc kube-dns kubectl -n kube-system edit cm kubeadm-config.

    — do të korrigjojmë

    data.ClusterConfiguration.networking.serviceSubnet Duke qenë se adresa e kube-dns ndryshoi, është e nevojshme të përditësojmë konfigurimin e kubelet në të gjithë nyjet: në subnetin e ri.

  10. kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. Përfundoni ripërshtimin e të gjitha pod-eve në klaster:
    kubectl get pods --no-headers=true --all-namespaces | sed -r 's/(S+)s+(S+).*/kubectl --namespace 1 delete pod 2/e'

Minimizimi i kohës së ndalimit

Mendoni se si mund të minimizoni kohën e ndalimit:

  1. Pas ndryshimeve në manifestet e kontrollit, krijoni një shërbim të ri kube-dns, për shembull me emrin kube-dns-tmp dhe një adresë të re 172.24.0.10.
  2. Bëni nëse në etcdhelper, i cili nuk do të modifikojë shërbimin kube-dns.
  3. Zëvendësoni në të gjitha kubelet-at adresën ClusterDNS me të re, ndërsa shërbimi i vjetër do të vazhdojë të funksionojë njëkohësisht me të riun.
  4. Prisni deri sa pod-et me aplikacione të kalojnë ose vetë për arsye natyrore, ose në një kohë të caktuar.
  5. Fshini shërbimin kube-dns-tmp dhe ndryshoni serviceSubnetCIDR për shërbimin kube-dns.

Ky plan do të lejojë minimizimin e kohës së ndalimit deri në ~minuta — për kohën e fshirjes së shërbimit kube-dns-tmp dhe zëvendësimin e subnetit për shërbimin kube-dns.

Modifikimi i podNetwork

Për njëherë, ne vendosëm të shihnim se si të modifikojmë podNetwork me ndihmën e etcdhelper-it të krijuar. Seanca e veprimeve është si më poshtë:

  • rregulloni konfigurimet në kubectl -n kube-system edit cm kubelet-config-1.16;
  • rregulloni manifestin e kube-controller-manager-it;
  • ndryshoni podCIDR drejtpërdrejt në etcd;
  • rindizni të gjithë nodet e klasterit.

Tani më shumë për këto veprime:

1. Modifikojmë ConfigMap të në hapësirën e emrave kubectl -n kube-system edit cm kubelet-config-1.16:

— do të korrigjojmë

— rregullojmë data.ClusterConfiguration.networking.podSubnet në maskë të re 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

— rregullojmë data.config.conf.clusterCIDR: 10.55.0.0/16.

2. Modifikojmë manifestin e menaxherit të controller-it:

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

— rregullojmë --cluster-cidr=10.55.0.0/16.

3. Shohim vlerat aktuale .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addresses për të gjithë nodet e klasterit:

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. Zëvendësojmë podCIDR, duke bërë rregullime drejtpërdrejt në 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. Do të kontrollojmë nëse podCIDR me të vërtetë është ndryshuar:

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. Ne do të rindezim një nga një të gjithë nyjet e klasterit.

7. Nëse të paktën një nyje e lë podCIDR të vjetër, atëherë kube-controller-manager nuk do të mund të startojë, dhe pod-et në klaster nuk do të planifikohen.

Në të vërtetë, mund të bëhet dhe më lehtë (p.sh., kështu). Por ne dëshironim të mësoheshim të punonim me etcd drejtpërdrejt, sepse ka raste kur modifikimi i objekteve Kubernetes në etcd është i vetmi opsion i mundshëm. (P.sh., nuk mund ta ndryshosh në mënyrë të thjeshtë fushën spec.clusterIP.)

Përfundimi

Artikulli shqyrton mundësinë e punës me të dhënat në etcd drejtpërdrejt, pra, duke e anashkaluar Kubernetes API. Ndonjëherë, ky qasje lejon të bëhen 'trikë të zgjuara'. Operacionet e paraqitura në tekst i kemi testuar në klastera të vërtetë K8s. Megjithatë, statusi i tyre i gatishmërisë për përdorim të gjerë është — PoC (provë e konceptit). Prandaj, nëse dëshironi të përdorni versionin e modifikuar të utilitetit etcdhelper në klasterët tuaj, bëjeni këtë me rrezikun tuaj.

P.S.

Lexoni gjithashtu në blogun tonë:

Burimi: habr.com

Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS 🔥 Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS | ProHoster