Notre expérience avec les données dans le cluster etcd Kubernetes directement (sans l'API K8s)

De plus en plus de clients nous demandent de fournir un accès au cluster Kubernetes pour pouvoir accéder aux services au sein du cluster : afin qu'ils puissent se connecter directement à une base de données ou à un service, pour connecter une application locale avec des applications au sein du cluster...

Notre expérience avec les données dans le cluster etcd Kubernetes directement (sans l'API K8s)

Par exemple, il est nécessaire de se connecter depuis votre machine locale à un service memcached.staging.svc.cluster.local. Nous fournissons cette fonctionnalité en utilisant un VPN au sein du cluster auquel le client se connecte. Pour ce faire, nous annonçons les sous-réseaux de pods, de services et transmettons le DNS du cluster au client. Ainsi, lorsqu'un client tente de se connecter au service memcached.staging.svc.cluster.local, la requête est adressée au DNS du cluster et reçoit en réponse l'adresse de ce service du réseau de service du cluster ou l'adresse du pod.

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

  • Sous-réseau 192.168.*.* souvent utilisé dans les réseaux des bureaux clients, et encore plus souvent dans les réseaux domestiques des 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, stage et/ou plusieurs clusters de développement). Ensuite, par défaut, ils auront tous les mêmes sous-réseaux pour les pods et les services, ce qui crée de grandes difficultés pour travailler simultanément avec des services dans plusieurs clusters.

Nous avons depuis longtemps adopté la pratique consistant à utiliser différents sous-réseaux pour les services et les pods au sein d'un même projet - en général, de sorte que tous les clusters aient des réseaux différents. Cependant, il existe un grand nombre de clusters en fonctionnement que je ne voudrais pas reconduire à partir de zéro, car ils exécutent de nombreux services, applications avec état, etc.

Et puis on s’est demandé : comment changer de sous-réseau dans un cluster existant ?

Recherche de solutions

La pratique la plus courante consiste à recréer tous services de type ClusterIP. Comme une option, peut conseiller et tels:

Le processus suivant a un problème : une fois tout configuré, les pods proposent l'ancienne adresse IP comme 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 kubeadm reset et le relancer.

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

  • La flanelle est utilisée ;
  • Il existe des clusters à la fois dans les nuages ​​et sur le matériel ;
  • Je voudrais éviter de redéployer tous les services du cluster ;
  • Il faut généralement tout faire avec un minimum de problèmes ;
  • La version de Kubernetes est 1.16.6 (cependant, les étapes suivantes seront similaires pour les autres versions) ;
  • La tâche principale est de s'assurer que dans un cluster déployé à l'aide de kubeadm avec un sous-réseau de service 192.168.0.0/16, remplacez-le par 172.24.0.0/16.

Et il se trouve que nous étions depuis longtemps intéressés à voir quoi et comment dans Kubernetes est stocké dans etcd, que peut-on en faire... Alors nous avons pensé : «Pourquoi ne pas simplement mettre à jour les données dans etcd, en remplaçant les anciennes adresses IP (sous-réseau) par de nouvelles? »

Après avoir recherché des outils prêts à l'emploi pour travailler avec des données dans etcd, nous n'avons rien trouvé qui résolve complètement le problème. (À propos, si vous connaissez des utilitaires permettant de travailler avec des données directement dans etcd, nous apprécierions les liens.) Cependant, un bon point de départ est etcdhelper depuis OpenShift (merci à ses auteurs !).

Cet utilitaire peut se connecter à etcd à l'aide de certificats et lire les données à partir de là à l'aide de commandes ls, get, dump.

Ajouter etcdhelper

La réflexion suivante est logique : « Qu'est-ce qui vous empêche d'ajouter cet utilitaire en ajoutant la possibilité d'écrire des données sur etcd ? »

C'est devenu une version modifiée d'etcdhelper avec deux nouvelles fonctions changeServiceCIDR и changePodCIDR. sur elle tu peux voir le code ici.

A quoi servent les nouvelles fonctionnalités ? Algorithme changeServiceCIDR:

  • créer un désérialiseur ;
  • compiler une expression régulière pour remplacer CIDR ;
  • nous passons en revue tous les services de type ClusterIP dans le cluster :
    • décoder la valeur de etcd dans un objet Go ;
    • en utilisant une expression régulière, nous remplaçons les deux premiers octets de l'adresse ;
    • attribuer au service une adresse IP du nouveau sous-réseau ;
    • créez un sérialiseur, convertissez l'objet Go en protobuf, écrivez de nouvelles données dans etcd.

Fonction changePodCIDR essentiellement similaire changeServiceCIDR - seulement au lieu de modifier la spécification du service, nous le faisons pour le nœud et modifions .spec.PodCIDR vers un nouveau sous-réseau.

Pratique

Changer de service CIDR

Le plan de mise en œuvre de la tâche est très simple, mais il implique un 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, ce temps d'arrêt peut être minimisé.

Étapes préparatoires :

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

Bref plan d'action pour changer de serviceCIDR :

  • modifier les manifestes du serveur api et du gestionnaire de contrôleur ;
  • réémission de certificats;
  • modification des services ClusterIP dans etcd ;
  • redémarrage de tous les pods du cluster.

Ce qui suit est une séquence complète d’actions en détail.

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

apt install etcd-client

2. Construisez etcdhelper :

  • Installez 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 économisons pour nous-mêmes etcdhelper.go, téléchargez les dépendances, collectez :
    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.yaml и /etc/kubernetes/manifests/kube-controller-manager.yaml changer le paramètre --service-cluster-ip-range vers un nouveau sous-réseau : 172.24.0.0/16 au lieu de 192.168.0.0/16.

5. Puisque nous modifions le sous-réseau de service sur lequel kubeadm émet des certificats pour apiserver (y compris), ils doivent être réémis :

  1. Voyons pour quels domaines et adresses IP le certificat actuel a été é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éparons une 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, le nouveau certificat ne sera pas émis :
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Réémettons les certificats pour le serveur API :
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. Vérifions que le certificat a été émis pour le nouveau sous-réseau :
    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. Après avoir réémis le certificat du serveur API, redémarrez son conteneur :
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. Régénérons la configuration pour admin.conf:
    kubeadm alpha certs renew admin.conf
  8. Modifions les données dans 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 

    Attention! À ce moment, la résolution de domaine cesse de fonctionner dans le cluster, car dans les pods existants /etc/resolv.conf l'ancienne adresse CoreDNS (kube-dns) est enregistrée et kube-proxy modifie les règles iptables de l'ancien sous-réseau vers le nouveau. Plus loin dans l'article, il est écrit sur les options possibles pour minimiser les temps d'arrêt.

  9. Corrigeons les ConfigMap dans l'espace de noms kube-system:
    kubectl -n kube-system edit cm kubelet-config-1.16

    - remplacer ici clusterDNS à la nouvelle adresse IP du service kube-dns : kubectl -n kube-system get svc kube-dns.

    kubectl -n kube-system edit cm kubeadm-config

    - nous allons le réparer data.ClusterConfiguration.networking.serviceSubnet vers un nouveau sous-réseau.

  10. L'adresse kube-dns ayant changé, il est nécessaire de mettre à jour la configuration kubelet sur tous les nœuds :
    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. Il ne reste plus qu'à 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'

Minimisez les temps d’arrêt

Réflexions sur la façon de minimiser les temps d'arrêt :

  1. Après avoir modifié les manifestes du plan de contrôle, créez un nouveau service kube-dns, par exemple, avec le nom kube-dns-tmp et nouvelle adresse 172.24.0.10.
  2. Faire if dans etcdhelper, qui ne modifiera pas le service kube-dns.
  3. Remplacer l'adresse dans tous les kubelets ClusterDNS à un nouveau, tandis que l'ancien service continuera à fonctionner simultanément avec le nouveau.
  4. Attendez que les modules contenant les applications se retournent soit d'eux-mêmes pour des raisons naturelles, soit à une heure convenue.
  5. Supprimer le service kube-dns-tmp et changer serviceSubnetCIDR pour le service Kube-DNS.

Ce plan vous permettra de minimiser les temps d'arrêt à environ une minute - pendant toute la durée de la suppression du service. kube-dns-tmp et changer le sous-réseau du service kube-dns.

Pod de modificationRéseau

En même temps, nous avons décidé d'examiner comment modifier podNetwork à l'aide du etcdhelper résultant. La séquence d'actions est la suivante :

  • correction des configurations dans kube-system;
  • correction du manifeste kube-controller-manager ;
  • changez podCIDR directement dans etcd ;
  • redémarrez tous les nœuds du cluster.

Maintenant, en savoir plus sur ces actions :

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

kubectl -n kube-system edit cm kubeadm-config

- corriger data.ClusterConfiguration.networking.podSubnet vers un nouveau sous-réseau 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

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

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

3. Regardez les valeurs actuelles .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addresses pour 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érifions 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. Redémarrons tous les nœuds du cluster un par un.

7. Si vous laissez au moins un nœud ancien podCIDR, alors kube-controller-manager ne pourra pas démarrer et les pods du cluster ne seront pas planifiés.

En fait, changer podCIDR peut être encore plus simple (par exemple, si). Mais nous voulions apprendre à travailler directement avec etcd, car il existe des cas de modification d'objets Kubernetes dans etcd - seulement variante possible. (Par exemple, vous ne pouvez pas simplement modifier le champ Service sans temps d'arrêt. spec.clusterIP.)

Total

L'article traite de la possibilité de travailler directement avec des données dans etcd, c'est-à-dire en contournant l'API Kubernetes. Parfois, cette approche permet de faire des « choses délicates ». Nous avons testé les opérations données dans le texte 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 propres risques.

PS

A lire aussi sur notre blog :

Source: habr.com

Ajouter un commentaire