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)](https://github.com/GoogleCloudPlatform/cloud-run-external-metrics-autoscaling/tree/main).

## 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](https://docs.github.com/en/actions/concepts/runners/about-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:

- [Add self-hosted GitHub runners](https://docs.cloud.google.com/run/docs/tutorials/github-runner#setup-github) to support a GitHub repository, and
  create Secret Manager secrets to securely store tokens and secrets.

- [Deploy a Cloud Run worker pool](https://docs.cloud.google.com/run/docs/tutorials/github-runner#deploy-pool) to Cloud Run
  and use the worker pool to accept jobs from GitHub actions.

- [Deploy the autoscaler CREMA service](https://docs.cloud.google.com/run/docs/tutorials/github-runner#deploy-autoscaler) to scale your worker pool.

- [Test your CREMA service](https://docs.cloud.google.com/run/docs/tutorials/github-runner#test) by verifying logs.

## Costs


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


- [Cloud Build](https://docs.cloud.google.com/build/docs)
- [Artifact Registry](https://docs.cloud.google.com/artifact-registry)
- [Secret Manager](https://docs.cloud.google.com/secret-manager)
- [Cloud Run](https://cloud.google.com/run)


To generate a cost estimate based on your projected usage,
use the [pricing calculator](https://docs.cloud.google.com/products/calculator).
New Google Cloud users might be eligible for a [free trial](https://docs.cloud.google.com/free).

<br />

## Before you begin

1.


   Enable the Cloud Run, Secret Manager,
   Parameter Manager, Artifact Registry, and Cloud Build APIs, if any are not already enabled.


   **Roles required to enable APIs**


   To enable APIs, you need the `serviceusage.services.enable` permission. If you
   created the project, then you likely already have this permission through the
   Owner role (`roles/owner`). Otherwise, you can get this permission through the
   Service Usage Admin role (`roles/serviceusage.serviceUsageAdmin`).
   [Learn how to grant roles](https://docs.cloud.google.com/iam/docs/granting-changing-revoking-access).

   [Enable the APIs](https://console.cloud.google.com/apis/enableflow?apiid=run.googleapis.com,secretmanager.googleapis.com,parametermanager.googleapis.com,cloudbuild.googleapis.com,artifactregistry.googleapis.com)
2. [Install and initialize the gcloud CLI](https://docs.cloud.google.com/sdk/docs/install).
3. Update components:

   ```bash
   gcloud components update
   ```
4. Set the following configuration variables for CREMA used in this tutorial:

   ```bash
   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 <var translate="no">PROJECT_ID</var> with the ID of your Google Cloud project.
5. 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](https://cloud.google.com/products/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:

- [Artifact Registry Repository Administrator](https://docs.cloud.google.com/iam/docs/roles-permissions/artifactregistry#artifactregistry.repoAdmin) (`roles/artifactregistry.repoAdmin`)
- [Cloud Build Editor](https://docs.cloud.google.com/iam/docs/roles-permissions/cloudbuild#cloudbuild.builds.editor) (`roles/cloudbuild.builds.editor`)
- [Cloud Run Admin](https://docs.cloud.google.com/iam/docs/roles-permissions/run#run.admin) (`roles/run.admin`)
- [Create Service Accounts](https://docs.cloud.google.com/iam/docs/roles-permissions/iam#iam.serviceAccountCreator) (`roles/iam.serviceAccountCreator`)
- [Secret Manager Admin](https://docs.cloud.google.com/iam/docs/roles-permissions/secretmanager#secretmanager.admin) (`roles/secretmanager.admin`)
- [Service Account User](https://docs.cloud.google.com/iam/docs/roles-permissions/iam#iam.serviceAccountUser) (`roles/iam.serviceAccountUser`)
- [Service Usage Consumer](https://docs.cloud.google.com/iam/docs/roles-permissions/serviceusage#serviceusage.serviceUsageConsumer) (`roles/serviceusage.serviceUsageConsumer`)
- [Storage Admin](https://docs.cloud.google.com/iam/docs/roles-permissions/storage#storage.admin) (`roles/storage.admin`)
- [Parameter Manager Admin](https://docs.cloud.google.com/iam/docs/roles-permissions/parametermanager#parametermanager.admin) (`roles/parametermanager.admin`)


For more information about granting roles, see [Manage access to projects, folders, and organizations](https://docs.cloud.google.com/iam/docs/granting-changing-revoking-access).


You might also be able to get
the required permissions through [custom
roles](https://docs.cloud.google.com/iam/docs/creating-custom-roles) or other [predefined
roles](https://docs.cloud.google.com/iam/docs/roles-overview#predefined).

> [!NOTE]
> **Note:** [IAM basic
> roles](https://docs.cloud.google.com/iam/docs/roles-overview#basic) might also contain permissions to complete the tutorial. You shouldn't grant basic roles in a production environment, but you can grant them in a development or test environment.

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](https://docs.github.com/en/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners) only.

### Create a custom service account

This tutorial uses a custom service account with the [minimum permissions required](https://docs.cloud.google.com/iam/docs/using-iam-securely) 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](https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/add-runners)
in the GitHub documentation.

### Identify the GitHub repository

In this tutorial, the <var translate="no">GITHUB_REPO</var> 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 <var translate="no">GITHUB_REPO</var> is `myuser/myrepo`.
- If your domain URL is `https://github.com/mycompany/ourrepo`, the <var translate="no">GITHUB_REPO</var> 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)](https://github.com/settings/tokens) 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](https://docs.github.com/en/actions/reference/self-hosted-runners-reference#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](https://docs.cloud.google.com/run/docs/tutorials/github-runner#access-token), 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 <var translate="no">GITHUB_TOKEN</var> 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](https://github.com/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 run worker-pools deploy WORKER_POOL_NAME \
         --region us-central1 \
         --source . \
         --instances 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:
   - <var translate="no">WORKER_POOL_NAME</var>: the name of the worker pool
   - <var translate="no">WORKER_POOL_LOCATION</var>: the region of the worker pool
   - <var translate="no">GITHUB_REPO</var>: the [GitHub repo name](https://docs.cloud.google.com/run/docs/tutorials/github-runner#identify-repo)

   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](https://github.com/actions/runner) image:

    FROM ghcr.io/actions/actions-runner:2.332.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](https://docs.github.com/en/actions/get-started/quickstart#creating-your-first-workflow).

If your repo has GitHub actions, verify that you completed the setup of your [self-hosted runner](https://docs.github.com/en/actions/reference/runners/self-hosted-runners) by invoking a GitHub action on your repository.

If your GitHub action doesn't use self-hosted runners, [change your
GitHub action's job](https://docs.github.com/en/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/using-self-hosted-runners-in-a-workflow#using-default-labels-to-route-jobs) 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](https://docs.github.com/en/actions/get-started/quickstart#creating-your-first-workflow) 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`](https://docs.github.com/en/webhooks/webhook-events-and-payloads#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](https://docs.cloud.google.com/secret-manager/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:
   - <var translate="no">PROJECT_ID</var>: the Google Cloud project ID
   - <var translate="no">WORKER_POOL_NAME</var>: the name of the worker pool you deployed
   - <var translate="no">GITHUB_RUNNER</var>: the name of the GitHub runner you configured
   - <var translate="no">REPOSITORY_OWNER</var>: the owner of the GitHub repository
   - <var translate="no">REPOSITORY_NAME</var>: 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 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 <var translate="no">WORKER_POOL_NAME</var> 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 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-24/runtimes/java25 \
      --labels=created-by=crema \
      --set-env-vars="CREMA_CONFIG=${CREMA_CONFIG_PARAM_VERSION},OUTPUT_SCALER_METRICS=True"

## Test your CREMA service

To verify your autoscaling service is working correctly, [check
the **Logs** tab of the Cloud Run service](https://docs.cloud.google.com/run/docs/logging#service-logs).

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](https://docs.cloud.google.com/run/docs/tutorials/github-runner#delete-resources).


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

To delete the project:

> [!CAUTION]
> **Caution** : Deleting a project has the following effects:
>
> - **Everything in the project is deleted.** If you used an existing project for the tasks in this document, when you delete it, you also delete any other work you've done in the project.
> - **Custom project IDs are lost.** When you created this project, you might have created a custom project ID that you want to use in the future. To preserve the URLs that use the project ID, such as an `appspot.com` URL, delete selected resources inside the project instead of deleting the whole project.
>
>
> If you plan to explore multiple architectures, tutorials, or quickstarts, reusing projects
> can help you avoid exceeding project quota limits.

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

   [Go to Manage resources](https://console.cloud.google.com/iam-admin/projects)
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.

<br />

<br />

### 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:

   ```bash
   gcloud run services delete SERVICE-NAME
   ```

   Replace <var translate="no">SERVICE-NAME</var> with the name of your service.

   You can also delete Cloud Run services from the
   [Google Cloud console](https://console.cloud.google.com/run).
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:

   - [Delete the Cloud Run worker pool](https://docs.cloud.google.com/run/docs/managing/workerpools#delete)
   - [Delete the service container image](https://docs.cloud.google.com/artifact-registry/docs/docker/manage-images#deleting_images) from Artifact Registry.
   - [Delete the service account `crema-service-account@PROJECT_ID.iam.gserviceaccount.com`](https://docs.cloud.google.com/iam/docs/creating-managing-service-accounts#iam-service-accounts-delete-gcloud)
   - [Delete the secrets in Secret Manager](https://docs.cloud.google.com/secret-manager/docs/delete-secrets)
   - [Delete the parameters in Parameter Manager](https://docs.cloud.google.com/secret-manager/parameter-manager/docs/delete-parameters)

## What's next

- Learn more about [Cloud Run worker pools](https://docs.cloud.google.com/run/docs/deploy-worker-pools).
- Explore other [Cloud Run demos, tutorials, and samples](https://docs.cloud.google.com/run/docs).
- Deploy your CREMA service using a [custom container image you build](https://github.com/GoogleCloudPlatform/cloud-run-external-metrics-autoscaling?tab=readme-ov-file#optional-build-the-container-image-from-source) from the source code with Cloud Build.