Vous avez probablement remarqué que nous avons automatisé la création d'une image Docker, et qu'elle réside maintenant dans notre registre de conteneurs sur Gitlab.
Maintenant, ce que nous voulons faire, c'est exécuter la bonne commande kubectl pour télécharger cette image dans notre cluster Kubernetes.
Est-ce qu'on peut automatiser ce processus dans notre .gitlab-ci.yml ? Oui !
Publier un image de prod
Nous allons ajouter une étape à notre pipeline: publish pour élever notre image déjà créée et testée (avec nos tests e2e) au statut de production, en créant un tag.
D'abord, nous allons protéger nos tags dans Gitlab, en précisant que seulement des tags d'un certain format puissent être déployé en production.
Naviguez dans Settings → Repository → Protected Tags. Tapez *.*.* dans la barre de recherche, puis cliquer sur "Create wildcard tag". Sélectionner le role Maintainers (seulement les utilisateurs privilégiés auront le droit à déployer); puis cliquer sur "Protect".
Retournez à votre .gitlab-ci.yaml, et ajoutez une phase publish :
stages: - test - build - e2e-test - publish
On ajoute une tâches de plus, qui sera prise en compte seulement si on crée un nouveau tag sur notre branch main. La première tâche va re-tagger notre image Docker avec la version précisé dans le nom de notre tag.
publish-job:stage:publishtags: - generalrules:# Only if we create a tag - if:$CI_COMMIT_TAGimage:docker:20.10.16services: - name:docker:20.10.16-dindalias:dockervariables:IMAGE_TAG:$CI_REGISTRY_IMAGE/api-$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHALATEST_TAG:$CI_REGISTRY_IMAGE/api-prod:$CI_COMMIT_TAGscript: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY# Créer l'image docker - docker build --pull -t $IMAGE_TAG -f ./docker/Dockerfile.prod .# Retagger l'image avec le tag fourni - echo "Retagging docker image..." - docker tag $IMAGE_TAG $LATEST_TAG# Elever notre image au tag "latest" - echo "Push image with latest tag..." - docker push $LATEST_TAG# Envoyer l'image à notre Container Repository - echo "Done."
Ajoutez cette tâche à votre pipeline, et ensuite créez un tag sur votre code dans Code → Tags. Normalement le tag représente la version, par exemple 1.0.1 :
Retournez dans Build → Pipelines, vous verrez le dernier pipeline aura recommencé avec une nouvelle tâche pour publier l'image avec le nom de notre tag. Une fois fini, naviguez dans Deploy → Container Registry. Vous verrez une nouveau dépôt api-prod qui contiendra toutes nos images de production.
Configurer l'accès k8s au Gitlab
Auparavant, nous avons déployé notre api manuellement via Docker Hub, en utilisant un dépôt public.
Cependant, Kubernetes doit maintenant accéder à notre registre de conteneurs privé GitLab pour récupérer les images nouvellement construites.
Nous devons configurer un accès pour k8s.
Commencez par créer un jeton d'accès que k8s pourra utiliser pour extraire des images de notre registre de conteneurs. Naviguez dans Settings → Access Tokens →, et créez un jeton d'accès uniquement pour k8s :
Pas de date d'expiration
Role : Developer
Scope : read_registry
Copiez le jeton d'accès.
Tester l'accès au registre de containers
Pour être certain d'avoir le bon jeton d'accès on peut essayer de récupérer une image déjà dans notre GitLab.
Naviguez dans Deploy → Container Registry, selectionnez un dépôt, et vous verrez les différents tags déjà publiés. Vous pouvez copier le chemin préci pour une image en cliquant sur le petit presse papier à coté d'un tag.
# Se connecter au registredocker login -u [VOTRE NOM D'UTILISATEUR GITLAB] -p [ACCESS TOKEN GÉNÉRÉ DANS L'ÉTAPE PRÉCÉDENTE] registry.mt.glassworks.tech
# Essayer de tirer une imagedockerrun [COLLEZ LELIENDEVOTRETAGICI]# Par exemple:# docker run registry.mt.glassworks.tech/kevin/devopsapi/api-prod:2.0.2
Si tout fonctionne sans erreur, vous avez les bons coordonnées pour récupérer des images de GitLab !
Retournez à votre Dev Container. Nous allons modifier notre configuration k8s pour pouvoir s'identifier auprès de notre GitLab :
# Naviguer dans le dossier k8scd/home/dev/k8s# Préparer l'accès au clusterexport KUBECONFIG=./kubeconfig.yaml
Créons un secret sur le cluster qui permet de nous identifier sur GitLab :
# Remplacez [VOTRE NOM D'UTILISATEUR GITLAB] par votre nom d'utilisateur sur Gitlab# Remplacez [ACCESS TOKEN GÉNÉRÉ DANS L'ÉTAPE PRÉCÉDENTE] par l'accès token crée ci-dessus kubectl create secret docker-registry registry-secret --docker-server=registry.mt.glassworks.tech --docker-username=[VOTRE NOM D'UTILISATEUR GITLAB] --docker-password=[ACCESS TOKEN GÉNÉRÉ DANS L'ÉTAPE PRÉCÉDENTE]
Supprimer un secret
Si jamais vous vous êtes trompé de secret, il faut le supprimer et créer de nouveau :
kubectldeletesecretregistry-secret
Ensuite, nous allons modifier notre fichier deployment.k8s.yaml (la modification sur le 2 dernières lignes) :
La tâche correspondante va connecter à notre cluster, et mettre à jour l'image utilisé par notre déploiement :
deploy-job:stage:deploytags: - generalrules:# Only if we create a tag - if:$CI_COMMIT_TAGwhen:manualimage:name:bitnami/kubectl:1.27.5entrypoint: ['']variables:LATEST_TAG:$CI_REGISTRY_IMAGE/api-prod:$CI_COMMIT_TAGKUBECONFIG:$KUBECONFIG_PRODscript: - kubectl set image deployment/devopsapi devopsapi=$LATEST_TAG
Prenez note de la phrase when: manual - j'ai choisi de faire la tâche manuellement, pour être sur de ne pas se tromper !!
Pour connecter à notre cluster, il faudrait s'identifier avec notre fichier kubeconfig. Il faudrait le fournir à GitLab en tant que secret avec le nom KUBECONFIG_PROD.
Naviguez dans **Settings → CI/CD → Variables **:
On ajoute notre variable KUBECONFIG_PROD :
Le type de variable est File, on le nomme KUBECONFIG_PROD, et on colle les contenus de kubeconfig.yaml que je vous ai passé.
Et, c'est tout bon, tout devrait être configuré.
Pour valider si tout fonctionne :
Attendez que les tests sont terminés sans problème
Créez un nouveau tag, et attendez que le build soit fait
Manuellement déclencher le déploiement en retournant dans la pipeline, entrant dans la tâche "deploy", et en lançant le job.
Comment savoir si notre image a été déployée ? Retournez à votre DevContainer, et tapez dans le terminal :
kubectldescribedeployments
Parmis les détails, vous devez voir l'image qui portera désormais la version que vous avez précisé dans le tag sur Gitlab !
Vérifiez que votre API fonctionne toujours :
# Remplacez l'adress IP et par l'adresse fourni du cluster# Remplacez MON_CHEMIN_UNIQUE par le chemin indique dans le ingresscurlhttp://195.154.72.167/MON_CHEMIN_UNIQUE/info
Testons notre pipeline CI/CD
Nous allons modifier notre endpoint /info pour retourner une information supplémentaire. Dans src/server_manager.ts
Utilisez votre compte git pour faire un commit et push, créez un tag, et déployer l'image sur le cluster.
Une fois que le pipeline se termine, testons notre API:
# Remplacez l'adress IP et par l'adresse fourni du cluster# Remplacez MON_CHEMIN_UNIQUE par le chemin indique dans le ingresscurlhttp://195.154.72.167/MON_CHEMIN_UNIQUE/info{"title":"DevOps Code Samples API","host":"288b4706e279","platform":"linux","type":"Linux","message":"CI/CD Rocks"}
CI/CD Rocks !
Debogger
N'oubliez pas d'utiliser les outils kubectl pour déboguer votre deploiement.
Par exemple, vous ne voyez pas votre modification dans le résultat ?
Vérifiez que la bonne image est précisé avec kubectl get deployments
Vérifiez que les pods tournent correctement avec kubectl get pods. Si vous voyez un état d'erreur, type Image Pull Error ou Image Pull Backoff il se trouve que k8s n'arrive pas à récupérer l'image de Gitlab. Vérifier bien d'avoir fourni les bons codes d'accès dans le chapitre précédente !
Le .gitlab-ci.yml
Voici le fichier complet :
# "stages" décrit les différentes étapes de notre déroulé (pipeline)stages: - test - build - e2e-test - publish - deployvariables:FF_NETWORK_PER_BUILD:"true"unit-testing-job:stage:test# chaque tâche doit préciser le "stage" dans lequel il se trouveimage:node:18# l'image docker à utiliser pour exécuter notre codetags:# le tag du "runner" à utiliser (nous avons précisé "general") - generalonly:# précise les conditions d'exécution de cette tâche - master - main - merge_requests - productionartifacts:# quels sont les fichiers sortant de cette tâchereports:coverage_report:coverage_format:coberturapath:coverage/unit/cobertura-coverage.xmlcoverage:'/Statements\s*: \d+\.\d+/'script:# les instructions à exécuter dans le conteneur Docker - echo "Compiling the code..." - npm install - echo "Running unit tests..." - npm run unit - echo "Complete."integration-testing-job:stage:test# Cette tâche va tourner dans l'étape test, en parallèle de l'autre testimage:node:18tags: - generalonly: - master - main - merge_requests - productionservices:# On peut préciser les services externes, comme dans un docker-compose.yml - name:mariadb:10alias:test-dbmscommand: ["--character-set-server=utf8mb4","--collation-server=utf8mb4_unicode_ci" ]variables:# Les variables pour initialiser MariaDB (normalement les contenus du .env)MYSQL_ALLOW_EMPTY_PASSWORD:"false"MYSQL_ROOT_PASSWORD:"rootpassword"MYSQL_DATABASE:"school_test"variables:# Les variables d'environnement pour nos testsDB_HOST:"test-dbms"# Le nom d'hôte est le alias du service ci-dessusDB_DATABASE:"school_test"# Le même nom que dans MYSQL_DATABASEDB_ROOT_USER:"root"DB_ROOT_PASSWORD:"rootpassword"# La même valeur que dans MYSQL_ROOT_PASSWORDDB_USER:"api-test"# La même valeur qu'on utilise dans dbms/ddl/init-test.sqlDB_PASSWORD:"testpassword"# La même valeur qu'on utilise dans dbms/ddl/init-test.sqlartifacts:reports:coverage_report:coverage_format:coberturapath:coverage/integration/cobertura-coverage.xmlcoverage:'/Statements\s*: \d+\.\d+/'script: - echo "Compiling the code..." - npm install - echo "Running integrations test..." - npm run integration-no-env # Notez qu'on tourne la version *-no-env, puisqu'on fournit le variables ci-dessus
- echo "Complete."build-job:stage:build# seulement dans le "build" stagetags: - generalonly: - master - main - merge_requests - productionimage:docker:20.10.16# L'image pour construire est docker (qui aura la commande docker dedans)services:# On va appeler au Docker Daemon, ce service nous en donne accès - name:docker:20.10.16-dindalias:dockervariables:# On va créer une image docker utilisant des variables fournis par GitlabIMAGE_TAG:$CI_REGISTRY_IMAGE/api-$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHAscript:# Se connecter à Gitlab - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - echo "Building docker image..."# Créer l'image docker - docker build --pull -t $IMAGE_TAG -f ./docker/Dockerfile.prod .# Envoyer l'image docker dans notre Container Registry sur Gitlab - docker push $IMAGE_TAG - echo "Done."e2e-test-job:stage:e2e-testtags: - generalonly: - master - main - merge_requests - productionservices:# Notre service MariaDB, configuré comme avant - name:mariadb:10alias:test-dbmscommand: ["--character-set-server=utf8mb4","--collation-server=utf8mb4_unicode_ci" ]variables:MYSQL_ALLOW_EMPTY_PASSWORD:"false"MYSQL_ROOT_PASSWORD:"rootpassword"MYSQL_DATABASE:"school_test"# On récupère l'image de notre API qu'on avait compilé dans l'étape "build", et on le lance comme service - name:$CI_REGISTRY_IMAGE/api-$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHAalias:school-api# on l'appelle school-api# Ici, le variables de connection pour que notre API puisse parler avec MariaDB # Attention à donner les bons accès à l'utilisateur apivariables:DB_HOST:"test-dbms"DB_DATABASE:"school_test"DB_USER:"api-test"DB_PASSWORD:"testpassword"# Le port sur lequel l'api va écouterPORT:5150# Ici, les variables pour nos tests # Notez qu'on a pas besoin du nom d'utilisateur de l'api, seulement le ROOT pour pouvoir vider et recréer la base de test
variables:FF_NETWORK_PER_BUILD:"true"DB_HOST:"test-dbms"DB_DATABASE:"school_test"DB_ROOT_USER:"root"DB_ROOT_PASSWORD:"rootpassword"API_HOST:"http://school-api:5150"# Attention le nom d'hôte correspond à l'alias de notre servicescript: - npm install - npm run swagger - npm run e2e-no-envpublish-job:stage:publishtags: - generalrules:# Only if we create a tag - if:$CI_COMMIT_TAGimage:docker:20.10.16services: - name:docker:20.10.16-dindalias:dockervariables:IMAGE_TAG:$CI_REGISTRY_IMAGE/api-$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHALATEST_TAG:$CI_REGISTRY_IMAGE/api-prod:$CI_COMMIT_TAGscript: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY# Créer l'image docker - docker build --pull -t $IMAGE_TAG -f ./docker/Dockerfile.prod .# Retagger l'image avec le tag fourni - echo "Retagging docker image..." - docker tag $IMAGE_TAG $LATEST_TAG# Elever notre image au tag "latest" - echo "Push image with latest tag..." - docker push $LATEST_TAG# Envoyer l'image à notre Container Repository - echo "Done."deploy-job:stage:deploytags: - generalrules:# Only if we create a tag - if:$CI_COMMIT_TAGwhen:manualimage:name:bitnami/kubectl:1.27.5entrypoint: ['']variables:LATEST_TAG:$CI_REGISTRY_IMAGE/api-prod:$CI_COMMIT_TAGKUBECONFIG:$KUBECONFIG_PRODscript: - kubectl set image deployment/devopsapi devopsapi=$LATEST_TAG