Déploiement continue

Table of Contents

1 Introduction

Vérifiez que vos pipelines sont toujours fonctionnels.

Pour cela trois pré-requis à vérifier :

  1. le "personnal access token" gitlab,
  2. le service gitlab-runner,
  3. le service podman.

Pour avoir accès à votre token et à la bonne configuration podman vous pouvez utiliser le script setenv-podman.sh, commande à taper dans chaque nouveau terminal ou à ajouter dans votre .bashrc :

source ~/setenv-podman.sh
# rappel : un personal access token valide doit se trouver dans le fichier ~/.gitlabtoken

Ensuite pour lancer le service gitlab-runner soit le service est déjà configuré et fonctionnel

systemctl --user status gitlab-runner.service
# répond : active (running)

soit vous le lancez manuellement dans un nouveau terminal

~/gitlab-runner run

Enfin pour valider que les pipelines fonctionnent, lancer un pipeline. Par exemple manuellement via la page Build -> Pipelines -> Run pipeline, en choisissant la branche 'auto' du projet Heat sur laquelle vous avez déjà travaillé.

2 Distribuer la dernière version stable

Le processus de développement d'un logiciel implique de faire des releases régulièrement. Par release on entend une version bien identifiée, testée, avec une interface fixée, une documentation, un changelog (document texte rappelant les modifications entre deux versions) et une ou plusieurs archives disponibles pour l'installation.

Le processus de mise à disposition de ces éléments peut être automatisé afin de gagner du temps humain à chaque création d'une nouvelle release. Ainsi cela permet d'en faire plus facilement et donc plus souvent.

2.1 Créer une release

A partir de votre branche 'auto' on vous demande de créer une nouvelle release source et binaire de Heat et de la publier sur Gitlab.

2.1.1 Manuellement via l'interface web Gitlab

Pour cela commencer par générer une archive source, cmake utilise cpack pour faciliter la création de package source et binaire. Utilisez par exemple ces commandes pour générer les archives source et binaire (compatible debian) :

rm -r build
cmake -S . -B build -DHEAT_DOC=ON
cmake --build build --target doc
cp -r build/doc/html doc/
cmake --build build --target package_source
cmake --build build --target package

Pour information, la version du paquet, ici 1.0.0, est définie par la variable cmake HEAT_VERSION dans le fichier CMakeLists.txt. Remarquez que l'on génère la documentation afin de l'incorporer dans l'archive source.

Sauvegardez les 6 lignes précédentes de création de paquets (les commandes avec rm, cmake, cp) dans un script ./tools/release.sh avec comme entête (1re ligne) #!/bin/sh. Testez son fonctionnement :

chmod +x ./tools/release.sh
./tools/release.sh
ll build/heat-1.0.0*

Maintenant sauvegardez votre état actuel via un tag git, ex. v1.0.0 :

git tag v1.0.0
git push origin --tags

Visitez ensuite la page Code -> Tags de votre projet et vérifiez que vous trouvez bien votre tag v1.0.0. Le tag git permet d'identifier cette version au sens git et donc de retrouver cette version précise du code source avec git checkout v1.0.0.

Sur la page des tags vous pouvez créer manuellement une release au sens Gitlab à partir d'un tag existant. Cliquez sur 'Create release' à droite, puis ajoutez quelques commentaires dans la section `Release notes` :

- Add gitlab-ci pipeline
- Use a Dockerfile to define the testing environment

Vous pouvez ajouter des liens type url et des fichiers attachés. Utilisez le bouton "Attach a file or image" (trombone dans la partie Release notes) afin d'y ajouter les fichiers .tar.gz et .deb que vous venez de générer avec cpack. Sauvegarder. Vous avez une nouvelle release sur la page Deploy -> Releases avec le code source téléchargeable ainsi que vos deux packages associés.

2.1.2 Via l'API Gitlab

La création de release Gitlab peut être opérée via une commande curl afin d'interagir avec l'API REST de Gitlab, l'opération peut ainsi être scriptable et, nous le verrons par la suite, automatisable.

Lors du TP précédent vous aviez créé un "Personnal Access Token" sauvegardé dans le fichier ~/.gitlabtoken. Ce token vous permet d'exécuter des commmandes pour interagir avec Gitlab et de réaliser des opérations sur vos projets. Voir la documentation sur son utilisation avec curl : https://docs.gitlab.com/ee/api/rest/#personalprojectgroup-access-tokens.

Exemple pour interroger Gitlab sur la liste des membres d'un projet spécifique. Le projet est identifiable via son numéro identifiant unique que l'on trouve sur la page principale (cf. "Project ID", trois petits points verticaux en haut à droite, à côté de Star et Fork) :

export TOKEN=`cat ~/.gitlabtoken`
export PROJECT_ID="votre numéro de projet"
curl --header "PRIVATE-TOKEN: $TOKEN" "https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/users"

# sortie json structurée
curl --header "PRIVATE-TOKEN: $TOKEN" "https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/users" | jq

Vous pouvez accéder à l'API sur les releases (lister, créer, supprimer) :

# afficher les releases existantes
curl --header "PRIVATE-TOKEN: $TOKEN" "https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/releases" | jq

# afficher leur noms
curl --header "PRIVATE-TOKEN: $TOKEN" "https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/releases" | jq '.[0].name'

# supprimer la release v1.0.0
export TAG_NAME=v1.0.0
curl --header "PRIVATE-TOKEN: $TOKEN" --request DELETE "https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/releases/$TAG_NAME" | jq

Activer le package registry : Settings -> General -> Visibility, project features -> Package registry -> enable and save changes. Vous pouvez maintenant téléverser des fichiers de type packages binaires via l'API du Generic Package Repository :

RELEASE_NUM=`echo $TAG_NAME | sed -e "s#v##"`
# téléverser heat-1.0.0.tar.gz sur le package registry
curl --header "PRIVATE-TOKEN: $TOKEN" --upload-file ./build/heat-$RELEASE_NUM.tar.gz "https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/packages/generic/release/$RELEASE_NUM/heat-$RELEASE_NUM.tar.gz"

# téléverser heat-1.0.0-Linux.deb sur le package registry
curl --header "PRIVATE-TOKEN: $TOKEN" --upload-file ./build/heat-$RELEASE_NUM-Linux.deb "https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/packages/generic/release/$RELEASE_NUM/heat-$RELEASE_NUM-Linux.deb"

# pour retrouver l'id du package
PACKAGE_ID=`curl --header "PRIVATE-TOKEN: $TOKEN" "https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/packages" | jq '.[0].id'`
# pour supprimer le package à partir de id
curl --header "PRIVATE-TOKEN: $TOKEN" --request DELETE "https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/packages/$PACKAGE_ID"

Vous pouvez visiter la page Deploy -> Package Registry et y voir vos packages. Vous pouvez aussi les supprimer à la main.

Vous pouvez les télécharger ensuite simplement via wget :

wget "https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/packages/generic/release/$RELEASE_NUM/heat-$RELEASE_NUM.tar.gz"
wget "https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/packages/generic/release/$RELEASE_NUM/heat-$RELEASE_NUM-Linux.deb"
ll heat-$RELEASE_NUM*
rm heat-$RELEASE_NUM.tar.gz
rm heat-$RELEASE_NUM-Linux.deb

Admettons que le tag est bien déjà créé et que les fichiers sont téléversés sur le package registry, nous allons maintenant procéder à la création de la release finale :

CMD=`echo curl --header \"Content-Type: application/json\" \
               --header \"PRIVATE-TOKEN: $TOKEN\" \
               --data \'{ \"name\": \"$TAG_NAME\", \"tag_name\": \"$TAG_NAME\", \
                          \"assets\": { \"links\": [{ \"name\": \"heat-$RELEASE_NUM.tar.gz\", \"url\": \"https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/packages/generic/release/$RELEASE_NUM/heat-$RELEASE_NUM.tar.gz\" }, \
                                                    { \"name\": \"heat-$RELEASE_NUM-Linux.deb\", \"url\": \"https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/packages/generic/release/$RELEASE_NUM/heat-$RELEASE_NUM-Linux.deb\" }] } \
                        }\' \
               --request POST \"https://gitlab-ce.iut.u-bordeaux.fr/api/v4/projects/$PROJECT_ID/releases\"`
eval $CMD | jq

Vous pouvez visiter la page Deploy -> Releases.

Modifiez maintenant le script release.sh afin de créer la release v1.0.0 via l'API Gitlab en y ajoutant les commandes pour téléverser les fichiers de packages et pour créer la release dans la foulée. Ajoutez ce script ./tools/release.sh au git (git add, commit, push).

2.2 Distribution en continu

Le but ici est d'automatiser la création et la distribution d'une release par un job gitlab-ci.

Lors d'un pipeline, gitlab-ci fourni des variables d'environnements contenant des informations : url du projet git, sur quelle référence git le pipeline est-il exécuté, un token Gitlab temporaire, etc. On va donc modifier le script release.sh pour utiliser les variables des pipelines existantes. Remplacer :

  • PRIVATE-TOKEN: $TOKEN par JOB-TOKEN: $CI_JOB_TOKEN
  • TAG_NAME par CI_COMMIT_TAG
  • PROJECT_ID par CI_PROJECT_ID

Attention de bien conserver le champs tag_name en minuscule dans la commande curl avec le `–data`.

Maintenant modifier .gitlab-ci.yml pour ajouter un job release (ajouter aussi le stage release après test tout en haut) qui permet de créer la release automatiquement lorsque l'utilisateur pousse un nouveau tag (respectant une certaine sémantique vX.Y.Z) sur Gitlab :

release:
  stage: release
  rules:
    - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_TAG =~ /^v[0-9]\.[0-9]\.[0-9]$/
  dependencies: []
  artifacts:
    name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
    paths:
      - build/heat-*.tar.gz
      - build/heat-*.deb
  script:
    - ./tools/release.sh

Note : le mot clef dependencies: [] est utilisé pour indiquer qu'on ne souhaite pas télécharger les artefacts des jobs des étapes précédentes puisqu'ils ne sont pas nécessaires ici.

Modifier le fichier dockerfile-buildenv afin d'ajouter les paquets bash, curl, jq, dpkg et graphviz (dépendances nécessaires pour la création de l'archive).

Utiliser la règle suivante dans le job buildenv à la place de vos anciennes expérimentations (only: changes, when: manual, only: schedule) Commiter, pousser sur la branche auto. Cela devrait reconstruire l'image de test, tout en évitant de recontruire l'image pour d'autres scénario (nouvelle étiquette, pipeline manuel, schedule).

rules:
  - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_TAG == null
    changes:
      paths:
        - dockerfile-buildenv

Modifier le numéro de version dans le CMakeLists.txt afin d'être à 1.1.0 :

set(HEAT_VERSION_MAJOR "1")
set(HEAT_VERSION_MINOR "1")
set(HEAT_VERSION_PATCH "0")

Commiter et pousser.

Tester à présent la création d'une étiquette et la pousser sur Gitlab :

git tag v1.1.0
git push origin --tags

Si ça ne fonctionne pas, après avoir analysé le log du job en échec, vous pouvez supprimer l'étiquette (en local et sur le serveur), puis modifier vos sources, commiter, pousser pour re-tester la procédure.

git tag -d v1.1.0
git push --delete origin v1.1.0
git tag v1.1.0
git push origin --tags

Ne pas hésiter à lire ceci à propos du versioning des releases : https://semver.org/.

Pour garantir que le numéro de tag est consistant avec le numéro de version du projet, cf. HEAT_VERSION dans le CMakeLists.txt principal qui donne la version aux fichiers générés .tar.gz/.deb, on peut ajouter ces lignes dans release.sh avant de téléverser les archives .tar.gz/.deb avec curl :

PACKAGE=`ls build/heat-*.tar.gz`
PACKAGE_NUM=`echo $PACKAGE | grep -o "[0-9].[0-9].[0-9]"`
if [ $PACKAGE_NUM != $RELEASE_NUM ]; then
  echo "PACKAGE=$PACKAGE"
  echo "PACKAGE_NUM=$PACKAGE_NUM"
  echo "RELEASE_NUM=$RELEASE_NUM"
  echo "(CI_COMMIT_TAG=$CI_COMMIT_TAG)"
  echo "Package number does not match RELEASE_NUM (computed from CI_COMMIT_TAG, without v), exit"
  exit 1
fi

3 Déploiement en continue

Jusqu'à présent nous avons pu mettre en oeuvre les tests unitaires automatiques (intégration continue) ainsi que la distribution d'une version stable via le processus de release (distribution continue). Pour certaines applications, par exemple des services web, il est en plus nécessaire d'installer la dernière version mise à jour dans son environnement de production, on parle alors de déploiement continu.

3.1 Déploiement de son application via Docker

Le fichier Dockerfile sert de recette au déploiement de notre application.

Ajouter un fichier Dockerfile au git afin d'installer l'application dans l'image docker :

ARG IMAGE_FROM
FROM $IMAGE_FROM
RUN mkdir -p heat/
COPY . heat/
RUN cd heat/ && cmake -B build && cmake --build build
USER root
RUN cd heat/ && cmake --install build && cd /builds && rm -r heat/
USER gitlab

Pour tester localement :

rm build -rf
podman build -t heat -f Dockerfile --build-arg IMAGE_FROM=gitlab-ce.iut.u-bordeaux.fr:5050/$USER/heat/testing .

Remarquez qu'on se base ici sur notre image de test pour éviter la réinstallation de tous les paquets.

Modifier le .gitlab-ci.yml afin d'y ajouter le job de déploiement (ajouter aussi deploy comme dernière étape dans stage en début de fichier) :

deploy:
  stage: deploy
  rules:
    - if: $CI_PIPELINE_SOURCE == "push"
  dependencies: []
  image: quay.io/podman/stable
  variables:
    IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
  before_script:
    - podman login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  script:
    - podman build -t $IMAGE_TAG -f Dockerfile --build-arg IMAGE_FROM=$CI_REGISTRY_IMAGE/testing .
    - podman push $IMAGE_TAG

Note : on aurait pu utiliser la même rules que pour le job release (tag push), mais on souhaite ici pouvoir déployer l'environnement de production pour chaque évolution dans les branches. Eventuellement, on pourrait déployer seulement sur la branche master et pour des tags, en ajoutant :

- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_TAG

Visiter la page Deploy -> Container Registry et vérifier la présence de votre image de production.

Tester l'image en local :

export TOKEN=`cat ~/.gitlabtoken`
podman login gitlab-ce.iut.u-bordeaux.fr:5050 -u $USER -p $TOKEN
podman run gitlab-ce.iut.u-bordeaux.fr:5050/$USER/heat:auto /usr/local/bin/heat_seq 33 33 33 1 0

3.2 Déploiement de la documentation

Un autre exemple courant de déploiement en continu est la documentation. On cherche par exemple à maintenir une page de documentation à jour par rapport au code de la branche principale.

Sur le Gitlab de l'IUT la fonctionnalité de pages (site web de pages html/css statiques associé au projet git) n'est pas activée, il faut donc tester cette fonctionnalité sur un autre Gitlab.

Dans votre projet Heat local commencez par pousser la branche master sur le serveur Gitlab.com :

export LOGIN="votre login gitlab.com"
git remote add gitlabcom https://gitlab.com/$LOGIN/heat.git
git switch master
git push gitlabcom master

Ensuite positionnez vous sur votre branche auto, créer une nouvelle branche pages et ajoutez un script ./tools/pages.sh générant la documentation :

#!/bin/sh

set -x

mkdir public

cmake -B build -DHEAT_DOC=ON
cmake --build build
cp -r build/doc/html public/doxygen

./build/heat_seq 33 33 500 1 0
python3 heat_plot.py
ffmpeg -i heat.avi -c:v libtheora -q:v 7 -c:a libvorbis -q:a 4 heat.ogv

cp tools/index.html public/
cp heat.ogv public/

On pourra utiliser le fichier ./tools/index.html suivant :

<video style="display:block; margin: 0 auto;" width="900" height="900" controls>
  <source src="heat.ogv" type="video/mp4">
  Your browser does not support the video tag.
</video>

See the doxygen documentation <a href=./doxygen/>here</a>.

Modifiez le fichier .gitlab-ci.yml pour y ajouter le job de construction de la documentation :

pages:
  stage: deploy
  image: fpruvost/heat
  dependencies: []
  artifacts:
    paths:
      - public
  script:
    - ./tools/pages.sh

Par convention le job doit être nommé exactement pages et les fichiers ajoutés dans les artefacts seront automatiquement téléversés sur le site web associé au projet sur Gitlab.com, voir la page Deploy -> Pages.

Pour information l'image docker fpruvost/heat a été construite avec ce Dockerfile voir sur dockerhub :

FROM ubuntu:24.04

# Installing as root: docker images are usually set up as root.
# Since some autotools scripts might complain about this being unsafe, we set
# FORCE_UNSAFE_CONFIGURE=1 to avoid configure errors.
ENV FORCE_UNSAFE_CONFIGURE=1
ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update -y
RUN apt-get install -y build-essential clang cmake curl doxygen ffmpeg gcovr git python-is-python3 python3-numpy python3-matplotlib xsltproc
RUN apt-get autoremove -y

Commentez les autres jobs (avec un . devant le nom de chaque job) puis testez le job pages en poussant votre branche pages sur Gitlab.com :

git add ...
git commit -m ...
git push gitlabcom pages

Dans vos pipelines un nouveau job pages:deploy sera créé automatiquement et se charge de la mise à jour du site web avec les nouveaux fichiers.

Vous pouvez maintenant visiter la page de votre site dont l'url est indiquée sur Deploy -> Pages, quelque chose comme : https://$login.gitlab.io/heat/.

Author: Florent Pruvost (modifié par Pierre Ramet)

Created: 2024-10-21 Mon 14:58

Validate