Héberger des exécuteurs GitHub avec des pools de nœuds de calcul Cloud Run

Ce tutoriel vous explique comment utiliser des runners GitHub auto-hébergés sur des pools de nœuds de calcul pour exécuter les workflows définis dans votre dépôt GitHub.

Vous allez déployer un pool de nœuds de calcul Cloud Run pour gérer cette charge de travail, et éventuellement déployer une fonction Cloud Run pour prendre en charge la mise à l'échelle du pool de nœuds de calcul.

À propos des exécuteurs GitHub auto-hébergés

Dans un workflow GitHub Actions, les runners sont les machines qui exécutent les tâches. Par exemple, un runner peut cloner votre dépôt localement, installer un logiciel de test, puis exécuter des commandes qui évaluent votre code.

Vous pouvez utiliser des exécuteurs auto-hébergés pour exécuter des actions GitHub sur des instances de pool de nœuds de calcul Cloud Run. Ce tutoriel vous explique comment mettre automatiquement à l'échelle un pool de runners en fonction du nombre de tâches en cours d'exécution et non planifiées. Il vous montre également comment réduire le pool à zéro lorsqu'il n'y a aucune tâche.

Récupérer l'exemple de code

Pour récupérer l’exemple de code à utiliser, procédez comme suit :

  1. Clonez le dépôt de l'exemple sur votre ordinateur local :

    git clone https://github.com/GoogleCloudPlatform/cloud-run-samples
    
  2. Accédez au répertoire contenant l'exemple de code Cloud Run :

    cd cloud-run-samples/github-runner
    

Comprendre le code principal

L'exemple est mis en œuvre avec un pool de nœuds de calcul et un autoscaler, comme décrit ci-dessous.

Pool de nœuds de calcul

Le pool de nœuds de calcul est configuré avec un fichier Dockerfile basé sur l'image actions/runner créée par GitHub.

Toute la logique est contenue dans cette image, à l'exception d'un petit script d'assistance.

FROM ghcr.io/actions/actions-runner:2.329.0

# Add scripts with right permissions.
USER root
# hadolint ignore=DL3045
COPY start.sh start.sh
RUN chmod +x start.sh

# Add start entrypoint with right permissions.
USER runner
ENTRYPOINT ["./start.sh"]

Ce script d'assistance s'exécute au démarrage du conteneur. Il s'enregistre dans le dépôt configuré en tant qu'instance éphémère à l'aide d'un jeton que vous allez créer. Le script définit également les actions à effectuer lorsque le conteneur est réduit.

# Configure the current runner instance with URL, token and name.
mkdir /home/docker/actions-runner && cd /home/docker/actions-runner
echo "GitHub Repo: ${GITHUB_REPO_URL} for ${RUNNER_PREFIX}-${RUNNER_SUFFIX}"
./config.sh --unattended --url ${GITHUB_REPO_URL} --pat ${GH_TOKEN} --name ${RUNNER_NAME}

# Function to cleanup and remove runner from Github.
cleanup() {
   echo "Removing runner..."
   ./config.sh remove --unattended --pat ${GH_TOKEN}
}

# Trap signals.
trap 'cleanup; exit 130' INT
trap 'cleanup; exit 143' TERM

# Run the runner.
./run.sh & wait $!

Autoscaler

L'autoscaler est une fonction qui augmente la taille du pool de nœuds de calcul lorsqu'une nouvelle tâche est ajoutée à la file d'attente ou la diminue lorsqu'une tâche est terminée. Il utilise l'API Cloud Run pour vérifier le nombre actuel de nœuds de calcul dans le pool et ajuste cette valeur si nécessaire.

try:
    current_instance_count = get_current_worker_pool_instance_count()
except ValueError as e:
    return f"Could not retrieve instance count: {e}", 500

# Scale Up: If a job is queued and we have available capacity
if action == "queued" and job_status == "queued":
    print(f"Job '{job_name}' is queued.")

    if current_instance_count < MAX_RUNNERS:
        new_instance_count = current_instance_count + 1
        try:
            update_runner_instance_count(new_instance_count)
            print(f"Successfully scaled up to {new_instance_count} instances.")
        except ValueError as e:
            return f"Error scaling up instances: {e}", 500
    else:
        print(f"Max runners ({MAX_RUNNERS}) reached.")

# Scale Down: If a job is completed, check to see if there are any more pending
# or in progress jobs and scale accordingly.
elif action == "completed" and job_status == "completed":
    print(f"Job '{job_name}' completed.")

    current_queued_actions, current_running_actions = get_current_actions()
    current_actions = current_queued_actions + current_running_actions

    if current_queued_actions >= 1:
        print(
            f"GitHub says {current_queued_actions} are still pending."
            f"Won't change scaling ({current_instance_count})."
        )
    elif current_queued_actions == 0 and current_running_actions >= 1:
        print(
            f"GitHub says no queued actions, but {current_running_actions} running actions."
            f"Won't change scaling ({current_instance_count})."
        )
    elif current_actions == 0:
        print(f"GitHub says no pending actions. Scaling to zero.")
        update_runner_instance_count(0)
        print(f"Successfully scaled down to zero.")
    else:
        print(
            f"Detected an unhandled state: {current_queued_actions=}, {current_running_actions=}"
        )
else:
    print(
        f"Workflow job event for '{job_name}' with action '{action}' and "
        f"status '{job_status}' did not trigger a scaling action."
    )

Configurer IAM

Ce tutoriel utilise un compte de service personnalisé avec les autorisations minimales requises pour utiliser les ressources provisionnées. Pour configurer le compte de service, procédez comme suit :

  1. Définissez votre ID de projet dans gcloud :

    gcloud config set project PROJECT_ID
    

    Remplacez PROJECT_ID par l'ID du projet.

  2. Créez un compte de service Identity and Access Management :

    gcloud iam service-accounts create gh-runners
    

  3. Accordez au compte de service les autorisations nécessaires pour agir en tant que compte de service sur votre projet :

    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member "serviceAccount:gh-runners@PROJECT_ID.iam.gserviceaccount.com" \
      --role=roles/iam.serviceAccountUser
    

    Remplacez PROJECT_ID par l'ID du projet.

Récupérer des informations GitHub

La documentation GitHub sur l'ajout de runners auto-hébergés suggère d'ajouter des runners via le site Web GitHub, qui fournit ensuite un jeton spécifique à utiliser pour l'authentification.

Ce tutoriel ajoutera et supprimera des runners de manière dynamique. Il a donc besoin d'un jeton GitHub statique.

Pour suivre ce tutoriel, vous devez créer un jeton GitHub permettant d'interagir avec le dépôt sélectionné.

Identifier le dépôt GitHub

Dans ce tutoriel, la variable GITHUB_REPO représente le nom du dépôt. Il s'agit de la partie du nom du dépôt GitHub qui suit le nom de domaine, à la fois pour les dépôts d'utilisateurs personnels et les dépôts d'organisation.

Vous ferez référence au nom du dépôt qui suit le nom de domaine pour les dépôts appartenant à des utilisateurs et à des organisations.

Ce tutoriel vous guidera à travers les étapes suivantes :

  • Pour https://github.com/myuser/myrepo, le GITHUB_REPO est myuser/myrepo.
  • Pour https://github.com/mycompany/ourrepo, le GITHUB_REPO est mycompany/ourrepo.

Créer un jeton d'accès

Vous devez créer un jeton d'accès sur GitHub et l'enregistrer de manière sécurisée dans Secret Manager :

  1. Assurez-vous d'être connecté à votre compte GitHub.
  2. Accédez à la page Settings > Developer Settings > Personal Access Tokens (Paramètres > Paramètres du développeur > Jetons d'accès personnels) de GitHub.
  3. Cliquez sur Générer un nouveau jeton, puis sélectionnez Générer un nouveau jeton (classique).
  4. Créez un jeton avec le champ d'application "repo".
  5. Cliquez sur Generate token (Générer un jeton).
  6. Copiez le jeton généré.

Créer une valeur secrète

Prenez le jeton secret que vous venez de créer, stockez-le dans Secret Manager et définissez les autorisations d'accès.

  1. Créez le secret dans Secret Manager :

    echo -n "GITHUB_TOKEN" | gcloud secrets create github_runner_token --data-file=-
    

    Remplacez GITHUB_TOKEN par la valeur que vous avez copiée à partir de GitHub.

  2. Accordez l'accès au secret que vous venez de créer :

    gcloud secrets add-iam-policy-binding github_runner_token \
      --member "serviceAccount:gh-runners@PROJECT_ID.iam.gserviceaccount.com" \
      --role "roles/secretmanager.secretAccessor"
    

Déployer le pool de nœuds de calcul

Créez un pool de nœuds de calcul Cloud Run pour traiter les actions GitHub. Ce pool utilisera une image basée sur l'image actions/runner créée par GitHub.

Configurer un pool de nœuds de calcul Cloud Run

  1. Accédez à l'exemple de code du pool de nœuds de calcul :

    cd worker-pool-container
    
  2. Déployez le pool de nœuds de calcul :

    gcloud beta run worker-pools deploy WORKER_POOL_NAME \
      --region WORKER_POOL_LOCATION \
      --source . \
      --scaling 1 \
      --set-env-vars GITHUB_REPO=GITHUB_REPO \
      --set-secrets GITHUB_TOKEN=github_runner_token:latest \
      --service-account gh-runners@PROJECT_ID.iam.gserviceaccount.com \
      --memory 2Gi \
      --cpu 4
    

    Remplacez les éléments suivants :

    • WORKER_POOL_NAME : nom du pool de nœuds de calcul.
    • WORKER_POOL_LOCATION : région du pool de nœuds de calcul
    • GITHUB_REPO le nom du dépôt GitHub identifié
    • PROJECT_ID l'ID du projet Google Cloud

    Si vous utilisez les déploiements de source Cloud Run pour la première fois dans ce projet, vous serez invité à créer un dépôt Artifact Registry par défaut.

Utiliser un pool de nœuds de calcul

Vous disposez désormais d'une seule instance dans votre pool de workers, prête à accepter les jobs des actions GitHub.

Pour vérifier que vous avez terminé la configuration de votre exécuteur auto-hébergé, appelez une action GitHub sur votre dépôt.

Pour que votre action utilise vos exécuteurs auto-hébergés, vous devez modifier le job d'une action GitHub. Dans le job, remplacez la valeur runs-on par self-hosted.

Si votre dépôt ne comporte pas encore d'actions, vous pouvez suivre le guide de démarrage rapide pour GitHub Actions.

Une fois que vous avez configuré une action pour utiliser les runners autohébergés, exécutez-la.

Vérifiez que l'action s'est bien déroulée dans l'interface GitHub.

Déployer l'autoscaler GitHub Runner

Vous avez déployé un nœud de calcul dans votre pool d'origine, ce qui permet de traiter une action à la fois. En fonction de votre utilisation de l'intégration continue, vous devrez peut-être mettre à l'échelle votre pool pour gérer un afflux de tâches à effectuer.

Une fois que vous avez déployé le pool de nœuds de calcul avec un runner GitHub actif, configurez le scaler automatique pour qu'il provisionne des instances de nœud de calcul en fonction de l'état des tâches dans la file d'attente des actions.

Cette implémentation écoute un événement workflow_job. Lorsqu'un job de workflow est créé, le pool de nœuds de calcul est mis à l'échelle, puis remis à l'échelle une fois le job terminé. Il ne mettra pas à l'échelle le pool au-delà du nombre maximal d'instances configuré et le mettra à zéro une fois que tous les jobs en cours seront terminés.

Vous pouvez adapter cet autoscaler en fonction de vos charges de travail.

Créer une valeur secrète de webhook

Pour créer une valeur secrète pour le webhook, procédez comme suit :

  1. Créez un secret Secret Manager contenant une valeur de chaîne arbitraire.

    echo -n "WEBHOOK_SECRET" | gcloud secrets create github_webhook_secret --data-file=-
    

    Remplacez WEBHOOK_SECRET par une valeur de chaîne arbitraire.

  2. Accordez l'accès au secret au compte de service de l'autoscaler :

    gcloud secrets add-iam-policy-binding github_webhook_secret \
      --member "serviceAccount:gh-runners@PROJECT_ID.iam.gserviceaccount.com" \
      --role "roles/secretmanager.secretAccessor"
    

Déployer la fonction pour recevoir les requêtes webhook

Pour déployer la fonction de réception des requêtes de webhook, procédez comme suit :

  1. Accédez à l'exemple de code du webhook :

    cd ../autoscaler
    
  2. Déployez la fonction Cloud Run :

    gcloud run deploy github-runner-autoscaler \
      --function github_webhook_handler \
      --region WORKER_POOL_LOCATION \
      --source . \
      --set-env-vars GITHUB_REPO=GITHUB_REPO \
      --set-env-vars WORKER_POOL_NAME=WORKER_POOL_NAME \
      --set-env-vars WORKER_POOL_LOCATION=WORKER_POOL_LOCATION \
      --set-env-vars MAX_RUNNERS=5 \
      --set-secrets GITHUB_TOKEN=github_runner_token:latest \
      --set-secrets WEBHOOK_SECRET=github_webhook_secret:latest \
      --service-account gh-runners@PROJECT_ID.iam.gserviceaccount.com \
      --allow-unauthenticated
    

    Remplacez les éléments suivants :

    • GITHUB_REPO : partie du nom de votre dépôt GitHub après le nom de domaine
    • WORKER_POOL_NAME : nom du pool de nœuds de calcul.
    • WORKER_POOL_LOCATION : région du pool de nœuds de calcul
    • REPOSITORY_NAME : nom du dépôt GitHub
  3. Notez l'URL à laquelle votre service a été déployé. Vous utiliserez cette valeur dans une prochaine étape.

  4. Accordez au compte de service les autorisations nécessaires pour mettre à jour votre pool de nœuds de calcul :

    gcloud alpha run worker-pools add-iam-policy-binding WORKER_POOL_NAME \
      --member "serviceAccount:gh-runners@PROJECT_ID.iam.gserviceaccount.com" \
      --role=roles/run.developer
    

    Remplacez PROJECT_ID par l'ID du projet.

Créer un webhook GitHub

Pour créer le webhook GitHub, procédez comme suit :

  1. Assurez-vous d'être connecté à votre compte GitHub.
  2. Accédez à votre dépôt GitHub.
  3. Cliquez sur Paramètres.
  4. Sous "Code and automation" (Code et automatisation), cliquez sur Webhooks.
  5. Cliquez sur Add webhook (Ajouter un Webhook).
  6. Saisissez ce qui suit :

    1. Dans URL de la charge utile, saisissez l'URL de la fonction Cloud Run que vous avez déployée précédemment.

      L'URL se présente comme suit : https://github-runner-autoscaler-PROJECTNUM.REGION.run.app, où PROJECTNUM est l'identifiant numérique unique de votre projet et REGION est la région dans laquelle vous avez déployé le service.

    2. Pour Type de contenu, sélectionnez application/json.

    3. Dans le champ Secret, saisissez la valeur WEBHOOK_SECRET que vous avez créée précédemment.

    4. Pour Vérification SSL, sélectionnez Activer la vérification SSL.

    5. Pour "Quels événements souhaitez-vous déclencher ce webhook ?", sélectionnez Me laisser sélectionner des événements individuels.

    6. Dans la sélection d'événements, sélectionnez Jobs de workflow. Désélectionnez toute autre option.

    7. Cliquez sur Add webhook (Ajouter un Webhook).

Réduire la taille de votre pool de nœuds de calcul

Le webhook est désormais en place. Vous n'avez donc pas besoin d'avoir un worker persistant dans le pool. Cela vous permettra également de ne pas avoir de workers en cours d'exécution lorsqu'il n'y a pas de travail à effectuer, ce qui réduira les coûts.

  • Ajustez votre pool pour qu'il puisse être mis à l'échelle à zéro :

    gcloud beta run worker-pools update WORKER_POOL_NAME \
      --region WORKER_POOL_LOCATION \
      --scaling 0
    

Utiliser votre runner Autoscaling

Pour vérifier que votre runner Autoscaling fonctionne correctement, exécutez une action que vous avez précédemment configurée sur runs-on: self-hosted.

Vous pouvez suivre la progression de vos actions GitHub dans l'onglet "Actions" de votre dépôt.

Vous pouvez vérifier l'exécution de votre fonction de webhook et de votre pool de workers en consultant l'onglet "Journaux" de la fonction Cloud Run et du pool de workers Cloud Run, respectivement.