Déployer depuis la CI/CD

Déployer à chaque push depuis GitHub Actions ou GitLab CI/CD, avec un identifiant restreint et un job qui échoue quand le déploiement échoue.

Un pipeline fait quatre choses : installer la CLI, se connecter avec un identifiant conservé dans le système de CI, créer l'application la première fois puis la mettre à jour à chaque fois ensuite, et attendre le déploiement pour que le job échoue quand celui-ci échoue. Deux modèles prêts à l'emploi font exactement cela. Cette page les présente en entier, explique chaque partie et décrit ce qui peut mal tourner.

1. Créer un identifiant pour le pipeline

Ne réutilisez pas l'identifiant de votre propre machine. Créez-en un réservé à la CI, avec uniquement ce dont le pipeline a besoin : il pourra être révoqué sans vous déconnecter, et ne permettra rien de plus que déployer s'il fuit.

export ISOGRID_CONFIG_HOME="$(mktemp -d)"
isogrid --api https://api.isogrid.skyvault.pro login \
  --scope application:create,application:write-any \
  --name github-ci

Le navigateur s'ouvre sur la page d'approbation. Vérifiez l'organisation qui y est affichée : l'identifiant y est rattaché et sera refusé partout ailleurs. Approuvez, puis copiez le jeton depuis la configuration temporaire et supprimez-la :

jq -r '.contexts[].token' "$ISOGRID_CONFIG_HOME/config.json"
rm -rf "$ISOGRID_CONFIG_HOME"

Utiliser un ISOGRID_CONFIG_HOME temporaire laisse votre propre connexion intacte. Sans cela, le nouvel identifiant remplacerait celui de ~/.isogrid/config.json pour cette adresse, et il vous faudrait isogrid login --force pour récupérer le vôtre.

Quelles portées

Portée Ce que le pipeline peut faire avec
application:create Créer l'application lors de la première exécution.
application:write-any Modifier, construire et déployer toutes les applications de l'organisation.
application:deploy-any Construire, déployer, démarrer et arrêter toutes les applications, sans modifier leurs paramètres.

Les modèles créent l'application si elle n'existe pas : ils ont donc besoin de application:create et de application:write-any. Une fois l'application créée, vous pouvez créer un identifiant plus restreint, sans application:create. application:deploy-any ne suffit que si le pipeline ne passe jamais de paramètres : pas de --env-file ni de changement de branche.

Une portée n'ajoute jamais rien à ce que vous détenez. Elle est croisée avec votre propre rôle et vos groupes à chaque requête : une portée que vous ne détenez pas est ignorée sans message, et si l'on réduit vos droits, l'identifiant du pipeline est réduit aussi. Certaines choses sont interdites à un identifiant, quoi qu'il demande : la facturation, la nomination d'administrateurs, et la création ou la révocation d'identifiants. Voir Rôles, groupes de permissions et accès.

Un identifiant dure 90 jours, sauf si vous avez choisi une durée plus courte sur la page d'approbation. Notez dans votre agenda de le remplacer avant.

2. Configurer le système de CI

Paramètre GitHub Actions GitLab CI/CD
ISOGRID_TOKEN Secret du dépôt Variable CI/CD, Masked (masquée)
ISOGRID_API_URL Variable du dépôt, par ex. https://api.isogrid.skyvault.pro Variable CI/CD
ISOGRID_APP Variable facultative. Par défaut, le nom du dépôt. Variable facultative. Par défaut, le nom du projet.
ISOGRID_CLUSTER Facultatif. Slug de la région ; nécessaire si plusieurs régions vous sont ouvertes. Idem
ISOGRID_TIER Facultatif. Type d'instance ; par défaut, le plus petit de la région. Idem
ISOGRID_NETWORK Facultatif. Slug du réseau ; par défaut, celui de votre organisation. Idem
ISOGRID_ENV_FILE Facultatif. Un fichier dotenv du dépôt à appliquer à chaque déploiement. Idem

Sur GitHub, ces réglages se trouvent sous Settings → Secrets and variables → Actions. Sur GitLab, sous Settings → CI/CD → Variables ; marquez aussi ISOGRID_TOKEN comme Protected (protégée) si seules les branches protégées déploient.

La CLI lit ISOGRID_TOKEN dans l'environnement et ne l'écrit jamais sur le disque : un pipeline qui met en cache le répertoire personnel ne transmet donc pas le jeton aux jobs suivants.

3. Ajouter le pipeline

La CLI écrit le fichier pour vous :

isogrid ci init github --app shop --cluster eu-west    # .github/workflows/isogrid-deploy.yml
isogrid ci init gitlab --app shop --cluster eu-west    # .gitlab-ci.yml
isogrid ci init github --output -                      # print it instead

Les valeurs que vous passez (--app, --cluster, --tier, --network, --env-file) sont écrites dans le fichier. Tout ce que vous omettez est lu dans les variables de CI ci-dessus. Un fichier existant n'est pas écrasé sans --force. Une fois le fichier écrit, la commande affiche les variables qu'il reste à définir.

Vous pouvez aussi télécharger les modèles sans la CLI :

curl -fsSL https://api.isogrid.skyvault.pro/api/v1/cli/ci-templates/github -o .github/workflows/isogrid-deploy.yml
curl -fsSL https://api.isogrid.skyvault.pro/api/v1/cli/ci-templates/gitlab -o .gitlab-ci.yml

Avant la première exécution

Le dépôt doit être connecté à votre organisation et ouvert à la personne dont le pipeline utilise l'identifiant. isogrid repos list l'affiche une fois que c'est le cas ; la colonne YOU doit inclure create-app et deploy. Voir Dépôts Git.

Le modèle GitHub Actions

# Deploy to ISOGrid from GitHub Actions.
#
# Save as .github/workflows/isogrid-deploy.yml (or generate it with
# `isogrid ci init github`). On every push to main, and whenever you run it by
# hand, it installs the ISOGrid CLI and either updates the application and
# deploys the pushed commit, or - the first time - creates the application.
#
# Set these in the repository (Settings -> Secrets and variables -> Actions):
#
#   Secrets
#     ISOGRID_TOKEN      A CLI credential. Mint a narrow one for CI: run
#                        `isogrid login --scope application:create,application:write-any --name github-ci`
#                        on your machine, then copy the token from
#                        ~/.isogrid/config.json. Never commit it.
#
#   Variables
#     ISOGRID_API_URL    Your platform's API, e.g. https://api.isogrid.skyvault.pro
#     ISOGRID_APP        Application name (default: this repository's name)
#     ISOGRID_CLUSTER    Region slug (optional when only one region is open to you)
#     ISOGRID_TIER       Instance type name (optional: the region's smallest)
#     ISOGRID_NETWORK    Network slug (optional: your organization's network)
#     ISOGRID_ENV_FILE   A dotenv file in the repository to apply (optional)
#
# The repository must already be connected to the organization through the
# ISOGrid GitHub app (`isogrid repos list` shows it once it is).

name: Deploy to ISOGrid

on:
  push:
    branches: [main]
  workflow_dispatch:

# One deployment of a branch at a time; a newer push waits rather than racing.
concurrency:
  group: isogrid-deploy-${{ github.ref }}
  cancel-in-progress: false

env:
  ISOGRID_API_URL: ${{ vars.ISOGRID_API_URL }}
  ISOGRID_TOKEN: ${{ secrets.ISOGRID_TOKEN }}
  ISOGRID_APP: ${{ vars.ISOGRID_APP || github.event.repository.name }}
  ISOGRID_CLUSTER: ${{ vars.ISOGRID_CLUSTER }}
  ISOGRID_TIER: ${{ vars.ISOGRID_TIER }}
  ISOGRID_NETWORK: ${{ vars.ISOGRID_NETWORK }}
  ISOGRID_ENV_FILE: ${{ vars.ISOGRID_ENV_FILE }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      # Only needed for ISOGRID_ENV_FILE: the platform clones the repository
      # itself to build it.
      - uses: actions/checkout@v4

      - name: Install the ISOGrid CLI
        run: |
          curl -fsSL "$ISOGRID_API_URL/api/v1/cli/install.sh" | ISOGRID_INSTALL_DIR="$HOME/.local/bin" sh
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"

      - name: Deploy
        run: |
          set -eu
          isogrid version

          ENV_ARGS=""
          if [ -n "$ISOGRID_ENV_FILE" ]; then
            ENV_ARGS="--env-file $ISOGRID_ENV_FILE"
          fi

          # --exact: an application called "api" must not be taken for "api-gateway".
          if isogrid apps get "$ISOGRID_APP" --exact --json > /dev/null 2>&1; then
            # Builds this exact commit and waits; the step fails if the build or
            # the deployment does.
            isogrid apps update "$ISOGRID_APP" \
              --branch "${{ github.ref_name }}" \
              $ENV_ARGS \
              --deploy --ref "${{ github.sha }}" --wait
          else
            isogrid apps create \
              --name "$ISOGRID_APP" \
              --provider github \
              --url "${{ github.server_url }}/${{ github.repository }}" \
              --branch "${{ github.ref_name }}" \
              --cluster "$ISOGRID_CLUSTER" \
              --tier "$ISOGRID_TIER" \
              --network "$ISOGRID_NETWORK" \
              $ENV_ARGS \
              --wait
          fi

Le modèle GitLab CI/CD

# Deploy to ISOGrid from GitLab CI/CD.
#
# Save as .gitlab-ci.yml at the root of the project (or generate it with
# `isogrid ci init gitlab`), or merge the job into the one you have. On every
# push to the default branch, and when run by hand, it installs the ISOGrid CLI
# and either updates the application and deploys the pushed commit, or - the
# first time - creates the application.
#
# Set these under Settings -> CI/CD -> Variables:
#
#   ISOGRID_TOKEN      A CLI credential; mark it Masked (and Protected if only
#                      protected branches deploy). Mint a narrow one for CI:
#                      `isogrid login --scope application:create,application:write-any --name gitlab-ci`
#                      on your machine, then copy the token from
#                      ~/.isogrid/config.json. Never commit it.
#   ISOGRID_API_URL    Your platform's API, e.g. https://api.isogrid.skyvault.pro
#
# and optionally override the defaults below the same way (project variables
# take precedence over the values written in this file):
#
#   ISOGRID_APP        Application name (default: the project's name)
#   ISOGRID_CLUSTER    Region slug (optional when only one region is open to you)
#   ISOGRID_TIER       Instance type name (optional: the region's smallest)
#   ISOGRID_NETWORK    Network slug (optional: your organization's network)
#   ISOGRID_ENV_FILE   A dotenv file in the repository to apply (optional)
#
# The project must already be connected to the organization through the
# ISOGrid GitLab integration (`isogrid repos list` shows it once it is).

stages:
  - deploy

variables:
  ISOGRID_APP: "$CI_PROJECT_NAME"
  ISOGRID_CLUSTER: ""
  ISOGRID_TIER: ""
  ISOGRID_NETWORK: ""
  ISOGRID_ENV_FILE: ""

isogrid-deploy:
  stage: deploy
  image: alpine:3.20
  # One deployment of a branch at a time.
  resource_group: isogrid-$CI_COMMIT_REF_SLUG
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_PIPELINE_SOURCE == "web"
  before_script:
    - apk add --no-cache curl
    - curl -fsSL "$ISOGRID_API_URL/api/v1/cli/install.sh" | ISOGRID_INSTALL_DIR=/usr/local/bin sh
    - isogrid version
  script:
    - |
      set -eu
      ENV_ARGS=""
      if [ -n "$ISOGRID_ENV_FILE" ]; then
        ENV_ARGS="--env-file $ISOGRID_ENV_FILE"
      fi

      # --exact: an application called "api" must not be taken for "api-gateway".
      if isogrid apps get "$ISOGRID_APP" --exact --json > /dev/null 2>&1; then
        # Builds this exact commit and waits; the job fails if the build or
        # the deployment does.
        isogrid apps update "$ISOGRID_APP" \
          --branch "$CI_COMMIT_REF_NAME" \
          $ENV_ARGS \
          --deploy --ref "$CI_COMMIT_SHA" --wait
      else
        isogrid apps create \
          --name "$ISOGRID_APP" \
          --provider gitlab \
          --url "$CI_PROJECT_URL" \
          --branch "$CI_COMMIT_REF_NAME" \
          --cluster "$ISOGRID_CLUSTER" \
          --tier "$ISOGRID_TIER" \
          --network "$ISOGRID_NETWORK" \
          $ENV_ARGS \
          --wait
      fi

--provider gitlab est indiqué explicitement : un GitLab auto-hébergé dont l'adresse ne contient pas « gitlab » fonctionne donc aussi, et $CI_PROJECT_URL transmet les groupes imbriqués tels quels.

Ce que fait le pipeline, étape par étape

Il installe la CLI depuis votre plateforme, en vérifiant sa somme de contrôle. La CLI et la plateforme sont toujours de la même génération, puisque l'une distribue l'autre.

Il vérifie si l'application existe avec apps get --exact. Sans --exact, une nouvelle application appelée api serait confondue avec une application api-gateway existante, et le pipeline déploierait la mauvaise.

Il la met à jour si elle existe. --branch fait suivre à l'application la branche qui a été poussée. --deploy --ref <commit> construit exactement ce commit, et non ce vers quoi pointe la branche au moment où la construction démarre : deux push rapprochés ne peuvent donc pas déployer le code l'un de l'autre. --env-file fusionne le fichier dans l'environnement de l'application ; les clés retirées du fichier restent sur l'application, sauf si vous ajoutez --replace-env.

Il la crée la première fois, à partir du dépôt qui a déclenché l'exécution. Des valeurs vides pour --cluster, --tier et --network signifient les valeurs par défaut.

Il attend. --wait ne rend la main qu'une fois la construction et le déploiement terminés, et se termine avec le code 1 si l'un des deux a échoué, y compris un déploiement ramené à la version précédente. C'est ce code de sortie qui fait échouer le job.

Il n'exécute qu'un déploiement à la fois par branche : concurrency sur GitHub, resource_group sur GitLab. Un second push attend le premier au lieu d'entrer en concurrence avec lui.

Écrire le vôtre

Le cœur des deux modèles tient en cinq lignes de shell : tout système de CI capable d'exécuter un shell peut s'en servir :

curl -fsSL "$ISOGRID_API_URL/api/v1/cli/install.sh" | sh
if isogrid apps get "$APP" --exact --json > /dev/null 2>&1; then
  isogrid apps update "$APP" --branch "$BRANCH" --deploy --ref "$COMMIT_SHA" --wait
else
  isogrid apps create --name "$APP" --provider github --url "$REPO_URL" --branch "$BRANCH" --wait
fi

Variante : construire l'image dans la CI

Si votre pipeline construit déjà une image de conteneur, laissez-le pousser l'image dans le registre de votre organisation et faites-la déployer par la plateforme, plutôt que de la construire une seconde fois.

Créez l'application une fois, à partir de l'image :

isogrid apps create --name worker --provider registry --url acme/worker:latest --cluster eu-west

Ensuite, chaque exécution pousse un nouveau tag et fait pointer l'application dessus :

docker build -t "$REGISTRY/$NAMESPACE/worker:$COMMIT_SHA" .
docker push "$REGISTRY/$NAMESPACE/worker:$COMMIT_SHA"
isogrid apps update worker --tag "$COMMIT_SHA" --deploy --wait

Le pipeline a besoin d'identifiants de registre en plus de ISOGRID_TOKEN : un compte robot créé depuis la page Images de la console, enregistré dans ISOGRID_REGISTRY_USER et dans un secret ou une variable masquée ISOGRID_REGISTRY_PASSWORD, avec ISOGRID_REGISTRY (l'hôte du registre) et ISOGRID_NAMESPACE (votre namespace). Les deux modèles contiennent ce job, en commentaire, à la fin du fichier. Les noms d'images doivent être en minuscules : définissez donc ISOGRID_APP explicitement si le nom du dépôt ne l'est pas.

Quand ça ne va pas

Pendant la mise en place, faites de isogrid whoami la première commande du job. Elle affiche à qui appartient l'identifiant, l'organisation à laquelle il est rattaché et les permissions qui lui seront réellement accordées.

« not signed in ». ISOGRID_TOKEN est vide dans le job. Sur GitHub, un secret n'est pas transmis aux workflows déclenchés depuis des forks. Sur GitLab, une variable Protected n'est pas transmise aux branches non protégées.

L'identifiant a expiré ou a été révoqué. Créez-en un nouveau et remplacez le secret. Rien d'autre n'a besoin de changer.

Une erreur de permission sur apps create. L'identifiant n'a pas application:create, ou vous ne la détenez pas vous-même. whoami indique lequel des deux.

Une erreur de permission sur apps update. L'identifiant ne peut pas modifier ou déployer cette application. Soit il n'a pas application:write-any, soit il n'a que application:deploy-any et la commande a passé un paramètre (--env-file, ou un --branch différent).

« is not a repository you have been given access to », ou « You do not have 'repo:deploy' on acme/api ». Le jeton agit en votre nom, et vous ne pouvez pas construire depuis ce dépôt. Quelqu'un d'autre a connecté le compte par lequel il arrive et ne vous a pas ouvert le dépôt. Demandez-lui, ou à un administrateur s'il est parti, d'exécuter isogrid repos grant acme/api --member you@example.com --preset repo-deployer. Si le dépôt est ouvert aux administrateurs et que vous en êtes un, créez l'identifiant en ajoutant aussi github:manage à ses portées : c'est sur cette permission que repose l'accès des administrateurs aux dépôts. Voir Dépôts Git.

« pinned to » une autre organisation. L'identifiant a été approuvé alors que le navigateur affichait une autre organisation, ou ISOGRID_ORGANIZATION ou --organization désigne une organisation à laquelle il n'est pas rattaché. Changez d'organisation dans la console et créez de nouveau l'identifiant.

« choose a region with --cluster ». Plusieurs régions vous sont ouvertes. Définissez ISOGRID_CLUSTER.

Le job a expiré mais le déploiement s'est terminé. --wait a abandonné après --timeout (20 minutes par défaut) ; le déploiement a continué. Augmentez-le avec --timeout 40m pour les constructions lentes.

Le job a échoué et le site est toujours en ligne. La nouvelle version n'est jamais devenue saine et la précédente a été remise en place. C'est bien un échec qui justifie de faire échouer le job. Consultez isogrid apps logs <app>.

Codes de sortie

Code Signification
0 Déployé, ou rien à changer.
1 Quelque chose a échoué : une erreur de l'API ou de permission, une construction échouée, un déploiement échoué ou annulé, un délai dépassé.
2 La commande a été mal appelée : une option inconnue, une valeur manquante, ou un delete sans --yes. Presque toujours une erreur dans le fichier du pipeline.