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 función de Cloud Run 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.

Objetivos

En este instructivo, podrás:

  • Implementa un grupo de trabajadores de Cloud Run en Cloud Run.
  • Implementa una función de Cloud Run para admitir el ajuste de escala del grupo de trabajadores.
  • Crea secretos de Secret Manager para almacenar de forma segura tokens y secretos.
  • Implementa un ejecutor de GitHub autohospedado para admitir un repositorio de GitHub.

Costos

En este documento, usarás los siguientes componentes facturables de Google Cloud:

Para obtener una estimación de costos en función del uso previsto, usa la calculadora de precios.

Es posible que los usuarios nuevos de Google Cloud cumplan con los requisitos para acceder a una prueba gratuita.

Antes de comenzar

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  5. Verify that billing is enabled for your Google Cloud project.

  6. Enable the Cloud Run, Secret Manager, Artifact Registry, and Cloud Build APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  7. Roles obligatorios

    Si quieres obtener los permisos que necesitas para completar el instructivo, pídele a tu administrador que te otorgue los siguientes roles de IAM en tu proyecto:

    Para obtener más información sobre cómo otorgar roles, consulta Administra el acceso a proyectos, carpetas y organizaciones.

    También puedes obtener los permisos necesarios mediante roles personalizados o cualquier otro rol predefinido.

    Necesitas permiso para editar la configuración de un repositorio de GitHub y configurar los ejecutores autoalojados. El repositorio puede ser propiedad del usuario o de una organización.

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 central

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.iam.gserviceaccount.com" \
      --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 autoalojados, se sugiere agregar ejecutores a través del sitio web de GitHub, que luego proporciona un token específico para usar en 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.iam.gserviceaccount.com" \
      --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.iam.gserviceaccount.com \
      --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 escalar tu grupo para controlar una afluencia de trabajo.

Una vez que implementes el grupo de trabajadores con un ejecutor de GitHub activo, configura el autoescalador para que aprovisione 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.iam.gserviceaccount.com" \
      --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.iam.gserviceaccount.com \
      --allow-unauthenticated
    

    Reemplaza lo siguiente:

    • GITHUB_REPO la parte del nombre de tu repositorio de GitHub 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.iam.gserviceaccount.com" \
      --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.

Realiza una limpieza

Para evitar cargos adicionales en tu cuenta de Google Cloud , borra todos los recursos que implementaste con este instructivo.

Borra el proyecto

Si creaste un proyecto nuevo para este instructivo, bórralo. Si usaste un proyecto existente y necesitas conservarlo sin los cambios que agregaste en este instructivo, borra los recursos que creaste para el instructivo.

La manera más fácil de eliminar la facturación es borrar el proyecto que creaste para el instructivo.

Para borrar el proyecto, sigue estos pasos:

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Elimina recursos de instructivos

  1. Borra el servicio de Cloud Run que implementaste en este instructivo. Los servicios de Cloud Run no generan costos hasta que reciben solicitudes.

    Para borrar tu servicio de Cloud Run, ejecuta el siguiente comando:

    gcloud run services delete SERVICE-NAME

    SERVICE-NAME por el nombre del servicio

    También puedes borrar los servicios de Cloud Run desde la consola deGoogle Cloud .

  2. Quita la configuración de región predeterminada de gcloud que agregaste durante la configuración del instructivo:

     gcloud config unset run/region
    
  3. Quita la configuración del proyecto:

     gcloud config unset project
    
  4. Borra otros recursos Google Cloud que creaste en este instructivo:

¿Qué sigue?