Mise en place d'un pipeline CI/CD self-hosted
Tags: cicd, devops, selfhosted
Published: 2023-03-27 14:32:30

Pour rappel, un pipeline CI/CD (ou pipeline d'*intégration et de déploiement continus*) est une série d'actions à effectuer en vue de distribuer de manière cohérente et fiable une nouvelle version d'un logiciel.

Dans cet article, nous allons voir comment mettre en place un pipeline CI/CD minimaliste (lancer automatiquement les builds et déploiements lors des événements *push* sur un dépôt Git) et auto-hébergé que vous pourrez par la suite adapter selon vos besoins.

## Prérequis

Il nous faut:

- Un serveur de build que nous nommerons *buildsrv*:
  - Sous linux
  - Accessible via SSH
  - Disposant des outils *make* et *git*
- Une forge git hébergeant les sources de notre *application*. Nous utiliserons ici [GitHub](https://github.com) mais toute autre forge capable d'appeler des webhooks lors de la réception d'évènements *push* sur ses dépôts fera aussi très bien l'affaire.
- Une instance de [Ctfreak](https://ctfreak.com) avec un compte administrateur (la *Free Edition* suffira). Nous utiliserons ici https://demo.ctfreak.com

## Les sources

Les sources de notre *application* peuvent se résumer à ce *Makefile*:

```makefile
build:
	@echo "Build my awesome app"
deploy: build
	@echo "Deploy my awesome app"
```

Vous pouvez les retrouver dans [ce dépôt git](https://github.com/jypsoftware/myawesomeapp), avec la branche *master* correspondant à la dernière version déployée et la branche *dev* à la version en cours de développement.

## Objectifs

Passons en revue les objectifs à atteindre par la mise en place de notre pipeline CI/CD:

- Pour la partie **I**ntégration **C**ontinue:
  - Compiler les sources (via la commande `make`) à chaque fois qu'un développeur push sur la branche *dev*.
  - Recevoir une notification en cas d'échec.
- Pour la partie **D**éploiement **C**ontinue:
  - Compiler les sources et déployer (via la commande `make deploy`) à chaque fois qu'un développeur push sur la branche *master*.
  - Recevoir une notification en cas de succès ou d'échec.

Pour ce faire [Ctfreak](https://ctfreak.com) va jouer le rôle de passerelle entre la forge git et le serveur de build.

C'est à dire qu'à chaque push reçu par la forge:

- Celle-ci va appeler un webhook défini dans Ctfreak.
- Ctfreak va alors se connecter via SSH au serveur de build pour exécuter la commande `make` adéquate, puis envoyer une notification.

## Configuration du serveur de build

Connectez-vous à votre serveur de build et lancez les commandes suivantes:

```bash
ctfreakdemo@buildsrv:~$ git clone https://github.com/jypsoftware/myawesomeapp.git myawesomeapp.master
> Cloning into 'myawesomeapp.master'...
> remote: Enumerating objects: 15, done.
> remote: Counting objects: 100% (15/15), done.
> remote: Compressing objects: 100% (13/13), done.
> remote: Total 15 (delta 2), reused 6 (delta 1), pack-reused 0
> Receiving objects: 100% (15/15), 14.38 KiB | 3.59 MiB/s, done.
> Resolving deltas: 100% (2/2), done.

ctfreakdemo@buildsrv:~$ git clone https://github.com/jypsoftware/myawesomeapp.git myawesomeapp.dev
> Cloning into 'myawesomeapp.dev'...
> remote: Enumerating objects: 15, done.
> remote: Counting objects: 100% (15/15), done.
> remote: Compressing objects: 100% (13/13), done.
> remote: Total 15 (delta 2), reused 6 (delta 1), pack-reused 0
> Receiving objects: 100% (15/15), 14.38 KiB | 7.19 MiB/s, done.
> Resolving deltas: 100% (2/2), done.

ctfreakdemo@buildsrv:~$ cd myawesomeapp.dev

ctfreakdemo@buildsrv:~/myawesomeapp.dev$ git checkout dev
> Branch 'dev' set up to track remote branch 'dev' from 'origin'.
> Switched to a new branch 'dev'
```

Comme vous pouvez le constater, nous avons cloné le repo git 2 fois (1 par branche), ce qui nous permettra d'éviter d'éventuels conflits si les builds des branches *master* et *dev* devaient s'exécuter de manière concurrente.

## Configuration de Ctfreak

Connectez vous à votre instance Ctfreak avec un compte administrateur.

### Ajout du serveur de build

Commencez par ajouter la clef SSH permettant de se connecter au serveur de build via *SSH Credential* → *New SSH Credential*:

![Ajout de la clef SSH](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/ssh.webp)

Puis ajoutez le serveur en lui même via *Nodes* → *Internal Nodes* → *New node*:

![Ajout du serveur](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/node.webp)

Vérifiez que la clef *SSH Key for buildsrv* est bien sélectionnée comme *Credential* (Ctfreak s’en servira pour se connecter au serveur), puis validez pour créer le node *buildsrv*.

### Création du projet CI/CD

Nous allons créer un projet dédié pour rassembler toutes les tâches en rapport avec notre pipeline CI/CD.

Allez dans *Projects* → *New Project*

![Création du projet](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/project.webp)

Validez pour créer le projet.

### Ajout d'un notifieur

Allez dans *Projects* → *CI/CD* → *Notifiers* → *New notifier*

![Sélection du type de notifieur](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/notifier1.webp)

Choisissez *Discord* comme type de notifieur (par exemple).

![Edition du notifieur](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/notifier2.webp)

Précisez l'URL du webhook discord à appeler et validez.

![Liste des notifieurs](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/notifier3.webp)

Une notification de test peut être envoyée en cliquant sur le bouton d'envoi (celui en forme d'avion en papier).

### Création de la tâche de build

Allez dans *Projects* → *CI/CD* → *New task*

![Sélection du type de tâche](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/task-type.webp)

Choisissez *Bash Script* comme type de tâche.

![Tâche d'intégration continue](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/task-dev.webp)

Comme vous l'aurez deviné, cette tâche va permettre de compiler la branche *dev* sur le serveur de build et envoyer une notification en cas d'échec.

La *stratégie d'exécution multiple* (*multiple execution policy*): *chaînage intelligent* (*smart chaining*) choisie ici va permettre d'éviter de lancer des builds superflus.

Prenons un exemple: vous avez un build qui dure une demi-heure et 3 pushs sont envoyés sur le dépôt git à 5 minutes d'intervalle.

Au premier push, le build est lancé immédiatement.

Au 2ème push, un 2ème build est mis en attente vu que le premier n'est pas encore terminé.

Au 3ème push, on ne prévoit pas de faire de 3ème build car le 2ème est toujours en attente.

### Création de la tâche de déploiement

Pour gagner du temps, créez cette tâche à partir de celle de build via *Projects* → *CI/CD* → *Buid myawesomeapp / dev* → *Duplicate*.

![Tâche de deploiement continue](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/task-master.webp)

En y apportant les modifications suivantes:

- Nous souhaitons recevoir une notification en fin de traitement dans tous les cas.
- Le contenu du script est mis à jour pour déployer à partir de la branche *master*.

### Ajout des webhooks

Maintenant que nos tâches de build et de déploiement sont créées, nous allons ajouter pour chacune d'elle un webhook entrant qui pourra être appelé par notre forge git.

Commençons avec la tâche de déploiement en allant dans *Projects* → *CI/CD* → *Buid myawesomeapp / dev* → *Incoming webhooks* → *New webhook*.

![Sélection du type de webhook](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-type.webp)

Choisissez *GitHub* comme type de webhook.

Lorsque GitHub appelera notre webhook suite à un push, il le fera quelque soit la branche concernée or notre tâche ne doit être exécuté que lorsque ce push concerne la branche *dev*.

Pour résoudre ce problème, nous ajoutons une *condition d'exécution* qui s'appliquera au payload JSON envoyé par GitHub. La tâche ne sera exécutée que si la condition est remplie (c-a-d le payload fait bien référence à la branche *dev*).

![Création du webhook dev 1](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-dev1.webp)

Définissez au besoin un *secret* qui permettra à Ctfreak de valider que les appels au webhook proviennent bien de GitHub et validez.

Une fois le webhook créé, nous récupérons son URL pour le paramétrage de GitHub.

![Création du webhook dev 2](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-dev2.webp)

Au niveau de la configuration de votre dépôt GitHub, ajoutez le webhook que nous venons de créer en précisant bien:

- Son URL et son *secret*
- Que c'est le format JSON qui est attendu
- Que le webhook ne doit être appelé QUE pour les évènements push

![Paramétrage du webhook dev dans github](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-dev-github.webp)

Procédez de la même façon (création du webhook, référencement dans GitHub) pour la tâche de déploiement (pensez à bien faire référence à la branche *master* dans la *condition d'exécution*):

![Création du webhook master](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-master.webp)

### Test du pipeline

Maintenant que tout est en place, voyons ce que provoque un git push sur la branche *dev*:

![](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-dev.webp)

Suite à l'appel du webhook *GitPushDev* la tâche de build a bien été exécutée.

![](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-dev-execution.webp)

En cliquant sur l'icône en forme d'œil, nous avons les logs de l'exécution.

![](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-dev-log.webp)

Même test avec un git push sur la branche *master*:

![](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-master.webp)

Ici c'est le webhook *GitPushMaster* qui a été appelé comme prévu.

![](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-master-execution.webp)

![](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-master-log.webp)

Et le petit plus ici, c'est la réception d'une notification Discord pour confirmer que le déploiement s'est bien passé.

![](https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-master-notifier.webp)

### Conclusion

Vous voilà avec un pipeline CI/CD opérationnel et suffisamment souple pour prendre en charge les workflows de build les plus complexes.

En optant pour cette solution self-hosted et en plaçant le cœur du processus de build dans un script shell plutôt que dans un logiciel d'intégration continue comme Jenkins, GitHub Actions, ..., vous bénéficierez de plusieurs avantages:

- Vous ne serez pas contraint par les limites de ce dernier (version de git pas la plus récente, non prise en charge de certaines spécificités de votre build, limite du nombre de build journalier souscrit atteinte, ...).
- Si votre serveur de build est indisponible, vous pourrez encore effectuer un déploiement en urgence à partir d'un poste de développeur (croyez-moi, ça peut servir).