Home » GitHub Runners en Kubernetes
GitHub Runners en Kubernetes

GitHub Runners en Kubernetes

¿Necesitas más control sobre tus flujos de trabajo de GitHub Actions? Los GitHub Runners auto-hospedados en Kubernetes te permiten personalizar tu entorno de ejecución, optimizar el rendimiento y cumplir con requisitos de seguridad exigentes. Descubre cómo Kubernetes puede potenciar tus flujos de trabajo de CI/CD.

Hoy me animo a compartir algo con lo que trabajo bastante a menudo: GitHub Runners en Kubernetes. Es lo que comúnmente se conoce como Self-hosted Runners y que nosotros nos encargamos de implementar y mantener en nuestra infraestructura propia.

¿Por qué hacerlo si GitHub ya nos ofrece sus propios runners de forma «gratuita»? Precisamente, aquí voy a explicar por qué hay algunas buenas razones para querer hacer esto.

1. Introducción

Si ya vienes usando GitHub Actions desde hace algún tiempo, sabrás que cada uno de los jobs de tu workflow corren en equipos o instancias conocidas como GitHub Runners, los cuales son provistos por el mismo GitHub de manera gratuita bajo ciertas condiciones, las cuales paso a resumir líneas abajo.

Los GitHub Runners son la infraestructura que ejecuta los flujos de trabajo de GitHub Actions. Existen dos tipos principales:

  • Los GitHub-hosted runners, que son máquinas virtuales mantenidas por GitHub, y los self-hosted runners, que son máquinas que gestionas tú mismo. Los GitHub-hosted runners son convenientes para la mayoría de los casos de uso, ya que no requieren configuración ni mantenimiento.
  • Sin embargo, los Self-hosted runners ofrecen mayor control y personalización, lo que los hace ideales para entornos con requisitos específicos de seguridad, rendimiento o software.

2. ¿Por qué usar GitHub Runners en Kubernetes?

Voy a comentar algunas de las razones más comunes para preferir correr tus propios Self-hosted runners en Kubernetes.

2.1. Costos GitHub-hosted Runners

Esta es el resumen de la capa gratuita de los Runners hospedados por GitHub:

Planes de GitHub y los minutos gratis de Runners incluidos

La información es clara: por ejemplo, si usas el plan «GitHub Free» tienes 2 mil minutos de ejecución libre al mes. ¿Es mucho o es poco? Todo depende de la cantidad y naturaleza de tus proyectos.

Pero, una vez superada la cantidad de minutos gratis al mes, los adicionales se cobrarán por minuto, según el tipo de instancia, sistema operativo y otras características adicionales (Ejm: uso de GPUs).

Por ejemplo, aquí los costos de las instancias Linux estándar:

Precio por minuto de runners GitHub-hosted  estándar

Si tu objetivo es cuidar los costos, entonces aquí ya tienes una buena razón para correr tus propios GitHub Runners en Kubernetes.

2.2. Acceso a redes internas

A menudo, se tiene que correr muchos workflows que interactúan con infraestructura y/o aplicaciones que están en nuestro Data Center o en un espacio privado de nube. Se trata de entornos que no son accesibles desde Internet (o al menos, no deben serlo).

Por ejemplo, puede ser frecuente que tengas workflows que necesiten generar reportes o backups de bases de datos que están en redes internas, inaccesibles desde Internet.

2.3. Personalización

GitHub hace un buen trabajo ofreciendo imágenes Docker de sus GitHub-hosted runners con una lista de software ya instalado. Incluye los paquetes más comúnmente usados para diferentes proyectos como Java, Python, Node.js, PHP, entre otros.

En este enlace puedes encontrar la lista de todas las imágenes disponibles y lo que incluye en cada una de ellas.

Por ejemplo, para Runners ubuntu-latest, aquí está la lista de paquetes incluidos.

Sin embargo, muchas veces requieres instalar software adicional antes de ejecutar ciertos jobs. Esto es algo que puedes hacer sin problemas con steps previos durante un workflow. Pero, ¿qué tanto tiempo toma instalar y configurar esos paquetes personalizados?

Todo esa instalación repetida una y otra vez le suma tiempo de duración a tus workflows. Y lo que muchos buscan es ejecutar jobs rápidos, sin demora.

En cambio, si corres tus GitHub Runners en Kubernetes, tienes la posibilidad de usar tu propia imagen hecha a medida. Así, cada workflow ejecutado ya no perderá tiempo en instalar paquetes que ya están incluidos dentro de tu imagen personalizada.

2.4. Rendimiento optimizado

Tal vez una de las razones más fuertes para usar tus propios Runners: correr tus workflows en hardware y entornos hechos a medida de tus necesidades.

Los GitHub-hosted runners podrían no adaptarse a necesidades de hardware específico, como grandes requerimientos de CPU y memoria, mayor espacio de disco, acceso a dispositivos locales en tu red, entre otros.

Corriendo tus propios Runners te permite superar esas limitaciones, pues tienes control total del hardware y software subyacente que quizá GitHub podría no dártelo como necesitas.

2.5. Otras razones

Las razones pueden ser múltiples, entre las que se incluyen pero no limitan a:

  • Seguridad estricta que impide el uso de entornos compartidos en la nube
  • Cumplimiento normativo, para industrias más reguladas
  • Control total del entorno con más flexibilidad

3. ¿Y por qué en Kubernetes?

La respuesta es casi una mención a las características que hacen fuerte a Kubernetes:

  • Escalabilidad dinámica: aumenta o reduce el número de Runners según la demanda
  • Uso eficiente de recursos: puedes correr múltiples Runners (Pods) en un único nodo. Por ejemplo, si corres tus runners en Amazon EKS, sumado con Karpenter, podrías usar instancias EC de tipo Spot y On Demand en una buena combinación para crear hardware subyacente para tus runners con una eficiencia económica.
  • Gestión centralizada y simplificada: una vez instalados los Runners, éstos se suman a tu lista de Pods en tus clusters que ya administras de forma estandarizada.

Entre otras buenas razones están la Alta Disponibilidad, el Aislamiento y Seguridad, así como también la Portabilidad.

En este punto, creo ya haberte dado más que razones suficientes para correr tus propios GitHub Runners en Kubernetes.

4. Instalación y configuración

4.1. Información previa

Antes de instalar el software de GitHub Runners, es importante saber que hay dos variantes:

  1. Legacy, basada en el espacio de nombres actions.summerwind.net
  2. Vigente, basada en Autoscaling Runner Scale Sets

El sitio GitHub del Actions Runner Controller (ARC) incluye la documentación del Legacy, pero el que explicaré a continuación es el Vigente.

Ya que hablé de ARC, debes saber que este es un Operador de Kubernetes que orquesta y escala los runners Self-hosted para GitHub Actions.

Instalar GitHub Runners en Kubernetes es relativamente simple. Básicamente, son estos pasos:

  1. Crear un token de GitHub
  2. Instalar el Controlador de Runner Scale Sets
  3. Instalar una o más instancias de Runner Scale Sets
  4. Personalizar la imagen Docker para los Runners (Opcional)

Eso es lo que veremos a continuación.

4.1. Paso 1 – Crear un token de GitHub

Debes crear un PAT (Personal Access Token) con los siguientes scopes:

  1. repo → (Si trabajas con repositorios privados, obligatorio)
  2. admin:org → Para administrar runners a nivel de organización
  3. admin:public_key → Para administrar claves públicas en runners
  4. admin:enterprise → (Si usas GitHub Enterprise, necesario para runners en toda la empresa)
  5. workflow → Permite ejecutar workflows de GitHub Actions
Scopes que debe tener el PAT de GitHub

Toma nota del token creado, que se usará en el paso 3.

4.3. Paso 2 – Instalación del Controlador

Instala el Controlador es tan fácil como ejecutar este comando:

helm install \
  -n github-arc \
  --create-namespace \
  runner-scale-set-controller \
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller

Esto debería crear un Deployment, muy probablemente con un nombre predeterminado como runner-scale-set-controller-gha-rs-controller

Este paso es indispensable y requisito para que puedan correr las instancias de Runner Scale Sets (próximo paso).

4.4. Paso 3 – Instalación de instancia de Runner Scale Sets

Una instancia de Runner Scale Set tiene un nombre propio y gestiona sus propios Runners como Pods, dentro de ciertos límites (cantidad mínima y máxima).

De este modo, es posible tener más de una instancia, cada cual con su propio nombre y sus propias características como se verá más adelante. Cada Team y/o repositorio de GitHub podría decidir utilizar una u otra instancia.

Por ahora, vamos a crear una única instancia llamada self-hosted-default, como sigue:

helm install \
  -n github-arc \
  self-hosted-default \
  --set minRunners=2 \
  --set githubConfigUrl=https://github.com/MiOrganizacion \
  --set githubConfigSecret.github_token=<TokenPAT> \
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set

Debes ajustar estos valores, según tus necesidades:

  • minRunners → Cantidad mínima de instancias que siempre estarán en ejecución. Puede ser 0, inclusive, y cuando existan uno o más Jobs de GitHub Actions en ejecución, se crearán los Runner necesarios a demanda.
  • githubConfigUrl → La URL de tu organización en GitHub
  • githubConfigSecret.github_token → El token PAT creado en el paso 1.

Una vez instalada la instancia, se crea un Custom Resource de tipo EphemeralRunnerSet con un nombre igual a la instancia más un prefijo aleatorio. Lo puedes consultar así:

kubectl -n github-arc get EphemeralRunnerSet

Importante:

  • El EphemeralRunnerSet funciona como un listener que siempre estará a la escucha de los jobs de GitHub Actions. Cuando detecte uno nuevo, verá si puede atender la petición con un Runner que esté vivo y desocupado, sino procederá a crear un Runner nuevo a demanda.

La instancia se llama igual al nombre del Release de Helm instalado. Es decir, self-hosted-default. Por ello, los Pods que son parte de dicha instancia se pueden consultar así:

kubectl -n github-arc get pod \
  -l actions.github.com/scale-set-name=self-hosted-default

Esto listará tanto el EphemeralRunnerSet , como los Runners (objetos de tipo EphemeralRunner).

Por último, si deseas solo consultar los Runners, puedes hacerlo así:

kubectl -n github-arc get ephemeralrunners

Esto mostrará información bastante útil sobre sus nombres, IDs, job de GitHub que lo está usando, estado y una referencia del workflow que lo invoca.

Importante: Si lo necesitas, puedes crear más instancias de Runner Scale Sets repitiendo el comndo Helm, pero usando un nombre de Release distinto.

4.5. Paso 4 – Personalizar la imagen Docker para los Runners (Opcional)

Cuando se instala una instancia de Runner Scale Set como lo mostré en los pasos anteriores, por defecto usará una imagen Docker como esta:

ghcr.io/actions/actions-runner:latest

Esta ya trae el software de GitHub Runner que necesitas. Lo malo es que carece de muchas herramientas que posiblemente necesitarás en tus workflows de GitHub Actions, tales como:

  • kubectl
  • AWS/Azure CLI
  • Terraform/OpenTofu
  • Docker
  • Etc.

Entonces, ¿debes instalar todas las herramientas necesarias como parte de tu workflow? Pues no. Justamente esa es una de las razones por las cuales aconsejé correr tus propios GitHub Runners en Kubernetes.

La solución pasa por crear tu propia imagen a medida que incluya todas las herramientas que necesitarás con frecuencia. Aquí un ejemplo del Dockerfile de esta imagen personalizada.

FROM ubuntu:24.04

ARG KUBECTL_VERSION="1.30.0"
ARG GITHUB_CLI_VERSION="2.62.0"
ARG GITHUB_RUNNER_VERSION="2.322.0"
ARG YQ_VERSION="4.45.1"
ARG KUBERGRUNT_VERSION="0.16.0"
ARG TOFU_VERSION="1.6.2"

ENV DEBIAN_FRONTEND=noninteractive

# Instalar dependencias base
RUN apt-get update && \
    apt-get upgrade -y && \
    apt-get install -y --no-install-recommends curl tzdata ca-certificates jq wget zip ssh python3-git \
    git unzip sudo lsb-release gpg-agent software-properties-common python3-setuptools python-is-python3 && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

# Instalar OpenTofu
RUN curl -Lo /tmp/tofu.zip https://github.com/opentofu/opentofu/releases/download/v${TOFU_VERSION}/tofu_${TOFU_VERSION}_linux_amd64.zip && \
    unzip /tmp/tofu.zip tofu && \
    install tofu /usr/bin && \
    rm -f /tmp/tofu.zip

# Instalar yq
RUN curl -Lo /usr/bin/yq https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_amd64 && \
    chmod +x /usr/bin/yq

# Instalar AWS CLI
RUN curl -Lo /tmp/awscliv2.zip https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip && \
    unzip -q /tmp/awscliv2.zip -d /tmp && \
    /tmp/aws/install -b /usr/bin && \
    rm -rf /tmp/aws /tmp/awscliv2.zip

# Instalar Docker
RUN install -m 0755 -d /etc/apt/keyrings && \
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc && \
    chmod a+r /etc/apt/keyrings/docker.asc && \
    echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu noble stable" > /etc/apt/sources.list.d/docker.list && \
    apt-get update && \
    apt-get install -y --no-install-recommends docker-ce docker-ce-cli containerd.io \
    docker-buildx-plugin docker-compose-plugin && rm -rf /var/lib/apt/lists/*

# Install kubectl
RUN curl -Lo /usr/bin/kubectl "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
    chmod +x /usr/bin/kubectl

# Instalar GitHub CLI
RUN curl -Lo tarball.tar.gz https://github.com/cli/cli/releases/download/v${GITHUB_CLI_VERSION}/gh_${GITHUB_CLI_VERSION}_linux_amd64.tar.gz && \
    tar -xzf tarball.tar.gz -C /usr/bin --strip-components=2 gh_${GITHUB_CLI_VERSION}_linux_amd64/bin/gh && \
    chmod +x /usr/bin/gh && \
    rm -f tarball.tar.gz

RUN adduser --disabled-password --gecos "" --uid 1001 runner \
    && usermod -aG sudo -s /bin/bash runner \
    && usermod -aG docker runner \
    && echo "%sudo   ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers \
    && echo "Defaults env_keep += \"DEBIAN_FRONTEND\"" >> /etc/sudoers

WORKDIR /home/runner

# Instalar GitHub runner
RUN curl -Lo tarball.tar.gz https://github.com/actions/runner/releases/download/v${GITHUB_RUNNER_VERSION}/actions-runner-linux-x64-${GITHUB_RUNNER_VERSION}.tar.gz && \
    tar -zxf tarball.tar.gz && \
    rm -f tarball.tar.gz

USER runner

Importante: Observa que estoy incluyendo la instalación del software de GitHub Runner (el cual es indispensable). Opcionalmente, también estoy incluyendo el GitHub CLI, pues podría requerirlo en alguno de mis workflows.

Digamos que esta imagen personalizada la construí y le apliqué un tag como mi-cuenta-docker/github-runner:1.0.0. De ser así, ahora debo crear un archivo values.yaml con las opciones personalizadas para la reinstalación de la instancia del Runner Scale Sets con Helm.

Este debería ser el contenido:

# values.yaml
---
minRunners: 2
githubConfigSecret:
  github_token: <TokenPAT>
githubConfigUrl: https://github.com/MiOrganizacion
template:
  spec:
    initContainers:
      - name: init-dind-externals
        image: ghcr.io/actions/actions-runner:latest
        command: ["cp", "-r", "-v", "/home/runner/externals/.", "/home/runner/tmpDir/"]
        volumeMounts:
          - name: dind-externals
            mountPath: /home/runner/tmpDir
    containers:
      - name: runner
        image: mi-cuenta-docker/github-runner:1.0.0
        command: ["/home/runner/run.sh"]
        env:
          - name: DOCKER_HOST
            value: unix:///var/run/docker.sock
        volumeMounts:
          - name: work
            mountPath: /home/runner/_work
          - name: dind-sock
            mountPath: /var/run
      - name: dind
        image: docker:dind
        args:
          - dockerd
          - --host=unix:///var/run/docker.sock
          - --group=$(DOCKER_GROUP_GID)
        env:
          - name: DOCKER_GROUP_GID
            value: "996"
        securityContext:
          privileged: true
        volumeMounts:
          - name: work
            mountPath: /home/runner/_work
          - name: dind-sock
            mountPath: /var/run
          - name: dind-externals
            mountPath: /home/runner/externals
    volumes:
      - name: work
        emptyDir: {}
      - name: dind-sock
        emptyDir: {}
      - name: dind-externals
        emptyDir: {}

El contenedor principal de nombre runner es aquel que referencia la imagen Docker personalizada que creamos líneas arriba.

Por último, ahora nos toca actualizar la instalación del Release del Runner Scale Set con Helm de esta manera:

helm upgrade \
  -n github-arc \
  self-hosted-default \
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
  -f values.yaml

Esperamos unos minutos y ya deberíamos tener los nuevos Pods de Runners usando la imagen Docker personalizada.

5. Conclusión

Este fue un post algo extenso, pero ameritaba hacerlo así. Es que no quería perderme ningún detalle sobre las razones para instalar tus propios Runners, la secuencia de instalación (Controlador e instancias de Runner Scale Sets), así como la personalización del imagen que seguramente la necesitarás.

Espero que el post te haya sido de utilidad. No dudes en compartirlo si crees que a alguien más le podría interesar.

¡Hasta la próxima!

Post navigation

Deja un comentario

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *