Alojamento de executores do GitHub com conjuntos de trabalhadores do Cloud Run

Este tutorial explica como usar executores do GitHub autoalojados em pools de trabalhadores para executar os fluxos de trabalho definidos no seu repositório do GitHub.

Vai implementar um grupo de trabalhadores do Cloud Run para processar esta carga de trabalho e, opcionalmente, implementar uma função do Cloud Run para suportar o dimensionamento do grupo de trabalhadores.

Acerca dos executores do GitHub autoalojados

Num fluxo de trabalho do GitHub Actions, os executores são as máquinas que executam tarefas. Por exemplo, um runner pode clonar o seu repositório localmente, instalar software de testes e, em seguida, executar comandos que avaliam o seu código.

Pode usar executores com alojamento próprio para executar ações do GitHub em instâncias do conjunto de trabalhadores do Cloud Run. Este tutorial mostra-lhe como dimensionar automaticamente um conjunto de executores com base no número de tarefas em execução e não agendadas, mesmo dimensionando o conjunto para zero quando não existem tarefas.

Obtenha o exemplo de código

Para obter o exemplo de código para utilização:

  1. Clone o repositório de exemplo para a sua máquina local:

    git clone https://github.com/GoogleCloudPlatform/cloud-run-samples
    
  2. Altere para o diretório que contém o código de exemplo do Cloud Run:

    cd cloud-run-samples/github-runner
    

Compreenda o código principal

O exemplo é implementado como um conjunto de trabalhadores e um escalador automático, descritos a seguir.

Conjunto de trabalhadores

O conjunto de trabalhadores é configurado com um Dockerfile baseado na imagem actions/runner criada pelo GitHub.

Toda a lógica está contida nesta imagem, exceto um pequeno 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"]

Este script auxiliar é executado quando o contentor é iniciado, registando-se no repositório configurado como uma instância efémera, através de um token que vai criar. O script também define as ações a realizar quando o contentor é reduzido.

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

Redimensionador automático

O dimensionamento automático é uma função que aumenta o número de trabalhadores quando existe uma nova tarefa na fila ou diminui o número de trabalhadores quando uma tarefa é concluída. Usa a API Cloud Run para verificar o número atual de trabalhadores no conjunto e ajusta esse valor conforme necessário.

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

Configure o IAM

Este tutorial usa uma conta de serviço personalizada com as autorizações mínimas necessárias para usar os recursos aprovisionados. Para configurar a conta de serviço, faça o seguinte:

  1. Defina o ID do projeto em gcloud:

    gcloud config set project PROJECT_ID
    

    Substitua PROJECT_ID pelo ID do seu projeto.

  2. Crie uma nova conta de serviço de gestão de identidade e de acesso:

    gcloud iam service-accounts create gh-runners
    

  3. Conceda autorizações à conta de serviço para agir como uma conta de serviço no seu projeto:

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

    Substitua PROJECT_ID pelo ID do seu projeto.

Obtenha informações do GitHub

A documentação do GitHub para adicionar executores autoalojados sugere adicionar executores através do Website do GitHub, que, em seguida, fornece um token específico para usar na autenticação.

Este tutorial adiciona e remove executores dinamicamente e precisa de um token do GitHub estático para o fazer.

Para concluir este tutorial, tem de criar um token do GitHub com acesso para interagir com o repositório selecionado.

Identifique o repositório do GitHub

Neste tutorial, a variável GITHUB_REPO representa o nome do repositório. Esta é a parte do nome do repositório do GitHub que se encontra após o nome do domínio, tanto para repositórios de utilizadores pessoais como para repositórios de organizações.

Vai referenciar o nome do repositório que aparece após o nome do domínio para repositórios pertencentes a utilizadores e a organizações.

Neste tutorial:

  • Para https://github.com/myuser/myrepo, o GITHUB_REPO é myuser/myrepo.
  • Para https://github.com/mycompany/ourrepo, o GITHUB_REPO é mycompany/ourrepo.

Crie uma chave de acesso

Tem de criar um token de acesso no GitHub e guardá-lo em segurança no Secret Manager:

  1. Certifique-se de que tem sessão iniciada na sua conta do GitHub.
  2. Navegue para a página Settings > Developer Settings > Personal Access Tokens do GitHub.
  3. Clique em Gerar novo token e selecione Gerar novo token (clássico).
  4. Crie um novo token com o âmbito "repo".
  5. Clique em Gerar token.
  6. Copie o símbolo gerado.

Crie um valor secreto

Use o token secreto que acabou de criar, armazene-o no Secret Manager e defina as autorizações de acesso.

  1. Crie o Secret no Secret Manager:

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

    Substitua GITHUB_TOKEN pelo valor que copiou do GitHub.

  2. Conceda acesso ao segredo recém-criado:

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

Implemente o grupo de trabalhadores

Crie um conjunto de trabalhadores do Cloud Run para processar ações do GitHub. Este conjunto vai usar uma imagem baseada na imagem actions/runner criada pelo GitHub.

Configure o grupo de trabalhadores do Cloud Run

  1. Navegue para o exemplo de código do worker pool:

    cd worker-pool-container
    
  2. Implemente o grupo de trabalhadores:

    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
    

    Substitua o seguinte:

    Se for a primeira vez que usa implementações de origem do Cloud Run neste projeto, é-lhe pedido que crie o repositório do Artifact Registry predefinido.

Usar o grupo de trabalhadores

Agora, tem uma única instância no seu conjunto de trabalhadores, pronta para aceitar tarefas das ações do GitHub.

Para verificar se concluiu a configuração do seu executor autoalojado, invoque uma ação do GitHub no seu repositório.

Para que a sua ação use os executores autoalojados, tem de alterar a tarefa de uma ação do GitHub. No trabalho, altere o valor runs-on para self-hosted.

Se o seu repositório ainda não tiver ações, pode seguir o início rápido para ações do GitHub.

Depois de configurar uma ação para usar os executores autoalojados, execute a ação.

Confirme se a ação é concluída com êxito na interface do GitHub.

Implemente o dimensionamento automático do GitHub Runner

Implementou um trabalhador no seu conjunto original, o que permite o processamento de uma ação de cada vez. Consoante a sua utilização da CI, pode ter de dimensionar o seu conjunto para processar um afluxo de trabalho a realizar.

Depois de implementar o conjunto de trabalhadores com um executor do GitHub ativo, configure o escalador automático para aprovisionar instâncias de trabalhadores com base no estado da tarefa na fila de ações.

Esta implementação ouve um evento workflow_job. Quando a tarefa do fluxo de trabalho é criada, aumenta a capacidade do conjunto de trabalhadores e, quando a tarefa é concluída, diminui novamente a capacidade. Não dimensiona o conjunto para além do número máximo de instâncias configurado e dimensiona para zero quando todas as tarefas em execução estiverem concluídas.

Pode adaptar este escalador automático com base nas suas cargas de trabalho.

Crie um valor secreto do webhook

Para criar um valor secreto para o webhook, faça o seguinte:

  1. Crie um segredo do Secret Manager que contenha um valor de string arbitrário.

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

    Substitua WEBHOOK_SECRET por um valor de string arbitrário.

  2. Conceda acesso ao segredo à conta de serviço do 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"
    

Implemente a função para receber pedidos de webhook

Para implementar a função de receção de pedidos de webhook, faça o seguinte:

  1. Navegue para o exemplo de código do webhook:

    cd ../autoscaler
    
  2. Implemente a função do 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
    

    Substitua o seguinte:

    • GITHUB_REPO a parte do nome do repositório do GitHub após o nome do domínio
    • WORKER_POOL_NAME o nome do conjunto de trabalhadores
    • WORKER_POOL_LOCATION a região do grupo de trabalhadores
    • REPOSITORY_NAME o nome do repositório do GitHub
  3. Tome nota do URL para o qual o seu serviço foi implementado. Vai usar este valor num passo posterior.

  4. Conceda autorizações à conta de serviço para atualizar o seu conjunto de trabalhadores:

    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
    

    Substitua PROJECT_ID pelo ID do seu projeto.

Crie um webhook do GitHub

Para criar o webhook do GitHub, siga estes passos:

  1. Certifique-se de que tem sessão iniciada na sua conta do GitHub.
  2. Navegue para o seu repositório do GitHub.
  3. Clique em Definições.
  4. Em "Código e automatização", clique em Webhooks.
  5. Clique em Adicionar webhook.
  6. Introduza os seguintes dados:

    1. Em URL de carga útil, introduza o URL da função do Cloud Run que implementou anteriormente.

      O URL tem o seguinte aspeto: https://github-runner-autoscaler-PROJECTNUM.REGION.run.app, em que PROJECTNUM é o identificador numérico exclusivo do seu projeto e REGION é a região para a qual implementou o serviço.

    2. Para Tipo de conteúdo, selecione application/json.

    3. Em Segredo, introduza o valor WEBHOOK_SECRET que criou anteriormente.

    4. Para a validação de SSL, selecione Ativar validação de SSL.

    5. Em "Que eventos quer acionar com este webhook?", selecione Permitir-me selecionar eventos individuais.

    6. Na seleção de eventos, selecione Tarefas de fluxo de trabalho. Desmarque qualquer outra opção.

    7. Clique em Adicionar webhook.

Reduza o número de trabalhadores

O webhook está agora implementado, pelo que não tem de ter um trabalhador persistente no conjunto. Isto também garante que não tem trabalhadores em execução quando não há trabalho a fazer, o que reduz os custos.

  • Ajuste o seu conjunto para dimensionar para zero:

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

Use o seu executor de dimensionamento automático

Para verificar se o executor de escalamento automático está a funcionar corretamente, execute uma ação que configurou anteriormente para runs-on: self-hosted.

Pode acompanhar o progresso das suas GitHub Actions no separador "Actions" do seu repositório.

Pode verificar a execução da função de webhook e do conjunto de trabalhadores consultando o separador Logs da função do Cloud Run e do conjunto de trabalhadores do Cloud Run, respetivamente.