Commencer simplement avec GitLabCI
Tags: ci, beginners, gitlabci, tutorial
Published: 2020-04-24 11:59:36


Tout le monde connait GitLab, et beaucoup de monde connait son outil d'intégration et de déploiement continus : GitLabCI 🦊.

Avec l'arrivée d'une application ne possédant aucun outil de CI/CD dans notre équipe, nous avons pu manipuler GitLabCI. C'est d'ailleurs le sujet d'une présentation sur laquelle nous avons travaillé avec un collègue (<a href="https://twitter.com/mr_martingale" target="_blank">Jean-Baptiste Martin</a> pour ne pas le citer 🙂).

C'est cette approche que je vais vous présenter dans cet article.


## Sommaire

[1 - Création d'un pipeline](#création-d-un-pipeline)
[2 - Notre premier job](#les-jobs)
[3 - Le registry](#le-registry)
[4 - Les environnements](#les-environnements)
[5 - GitLab Pages](#gitlab-pages)
[6 - Kubernetes](#kubernetes)
[The (happy) end ](#the-happy-end)


### 🛠 Création d'un pipeline <a name="création-d-un-pipeline"></a>


Pour créer un pipeline GitLabCI, c'est tout simple, il suffit de créer un fichier *.gitlab-ci.yml* à la racine de votre projet.

Les pipelines sont disponibles via le menu "CI/CD" > "Pipelines".

<img src="http://jeanphi-baconnais.gitlab.io/img/gitlabci-menu.png"  width="200" >

<br/>

#### Notre premier pipeline 

Un pipeline est un élément assez simple, ayant un status : in _progress_, _pending_, _passed_ et (je ne vous le souhaite pas) _failed_.

En consultant un pipeline, nous pouvons voir sa structuration, il est constitué de 1 ou plusieurs jobs, organisés dans différentes colonnes. Tout cela se configure dans le fameux fichier *.gitlab-ci.yml*.

Je vous entend déjà raler en voyant l'extension yml, mais rassurez vous, GitLab a mis un outil *CI Lint* qui vous permet de valider vos pipelines. Utile pour éviter de voir vos pipelines planter car le yaml est invalide ! Il est disponible dans la page des pipelines :

<img src="http://jeanphi-baconnais.gitlab.io/img/gitlabci-ci.png" >

<br/>

#### Structuration du fichier .gitlab-ci.yml

Le fichier *.gitlab-ci.yml* commence généralement par les définitions de nos "colonnes". Il s'agit de *stage*. Un pipeline peut contenir autant de _stage_ qu'il nous semble utile. Les différents traitements se feront de manière séquentielle.
Dans cet exemple il y a 5 étapes dans la construction  de notre projet :
```
stages:
  - build_and_push
  - deploy_tis
  - deploy_re7
  - notif_prepa_liv_prod
  - deploy_prod
```

- La première étape permet de builder une image docker de notre projet et le pusher sur un repository. 
- La seconde deploie notre composant sur un environnement de fabrication. 
- La troisième sur un environnement de recette.
- la quatrième envoie une notification dans slack qu'une livraison en production est en prévision. 
- La dernière déploie notre application en production.

Voila graphiquement à quoi cela ressemble :


![Pipeline](http://jeanphi-baconnais.gitlab.io/img/gitlabci-pipeline.png)


L'enchainement des différents _stages_ se fait automatiquement si tous les jobs se font sans erreur. Dans l'exemple, il a été (heureusement) mis en place un système d'exécution manuelle. Notre application n'ayant très peu de tests automatisé, il est préférable de faire quelques tests avant d'envoyer une nouvelle version en production 🤭. Il est donc possible de spécifier qu'un _stage_ est à exécuter uniquement après une action manuelle : *when: manual*
Ce qui visuellement dans notre pipeline est repérable grâce à la flèche noire en haut du stage :

<img src="http://jeanphi-baconnais.gitlab.io/img/gitlabci-manual.png"  width="200" >

Comment s'exécute le pipeline ? 

Sur chaque étape, il est possible de mentionner la branche qui va enclencher notre pipeline.

```
 only:
    - dev
    - master
```
Dans cet exemple, dès qu'un _push_ est effectué sur la branche dev et master, le pipeline définit va s'exécuter.

Afin d'optimiser le temps de traitement de notre pipeline, il est possible (voire conseillé) de paralléliser les traitements. Pour cela plusieurs jobs peuvent être associés à un _stage_. Dans cet exemple, tous les jobs de build de composants sont réalisés en parallèle.

<img src="http://jeanphi-baconnais.gitlab.io/img/gitlabci-multjobs.png"  width="500" >

Au niveau du fichier *.gitlab-ci.yml* cela se traduit de cette façon :

- 1 stage

```
stages:
       - build
```

- chaque job reçoit la configuration suivante :

```
📦 front :
       stage: build

📦 api_go :
       stage: build

📦 node :
       stage: build
```

NB : Un pipeline peut également être programmé via la partie "Schedule". Accessible via le menu, un pipeline peut être programmé. 


<br/>

### 🛠 Les jobs  <a name="les-jobs"></a>

#### Notre premier job


Un job est ce qui va permettre de réaliser une ou plusieurs tâches. Le job le plus simple doit être composé d'une action dans une partie _script_ :

```
job:
  script : 
     - echo "Voici mon premier job"
```

Il existe d'autres mots clés qui vont nous permettre de faire des actions plus complexes qu'un simple echo 😁

<br/>

#### L'attribut stage

Tout d'abord un _stage_ peut lui être attribué, permettant ainsi de classifier nos jobs comme montré dans le paragraphe ci-dessus. Si on définit 2 stages tels que :

```
stages:
  - first_stage
  - second_stage
```

Nous pourrions avoir plusieurs jobs définis tels que :

```
job1:
  stage: first_stage
  script : 
     - echo "Voici mon premier job"
job2:
  stage: second_stage
  script : 
     - echo "Voici mon second job"
```

et nous pourrions avoir plusieurs jobs associés au même stage :


```
job1:
  stage: first_stage
  script : 
     - echo "Voici mon premier job"
job2:
  stage: second_stage
  script : 
     - echo "Voici mon second job"
job3:
  stage: first_stage
  script : 
     - echo "Voici mon troisième job exécuté lors du premier stage"
```

<br/>

#### L'attribut image

Il y a ensuite le mot clé _image_. Il permet d'effectuer une action à l'aide d'image docker. Il peut être positionné pour l'ensemble des jobs dans la première ligne du fichier _.gitlab-ci.yml_, ou pour chaque job.

Commun à tous les jobs :

```
image: alpine

job1:
  stage: first_stage
  script : 
     - echo "Voici mon premier job"
job2:
  stage: second_stage
  script : 
     - echo "Voici mon second job"

```

ou pour chaque job :


```
job1:
  image: alpine
  stage: first_stage
  script : 
     - echo "Voici mon premier job"
job2:
  image: alpine
  stage: second_stage
  script : 
     - echo "Voici mon second job"

```

Si on a notre pipeline qui contient la plupart de job qui utilisent une image, il vaut mieux spécifier l'image comme globale à notre pipeline, puis à surcharger les jobs avec des images spécifiques :


```
image: alpine

job1: # utilisera l'image de base alpine
  script : 
     - echo "Voici mon premier job"
job2:
  image: seconde_image_docker # ce job spécifie l'image qu'il a besoin pour effectuer une action spécifique.
  stage: second_stage
  script : 
     - echo "Voici mon second job"

```

En choisissant l'image, on peut imaginer par exemple lancer des commandes maven pour pouvoir exécuter les tests et poursuivre le pipeline si tous les tests sont passés.

```
test:
  stage: test
  script:
    - mvn clean verify
```


<br/>

#### L'attribut only 

L'attribut _only_ est simple à comprendre, il permet d'éxécuter votre job seulement sur une branche ou une typologie de branche. Par exemple un déclenchement de job sur la branche master s'écrit comme ceci :

```
  only:
    - master
```

On peut également définir une liste de branches :


```
  only:
    - dev
    - master
```

ou un pattern de branches :

```
  only:
    - features*
```

<br/>

#### L'attribut when

L'attribut _when_ permet de conditionné l'exécution d'un job. Cinq valeurs sont disponibles et permettent de conditionner l'exécution d'un job. Elles sont assez parlantes : on_success, on_failure, always, manual et la dernière arrivée manual.

Pour plus de détail sur les différents états, se référer à la doc GitLab https://docs.GitLab.com/ee/ci/yaml/#when

La première utilisation que j'ai utilisée, c'est celle spécifiée précédemment, c'est le manual. En effet, avec cette description, ce job ne s'exécutera qu'après intervention manuelle d'un utilisateur :

```
job1: 
  image: alpine
  stage: second_stage
  script : 
     - echo "Voici mon second job"
  when: manual
```
<br/>

#### L'attribut artifact

Un artifact est un moyen de récupérer des fichiers générés par un job. Cela peut servir à récupérer un rapport de test par exemple. Afin de ne pas trop surcharger les infrastructures et ne garder indéfiniement des fichiers, la notion d'expiration est possible.

```
expire_in: 1 week
```

Par exemple, pour ce site réalisé avec l'outil **gohugo**, j'exécute dans un job la commande **hugo** qui permet de générer le site statique. Tous les fichiers vont dans le répertoire **public** : 

```
  artifacts:
    paths:
    - public

```

Sous l'interface du pipeline il est possible de récupérer les sources : 

![Artefact](http://jeanphi-baconnais.gitlab.io/img/gitlabci-artefact.png)

<br/>

#### L'attribut variables

Dans un pipeline il est possible de créer des variables propres à un job :

```
  variables:
    DATA: "{'text':'une donnée json que vous voulez utilisez plus tard'}"
```

Cette variable pourra être simplement utilisée dans la partie script :

```
script:  
    - curl -X POST -H "${CONTENT}"  --data "${DATA}" "${WEBHOOK_LIVRAISON}"
```

Pour intégrer et réutiliser une variable dans plusieurs jobs, c'est exactement la même chose mais à l'extérieur d'un job 😉.


Il existe encore un niveau au dessus en terme de variable, notamment utilisé pour stocker les mots de passe qui ne doivent pas se trouver dans votre code source ! Dans le menu "Settings" > "CI / CD" plusieurs options sont disponibles, dont les variables.

<img src="http://jeanphi-baconnais.gitlab.io/img/gitlabci-variables-menu.png"  width="300" >

Dans cet exemple on voit deux variables créées et masquées qui sont utilisables dans le pipeline comme une variable créée dans le fichier _.gitlab-ci.yml_.

![Registry images](http://jeanphi-baconnais.gitlab.io/img/gitlabci-variables.png)

GitLab met également des variables prédéfinies, comme l'identifiant du pipeline **CI_PIPELINE_ID**, le user **CI_REGISTRY_USER**, le message du commit utilisé **CI_COMMIT_MESSAGE**, des informations sur l'utilisateur exécutant le pipeline,  etc. 

Plus de détail sur cette page : https://docs.gitlab.com/ee/ci/variables/predefined_variables.html


<br/>

#### L'attribut tags

L'attribut tag permet de sélectionner un _Runner_, un quoi ? 🤔

Rapidement un runner est ce qui permet d'interpréter les fichiers _.gitlab-ci.yml_. Si vous utilisez l'instance publique GitLab.com, des runners sont configurés et sont ceux utilisés par défaut, ce sont les _Shared runner_, les runners partagés avec tout le monde. Si on est administrateur de son instance GitLab privée, on peut s'amuser à créer des _Specified Runner_, c'est-à-dire des runners spécifiés à un projet ou un groupe de projet. Cela permet de partager les ressources machines en fonction des projets ou groupement de projet.

<br/>

### Le registry <a name="le-registry"></a>

Le _container registry_ de GitLab permet de stocker des images dans votre projet, que ce soit via GitLabCI ou même manuellement en ligne de commande ou par d'autres outils. Simple à comprendre et à utiliser !

C'est disponible dans le menu "Packages" > "Container Registry".

<img src="http://jeanphi-baconnais.gitlab.io/img/gitlabci-registry-acces.png"  width="300" >

Il permet même de stocker plusieurs niveaux. Par exemple dans ce projet je stocke 4 images pour lesquelles je peux déployer plusieurs tags.

![Registry images](http://jeanphi-baconnais.gitlab.io/img/gitlabci-registry-images.png)

![Registry images](http://jeanphi-baconnais.gitlab.io/img/gitlabci-registry-imagestags.png)

Vos images seront accessibles facilement avec un 

```
docker pull registry.gitlab.com/jeanphi-baconnais/arbregen/front:master_133030344
```  

On peut ainsi imaginer un job qui build l'image docker de votre composant via :

```
🐳  build_and_deploy: 
  stage: build
  image: docker:stable
  services:
    - docker:dind
  script:
    - docker login -u GitLabci -p "$CI_SECRET"  registry.GitLab.com
    - docker build -t <your image name> .
    - docker push <your image name>:latest
```


Cette tache est encore plus facile avec l'utilisation de l'outil **Kaniko** (https://github.com/GoogleContainerTools/kaniko) de Google :

```
🐳 build_push_image_docker:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE
```

Kaniko s'occupe de builder l'image à partir d'un dockerfile et de le pusher dans le repository renseigné.

<br/>

### Les environnements <a name="les-environnements"></a>

La partie environnement n'est pas un élément que j'ai approfondi à fond. Cela permet de déclarer des environnements, et de les voir dans la partie environnement disponible dans le menu de GitLab. Un environnement peut être nommé, une url peut y être affectée et cela alimenté automatiquement le menu environnement. 

![Environnements](http://jeanphi-baconnais.gitlab.io/img/gitlabci-environnement.png)

En configurant un cluster kubernetes, il est possible de consulter directement les métriques, avoir accès à un terminal ou alors rejouer votre stage.

![Environnements](http://jeanphi-baconnais.gitlab.io/img/gitlabci-environnement-kubernetes.png)


<br/>

### GitLab Pages <a name="gitlab-pages"></a>

GitLab met à disposition un outil bien pratique, _GitLab Pages_, qui permet d'héberger gratuitement un site basé sur votre repository. C'est exactement avec cet outil que j'ai pu avoir mon site perso _http://jeanphi-baconnais.gitlab.io_.

Lors de la création de votre projet, il est possible de partir de templates, et ceux préfixés par "Pages" permettent de générer un projet possédant directement la configuration nécessaire pour vous permettre d'avoir un site disponible.

Le nom du projet doit contenir votre user.gitlab.io. Contrainte imposée par GitLab.

![Create project](http://jeanphi-baconnais.gitlab.io/img/gitlabci-createproject.png)

Pour mon site, je me base sur le framework GoHugo (cf https://gohugo.io/).

Le fichier *.gitlab-ci.yml* généré est le suivant :

```
image: registry.GitLab.com/pages/hugo:latest

pages:
  script:
  - hugo
  artifacts:
    paths:
    - public
  only:
    refs:
    - master

```

La commande **hugo** permettant de builder le site est exécutée et produit le résultat dans l'artifact _"public"_, le site buildé est mis à disposition et votre site est opérationnel 😎.

Si votre site n'est pas disponible, vous pouvez vérifier la configuration dans le menu "Settings" > "Pages".

<img src="http://jeanphi-baconnais.gitlab.io/img/gitlabci-pagesconf.png"  width="300" >

L'url de votre page doit être affichée telle que :

![Conf Pages](http://jeanphi-baconnais.gitlab.io/img/gitlabci-pagesconf2.png)

<br/>


## Kubernetes

Il est possible d'intégrer (facilement) des cluster kubernetes à GitLab. Après avoir créé son compte sur Google Kubernetes Engine ou Amazon, GitLab va permettre via le menu "Operations" > "Kubernetes" de créer ou déclarer un cluster Kubernetes sur l'une des deux plateformes. Mon exemple concerne l'infrastructure Google.

![Kubernetes](http://jeanphi-baconnais.gitlab.io/img/gitlabci-kubernetes-cluster.png)

Après avoir un cluster, plusieurs outils sont intégrables via l'onglet "Applications", comme Helm, Ingress, Prometheus, etc.

![Kubernetes](http://jeanphi-baconnais.gitlab.io/img/gitlabci-kubernetes-applications.png)
![Kubernetes](http://jeanphi-baconnais.gitlab.io/img/gitlabci-kubernetes-applications-2.png)

On peut même y voir l'état de santé de notre cluster.

![Kubernetes](http://jeanphi-baconnais.gitlab.io/img/gitlabci-kubernetes-health.png)

Pour déployer une application via GitLabCI, c'est très simple, cela se passe toujours dans le *.gitlab-ci.yml* et via l'image *google/cloud-sdk*, l'outil *kubectl* est disponible et permet donc de déclarer des déploiements, services en ligne de commande ou bien via un fichier yaml. C'est également possible avec Helm si vous avez bien activé l'application auparavant.

```
🐋 deploy_gcp :
  stage: deploy
  image: google/cloud-sdk
  script:
    - kubectl apply -f k8s/hello-world.yaml
```

<br/>


### The (happy) end <a name="the-happy-end"></a>

L'approche GitLabCI est très simple à prendre en main, par contre, il faut l'avouer qu'au départ, c'était assez sportif de construire nos pipelines.
Dès qu'un nouveau job était en construction, il plante souvent ... J'ajoute une ligne dans mon job, je *git add*, *git commit*, *git push*, regarde mon pipeline tourner, puis planter. Je modifiais le pipeline,  *git add*, *commit*, *push*, regardait mon pipeline tourner, puis planter, etc.
Même avec l'utilisation du *Web IDE*, ça facilitait les opérations git, mais les statistiques de nos pipelines n'étaient pas dingues. Sur le premier projet de nos tests avec GitLabCI, 40% de tests passés avec succès !

<img src="http://jeanphi-baconnais.gitlab.io/img/gitlabci-stats.png"  width="300" >

Les *GitLab Runner* permettent à GitLab de constuire vos pipelines, et bien l'outil *GitLab-Runner* peut être installé sur vos machines : https://docs.gitlab.com/runner/install/

Cela permet de gagner un temps fou, et d'améliorer vos statistiques de pipeline si votre chef préféré vous espionne 😅

Si vous avez un pipeline constitué de plusieurs jobs, il est même possible d'exécuter un seul job. Par exemple si vous avez un job 'job1' déclaré dans votre *.gitlab-ci.yml*, vous pourrez l'exécuter avec les process de votre machine via un ```gitlab-runner exec docker job1```.

Je ne l'ai pas encore dit, mais vos runner peuvent utiliser plusieurs types d'executor : docker (le seul que j'ai utilisé), shell, sh, virtual box et même kubernetes (que je découvre en écrivant cet article). Je vous laisse lire la documentation si vous voulez approfondir ce point : https://docs.gitlab.com/runner/executors/.

Avec toutes ces notions, nous avons pu automatiser nos pipelines de CI/CD pour automatiser les builds d'applications, les installer sur des machines distantes, informer via Slack des livraisons effectuées avec le commit, bref nous faire gagner en temps et en confort 😎 Même l'intégration sur un cluster Kubernetes est simple et rapide à mettre en place.


La documentation de GitLab est très bien faite et est en constante évolution : https://docs.gitlab.com/ee/ci/yaml/.

Merci de votre lecture et bons pipelines !

<br/>
<br/>
<br/>