Home >> Blog >> La gestion automatisée des dépendances dans un process GitOps

La gestion automatisée des dépendances dans un process GitOps

07 novembre 2024

Par Maxime Ancellin.

Introduction

Dans mon dernier article, je vous ai présenté des outils de gestion de dépendances et en particulier Renovate.

Maintenant, nous allons voir comment gérer notre infrastructure et ses applications comme n’importe quelles dépendances applicatives.

Cela nous permettra d’automatiser facilement les déploiements et de maintenir notre stack technique à jour.

Le tout se fera au travers d’un processus GitOps.
Si vous n’êtes pas familier avec ce terme, je vous invite à lire notre article dédié sur ce sujet: GitOps and the Millefeuille dilemma.

Ce post est la suite de l’article « La gestion automatisée des dépendances« , je vous invite donc à lire celui-ci au préalable.

Contexte

La démarche présentée ci-dessous s’applique principalement à un environnement de petite taille, tel que celui d’une start-up ou d’une scale-up.
Ce type de contexte se distingue par une organisation allégée, avec très peu d’intermédiaires et des collaborateurs qui endossent souvent des responsabilités transversales.

L’ensemble de la chaine technique est contrôlé par une poigné de personnes et cela rend la gouvernance plus facile.

Cette approche diffère de celle d’une entreprise plus structurée, où des équipes spécialisées détiennent des responsabilités bien définies sur des périmètres spécifiques.

Pour explorer une perspective complémentaire, vous pouvez consulter cet article : Promouvoir les changements et les releases avec GitOps.

Les Enjeux de la Gestion des Dépendances en GitOps

Nos infrastructures techniques ont beaucoup évolué ces dernières années avec les concepts de micro-services et de cloud native applications.

Cela implique de gérer de grands ensembles d’applications à jour, ce qui est indispensable pour garantir le bon fonctionnement des systèmes ainsi que leur sécurité.

Voici quelques bases importantes pour la suite.

Les micros services

Je ne vais pas rentrer dans le détail ici, mais nous allons décrire les caractéristiques de ce type d’application afin que nous soyons alignés pour la suite de cet article.

  • Indépendance : Chaque microservice est autonome, peut être développé, déployé et mis à jour indépendamment.
  • Responsabilité unique : Chaque microservice gère une fonction métier spécifique, suivant le principe de « Single Responsibility ».
  • Autonomie des données : Chaque microservice possède sa propre base de données ou son propre stockage.
  • Scalabilité individuelle : Chaque microservice peut être mis à l’échelle indépendamment en fonction des besoins.
  • Résilience : Si un microservice échoue, les autres continuent de fonctionner, garantissant la tolérance aux pannes.
  • Observable : Chaque microservice se rend observable en exposant ses metrics, il peut également être instrumenté d’un APM.

Afin de respecter ces quelques grands principes, en général, on a un projet Git pour chacun de ces services.

Cela complexifie donc grandement la gestion des applications à cause de leur grand nombre.

Automatisation

Pour pallier aux problématiques de gestion d’un nombre d’applications grandissant, nous avons cherché à automatiser un maximum les tâches redondantes de mise à jour des outils que nous embarquons.
Ce sujet a été traité dans mon dernier article.

Mais maintenant, se pose la question de la gestion de ces applications au sein de notre infrastructure. Ce qui fait qu’elles deviennent elles-mêmes une forme de dépendance.

En effet, nos applications sont versionnées et autonomes.

Pour une grande partie des systèmes modernes, ces applications embarquent dans leurs projets Git la création de leurs conteneurs, voire même de leur configuration de déploiement.
Cela permet d’avoir dans ce même projet et les versions l’ensemble des ressources qui rendent notre application « prête à déployer ».

Exemple de projet applicatif

Dans notre projet, nous ajoutons un dossier deployments, qui contient les configurations suivantes:

├── charts # Fichier de références pour Helm
│   ├── Chart.yaml
│   ├── default.yaml
│   ├── staging.yaml
│   ├── pre-production.yaml
│   └── production.yaml
└── configurations # Manifests Kubernetes
    ├── staging
    │   └── secrets
    │       ├── my-secret-a-sealed.yaml
    │       └── my-secret-b-sealed.yaml
    ├── pre-production
    │   └── secrets
    │       ├── my-secret-a-sealed.yaml
    │       └── my-secret-b-sealed.yaml
    └── production
        └── secrets
            ├── my-secret-a-sealed.yaml
            └── my-secret-b-sealed.yaml

Intégration de Renovate dans un Workflow GitOps

Maintenant, nous allons voir comment il est possible de gérer notre infrastructure et ses applications comme n’importe quelle dépendance applicative.

Pour cela, nous allons avoir besoin d’un référentiel de nos projets Git/applicatifs.
Cela va nous permettre plusieurs choses :

  • Avoir un endroit de référence sur lequel nos pipelines GitOps pourront s’appuyer pour déployer les applications.
  • Permettre à Renovate d’avoir un endroit sur lequel s’appuyer pour faire ses mises à jour.

Ce référentiel est pratique, mais cela ne nous permet pas de régler notre problématique d’environnement.

Afin de pouvoir gérer de multiples environnements, nous allons en créer un pour chacun d’entre eux.

Par exemple, je pourrai créer un Helm chart qui me permettra de déployer une liste d’applications (tout dépend de votre chaîne GitOps existante).

applications:
    - name: frontend
      sources:
        - repoURL: git@github.com:my-org/frontend.git
          targetRevision: 1.2.3
    - name: api
      sources:
        - repoURL: git@github.com:my-org/api.git
          targetRevision: 5.4.3

Peu importe le format que l’on choisira, Renovate sera en mesure via un customManagers de type regex de retrouver la dépendance ainsi que la version.

Dans notre cas, nous pouvons utiliser la configuration ci-dessous:

renovate.json

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "separateMajorMinor": false,
  "customManagers": [
    {
      "customType": "regex",
      "fileMatch": [
        "staging.yaml"
      ],
      "matchStrings": [
        "repoURL: (?<depName>.*?)n(( |n)*)targetRevision: (?<currentValue>.*)"
      ],
      "datasourceTemplate": "git-tags"
    },
  ],
  "packageRules": [
    {
      "matchManagers": [
        "custom.regex"
      ],
      "matchFileNames": [
        "staging.yaml"
      ],
      "groupName": "release-to-staging",
      "reviewers": [
        "team:qa",
      ]
    },
  ]
}

Nous devons avoir un fonctionnement se rapprochant de celui décrit par les schémas ci-dessous :

  1. Lecture du fichier référent nos applications


  2. Lecture des projets Git applicatifs


  3. Déploiement des applications


Maintenant que nous avons notre référentiel et que Renovate est capable de les interpréter, nous allons voir comment il est possible de gérer automatiquement les mises à jour et donc les mises en production.

Mise en Œuvre de l’Automatisation des Mises à Jour

Nous avons vu qu’il était relativement facile de gérer automatiquement les mises à jour des dépendances dans un fichier, malgré un format personnalisé.

Maintenant, il faut que nous puissions gérer la promotion entre les environnements afin d’éviter que tous nos environnements se voient proposer les mêmes mises à jour au même moment, sans respecter le processus de promotion entre les environnements.

Si nous reprenons notre fichier d’environnement de la section précédente et que nous partons du principe que nous avons les environnements suivants, avec chacun leurs règles :

  • staging.yaml: Déploiement en continu des nouvelles versions.
  • preprod.yaml: Mise à jour des versions depuis staging.
  • prod.yaml: Mise à jour des versions depuis pré-production.

La représentation du flux entre les environnements sera donc le suivant.


D’un point de vue de la configuration Renovate, nous allons devoir faire la configuration suivante:

{
  "customDatasources": {
    "localstaging": {
      "format": "yaml",
      "defaultRegistryUrlTemplate": "file://applications/staging.yaml",
      "transformTemplates": [
        "{"releases":[{"version": 'applications'.applications.sources[repoURL='{{depName}}'].targetRevision }]}"
      ]
    },
    "localpreprod": {
      "format": "yaml",
      "defaultRegistryUrlTemplate": "file://applications/preprod.yaml",
      "transformTemplates": [
        "{"releases":[{"version": 'applications'.applications.sources[repoURL='{{depName}}'].targetRevision }]}"
      ]
    }
  },
  "customManagers": [
    {
      "customType": "regex",
      "fileMatch": [
        "applications/staging.yaml"
      ],
      "matchStrings": [
        "repoURL: (?<depName>.*?)\n(( |\n)*)targetRevision: (?<currentValue>.*)"
      ],
      "datasourceTemplate": "git-tags"
    },
    {
      "customType": "regex",
      "fileMatch": [
        "applications/preprod.yaml"
      ],
      "matchStrings": [
        "repoURL: (?<depName>.*?)\n(( |\n)*)targetRevision: (?<currentValue>.*)"
      ],
      "datasourceTemplate": "custom.localstaging"
    },
    {
      "customType": "regex",
      "fileMatch": [
        "applications/prod.yaml"
      ],
      "matchStrings": [
        "repoURL: (?<depName>.*?)\n(( |\n)*)targetRevision: (?<currentValue>.*)"
      ],
      "depNameTemplate": "{{{ replace 'git@github.com:my-org/(.*).git' '$1' depName }}}",
      "datasourceTemplate": "custom.localpreprod"
    }
  ],
  "packageRules": [
    {
      "matchManagers": [
        "custom.regex"
      ],
      "matchFileNames": [
        "staging.yaml"
      ],
      "groupName": "staging",
      "automerge": true
    },
    {
      "matchManagers": [
        "custom.regex"
      ],
      "matchFileNames": [
        "preprod.yaml"
      ],
      "groupName": "pre-production",
      "reviewers": [
        "team:qa"
      ]
    },
    {
      "matchManagers": [
        "custom.regex"
      ],
      "matchFileNames": [
        "prod.yaml"
      ],
      "groupName": "production",
      "reviewers": [
        "team:leader"
      ]
    }
  ]
}

Nous avons donc une nouvelle notion qui est apparue depuis tout à l’heure.
La notion de customDatasources.

Une « datasource » personnalisé est défini par environnement sourçant un fichier YAML :

  • localstaging : Source de référence pour preprod.yml.
  • localpreprod : Source pour prod.yaml.

Chaque datasource est configurée pour lire le fichier YAML correspondant et extraire la version (targetRevision) d’une application via l’instruction transformTemplates.

Nous ne l’utilisons pas pour l’environnement de staging car nous utilisons directement les git-tags fournis par Renovate.

Avantages de l’Approche Automatisée

En général, nous aimons tous l’automatisation, car cela nous évite les tâches qui prennent du temps pour peu de valeur.

Mais il est toujours intéressant de voir plus loin.

Avec la proposition ci-dessus, nous pouvons éviter toutes les erreurs humaines sur les versions qui seront déployées sur les environnements.

Le fait que cela passe par des « pull requests » et que nous gérons cela en GitOps nous permet d’avoir une parfaite traçabilité des actions effectuées.

De plus, nous renforçons le processus de promotion entre les environnements, car l’outil se base uniquement sur l’environnement inférieur pour sourcer les nouvelles versions à déployer.

Vous avez également la possibilité de choisir le cadencement de Renovate afin de définir précisément votre rythme de déploiement.

Sans cela, votre limite sera uniquement la vitesse à laquelle vous itérez et fusionnez les changements.

Attention, afin d’arriver à une telle dynamique, il est important que vous ayez automatisé le reste du processus.
Sinon, vous allez vous retrouver rapidement bloqué à certaines étapes, et cela créera une frustration et de l’énervement autour des éléments bloquants.

Bonnes Pratiques et Conseils

Évidemment, ce genre d’automatisation peut bousculer beaucoup vos habitudes, mais cela apporte également de nombreux avantages.

Pour limiter la friction et faciliter l’adoption, voici quelques conseils.

Alignement organisationnel

Il est important de calquer votre architecture sur votre schéma organisationnel.

C’est-à-dire que votre architecture de sources GitOps doit parfaitement correspondre à vos équipes et que les droits configurés correspondent à votre hiérarchie.

Le risque de ne pas faire cela est que personne ne sera là ou ne pourra facilement autoriser les déploiements qui sont proposés.

Cela est globalement lié au GitOps, mais Renovate peut mettre en évidence les manquements lors de la mise en place de cela.

Apporter de la visibilité

Afin d’avoir une meilleure visibilité sur les « pull requests » qui seront proposées, je ne peux que vous conseiller de maintenir un fichier changelog.md à jour dans votre Git.

Cela permettra à Renovate de l’inclure lors des propositions, et vous pourrez avoir l’ensemble des informations en un seul coup d’œil.

Si vous n’avez pas le temps de le faire, il existe de nombreux outils (Git-chglog, Conventional-changelog) permettant de le faire automatiquement.

Garder la main sur le processus

L’automatisation ne veut pas dire aucun contrôle.
Cela permet de gagner du temps sur des tâches chronophages et de se concentrer uniquement sur l’essentiel.

Ce que nous permet de faire Renovate dans notre cas.
Il s’occupe de trouver les mises à jour à effectuer pour chacun de nos environnements, tout en nous les proposant afin que nous ayons « juste » à valider celles-ci.

Vous pouvez également travailler avec les packageRules afin de définir des notifications pour les personnes concernées par la « pull request » proposée.

Conclusion

Vous l’aurez compris avec ces deux articles sur Renovate: l’essayer, c’est l’adopter.
Une fois cela en place, il vous sera difficile de revenir en arrière.

En réalité, cela se fait avec relativement peu d’effort, mais vous apportera un gain de temps et d’efficacité considérable.

De plus, vous pouvez l’intégrer à plusieurs niveaux:

  • Pour la gestion des dépendances de vos applications
  • Pour gérer les déploiements de nouvelles versions de vos services ou de votre infrastructure.

Le tout peut être sublimé via une approche GitOps.

Nous avons exploré comment cette approche permet de structurer efficacement les mises à jour à travers différents environnements, tout en garantissant un contrôle total via des « pull requests » et en respectant les processus de promotion.

Adopter cette méthode vous permet non seulement de réduire les erreurs humaines, mais aussi de fluidifier vos déploiements en conservant une traçabilité complète de chaque étape.

C’est une solution puissante et peu coûteuse pour optimiser vos pipelines de livraison continue tout en restant aligné avec les principes du GitOps.

Néanmoins, avant de mettre cela en place, pensez à adapter votre démarche à votre organisation.
Pour notre exemple, cela a été fait dans un context d’entreprise de taille petite/moyenne, tel que celui d’une start-up ou d’une scale-up.

Maintenant, à vous de faire vos mises en production en un simple clic !

Laisser un commentaire

  Edit this page