Modifier

Concevoir un pipeline CI/CD pour microservices sur Kubernetes avec Azure DevOps et Helm

Azure Kubernetes Service (AKS)
Azure Container Registry
Azure DevOps

Il peut être difficile de créer un processus CI/CD (intégration continue et livraison continue) fiable pour une architecture de microservices. Chaque équipe doit être en mesure de mettre les services en production rapidement et en toute fiabilité, sans perturber les autres équipes ni déstabiliser l'application dans son ensemble.

Cet article décrit un exemple de pipeline CI/CD pour le déploiement de microservices sur Azure Kubernetes Service (AKS). Chaque équipe et chaque projet étant uniques, cet article ne doit pas être considéré comme un ensemble de règles à suivre à la lettre. Il s'agit plutôt d'un point de départ pour concevoir votre propre processus CI/CD.

Les objectifs d'un pipeline CI/CD pour les microservices hébergés sur Kubernetes peuvent être résumés comme suit :

  • Les équipes peuvent générer et déployer leurs services en toute indépendance.
  • Les modifications de code qui franchissent le processus CI sont automatiquement déployées dans un environnement de type production.
  • Des critères de qualité sont appliqués à chaque étape du pipeline.
  • Une nouvelle version d'un service peut être déployée aux côtés de la version précédente.

Pour plus d'informations, consultez Intégration continue/livraison continue (CI/CD) pour les architectures de microservices.

Hypothèses

Cet exemple repose sur les hypothèses suivantes en termes d'équipe de développement et de code base :

  • Le référentiel de code est un « référentiel unique », avec des dossiers organisés par microservice.
  • La stratégie de création de branche de l’équipe est basée sur un développement de type tronc.
  • L'équipe utilise des branches de mise en production pour gérer les mises en production. Des mises en production distinctes sont créées pour chaque microservice.
  • Le processus CI/CD utilise Azure Pipelines pour générer, tester et déployer les microservices sur AKS.
  • Les images conteneur de chaque microservice sont stockées dans Azure Container Registry.
  • L'équipe utilise des graphiques Helm pour empaqueter chaque microservice.
  • Un modèle de déploiement Push est utilisé, où Azure Pipelines et les agents associés effectuent des déploiements en se connectant directement au cluster AKS.

Ces hypothèses déterminent un certain nombre de détails spécifiques au pipeline CI/CD. Cela dit, l'approche de base décrite ici peut être adaptée à d'autres processus, outils et services, tels que Jenkins ou Docker Hub.

Autres solutions

Voici les alternatives courantes que les clients peuvent utiliser lors du choix d’une stratégie CI/CD avec Azure Kubernetes Service :

  • En guise d’alternative à l’utilisation de Helm comme outil de gestion et de déploiement de package, Kustomize est un outil de gestion de configuration native Kubernetes qui introduit un moyen gratuit de modèle pour personnaliser et paramétrer la configuration de l’application.
  • En guise d’alternative à l’utilisation d’Azure DevOps pour les référentiels et pipelines Git, les référentiels GitHub peuvent être utilisés pour les référentiels Git privés et publics, et les actions GitHub peuvent être utilisées pour les pipelines CI/CD.
  • En guise d’alternative à l’utilisation d’un modèle de déploiement Push, la gestion de la configuration Kubernetes à grande échelle peut être effectuée à l’aide de GitOps (modèle de déploiement pull), où un opérateur Kubernetes en cluster synchronise l’état du cluster, en fonction de la configuration stockée dans un référentiel Git.

Builds de validation

Supposons qu'un développeur travaille sur un microservice appelé « Delivery Service ». Lors du développement d’une nouvelle fonctionnalité, le développeur archive le code dans une branche de fonctionnalité. Par convention, les branches de fonctionnalité sont nommées feature/*.

CI/CD workflow

Le fichier de définition de build inclut un déclencheur qui filtre par nom de branche et par chemin source :

trigger:
  batch: true
  branches:
    include:
    # for new release to production: release flow strategy
    - release/delivery/v*
    - refs/release/delivery/v*
    - master
    - feature/delivery/*
    - topic/delivery/*
  paths:
    include:
    - /src/shipping/delivery/

Avec cette approche, chaque équipe peut avoir son propre du pipeline de build. Seul le code qui est archivé dans le dossier /src/shipping/delivery déclenche une build de Delivery Service. L'envoi (push) des validations vers une branche qui correspond au filtre déclenche une build CI. À ce stade du workflow , la build CI exécute une vérification minimale du code :

  1. Génération du code
  2. Exécution de tests unitaires

L'objectif est de réduire les délais de build pour que le développeur obtienne des commentaires rapides. Lorsque la fonctionnalité est prête à être fusionnée dans la branche master, le développeur ouvre une demande de tirage. Cette opération déclenche une autre build CI qui effectue des vérifications supplémentaires :

  1. Génération du code
  2. Exécution de tests unitaires
  3. Génération de l'image du conteneur d'exécution
  4. Exécution d'analyses de vulnérabilité sur l'image

Diagram showing ci-delivery-full in the Build pipeline.

Remarque

Dans Azure DevOps Repos, vous pouvez définir des stratégies pour protéger les branches. Par exemple, la stratégie peut exiger une build CI réussie ainsi qu’une validation d’un approbateur pour fusionner dans la branche master.

Build CI/CD complète

À un moment donné, l’équipe est prête à déployer une nouvelle version du service « Delivery ». Le gestionnaire de mises en production crée une branche à partir de la branche primaire conformément au modèle de dénomination suivant : release/<microservice name>/<semver>. Par exemple : release/delivery/v1.0.2.

Diagram showing ci-delivery-full in the Build pipeline and cd-delivery in the Release pipeline.

La création de cette branche déclenche une build CI complète qui exécute toutes les étapes précédentes, plus ceci :

  1. L'envoi (push) de l'image conteneur à Azure Container Registry. L’image est étiquetée avec le numéro de version tiré du nom de la branche.
  2. L'exécution de helm package afin d'empaqueter le graphique Helm pour le service. Le graphique est également étiqueté avec un numéro de version.
  3. L'envoi (push) du package Helm à Container Registry.

Si cette build réussit, elle déclenche un processus de déploiement (CD) avec un pipeline de mise en production Azure Pipelines. Ce pipeline de données se décompose en plusieurs étapes :

  1. Déployez le graphique Helm sur un environnement AQ.
  2. Un approbateur effectue une validation avant que le package passe en production. Consultez Contrôler le déploiement de mise en production avec des approbations.
  3. Réappliquez une étiquette à l'image Docker pour l'espace de noms de production dans Azure Container Registry. Par exemple, si l’étiquette actuelle est myrepo.azurecr.io/delivery:v1.0.2, l’étiquette de production est myrepo.azurecr.io/prod/delivery:v1.0.2.
  4. Déployez le graphique Helm dans l'environnement de production.

Même dans un référentiel unique, ces tâches peuvent être limitées à des microservices individuels, afin que les équipes puissent effectuer des déploiements très rapidement. Le processus comporte quelques étapes manuelles : Approbation des demandes de tirage, création de branches de mise en production et approbation des déploiements sur le cluster de production. Ces étapes sont définies comme « manuelles » ; elles peuvent cependant être entièrement automatisées si l'organisation le préfère.

Isolation des environnements

Vous disposerez de plusieurs environnements dans lesquels vous déploierez des services, notamment des environnements de développement, de test de détection de fumée, de test d'intégration, de test de chargement et, enfin, de production. Ces environnements ont besoin d’un certain niveau d’isolation. Dans Kubernetes, vous avez le choix entre l’isolation physique et l’isolation logique. Dans le cadre de l’isolation physique, le déploiement est effectué sur des clusters distincts. L'isolation logique utilise des espaces de noms et des stratégies, comme décrit précédemment.

Nous vous recommandons de créer un cluster de production dédié ainsi qu’un cluster distinct pour vos environnements de développement/test. L’isolation logique permet de séparer les environnements au sein du cluster de développement/test. Les services déployés sur le cluster de développement/test ne doivent jamais avoir accès à des magasins de données qui contiennent des données métier.

Processus de génération

Si possible, empaquetez votre processus de génération dans un conteneur Docker. Cette configuration vous permettra de créer les artefacts de build à l'aide de Docker, sans avoir à configurer l'environnement de build sur chaque machine de build. Un processus de génération en conteneur facilite le scale-out du pipeline CI en ajoutant de nouveaux agents de build. En outre, n'importe quel développeur de l'équipe peut créer le code en se contentant d'exécuter le conteneur de build.

L'utilisation de builds multi-étapes dans Docker vous permet de définir l'environnement de génération et l'image d'exécution au sein d'un même Dockerfile. Par exemple, voici un Dockerfile qui génère une application .NET :

FROM mcr.microsoft.com/dotnet/core/runtime:3.1 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src/Fabrikam.Workflow.Service

COPY Fabrikam.Workflow.Service/Fabrikam.Workflow.Service.csproj .
RUN dotnet restore Fabrikam.Workflow.Service.csproj

COPY Fabrikam.Workflow.Service/. .
RUN dotnet build Fabrikam.Workflow.Service.csproj -c release -o /app --no-restore

FROM build AS testrunner
WORKDIR /src/tests

COPY Fabrikam.Workflow.Service.Tests/*.csproj .
RUN dotnet restore Fabrikam.Workflow.Service.Tests.csproj

COPY Fabrikam.Workflow.Service.Tests/. .
ENTRYPOINT ["dotnet", "test", "--logger:trx"]

FROM build AS publish
RUN dotnet publish Fabrikam.Workflow.Service.csproj -c Release -o /app

FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "Fabrikam.Workflow.Service.dll"]

Ce Dockerfile définit plusieurs étapes de génération. Notez que l’étape nommée base utilise le runtime .NET, tandis que l’étape nommée build utilise le Kit de développement logiciel (SDK) .NET complet. L’étape build est utilisée pour générer le projet .NET. Mais le conteneur d'exécution final est généré à partir de base, qui ne contient que le runtime et est nettement plus petit que l'image SDK complète.

Générer un Test Runner

Une autre bonne pratique consiste à exécuter des tests unitaires dans le conteneur. Par exemple, voici un extrait de fichier Docker qui génère un Test Runner :

FROM build AS testrunner
WORKDIR /src/tests

COPY Fabrikam.Workflow.Service.Tests/*.csproj .
RUN dotnet restore Fabrikam.Workflow.Service.Tests.csproj

COPY Fabrikam.Workflow.Service.Tests/. .
ENTRYPOINT ["dotnet", "test", "--logger:trx"]

Un développeur peut utiliser ce fichier Docker pour exécuter les tests localement :

docker build . -t delivery-test:1 --target=testrunner
docker run delivery-test:1

Le pipeline CI doit également exécuter les tests dans le cadre de l'étape de vérification de build.

Notez que ce fichier utilise la commande Docker ENTRYPOINT pour exécuter les tests, et non la commande Docker RUN.

  • Si vous utilisez la commande RUN, les tests s'exécutent chaque fois que vous générez l'image. En utilisant ENTRYPOINT, les tests sont choisis. Ils ne s'exécutent que lorsque vous ciblez explicitement l'étape testrunner.
  • Un échec au test n'entraîne pas l'échec de la commande Docker build. Vous pouvez ainsi distinguer les échecs de génération de conteneurs des échecs de test.
  • Les résultats des tests peuvent être enregistrés sur un volume monté.

Meilleures pratiques en matière de conteneurs

Voici quelques autres bonnes pratiques à prendre en compte pour les conteneurs :

  • Définissez des conventions d’organisation pour les balises de conteneurs, la gestion des versions et des conventions de d’affectation de noms pour les ressources déployées sur le cluster (pod, services, etc.). Cela peut simplifier le diagnostic des problèmes de déploiement.

  • Durant le cycle de développement et de test, le processus d’intégration et de livraison continue génère de nombreuses images de conteneur. Seules certaines de ces images sont candidates à la mise en production, puis seules certaines de ces candidatures seront promues en production. Vous devez disposer d'une stratégie claire de gestion des versions, qui vous permette de savoir quelles images sont déployées en production à un instant t, et ainsi de revenir à une version antérieure si nécessaire.

  • Déployez toujours des balises de version de conteneur spécifiques, et non latest.

  • Utilisez des espaces de noms dans Azure Container Registry pour isoler les images approuvées pour la production des images qui sont toujours en cours de test. Ne déplacez pas une image dans l'espace de noms de production tant que vous n'êtes pas prêt à la déployer en production. Si vous combinez cette pratique à la gestion sémantique des versions des images de conteneurs, vous pouvez réduire les risques de déploiement accidentel d’une version non approuvée pour publication.

  • Suivez le principe du moindre privilège en exécutant des conteneurs en tant qu'utilisateur non privilégié. Dans Kubernetes, vous pouvez créer une stratégie de sécurité des pods qui empêche les conteneurs de s'exécuter en tant que root (racine).

Graphiques Helm

Envisagez d’utiliser Helm pour gérer la génération et le déploiement des services. Les fonctionnalités Helm suivantes facilitent l'intégration et le déploiement continus :

  • Souvent, un même microservice est défini par plusieurs objets Kubernetes. Helm permet à ces objets d'être empaquetés au sein d'un même graphique Helm.
  • Un graphique peut être déployé avec une commande Helm unique, plutôt qu'avec une série de commandes kubectl.
  • Les graphiques disposent de versions explicites. Utilisez Helm pour publier une version, visualiser les versions et revenir à une version précédente. Suivi des mises à jour et des révisions, à l’aide du contrôle de version sémantique, avec possibilité de restaurer une version précédente
  • Les graphiques Helm utilisent des modèles pour éviter la duplication dans plusieurs fichiers d'informations telles que les étiquettes et les sélecteurs.
  • Helm peut gérer les dépendances entre les graphiques.
  • Les graphiques peuvent être stockés dans un référentiel Helm, tel qu'Azure Container Registry, et intégrés dans le pipeline de build.

Pour plus d’informations sur l’utilisation de Container Registry comme dépôt Helm, consultez Utiliser Azure Container Registry comme référentiel Helm pour les graphiques de votre application.

Un même microservice peut impliquer plusieurs fichiers de configuration Kubernetes. La mise à jour d'un service peut nécessiter la modification de tous ces fichiers pour mettre à jour les sélecteurs, les étiquettes et les balises d'image. Helm les traite comme un package unique appelé graphique et vous permet de mettre à jour facilement les fichiers YAML à l'aide de variables. Helm utilise un langage de modèle (basé sur les modèles Go) pour vous permettre d'écrire des fichiers de configuration YAML paramétrés.

Par exemple, voici un extrait de fichier YAML qui définit un déploiement :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "package.fullname" . | replace "." "" }}
  labels:
    app.kubernetes.io/name: {{ include "package.name" . }}
    app.kubernetes.io/instance: {{ .Release.Name }}
  annotations:
    kubernetes.io/change-cause: {{ .Values.reason }}

...

  spec:
      containers:
      - name: &package-container_name fabrikam-package
        image: {{ .Values.dockerregistry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        env:
        - name: LOG_LEVEL
          value: {{ .Values.log.level }}

Vous pouvez constater que le nom du déploiement, les étiquettes et les spécifications des conteneurs utilisent tous des paramètres de modèle, qui sont fournis au moment du déploiement. Par exemple, à partir de la ligne de commande :

helm install $HELM_CHARTS/package/ \
     --set image.tag=0.1.0 \
     --set image.repository=package \
     --set dockerregistry=$ACR_SERVER \
     --namespace backend \
     --name package-v0.1.0

Bien que votre pipeline CI/CD puisse installer un graphique directement dans Kubernetes, nous vous recommandons de créer une archive de graphique (fichier .tgz) et d'envoyer (push) le graphique à un référentiel Helm tel qu'Azure Container Registry. Pour plus d'informations, consultez Empaqueter des applications basées sur Docker au sein de graphiques Helm dans Azure Pipelines.

Révisions

Les graphiques Helm ont toujours un numéro de version, qui doit utiliser la gestion sémantique de version. Un graphique peut également avoir une appVersion. Ce champ est facultatif et ne doit pas nécessairement être lié à la version du graphique. Certaines équipes peuvent souhaiter appliquer les versions séparément des mises à jour des graphiques. Mais il existe une approche plus simple qui consiste à utiliser un seul numéro de version afin de créer une relation 1:1 entre la version du graphique et la version de l'application. Vous pouvez ainsi stocker un graphique par version et déployer facilement la version souhaitée :

helm install <package-chart-name> --version <desiredVersion>

Une autre bonne pratique consiste à fournir une annotation sur la cause de la modification dans le modèle de déploiement :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "delivery.fullname" . | replace "." "" }}
  labels:
     ...
  annotations:
    kubernetes.io/change-cause: {{ .Values.reason }}

Cela vous permet de consulter le champ de la cause de la modification pour chaque révision, à l'aide de la commande kubectl rollout history. Dans l'exemple précédent, la cause de la modification est fournie sous forme de paramètre de graphique Helm.

kubectl rollout history deployments/delivery-v010 -n backend
deployment.extensions/delivery-v010
REVISION  CHANGE-CAUSE
1         Initial deployment

Vous pouvez également utiliser la commande helm list pour afficher l'historique des révisions :

helm list
NAME            REVISION    UPDATED                     STATUS        CHART            APP VERSION     NAMESPACE
delivery-v0.1.0 1           Sun Apr  7 00:25:30 2020    DEPLOYED      delivery-v0.1.0  v0.1.0          backend

Pipeline Azure DevOps

Dans Azure Pipelines, les pipelines sont divisés en pipelines de build et pipelines de mise en production. Le pipeline de build exécute le processus CI et crée des artefacts de build. Pour une architecture de microservices sur Kubernetes, ces artefacts sont les images conteneur et les graphiques Helm qui définissent chaque microservice. Le pipeline de mise en production exécute ce processus CD qui déploie un microservice dans un cluster.

En fonction du flux CI décrit plus haut dans cet article, un pipeline de build peut être constitué des tâches suivantes :

  1. Générez le conteneur Test Runner.

    - task: Docker@1
      inputs:
        azureSubscriptionEndpoint: $(AzureSubscription)
        azureContainerRegistry: $(AzureContainerRegistry)
        arguments: '--pull --target testrunner'
        dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName)
        imageName: '$(imageName)-test'
    
  2. Exécutez les tests, en appelant docker run sur le conteneur Test Runner.

    - task: Docker@1
      inputs:
        azureSubscriptionEndpoint: $(AzureSubscription)
        azureContainerRegistry: $(AzureContainerRegistry)
        command: 'run'
        containerName: testrunner
        volumes: '$(System.DefaultWorkingDirectory)/TestResults:/app/tests/TestResults'
        imageName: '$(imageName)-test'
        runInBackground: false
    
  3. Publiez les résultats des tests. Consultez Créer une image.

    - task: PublishTestResults@2
      inputs:
        testResultsFormat: 'VSTest'
        testResultsFiles: 'TestResults/*.trx'
        searchFolder: '$(System.DefaultWorkingDirectory)'
        publishRunAttachments: true
    
  4. Générez le conteneur d'exécution.

    - task: Docker@1
      inputs:
        azureSubscriptionEndpoint: $(AzureSubscription)
        azureContainerRegistry: $(AzureContainerRegistry)
        dockerFile: $(System.DefaultWorkingDirectory)/$(dockerFileName)
        includeLatestTag: false
        imageName: '$(imageName)'
    
  5. Envoyez (push) l'image conteneur à Azure Container Registry (ou à un autre registre de conteneurs).

    - task: Docker@1
      inputs:
        azureSubscriptionEndpoint: $(AzureSubscription)
        azureContainerRegistry: $(AzureContainerRegistry)
        command: 'Push an image'
        imageName: '$(imageName)'
        includeSourceTags: false
    
  6. Empaquetez le graphique Helm.

    - task: HelmDeploy@0
      inputs:
        command: package
        chartPath: $(chartPath)
        chartVersion: $(Build.SourceBranchName)
        arguments: '--app-version $(Build.SourceBranchName)'
    
  7. Envoyez (push) le package Helm à Azure Container Registry (ou à un autre référentiel Helm).

    task: AzureCLI@1
      inputs:
        azureSubscription: $(AzureSubscription)
        scriptLocation: inlineScript
        inlineScript: |
        az acr helm push $(System.ArtifactsDirectory)/$(repositoryName)-$(Build.SourceBranchName).tgz --name $(AzureContainerRegistry);
    

La sortie du pipeline CI est composée d'une image conteneur prête pour la production et d'un graphique Helm mis à jour pour le microservice. À ce stade, le pipeline de mise en production peut prendre le relais. Il y aura un pipeline de mise en production unique pour chaque microservice. Le pipeline de mise en production est configuré pour avoir une source de déclencheur définie sur le pipeline CI qui a publié l’artefact. Ce pipeline vous permet d’avoir des déploiements indépendants de chaque microservice. Le pipeline de mise en production effectue les étapes suivantes :

  • Déployer le graphique Helm sur des environnements dev/QA/intermédiaires. La commande Helm upgrade peut être utilisée avec l’indicateur --install pour prendre en charge la première installation et les mises à niveau suivantes.
  • Attendre qu'un approbateur approuve ou rejette le déploiement
  • Ré-étiqueter l'image conteneur pour la mise en production
  • Envoyer (push) l'étiquette de mise en production au registre de conteneurs
  • Déployer le graphique Helm dans le cluster de production.

Pour plus d'informations sur la création d'un pipeline de mise en production, consultez Pipelines de mise en production, versions préliminaires et options de mise en production.

Le diagramme suivant illustre le processus CI/CD de bout en bout décrit dans cet article :

CD/CD pipeline

Contributeurs

Cet article est géré par Microsoft. Il a été écrit à l’origine par les contributeurs suivants.

Auteur principal :

  • John Poole | Architecte de solutions cloud senior

Pour afficher les profils LinkedIn non publics, connectez-vous à LinkedIn.

Étapes suivantes