Blog de Amazon Web Services (AWS)

Escalar las acciones de GitHub Ejecutantes autoalojados con Amazon ECS

Vinicius Schettino, Ingeniero de DevOps, Directo
Gabriel Bella Martini, arquitecto de soluciones, sector público de AWS Brasil
Thiago Padua, arquitecto de soluciones, sector público de AWS Brasil

 

Passi Direto, es la mayor red de estudios e intercambio de materiales didácticos de Brasil, la cual experimentó un crecimiento vertiginoso entre 2019 y 2020, llegando a más de 10 millones de estudiantes utilizando su plataforma. Así, se produjo la expansión del sector de ingeniería de la misma, lo que llevó a la necesidad de adoptar un modelo de trabajo distribuido, con equipos multidisciplinarios enfocados en elementos específicos del producto. En un día típico, el equipo maneja alrededor de 1.800 procedimientos automatizados, con un promedio de 55 horas de ejecución. Para soportar esta creciente carga de trabajo de manera óptima y eficiente, Jenkins ha sido reemplazado gradualmente por GitHub Actions como la principal herramienta CI/CD.

Sin embargo, desde el punto de vista de la infraestructura necesaria para ejecutar las rutinas de pruebas, implementación y desarrollo, el modelo autogestionado de GitHub Actions no aborda todos los casos de los equipos en Passi Direto. Uno de los principales desafíos son las canalizaciones que exigen más recursos computacionales, especialmente relacionados con los datos y el aprendizaje automático. Incluso para tareas más cortas, hay casos de integración con varios servicios de AWS protegidos por Amazon VPC y grupos de seguridad. Por lo tanto, Passi Direto decidió implementar una solucion open-source utilizando ejecutores auto-hospedados a utilizando Amazon Elastic Container Services (ECS). Las principales ventajas encontradas fueron escalabilidad (más tareas ejecutándose en paralelo), reducción de costos (ejecutantes conectados sólo cuando es necesario) y flexibilidad con funciones personalizadas para tareas específicas (requisitos de memoria y GPU, por ejemplo). En este artículo vamos a explicar la arquitectura de la solución creada, los resultados clave y los planes para futuras mejoras.

 

Resumen de la solución

La solución inicializa los ejecutores de GitHub Actions a través de tareas ECS alojadas en instancias de Amazon EC2. Hay tres componentes clave en la arquitectura:

Todos los componentes anteriores son iniciativas de código abierto de Passi Direct y están disponibles para la contribución «tal cual» y la replicación de la solución, o como referencia para enfoques similares.

El siguiente diagrama de secuencia describe el comportamiento de estos componentes desde el lanzamiento de una canalización:

 

Figura 1: Diagrama de ejecución de corredores auto-hospedados

 

La canalización es iniciada por GitHub en función de un evento, como un nuevo Pull Request o la creación de una etiqueta . El ejecutante se activa en un contenedor Docker por una tarea que precede a la rutina que se inició. Esta tarea se denominará pre-trabajo y es responsable de activar la tarea en el clúster de ECS y esperar a que el ejecutante esté listo para recibir la carga de trabajo descripta en el flujo de trabajo .

El pre-trabajo se ejecuta en la infraestructura de acciones de GitHub, ejecutando una acción que utiliza AWS SDK para Python, boto3, para iniciar la tarea que inicializará el contenedor del ejecutor. Puede configurar parámetros como memoria de CPU reservada para el ejecutante, modelo de red, etiquetas y otros valores de tiempo de ejecución de definición de tareas.

El clúster ECS es responsable de inicializar las instancias necesarias para manejar la carga de trabajo en cola. De acuerdo con la configuración del proveedor de capacidad, el clúster implementará nuevas instancias o eliminará instancias inactivas. Puede encontrar más detalles sobre la escalabilidad de los clústeres de ECS en este artículo.

ECS puede lanzar contenedores tanto en instancias EC2 como en AWS Fargate. Debido a que GitHub Actions también realiza sus tareas dentro de contenedores, es necesario usar Docker in-Docker (DInd) , que requiere el uso de parametros para que funcione correctamente, que solo se admite en instancias EC2 y, por lo tanto, se adopta de forma predeterminada en la solución. Sin embargo, todavía es posible utilizar Fargate de forma limitada, ya que trae beneficios como la reducción del tiempo de inicio para los ejecutantes y no hay necesidad de administrar instancias directamente.

El siguiente dibujo compara el modelo de ejecución de ECS utilizando EC2 con Fargate:

 

Figura 2: ECS, EC2 y clúster de fargate

 

La definición de tarea se basa en definir el contenedor del ejecutor y establecer algunos valores predeterminados para la configuración de red, los volúmenes y las variables de entorno. Para el ejecutante, la solución utiliza un enfoque efímero, donde el contenedor se detiene poco después de que finalice una canalización. Esto garantiza el aislamiento entre compilaciones, facilitando la escalabilidad y la optimización de recursos, ya que en tiempos de inactividad, como fuera de horario, huecos, fines de semana y días festivos, no hay necesidad de instancias conectadas. Aunque el enfoque efímero aún no está plenamente respaldado por GitHub Actions, hay varias iniciativas del núcleo del proyecto y de la comunidad destinadas a habilitar este modelo de trabajo.

Toda la infraestructura del proyecto se mantiene mediante scripts CDK y llamadas a las API de AWS.

 

Ejemplo de implementación

Para demostrar la solución, se ejemplifica un caso de uso simple: cuando se crea una versión en GitHub, se debe ejecutar una canalización de implementación . Dado que este producto ficticio requiere acceso a instancias contenidas en una VPC privada, se decidió utilizar un ejecutor auto-hospedado. El flujo de trabajo se describiría mediante un archivo YAML con el siguiente formato:

 

name: 'Deploy new release to AWS'
on:
  release:
    types: [published]
jobs:
  pre-job:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
        aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
    - name: Provide a self hosted to execute this job
      uses: PasseiDireto/gh-runner-task-action@main
      with:
        github_pat: ${{ secrets.GITHUB_ACTIONS_RUNNER_TOKEN }}
        task_definition: 'gh-runner'
        cluster: 'gh-runner'
  deploy:
    runs-on: self-hosted
    needs: pre-job
    container:
      image: python:3.9-alpine
    steps:
      - uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.ECR_ACCESS_ID }}
          aws-secret-access-key: ${{ secrets.ECR_SECRET_KEY }}
          aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
      - name: Deploy
        env:
          ENVIRONMENT: ${{ github.refs }}
        run: python scripts/deploy.py $ENVIRONMENT

Cuando se libera una nueva versión, GitHub activa el pre-trabajo y luego se implementa. Gráficamente, puede observar el flujo en la interfaz de GitHub:

 

Figura 3 – Flujo de muestra de GitHub

 

El pre-trabajo se ejecuta rápidamente, con una pequeña sobrecarga en canalizaciónes más grandes. Esto es especialmente cierto en los casos en que ya existe una instancia activa de EC2 y con la imagen del ejecutante ya disponible. GitHub y AWS cobrarán los costes previos al trabajo y a la implementación, respectivamente. Por último, puede observar la dependencia entre las dos tareas de canalización, causada por la directiva de necesidades que se insertó en la implementación.

Cuando los ejecutantes están conectados, puede observar el recuento de tareas dentro delclústerde ECS:

 

Figura 4: Tareas ECS

 

De acuerdo con el ciclo de vida del trabajo de ECS, la ruta predeterminada sería PROVISIONING (instancias disponibles pendientes), PENDIENTE (al inicializar el contenedor) y EJECUTANDO (la tarea se está ejecutando correctamente).  En el repositorio (en configuraciones/acciones) se pueden ver los ejecutantes registrados:

 

Figura 5: Runners autohospedados disponibles

 

Al final de la canalización, el ejecutante se elimina automáticamente del grupo de repositorios y la tarea ECS finaliza correctamente.

Aún puede utilizar la directiva matricial para paralelizar trabajos escalando el número de ejecutantes según el número de tareas que se deben realizar. Por ejemplo, en el paso inicial del flujo de trabajo se decide qué componentes deben pasar por una nueva compilación y, a continuación, se iniciará el número requerido de ejecutantes para que el proceso pueda ocurrir en paralelo:

 

Figura 6 – Uso de Matriz

 

Voz del cliente

«Con GHA tenemos más libertad y facilidad para escalar nuestra implementación, sin mencionar que todo en un solo lugar facilita la supervisión.  Además, con Jenkins necesitábamos las máquinas siempre encendidas, mientras que con GHA podemos tener el esquema de uso solo cuando sea necesario, lo que puede llevar a un retraso un poco más en algunas construcciones, pero que se compensa en la economía. «— Renato Bibiano, Tech Manager [Data Squad]

 

Finalización y pasos siguientes

Passei Direto está sufriendo una migración gradual de Jenkins a esta nueva solución. Sin embargo, desde el punto de vista del costo, las diferencias ya son evidentes. Con AWS Cost Dashboard puede ver una reducción de costes de CI/CD de 11 veces en los equipos que ya han adoptado completamente las acciones de GitHub con ECS, principalmente debido al cambio en el modelo de ejecución de 24×7 a bajo demanda.

Passei Direto está trabajando en mejoras a la solución presentada en esta entrada de blog. Entre ellos podemos destacar:

  • Actualización automática del ejecutante en el contenedor;
  • Optimización de la imagen ejecutante desde el punto de vista de seguridad, tamaño y tiempo de arranque;
  • Creación de definiciones de tareas utilizando Fargate para casos específicos;
  • Implementación de los registros de ejecución en Amazon CloudWatch;
  • Monitoreo centralizado de canalizaciónes
  • El uso de etiquetas para identificar a los ejecutores y evitar las condiciones de funcionamiento;
  • Mejoras en los enfoques de paralelismo para iniciar trabajos de acuerdo con la disponibilidad de los ejecutores, acelerando el proceso.

En los próximos meses esperamos que decenas de nuevas canalizaciónes sean migrados hacia el nuevo enfoque. Así, evolucionaremos gradualmente la propuesta y publicaremos resultados más maduros, con el objetivo de ayudar a la comunidad a construir una solución escalable para ejecutores de Acciones GitHub autohospedados.

Colaboraron en la solución Rodrigo Martins y Robson Andrade, ingenieros de DevOps de Passi Direct.


Sobre los autores

Vinícius Schettino es Ingeniero de DevOps en Passei Direto con más de 10 años de experiencia en ingeniería de software. Enfocado en datos CI/CD, MLOP, calidad de software y automatización.

 

 

 

 

Gabriel Bella Martini es arquitecto de soluciones de AWS con un enfoque en los clientes de educación. Tiene experiencia en diferentes proyectos relacionados con la Inteligencia Artificial y tiene gran interés en los gráficos por ordenador.

 

 

 

 

Thiago Padua es arquitecto de soluciones de AWS trabaja con el desarrollo y el apoyo de socios del sector público. Anteriormente trabajó con desarrollo de software e integración de sistemas, principalmente en la industria de las telecomunicaciones. Tiene un interés especial en microservicios, arquitectura sin servidor y contenedor.