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

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

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

关于自托管的 GitHub 运行程序

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

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

检索代码示例

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

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

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

# 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." \
      --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." \
      --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. \
      --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." \
      --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. \
      --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." \
      --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 是否正常运行,请运行您之前配置为 runs-on: self-hosted 的操作。

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

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