Salutations à tous ! Aujourd'hui, nous aimerions partager notre expérience d'utilisation de Google Kubernetes Engine pour gérer nos clusters Kubernetes. Nous l'utilisons depuis trois ans en production et sommes ravis de ne plus avoir à nous soucier de la gestion de ces clusters nous-mêmes.
Actuellement, nous avons tous nos environnements de test et clusters d'infrastructure uniques sous le contrôle de Kubernetes. Aujourd'hui, nous voulons parler de la façon dont nous avons rencontré un problème sur notre cluster de test et comment nous espérons que cet article fera gagner du temps et des efforts aux autres.
Nous devons fournir des informations sur notre infrastructure de test pour bien comprendre notre problème. Nous avons plus de cinq environnements de test permanents et déployons des environnements pour les développeurs sur demande. Le nombre de modules en semaine atteint 6000 en journée et ne cesse de croître. Étant donné que la charge est instable, nous emballons les modules très étroitement pour économiser sur les coûts, et la revente de ressources est notre meilleure stratégie.
Cette configuration a bien fonctionné pour nous jusqu'au jour où nous avons reçu une alerte et n'avons pas pu supprimer un espace de noms. Le message d'erreur que nous avons reçu concernant la suppression de l'espace de noms était :
$ kubectl delete namespace arslanbekov Error from server (Conflict): Operation cannot be fulfilled on namespaces "arslanbekov": The system is ensuring all content is removed from this namespace. Upon completion, this namespace will automatically be purged by the system.
Même l'utilisation de l'option de suppression forcée n'a pas résolu le problème :
$ kubectl get namespace arslanbekov -o yaml apiVersion: v1 kind: Namespace metadata: ... spec: finalizers: - kubernetes status: phase: Terminating
Pour résoudre le problème d'espace de noms bloqué, nous avons suivi un guide . Pourtant, cette solution temporaire n'était pas idéale car nos développeurs auraient dû pouvoir créer et supprimer leurs environnements à volonté, en utilisant l'abstraction de l'espace de noms.
Déterminés à trouver une meilleure solution, nous avons décidé d'approfondir nos recherches. L'alerte indiquait un problème de métrique, que nous avons confirmé en exécutant une commande :
$ kubectl api-resources --verbs=list --namespaced -o name error: unable to retrieve the complete list of server APIs: metrics.k8s.io/v1beta1: the server is currently unable to handle the request
Nous avons découvert que le pod metrics-server rencontrait une erreur de mémoire insuffisante (OOM) et une erreur de panique dans les journaux :
apiserver panic'd on GET /apis/metrics.k8s.io/v1beta1/nodes: killing connection/stream because serving request timed out and response had been started goroutine 1430 [running]:
La raison était dans les limites des ressources du pod :
Le conteneur rencontrait ces problèmes en raison de sa définition, qui était la suivante (bloc de limites) :
resources: limits: cpu: 51m memory: 123Mi requests: cpu: 51m memory: 123Mi
Le problème était que le conteneur ne disposait que de 51 millions de CPU , ce qui équivaut à peu près à 0,05 d'un processeur central , et cela n'était pas suffisant pour gérer les métriques d'un si grand nombre de pods. Le planificateur CFS est principalement utilisé.
Habituellement, la résolution de ces problèmes est simple et implique simplement d'allouer plus de ressources au pod. Cependant, dans GKE, cette option n'est pas disponible dans l'interface utilisateur ni via l'interface de ligne de commande gcloud. En effet, Google protège les ressources système contre la modification, ce qui est compréhensible étant donné que toute la gestion est effectuée de leur côté.
Nous avons découvert que nous n'étions pas les seuls à être confrontés à ce problème et avons trouvé un problème similaire où l'auteur a essayé de modifier manuellement la définition du pod. Il a réussi, mais nous non. Lorsque nous avons tenté de modifier les limites de ressources dans le fichier YAML, GKE les a rapidement annulées.
Il fallait trouver une autre solution.
Notre première étape a été de comprendre pourquoi les limites de ressources ont été fixées à ces valeurs. Le pod était composé de deux conteneurs : le metrics-server
et l' addon-resizer
. Ce dernier était responsable de l'ajustement des ressources au fur et à mesure que des nœuds étaient ajoutés ou supprimés du cluster, agissant comme un gardien de la mise à l'échelle verticale du cluster.
Sa définition de ligne de commande était la suivante :
command: - /pod_nanny - --config-dir=/etc/config - --cpu=40m - --extra-cpu=0.5m - --memory=35Mi - --extra-memory=4Mi ...
Dans cette définition, le processeur et la mémoire représentent les ressources de base, tandis que extra-cpu
et extra-memory
représentent des ressources supplémentaires par nœud. Les calculs pour 180 nœuds seraient les suivants :
0.5m * 180 + 40m=~130m
La même logique est appliquée aux ressources mémoire.
Malheureusement, la seule façon d'augmenter les ressources était d'ajouter plus de nœuds, ce que nous ne voulions pas faire. Nous avons donc décidé d'explorer d'autres options.
Bien que nous n'ayons pas pu résoudre entièrement le problème, nous voulions stabiliser le déploiement le plus rapidement possible . Nous avons appris que certaines propriétés de la définition YAML pouvaient être modifiées sans être annulées par GKE. Pour résoudre ce problème, nous avons augmenté le nombre de répliques de 1 à 5 , ajouté un bilan de santé et ajusté la stratégie de déploiement conformément à cet article .
Ces actions ont permis de réduire la charge sur l'instance metrics-server et de garantir que nous disposions toujours d'au moins un pod fonctionnel pouvant fournir des métriques. Nous avons pris le temps de reconsidérer le problème et de rafraîchir nos pensées. La solution a fini par être simple et évidente rétrospectivement.
Nous avons approfondi les caractéristiques internes de l'addon-resizer et découvert qu'il pouvait être configuré via un fichier de configuration et des paramètres de ligne de commande. À première vue, il semblait que les paramètres de ligne de commande devaient remplacer les valeurs de configuration, mais ce n'était pas le cas.
Après enquête, nous avons découvert que le fichier de configuration était connecté au pod via les paramètres de ligne de commande du conteneur addon-resizer :
--config-dir=/etc/config
Le fichier de configuration a été mappé en tant que ConfigMap avec le nom metrics-server-config
dans l'espace de noms du système, et GKE n'annule pas cette configuration !
Nous avons ajouté des ressources via cette configuration comme suit :
apiVersion: v1 data: NannyConfiguration: |- apiVersion: nannyconfig/v1alpha1 kind: NannyConfiguration baseCPU: 100m cpuPerNode: 5m baseMemory: 100Mi memoryPerNode: 5Mi kind: ConfigMap metadata:
Et ça a marché ! C'était une victoire pour nous.
Nous avons laissé deux pods avec des vérifications de l'état et une stratégie de temps d'arrêt zéro en place pendant le redimensionnement du cluster, et nous n'avons plus reçu d'alertes après avoir apporté ces modifications.