Experiența noastră cu datele în clusterul etcd Kubernetes direct (fără API-ul K8s)

Din ce în ce mai mult, clienții ne cer să oferim acces la clusterul Kubernetes pentru a putea accesa serviciile din cluster: pentru a ne putea conecta direct la o bază de date sau serviciu, pentru a conecta o aplicație locală cu aplicații din cluster...

Experiența noastră cu datele în clusterul etcd Kubernetes direct (fără API-ul K8s)

De exemplu, este nevoie să vă conectați de la mașina dvs. locală la un serviciu memcached.staging.svc.cluster.local. Oferim această capacitate folosind un VPN în clusterul la care se conectează clientul. Pentru a face acest lucru, anunțăm subrețele de pod-uri, servicii și push cluster DNS către client. Astfel, atunci când un client încearcă să se conecteze la serviciu memcached.staging.svc.cluster.local, cererea ajunge la cluster DNS și, ca răspuns, primește adresa acestui serviciu de la rețeaua de servicii cluster sau adresa pod.

Configuram clusterele K8s folosind kubeadm, unde este subrețeaua implicită de serviciu 192.168.0.0/16, iar rețeaua de păstăi este 10.244.0.0/16. De obicei, totul funcționează bine, dar există câteva puncte:

  • Subrețea 192.168.*.* folosit adesea în rețelele de birou ale clientului și chiar mai des în rețelele de acasă ale dezvoltatorilor. Și apoi obținem conflicte: routerele de acasă funcționează pe această subrețea și VPN-ul împinge aceste subrețele de la cluster la client.
  • Avem mai multe clustere (de producție, de scenă și/sau mai multe clustere de dezvoltare). Apoi, implicit, toate vor avea aceleași subrețele pentru pod-uri și servicii, ceea ce creează mari dificultăți pentru lucrul simultan cu servicii din mai multe clustere.

Am adoptat cu mult timp în urmă practica utilizării diferitelor subrețele pentru servicii și poduri în cadrul unui proiect - în general, astfel încât toate clusterele să aibă rețele diferite. Cu toate acestea, există un număr mare de clustere în funcțiune pe care nu aș dori să le transfer de la zero, deoarece rulează multe servicii, aplicații cu state, etc.

Și apoi ne-am întrebat: cum să schimbăm subrețeaua într-un cluster existent?

Căutarea deciziilor

Cea mai comună practică este recrearea toate servicii cu tip ClusterIP. Ca o opțiune, poate sfătui și asta:

Următorul proces are o problemă: după ce a fost configurat totul, pod-urile vin cu vechiul IP ca server de nume DNS în /etc/resolv.conf.
Deoarece încă nu am găsit soluția, a trebuit să resetam întregul cluster cu kubeadm reset și să-l inițiez din nou.

Dar acest lucru nu este potrivit pentru toată lumea... Iată introduceri mai detaliate pentru cazul nostru:

  • Se folosește flanel;
  • Există clustere atât în ​​nori, cât și pe hardware;
  • Aș dori să evit redistribuirea tuturor serviciilor din cluster;
  • Este necesar să faceți în general totul cu un număr minim de probleme;
  • Versiunea Kubernetes este 1.16.6 (cu toate acestea, pașii suplimentari vor fi similari pentru alte versiuni);
  • Sarcina principală este să vă asigurați că într-un cluster implementat folosind kubeadm cu o subrețea de servicii 192.168.0.0/16, înlocuiți-l cu 172.24.0.0/16.

Și s-a întâmplat că eram de mult interesați să vedem ce și cum este stocat în Kubernetes în etcd, ce se poate face cu el... Așa că ne-am gândit: „De ce să nu actualizați datele în etcd, înlocuind vechile adrese IP (subrețea) cu altele noi? "

După ce am căutat instrumente gata făcute pentru a lucra cu date în etcd, nu am găsit nimic care să rezolve complet problema. (Apropo, dacă știți despre vreo utilitate pentru lucrul cu date direct în etcd, am aprecia linkurile.) Cu toate acestea, un bun punct de plecare este etcdhelper din OpenShift (mulțumesc autorilor săi!).

Acest utilitar se poate conecta la etcd folosind certificate și poate citi datele de acolo folosind comenzi ls, get, dump.

Adăugați etcdhelper

Următorul gând este logic: „Ce vă împiedică să adăugați acest utilitar prin adăugarea capacității de a scrie date în etcd?”

A devenit o versiune modificată a etcdhelper cu două funcții noi changeServiceCIDR и changePodCIDR. pe ea poti vedea codul aici.

Ce fac noile caracteristici? Algoritm changeServiceCIDR:

  • creați un deserializator;
  • compilați o expresie regulată pentru a înlocui CIDR;
  • parcurgem toate serviciile cu tipul ClusterIP din cluster:
    • decodați valoarea din etcd într-un obiect Go;
    • folosind o expresie regulată înlocuim primii doi octeți ai adresei;
    • atribuiți serviciului o adresă IP din noua subrețea;
    • creați un serializator, convertiți obiectul Go în protobuf, scrieți date noi în etcd.

Funcție changePodCIDR în esenţă asemănătoare changeServiceCIDR - doar în loc să edităm specificația serviciului, o facem pentru nod și schimbăm .spec.PodCIDR la o nouă subrețea.

Practică

Schimbați serviciul CIDR

Planul de implementare a sarcinii este foarte simplu, dar implică timp de nefuncționare în timp ce toate podurile din cluster sunt recreate. După ce am descris pașii principali, vom împărtăși și gânduri despre cum, în teorie, acest timp de nefuncționare poate fi minimizat.

Etape pregătitoare:

  • instalarea software-ului necesar și asamblarea etcdhelper-ului patchizat;
  • backup etcd și /etc/kubernetes.

Scurt plan de acțiune pentru schimbarea serviciului CIDR:

  • modificarea manifestelor apiserver și controller-manager;
  • reemiterea certificatelor;
  • schimbarea serviciilor ClusterIP în etcd;
  • reporniți toate podurile din cluster.

Următoarea este o secvență completă de acțiuni în detaliu.

1. Instalați etcd-client pentru descărcarea datelor:

apt install etcd-client

2. Construiți etcdhelper:

  • Instalați 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
  • Salvăm pentru noi înșine etcdhelper.go, descărcați dependențe, colectați:
    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. Faceți o copie de rezervă 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. Schimbați subrețeaua de serviciu în manifestele planului de control Kubernetes. În dosare /etc/kubernetes/manifests/kube-apiserver.yaml и /etc/kubernetes/manifests/kube-controller-manager.yaml modifica parametrul --service-cluster-ip-range la o subrețea nouă: 172.24.0.0/16 în loc de 192.168.0.0/16.

5. Deoarece schimbăm subrețeaua de servicii către care kubeadm emite certificate pentru apiserver (inclusiv), acestea trebuie să fie reemise:

  1. Să vedem pentru ce domenii și adrese IP a fost emis certificatul actual:
    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. Să pregătim o configurație minimă pentru 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-адрес мастер узла
  3. Să ștergem vechiul crt și cheia, deoarece fără aceasta noul certificat nu va fi emis:
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Să reemitem certificate pentru serverul API:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. Să verificăm dacă certificatul a fost emis pentru noua subrețea:
    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. După reemiterea certificatului de server API, reporniți containerul acestuia:
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. Să regenerăm configurația pentru admin.conf:
    kubeadm alpha certs renew admin.conf
  8. Să edităm datele î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 

    Atenție! În acest moment, rezoluția domeniului nu mai funcționează în cluster, deoarece în podurile existente în /etc/resolv.conf vechea adresă CoreDNS (kube-dns) este înregistrată, iar kube-proxy schimbă regulile iptables de la vechea subrețea la cea nouă. Mai departe, în articol este scris despre posibilele opțiuni pentru a minimiza timpul de nefuncționare.

  9. Să reparăm ConfigMap-urile din spațiul de nume kube-system:
    kubectl -n kube-system edit cm kubelet-config-1.16

    - înlocuiți aici clusterDNS la noua adresă IP a serviciului kube-dns: kubectl -n kube-system get svc kube-dns.

    kubectl -n kube-system edit cm kubeadm-config

    - o vom repara data.ClusterConfiguration.networking.serviceSubnet la o nouă subrețea.

  10. Deoarece adresa kube-dns s-a schimbat, este necesar să actualizați configurația kubelet pe toate nodurile:
    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. Tot ce rămâne este să reporniți toate podurile din cluster:
    kubectl get pods --no-headers=true --all-namespaces |sed -r 's/(S+)s+(S+).*/kubectl --namespace 1 delete pod 2/e'

Minimizați timpul de nefuncționare

Gânduri despre cum să minimizați timpul de nefuncționare:

  1. După modificarea manifestelor planului de control, creați un nou serviciu kube-dns, de exemplu, cu numele kube-dns-tmp si noua adresa 172.24.0.10.
  2. face if în etcdhelper, care nu va modifica serviciul kube-dns.
  3. Înlocuiți adresa în toate kubeletele ClusterDNS la unul nou, în timp ce vechiul serviciu va continua să funcționeze simultan cu cel nou.
  4. Așteptați până când capsulele cu aplicații se răstoarnă fie singure din motive naturale, fie la un moment convenit.
  5. Ștergeți serviciul kube-dns-tmp și schimbare serviceSubnetCIDR pentru serviciul kube-dns.

Acest plan vă va permite să minimizați timpul de nefuncționare la ~ un minut - pe durata eliminării serviciului kube-dns-tmp și schimbarea subrețelei pentru serviciu kube-dns.

Modificare podNetwork

În același timp, am decis să analizăm cum să modificăm podNetwork folosind etcdhelper rezultat. Secvența acțiunilor este următoarea:

  • remedierea configurațiilor în kube-system;
  • repararea manifestului kube-controller-manager;
  • schimba podCIDR direct în etcd;
  • reporniți toate nodurile clusterului.

Acum mai multe despre aceste acțiuni:

1. Modificați ConfigMaps în spațiul de nume kube-system:

kubectl -n kube-system edit cm kubeadm-config

- corectarea data.ClusterConfiguration.networking.podSubnet la o nouă subrețea 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

- corectarea data.config.conf.clusterCIDR: 10.55.0.0/16.

2. Modificați manifestul controller-manager:

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

- corectarea --cluster-cidr=10.55.0.0/16.

3. Priviți valorile curente .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addresses pentru toate nodurile cluster:

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. Înlocuiți podCIDR făcând modificări direct î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. Să verificăm dacă podCIDR sa schimbat cu adevărat:

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. Să repornim toate nodurile cluster unul câte unul.

7. Dacă părăsiți cel puțin un nod vechi podCIDR, atunci kube-controller-manager nu va putea porni, iar podurile din cluster nu vor fi programate.

De fapt, schimbarea podCIDR se poate face și mai simplu (de exemplu, astfel). Dar am vrut să învățăm cum să lucrăm direct cu etcd, deoarece există cazuri când editați obiecte Kubernetes în etcd - singurul varianta posibila. (De exemplu, nu puteți schimba câmpul Service fără timp de nefuncționare spec.clusterIP.)

Total

Articolul discută posibilitatea de a lucra direct cu date în etcd, i.e. ocolind API-ul Kubernetes. Uneori, această abordare vă permite să faceți „lucruri complicate”. Am testat operațiunile date în text pe clustere reale K8s. Cu toate acestea, statutul lor de pregătire pentru utilizare pe scară largă este PoC (dovada conceptului). Prin urmare, dacă doriți să utilizați o versiune modificată a utilitarului etcdhelper pe clusterele dvs., faceți-o pe propriul risc.

PS

Citește și pe blogul nostru:

Sursa: www.habr.com

Adauga un comentariu