Concourse CI

Logo Concourse CI

Qu’est ce donc ?

Concourse CI est une plateforme d’intégration et de déploiement continu (CI/CD) open source et développée en GO, supportée par Pivotal.

Ce qui différencie Concourse des autres plateformes de CI est l’exécution des tâches du pipeline dans des conteneurs. Cela permet de rendre la définition du pipeline et des ressources qui le compose indépendantes de l’environnement d’exécution.

Nous verrons les détails de tout ça plus loin 😉

Dans le reste de l’article, les termes liés aux concepts amenés par Concourse seront volontairement laissés en anglais afin de faciliter le lien avec l’utilisation du produit.

Voici un aperçu de l’interface de Concourse :

Aperçu Interface Concourse
Interface principale de Concourse

Concepts & Architecture

En premier lieu, intéressons nous à l’architecture et à la modélisation des concepts utilisés.

Voici un petit schéma pour situer les concepts que nous allons aborder :

Schéma architecture Concourse
Les concepts clés

Pipeline

Comme évoqué plus haut, à quoi correspond un Pipeline dans Concourse ?

C’est la définition globale de notre chaîne de construction et de déploiement. Il peut être matérialisé par un ou plusieurs fichiers au format Yaml.

On y retrouvera la définition des Jobs et des Resources.

Un Pipeline a son cycle de vie propre. Cela lui permet de lancer des Builds pour les Jobs définis lorsque les Resources associées ont de nouvelles données d’entrée.

Voici un exemple de Pipeline qui affiche le traditionnel « Hello world » dans la console :

jobs:  
# On définit le nom du Job
- name: job-hello-world 
    public: true
# On définit le Build Plan
    plan: 
# On définit le nom de la Task qui sera exécutée lors du Build
      - task: hello-world 
# On configure l'image du conteneur d'exécution dans lequel notre Build va être exécuté
        config: 
          platform: linux
          image_resource:
            type: docker-image
            source: {repository: busybox}
# On définit la commande qui sera lancée pour cette Task
          run: 
            path: echo
            args: [hello world]

Jobs

Les Jobs permettent de déterminer les actions d’un Pipeline. Ils vont définir la façon dont les Resources qui leurs sont propres vont évoluer durant les Builds.

Un Job possède un Build Plan qui permet de définir le séquencement des Steps qui seront exécutées durant les Builds.

Un Build est donc par essence l’exécution d’un Build Plan.

Steps

Les principaux Steps utilisés par les Build Plan sont les suivants :

  • Le Task Step : qui permet l’exécution d’une Command/Task
  • Le Get Step : qui permet de récupérer une Resource
  • Le Put Step : qui permet de mettre à jour une Resource

Resources

Les Resources représentent les entrées et les sorties externes d’un Job. Elles font office de connecteur.

La liste des Resources disponibles se situe sur : https://resource-types.concourse-ci.org/

On peut y retrouver, par exemple, une Resource GIT qui va permettre à un Job de se sourcer sur un repository GIT et d’être à l’écoute des modifications sur ce dernier.

Si on reprend notre Pipeline « Hello World » et qu’on y ajoute, pour l’exemple, une Resource GIT :

# On déclare un bloc dans lequel on va définir les Resources utilisées par le Pipeline
resources:
# On définit un nom à notre Resource pour pouvoir y faire référence dans les Jobs
  - name: git-demo 
    type: git
    source:
# On définit les paramètres propres à notre Resource
# Ici passés sous forme de variables externes
      uri: ((git-uri))
      private_key: ((git-key))
      branch: ((git-branch))

jobs:
  - name: job-hello-world 
    public: true
    plan: 
# On ajoute à notre Build Plan une Get Task vers la Resource de notre repository GIT
      - get: git-demo
# On définit un trigger pour scruter les changements de façon automatique
        trigger: true 
      - task: hello-world 
        config: 
          platform: linux
          image_resource:
            type: docker-image
            source: {repository: busybox}
          run: 
            path: echo
            args: [hello world]

Task

Les Tasks représentent les actions effectuées par le Job. Elles sont exécutées dans un conteneur et sont par essence sans état (stateless).

Elles peuvent utiliser les Resources définies dans le Job sous forme d’input ou d’output.

Une Task étant exécutée dans un conteneur, les données (fichiers, archives, console output…) qu’elle va générer seront éphémères. Elles seront perdues après la destruction du conteneur.

Pour remédier à ce problème, la Task peut écrire dans un output défini. Généralement un répertoire qui sera accessible par les autres Steps du Build Plan. Par exemple, cet output peut être utilisé par une Resource dans un Put Step.

Exemple de Pipeline

Voici un exemple d’un Pipeline qui prend un Repository Git en Resource d’entrée et un Nexus en Sortie. La Task exécutée est une compilation Maven.

# On utilise un type de Resource d'une image docker Maven
resource_types:
  - name: maven
    type: docker-image
    source:
      repository: nulldriver/maven-resource
      tag: latest

resources:
# La Resource vers le Repository Git
  - name: git-kanoma
    type: git
    source:
      uri: ((git-uri))
      private_key: ((git-key))
      branch: ((git-branch))
# La Resource vers le Repository Nexus Maven
  - name: nexus-mvn
# On utilise notre type de Resource défini plus haut
    type: maven
    source:
      url: http://localhost:8081/repository/maven-releases/
      snapshot_url: http://localhost:8081/repository/maven-snapshots/
      artifact: fr.kanoma:demo-concourse:jar
      username: admin
      password: admin123
      skip_cert_check: true

jobs:
  - name: mvn-build-demo
    serial: true
    plan:
# Le Get Step se base sur la Resource Git
      - get: git-kanoma
        trigger: true
# La Task effectue un build Maven
      - task: build-demo-concourse
        config:
# L'image docker utilisée est une image Maven
          platform: linux
          image_resource:
            type: docker-image
            source:
              repository: maven
# En Input de notre Task on retrouve notre Resource Git
          inputs:
            - name: git-kanoma
# En Output de notre Task, on défini le repertoire de sortie
          outputs:
            - name: demo-concourse-build-output
# On définit un cache qui sera utilisé pour les différentes construction du Build
# Ce qui évite de télécharger de nouveau les dépendances Maven
          caches:
            - path: git-kanoma/demo-concourse/m2
# On définit l'action de notre Task, ici un "Maven install"
 
          run:
            path: sh
            args:
              - -exc
              - |
                cd git-kanoma/demo-concourse
                rm -rf ~/.m2
                ln -fs $(pwd)/m2 ~/.m2
                mvn install
# On recopie notre archive Jar générée dans l'output de la Task
                cp target/*.jar ../../demo-concourse-build-output/demo-concourse.jar
# On définit notre Put Step qui va écrire dans le Repository Nexus
      - put: nexus-mvn
        params:
# On utilise l'output de la Task de notre Build Plan
          file: demo-concourse-build-output/demo-concourse.jar
          pom_file: git-kanoma/demo-concourse/pom.xml

Bien entendu ici, le Pipeline est d’un seul tenant dans un seul fichier. Rien n’empêche de fractionner notre Pipeline en plusieurs fichiers.

Fragmentation du Pipeline

Focalisons nous sur le Job. Nous allons extraire notre tâche dans un fichier distinct.

# ... import des Resources inchangé ...
jobs:
  - name: mvn-build-demo
    serial: true
    plan:
      - get: git-kanoma
        trigger: true
# On fait désormais référence à un fichier Yaml présent sur dans notre Repository Git
      - task: build-demo-concourse
        file: git-kanoma/pipelines/tasks/build-demo-concourse.yml

      - put: nexus-mvn
        params:
          file: demo-concourse-build-output/demo-concourse.jar
          pom_file: git-kanoma/demo-concourse/pom.xml

De cette manière, la définition de notre Task est portée par notre projet et sa description est commitée avec le reste du code. La Task de notre Pipeline fait alors référence à un fichier de description Yaml présent dans la Resource Git définie.

Néanmoins, cette division peut entraîner une compréhension plus difficile du Pipeline. En effet, sa définition est fragmentée et maintenue à plusieurs endroits distincts.

Le fichier de définition de notre Task est le suivant (build-demo-concourse.yml) :

# On configure l'image à utiliser
platform: linux
image_resource:
  type: docker-image
  source:
    repository: maven

inputs:
  - name: git-kanoma
outputs:
  - name: demo-concourse-build-output
caches:
  - path: git-kanoma/demo-concourse/m2

# On définit l'action de notre Task dans un fichier séparé
run:
  path: /bin/bash
  args: [ git-kanoma/pipelines/tasks/build-demo-concourse.sh ]

Le script représentant l’action de la Task (build-demo-concourse.sh) :

#!/bin/bash

cd git-kanoma/demo-concourse
rm -rf ~/.m2
ln -fs $(pwd)/m2 ~/.m2
mvn install
cp target/*.jar ../../demo-concourse-build-output/demo-concourse.jar

FLY CLI

Une fois que l’on a rédigé notre Pipeline au format Yaml, que doit-on en faire ?

L’outil de lignes de commandes FLY CLI est là pour ça.

En effet, il va nous permettre d’exécuter les principales actions suivantes sur une instance Concourse :

  • se connecter / déconnecter
  • envoyer et vérifier des définitions de Pipelines (Yaml)
  • récupérer la définition d’un Pipeline
  • supprimer un Pipeline
  • lancer / mettre en pause des Pipelines

Exemple :

# Se connecter à l'instance Concourse
fly -t handson login -c http://localhost:8080 -u test -p test

# Créer le pipeline
fly set-pipeline -t handson -p demo -c pipeline.yml -l credentials.yml

# Lancer le job
fly up -t handson -p demo

Interface Concourse

Une fois notre Pipeline envoyé à l’instance Concourse voici le résultat :

Affichage construction

On retrouve notre Resource Git en entrée, notre Task de construction Maven et notre Resource Nexus en sortie.

Lançons un Build et vérifions ce qui se passe, en cliquant sur notre Build on obtient l’écran de détails suivant :

Détail construction

On retrouve notre Get Step de récupération des sources via la Resource Git. Puis notre Task de construction Maven. Et enfin, notre Put Step pour pousser le résultat via notre Resource Nexus.

Lorsque l’on retourne sur l’écran principal, on constate que notre Build est correctement passé et s’affiche désormais en vert 😉

Résultat contruction OK

Conclusion

Concourse CI est une plateforme simple à prendre en main et ses concepts sont rapidement assimilables. Le fait de travailler avec des conteneurs et d’avoir une définition des Pipelines complètement dissociée de l’environnement d’exécution reste un plus indéniable.

Néanmoins, il faut rester vigilant sur l’écriture des descripteurs de Pipelines car ils peuvent rapidement devenir assez imposants et difficile à lire. Pour palier à ce problème, la segmentation en plusieurs fichiers peut permettre de réduire la complexité mais parfois au détriment de la lisibilité.

Documentation

S’abonner
Notifier de
guest
0 Commentaires
Inline Feedbacks
View all comments