Ospitare runner GitHub con i pool di worker Cloud Run

Questo tutorial ti guida nell'utilizzo di runner GitHub self-hosted nei pool di worker per eseguire i workflow definiti nel tuo repository GitHub.

Eseguirai il deployment di un pool di worker Cloud Run per gestire questo carico di lavoro e, facoltativamente, il deployment di una funzione Cloud Run per supportare lo scaling del pool di worker.

Informazioni sui runner GitHub self-hosted

In un flusso di lavoro GitHub Actions, i runner sono le macchine che eseguono i job. Ad esempio, un runner può clonare il tuo repository localmente, installare il software di test e poi eseguire comandi che valutano il tuo codice.

Puoi utilizzare i runner self-hosted per eseguire GitHub Actions sulle istanze del pool di worker Cloud Run. Questo tutorial mostra come scalare automaticamente un pool di runner in base al numero di job in esecuzione e non pianificati, scalando il pool fino a zero quando non ci sono job.

Recupera il esempio di codice

Per recuperare l'esempio di codice da utilizzare:

  1. Clona il repository di esempio sulla tua macchina locale:

    git clone https://github.com/GoogleCloudPlatform/cloud-run-samples
    
  2. Passa alla directory che contiene il codice campione di Cloud Run:

    cd cloud-run-samples/github-runner
    

Comprendere il codice principale

L'esempio è implementato come un pool di worker e un gestore della scalabilità automatica, descritti di seguito.

Pool di worker

Il pool di worker è configurato con un Dockerfile basato sull'immagine actions/runner creata da GitHub.

Tutta la logica è contenuta in questa immagine, ad eccezione di un piccolo script di supporto.

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"]

Questo script helper viene eseguito all'avvio del container, registrandosi nel repository configurato come istanza effimera, utilizzando un token che creerai. Lo script definisce anche le azioni da intraprendere quando il container viene ridimensionato.

# 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 $!

Gestore della scalabilità automatica

Il gestore della scalabilità automatica è una funzione che aumenta le dimensioni del pool di worker quando c'è un nuovo job nella coda o le riduce quando un job è completato. Utilizza l'API Cloud Run per controllare il numero attuale di worker nel pool e regola questo valore in base alle esigenze.

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."
    )

Configura IAM

Questo tutorial utilizza un account di servizio personalizzato con le autorizzazioni minime richieste per utilizzare le risorse di cui è stato eseguito il provisioning. Per configurare il account di servizio:

  1. Imposta l'ID progetto in gcloud:

    gcloud config set project PROJECT_ID
    

    Sostituisci PROJECT_ID con l'ID progetto.

  2. Crea un nuovo account di servizio Identity and Access Management:

    gcloud iam service-accounts create gh-runners
    

  3. Concedi all'account di servizio le autorizzazioni per fungere da service account nel tuo progetto:

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

    Sostituisci PROJECT_ID con l'ID progetto.

Recuperare informazioni da GitHub

La documentazione di GitHub per l'aggiunta di runner autogestiti suggerisce di aggiungere i runner tramite il sito web GitHub, che fornisce poi un token specifico da utilizzare per l'autenticazione.

Questo tutorial aggiungerà e rimuoverà dinamicamente i runner e richiede un token GitHub statico.

Per completare questo tutorial, devi creare un token GitHub con accesso per interagire con il repository selezionato.

Identificare il repository GitHub

In questo tutorial, la variabile GITHUB_REPO rappresenta il nome del repository. Questa è la parte del nome del repository GitHub che segue il nome di dominio, sia per i repository degli utenti personali sia per i repository dell'organizzazione.

Per i repository di proprietà dell'utente e dell'organizzazione, farai riferimento al nome del repository che segue il nome di dominio.

In questo tutorial:

  • Per https://github.com/myuser/myrepo, il valore GITHUB_REPO è myuser/myrepo.
  • Per https://github.com/mycompany/ourrepo, il valore GITHUB_REPO è mycompany/ourrepo.

Crea token di accesso

Devi creare un token di accesso su GitHub e salvarlo in modo sicuro in Secret Manager:

  1. Assicurati di aver eseguito l'accesso al tuo account GitHub.
  2. Vai alla pagina Settings (Impostazioni) > Developer Settings (Impostazioni sviluppatore) > Personal Access Tokens (Token di accesso personale) di GitHub.
  3. Fai clic su Genera nuovo token e seleziona Genera nuovo token (classico).
  4. Crea un nuovo token con l'ambito "repo".
  5. Fai clic su Genera token.
  6. Copia il token generato.

Crea valore secret

Prendi il token segreto appena creato, archivialo in Secret Manager e imposta le autorizzazioni di accesso.

  1. Crea il secret in Secret Manager:

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

    Sostituisci GITHUB_TOKEN con il valore che hai copiato da GitHub.

  2. Concedi l'accesso al secret appena creato:

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

Esegui il deployment del worker pool

Crea un pool di worker Cloud Run per elaborare le azioni di GitHub. Questo pool utilizzerà un'immagine basata sull'immagine actions/runner creata da GitHub.

Configura il pool di worker Cloud Run

  1. Vai al codice campione per il pool di worker:

    cd worker-pool-container
    
  2. Esegui il deployment del worker pool:

    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
    

    Sostituisci quanto segue:

    Se è la prima volta che utilizzi i deployment dell'origine Cloud Run in questo progetto, ti verrà chiesto di creare il repository Artifact Registry predefinito.

Utilizzo del pool di worker

Ora hai una singola istanza nel pool di worker, pronta ad accettare i job dalle azioni di GitHub.

Per verificare di aver completato la configurazione del runner self-hosted, richiama un'azione GitHub sul tuo repository.

Affinché l'azione utilizzi i runner self-hosted, devi modificare un job di un'azione di GitHub. Nel job, modifica il valore runs-on in self-hosted.

Se il tuo repository non ha ancora azioni, puoi seguire la guida di avvio rapido per GitHub Actions.

Dopo aver configurato un'azione per utilizzare i runner self-hosted, esegui l'azione.

Verifica che l'azione venga completata correttamente nell'interfaccia di GitHub.

Esegui il deployment di GitHub Runner Autoscaler

Hai eseguito il deployment di un worker nel pool originale, il che consentirà l'elaborazione di un'azione alla volta. A seconda dell'utilizzo di CI, potrebbe essere necessario scalare il pool per gestire un afflusso di lavoro da svolgere.

Dopo aver eseguito il deployment del pool di worker con un runner GitHub attivo, configura lo scalatore automatico per eseguire il provisioning delle istanze worker in base allo stato del job nella coda delle azioni.

Questa implementazione è in ascolto di un evento workflow_job. Quando viene creato il job del flusso di lavoro, il pool di worker viene scalato e, una volta completato il job, viene ridimensionato di nuovo. Non verrà scalato oltre il numero massimo di istanze configurato e verrà scalato a zero al termine di tutti i job in esecuzione.

Puoi adattare questo gestore della scalabilità automatica in base ai tuoi workload.

Crea il valore del secret webhook

Per creare un valore secret per il webhook:

  1. Crea un secret di Secret Manager contenente un valore stringa arbitrario.

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

    Sostituisci WEBHOOK_SECRET con un valore stringa arbitrario.

  2. Concedi l'accesso al secret al account di servizio dello strumento di scalabilità automatica:

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

Esegui il deployment della funzione per ricevere le richieste webhook

Per eseguire il deployment della funzione per la ricezione delle richieste webhook:

  1. Vai al codice campione per il webhook:

    cd ../autoscaler
    
  2. Esegui il deployment della funzione 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
    

    Sostituisci quanto segue:

    • GITHUB_REPO la parte del nome del repository GitHub dopo il nome di dominio
    • WORKER_POOL_NAME il nome del pool di worker
    • WORKER_POOL_LOCATION la regione del worker pool
    • REPOSITORY_NAME il nome del repository GitHub
  3. Prendi nota dell'URL in cui è stato eseguito il deployment del servizio. Utilizzerai questo valore in un passaggio successivo.

  4. Concedi le autorizzazioni all'account di servizio per aggiornare il pool di lavoratori:

    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
    

    Sostituisci PROJECT_ID con l'ID progetto.

Crea webhook GitHub

Per creare il webhook GitHub:

  1. Assicurati di aver eseguito l'accesso al tuo account GitHub.
  2. Vai al tuo repository GitHub.
  3. Fai clic su Impostazioni.
  4. Nella sezione "Codice e automazione", fai clic su Webhook.
  5. Fai clic su Aggiungi webhook.
  6. Inserisci quanto segue:

    1. In Payload URL (URL payload), inserisci l'URL della funzione Cloud Run che hai eseguito il deployment in precedenza.

      L'URL avrà il seguente aspetto: https://github-runner-autoscaler-PROJECTNUM.REGION.run.app, dove PROJECTNUM è l'identificatore numerico univoco del tuo progetto e REGION è la regione in cui hai eseguito il deployment del servizio.

    2. Per Tipo di contenuti, seleziona application/json.

    3. In Secret, inserisci il valore WEBHOOK_SECRET che hai creato in precedenza.

    4. Per SSL verification (Verifica SSL), seleziona Enable SSL verification (Attiva verifica SSL).

    5. In "Quali eventi vuoi che attivino questo webhook?", seleziona Consenti la selezione di singoli eventi.

    6. Nella selezione degli eventi, seleziona Job del workflow. Deseleziona qualsiasi altra opzione.

    7. Fai clic su Aggiungi webhook.

Ridurre le dimensioni del pool di worker

Il webhook è ora attivo, quindi non è necessario avere un worker persistente nel pool. In questo modo, inoltre, non avrai worker in esecuzione quando non c'è lavoro da svolgere, riducendo i costi.

  • Modifica il pool per scalare fino a zero:

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

Utilizzare il runner di scalabilità automatica

Per verificare che il runner di scalabilità automatica funzioni correttamente, esegui un'azione che hai configurato in precedenza per runs-on: self-hosted.

Puoi monitorare l'avanzamento delle tue GitHub Actions nella scheda "Azioni" del repository.

Puoi controllare l'esecuzione della funzione webhook e del pool di worker controllando la scheda Log della funzione Cloud Run e del pool di worker Cloud Run rispettivamente.