Contactez-nous
-
kubernetes

6 étapes pour détecter les dépréciations d'API Kubernetes via Kube-no-trouble

6 étapes pour détecter les dépréciations d'API Kubernetes via Kube-no-trouble

Sommaire

 Introduction

Si vous avez déjà géré un cluster Kubernetes, vous avez probablement dû mettre à jour sa version à un moment ou un autre. Dans ce cas de figure, il est nécessaire de pouvoir détecter simplement toute modification à même d’endommager les workloads, et cela inclut notamment la détection de dépréciation d'API. Dans cet article, je vous propose donc de nous intéresser à ce problème et de trouver un moyen de le régler par le biais d’un outil fort pratique : Kube-no-trouble.

Une histoire d’API

Pour illustrer notre problème, intéressons-nous au cas de Bob. Bob est un administrateur Kubernetes très sympathique qui souhaite upgrader son cluster de la version 1.21 à 1.25 afin de profiter de nouvelles fonctionnalités. Seulement, Bob est également très consciencieux et sait qu’un upgrade de Kubernetes ne se fait pas sans vérification d’éventuelles dépréciations, surtout au niveau de l’API Kubernetes.

Pour ceux qui ne s’y connaîtraient pas autant que Bob, un petit rappel s’impose.
L’API Kubernetes est une API REST exposée par l’API Server. C’est un composant central de Kubernetes, qui permet de consulter et de manipuler des objets API dans Kubernetes (Pods, Namespaces, Ingresses, ConfigMap et autres jobyeusetés). L’API Kubernetes est découpée en groupes (Apps, Networking, RBAC, Storage, etc) qui ont chacun un cycle de vie indépendant, et donc, des versions différentes. La combinaison d’un groupe et d’une version s’appelle l’apiVersion : vous savez, c’est la petite ligne en haut de chaque YAML. Justement, Bob semble avoir trouvé une apiVersion :

apiVersion: networking.k8s.io/v1beta1

Les groupes de l’API Kubernetes suivent une politique de dépréciation précise. Ces derniers sont communément introduits en phase expérimentale avec une version alpha, avant de passer en pre-release avec une version beta et de devenir stables : on parle alors de GA pour Generally Available.

Dans le cas présenté plus haut, on a ainsi affaire à un objet du groupe Networking à la version v1beta1 : il s’agit sans doute d’un Ingress faisant partie d’un groupe API en pre-release. Et c’est là que les ennuis commencent : durant ses recherches de dépréciation, Bob a justement remarqué… Que l’apiVersion v1beta1 avec laquelle ont été créés certains des Ingresses de son cluster sera déprécié dans la version 1.22.

Pour le bien de Bob et de son cluster, il serait donc de bon ton de trouver toutes les occurrences de cette apiVersion au sein des déployés pour pouvoir mettre à jour les ressources dépréciées.

API fait de la résistance

Bob se retrousse les manches avec un objectif clair : détecter tous les Ingresses porteurs de l’apiVersion v1beta1. Il procède donc à la recherche suivante pour lister les différentes apiVersions utilisées par ses Ingresses :

$ kubectl get ingress -n test-kubent -o json | jq '.items[].apiVersion'

"networking.k8s.io/v1"
"networking.k8s.io/v1"
"networking.k8s.io/v1"
"networking.k8s.io/v1"
"networking.k8s.io/v1"

Légère circonspection de la part de Bob, qui constate que tous les Ingresses sont affichés en v1. Étant sûr que certains Ingresses ne sont pas conformes, ce dernier tente donc une approche moins ambigüe et ne demande que les Ingresses dotés de l’apiVersion v1beta1 pour le groupe API Networking :

$ kubectl get ingresses.v1beta1.networking.k8s.io -n test-kubent -o json | 
jq '.items[].apiVersion'

"networking.k8s.io/v1beta1"
"networking.k8s.io/v1beta1"
"networking.k8s.io/v1beta1"
"networking.k8s.io/v1beta1"
"networking.k8s.io/v1beta1"

Voilà à présent que tous les Ingresses sont affichés dans la version v1beta1 : notre pauvre administrateur Kubernetes n’est plus circonspect, il est perdu. Ce que Bob ne sait pas (encore), c’est que l’API Server de Kubernetes a pour consigne de renvoyer toute ressource demandée dans l’apiVersion spécifiée, quitte à la convertir à la bonne version si besoin est. Ce comportement est intentionnel et découle d’un concept de base de l’API Server : tous les objets du cluster doivent être disponibles dans toutes les apiVersions servies, quelle que soit leur apiVersion d’origine. Impossible donc de différencier les apiVersions de différents objets via kubectl, étant donné que l’apiVersion d’origine d’un objet est volontairement non exposée dans l’API.

Plus il avance dans ses recherches et plus Bob comprend alors que sa chasse aux dépréciations d’API ne sera ni simple ni rapide. Il existe bien quelques méthodes pour retrouver les ressources obsolètes, mais toutes sont assez fastidieuses : récupération scriptée des annotations kubectl.kubernetes.io/last-applied-configuration, inspections des logs de l’API Server ou des controllers… À cette difficulté s’ajoute le fait que le problème de recherche de Bob ne s’applique pas qu’aux Ingresses, ce qui implique une longue vérification manuelle et, par conséquent, des erreurs humaines en pagaille. Pas le choix, Bob doit trouver une autre manière de trouver les dépréciations d’API que par ce genre de manœuvre.

# Récupération "magnifique" de l'annotation last-applied-configuration 
pour trouver les ingress créés en version v1beta1

$ kubectl get ingress -n test-kubent -o json | jq -r '.items[] |
select(.metadata.annotations."kubectl.kubernetes.io/last-applied-configuration" |
contains("networking.k8s.io/v1beta1")) | .metadata.name'


my-wonderful-ingress
my-genius-ingress

Kube-no-trouble à la rescousse

Après quelques recherches, Bob tombe sur l’outil adapté à ses besoins : Kube-no-trouble ! Petit état des lieux. Kube-no-trouble (ou plus simplement kubent) est un outil spécifiquement conçu pour détecter les dépréciations d’API de clusters Kubernetes. Ce dernier est apparu en réponse à la sortie de Kubernetes 1.16, une version historiquement reconnue pour ses nombreuses dépréciations dans différents domaines clés (DaemonSet, ReplicaSet, Deployment et j’en passe). 

N’y tenant plus, Bob a d’ailleurs installé l’outil. Pour ce faire, rien de bien compliqué : 

$ sh -c "$(curl -sSL https://git.io/install-kubent)"

Une fois kubent installé, il suffit de le lancer en lui donnant le bon contexte Kubernetes. Si l’on se trouve déjà dans le contexte que l’on souhaite examiner, il suffit de lancer la commande kubent. Si le contexte n’est pas le bon, trois options sont envisageables : 

  • Changer de contexte avant de lancer kubent
  • Utiliser directement kubent en spécifiant le chemin d’un kubeconfig dont le current-context est celui que vous voulez examiner
  • Utiliser kubent en spécifiant le contexte contenu dans votre kubeconfig actuel
# Si l'on est placé dans le bon contexte
$ kubent

# Si l'on veut se placer d'abord dans le bon contexte
$ kubectl config use-context my-awesome-context && kubent

# Si notre fichier kubeconfig contient le contexte, mais que ce n'est pas le contexte actuel
$ kubent -x my-awesome-contexte

# Si l'on préfère appeller un fichier de configuration qui contient le bon contexte actuel
$ kubent -k ~/.kube/config

En l'occurrence, Bob est déjà placé dans le bon contexte. Impatient de voir le résultat, il lance donc kubent :

$ kubent

10:40AM INF >>> Kube No Trouble `kubent` <<<
10:40AM INF version 0.7.0 (git sha d1bb4e5fd6550b533b2013671aa8419d923ee042)
10:40AM INF Initializing collectors and retrieving data
10:40AM INF Target K8s version is 1.21.14+k3s1
10:40AM INF Retrieved 11 resources from collector name=Cluster
10:40AM INF Retrieved 9 resources from collector name="Helm v3"
10:40AM INF Loaded ruleset name=custom.rego.tmpl
10:40AM INF Loaded ruleset name=déprécié-1-16.rego
10:40AM INF Loaded ruleset name=déprécié-1-22.rego
10:40AM INF Loaded ruleset name=déprécié-1-25.rego
10:40AM INF Loaded ruleset name=déprécié-1-26.rego
10:40AM INF Loaded ruleset name=déprécié-future.rego
__________________________________________________________________________________________
>>> déprécié APIs removed in 1.22 <<<
------------------------------------------------------------------------------------------
KIND      NAMESPACE     NAME                   API_VERSION                 REPLACE_WITH (SINCE)
Ingress   test-kubent   my-genius-ingress      networking.k8s.io/v1beta1   networking.k8s.io/v1 (1.19.0)
Ingress   test-kubent   my-wonderful-ingress   networking.k8s.io/v1beta1   networking.k8s.io/v1 (1.19.0)
__________________________________________________________________________________________
>>> déprécié APIs removed in 1.25 <<<
------------------------------------------------------------------------------------------
KIND                NAMESPACE     NAME               API_VERSION      REPLACE_WITH (SINCE)
CronJob             test-kubent   my-magic-cronjob   batch/v1beta1    batch/v1 (1.21.0)
PodSecurityPolicy   <undefined>   my-solid-psp       policy/v1beta1   <removed> (1.21.0)

Comme on peut le voir, plusieurs informations importantes sont listées dans ces logs. Premièrement, Bob a trouvé les Ingress touchées par la dépréciation d’API ! Elles sont au nombre de deux, et kubent indique que l’apiVersion networking.k8s.io/v1beta1 sera supprimée au profit de networking.k8s.io/v1.

En plus du signalement des Ingresses, on voit également que kubent a remonté deux autres problèmes. L’apiVersion batch/v1beta1 associée aux CronJobs sera notamment supprimée au profit de batch/v1 à la version 1.22, un problème que Bob n’a pas vu venir. La seconde dépréciation semble plus problématique et indique que l’apiVersion policy/v1beta1 sera supprimée sans remplacement. Toutefois, ce souci a été anticipé par Bob, qui sait que cela indique le remplacement des PodSecurityPolicies par les Pod Security Admissions (une des nouvelles fonctionnalités qu’il attendait dans la version 1.25).

Si on fouille un peu dans les entrailles de Kube-no-trouble, on peut aussi apprendre comment ce dernier détecte les dépréciations d’API. Il se sert pour ça de l’annotation kubectl.kubernetes.io/last-applied-configuration, créée automatiquement lors de chaque commande kubectl. Cette annotation est au format JSON et comporte certains champs d’information que kubent récolte et compare à un catalogue de règles de dépréciations. D’ailleurs, les plus attentifs se souviendront que c’est via cette même annotation que Bob avait lui aussi réussi à détecter les apiVersion originelles de ses ressources ! Bob, ce bricoleur.

# Annotation kubectl simplifiée pour Kube-no-trouble
# On remarque que le champ "spec" n'est pas nécessaire
kubectl.kubernetes.io/last-applied-configuration = {
   "apiVersion": api_version,
   "kind": kind,
   "metadata": {
       "name": name,
       "namespace": namespace
    }
}

Les problèmes de ce cher Bob paraissent donc réglés : toutes les dépréciations d’API ont été trouvées grâce à kubent, et notre administrateur Kubernetes préféré peut désormais détecter les obsolescences d’API sans effort. Néanmoins, est-il possible de pousser kubent un peu plus loin ?

Kube-no-trouble pour les outils maison

Bien que Bob ait détecté toutes les dépréciations qu’il s’attendait à déceler, il existe bien d’autres cas où l'obsolescence d’API doit être surveillée ; notamment lors de déploiements Kubernetes par des outils autres que kubectl. Détaillons un peu tout ça.

Bob fait partie d’une équipe technique ayant développé un outil interne de déploiement de ressources Kubernetes. Ce dernier est basé sur le client Kubernetes officiel pour Python et n’utilise pas kubectl ; par conséquent, les déploiements ne génèrent pas d’annotation kubectl.kubernetes.io/last-applied-configuration… Ce qui est problématique. En effet, comme nous l’avons signalé plus tôt, c’est précisément cette annotation qui permet à kubent de retrouver les dépréciations d’API ! En l’état, demander à kubent de détecter les ressources créées via l’outil de Bob ne servira à rien. Pour régler ce souci, rien de bien méchant : il faut paramétrer l’outil interne de Bob pour qu’il génère une annotation personnalisée, et indiquer à kubent qu’il doit vérifier une annotation supplémentaire.

Prenons pour exemple un script de création d’Ingress provenant de l’outil interne de l’équipe de Bob. De base, ce script n’ajoute pas d’annotation à la création, donc tout Ingress créé par ce script sera ignoré par kubent. Pour éviter ce problème, il faut alors modifier le script pour qu’il ajoute une annotation au bon format lors de la création d’un Ingress.

import json
import sys
from kubernetes import client, config

annotation_key = "kubernetes.client.python/last-applied-configuration"
api_version = "networking.k8s.io/v1beta1"
kind = "Ingress"

# Create Ingress with arguments defined while launching the script
def create_ingress (name="my-ingress", namespace="default", host="my-ingress.localhost", service="my-service", port=80):

    # Load Kubernetes configuration and create a client

   config.load_kube_config()
   api_client = client.ApiClient()

    # Create the annotation payload

    annotation_value = {
       "apiVersion": api_version,
       "kind": kind,
       "metadata": {
           "name": name,
           "namespace": namespace
       }
   }

   # Create an Ingress object with specified arguments

    ingress = client.NetworkingV1beta1Ingress(
       api_version=api_version,
       kind=kind,
       metadata=client.V1ObjectMeta(
           name=name, 
           annotations={annotation_key: json.dumps(annotation_value)}
       ),
       spec=client.NetworkingV1beta1IngressSpec(
           rules=[
               client.NetworkingV1beta1IngressRule(
                   host=host,
                   http=client.NetworkingV1beta1HTTPIngressRuleValue(
                       paths=[
                           client.NetworkingV1beta1HTTPIngressPath(
                               path="/",
                               backend=client.NetworkingV1beta1IngressBackend(
                                   service_name=service,
                                   service_port=port,
                               ),
                           ),
                       ],
                   ),
               ),
           ],
       ),
    )

    # Create the Ingress in Kubernetes
   api_instance = client.NetworkingV1beta1Api(api_client)
   api_instance.create_namespaced_ingress(
       namespace=namespace,
       body=ingress,
   )

create_ingress(*sys.argv[1:])

Comme on peut le voir en gras et vert ci-dessus, nous avons ajouté une annotation formatée selon celle de kubectl, mais avec un nom différent indiquant l’origine de l’Ingress : kubernetes.client.python/last-applied-configuration. Le travail avance, mais n’est pas fini, puisqu’il nous faut désormais indiquer à kubent qu’une annotation additionnelle doit être vérifiée. Le monde étant bien fait, Kube-no-trouble dispose d’une option faite pour ça : -- additionnal-annotation (ou son équivalent raccourci -A). Si Bob crée une ressource via ce script et lance kubent en spécifiant son annotation additionnelle, le problème sera donc réglé et ces mêmes ressources pourront être détectées en cas de dépréciation d’API.

$ python3 k8s-client-create-ingress.py my-super-ingress test-kubent

$ kubent --additionnal-annotation kubernetes.client.python/last-applied-configuration

3:48PM INF >>> Kube No Trouble `kubent` <<<
3:48PM INF version 0.7.0 (git sha d1bb4e5fd6550b533b2013671aa8419d923ee042)
3:48PM INF Initializing collectors and retrieving data
3:48PM INF Target K8s version is 1.21.4+k3s1
3:48PM INF Retrieved 4 resources from collector name=Cluster
3:48PM INF Retrieved 13 resources from collector name="Helm v3"
3:48PM INF Loaded ruleset name=custom.rego.tmpl
3:48PM INF Loaded ruleset name=deprecated-1-16.rego
3:48PM INF Loaded ruleset name=deprecated-1-22.rego
3:48PM INF Loaded ruleset name=deprecated-1-25.rego
3:48PM INF Loaded ruleset name=deprecated-1-26.rego
3:48PM INF Loaded ruleset name=deprecated-future.rego
__________________________________________________________________________________________
>>> Deprecated APIs removed in 1.22 <<<
------------------------------------------------------------------------------------------
KIND      NAMESPACE    NAME               API_VERSION               REPLACE_WITH (SINCE)
Ingress   test-kubent  my-client-ingress  networking.k8s.io/v1beta1 networking.k8s.io/v1 (1.19.0)

Malgré ce franc succès, Bob commence à sentir le coup venir ; même si le spectre de vérification de kubent s’élargit, il reste encore des angles morts. Et notre administrateur Kubernetes ne croit pas si bien dire, sachant qu’il reste encore un cas où une dépréciation d’API n’est pas détectée : lorsque l’on utilise des ressources personnalisées utilisant un kind inconnu de kubent. Pour régler ce problème, munissons-nous de votre scaphandre et plongeons ensemble dans les eaux profondes de Kube-no-trouble.

Kube-no-trouble pour les ressources personnalisées

Dans Kubernetes, il est parfaitement possible de créer ses ressources avec un nom et un schéma personnalisés. Ces ressources particulières sont définies par des CustomResourceDefinitions (ou CRD pour les intimes) qui permettent d’étendre l’API Kubernetes et ainsi de rendre toutes sortes de ressources facilement utilisables et contrôlables par kubectl. Les CRD sont utilisées par de nombreuses solutions, qui s’en servent pour intégrer simplement leurs outils à Kubernetes. Le cluster de Bob regorge d’ailleurs de CRD venant de tiers, sans même qu’il le sache ! 

$ kubectl get crd   

NAME                                    CREATED AT
helmcharts.helm.cattle.io               2023-04-12T13:49:12Z
helmchartconfigs.helm.cattle.io         2023-04-12T13:49:12Z
tlsstores.traefik.containo.us           2023-04-12T13:49:30Z
tlsoptions.traefik.containo.us          2023-04-12T13:49:30Z
serverstransports.traefik.containo.us   2023-04-12T13:49:30Z
middlewares.traefik.containo.us         2023-04-12T13:49:30Z
ingressrouteudps.traefik.containo.us    2023-04-12T13:49:30Z
ingressroutetcps.traefik.containo.us    2023-04-12T13:49:30Z
traefikservices.traefik.containo.us     2023-04-12T13:49:30Z
ingressroutes.traefik.containo.us       2023-04-12T13:49:30Z

On peut voir plus haut que les projets Helm et Traefik ont déployé leurs CRDs dans le cluster de Bob. Pour illustrer la situation, considérons une des CRDs de Traefik : 

# Représentation très simplifiée de la CRD "ingressroutes.traefik.containo.us"

Ressource définie : 
 kind : IngressRoute
 group : traefik.containo.us 
 version : v1alpha1

Il est donc possible de requêter / créer / détruire des ressources de type IngressRoute via les commandes kubectl habituelles, ce qui génèrerait une annotation reconnue par kubent. Cependant, que se passerait-il si l’apiVersion de ces IngressRoutes était dépréciée ? Kube-no-trouble ne serait pas en mesure de détecter ce problème, puisque son catalogue de dépréciations ne répertorie actuellement que les ressources provenant de Kubernetes (et pas celles venant de tiers comme Traefik). Si Bob veut pouvoir détecter ces dépréciations, il va devoir agir en plusieurs étapes :

  • Enrichir le catalogue de dépréciations de kubent
  • Construire un nouveau binaire kubent
  • Lancer le binaire construit en indiquant quel kind externe surveiller.

⚠️ Cette méthode est assez fastidieuse, mais elle est actuellement la seule disponible pour ajouter des types inconnus de ressources. Si vous vous sentez l’âme d’un développeur Go, je vous invite à soumettre une PR à l’équipe de kubent pour rendre l’ajout de kinds moins complexe !

Afin de montrer ce cheminement d’action, reprenons le cas de la ressource personnalisée IngressRoute à l’apiVersion traefik.containo.us/v1alpha1, et inventons-lui une dépréciation au profit de traefik.containo.us/v1 pour la version 1.26 de Kubernetes. Pour enrichir le catalogue de dépréciations de Kube-no-trouble, il faut cloner son dépôt et aller modifier les fichiers rego voulus. Rego est un langage de règles open-source permettant de définir des politiques complexes, mais simples à lire ; c’est en l'occurrence ce dont se sert kubent pour définir ses règles de dépréciations, alors ne nous en privons pas : 

$ git clone https://github.com/doitintl/kube-no-trouble.git

$ cd kube-no-trouble

$ ll pkg/rules/rego

-rw-r--r--  1 clementmaciejewski  staff   925B Apr 12 15:15 custom.rego.tmpl
-rw-r--r--  1 clementmaciejewski  staff   1.5K Apr 12 15:15 deprecated-1-16.rego
-rw-r--r--  1 clementmaciejewski  staff   3.5K Apr 13 10:59 deprecated-1-22.rego
-rw-r--r--  1 clementmaciejewski  staff   1.4K Apr 12 15:15 deprecated-1-25.rego
-rw-r--r--  1 clementmaciejewski  staff   1.1K Apr 13 10:48 deprecated-1-26.rego
-rw-r--r--  1 clementmaciejewski  staff   1.3K Apr 12 15:15 deprecated-future.rego

$ vim pkg/rules/rego/deprecated-1-26.rego

$ git diff

diff --git a/pkg/rules/rego/deprecated-1-26.rego b/pkg/rules/rego/deprecated-1-26.rego
index fe67e3e..0cffb01 100644
--- a/pkg/rules/rego/deprecated-1-26.rego
+++ b/pkg/rules/rego/deprecated-1-26.rego
@@ -20,11 +20,18 @@ deprecated_resource(r) = api {
}

 deprecated_api(kind, api_version) = api {
    deprecated_apis = {
        "HorizontalPodAutoscaler": {
            "old": ["autoscaling/v2beta1", "autoscaling/v2beta2"],
            "new": "autoscaling/v2",
            "since": "1.23",
-        }}
+        },
+        "IngressRoute": {
+            "old": ["traefik.containo.us/v1alpha1"],
+            "new": "traefik.containo.us/v1",
+            "since": "1.19.0",
+        }
+    }

Comme on peut le voir ci-dessus, on a dû ajouter un bloc dans les deprecated_apis du fichier deprecated-1-26.rego. Cet ajout définit l’apiVersion dépréciée (traefik.containo.us/v1alpha1), sa remplaçante (traefik.containo.us/v1) et la date de disponibilité de cette dernière (depuis Kubernetes 1.19.0). Une fois ces modifications effectuées, on peut reconstruire le binaire de kubent afin que notre nouvelle règle soit ajoutée : 

$ go build -o bin/kubent cmd/kubent/main.go

Dernière étape : lancer la version reconstruite de kubent en lui indiquant de prendre en compte un kind externe. Pour ce faire, kubent a prévu l’option --additionnal-kind (ou -a pour les intimes). Comme son nom l’indique, cette option permet de prendre en compte d’autres kinds que ceux de Kubernetes, ce qui est très pratique pour détecter les dépréciations d’API venant de tiers (ou de chez soi). D’ailleurs, Bob va en faire l’expérience pas plus tard que maintenant avec son IngressRoute

$ ./bin/kubent -a IngressRoute.v1alpha1.traefik.containo.us

11:23AM INF >>> Kube No Trouble `kubent` <<<
11:23AM INF version dev (git sha dev)
11:23AM INF Initializing collectors and retrieving data
11:23AM INF Target K8s version is 1.21.4+k3s1
11:23AM INF Retrieved 3 resources from collector name=Cluster
11:23AM INF Retrieved 13 resources from collector name="Helm v3"
11:23AM INF Loaded ruleset name=custom.rego.tmpl
11:23AM INF Loaded ruleset name=deprecated-1-16.rego
11:23AM INF Loaded ruleset name=deprecated-1-22.rego
11:23AM INF Loaded ruleset name=deprecated-1-25.rego
11:23AM INF Loaded ruleset name=deprecated-1-26.rego
11:23AM INF Loaded ruleset name=deprecated-future.rego
__________________________________________________________________________________________
>>> Additional resources (custom) <<<
------------------------------------------------------------------------------------------
KIND           NAMESPACE   NAME               API_VERSION                    REPLACE_WITH (SINCE)
IngressRoute   default     my-ingress-route   traefik.containo.us/v1alpha1   <na> ()
__________________________________________________________________________________________
>>> Deprecated APIs removed in 1.26 <<<
------------------------------------------------------------------------------------------
KIND           NAMESPACE   NAME               API_VERSION                    REPLACE_WITH (SINCE)
IngressRoute   default     my-ingress-route   traefik.containo.us/v1alpha1   traefik.containo.us/v1 (1.19.0)

On voit ici que Bob a bien utilisé le nouveau binaire construit dans bin en spécifiant son kind additionnel. Il a ainsi pu détecter une IngressRoute dépréciée et sera à même de détecter toutes sortes de ressources personnalisées à l’avenir ; une flèche de plus à son arc ! Toutefois, Kube-no-trouble ne nous réserve-t-il pas encore quelques surprises de dernière minute ?

Kube-no-trouble pour la conteneurisation

Même si kubent se veut simple d’utilisation et ne comporte pas pléthore de fonctionnalités annexes, il n’a pas encore dit son dernier mot. Dans la catégorie des ajouts bien sentis, je vous présente donc kubent en tant qu’image Docker. Affectueusement nommée ghcr.io/doitintl/kube-no-trouble, cette image peut s’avérer très utile dans de diverses situations : 

  • Au sein d’une équipe technique
  • Dans un pipeline de CI/CD
  • En tant que pod d’un cluster Kubernetes

Dans le cas d’une utilisation de Kube-no-trouble au sein d’une équipe technique, l’idée est simple : construire une version unifiée de kubent, la pousser sur un registre d’images privé et la rendre accessible à tous les membres de l’équipe. L’objectif de cette méthode ? Permettre à tout le monde de travailler sur la même itération de kubent et ainsi détecter les mêmes dépréciations d’API. Pour modifier l’image de kubent, rien de plus simple : il suffit de cloner son dépôt et aimer le développement en Go. Attention toutefois si vous souhaitez inclure des outils externes au binaire de kubent ; Bob a essayé et s’est frotté à l’image scratch du projet, une aventure à réserver aux Dockeriens avertis.

# Pour utiliser kubent via Docker, il faut lui fournir le kubeconfig adéquat

$ docker run -it --rm \
 -v "${HOME}/.kube/config:/.kubeconfig" \
 ghcr.io/doitintl/kube-no-trouble:latest \
 -k /.kubeconfig
En dehors de cette utilisation triviale de l’image, on note également que kubent peut s’avérer fort utile dans une pipeline de CI/CD. Je n’en ai pas parlé auparavant, mais kubent est capable d’envoyer le résultat de son analyse en JSON plutôt qu’en texte, ce qui facilite grandement le traitement des informations : une aubaine pour faire de la CI/CD ou du monitoring ! On peut imaginer par exemple des jobs GitLab CI retournant une erreur si la moindre dépréciation est détectée, comme ci-dessous :
# Ce job GitLab CI analyse les dépréciations d'API

my-deprecation-test:
 image: ghcr.io/doitintl/kube-no-trouble:latest
 before_script:
   - mkdir ~/.kube
   - echo $KUBECONFIG > ~/.kube/config 
 script:
   - echo "Si une dépréciation est détectée, ce job renvoie une erreur"
    - kubent -o json | jq -e 'length != 0' && exit

Utiliser Kube-no-trouble en tant que pod Kubernetes via l’image Docker peut enfin s’avérer particulièrement utile ; que ce soit pour sous-traiter la gestion de son cycle de vie à Kubernetes, pour internaliser le processus au sein de son cluster ou tout simplement pour l’automatiser. On peut se servir de kubent en tant que pod pour un scan ponctuel, mais je le trouve personnellement bien plus pratique en tant que CronJob où il permet une vérification permanente et automatisée.

# Pour utiliser kubenet avec kubectl, il faut les permissions adéquates
# Exemple de permissions : https://github.com/doitintl/kube-no-trouble/blob/master/docs/k8s-sa-and-role-example.yaml

# Exemple de pod "one-shot"

$ kubectl run kubent --restart=Never --rm -i --tty \
   --image ghcr.io/doitintl/kube-no-trouble:latest \
   --overrides='{"spec": {"serviceAccount": "kubent"}}'

# Exemple de CronJob lançant kubent chaque matin à 9h

apiVersion: batch/v1
kind: CronJob
metadata:
 name: kubent
spec:
 schedule: "*/5 * * * *"
 jobTemplate:
   spec:
     ttlSecondsAfterFinished: 120
     template:
       metadata:
         labels:
           app: kubent
       spec:
         serviceAccountName: kubent
         containers:
         - name: kube-no-trouble
           image: ghcr.io/doitintl/kube-no-trouble:latest
          restartPolicy: Never

Avec un CronJob comme ci-dessus, on peut récupérer périodiquement les logs de notre scan pour voir l’état de notre cluster, qu’il y ait une migration prévue ou non. Pour aller plus loin encore, on pourrait également se servir de ces logs avec une stack Prometheus / Grafana / Loki, et paramétrer un tableau de bord répertoriant nos dépréciations, leur nombre et leur type… De quoi voir venir longtemps à l’avance tout type de menace. Il semblerait d’ailleurs que Bob ait pu se construire un tableau de bord Grafana, lui permettant ainsi de repérer un nombre important de dépréciations d’API dans les dernières minutes : l’alerte Grafana a été déclenchée, quelqu’un est à coup sûr en train de déployer des ressources dépréciées !

Avec toutes les fonctionnalités de Kube-no-trouble à sa disposition et la mise en place d’un monitoring solide, nul doute que notre administrateur Kubernetes préféré saura à présent détecter la moindre source de dépréciation d’API et agir en conséquence. Souhaitons-lui le meilleur pour sa migration Kubernetes.

Tableau de bord Grafana comptant les dépréciations d’API et les listant dans un tableau

Conclusion

Kube-no-trouble est un outil très pratique pour détecter facilement les dépréciations d’API au sein de clusters Kubernetes. Très simple d’utilisation, ce dernier résout un problème gênant et permet d’aborder plus sereinement ses upgrades de cluster. Toutefois, la vérification de dépréciations d’API n’est qu’une des étapes de vérification pré-upgrade. Il est nécessaire de lire attentivement les notes de release de Kubernetes pour ne louper aucune modification importante (ce que Reddit a récemment appris à ses dépens, avec une dépréciation de label ayant paralysé leur infrastructure durant plus de 6 heures). En résumé : restez concentrés, lisez la documentation et Kube sera vraiment no trouble.