blog

Implémentation des prévisualisations de branche sur Google Cloud Platform

Rédigé par David Ferland | Jul 12, 2024 6:29:23 PM
 

Contenu

  1. Pourquoi les prévisualisations de branche sont essentielles dans le développement logiciel

  2. Implémentation des prévisualisations de branche via Github Actions avec Google Cloud Platform

  3. Nettoyage

Pourquoi les prévisualisations de branche sont essentielles dans le développement logiciel

Chaque développeur sait que l'écriture de code n'est qu'une partie du processus de développement logiciel. Une étape tout aussi importante est de tester les changements pour s'assurer qu'ils fonctionnent comme prévu et ne introduisent pas de bugs dans le système existant. C'est là que les prévisualisations de branche entrent en jeu. Elles fournissent un terrain de staging où les développeurs peuvent déployer, tester et prévisualiser des changements de manière isolée par rapport à la base de code principale, garantissant ainsi un environnement de production fluide et exempt de bugs.

Qu'est-ce que les prévisualisations de branche ?

Dans les environnements de développement où plusieurs contributeurs poussent fréquemment du nouveau code, suivre et tester ces mises à jour peut devenir assez complexe. Les prévisualisations de branche simplifient considérablement ce processus. Elles permettent de créer des environnements temporaires où le code de différentes branches peut être déployé, testé et prévisualisé avant d'être fusionné dans la branche principale.

Pourquoi les prévisualisations de branche sont-elles si utiles ?

1. Environnement de test isolé :

Les prévisualisations de branche permettent aux développeurs de tester leur code dans un environnement identique à la production. Cela isole le nouveau code de l'environnement de production stable. Cela signifie qu'il est possible d'identifier et de corriger les problèmes avant qu'ils n'affectent le système existant.

2. Meilleure collaboration :

Les membres de l'équipe peuvent partager l'URL unique de la prévisualisation de la branche avec leurs collègues et les parties prenantes. Cela permet des retours interactifs, favorisant une collaboration fluide que ce soit pour ajuster les composants de l'interface utilisateur, corriger des bugs ou améliorer les flux de travail.

3. Détection précoce des problèmes d'intégration :

Les prévisualisations de branche permettent aux développeurs de tester comment le nouveau code interagit avec la base de code existante, offrant ainsi la possibilité de rectifier tout problème d'intégration. En capturant ces problèmes potentiels tôt (avant la fusion), les équipes peuvent assurer un processus d'intégration plus fluide lorsque le nouveau code est finalement poussé dans la branche principale.

4. Assurance qualité :

En testant les nouvelles branches dans un environnement sécurisé, les équipes peuvent garantir la qualité de leur logiciel. Cette mesure supplémentaire aide les équipes à maintenir une base de code robuste et exempte d'erreurs.

5. Déploiement et intégration continus :

Avec le concept de DevOps et de CI/CD devenant des pratiques standard dans le développement logiciel, les prévisualisations de branche constituent une partie vitale de ce cycle de vie. La possibilité de prévisualiser les changements dans un environnement séparé mais identique facilite le processus CI/CD en garantissant que les changements ne vont pas casser l'environnement de production.

Prévisualisations de branche - Un avantage pour le développement logiciel

En résumé, les prévisualisations de branche ne sont pas seulement utiles mais cruciales dans le cycle de vie moderne du développement logiciel. En fournissant aux développeurs une plateforme où les changements proposés peuvent être testés de manière isolée, nous progressons vers un développement logiciel sans risque et de haute qualité.

Les prévisualisations de branche favorisent la collaboration, améliorent la qualité du code et optimisent le flux de travail efficace du CI/CD, en en faisant une fonctionnalité indispensable pour les équipes de développement. En tant que développeur, si vous n'utilisez pas encore les prévisualisations de branche, vous passez à côté d'un élément qui pourrait considérablement rationaliser votre processus de développement.

La mise en œuvre des prévisualisations de branche via Github Actions avec GCP

Google propose un excellent tutoriel pour implémenter des prévisualisations de branche avec son service Cloud Build. Vous pouvez suivre ce tutoriel et vérifier s'il répond à vos besoins. Cependant, après avoir parcouru ce tutoriel moi-même, j'ai réalisé que j'avais besoin de plus de personnalisation et qu'il serait préférable d'intégrer la fonctionnalité de déploiement de branche dans le flux de travail actuel de Github de mon organisation plutôt que d'utiliser un fichier cloudbuild.

Exigences :

  • Un compte de service Google Cloud avec ces autorisations (Plus d'informations sur les comptes de service ici) here

  • Cloud Run Admin

  • Artifact Registry Administrator

  • Un secret Github contenant les informations d'accès au compte de service au format JSON (Plus d'informations sur la création de ces informations d'accès ici here)

  • Un référentiel Artifact Registry (Plus d'informations sur la création de référentiels ici here)

  • Connaissance de la containerize de votre application avec Docker

Services que nous utiliserons pour le déploiement de branche :

  • Cloud Run

  • Artifact Registry

  • Cloud SQL (Bonus)

Variables :

  • LOCATION : Région où le service est situé, liste des régions disponibles ici here

  • IMAGE_NAME : Nom de l'image que vous avez construite avec Docker Compose tel que spécifié dans votre fichier docker-compose

  • PROJECT_ID : ID de votre projet GCP trouvé dans Cloud console

  • REPOSITORY : Nom de votre référentiel Artifact Registry

  • APP_NAME : Nom de votre application dans le référentiel

  • NAMESPACE : Soit votre nom d'utilisateur Github, soit votre organisation

  • USERNAME : Votre nom d'utilisateur Github

Ceci est le flux de développement final, chaque carré avec un losange à l'intérieur représente un déploiement.

Dans ce tutoriel, nous nous concentrerons sur la partie staging du flux de développement.

Configuration du Workflow

env:
  TAG: pr-$ # This is to identify revisions
  DOCKER_BUILDKIT: 1
  COMPOSE_DOCKER_CLI_BUILD: 1

on:
  pull_request:
    branches:
      - 'main'

J'ai divisé le workflow de prévisualisation de branche en deux tâches principales.

1. Configurer et pousser votre container vers l'Artifact Registry

La première tâche consiste à construire l'image Docker de votre application et à la pousser vers l'Artifact Registry.

Ici, le secret GCP_CREDENTIALS correspond à vos informations d'identification du compte de service.

configure_image:
    environment: staging
    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v3

    - id: auth
      uses: google-github-actions/auth@v1
      with:
        credentials_json: $ # Service account with enough permissions

    - name: Set up Cloud SDK # Necessary because we're using the gcp cli
      uses: google-github-actions/setup-gcloud@v1

    - name: Build Docker image
      run: docker compose -f docker-compose.yaml build

    - name: Tag and Push Container
      run: |
        gcloud auth configure-docker LOCATION-docker.pkg.dev
        docker tag IMAGE_NAME LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY/APP_NAME:$
        docker push LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/APP_NAME:$

2. Déployer sur Cloud Run

deploy:
    environment: staging
    runs-on: ubuntu-latest
    needs: configure_image

    steps:
      - id: auth
        uses: google-github-actions/auth@v1
        with:
          credentials_json: $ # Service account with enough permissions

      - name: Deploy to Cloud Run
        id: deploy
        uses: google-github-actions/deploy-cloudrun@v1
        with:
          service: SERVICE_NAME
          image: LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/APP_NAME:$
          region: LOCATION
          tag: $
          no_traffic: true # We dont want traffic on the created revision 

Voici le résultat dans l'onglet "Revisions" du service Cloud Run. Chaque révision est une PR (Pull Request). Vous pouvez prévisualiser vos branches en cliquant sur le tag de la révision. Par exemple, il s'agit d'un service Cloud Run de staging avec 4 PR ouvertes et en cours de révision et/ou de test.

Notez que chaque nouveau commit sur la branche en prévisualisation créera une nouvelle révision avec le commit le plus à jour et désactivera la révision du dernier commit, ce qui est très pratique.

Bonus : Prévisualiser votre branche directement dans Github

Parfois, il est utile pour les réviseurs de voir les changements visuellement. Pour faciliter cela, vous pouvez ajouter un lien pour ouvrir l'URL de votre révision directement depuis Github.

1. Tout d'abord, vous devez ajouter le custom cloud builder de prévisualisation de déploiement de GCP à votre package Github de l'association.

Cela nécessite un token avec la permission write:package et une certaine configuration de Docker. Voici les commandes de base nécessaires pour pousser les images de prévisualisation de déploiement de Google vers votre package Github :

git clone <https://github.com/GoogleCloudPlatform/python-docs-samples>
cd python-docs-samples/run/deployment-previews
docker build .
docker push ghcr.io/ORGANIZATION/IMAGE_NAME: latest
git clone https://github.com/GoogleCloudPlatfor/python-docs-samples

cd python-docs-samples/run/deployment-previews

docker build .
docker push ghcr.io/ORGANIZATION/IMAGE_NAME: latest

Plus d'informations sur ce processus peuvent être trouvées dans Github’s official docs et dans ce tutoriel tutorial   

Une fois l'image téléchargée avec succès sur Github, vous devriez maintenant avoir un nouveau package dans l'onglet "package".

2. Vous pouvez maintenant utiliser le package dans vos workflows Github

preview:
    needs: deploy
    environment: staging
    runs-on: ubuntu-latest
    container: # Using the ghcr.io image that we pushed
      image: ghcr.io/NAMESPACE/deployment-previews
      credentials:
        username: USERNAME # Username associated with github token
        password: $ # Repo github token

    steps:
    - id: auth
      uses: google-github-actions/auth@v1
      with:
        credentials_json: $
        create_credentials_file: true # Required for the check_status.py file 

    - name: Setup Cloud SDK
      uses: google-github-actions/setup-gcloud@v1.1.1
      with:
        project_id: PROJECT_ID # Project id from GCP

    - name: Link revision on pull request
      run: |
        python3 /app/check_status.py set --project-id PROJECT_ID \
          --region LOCATION \
          --service APP_NAME \
          --pull-request $ \
          --repo-name $ \
          --commit-sha $

Une fois que toutes vos vérifications sont passées, vous devriez voir cette nouvelle bannière en cliquant sur le bouton "Show all checks" vers la fin de la page PR.

En cliquant sur le lien "Details", vous devriez être redirigé vers l'URL de prévisualisation de votre branche. Gardez à l'esprit que toutes les révisions utilisent la même base de données.

Bonus 2 : Link une nouvelle database Cloud SQL à votre révision

Parfois, vous ne voulez pas que votre branche de fonctionnalité impacte directement la database de staging. Par exemple, si vous modifiez ou ajoutez une nouvelle entité et que vous ne voulez pas ajouter de mauvaises données par erreur dans la database de staging. Pour éviter cela, j'ai créé quelques étapes pour cloner la database de staging lorsque les développeurs le demandent en ajoutant le mot-clé "needs_db" dans le titre de la PR.

1. Ajoutez cette étape à la tâche configure_image

- name: Clone Cloud SQL instance
      id: clone_sql_instance
      if: contains(github.event.pull_request.title, 'needs_db')
      run: |
        INSTANCE_NAME="instance_name"
        CLONE_NAME="instance_name-pr-$"
        gcloud sql instances clone $INSTANCE_NAME $CLONE_NAME
      continue-on-error: true

Cette étape clone l'instance Cloud SQL du projet nommée "instance_name" si le titre de la PR contient le mot "needs_db". Pour moi, c'est la solution la plus simple et si les développeurs oublient d'écrire le mot-clé dans leur PR initiale, ils peuvent toujours revenir et modifier le nom pour que l'instance Cloud SQL soit clonée. L'instance clonée est alors nommée avec le numéro de PR pour trouver facilement quelle database est liée à quelle PR.

2. Si nécessaire, mettez à jour vos fichiers d'environnement pour qu'ils pointent vers l'adresse IP de votre nouvelle database

Vous pouvez obtenir l'adresse IP de votre nouvelle instance avec cette étape. La condition if se réfère à l'étape Clone Cloud SQL instance, c'est pourquoi nous devons lui donner un identifiant. Cette étape va également dans la tâche configure_image.

- name: Update database url
      if: contains(github.event.pull_request.title, 'needs_db') && steps.clone_sql_instance.conclusion == 'success'
      run: |
        DB_PRIVATE_IP=$(gcloud sql instances describe instance_name-pr-$ --format='get(ipAddresses[2].ipAddress)')
				# Update .env files here

Now you should have a new database instance linked to your revision, this new database will be in your SQL service in GCP.

Nettoyage

Maintenant que vous avez beaucoup de révisions et d'instances de database, vous devez les nettoyer après que votre PR ait été approuvée et fusionnée dans la branche principale.

Je fais généralement mon nettoyage dans un fichier de workflow différent qui est exécuté lors du push sur la branche principale.

env:
  DOCKER_BUILDKIT: 1
  COMPOSE_DOCKER_CLI_BUILD: 1
  # This is to speed up docker compose

on:
  push:
    branches:
      - 'main'

1. Nettoyez les tags en utilisant le fichier check_status fourni par l'image de prévisualisation de déploiement de Google.

Si vous n'avez pas encore ajouté l'image à votre registre de packages de l'association, vous pouvez suivre le début de l'étape bonus "Prévisualiser votre branche directement dans Github" que nous avons couverte plus tôt dans ce blog.

clean_up_tags:
    needs: deploy
    environment: staging
    runs-on: ubuntu-latest
    container: # Using the 
      image: ghcr.io/ASSOCIATION_NAME/deployment-previews
      credentials:
        username: USERNAME #Username associated with github token
        password: $ #Repo github token

    steps:
      - id: auth
        uses: google-github-actions/auth@v1
        with:
          credentials_json: $
          create_credentials_file: true #required for the check_status.py file

      - name: Setup Cloud SDK
        uses: google-github-actions/setup-gcloud@v1.1.1
        with:
          project_id: PROJECT_ID # Project id from GCP

      - name: Clean up old tag
        run: |
          python3 /app/check_status.py cleanup --project-id PROJECT_ID \
            --region LOCATION \
            --service $

Nettoyer les tags est important car cela désactivera les prévisualisations de branche qui ont été fusionnées, rendant ainsi le script de nettoyage de l'étape suivante plus simple.

2. Supprimez les anciennes images et révisions fusionnées avec un script personnalisé

Au cours de mes recherches, je n'ai pas trouvé de moyen facile de supprimer les révisions inutilisées et les images associées dans l'Artifact Registry, alors j'ai créé mon propre script de nettoyage en bash.

clean_up_revisions_and_artifacts:
      needs: clean_up_tags
      environment: staging
      runs-on: ubuntu-latest

      steps:
        - name: Checkout code
          uses: actions/checkout@v3

        - name: Login
          uses: google-github-actions/auth@v1
          with:
            credentials_json: $

        - name: Fix permissions
          run: chmod 700 ./gcloud_scripts/clean_up.sh

        - name: Run clean up script
          run: ./gcloud_scripts/clean_up.sh -r LOCATION -s APP_NAME -p PROJECT_ID
          shell: bash

Le script :

#!/bin/bash

while getopts r:s:p: flag # Flag handler
do
    case "${flag}" in
        r) region=${OPTARG};;
        s) service=${OPTARG};;
        p) project_id=${OPTARG};;
        *)
    esac
done

# Clean revisions
for rev in $(gcloud run revisions list --region="$region" --service="$service" --filter="status.conditions.type:Active AND status.conditions.status:'False'" --format='value(metadata.name)') # Fetch all the disabled revisions 
do
  echo "Deleting revision : $rev"
  gcloud run revisions delete "$rev" --region="$region" --quiet # Quiet flag is necessary
done

# Clean artifact registry
active_images_digest=()
# Fetch all the revisions that are not retired
for active_revisions in $(gcloud run revisions list --region="$region" --service="$service" --filter="-status.conditions.reason:Retired" --format='value(metadata.name)')
# Make and Array of the image digest of active revisions
do
  active_image=$(gcloud run revisions describe "$active_revisions" --region="$region" --format='value(image)')
  active_image=${active_image:89:160}
  echo "Active image : $active_image"
  active_images_digest+=("$active_image")
done

# List images in Artifact Registry
for digest in $(gcloud artifacts docker images list "$region"-docker.pkg.dev/"$project_id"/shopify-backend/"$service" --format='value(version)')
do
  # Delete images that are not in the Array of active images digest
  if [[ ! " ${active_images_digest[*]} " =~ ${digest} ]]; then
    echo "Digest to delete : $digest"
    gcloud artifacts docker images delete "$region"-docker.pkg.dev/"$project_id"/shopify-backend/"$service"@"$digest" --delete-tags
  fi
done

Bonus : Nettoyage de la database

Si vous avez décidé de mettre en œuvre la duplication de l'instance de database dans votre workflow, voici l'étape pour la supprimer une fois que la PR est fusionnée dans la branche principale. Elle inclut quelques astuces pour trouver le titre de la PR et le numéro du commit actuel sur lequel le workflow est exécuté. Nous avons besoin du titre de la PR pour vérifier si le commit fait partie d'une PR qui avait le mot-clé 'needs_db'.

teardown_pr_db:
    environment: staging
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Login
        uses: google-github-actions/auth@v1
        with:
          credentials_json: $

      - name: Find PR title
        run: |
          PR_TITLE=$(gh pr list --search $ --state merged --json title --jq '.[0].title')
          echo "PR_TITLE<<EOF" >> $GITHUB_ENV
          echo $PR_TITLE >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV

      - name: Find PR number
        if: contains(env.PR_TITLE, 'needs_db')
        run: |
          PR_NUMBER=$(gh pr list --search $ --state merged --json number --jq '.[0].number')
          echo "PR_NUMBER<<EOF" >> $GITHUB_ENV
          echo $PR_NUMBER >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV

      - name: Remove deletion protection
        if: contains(env.PR_TITLE, 'needs_db')
        run: gcloud sql instances patch instance_name-pr-$ --no-deletion-protection

      - name: Delete database
        if: contains(env.PR_TITLE, 'needs_db')
        run: gcloud sql instances delete instance_name-api-pr-$

Done!

Voilà, vous avez maintenant mis en place un très bon flux de développement dans vos projets personnels ou organisationnels. Je sais que cela fait beaucoup à assimiler, alors n'hésitez pas à laisser des commentaires sur ce blog pour aider à l'améliorer ou si vous avez des questions !