Intégration Continue avec GitLab-CI

Présentation de Gauthier C. est mise à disposition selon les termes de la licence Creative Commons Attribution 4.0 International.

ccby

Les préconisations de DevOps

Culture collaborative, procédés et outils, mesure

  • Casser les silots

  • Utiliser des outils pour accélérer les procédés et diminuer les risques d’erreurs humaines.

Plus petit, plus vite et plus souvent

Travailler sur les développements petits (micro-services), les installer en production plus vite.

Des équipes intégrées multicompétentes et autonomes

Modifier l’organisation de la DSI autour d’équipes autonomes. multicompétentes, en charge de l’ensemble du cycle de vie d’une release applicative.

Une architecture agile

Agilité de l’architecture du système d’information. Pour que les équipes soient autonomes et puissent conduire à leur rythme les mises en production.

La sureté de fonctionnement des plateformes

Qualité des tests. Déploiement et activation progressive des fonctionnalités pour limiter les risques inhérents à toute modification du système opéré en production.

Développement continu

Scrum

  • Choix en début de sprints des fonctionnalités à développer.

  • Brièveté des itérations pour livrer rapidement.

  • Nombre de fonctionnalités developpées par sprint limité pour augmenter la vélocité

Kanban

  • Permet une remise en cause du périmètre par rapport à Scrum.

  • Pas de sprints mais un nombre de developpement en parallèle limité.

Qu’est ce qu’une CI ?

CI ou Intégration Continue en français

L’intégration continue est un ensemble de pratiques utilisées en génie logiciel consistant à vérifier à chaque modification de code source que le résultat des modifications ne produit pas de régression dans l’application développée.
— Wikipedia

Et lorsque l’on parle d’une CI/CD ?

CD ⇒ Continuous Delivery ou Continuous Deployement

  • Delivery: Opération de livrer un logiciel

  • Deployement: Mise en production totalement automatisée du logiciel

Que fait-on dans une Intégration Continue?

Des tests

  • Tests unitaires

  • Tests de non régression

  • Tests d’intégration

  • Tests fonctionnels ou tests d’acceptance

  • Tests end to end

  • Tests de sécurité

  • Tests de qualité

  • Tests de performance

Et quoi d’autres ?

  • Linter (alignement des conventions de codage)

  • Compilation, création d’artéfacts

  • Couverture du code par les tests

  • Génération de la documentation

À noter

  • Les tests ne sont pas une variable d’ajustement du planing.

  • Les developpeurs testent leur code en continu.

  • Les tests se font en parallèle.

  • L’automatisation des builds et les tests unitaires valident les changements.

Review Apps

  • Déploiement d’un environnement temporaire pour chaque merge request.

Objectifs

  • Bugs trouvés et corrigés plus rapidement.

  • Productivité amélioré grace à l’automatisation.

  • Améliorer la qualité du code

  • Feedback rapide

  • Maintenir le code dans un état permettant le déploiement

  • Fournir des environnement de déploiement à la demande

7 raisons pour passer à l’intégration continue

  • Exécutez vos tests dans un environnement réel

  • Augmentez votre couverture de code

  • Déployez votre code en production

  • Développez des projets tout de suite

  • Développez plus rapidement

  • Ne rien casser

  • Diminuez le temps passé à faire des revues de code

Contraintes

  • Automatiser tout ce qu’il est possible d’automatiser.

  • Travailler par petites itérations

  • Disposer d’un environnement d’exécution (kubernetes, docker, …​)

  • Éviter de dépasser 10 minutes de CI à chaque push

Les outils de CI/CD

outils cicd
gitlab devops

GitLab

gitlab1

GitLab

gitlab2

GitLab

gitlab3

GitLab

gitlab4

GitLab Workflow

gitlab workflow
gitlab runner

.gitlab-ci.yml

Ajouter un fichier .gitlab-ci.yml à la racine du projet

  • .gitlab-ci.yml définit un ensemble de jobs (commandes) et de contraintes par stages

  • L’ensemble des stages forme un pipeline

  • Le pipeline est déclenché à chaque commit ou push et s’exécute dans des Runners

  • .gitlab-ci.yml décrit aux Runners ce qu’il faut faire

Exemple de .gitlab-ci.yml

stages:
  - test
  - build
  - deploy

test:
  stage: test
  script: echo "Running tests"

build:
  stage: build
  script: echo "Building the app"

deploy_staging:
  stage: deploy
  script:
    - echo "Deploy to staging server"
  environment:
    name: staging
    url: https://staging.example.com
  only:
    - master

GitLab Runner

Présentation

Le runner permet d’exporter l’exécution des scripts .gitlab-ci.yml sur la machine qui héberge le runner.

Il est écrit en Go et compilé pour Linux, Windows, MacOS, FreeBSD.

Différents type de Runner

  • Les runners partagés sont accessibles à tous les groupes et projets d’une instance GitLab.

  • Les runners de groupe sont accessibles à tous les projets et sous-groupes d’un groupe.

  • Les runners spécifiques sont associés à des projets spécifiques.

Environnement d’exécution

  • Shell

  • Parallels/VirtualBox

  • Docker

  • Docker Machine (auto-scaling)

  • Kubernetes

  • SSH

  • Custom

Choix du Runner

ExecutorSSHShellVirtualBoxDockerKubernetes

Env de build nettoyé pour chaque jobs

Réutilisation du clone s’il existe

Accès au fs protégé

Debug

facile

facile

difficile

moyen

moyen

Runners tags

Lorsque un runner (partagés ou dédiés) est spécialisé (windows/linux/docker/java/…​), il est possible de définir un tag lors de l’enregistrement pour que seuls les jobs ayant le tag s’exécute dessus.

Enregistrement du runner

gitlab-runner register \
  --non-interactive \
  --url "<URL SERVEUR GITLAB>" \
  --registration-token "PROJECT_REGISTRATION_TOKEN" \
  --executor "<TYPE>" \
  --docker-image <IMAGE DOCKER PAR DEFAUT> \
  --description "<DESCRIPTION>" \
  --tag-list "<TAG1, TAG2...>" \
  --run-untagged \
  --locked="false"

Fichier de configuration du Runner

Exemple de fichier /etc/gitlab/config.toml

[[runners]]
  name = "ruby-2.6-docker"
  url = "https://gitlab.mondomaine.fr/"
  token = "xxxxxxxxxxxxxx"
  limit = 0
  executor = "docker"
  builds_dir = ""
  [runners.docker]
    host = ""
    image = "ruby:2.6"
    privileged = false
    disable_cache = false
    cache_dir = ""

Sécurité

Lorsque vous utilisez GitLab Runner, vous devez être conscient des implications potentielles sur la sécurité lors de l’exécution de vos jobs.

  • Avec les runners partagés, toute personne exécutant un job peut accéder au code de quelqu’un d’autre qui s’exécute sur le même runner.

  • Les runners utilisent un jeton pour s’identifier au serveur GitLab. Si vous clonez un runner, le runner cloné pourrait récupérer les mêmes jobs.

.gitlab-ci.yml

Doit être placé à la racine du dépôt.

Le fichier YAML définit un ensemble de jobs avec des contraintes indiquant quand ils doivent être exécutés. Les jobs sont définis comme des éléments de niveau supérieur avec un nom et doivent toujours contenir au moins la commande script.

Exemple de fichier

job1:
  script: "execute-script-for-job1"

job2:
  script: "execute-script-for-job2"

Liste des mots réservés

Un job peut avoir toute sorte de nom sauf :

Mot réservéDescription

image

Nom de l’image docker

services

Nom du services docker

stages

Define build stages

types

Ancien nom pour stages (deprecated)

before_script

Commandes à lancer avant chaques script

after_script

Commandes à lancer après chaque script

cache

Liste des fichiers qui doivent être mis en cache entre 2 exécutions

Un job commençant par un "." est inactif.

Stages

  • stages est utilisé pour définir les étapes du pipelines.

  • L’ordre des stages définit l’ordre d’exécution des taches.

  • Tout les jobs exécuté dans le même stage se déroulent en parallèle.

  • Les jobs de l’étape suivantes sont exécuté une fois les jobs du stage précédent terminés.

Exemple:

stages:
  - build
  - test
  - deploy

Si aucun stages n’est définie dans .gitlab-ci.yml, alors test et deploy peuvent être utilisé.

Si un job n’a pas de stage, le job est affecté au stage test.

Variables

Gitlab CI vous permet de définir des variables utilisables dans l’environnement d’exécution des jobs.

variables:
  DATABASE_URL: "postgres://postgres@postgres/my_database"

Ces variables seront visibles en clair dans le dépôt, il ne faut pas y mettre de données confidentielles.

Si la variabe est définit au niveau du job, elle ne sera utilisable que par celui-ci.

Variables prédéfinits

Toutes une liste de variables sont envoyés au runner directement par Gitlab.

Cache

cache est utilisé pour définir une liste de fichier ou de répertoire qui doivent être mis en cache entre 2 jobs.

Cache tous les fichiers dans les dossiers binaries et .config:

rspec:
  script: test
  cache:
    paths:
    - binaries/
    - .config

Cache:key

La directive key permet de définir une affinité du cache entre les jobs.

Cache pour les jobs de même nom:

cache:
  key: "$CI_JOB_NAME"
  untracked: true

Cache par branche Git:

cache:
  key: "$CI_COMMIT_REF_NAME"
  untracked: true

Par branche et jobs dans la branche:

cache:
  key: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
  untracked: true

Les Jobs

Vous pouvez définir un nombre illimité de jobs dans votre fichier .gitlab-ci.yml. Chaque job aura un nom unique qui n’est pas un des mots clefs présentés précédement.

Script

script est un script shell exécuté par le runner. Par exemple:

job:
  script: "bundle exec rspec"

Il est possible de spécifier un grand nombre de commmandes:

job:
  script:
    - uname -a
    - bundle exec rspec

Il est préférable de protéger les commandes par des " ".

Attention aux caractères : {, }, [, ], &, #, ?, |, -, <, >, =, !, %, @, *, ' et :

stage

stage permet de regrouper les jobs entre eux. Les jobs d’un même stage sont exécutés en parallèle.

only et except

only et except sont 2 paramètres qui permettent de définir dans quelles conditions le job est appelé. 1. only définit le nom des branches, tags, …​ pour lesquels il faut exécuter le job. 2. except définit le nom des branches, tags …​ pour lesquels il ne faut pas exécuter le job.

  • only et except sont inclusifs

  • il est possible d’utiliser des expressions régulières.

branches, tags, api, external, pipelines, pushes, schedules, triggers, web

Exemples only et except

Dans cet exemple, le job est lancé uniquement lorsque le "refs" commence par issue-, toutes les branches sont ignorées:

job:
  # use regexp
  only:
    - /^issue-.*$/
  # use special keyword
  except:
    - branches

Il est possible de ne lancer le job que sur le dépôt officiel (non les forks), sauf master :

job:
  only:
    - branches@gitlab-org/gitlab-ce
  except:
    - master@gitlab-org/gitlab-ce

Jobs variables

Comme pour les variables globales mais dont la portée est limitée au job

job_name:
  variables: {}

tag

Les tags sont utilisés pour lancer les jobs sur des runners en particulier.

job:
  tags:
    - ruby
    - postgres

Ici le job s’exécutera sur les runners qui auront pour tag ruby et postgres.

allow_failure

allow_failure est utilisé pour autorisé l’exécution des jobs suivants même si le script du job en question est en erreur.

job1:
  stage: test
  script:
  - execute_script_qui_ne_reussit_pas
  allow_failure: true

job2:
  stage: test
  script:
  - execute_script_suivant

when

when est utilisé en fonction de 4 critères:
  1. on_success: exécute le job uniquement lorsque tous les travaux des étapes précédentes réussissent. C’est la valeur par défaut.

  2. on_failure: exécute le job seulement quand au moins un travail des étapes précédentes échoue.

  3. always: exécuter le job quel que soit le statut des travaux des étapes précédentes.

  4. manual: exécute le job manuellement (ajouté dans GitLab 8.10)

cleanup_build_job:
  stage: cleanup_build
  script:
  - cleanup build when failed
  when: on_failure

environment

environment permet de définir dans quel environnement il faut déployer.

deploy to production:
  stage: deploy
  script: git push production HEAD:master
  environment:
    name: production
    url: https://prod.example.com

Habituellement nous avons 3 environnements : Developpement, qualification, production.

artifacts

artifacts permet de spécifier une liste de fichiers ou de répertoires générer lors d’un job de build en vue de les sauvegarder.

default-job:
  script:
    - mvn test -U
  except:
    - tags

release-job:
  script:
    - mvn package -U
  artifacts:
    paths:
    - target/*.war
  only:
    - tags

Dans l’exemple ci-dessus, les données sont envoyés à GitLab pour les proposer au téléchargement dans l’interface web.

artifacts:name

La directive name permet de définir le nom de l’archive créée.

job:
  artifacts:
    name: "$CI_JOB_NAME"

artifacts:when

La directive name permet de définit le temps de rétention de l’archive.

job:
  artifacts:
    expire_in: 1 week

Exemple de format de date:

  • '3 mins 4 sec'

  • '2 hrs 20 min'

  • '2h20min'

  • '6 mos 1 day'

  • '47 yrs 6 mos and 4d'

  • '3 weeks and 2 days'

dependencies

Cette fonctionnalité permet de définit les artifacts à passer d’un job à l’autre.

Dans l’exemple ci-dessous, nous définissons deux jobs avec des artifacts, build:osx et build:linux. Lorsque le test:osx est exécuté, les artifacts de build:osx seront téléchargés et extraits dans le contexte de build. Même chose pour test:linux et artifacts de build:linux.

build:osx:
  stage: build
  script: make build:osx
  artifacts:
    paths:
    - binaries/

build:linux:
  stage: build
  script: make build:linux
  artifacts:
    paths:
    - binaries/

test:osx:
  stage: test
  script: make test:osx
  dependencies:
  - build:osx

test:linux:
  stage: test
  script: make test:linux
  dependencies:
  - build:linux

before_script et after_script

Il est possible de surcharger before_script et after_script dans un job.

before_script:
- global before script

job:
  before_script:
  - execute this instead of global before script
  script:
  - my command
  after_script:
  - execute this after my script

coverage

coverage permet de récupérer la valeur de couverture de test dans la sortie du script du job, grace à une expression régulière :

job1:
  script: rspec
  coverage: '/Code coverage: \d+\.\d+/'

Git Strategy

La variable GIT_STRATEGY permet de définir la façon dont le runner rapatrie le code.
  1. clone télécharge le dépôt complet à chaque job

  2. fetch réutilise l’espace de travail du projet (clone s’il n’existe pas).* git clean* est utilisé pour annuler toutes les modifications apportées par le dernier job, et git fetch est utilisé pour récupérer les validations effectuées depuis le dernier job exécuté.

  3. none réutilise également l’espace de travail du projet, mais ignore toutes les opérations Git. Utile pour les jobs qui fonctionnent exclusivement sur des artifacts (par exemple, le déploiement).

variables:
  GIT_STRATEGY: fetch

Job stages attempts

Vous pouvez définir le nombre de tentatives du job en cours à exécuter l’une des étapes suivantes:

VariableDescription de la variable

GET_SOURCES_ATTEMPTS

Nombre de tentatives d’extraction des sources

ARTIFACT_DOWNLOAD_ATTEMPTS

Nombre de tentatives de téléchargement d’artifacts

RESTORE_CACHE_ATTEMPTS

Nombre de tentatives de restauration du cache

Job stages attempts

La valeur par défaut est une tentative.

variables:
  GET_SOURCES_ATTEMPTS: 3

Ces variables peuvent être définit au niveau global ou par job.

pages

pages est un job particulier utilisé pour télécharger du contenu statique sur GitLab qui peut être utilisé pour servir votre site Web. Il a une syntaxe spéciale, donc les deux exigences ci-dessous doivent être satisfaites:
  1. Tout contenu statique doit être placé dans un répertoire public

  2. les artifacts doivent pointer vers le répertoire public/.

Exercices

Utilisation de Docker

GitLab peut utiliser Docker pour tester et construire n’importe quelle application.

Docker, lorsqu’il est utilisé avec GitLab CI, exécute chaque travail dans un conteneur séparé et isolé à l’aide de l’image prédéfinie définie dans .gitlab-ci.yml.

Cela rend plus facile d’avoir un environnement de construction simple et reproductible qui peut également fonctionner sur votre poste de travail. L’avantage supplémentaire est que vous pouvez tester toutes les commandes que nous explorerons plus tard à partir de votre shell, plutôt que d’avoir à les tester sur un serveur CI dédié.

Enregistrement du runner pour Docker

Pour utiliser GitLab Runner avec Docker, vous devez enregistrer un nouveau Runner comme ceci:

sudo gitlab-runner register \
  --url "https://gitlab.example.com/" \
  --registration-token "PROJECT_REGISTRATION_TOKEN" \
  --description "docker-ruby-2.1" \
  --executor "docker" \
  --docker-image ruby:2.1 \
  --docker-postgres latest \
  --docker-mysql latest

Qu’est-ce qu’une image ?

Le mot-clé image est le nom de l’image Docker que le runner lancera pour exécuter les tâches CI.

Qu’est-ce qu’un service ?

Le mot clé services définit juste une autre image Docker qui est exécutée pendant votre job et est liée à l’image Docker définie par le mot-clé image. Cela vous permet d’accéder à l’image du service pendant la construction.

L’image de service peut exécuter n’importe quelle application, mais le cas d’utilisation le plus courant consiste à exécuter un conteneur de base de données, par exemple mysql . Il est plus facile et plus rapide d’utiliser une image existante et de l’exécuter en tant que conteneur supplémentaire que d’installer mysql chaque fois que le projet est construit.

Tests sur des environnements différents

before_script:
  - bundle install

test:2.1:
  image: ruby:2.1
  services:
  - postgres:9.3
  script:
  - bundle exec rake spec

test:2.2:
  image: ruby:2.2
  services:
  - postgres:9.4
  script:
  - bundle exec rake spec

Changement d’entrypoint

image:
  name: ruby:2.2
  entrypoint: ["/bin/bash"]

services:
- name: my-postgres:9.4
  alias: db-postgres
  entrypoint: ["/usr/local/bin/db-postgres"]
  command: ["start"]

before_script:
- bundle install

test:
  script:
  - bundle exec rake spec

Choix d’une image dans un autre registre

image:
	name: "registry.example.com/my/image:latest"

services:
- name: postgresql:9.4
- name: redis:latest

Paramètres disponibles pour services

RéglageDescription

name

nom de l’image docker du registre

entrypoint

Point d’entrée du conteneur.

command

Commande ou script qui devrait être utilisé comme commande du conteneur

alias

Alias supplémentaire pouvant être utilisé pour accéder au service

Démarrage de plusieurs services de la même image

services:
- name: mysql:latest
  alias: mysql-1
- name: mysql:latest
  alias: mysql-2

Fin