Notre expérience de travail avec les données dans etcd Kubernetes-cluster directement (sans K8s API)

De plus en plus souvent, les clients se tournent vers nous avec une demande d'accÚs au cluster Kubernetes pour la possibilité d'accéder à des services au sein du cluster: afin de pouvoir se connecter directement à une base de données ou un service, pour connecter l'application locale avec des applications au sein du cluster ...



Par exemple, il est nécessaire de se connecter de votre machine locale au service memcached.staging.svc.cluster.local. Nous fournissons cette opportunité avec un VPN à l'intérieur du cluster auquel le client se connecte. Pour ce faire, nous annonçons les sous-réseaux de pods, services et push DNS du cluster au client. Ainsi, lorsque le client tente de se connecter au service memcached.staging.svc.cluster.local, la demande est envoyée au DNS du cluster et reçoit en réponse l'adresse de ce service du réseau de services du cluster ou l'adresse du pod.

Nous configurons les clusters K8s Ă  l'aide de kubeadm, oĂč le sous-rĂ©seau de service est par dĂ©faut 192.168.0.0/16et le rĂ©seau de pods 10.244.0.0/16. Habituellement, tout fonctionne bien, mais il y a quelques points:

  • Le sous-rĂ©seau est 192.168.*.*souvent utilisĂ© dans les rĂ©seaux bureautiques de clients, et encore plus souvent dans les rĂ©seaux domestiques de dĂ©veloppeurs. Et puis nous obtenons des conflits: les routeurs domestiques fonctionnent sur ce sous-rĂ©seau et le VPN pousse ces sous-rĂ©seaux du cluster vers le client.
  • Nous avons plusieurs clusters (production, Ă©tape et / ou plusieurs clusters de dĂ©veloppement). Ensuite, dans chacun d'eux par dĂ©faut, il y aura les mĂȘmes sous-rĂ©seaux pour les pods et les services, ce qui crĂ©e de grandes difficultĂ©s pour travailler avec des services dans plusieurs clusters simultanĂ©ment.

Depuis un certain temps, nous avons adopté la pratique d'utiliser différents sous-réseaux pour les services et les pods dans le cadre d'un projet - en général, de sorte que tous les clusters soient avec des réseaux différents. Cependant, il existe un grand nombre de clusters en fonctionnement que je ne voudrais pas rouler à partir de zéro, car ils exécutent de nombreux services, applications avec état, etc.

Et puis nous nous sommes demandé: comment changer le sous-réseau dans un cluster existant?

Recherche de décisions


La pratique la plus courante consiste à recréer tous les services avec le type ClusterIP. Alternativement, ils peuvent également le conseiller :

Le processus suivant a un problÚme: aprÚs tout configuré, les pods proposent l'ancien IP en tant que serveur de noms DNS dans /etc/resolv.conf.
Comme je n'ai toujours pas trouvé la solution, j'ai dû réinitialiser l'ensemble du cluster avec la réinitialisation de kubeadm et l'initier à nouveau.

Mais cela ne convient pas à tout le monde ... Voici des notes d'introduction plus détaillées pour notre cas:

  • UtilisĂ© par Flannel;
  • Il y a des grappes Ă  la fois dans les nuages ​​et sur le fer;
  • Je voudrais Ă©viter le dĂ©ploiement rĂ©pĂ©tĂ© de tous les services du cluster;
  • Il faut tout faire avec un minimum de problĂšmes;
  • La version de Kubernetes est 1.16.6 (cependant, d'autres actions seront similaires pour les autres versions);
  • La tĂąche principale consiste Ă  le 192.168.0.0/16remplacer par un cluster dĂ©ployĂ© Ă  l'aide de kubeadm avec un sous-rĂ©seau de service 172.24.0.0/16.

Et il se trouve que pendant longtemps, il Ă©tait intĂ©ressant pour nous de voir quoi et comment dans Kubernetes il est stockĂ© dans etcd, ce qui peut ĂȘtre fait avec tout cela ... Nous avons donc pensĂ©: « Pourquoi ne pas simplement mettre Ă  jour les donnĂ©es dans etcd en remplaçant les anciennes adresses IP (sous-rĂ©seau) aux nouveaux ? "

À la recherche d'outils prĂȘts Ă  l'emploi pour travailler avec des donnĂ©es dans etcd, nous n'avons rien trouvĂ© qui rĂ©sout complĂštement la tĂąche. (Soit dit en passant, si vous connaissez des utilitaires pour travailler directement avec des donnĂ©es dans etcd, nous vous serons reconnaissants pour les liens.) Cependant, OpenShift etcdhelper est devenu un bon point de dĂ©part (grĂące Ă  ses auteurs!) .

Cet utilitaire est capable de se connecter à l' aide de certificats ETCD et lire les données en utilisant les commandes ls, get, dump.

Ajouter etcdhelper


La pensĂ©e suivante est logique: "Qu'est-ce qui empĂȘche d'ajouter cet utilitaire, ajoutant la possibilitĂ© d'Ă©crire des donnĂ©es dans etcd?"

Il a été traduit dans une version modifiée de etcdhelper avec deux nouvelles changeServiceCIDRet fonctionnalités changePodCIDR. Vous pouvez voir son code ici .

Que font les nouvelles fonctionnalités? Algorithme changeServiceCIDR:

  • crĂ©er un dĂ©sĂ©rialiseur;
  • compiler une expression rĂ©guliĂšre pour remplacer CIDR;
  • nous passons par tous les services avec le type ClusterIP dans le cluster:

    • dĂ©code la valeur de etcd en l'objet Go;
    • en utilisant une expression rĂ©guliĂšre, remplacez les deux premiers octets de l'adresse;
    • attribuer au service une adresse IP Ă  partir d'un nouveau sous-rĂ©seau;
    • crĂ©er un sĂ©rialiseur, convertir l'objet Go en protobuf, Ă©crire de nouvelles donnĂ©es dans etcd.

La fonction changePodCIDRest essentiellement la mĂȘme changeServiceCIDR- seulement au lieu de modifier la spĂ©cification de service, nous le faisons pour le nƓud et le changeons .spec.PodCIDRen un nouveau sous-rĂ©seau.

Entraine toi


Changer de serviceCIDR


Le plan de mise en Ɠuvre de la tĂąche est trĂšs simple, mais implique des temps d'arrĂȘt au moment de la recrĂ©ation de tous les pods du cluster. AprĂšs avoir dĂ©crit les principales Ă©tapes, nous partagerons Ă©galement nos rĂ©flexions sur la façon dont, en thĂ©orie, cette simple Ă©tape peut ĂȘtre minimisĂ©e.

Actions préparatoires:

  • installer le logiciel nĂ©cessaire et assembler le etcdhelper patchĂ©;
  • sauvegarde etcd et /etc/kubernetes.

Plan d'action court pour changer de serviceCIDR:

  • Modification des manifestes Apiserver et Controller-Manager
  • rĂ©Ă©mission de certificats;
  • Les services ClusterIP changent dans etcd;
  • redĂ©marrez tous les pods d'un cluster.

Ce qui suit est une séquence complÚte d'actions en détail.

1. Installez etcd-client pour le vidage des données:

apt install etcd-client

2. Nous collectons etcdhelper:

  • Nous mettons 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
  • Nous nous sauvons etcdhelper.go, chargeons les dĂ©pendances, collectons:

    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. Faites une sauvegarde 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. Modifiez le sous-réseau de service dans les manifestes du plan de contrÎle Kubernetes. Dans les fichiers /etc/kubernetes/manifests/kube-apiserver.yamlet /etc/kubernetes/manifests/kube-controller-manager.yamlremplacez le paramÚtre --service-cluster-ip-rangepar un nouveau sous-réseau: à la 172.24.0.0/16place 192.168.0.0/16.

5. Étant donnĂ© que nous modifions le sous-rĂ©seau de service auquel kubeadm Ă©met des certificats pour apiserver (y compris), ils doivent ĂȘtre rĂ©Ă©mis:

  1. Voyons quels domaines et adresses IP le certificat actuel est Ă©mis:

    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. Préparez la configuration minimale pour 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. Supprimons l'ancien crt et la clé, car sans cela, aucun nouveau certificat ne sera émis:

    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. RĂ©Ă©mettez les certificats pour le serveur API:

    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. , :

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

    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. admin.conf:

    kubeadm alpha certs renew admin.conf
  8. 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 

    ! , pod' /etc/resolv.conf CoreDNS (kube-dns), kube-proxy iptables . .
  9. ConfigMap' kube-system:

    kubectl -n kube-system edit cm kubelet-config-1.16

    — clusterDNS IP- kube-dns: kubectl -n kube-system get svc kube-dns.

    kubectl -n kube-system edit cm kubeadm-config

    — data.ClusterConfiguration.networking.serviceSubnet .
  10. kube-dns, kubelet :

    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. Il reste à redémarrer tous les pods du cluster:

    kubectl get pods --no-headers=true --all-namespaces |sed -r 's/(\S+)\s+(\S+).*/kubectl --namespace \1 delete pod \2/e'

Temps d'arrĂȘt minimisĂ©s


RĂ©flexions sur la façon de minimiser les temps d'arrĂȘt:

  1. AprÚs avoir modifié les manifestations du plan de contrÎle, créez un nouveau service kube-dns, par exemple, avec un nom kube-dns-tmpet une nouvelle adresse 172.24.0.10.
  2. Make ifin etcdhelper, qui ne modifiera pas le service kube-dns.
  3. Remplacez l'adresse dans tous les kubelet ClusterDNSpar une nouvelle, tandis que l'ancien service continuera de fonctionner simultanément avec le nouveau.
  4. Attendez que les modules contenant des applications se déplacent seuls pour des raisons naturelles ou à une heure convenue.
  5. Supprimez le service kube-dns-tmpet modifiez-le serviceSubnetCIDRpour le service kube-dns.

Ce plan minimisera les temps d'arrĂȘt jusqu'Ă  ~ une minute - au moment de la suppression du service kube-dns-tmpet du remplacement du sous-rĂ©seau du service kube-dns.

PodNetwork de modification


Dans le mĂȘme temps, nous avons dĂ©cidĂ© de voir comment modifier podNetwork en utilisant le etcdhelper rĂ©sultant. La sĂ©quence d'actions est la suivante:

  • nous corrigeons les configurations kube-system;
  • nous corrigeons le manifeste de kube-controller-manager;
  • nous changeons podCIDR directement dans etcd;
  • redĂ©marrez tous les nƓuds du cluster.

En savoir plus sur ces actions:

1. Modifiez ConfigMap dans l'espace de noms kube-system:

kubectl -n kube-system edit cm kubeadm-config

- fixé data.ClusterConfiguration.networking.podSubnetsur un nouveau sous-réseau 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

- nous corrigeons data.config.conf.clusterCIDR: 10.55.0.0/16.

2. Modifiez le manifeste du contrĂŽleur-gestionnaire:

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

- nous corrigeons --cluster-cidr=10.55.0.0/16.

3. Regardez les valeurs actuelles .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addressespour tous les nƓuds du 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. Remplacez podCIDR en apportant des modifications directement Ă  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. Vérifiez que podCIDR a vraiment changé:

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. À son tour, nous redĂ©marrerons tous les nƓuds du cluster.

7. Si au moins un nƓud quitte l'ancien podCIDR , le gestionnaire de contrĂŽleur de kube ne pourra pas dĂ©marrer et les pods du cluster ne seront pas planifiĂ©s.

En fait, le changement de podCIDR peut ĂȘtre facilitĂ© (par exemple, comme ceci ). Mais nous voulions apprendre Ă  travailler directement avec etcd, car il y a des cas oĂč l'Ă©dition d'objets Kubernetes dans etcd est la seule option possible. (Par exemple, vous ne pouvez pas simplement modifier le champ d'un service sans interruption spec.clusterIP.)

Total


L'article considÚre la possibilité de travailler directement avec des données dans etcd, c'est-à-dire contourner l'API Kubernetes. Parfois, cette approche vous permet de faire des «choses délicates». Les opérations décrites dans le texte ont été testées sur de vrais clusters K8. Cependant, leur état de préparation à une utilisation généralisée est PoC (preuve de concept) . Par conséquent, si vous souhaitez utiliser une version modifiée de l'utilitaire etcdhelper sur vos clusters, faites-le à vos risques et périls.

PS


Lisez aussi dans notre blog:


All Articles