Run and scale self-hosted GitHub runners on Cloud Run worker pools

This tutorial shows you how to use self-hosted GitHub runners on worker pools to execute the workflows defined in your GitHub repository, and scale your worker pool with Cloud Run External Metrics Autoscaling (CREMA).

About self-hosted GitHub runners

In a GitHub Actions workflow, runners are the machines that execute jobs. For example, a runner can clone your repository locally, install testing software, and then run commands that evaluate your code.

You can use self-hosted runners to run GitHub Actions on Cloud Run worker pool instances. This tutorial shows you how to automatically scale a pool of runners based on the number of running and unscheduled jobs.

Objectives

In this tutorial, you will:

Costs

In this document, you use the following billable components of Google Cloud:

To generate a cost estimate based on your projected usage, use the pricing calculator.

New Google Cloud users might be eligible for a free trial.

Before you begin

  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, Parameter 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. Install and initialize the gcloud CLI.
  8. Update components:
    gcloud components update
  9. Set the following configuration variables for CREMA used in this tutorial:
    PROJECT_ID=PROJECT_ID CREMA_SERVICE_ACCOUNT_NAME=crema-service-account@$PROJECT_ID.iam.gserviceaccount.com CREMA_REPO_NAME=crema AR_REGION=us-central1
    Replace PROJECT_ID with the ID of your Google Cloud project.
  10. You incur charges for your Cloud Run scaling service based on how often you trigger scaling. For more information, estimate costs with the pricing calculator.

Required roles

To get the permissions that you need to complete the tutorial, ask your administrator to grant you the following IAM roles on your project:

For more information about granting roles, see Manage access to projects, folders, and organizations.

You might also be able to get the required permissions through custom roles or other predefined roles.

You need permission to edit the settings on a GitHub repository to configure the self-hosted runners. The repository can be user-owned, or an organisation owned repository.

GitHub recommends using self-hosted runners with private repositories only.

Create a custom service account

This tutorial uses a custom service account with the minimum permissions required to use the provisioned resources. To set up the service account, do the following:

gcloud iam service-accounts create crema-service-account \  --display-name="CREMA Service Account" 

Add self-hosted GitHub runners

To add self-hosted GitHub runners, follow the instructions in adding self-hosted runners in the GitHub documentation.

Identify the GitHub repository

In this tutorial, the GITHUB_REPO variable represents the repository name. This is the part of the name that you find after the domain name for both personal user repositories and organization repositories. For example:

  • If your domain URL is https://github.com/myuser/myrepo, the GITHUB_REPO is myuser/myrepo.
  • If your domain URL is https://github.com/mycompany/ourrepo, the GITHUB_REPO is mycompany/ourrepo.

Create access token

Create a GitHub access token to dynamically add and remove runners by interacting with your selected repository. To create an access token on GitHub and save it in the Secret Manager, follow these steps:

  1. Ensure you are logged into your GitHub account.
  2. Navigate to GitHub's Settings > Developer Settings > Personal Access Tokens > Tokens (classic) page.
  3. Click Generate new token, and select Generate new token (classic).
  4. For the token scope, select the repo checkbox.
  5. Click Generate token.
  6. Copy the generated token.

For more information on access tokens see Authentication requirements in GitHub documentation.

Create a secret for your access token using Secret Manager

Take the secret token you created in the previous step, and store it in Secret Manager. To set access permissions, follow these steps:

  1. Create the secret in Secret Manager:

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

    Replace the GITHUB_TOKEN with the value you copied from GitHub.

  2. Grant the roles/secretmanager.secretAccessor to your custom service account to access your newly created secret:

    gcloud secrets add-iam-policy-binding github_runner_token \  --member "serviceAccount:$CREMA_SERVICE_ACCOUNT_NAME" \  --role "roles/secretmanager.secretAccessor" 

Deploy a worker pool

Create a Cloud Run worker pool to process GitHub actions. This pool will use an image based on the GitHub-created actions/runner image. To deploy a worker pool, follow these steps:

  1. Clone the sample repository to your local machine to retrieve the code sample for use:

    git clone https://github.com/GoogleCloudPlatform/cloud-run-samples 
  2. Change to the directory that contains the Cloud Run sample code:

    cd cloud-run-samples/github-runner/worker-pool-container 
  3. Deploy the worker pool:

    gcloud beta run worker-pools deploy WORKER_POOL_NAME \  --region us-central1 \  --source . \  --scaling 1 \  --set-env-vars GITHUB_REPO=GITHUB_REPO \  --set-secrets GITHUB_TOKEN=github_runner_token:latest \  --service-account $CREMA_SERVICE_ACCOUNT_NAME \  --memory 2Gi \  --cpu 4 

    Replace the following:

    • WORKER_POOL_NAME: the name of the worker pool
    • WORKER_POOL_LOCATION: the region of the worker pool
    • GITHUB_REPO: the GitHub repo name

    If this is the first time using Cloud Run source deploys in this project, Cloud Run prompts you to create a default Artifact Registry repository.

Understand the code sample

The worker pool is configured with a Dockerfile that is based on the GitHub-created actions/runner image:

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

This helper script runs when the container is started, registering itself to the configured repository as an ephemeral instance, using a token you create.

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

Use the worker pool to accept jobs from GitHub actions

Your worker pool instance is ready to accept jobs from GitHub actions.

If your repo doesn't already have any GitHub actions, follow the instructions in the quickstart for creating your first workflow.

If your repo has GitHub actions, verify that you completed the setup of your self-hosted runner by invoking a GitHub action on your repository.

If your GitHub action doesn't use self-hosted runners, change your GitHub action's job from runs-on value to self-hosted.

Once you can configured an action to use the self-hosted runners, run the action.

Confirm the action completes successfully in the GitHub interface.

Deploy the autoscaler CREMA service

You deployed one worker in your original pool, which allows processing of one action at a time. Depending on your Continuous Integration (CI) usage, you might need to scale your pool to handle an influx of work to be done.

Once you deploy the worker pool with an active GitHub runner, configure the CREMA autoscaler to provision worker instances based on the job status in the actions queue.

This implementation listens for a workflow_job event. When you create a workflow job, it scales up the worker pool, and once the job is completed, scales it down again. It won't scale the pool beyond the maximum number of instances you configure, and scales to zero when all running jobs have completed.

You can adapt CREMA based on your workloads.

Configure the autoscaler

This tutorial uses the Parameter Manager to store the YAML configuration file for CREMA.

  1. Create a parameter in the Parameter Manager to store parameter versions for CREMA:

    PARAMETER_ID=crema-config PARAMETER_REGION=global gcloud parametermanager parameters create $PARAMETER_ID --location=$PARAMETER_REGION --parameter-format=YAML 
  2. Create a YAML file, my-crema-config.yaml in the parent directory to define the autoscaler configuration:

    apiVersion: crema/v1 kind: CremaConfig metadata:  name: gh-demo spec:  pollingInterval: 10  triggerAuthentications:  - metadata:  name: github-trigger-auth  spec:  gcpSecretManager:  secrets:  - parameter: personalAccessToken  id: github_runner_token  version: latest  scaledObjects:  - spec:  scaleTargetRef:  name: projects/PROJECT_ID/locations/us-central1/workerpools/WORKER_POOL_NAME  triggers:  - type: github-runner  name: GITHUB_RUNNER  metadata:  owner: REPOSITORY_OWNER  runnerScope: repo  repos: REPOSITORY_NAME  targetWorkflowQueueLength: 1  authenticationRef:  name: github-trigger-auth  advanced:  horizontalPodAutoscalerConfig:  behavior:  scaleDown:  stabilizationWindowSeconds: 10  policies:  - type: Pods  value: 100  periodSeconds: 10  scaleUp:  stabilizationWindowSeconds: 10  policies:  - type: Pods  value: 2  periodSeconds: 10 

    Replace the following:

    • PROJECT_ID: the Google Cloud project ID
    • WORKER_POOL_NAME: the name of the worker pool you deployed
    • GITHUB_RUNNER: the name of the GitHub runner you configured
    • REPOSITORY_OWNER: the owner of the GitHub repository
    • REPOSITORY_NAME: the GitHub repository name
  3. Upload your local YAML file as a new parameter version:

    LOCAL_YAML_CONFIG_FILE=my-crema-config.yaml PARAMETER_VERSION=1 gcloud parametermanager parameters versions create $PARAMETER_VERSION \  --location=$PARAMETER_REGION \  --parameter=$PARAMETER_ID \  --payload-data-from-file=$LOCAL_YAML_CONFIG_FILE 

Grant additional permissions to your custom service account

To scale the worker pool you specified in your YAML configuration, grant the following permissions on the custom service account:

  1. Grant your CREMA service account permission to read from the Parameter Manager:

    gcloud projects add-iam-policy-binding $PROJECT_ID \  --member="serviceAccount:$CREMA_SERVICE_ACCOUNT_NAME" \  --role="roles/parametermanager.parameterViewer" 
  2. Grant your CREMA service account the roles/run.developer role on the worker pool:

    WORKER_POOL_NAME=WORKER_POOL_NAME WORKER_POOL_REGION=us-central1 gcloud beta run worker-pools add-iam-policy-binding $WORKER_POOL_NAME \  --region=$WORKER_POOL_REGION \  --member="serviceAccount:$CREMA_SERVICE_ACCOUNT_NAME" \  --role="roles/run.developer" 

    Replace WORKER_POOL_NAME with the name of the worker pool.

  3. Grant your CREMA service account permission to write metrics:

     gcloud projects add-iam-policy-binding $PROJECT_ID \  --member="serviceAccount:$CREMA_SERVICE_ACCOUNT_NAME" \  --role="roles/monitoring.metricWriter" 
  4. Grant your CREMA service account the service account user role:

    gcloud projects add-iam-policy-binding $PROJECT_ID \  --member="serviceAccount:$CREMA_SERVICE_ACCOUNT_NAME" \  --role="roles/iam.serviceAccountUser" 

Deploy the service to scale your workloads

To deploy the service to scale your worker pool, run the following command with a prebuilt container image:

SERVICE_NAME=my-crema-service SERVICE_REGION=us-central1 CREMA_CONFIG_PARAM_VERSION=projects/$PROJECT_ID/locations/$PARAMETER_REGION/parameters/$PARAMETER_ID/versions/$PARAMETER_VERSION IMAGE=us-central1-docker.pkg.dev/cloud-run-oss-images/crema-v1/autoscaler:1.0 gcloud beta run deploy $SERVICE_NAME \  --image=${IMAGE} \  --region=${SERVICE_REGION} \  --service-account="${CREMA_SERVICE_ACCOUNT_NAME}" \  --no-allow-unauthenticated \  --no-cpu-throttling \  --base-image=us-central1-docker.pkg.dev/serverless-runtimes/google-22/runtimes/java21 \  --labels=created-by=crema \  --set-env-vars="CREMA_CONFIG=${CREMA_CONFIG_PARAM_VERSION},OUTPUT_SCALER_METRICS=True" 

Create webhook secret value

To create a secret value to access the GitHub webhook, do the following:

  1. Create a Secret Manager secret to manage access to your GitHub webhook.

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

    Replace WEBHOOK_SECRET with an arbitrary string value.

  2. Grant access to the secret to the autoscaler service account:

    gcloud secrets add-iam-policy-binding github_webhook_secret \  --member "serviceAccount:$CREMA_SERVICE_ACCOUNT_NAME" \  --role "roles/secretmanager.secretAccessor" 

Create GitHub webhook

To create the GitHub webhook, follow these steps:

  1. Ensure you are logged into your GitHub account.
  2. Navigate to your GitHub repository.
  3. Click Settings.
  4. Under Code and automation, click Webhooks.
  5. Click Add webhook.
  6. Enter the following:

    1. In Payload URL, enter the URL of the Cloud Run CREMA service you deployed, my-crema-service.
    2. For Content type, select application/json.
    3. For Secret, enter the WEBHOOK_SECRET value you created previously.
    4. For SSL verification, select Enable SSL verification.
    5. For Which events would you like to trigger this webhook?, select Let me select individual events.
    6. In the event selection, select Workflow jobs. Unselect any other option.
    7. Click Add webhook.

Test your CREMA service

To verify your autoscaling service is working correctly, check the Logs tab of the Cloud Run service.

You should see the following logs in your service's logs each time metrics are refreshed:

Each log message is labeled with the component that emitted it.

[INFO] [METRIC-PROVIDER] Starting metric collection cycle [INFO] [METRIC-PROVIDER] Successfully fetched scaled object metrics ... [INFO] [METRIC-PROVIDER] Sending scale request ... [INFO] [SCALER] Received ScaleRequest ... [INFO] [SCALER] Current instances ... [INFO] [SCALER] Recommended instances ... 

Clean up

To avoid additional charges to your Google Cloud account, delete all the resources you deployed with this tutorial.

Delete the project

If you created a new project for this tutorial, delete the project. If you used an existing project and need to keep it without the changes you added in this tutorial, delete resources that you created for the tutorial.

The easiest way to eliminate billing is to delete the project that you created for the tutorial.

To delete the project:

  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.

Delete tutorial resources

  1. Delete the Cloud Run service you deployed in this tutorial. Cloud Run services don't incur costs until they receive requests.

    To delete your Cloud Run service, run the following command:

    gcloud run services delete SERVICE-NAME

    Replace SERVICE-NAME with the name of your service.

    You can also delete Cloud Run services from the Google Cloud console.

  2. Remove the gcloud default region configuration you added during tutorial setup:

     gcloud config unset run/region 
  3. Remove the project configuration:

     gcloud config unset project 
  4. Delete other Google Cloud resources created in this tutorial:

What's next