Aloja ejecutores de GitHub con grupos de trabajadores de Cloud Run

En este instructivo, se explica cómo usar ejecutores de GitHub autohospedados en grupos de trabajadores para ejecutar los flujos de trabajo definidos en tu repositorio de GitHub.

Implementarás un grupo de trabajadores de Cloud Run para controlar esta carga de trabajo y, de manera opcional, implementarás una Cloud Run Function para admitir el ajuste de escala del grupo de trabajadores.

Acerca de los ejecutores de GitHub autoalojados

En un flujo de trabajo de GitHub Actions, los ejecutores son las máquinas que ejecutan trabajos. Por ejemplo, un ejecutor puede clonar tu repositorio de forma local, instalar software de prueba y, luego, ejecutar comandos que evalúen tu código.

Puedes usar ejecutores autoalojados para ejecutar acciones de GitHub en instancias del grupo de trabajadores de Cloud Run. En este instructivo, se muestra cómo escalar automáticamente un grupo de ejecutores según la cantidad de trabajos en ejecución y no programados, incluso escalar el grupo a cero cuando no hay trabajos.

Recupera la muestra de código

A fin de recuperar la muestra de código para su uso, haz lo siguiente:

  1. Clona el repositorio de muestra en tu máquina local:

    git clone https://github.com/GoogleCloudPlatform/cloud-run-samples
    
  2. Ve al directorio que contiene el código de muestra de Cloud Run:

    cd cloud-run-samples/github-runner
    

Comprende el código principal

La muestra se implementa como un grupo de trabajadores y un escalador automático, como se describe a continuación.

Grupo de trabajadores

El grupo de trabajadores se configura con un Dockerfile basado en la imagen actions/runner creada por GitHub.

Toda la lógica se encuentra contenida en esta imagen, excepto un pequeño script auxiliar.

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

Esta secuencia de comandos de ayuda se ejecuta cuando se inicia el contenedor y se registra en el repositorio configurado como una instancia efímera con un token que crearás. La secuencia de comandos también define qué acciones se deben realizar cuando se reduce el tamaño del contenedor.

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

Escalador automático

El escalador automático es una función que aumenta la escala del grupo de trabajadores cuando hay un trabajo nuevo en la cola o la disminuye cuando se completa un trabajo. Utiliza la API de Cloud Run para verificar la cantidad actual de trabajadores en el grupo y ajusta ese valor según sea necesario.

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

En este instructivo, se usa una cuenta de servicio personalizada con los permisos mínimos requeridos para usar los recursos aprovisionados. Para configurar la cuenta de servicio, haz lo siguiente:

  1. Establece tu ID del proyecto en gcloud:

    gcloud config set project PROJECT_ID
    

    Reemplaza PROJECT_ID con el ID del proyecto.

  2. Crea una cuenta de servicio de Identity and Access Management nueva:

    gcloud iam service-accounts create gh-runners
    

  3. Otorga permisos a la cuenta de servicio para que actúe como una cuenta de servicio en tu proyecto:

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

    Reemplaza PROJECT_ID con el ID del proyecto.

Recupera información de GitHub

En la documentación de GitHub para agregar ejecutores autohospedados, se sugiere agregar ejecutores a través del sitio web de GitHub, que luego proporciona un token específico para la autenticación.

En este instructivo, se agregarán y quitarán ejecutores de forma dinámica, y se necesita un token estático de GitHub para hacerlo.

Para completar este instructivo, debes crear un token de GitHub con acceso para interactuar con el repositorio seleccionado.

Identifica el repositorio de GitHub

En este instructivo, la variable GITHUB_REPO representa el nombre del repositorio. Es la parte del nombre del repositorio de GitHub que se encuentra después del nombre de dominio, tanto para los repositorios de usuarios personales como para los de organizaciones.

Harás referencia al nombre del repositorio que aparece después del nombre de dominio para los repositorios que pertenecen a usuarios y a organizaciones.

En este instructivo:

  • Para https://github.com/myuser/myrepo, el GITHUB_REPO es myuser/myrepo.
  • Para https://github.com/mycompany/ourrepo, el GITHUB_REPO es mycompany/ourrepo.

Crea un token de acceso

Debes crear un token de acceso en GitHub y guardarlo de forma segura en Secret Manager:

  1. Asegúrate de haber accedido a tu cuenta de GitHub.
  2. Navega a la página Configuración > Configuración para desarrolladores > Tokens de acceso personal de GitHub.
  3. Haz clic en Generate new token y selecciona Generate new token (classic).
  4. Crea un token nuevo con el permiso "repo".
  5. Haz clic en Generate token.
  6. Copia el token generado.

Crea un valor secreto

Toma el token secreto que acabas de crear, almacénalo en Secret Manager y establece permisos de acceso.

  1. Crea el secreto en Secret Manager:

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

    Reemplaza GITHUB_TOKEN por el valor que copiaste de GitHub.

  2. Otorga acceso al secreto que acabas de crear:

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

Implementar grupo de trabajadores

Crea un grupo de trabajadores de Cloud Run para procesar acciones de GitHub. Este grupo usará una imagen basada en la imagen actions/runner creada por GitHub.

Configura el grupo de trabajadores de Cloud Run

  1. Navega al código de muestra del grupo de trabajadores:

    cd worker-pool-container
    
  2. Implementa el grupo de trabajadores:

    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. \
      --memory 2Gi \
      --cpu 4
    

    Reemplaza lo siguiente:

    • WORKER_POOL_NAME: El nombre del grupo de trabajadores
    • WORKER_POOL_LOCATION la región del grupo de trabajadores
    • GITHUB_REPO el nombre del repo de GitHub identificado
    • PROJECT_ID el Google Cloud ID del proyecto

    Si es la primera vez que usas implementaciones de fuentes de Cloud Run en este proyecto, se te pedirá que crees un repositorio predeterminado de Artifact Registry.

Usar un grupo de trabajadores

Ahora tienes una sola instancia en tu grupo de trabajadores, lista para aceptar trabajos de las acciones de GitHub.

Para verificar que completaste la configuración de tu ejecutor autohospedado, invoca una acción de GitHub en tu repositorio.

Para que tu acción use los ejecutores autoalojados, debes cambiar el trabajo de una acción de GitHub. En el trabajo, cambia el valor de runs-on a self-hosted.

Si tu repo aún no tiene ninguna acción, puedes seguir la Guía de inicio rápido de GitHub Actions.

Una vez que hayas configurado una acción para usar los ejecutores autohospedados, ejecútala.

Confirma que la acción se complete correctamente en la interfaz de GitHub.

Implementa el escalador automático de GitHub Runner

Implementaste un trabajador en tu grupo original, lo que permitirá procesar una acción a la vez. Según el uso que hagas de la CI, es posible que debas ajustar la escala de tu grupo para controlar una afluencia de trabajo.

Una vez que implementes el grupo de trabajadores con un ejecutor de GitHub activo, configura el escalador automático para aprovisionar instancias de trabajadores según el estado del trabajo en la cola de acciones.

Esta implementación escucha un evento workflow_job. Cuando se crea un trabajo de flujo de trabajo, se aumentará la cantidad de trabajadores del grupo y, una vez que se complete el trabajo, se reducirá nuevamente. No se escalará el grupo más allá de la cantidad máxima de instancias configuradas y se reducirá a cero cuando se completen todos los trabajos en ejecución.

Puedes adaptar este escalador automático según tus cargas de trabajo.

Crea un valor secreto de webhook

Para crear un valor secreto para el webhook, haz lo siguiente:

  1. Crea un secreto de Secret Manager que contenga un valor de cadena arbitrario.

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

    Reemplaza WEBHOOK_SECRET por un valor de cadena arbitrario.

  2. Otorga acceso al secreto a la cuenta de servicio del escalador automático:

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

Implementa la función para recibir solicitudes de webhook

Para implementar la función para recibir solicitudes de webhook, haz lo siguiente:

  1. Navega al código de muestra del webhook:

    cd ../autoscaler
    
  2. Implementa la Cloud Run Function:

    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. \
      --allow-unauthenticated
    

    Reemplaza lo siguiente:

    • GITHUB_REPO la parte del nombre de tu repositorio de GitHub que se encuentra después del nombre de dominio
    • WORKER_POOL_NAME: El nombre del grupo de trabajadores
    • WORKER_POOL_LOCATION la región del grupo de trabajadores
    • REPOSITORY_NAME el nombre del repositorio de GitHub
  3. Toma nota de la URL en la que se implementó tu servicio. Usarás este valor en un paso posterior.

  4. Otorga permisos a la cuenta de servicio para actualizar tu grupo de trabajadores:

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

    Reemplaza PROJECT_ID con el ID del proyecto.

Crea un webhook de GitHub

Para crear el webhook de GitHub, sigue estos pasos:

  1. Asegúrate de haber accedido a tu cuenta de GitHub.
  2. Navega a tu repositorio de GitHub.
  3. Haz clic en Configuración.
  4. En "Código y automatización", haz clic en Webhooks.
  5. Haz clic en Add webhook (Agregar webhook).
  6. Ingresa lo siguiente:

    1. En URL de carga útil, ingresa la URL de la función de Cloud Run que implementaste antes.

      La URL se verá de la siguiente manera: https://github-runner-autoscaler-PROJECTNUM.REGION.run.app, en la que PROJECTNUM es el identificador numérico único de tu proyecto y REGION es la región en la que implementaste el servicio.

    2. En Content type, selecciona application/json.

    3. En Secret, ingresa el valor de WEBHOOK_SECRET que creaste antes.

    4. En Verificación de SSL, selecciona Habilitar la verificación de SSL.

    5. En "¿Qué eventos deseas que activen este webhook?", selecciona Permítanme seleccionar eventos individuales.

    6. En la selección de eventos, elige Trabajos de flujo de trabajo. Anula la selección de cualquier otra opción.

    7. Haz clic en Add webhook (Agregar webhook).

Reduce la escala de tu grupo de trabajadores

El webhook ya está en su lugar, por lo que no es necesario que haya un trabajador persistente en el grupo. Esto también garantizará que no tengas trabajadores en ejecución cuando no haya trabajo que hacer, lo que reducirá los costos.

  • Ajusta tu grupo para que se escale a cero:

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

Usa tu ejecutor de ajuste de escala automático

Para verificar que el ejecutor del ajuste de escala automático funcione correctamente, ejecuta una acción que hayas configurado previamente en runs-on: self-hosted.

Puedes hacer un seguimiento del progreso de tus acciones de GitHub en la pestaña "Acciones" de tu repositorio.

Puedes verificar la ejecución de tu función de webhook y el grupo de trabajadores en las pestañas Registros de la función de Cloud Run y el grupo de trabajadores de Cloud Run, respectivamente.