使用 Cloud Run 工作器池托管 GitHub 运行程序

本教程将引导您在工作器池中使用自托管 GitHub runner 来执行 GitHub 代码库中定义的工作流。

您将部署一个 Cloud Run 工作器池来处理此工作负载,并可以选择性地部署一个 Cloud Run 函数来支持工作器池的扩缩。

关于自托管的 GitHub 运行程序

在 GitHub Actions 工作流中,运行程序是执行作业的机器。例如,运行程序可以在本地克隆您的代码库、安装测试软件,然后运行评估代码的命令。

您可以使用自托管的 runner 在 Cloud Run 工作器池实例上运行 GitHub Actions。本教程将向您展示如何根据正在运行和未调度的作业数量自动扩缩运行程序池,甚至在没有作业时将池缩减为零。

目标

在此教程中,您将学习以下操作:

  • 将 Cloud Run 工作器池部署到 Cloud Run。
  • 部署 Cloud Run 函数以支持工作器池扩缩。
  • 创建 Secret Manager Secret 以安全地存储令牌和 Secret。
  • 部署自托管的 GitHub runner 以支持 GitHub 代码库。

费用

在本文档中,您将使用 Google Cloud的以下收费组件:

如需根据您的预计使用量来估算费用,请使用价格计算器

新 Google Cloud 用户可能有资格申请免费试用

准备工作

  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 (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 (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. 所需的角色

    如需获得完成本教程所需的权限,请让您的管理员为您授予项目的以下 IAM 角色:

    如需详细了解如何授予角色,请参阅管理对项目、文件夹和组织的访问权限

    您也可以通过自定义角色或其他预定义角色来获取所需的权限。

    您需要拥有修改 GitHub 代码库设置的权限,才能配置自托管的 runner。代码库可以是用户拥有的,也可以是组织拥有的。

检索代码示例

如需检索可用的代码示例,请执行以下操作:

  1. 将示例代码库克隆到您的本地机器:

    git clone https://github.com/GoogleCloudPlatform/cloud-run-samples
    
  2. 切换到包含 Cloud Run 示例代码的目录:

    cd cloud-run-samples/github-runner
    

了解核心代码

此示例以工作器池和自动扩缩器的形式实现,如下文所述。

工作器池

工作器池通过基于 GitHub 创建的 actions/runner 映像的 Dockerfile 进行配置。

除了一个小型辅助脚本之外,所有逻辑都包含在此映像中。

FROM ghcr.io/actions/actions-runner:2.328.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"]

此辅助脚本会在容器启动时运行,使用您将创建的令牌将自身注册到配置的代码库中,作为临时实例。该脚本还定义了在容器缩减时要执行的操作。

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

自动扩缩器

自动扩缩器是一种函数,可在队列中有新作业时纵向扩缩工作器池,或在作业完成时纵向缩减工作器池。它使用 Cloud Run API 检查池中的当前工作器数量,并根据需要调整该值。

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

配置 IAM

本教程使用具有使用已配置资源所需的最低权限的自定义服务账号。如需设置服务账号,请执行以下操作:

  1. gcloud 中设置项目 ID:

    gcloud config set project PROJECT_ID
    

    PROJECT_ID 替换为您的项目 ID。

  2. 创建新的 Identity and Access Management 服务账号:

    gcloud iam service-accounts create gh-runners
    

  3. 向服务账号授予权限,使其能够充当您项目中的服务账号:

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

    PROJECT_ID 替换为您的项目 ID。

检索 GitHub 信息

GitHub 文档中关于添加自托管运行程序的部分建议通过 GitHub 网站添加运行程序,然后提供用于身份验证的特定令牌。

本教程将动态添加和移除运行程序,因此需要静态 GitHub 令牌才能实现此目的。

如需完成本教程,您需要创建一个 GitHub 令牌,该令牌具有与所选代码库互动的访问权限。

确定 GitHub 代码库

在本教程中,GITHUB_REPO 变量表示代码库名称。这是 GitHub 代码库名称中域名后面的部分,适用于个人用户代码库和组织代码库。

您将引用域名后面的代码库名称,无论是用户拥有的代码库还是组织拥有的代码库。

在本教程中:

  • 对于 https://github.com/myuser/myrepoGITHUB_REPOmyuser/myrepo
  • 对于 https://github.com/mycompany/ourrepoGITHUB_REPOmycompany/ourrepo

创建访问令牌

您需要在 GitHub 上创建访问令牌,并将其安全地保存在 Secret Manager 中:

  1. 确保您已登录 GitHub 账号。
  2. 前往 GitHub 的设置 > 开发者设置 > 个人访问令牌页面。
  3. 点击生成新令牌,然后选择生成新令牌(经典)
  4. 创建具有“代码库”范围的新令牌。
  5. 点击 生成令牌
  6. 复制生成的令牌。

创建密钥值

获取您刚刚创建的 Secret 令牌,将其存储在 Secret Manager 中,并设置访问权限。

  1. 在 Secret Manager 中创建 Secret:

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

    GITHUB_TOKEN 替换为您从 GitHub 复制的值。

  2. 授予对新创建的 Secret 的访问权限:

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

部署工作器池

创建 Cloud Run 工作器池以处理 GitHub 操作。此池将使用基于 GitHub 创建的 actions/runner 映像的映像。

设置 Cloud Run 工作器池

  1. 导航到工作器池的示例代码:

    cd worker-pool-container
    
  2. 部署工作器池:

    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
    

    替换以下内容:

    如果您是首次在此项目中使用 Cloud Run 源代码部署,系统会提示您创建默认 Artifact Registry 制品库。

使用工作器池

现在,您的工作器池中有一个实例,可以接受来自 GitHub Actions 的作业。

如需验证您是否已完成自托管运行器的设置,请在代码库中调用 GitHub 操作。

如需让操作使用自托管的 runner,您需要更改 GitHub 操作的作业。在作业中,将 runs-on 值更改为 self-hosted

如果您的代码库尚无任何操作,您可以按照 GitHub Actions 快速入门中的说明进行操作。

配置好使用自托管运行器的操作后,运行该操作。

确认该操作已在 GitHub 界面中成功完成。

部署 GitHub Runner Autoscaler

您在原始池中部署了一个工作器,这样便可一次处理一个操作。根据您的 CI 使用情况,您可能需要扩缩池来处理大量待完成的工作。

在部署包含有效 GitHub 运行程序的工作器池后,配置自动扩缩器以根据操作队列中的作业状态预配工作器实例。

此实现会监听 workflow_job 事件。创建工作流作业时,系统会扩大工作器池,并在作业完成后再次缩小工作器池。它不会将池扩缩到超出配置的实例数量上限,并且会在所有正在运行的作业完成后缩减到零。

您可以根据工作负载调整此自动扩缩器。

创建 webhook 密文值

如需为 Webhook 创建密钥值,请执行以下操作:

  1. 创建包含任意字符串值的 Secret Manager Secret。

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

    WEBHOOK_SECRET 替换为任意字符串值。

  2. 向自动扩缩器服务账号授予对 Secret 的访问权限:

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

部署函数以接收 webhook 请求

如需部署用于接收 Webhook 请求的函数,请执行以下操作:

  1. 找到 webhook 的示例代码:

    cd ../autoscaler
    
  2. 部署 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
    

    替换以下内容:

    • GITHUB_REPO GitHub 代码库名称中域名后面的部分
    • WORKER_POOL_NAME:工作器池的名称
    • WORKER_POOL_LOCATION 工作器池的区域
    • REPOSITORY_NAME GitHub 代码库名称
  3. 记下服务部署到的网址。您将在后面的步骤中使用此值。

  4. 授予服务账号更新工作器池的权限:

    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
    

    PROJECT_ID 替换为您的项目 ID。

创建 GitHub webhook

如需创建 GitHub Webhook,请按照以下步骤操作:

  1. 确保您已登录 GitHub 账号。
  2. 前往您的 GitHub 代码库。
  3. 点击设置
  4. 在“代码和自动化”下,点击 Webhook
  5. 点击 Add webhook
  6. 输入以下内容:

    1. 载荷网址中,输入您之前部署的 Cloud Run 函数的网址。

      该网址将类似于:https://github-runner-autoscaler-PROJECTNUM.REGION.run.app,其中 PROJECTNUM 是项目的唯一数字标识符,REGION 是您将服务部署到的区域。

    2. 对于内容类型,请选择 application/json

    3. 对于密钥,请输入您之前创建的 WEBHOOK_SECRET 值。

    4. 对于 SSL 验证,选择启用 SSL 验证

    5. 对于“您希望通过哪些事件触发此 Webhook?”,请选择让我选择单个事件

    6. 在活动选择中,选择工作流作业。取消选择任何其他选项。

    7. 点击 Add webhook

缩减工作器池

现在,Webhook 已就位,因此您无需在池中拥有持久性工作器。这还将确保在没有工作要完成时,您不会有任何正在运行的工作人员,从而降低成本。

  • 调整池以缩减至零:

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

使用自动扩缩 runner

如需验证自动扩缩 runner 是否正常运行,请运行您之前配置为 runs-on: self-hosted 的操作。

您可以在代码库的“操作”标签页中跟踪 GitHub 操作的进度。

您可以分别检查 Cloud Run 函数和 Cloud Run 工作器池的“日志”标签页,以查看 Webhook 函数和工作器池的执行情况。

清理

为避免您的 Google Cloud 账号产生额外费用,请删除您在本教程中部署的所有资源。

删除项目

如果您为本教程创建了一个新项目,请删除项目。 如果您使用的是某个现有项目,并且需要保留此项目但不保留在本教程中所做的更改,请删除为本教程创建的资源

为了避免产生费用,最简单的方法是删除您为本教程创建的项目。

要删除项目,请执行以下操作:

  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.

删除教程资源

  1. 删除您在本教程中部署的 Cloud Run 服务。Cloud Run 服务在收到请求之前不会产生费用。

    如需删除 Cloud Run 服务,请运行以下命令:

    gcloud run services delete SERVICE-NAME

    SERVICE-NAME 替换为服务的名称。

    您还可以通过Google Cloud 控制台删除 Cloud Run 服务。

  2. 移除您在教程设置过程中添加的 gcloud 默认区域配置:

     gcloud config unset run/region
    
  3. 移除项目配置:

     gcloud config unset project
    
  4. 删除在本教程中创建的其他 Google Cloud 资源:

后续步骤