Tour d'horizon de Github Action

le 25/05/2020 par Adrien Boulay
Tags: Software Engineering, Devops

Fin de l’année dernière, Github a lancé son nouveau service Actions lui permettant de faire une entrée fracassante sur le marché de la CI/CD. Comme son célèbre compère Gitlab, celui-ci permet d’avoir accès à un système de pipeline très complet, directement au sein des dépôts Github.

Petit tour d’horizon de ce nouveau service.

Note : vous trouverez ci-dessous deux des projets que j’ai utilisé pour rédiger cette article : tezos-link et kata-roman.

Fonctionnement

Github Actions est un service entrant directement en concurrence avec Gitlab-CI, Jenkins ou encore CircleCI. Dans celui-là, on va retrouver deux concepts majeurs : les workflows et les actions.

Des workflows...

Un workflow est un fichier yaml qui va contenir la description du pipeline. Celui-ci se décompose de la manière suivante :

On retrouve tout d’abord les conditions d’exécution du workflow. Parmi celles-ci, on va tout d’abord retrouver le type de déclencheur (aussi appelé event). On en retrouve de différents types :

  • Les events liés au cycle de vie du code (push, pull_request…)
  • Les events liés à la gestion du dépôt Github (pose d’un label, création d’une issue…)
  • Les events liés à la gestion d’un projet Github Project (création d’un projet Github, mouvement de carte sur un projet Github...)
  • Le déclenchement d’un cron à un timestamp spécifique

On va ensuite pouvoir lui appliquer des règles de filtrage en fonction du type d’event. Ainsi, il sera possible de déclencher le workflow si le code a été modifié dans un certain dossier par exemple.

Ensuite nous retrouvons les jobs qui vont représenter les étapes logiques de notre pipeline. Ceux-ci contiennent des steps (étapes exécutées séquentiellement) ainsi que des paramètres servant à décrire les caractéristiques du job (dépendance entre les jobs, stratégie d’exécution...).

Exemple d'un workflow basique

Chaque job va être récupéré par un runner et être exécuté (si possible) en parallèle. Il est possible de mettre des contraintes sur nos différents jobs pour pouvoir choisir :

  • l’environnement dans lequel il est exécuté. Github met à disposition des runners Linux, Windows et MacOs permettant d’effectuer nos jobs sur différents environnements. Pratique quand on souhaite voir si son application peut être lancée dans un shell MacOS et Ubuntu par exemple. Cependant, selon le type de machine employé, un multiplicateur sur le nombre de minute sera appliqué (voir la partie pricing pour plus de détail). On notera également qu’il est possible de mettre en place ses propres runners pour avoir ses propres environnements de test dédiés.
  • s’il nécessite certains prérequis à son bon fonctionnement (exécution de jobs antérieurs…).

Pour les amateurs de mono-dépôt Git, Github Action permet d’avoir plusieurs workflows déclarés simultanément dans un même dépôt. Couplé avec les bonnes règles de filtrages (par dossier par exemple), on va pouvoir définir des workflows dédiés à des usages spécifiques :

  • Tests spécifiques par type de code (front et back). Cela permet d’éviter de déclencher l’exécution de tests n’ayant aucun rapport avec la base de code modifié.
  • Définir également des stratégies de déploiement spécifique en fonction du code modifié.
  • On pourrait également imaginer des déclenchements de pipelines afin d’effectuer des opérations spécifiques (préparation d’image de test spécifique, lancement de procédures de snapshot...)

Chaque workflow doit être dans son propre fichier yaml et ceux-ci sont regroupés dans un sous-répertoire workflows au sein du dossier .github à la racine du projet.

Sur le projet Tezos-Link, cela nous a permis de séparer les différentes parties de notre projet dans des workflows dédiés (front, back, lambda, infra…) ce qui nous permet d’avoir une vue basée par groupement logique de code.

Un fichier dédié pour chaque workflow

L’interface de Github est assez triviale d’utilisation. On y retrouve une vue détaillée en fonction de chacun de nos workflows ainsi que la possibilité de créer des badges spécifiques pour les fichiers README. Cependant, on regrettera assez rapidement l’absence d’un bouton dans l’interface pour déclencher manuellement l’exécution d’un pipeline sur un build donné. (Il est possible de le faire, mais cela demande de passer par des outils externes comme https://www.actionspanel.app/).

L’interface permet de voir chaque fois que notre workflow a été exécuté

Enfin pour la visualisation du workflow, on dispose également d’une interface nous présentant les logs de nos différents jobs ainsi que le temps d’exécution par steps. Les jobs sont déclarés les uns en dessous des autres ce qui entraîne une perte de lisibilité par rapport à la traditionnelle version graphique que l’on retrouve chez les différents concurrents comme Jenkins (blue-ocean) ou CircleCI par exemple.

Une interface pas ultra claire malheureusement

Lorsqu’on utilise Github Action pour mettre en place un pipeline, on remarque assez rapidement l’absence de la notion de stage (que l’on retrouve chez Gitlab-CI par exemple) et qui évolue plus vers une notion de dépendance (à l’aide du mot clé needs) qui est la bienvenue, car elle peut nous permettre un gain de temps significatif si correctement utilisé.

Prenons par exemple un pipeline à base de stage composé de la façon suivante :

Une des limitations que nous imposait le système de stage est que le “chemin critique” de notre pipeline passait forcément par l’attente de la tâche la plus longue avant de pouvoir avancer même si celle-ci n’est utile que bien plus tard.

Avec la logique de dépendance, il est possible de revoir la parallélisation des tâches et ainsi obtenir un potentiel gain de temps sur l’exécution de notre pipeline.

On se retrouve donc avec quelque chose de bien plus proche d’un diagramme de dépendance. Couplé avec les filtres, on se retrouve avec un système vraiment efficace.

Un autre point fort des workflows se situe dans les matrices de tests (appelé matrix). Ces matrices permettent d’appliquer un template sur un job afin de changer les conditions lors de l’exécution (runner utilisé, version de langage…). Cela va alors créer un nombre de jobs correspondant au produit des tableaux d'élément déclaré dans ma matrice.

Prenons un exemple. J’ai une application Node.js qui doit être connectée avec une base de données MongoDB. Je souhaite pouvoir tester que mon application est rétrocompatible avec plusieurs versions de Node.js et qu’elle fonctionne correctement avec MongoDB dans 2 versions différentes.

On se retrouve ainsi avec un workflow qui va comprendre 8 jobs générés dynamiquement correspondants à un produit entre les matrices node-version et mongodb-version.

Si je tente une montée de version de Node.js pour mon application, il faudra simplement que je rajoute la version voulue dans la liste et je pourrais ainsi tester le bon fonctionnement pour cette nouvelle version ainsi que la rétrocompatibilité avec les anciennes.

Ces jobs seront exécutés en parallèle et si une dépendance est mise en place vers ce job, elle attendra la fin de l’exécution des 8 jobs.

On notera quand même que la limite maximum à cette génération est de 256 jobs générés (ce qui peut être atteint assez rapidement s’il y a beaucoup de croisement de matrice).

On voit ici la duplication des jobs liée à la matrice

Ceci est particulièrement pratique pour éviter les duplicatas de code et représente un gain de temps non négligeable tant sur l’écriture du pipeline que sur sa maintenabilité.

Et des actions...

Pour l’instant, nous avons vu un système de pipeline classique pour ce type de service. Une des principales nouveautés proposées par Github Actions est son système d’Action. Celui-ci est similaire à ce qu’on peut retrouver sur Azure DevOps ou CircleCI (avec les orbs). Une Action est similaire à une définition de fonction. On peut lui définir :

  • des inputs (équivalent des paramètres)
  • des outputs (équivalent des sorties)
  • un comportement spécifique (runs) qui aura lieu lors de son exécution.

Cette “fonction” pourra être ensuite appelée dans les workflows qui y ont accès, réutilisée et même partagée. Cette description se fait au travers d’un fichier yaml présent dans le dossier .github/actions/<nom de mon action>. Pour l’heure, il est possible d’appeler exclusivement des conteneurs Docker et des scripts Node.js.

Son principal objectif est de permettre une utilisation facilitée de code complexe à mettre en œuvre ou demandant des outils spécifiques pour fonctionner.

Quelques exemples d’utilisation : abstraction du déploiement de l’infrastructure, wrapping autour de l’exécution de scripts utilitaires…

Exemple d’une action custom à travers un conteneur

L’Action d’exemple présentée ci-dessus va simplement prendre un nombre en paramètre et l’ajouter à la suite de la commande du conteneur. Cela me permettra de l’utiliser dans un autre workflow directement (sans avoir d’étape d’installation du binaire et de ces dépendances par exemple). Pour l’utiliser dans un workflow, il suffit simplement d’utiliser le mot-clé uses avec un chemin vers l’action (chemin relatif ou un dépôt Github) pour la récupérer et le mot clé with pour lui donner des inputs.

Utilisation de l’action run-container présente dans le dépôt

L’avantage de ce système est que cela permet très facilement de partager des actions en les plaçant dans un dépôt spécifique et en ciblant le dépôt en question dans le workflow. On se retrouve alors avec un modèle hautement partageable et réutilisable comme pour des modules Terraform ou des collections Ansible. Ces éléments sont essentiels dans la mise en place de workflow car des fonctions vitales pour un pipeline comme la récupération du dépôt (actions/checkout) ou la gestion des artefacts (actions/cache) se font au travers d’Actions proposées nativement par Github. Ces derniers ont d’ailleurs mis en place une marketplace où l’on pourra retrouver les Actions de la communauté.

La marketplace de Github Actions

Le but de cette marketplace est de faciliter le partage et la récupération d’Action produite par la communauté. Par exemple, l’Action officielle Terraform (proposée par Hashicorp) permet de nous éviter la mise en place de l’outil (et donc de gagner du temps). Le code de ces actions est disponible publiquement et peut être consulté facilement. (ex : setup-terraform)

Utilisation de l’action officielle hashicorp/terraform-github-action

Une des forces de ce système est donc le gain de temps procuré, tant sur le développement (je n’ai pas besoin de préconfigurer une image docker ou développer un script qui jouerai tout pour moi) que sur le temps d’exécution de mon pipeline (plus besoin de steps d’installation pour utiliser certains binaires par exemple).

Parmi les Actions proposés par la communauté, on en retrouve qui peuvent nous servir à faciliter les migrations depuis des outils de CI/CD déjà existant comme Jenkins par exemple.

Action permettant l'exécution de Jenkinsfile directement dans un workflow

Le coût de la solution

Comme pour beaucoup de solution de CI/CD sur le marché, Github Actions arrive avec une offre compétitive :

  • il est gratuit et illimité pour les projets publics (démontrant une fois de plus l’envie de Github d’attirer les projets de ce type).
  • Pour les projets privés, l’offre gratuite comprend 2000 minutes (/mois) de temps de build et 500 Mo de stockage pour les artefacts. Cette offre gratuite est extensible à travers divers tiers payants.

En comparant sur le marché par rapport aux autres concurrents :

ProduitOffre
Github-Action2,000 minutes/mois
Gitlab-CI (tier gratuit)2,000 minutes/mois
CircleCI (tier gratuit)1,000 minutes/mois
Travis (free trial)100 builds fixes

Ces minutes 2000 minutes par mois peuvent subir un multiplicateur selon le type de machine cible. D’après la documentation de Github, les multiplicateurs en fonction des OS sont les suivants :

OSmultiplicateur (* minutes consommées)
Linux1
MacOS10
Windows2

Un runner utilisant Windows va donc avoir un ratio de 2 minutes écoulé pour 1 minute de build. Enfin, utiliser ses propres runners permet de se soustraire des règles liées aux runners de Github.

En termes de concurrence des jobs, Github est aussi dans le “tier” haut par rapport à ces concurrents sur le “free tier” sur ces runners (seul Gitlab-CI le dépasse) :

ProduitNombre de jobs concurrents possibles
Github-Actions20
GitlabN/A
Circle-CI3
Travis2

Pour les tiers plus importants (Teams, Enterprise), la différence (vis-à-vis des Actions) ne se jouera que sur le nombre de minutes totales passant à 13000 pour Teams (3000 gratuites + 10000) et 50000 pour Entreprise. La réelle différence se fera sur le stockage dans la registry Github Package Registry.

Conclusion

Avec un système de multi-pipeline et son système d’Actions, Github Actions fait un pas-de-géant dans le monde de la CI/CD. Celui-ci n’est plus dépendant de solution externe telle que Circle-CI ou Travis-CI et dispose désormais d’un produit complet CI/CD disposant d’une bonne partie des outils que l’on attend de ce genre de solution.

De nombreux projets open source particulièrement connus ont d’ailleurs amorcé leur migration vers cette nouvelle solution. Parmi ceux-ci, on peut citer hashicorp/vagrant, kubernetes/minikube, une grande partie des collections Ansible... (ex : ansible-collections/kubernetes).

A titre personnel, c’est une technologie que j’aime utiliser pour les gains qu’elle m’apporte :

  • Gain de temps sur la rédaction de workflow grâce à quelques features bien pratiques comme les Actions.
  • Gain de temps sur l’exécution en décrochant les dépendances.
  • Gain en terme d’organisation de mon espace de travail. Je n’ai pas un fichier avec plein de filtre dans tous les sens pour l’exécution des jobs. Tout est rangé dans son fichier.
  • Enfin, pour l’avoir mis en place sur un projet avec un collègue, l’abstraction proposée par les Actions me permet de mettre à disposition des Actions préconçues (Actions-as-a-Service). Cela permet d’éviter une certaine complexité pour les équipes de développement tout en rendant le partage encore plus simple (au travers d’action dédiée aux tâches de déploiements par exemple...).

Pour récapituler :

Les plus
- Directement intégré au sein du dépôt Github.<br>- Une syntaxe disposant de feature vraiment agréable à utiliser.<br>- Les actions apportent un vrai gain de temps pour la mise en place de pipeline.<br>- Une communauté particulièrement active autour de ce projet.<br>- Prix et capacité attractive (surtout pour les projets open source).
Les inconvénients
- Absence de visualisation sous forme de graphe ce qui rend la lisibilité complexe sur des pipelines de tailles importantes.<br>- Un manque regrettable de quelques features clés comme un trigger manuel des pipelines.<br>- Obligation de passer par des artefacts pour conserver des packages entre deux jobs.