{"id":698,"date":"2026-02-22T12:16:54","date_gmt":"2026-02-22T11:16:54","guid":{"rendered":"https:\/\/secure-pipelines.com\/ci-cd-security\/lab-ephemeral-self-hosted-runners-actions-runner-controller-2\/"},"modified":"2026-03-25T05:19:59","modified_gmt":"2026-03-25T04:19:59","slug":"lab-ephemeral-self-hosted-runners-actions-runner-controller-2","status":"publish","type":"post","link":"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/lab-ephemeral-self-hosted-runners-actions-runner-controller-2\/","title":{"rendered":"Laboratorio: Ejecutar Runners Autoalojados Ef\u00edmeros de GitHub Actions con Actions Runner Controller"},"content":{"rendered":"<h2>Descripci\u00f3n general<\/h2>\n<p>Los runners alojados en GitHub son compartidos y ef\u00edmeros por defecto \u2014 cada trabajo obtiene una m\u00e1quina virtual nueva que se destruye despu\u00e9s de que el trabajo se completa. Los runners autoalojados, por otro lado, son persistentes y compartidos entre ejecuciones de flujos de trabajo. Esto crea un riesgo de seguridad significativo: secretos, tokens y artefactos de compilaci\u00f3n de un trabajo pueden filtrarse al siguiente. Un flujo de trabajo comprometido puede envenenar el entorno del runner para todos los trabajos futuros.<\/p>\n<p><strong>Actions Runner Controller (ARC)<\/strong> resuelve este problema. ARC es un operador nativo de Kubernetes que te proporciona runners autoalojados ef\u00edmeros, autoescalables y basados en contenedores. Cada trabajo obtiene un pod nuevo que se destruye cuando el trabajo se completa \u2014 igual que los runners alojados en GitHub, pero ejecut\u00e1ndose en tu propia infraestructura con tus propias herramientas y pol\u00edticas de red.<\/p>\n<p>En este laboratorio pr\u00e1ctico, vas a:<\/p>\n<ul>\n<li>Desplegar ARC en un cl\u00faster local de Kubernetes<\/li>\n<li>Configurar runner scale sets ef\u00edmeros<\/li>\n<li>Demostrar el aislamiento entre trabajos (el beneficio de seguridad principal)<\/li>\n<li>Construir im\u00e1genes personalizadas de runners<\/li>\n<li>Implementar aislamiento de grupos de runners para separaci\u00f3n de funciones<\/li>\n<li>Configurar autoescalado<\/li>\n<li>Aplicar pol\u00edticas de red para restringir el acceso de red de los runners<\/li>\n<\/ul>\n<h2>Requisitos previos<\/h2>\n<p>Antes de comenzar este laboratorio, aseg\u00farate de tener lo siguiente:<\/p>\n<ul>\n<li><strong>Cl\u00faster de Kubernetes<\/strong> \u2014 <a href=\"https:\/\/kind.sigs.k8s.io\/\" target=\"_blank\" rel=\"noopener\">kind<\/a>, <a href=\"https:\/\/minikube.sigs.k8s.io\/\" target=\"_blank\" rel=\"noopener\">minikube<\/a>, o un cl\u00faster gestionado en la nube (EKS, GKE, AKS)<\/li>\n<li><strong>Helm 3<\/strong> \u2014 Instalar desde <a href=\"https:\/\/helm.sh\/docs\/intro\/install\/\" target=\"_blank\" rel=\"noopener\">helm.sh<\/a><\/li>\n<li><strong>kubectl<\/strong> \u2014 Configurado para comunicarse con tu cl\u00faster<\/li>\n<li><strong>Cuenta de GitHub<\/strong> \u2014 Con acceso de administrador a un repositorio u organizaci\u00f3n<\/li>\n<li><strong>GitHub App o Personal Access Token (PAT)<\/strong> \u2014 Con los alcances <code>repo<\/code> y <code>admin:org<\/code> (PAT) o los permisos apropiados de GitHub App<\/li>\n<li><strong>Docker<\/strong> \u2014 Para construir im\u00e1genes personalizadas de runners (Ejercicio 4)<\/li>\n<\/ul>\n<h2>Configuraci\u00f3n del entorno<\/h2>\n<p>Usaremos <strong>kind<\/strong> (Kubernetes in Docker) para crear un cl\u00faster local. Esto mantiene el laboratorio autocontenido y f\u00e1cil de limpiar.<\/p>\n<h3>Crear un cl\u00faster kind<\/h3>\n<pre><code>kind create cluster --name arc-lab<\/code><\/pre>\n<p>Verifica que el cl\u00faster est\u00e9 ejecut\u00e1ndose:<\/p>\n<pre><code>kubectl cluster-info --context kind-arc-lab<\/code><\/pre>\n<h3>Crear un repositorio de prueba en GitHub<\/h3>\n<p>Crea un nuevo repositorio (por ejemplo, <code>arc-lab-test<\/code>) en tu cuenta de GitHub. A\u00f1ade un archivo de flujo de trabajo simple en <code>.github\/workflows\/test.yml<\/code>:<\/p>\n<pre><code>name: ARC Test Workflow\non:\n  push:\n    branches: [main]\n  workflow_dispatch:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Hello from GitHub-hosted runner\n        run: echo \"This runs on a GitHub-hosted runner\"<\/code><\/pre>\n<p>Haz push de esto a tu repositorio. Lo modificaremos m\u00e1s adelante para apuntar a los runners de ARC.<\/p>\n<h2>Ejercicio 1: Instalar ARC con Helm<\/h2>\n<p>Actions Runner Controller v2 usa charts de Helm para desplegar dos componentes: un <strong>controlador<\/strong> que gestiona el ciclo de vida de los pods de runners, y uno o m\u00e1s <strong>runner scale sets<\/strong> que se registran con GitHub y aceptan trabajos.<\/p>\n<h3>Paso 1: A\u00f1adir el repositorio de Helm<\/h3>\n<pre><code>helm repo add actions-runner-controller \\\n  https:\/\/actions-runner-controller.github.io\/actions-runner-controller\nhelm repo update<\/code><\/pre>\n<h3>Paso 2: Configurar la autenticaci\u00f3n<\/h3>\n<p>ARC necesita autenticarse con la API de GitHub. Tienes dos opciones:<\/p>\n<p><strong>Opci\u00f3n A: GitHub App (Recomendado para producci\u00f3n)<\/strong><\/p>\n<p>Crea una GitHub App en la configuraci\u00f3n de tu organizaci\u00f3n o cuenta:<\/p>\n<ol>\n<li>Ve a <strong>Settings \u2192 Developer settings \u2192 GitHub Apps \u2192 New GitHub App<\/strong><\/li>\n<li>Establece los siguientes permisos:\n<ul>\n<li>Repository: <code>Actions<\/code> (read), <code>Administration<\/code> (read\/write), <code>Metadata<\/code> (read)<\/li>\n<li>Organization: <code>Self-hosted runners<\/code> (read\/write)<\/li>\n<\/ul>\n<\/li>\n<li>Genera una clave privada y desc\u00e1rgala<\/li>\n<li>Instala la App en tu organizaci\u00f3n o repositorio<\/li>\n<li>Anota el App ID y el Installation ID<\/li>\n<\/ol>\n<p><strong>Opci\u00f3n B: Personal Access Token (M\u00e1s simple para laboratorios)<\/strong><\/p>\n<p>Crea un PAT (cl\u00e1sico) con los alcances <code>repo<\/code> y <code>admin:org<\/code>, o un PAT de grano fino con permisos de Actions y Administration. Para este laboratorio, usaremos un PAT por simplicidad.<\/p>\n<h3>Paso 3: Instalar el controlador ARC<\/h3>\n<pre><code>helm install arc \\\n  actions-runner-controller\/gha-runner-scale-set-controller \\\n  --namespace arc-systems \\\n  --create-namespace<\/code><\/pre>\n<p>Verifica que el controlador est\u00e9 ejecut\u00e1ndose:<\/p>\n<pre><code>kubectl get pods -n arc-systems<\/code><\/pre>\n<p>Deber\u00edas ver una salida similar a:<\/p>\n<pre><code>NAME                                     READY   STATUS    RESTARTS   AGE\narc-gha-runner-scale-set-controller-xxx  1\/1     Running   0          30s<\/code><\/pre>\n<h3>Paso 4: Instalar un Runner Scale Set<\/h3>\n<p>Ahora despliega un runner scale set que se registre con tu repositorio de GitHub:<\/p>\n<pre><code>helm install arc-runner-set \\\n  actions-runner-controller\/gha-runner-scale-set \\\n  --namespace arc-runners \\\n  --create-namespace \\\n  --set githubConfigUrl=\"https:\/\/github.com\/&lt;org&gt;\/&lt;repo&gt;\" \\\n  --set githubConfigSecret.github_token=\"&lt;PAT&gt;\"<\/code><\/pre>\n<p>Reemplaza <code>&lt;org&gt;\/&lt;repo&gt;<\/code> con la ruta de tu repositorio y <code>&lt;PAT&gt;<\/code> con tu personal access token.<\/p>\n<p>Verifica el runner scale set:<\/p>\n<pre><code>kubectl get pods -n arc-runners<\/code><\/pre>\n<p>En este punto, puede que no haya pods de runners todav\u00eda \u2014 ARC utiliza un modelo de escalar a cero. Los pods se crean solo cuando se encolan trabajos.<\/p>\n<h3>Paso 5: Verificar en GitHub<\/h3>\n<p>Navega a tu repositorio en GitHub: <strong>Settings \u2192 Actions \u2192 Runners<\/strong>. Deber\u00edas ver el runner scale set listado con el nombre <code>arc-runner-set<\/code>. El estado muestra que est\u00e1 listo para aceptar trabajos.<\/p>\n<h2>Ejercicio 2: Ejecutar un flujo de trabajo en runners de ARC<\/h2>\n<p>Ahora actualiza el flujo de trabajo de prueba para apuntar al runner scale set de ARC en lugar de los runners alojados en GitHub.<\/p>\n<h3>Paso 1: Actualizar el flujo de trabajo<\/h3>\n<p>Modifica <code>.github\/workflows\/test.yml<\/code> para usar la etiqueta del runner de ARC:<\/p>\n<pre><code>name: ARC Test Workflow\non:\n  push:\n    branches: [main]\n  workflow_dispatch:\n\njobs:\n  test:\n    runs-on: arc-runner-set\n    steps:\n      - name: Hello from ARC runner\n        run: |\n          echo \"This runs on an ephemeral ARC runner!\"\n          echo \"Hostname: $(hostname)\"\n          echo \"Runner OS: $(uname -a)\"\n      - name: Show environment\n        run: env | sort<\/code><\/pre>\n<p>El cambio clave es <code>runs-on: arc-runner-set<\/code> \u2014 esto coincide con el nombre de la release de Helm para el runner scale set.<\/p>\n<h3>Paso 2: Activar el flujo de trabajo<\/h3>\n<p>Haz push del archivo de flujo de trabajo actualizado o usa el bot\u00f3n \u00abRun workflow\u00bb (workflow_dispatch) en la interfaz de GitHub Actions.<\/p>\n<h3>Paso 3: Observar el pod del runner<\/h3>\n<p>Observa el namespace <code>arc-runners<\/code> mientras se ejecuta el flujo de trabajo:<\/p>\n<pre><code>kubectl get pods -n arc-runners -w<\/code><\/pre>\n<p>Ver\u00e1s un pod creado para el trabajo:<\/p>\n<pre><code>NAME                          READY   STATUS    RESTARTS   AGE\narc-runner-set-xxxxx-runner   1\/1     Running   0          5s<\/code><\/pre>\n<p>Despu\u00e9s de que el trabajo se complete, el pod se termina y se elimina:<\/p>\n<pre><code>NAME                          READY   STATUS      RESTARTS   AGE\narc-runner-set-xxxxx-runner   0\/1     Completed   0          45s<\/code><\/pre>\n<p>Ejecuta <code>kubectl get pods -n arc-runners<\/code> de nuevo \u2014 el pod ya no est\u00e1. Este es el modelo ef\u00edmero: cada trabajo obtiene un contenedor nuevo, y el contenedor se destruye cuando el trabajo termina. No hay persistencia de estado entre trabajos.<\/p>\n<h2>Ejercicio 3: Demostrar la seguridad ef\u00edmera<\/h2>\n<p>Este ejercicio demuestra el beneficio de seguridad principal de los runners ef\u00edmeros: <strong>no hay contaminaci\u00f3n entre trabajos<\/strong>.<\/p>\n<h3>Paso 1: Crear un flujo de trabajo que escriba datos sensibles<\/h3>\n<p>Crea <code>.github\/workflows\/ephemeral-test.yml<\/code>:<\/p>\n<pre><code>name: Ephemeral Security Test\non: workflow_dispatch\n\njobs:\n  write-secret:\n    runs-on: arc-runner-set\n    steps:\n      - name: Write sensitive data\n        run: |\n          echo \"SECRET_API_KEY=sk-prod-abc123xyz\" &gt; \/tmp\/secret-data\n          echo \"DB_PASSWORD=super-secret-password\" &gt;&gt; \/tmp\/secret-data\n          echo \"Written sensitive data to \/tmp\/secret-data\"\n          cat \/tmp\/secret-data\n\n  read-secret:\n    runs-on: arc-runner-set\n    needs: write-secret\n    steps:\n      - name: Attempt to read previous job data\n        run: |\n          echo \"Checking if \/tmp\/secret-data exists from previous job...\"\n          if [ -f \/tmp\/secret-data ]; then\n            echo \"SECURITY RISK: Found data from previous job!\"\n            cat \/tmp\/secret-data\n          else\n            echo \"SECURE: \/tmp\/secret-data does not exist.\"\n            echo \"Each job gets a fresh container \u2014 no cross-job contamination.\"\n          fi<\/code><\/pre>\n<h3>Paso 2: Ejecutar el flujo de trabajo<\/h3>\n<p>Activa el flujo de trabajo mediante <code>workflow_dispatch<\/code>. El primer trabajo (<code>write-secret<\/code>) escribe datos sensibles en <code>\/tmp\/secret-data<\/code>. El segundo trabajo (<code>read-secret<\/code>) se ejecuta en un nuevo pod e intenta leer ese archivo.<\/p>\n<h3>Paso 3: Verificar los resultados<\/h3>\n<p>En los logs de GitHub Actions, ver\u00e1s:<\/p>\n<ul>\n<li><strong>Trabajo write-secret:<\/strong> Escribe exitosamente el archivo e imprime el contenido<\/li>\n<li><strong>Trabajo read-secret:<\/strong> El archivo no existe \u2014 la salida muestra <code>SECURE: \/tmp\/secret-data does not exist.<\/code><\/li>\n<\/ul>\n<p>Cada trabajo se ejecut\u00f3 en un pod separado, reci\u00e9n creado. Cuando el pod de <code>write-secret<\/code> fue destruido, todos los datos \u2014 incluyendo el archivo sensible \u2014 fueron destruidos con \u00e9l.<\/p>\n<h3>Por qu\u00e9 esto importa<\/h3>\n<p>En un <strong>runner autoalojado persistente<\/strong>, el archivo <code>\/tmp\/secret-data<\/code> seguir\u00eda en disco cuando se ejecuta el segundo trabajo. Un flujo de trabajo malicioso en un pull request podr\u00eda leer secretos, tokens o credenciales dejados por trabajos anteriores. Con runners ef\u00edmeros, este vector de ataque se elimina.<\/p>\n<h2>Ejercicio 4: Im\u00e1genes personalizadas de runners<\/h2>\n<p>Los runners de ARC usan una imagen de contenedor base. Para uso en el mundo real, necesitas personalizar esta imagen para incluir tus herramientas de compilaci\u00f3n.<\/p>\n<h3>Paso 1: Crear un Dockerfile personalizado<\/h3>\n<p>Crea un <code>Dockerfile<\/code> para tu runner personalizado:<\/p>\n<pre><code>FROM ghcr.io\/actions\/actions-runner:latest\n\nUSER root\n\n# Install build tools\nRUN apt-get update &amp;&amp; apt-get install -y \\\n    curl \\\n    wget \\\n    git \\\n    jq \\\n    unzip \\\n    build-essential \\\n    &amp;&amp; rm -rf \/var\/lib\/apt\/lists\/*\n\n# Install Go\nRUN wget -q https:\/\/go.dev\/dl\/go1.22.4.linux-amd64.tar.gz \\\n    &amp;&amp; tar -C \/usr\/local -xzf go1.22.4.linux-amd64.tar.gz \\\n    &amp;&amp; rm go1.22.4.linux-amd64.tar.gz\nENV PATH=\"$PATH:\/usr\/local\/go\/bin\"\n\n# Install cosign\nRUN curl -sSL -o \/usr\/local\/bin\/cosign \\\n    https:\/\/github.com\/sigstore\/cosign\/releases\/latest\/download\/cosign-linux-amd64 \\\n    &amp;&amp; chmod +x \/usr\/local\/bin\/cosign\n\n# Install Docker CLI (for Docker-in-Docker workflows)\nRUN curl -fsSL https:\/\/get.docker.com | sh\n\nUSER runner<\/code><\/pre>\n<h3>Paso 2: Construir y subir la imagen<\/h3>\n<pre><code># Build the image\ndocker build -t ghcr.io\/&lt;org&gt;\/custom-runner:latest .\n\n# Authenticate to GitHub Container Registry\necho \"&lt;PAT&gt;\" | docker login ghcr.io -u &lt;username&gt; --password-stdin\n\n# Push the image\ndocker push ghcr.io\/&lt;org&gt;\/custom-runner:latest<\/code><\/pre>\n<h3>Paso 3: Configurar ARC para usar la imagen personalizada<\/h3>\n<p>Crea un archivo de valores <code>custom-runner-values.yaml<\/code>:<\/p>\n<pre><code>githubConfigUrl: \"https:\/\/github.com\/&lt;org&gt;\/&lt;repo&gt;\"\ngithubConfigSecret:\n  github_token: \"&lt;PAT&gt;\"\n\ntemplate:\n  spec:\n    containers:\n      - name: runner\n        image: ghcr.io\/&lt;org&gt;\/custom-runner:latest\n        command: [\"\/home\/runner\/run.sh\"]\n        resources:\n          requests:\n            cpu: \"500m\"\n            memory: \"512Mi\"\n          limits:\n            cpu: \"2\"\n            memory: \"2Gi\"<\/code><\/pre>\n<p>Actualiza el runner scale set con la imagen personalizada:<\/p>\n<pre><code>helm upgrade arc-runner-set \\\n  actions-runner-controller\/gha-runner-scale-set \\\n  --namespace arc-runners \\\n  -f custom-runner-values.yaml<\/code><\/pre>\n<h3>Paso 4: Verificar las herramientas personalizadas<\/h3>\n<p>Crea un flujo de trabajo que use las herramientas personalizadas:<\/p>\n<pre><code>name: Custom Runner Tools Test\non: workflow_dispatch\n\njobs:\n  verify-tools:\n    runs-on: arc-runner-set\n    steps:\n      - name: Verify Go\n        run: go version\n      - name: Verify cosign\n        run: cosign version\n      - name: Verify Docker CLI\n        run: docker --version<\/code><\/pre>\n<p><strong>Beneficio de seguridad:<\/strong> Al construir tu propia imagen de runner, controlas exactamente qu\u00e9 herramientas y dependencias est\u00e1n presentes en el entorno de compilaci\u00f3n. No hay binarios inesperados, no hay software preinstalado que no hayas aprobado, y puedes fijar cada herramienta a una versi\u00f3n espec\u00edfica. Tambi\u00e9n puedes escanear la imagen en busca de vulnerabilidades antes de desplegarla.<\/p>\n<h2>Ejercicio 5: Aislamiento de grupos de runners<\/h2>\n<p>Los diferentes flujos de trabajo tienen diferentes niveles de confianza. La validaci\u00f3n de pull requests no deber\u00eda tener acceso a secretos de producci\u00f3n. Los flujos de trabajo de despliegue necesitan secretos pero solo deber\u00edan ejecutarse desde la rama main. ARC te permite implementar esta separaci\u00f3n creando runner scale sets distintos con diferentes etiquetas y configuraciones.<\/p>\n<h3>Paso 1: Crear un Runner Scale Set para validaci\u00f3n de PR<\/h3>\n<p>Crea <code>pr-runner-values.yaml<\/code>:<\/p>\n<pre><code>githubConfigUrl: \"https:\/\/github.com\/&lt;org&gt;\/&lt;repo&gt;\"\ngithubConfigSecret:\n  github_token: \"&lt;PAT&gt;\"\n\ntemplate:\n  spec:\n    containers:\n      - name: runner\n        image: ghcr.io\/&lt;org&gt;\/custom-runner:latest\n        command: [\"\/home\/runner\/run.sh\"]\n        env:\n          - name: RUNNER_GROUP\n            value: \"pr-validation\"\n        resources:\n          requests:\n            cpu: \"250m\"\n            memory: \"256Mi\"\n          limits:\n            cpu: \"1\"\n            memory: \"1Gi\"<\/code><\/pre>\n<pre><code>helm install arc-runner-pr \\\n  actions-runner-controller\/gha-runner-scale-set \\\n  --namespace arc-runners \\\n  -f pr-runner-values.yaml<\/code><\/pre>\n<h3>Paso 2: Crear un Runner Scale Set para despliegue<\/h3>\n<p>Crea <code>deploy-runner-values.yaml<\/code>:<\/p>\n<pre><code>githubConfigUrl: \"https:\/\/github.com\/&lt;org&gt;\/&lt;repo&gt;\"\ngithubConfigSecret:\n  github_token: \"&lt;PAT&gt;\"\n\ntemplate:\n  spec:\n    containers:\n      - name: runner\n        image: ghcr.io\/&lt;org&gt;\/custom-runner:latest\n        command: [\"\/home\/runner\/run.sh\"]\n        env:\n          - name: RUNNER_GROUP\n            value: \"deployment\"\n        resources:\n          requests:\n            cpu: \"500m\"\n            memory: \"512Mi\"\n          limits:\n            cpu: \"2\"\n            memory: \"2Gi\"\n    serviceAccountName: deploy-runner-sa\n    nodeSelector:\n      runner-type: deployment<\/code><\/pre>\n<pre><code>helm install arc-runner-deploy \\\n  actions-runner-controller\/gha-runner-scale-set \\\n  --namespace arc-runners \\\n  -f deploy-runner-values.yaml<\/code><\/pre>\n<h3>Paso 3: Configurar flujos de trabajo para aislamiento<\/h3>\n<p>Usa diferentes etiquetas de runner seg\u00fan el activador del flujo de trabajo:<\/p>\n<pre><code>name: CI\/CD Pipeline\non:\n  pull_request:\n    branches: [main]\n  push:\n    branches: [main]\n\njobs:\n  validate:\n    if: github.event_name == 'pull_request'\n    runs-on: arc-runner-pr\n    steps:\n      - uses: actions\/checkout@v4\n      - name: Run tests\n        run: make test\n      - name: Run linter\n        run: make lint\n\n  deploy:\n    if: github.ref == 'refs\/heads\/main' &amp;&amp; github.event_name == 'push'\n    runs-on: arc-runner-deploy\n    steps:\n      - uses: actions\/checkout@v4\n      - name: Deploy to production\n        run: make deploy\n        env:\n          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}<\/code><\/pre>\n<p>Esto implementa <strong>separaci\u00f3n de funciones a nivel de runner<\/strong>. Los trabajos de validaci\u00f3n de PR se ejecutan en runners que no tienen acceso a secretos de despliegue ni a segmentos de red privilegiados. Los trabajos de despliegue se ejecutan en un conjunto separado de runners que tienen las credenciales y acceso de red necesarios, pero solo se activan con pushes a main.<\/p>\n<h2>Ejercicio 6: Autoescalado<\/h2>\n<p>ARC soporta autoescalado de forma nativa. Los pods de runners se crean bajo demanda y se destruyen cuando est\u00e1n inactivos. Puedes configurar r\u00e9plicas m\u00ednimas y m\u00e1ximas para controlar el costo y la capacidad de respuesta.<\/p>\n<h3>Paso 1: Configurar par\u00e1metros de autoescalado<\/h3>\n<p>Actualiza tu archivo de valores del runner scale set para incluir par\u00e1metros de escalado:<\/p>\n<pre><code>githubConfigUrl: \"https:\/\/github.com\/&lt;org&gt;\/&lt;repo&gt;\"\ngithubConfigSecret:\n  github_token: \"&lt;PAT&gt;\"\n\nminRunners: 0\nmaxRunners: 10\n\ntemplate:\n  spec:\n    containers:\n      - name: runner\n        image: ghcr.io\/actions\/actions-runner:latest\n        command: [\"\/home\/runner\/run.sh\"]<\/code><\/pre>\n<pre><code>helm upgrade arc-runner-set \\\n  actions-runner-controller\/gha-runner-scale-set \\\n  --namespace arc-runners \\\n  -f autoscale-values.yaml<\/code><\/pre>\n<h3>Paso 2: Generar carga<\/h3>\n<p>Crea un flujo de trabajo que active m\u00faltiples trabajos en paralelo:<\/p>\n<pre><code>name: Autoscale Test\non: workflow_dispatch\n\njobs:\n  parallel-job:\n    runs-on: arc-runner-set\n    strategy:\n      matrix:\n        id: [1, 2, 3, 4, 5]\n    steps:\n      - name: Simulate work\n        run: |\n          echo \"Job ${{ matrix.id }} running on $(hostname)\"\n          sleep 60<\/code><\/pre>\n<p>Activa este flujo de trabajo y observa c\u00f3mo los pods escalan:<\/p>\n<pre><code>kubectl get pods -n arc-runners -w<\/code><\/pre>\n<p>Ver\u00e1s cinco pods creados \u2014 uno para cada trabajo de la matriz:<\/p>\n<pre><code>NAME                              READY   STATUS    RESTARTS   AGE\narc-runner-set-abcde-runner       1\/1     Running   0          5s\narc-runner-set-fghij-runner       1\/1     Running   0          5s\narc-runner-set-klmno-runner       1\/1     Running   0          5s\narc-runner-set-pqrst-runner       1\/1     Running   0          5s\narc-runner-set-uvwxy-runner       1\/1     Running   0          5s<\/code><\/pre>\n<p>Despu\u00e9s de que los trabajos se completen (60 segundos), todos los pods se terminan. El namespace vuelve a cero pods.<\/p>\n<h3>Paso 3: Configurar el retraso de reducci\u00f3n de escala<\/h3>\n<p>Para optimizaci\u00f3n de costos, puede que quieras que los pods permanezcan activos por un corto per\u00edodo despu\u00e9s de que un trabajo se complete. Esto evita la latencia de arranque en fr\u00edo para cargas de trabajo intermitentes. El comportamiento de escalar a cero de ARC es la opci\u00f3n predeterminada y m\u00e1s segura. Si necesitas runners en caliente, mant\u00e9n la ventana corta (menos de 5 minutos) y aseg\u00farate de que el modo ef\u00edmero siga aplic\u00e1ndose.<\/p>\n<h2>Ejercicio 7: Pol\u00edticas de red para runners<\/h2>\n<p>Las NetworkPolicies de Kubernetes te permiten restringir el acceso de red de los pods de runners. Esta es una defensa cr\u00edtica contra la exfiltraci\u00f3n de datos desde compilaciones comprometidas.<\/p>\n<h3>Paso 1: Crear una NetworkPolicy<\/h3>\n<p>Aplica la siguiente NetworkPolicy al namespace <code>arc-runners<\/code>:<\/p>\n<pre><code>apiVersion: networking.k8s.io\/v1\nkind: NetworkPolicy\nmetadata:\n  name: runner-egress-policy\n  namespace: arc-runners\nspec:\n  podSelector: {}\n  policyTypes:\n    - Egress\n  egress:\n    # Allow DNS resolution\n    - to:\n        - namespaceSelector: {}\n      ports:\n        - protocol: UDP\n          port: 53\n        - protocol: TCP\n          port: 53\n    # Allow GitHub API and Actions services\n    - to:\n        - ipBlock:\n            cidr: 140.82.112.0\/20\n        - ipBlock:\n            cidr: 143.55.64.0\/20\n        - ipBlock:\n            cidr: 185.199.108.0\/22\n        - ipBlock:\n            cidr: 4.0.0.0\/8\n      ports:\n        - protocol: TCP\n          port: 443\n    # Allow your container registry (example: ghcr.io)\n    - to:\n        - ipBlock:\n            cidr: 140.82.112.0\/20\n      ports:\n        - protocol: TCP\n          port: 443\n    # Allow your artifact storage (replace with your CIDR)\n    # - to:\n    #     - ipBlock:\n    #         cidr: 10.0.0.0\/8\n    #   ports:\n    #     - protocol: TCP\n    #       port: 443<\/code><\/pre>\n<pre><code>kubectl apply -f runner-network-policy.yaml<\/code><\/pre>\n<p><strong>Nota:<\/strong> GitHub publica sus rangos de IP en <a href=\"https:\/\/api.github.com\/meta\" target=\"_blank\" rel=\"noopener\">https:\/\/api.github.com\/meta<\/a>. Usa los rangos de <code>actions<\/code> y <code>api<\/code>. Los CIDRs anteriores son ejemplos \u2014 verifica los rangos actuales y actual\u00edzalos en consecuencia.<\/p>\n<h3>Paso 2: Probar la NetworkPolicy<\/h3>\n<p>Crea un flujo de trabajo que intente alcanzar una URL externa:<\/p>\n<pre><code>name: Network Policy Test\non: workflow_dispatch\n\njobs:\n  test-network:\n    runs-on: arc-runner-set\n    steps:\n      - name: Test GitHub API (should work)\n        run: curl -s -o \/dev\/null -w \"%{http_code}\" https:\/\/api.github.com\n\n      - name: Test external URL (should be blocked)\n        run: |\n          if curl -s --connect-timeout 5 https:\/\/evil-exfiltration-server.example.com; then\n            echo \"FAIL: External access was allowed\"\n            exit 1\n          else\n            echo \"PASS: External access was blocked by NetworkPolicy\"\n          fi<\/code><\/pre>\n<p>Cuando ejecutes este flujo de trabajo:<\/p>\n<ul>\n<li>La solicitud a la API de GitHub tiene \u00e9xito (HTTP 200) porque la NetworkPolicy permite el tr\u00e1fico a los rangos de IP de GitHub.<\/li>\n<li>La solicitud a la URL externa agota el tiempo y falla porque no est\u00e1 en la lista de egreso permitido.<\/li>\n<\/ul>\n<p>Esto previene que una compilaci\u00f3n comprometida exfiltre c\u00f3digo fuente, secretos o artefactos de compilaci\u00f3n a un servidor controlado por un atacante. Incluso si una dependencia maliciosa ejecuta c\u00f3digo arbitrario durante la compilaci\u00f3n, no puede comunicarse con el exterior.<\/p>\n<h2>Limpieza<\/h2>\n<p>Elimina todos los recursos creados durante este laboratorio:<\/p>\n<pre><code># Delete Helm releases\nhelm uninstall arc-runner-set -n arc-runners\nhelm uninstall arc-runner-pr -n arc-runners\nhelm uninstall arc-runner-deploy -n arc-runners\nhelm uninstall arc -n arc-systems\n\n# Delete namespaces\nkubectl delete namespace arc-runners\nkubectl delete namespace arc-systems\n\n# Delete the kind cluster\nkind delete cluster --name arc-lab<\/code><\/pre>\n<p>Si creaste una GitHub App para este laboratorio, puedes eliminarla desde <strong>Settings \u2192 Developer settings \u2192 GitHub Apps<\/strong>. Revoca cualquier PAT que hayas creado.<\/p>\n<h2>Conclusiones clave<\/h2>\n<ul>\n<li><strong>Los runners ef\u00edmeros eliminan la contaminaci\u00f3n entre trabajos.<\/strong> Cada trabajo obtiene un contenedor nuevo \u2014 secretos, tokens y artefactos de compilaci\u00f3n se destruyen cuando el trabajo se completa.<\/li>\n<li><strong>ARC proporciona los beneficios de runners autoalojados sin los riesgos de seguridad.<\/strong> Obtienes herramientas personalizadas, acceso a red privada y control de costos mientras mantienes el modelo de seguridad ef\u00edmero.<\/li>\n<li><strong>Las im\u00e1genes personalizadas de runners te dan control total sobre el entorno de compilaci\u00f3n.<\/strong> Fija versiones de herramientas, escanea vulnerabilidades y elimina el riesgo de cadena de suministro del software preinstalado.<\/li>\n<li><strong>El aislamiento de grupos de runners implementa la separaci\u00f3n de funciones.<\/strong> Los flujos de trabajo de validaci\u00f3n de PR y despliegue se ejecutan en conjuntos de runners separados con diferentes privilegios y acceso de red.<\/li>\n<li><strong>Las pol\u00edticas de red son una capa cr\u00edtica de defensa.<\/strong> Restringir el egreso de los runners previene la exfiltraci\u00f3n de datos incluso si un paso de compilaci\u00f3n est\u00e1 comprometido.<\/li>\n<li><strong>El autoescalado a cero reduce costos y superficie de ataque.<\/strong> Los pods de runners existen solo durante la duraci\u00f3n de un trabajo \u2014 no hay infraestructura persistente que mantener o asegurar.<\/li>\n<\/ul>\n<h2>Pr\u00f3ximos pasos<\/h2>\n<p>Contin\u00faa fortaleciendo tu postura de seguridad CI\/CD con estas gu\u00edas relacionadas:<\/p>\n<ul>\n<li><a href=\"\/github-actions\/securing-github-actions-runners\/\">Securizar los runners de GitHub Actions<\/a> \u2014 Gu\u00eda detallada sobre las mejores pr\u00e1cticas de seguridad de runners, gesti\u00f3n de tokens y monitoreo tanto para runners alojados en GitHub como autoalojados.<\/li>\n<li><a href=\"\/es\/ci-cd-security\/separation-of-duties-least-privilege-ci-cd-pipelines\/\">Separaci\u00f3n de funciones y privilegio m\u00ednimo en pipelines CI\/CD<\/a> \u2014 Gu\u00eda completa para implementar principios de privilegio m\u00ednimo en todo tu pipeline CI\/CD, desde el control de c\u00f3digo fuente hasta el despliegue en producci\u00f3n.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Descripci\u00f3n general Los runners alojados en GitHub son compartidos y ef\u00edmeros por defecto \u2014 cada trabajo obtiene una m\u00e1quina virtual nueva que se destruye despu\u00e9s de que el trabajo se completa. Los runners autoalojados, por otro lado, son persistentes y compartidos entre ejecuciones de flujos de trabajo. Esto crea un riesgo de seguridad significativo: secretos, &#8230; <a title=\"Laboratorio: Ejecutar Runners Autoalojados Ef\u00edmeros de GitHub Actions con Actions Runner Controller\" class=\"read-more\" href=\"https:\/\/secure-pipelines.com\/es\/ci-cd-security\/lab-ephemeral-self-hosted-runners-actions-runner-controller-2\/\" aria-label=\"Leer m\u00e1s sobre Laboratorio: Ejecutar Runners Autoalojados Ef\u00edmeros de GitHub Actions con Actions Runner Controller\">Leer m\u00e1s<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[55,56],"tags":[],"post_folder":[],"class_list":["post-698","post","type-post","status-publish","format-standard","hentry","category-ci-cd-security","category-github-actions"],"_links":{"self":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/698","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/comments?post=698"}],"version-history":[{"count":0,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/posts\/698\/revisions"}],"wp:attachment":[{"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/media?parent=698"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/categories?post=698"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/tags?post=698"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/secure-pipelines.com\/es\/wp-json\/wp\/v2\/post_folder?post=698"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}