https://d226lax1qjow5r.cloudfront.net/blog/blogposts/setting-up-ci-cd-with-github-actions/Blog_GitHub-Desktop_1200x600.png

Mise en place de CI/CD avec Github Actions

Publié le February 17, 2021

Temps de lecture : 9 minutes

L'intégration et le déploiement continus sont des incontournables pour les organisations qui souhaitent évoluer et fournir des logiciels de haute qualité à grande vitesse. Cet article vous guide à travers le processus d'utilisation des actions github pour construire des flux CI/CD.

Qu'est-ce que la CI/CD ?

CI/CD est un processus qui relie le développement au déploiement par le biais d'un processus d'intégration automatisé. L'idée est de permettre aux développeurs de pousser les changements de code comme seule étape nécessaire au déploiement de ces changements.

Le pipeline CI permet au travail d'équipe de progresser en douceur en automatisant les normes et en garantissant la qualité du logiciel. Des outils tels que les linters et les tests automatisés fournissent un retour d'information qui permet de fusionner les modifications avec la branche principale. Ces modifications intégrées sont finalement envoyées aux utilisateurs finaux (production).

Le pipeline CD reçoit le code testé et approuvé. Il confirme que tous les artefacts nécessaires sont déployés au bon endroit. Quelques exemples : le déploiement d'une application web sur un serveur, la publication d'une bibliothèque dans le dépôt d'un gestionnaire de paquets ou la publication d'une application mobile dans l'app store.

L'automatisation de ces processus garantit deux choses importantes : le processus se déroule rapidement et il est beaucoup moins sujet aux erreurs.

Vous voulez savoir comment les équipes accélèrent leurs processus de développement, d'intégration et de déploiement ? Prêt à construire un nouveau pipeline CI/CD ? C'est parti !

Un flux CI/CD de base

  1. Transférer une modification vers une branche de fonctionnalités.

  2. Créer une Pull Request pour ce changement

  3. L'IC se met en marche et exécute ce qui suit :

  4. Lint, Test, Build

  5. Une fois que l'IC a terminé, il marque la PR comme valide et un processus d'examen du code commence.

  6. Lorsque le PR est approuvé, le code est fusionné dans le master.

  7. Après la fusion, le processus de CD se met en place :

  8. Lint, Test, Bump version, Déploiement

Nous allons construire un CI/CD pour une application simple, que vous pouvez cloner ici.

Ajouter un flux de travail Github à votre projet

Créez un dossier .github/workflows. C'est dans ce dossier que nous ajouterons nos actions github. Les actions sont définies dans des fichiers yaml dans une structure assez simple qui consiste en trois parties :

nom: le nom du flux de travail, dans notre cas Test and Build

sur: le déclencheur qui démarre le flux de travail lorsque les conditions sont remplies. Dans notre cas, nous voulons que le flux de travail s'exécute lorsqu'une demande d'extraction est créée et poussée. Nous verrons des exemples d'autres déclencheurs plus loin dans le tutoriel.

emplois: les commandes réelles qui s'exécutent dans le flux. Il peut s'agir de plusieurs tâches qui s'exécutent en parallèle ou de tâches qui dépendent les unes des autres. Dans cet exemple, il existe un travail appelé build-testqui s'exécute uniquement sur le système d'exploitation ubuntu-latest.

name: Test and Build
on:
 pull_request:
   branches:
     - main
jobs:
 build-test:
   runs-on: ubuntu-latest
   steps:
     - name: Checkout
       uses: actions/checkout@v2
       with:
         fetch-depth: 0
     - name: Setup NodeJS 14
       uses: actions/setup-node@v1
       with:
         node-version: 14
     - name: Install yarn
       run: npm install -g yarn
     - name: Install dependencies
       run: yarn install
     - name: Test
       run: yarn test

La première étape consiste à vérifier le référentiel, en utilisant une action pré-fabriquée actions/checkout@v2 de la place de marché des actions. Nous passons la variable fetch-depth: 0 à l'aide de la méthode avec qui indique au processus d'extraction de récupérer le référentiel avec tout son historique. Les étapes suivantes concernent l'installation et l'exécution des commandes bash.

Construire un flux de travail d'intégration continue

  1. Déclencher le flux à l'aide d'une demande de retrait

  2. Lancer la machine

  3. Installer nodeJs sur la machine

  4. Consulter le référentiel

  5. Installer les dépendances

  6. Test, Lint, Build

  7. Afficher les résultats dans la page PR

Pour commencer

  • Si vous n'avez pas encore forké et cloné le dépôt, faites-le maintenant et créez une branche "add-ci".

  • Créer un fichier .github/workflows/ci.yml et copier le contenu de ce fichier dans le nouveau fichier.

  • Livrer les changements et pousser vers la nouvelle branche. Ouvrir une demande d'extraction de la nouvelle branche vers la branche principale.

Si vous regardez la pull request sur github, vous devriez voir les tests fonctionner :

running tests

Vous remarquerez que github connaît déjà le nom du flux de travail, le nom du travail et le déclencheur. Une fois terminé, il devrait ressembler à ceci :

all checks have passed

Pouvez-vous déceler un problème dans le processus ?

Ajout de règles de fusion

Le premier problème est que le Fusionner la demande d'extraction était déjà disponible pendant le processus de CI, alors que nous voulons que la fusion ne soit disponible que si tous les flux de travail requis sont terminés.

Nous pouvons corriger ce problème dans les paramètres du repo en allant dans : Paramètres>Branches>Ajouter une règle

branch settings

Nous sélectionnerons ici Exiger que les vérifications d'état passent avant de fusionner et vérifier tout ce qui se trouve en dessous. Vous verrez tous les flux de travail qui sont nécessaires pour activer la fusion - dans notre cas, nous avons seulement build-test.

require status checks

Pour Modèle de nom de branche insérer main et créer la règle. Et si vous retournez sur la page de demande de téléchargement, vous verrez qu'aucune demande de téléchargement ne peut être fusionnée avant que les tests ne soient passés, à moins bien sûr que vous n'ayez des privilèges d'administrateur.

cannot merge yet

Fusionner après l'examen du code

Avant d'aller plus loin, nous devons également décider comment gérer le passage des Pull Requests. Il existe trois approches principales :

  1. Permettre à n'importe qui de fusionner une fois que les tests ont été réussis

  2. Fusionner automatiquement une fois les tests réussis

  3. Nécessité d'une revue de code pour la fusion

Nous choisirons la première option, qui est la plus courante, et que nous pouvons modifier en éditant la règle de branchement que nous venons de créer. En haut de la liste, cochez Require pull request reviews before merge et cliquez sur enregistrer.

C'est tout pour l'intégration continue ! Maintenant, toutes les nouvelles Pull Requests seront testées et révisées avant d'être fusionnées dans main ! La prochaine étape est le déploiement.

Construire un flux de travail pour le déploiement continu

Le déploiement peut aller du téléchargement de fichiers statiques sur github, à la publication de paquets npm, en passant par le déploiement d'un maillage complet de microservices. Voici le processus que nous suivrons pour notre application une fois le code fusionné dans le dépôt principal :

  1. Installer les dépendances

  2. Test

  3. Version à bosse

  4. Construire

  5. Marquer le communiqué

  6. Publier sur npm

  7. Déployer l'application de démonstration

Examinons les différentes parties du fichier yaml qui gère les actions :

Le déclencheur

Ce sont les conditions dans lesquelles notre code de déploiement s'exécutera. Il se déclenchera sur une pull request qui a été fermée et fusionnée.

name: Test, Build and Deploy

on:
 pull_request:
   types: [closed]

jobs:
 build-test-release:
   if: github.event.action == 'closed' && github.event.pull_request.merged == true
   runs-on: ubuntu-latest

Permissions pour les actions Git

Dans l'étape de vérification, nous allons ajouter une ligne supplémentaire que nous n'avions pas dans le fichier yaml précédent :

steps:
  - name: Checkout
    uses: actions/checkout@v2
    with:
      fetch-depth: 0
      token: ${{ secrets.CI_REPOSITORY_ACCESS_TOKEN }}

Nous devons définir explicitement le jeton afin de pouvoir faire des choses pour lesquelles le jeton par défaut, secrets.GITHUB_TOKENpar défaut, n'a pas les permissions nécessaires. Dans notre cas, nous voulons pousser un changement vers main sans revue de code, donc nous avons besoin d'un jeton avec des privilèges d'administrateur. Nous définissons ce jeton dans Settings -> Secrets.

Augmenter les versions des paquets

Cette étape diffère d'un projet à l'autre. L'idée est d'augmenter la version du paquet que vous avez l'intention de publier dans le dépôt de paquets. Nous allons le faire en passant en revue toutes les bibliothèques qui ont changé dans cette branche, et augmenter leur version NPM :

- name: Raise version of affected libraries
  run: |
    LATEST_TAG=$(git tag -l "v*" --sort=-version:refname | head -n 1)
    LIBS=$(yarn nx affected:libs --base=$LATEST_TAG --head=HEAD --plain | awk 'NR > 2 && $1 != "Done" { print $1 }')
    for LIBRARY in $LIBS
    do
      cd ./libs/$LIBRARY
      npm version minor --no-git-tag-version --no-push
      echo "Bumping $LIBRARY"
      cd ..
      cd ..
    done
    npm version minor --no-git-tag-version --no-push

Notez que nous utilisons NX, un cadre de gestion de monorepo, qui aide à rendre les constructions et le développement beaucoup plus rapides. Nous utilisons la fonctionnalité affected qui nous indique quelles bibliothèques ont été "affectées" par la PR.

Construire

Le processus de construction utilise également la fonction affected également. Il construit les bibliothèques modifiées par rapport à la branche principale avec les presets de production :

- name: Build components
  run: yarn nx affected:build --prod --with-deps --base=main

Marquer le communiqué

Le marquage des rejets se fait en deux étapes :

La première, get-npm-versionutilise une action de la place de marché qui extrait la version du paquet principal. La deuxième étape valide les modifications apportées dans l'étape Raise version mentionnée ci-dessus, crée une balise avec la version du nouveau package.json, et pousse le changement.

Vous vous souvenez du CI_REPOSITORY_ACCESS_TOKEN secret ? C'est ici qu'il entre en jeu. Parce que nous empêchons la fusion vers master sans une revue de la demande d'extraction, nous avons besoin d'un jeton d'administration pour cette partie, qui nous permettra de contourner la règle et de pousser les changements automatiquement.

- name: get-npm-version
  id: package-version
  uses: martinbeentjes/npm-get-version-action@master

- name: Tag the release
  run: |
    git fetch
    git config user.email "unicorn.ci@yonatankra.com"
    git config user.name "Unicorn CI"
    git add --all
    git commit -m "update versions to ${{ steps.package-version.outputs.current-version }}"
    git push

- name: Tag release
  run: |
    git tag -a v${{ steps.package-version.outputs.current-version }} -m "tag release v${{ steps.package-version.outputs.current-version }}"
    git push --follow-tags

Déployer l'application de démonstration

Enfin, nous construisons notre application et la déployons :

- name: Build Demo
  run: yarn build:deploy

- name: Deploy 🚀
  uses: JamesIves/github-pages-deploy-action@3.7.1
  with:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    BRANCH: gh-pages # The branch the action should deploy to.
    FOLDER: dist/apps/unicorn-hunt # The folder the action should deploy.
    CLEAN: true # Automatically remove deleted files from the deploy branch

Vous pouvez trouver le code yaml complet ici

Optimiser le flux CI/CD

Utilisation de la mise en cache dans les actions github

Si vous avez suivi et créé le flux CI/CD, vous pouvez remarquer qu'il prend du temps à s'exécuter. Une solution facile est d'utiliser le cache et d'économiser sur les temps d'installation des dépendances, ce que nous pouvons également faire avec les actions github.

La mise en cache fonctionne en vérifiant s'il y a une correspondance dans le cache, et si c'est le cas, l'étape d'installation des dépendances est ignorée. Voici à quoi ressemble le code :

- name: Install yarn
  run: npm install -g yarn

- name: Get yarn cache directory path
  id: yarn-cache-dir-path
  run: echo "::set-output name=dir::$(yarn config get cacheFolder)"

- name: Cache yarn dependencies
  uses: actions/cache@v2
  id: yarn-cache
  with:
    path: |
      ${{ steps.yarn-cache-dir-path.outputs.dir }}
      **\node_modules
    key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
    restore-keys: |
      ${{ runner.os }}-yarn-

- name: Install dependencies
  if: steps.yarn-cache.outputs.cache-hit != 'true'
  run: yarn install

La première étape installe globalement yarn. La seconde obtient le chemin du dossier de cache de yarn. Ensuite, l'étape de mise en cache proprement dite utilise l'action cache et transmet les chemins vers le cache (node_modules et le dossier du cache de yarn) et lui donne une clé de cache en hachant le fichier yarn.lock. De cette façon, si nous installons une nouvelle dépendance ou mettons à jour une dépendance, cela causera un cache manque. La dernière étape définit une condition sur l'étape install-dependencies à l'aide de l'option cache-hit définie par l'action de cache.

Modulariser le processus

La CI/CD est donc en cours d'exécution. Il utilise le cache. Plus tôt, nous avons mentionné que nous aimerions également publier nos deux bibliothèques dans le dossier libs dans npm. Nous pouvons techniquement ajouter une autre étape à notre processus de CD, mais cela pourrait le rendre lourd et difficile à maintenir.

Au lieu de cela, nous avons divisé le processus en flux de travail distincts qui sont déclenchés par le processus principal du CD. Le CD génère alors une version, qui déclenche les autres processus, ce qui entraîne le déploiement et la publication vers NPM.

Outre une meilleure maintenabilité, cela permet également une meilleure gestion des erreurs. En effet, supposons que le versionnement ait réussi, que la publication ait réussi, mais que le déploiement ait échoué. Cela nous permet de relancer le déploiement et de le déboguer jusqu'à ce qu'il réussisse.

Le code complet de la solution est disponible ici.

Résumé

Dans cet article, nous avons construit un processus CI/CD en utilisant les actions github.

Nous avons commencé par ajouter le processus CI qui exécute les tests et indique à nos réviseurs de code que la Pull Request est prête à être révisée. Nous avons également appris comment bloquer la fusion dans une branche sans que les tests et la révision ne soient passés. Nous avons examiné un code CI simplifié, mais vous pouvez ajouter plus de commandes bash à la phase de test, ou même ajouter plus d'étapes comme linting ou prettier.

Nous avons ensuite créé un processus de CD qui permet de passer d'une version à l'autre et de déployer la démo. Nous avons appris comment mettre en cache l'installation des dépendances afin de gagner du temps, et comment modulariser le processus de CD pour gagner en contrôle et en maintenabilité.

Il y a beaucoup d'autres choses à faire avec les actions github. Nous espérons que ce tutoriel vous a permis de commencer à construire un processus CI/CD utile et significatif pour votre équipe.

Partager:

https://a.storyblok.com/f/270183/400x400/7bf76cb05c/yonatankra.png
Yonatan KraArchitecte logiciel Vonage

Yonatan a participé à des projets impressionnants à l'université et dans l'industrie - de C/C++ à PHP et javascript en passant par Matlab. Il a été directeur technique chez Webiks et architecte logiciel chez WalkMe. Il est actuellement architecte logiciel chez Vonage et instructeur.