Saltar a contenido

CI/CD

Bibliografía

Introducción

GitHub es una plataforma de desarrollo colaborativo diseñada para la gestión de proyectos de software. Proporciona herramientas avanzadas para el control de versiones mediante Git, así como funcionalidades para la integración y entrega continua (Continuous Integration - CI y Continuous Deployment - CD, respectivamente). Con el tiempo, GitHub se ha consolidado como una herramienta esencial para desarrolladores y equipos de software, destacando entre sus características GitHub Actions, que permite la automatización de flujos de trabajo directamente dentro de los repositorios facilitando la integración con servicios externos, y GitHub Pages, que ofrece una manera sencilla de publicar sitios web estáticos directamente desde un repositorio.

Una de las principales ventajas de utilizar GitHub Actions en lugar de herramientas como Jenkins u otras soluciones similares es su integración nativa con GitHub. Además, su Marketplace proporciona un amplio catálogo de acciones desarrolladas tanto por GitHub como por terceros, lo que permite extender y personalizar los flujos de trabajo de manera eficiente.

GitHub Actions

La implementación de CI/CD permite automatizar procesos de desarrollo, mejorando la eficiencia y reduciendo errores en la integración y despliegue de software. La integración continua (CI) se refiere a la automatización de la integración de código en un repositorio compartido, asegurando que los cambios sean validados continuamente mediante pruebas y compilaciones. El despliegue continuo (CD) automatiza el proceso de despliegue de código en entornos de producción, facilitando la entrega continua de nuevas versiones del software.

GitHub Actions y su funcionamiento

GitHub Actions es una plataforma que permite la automatización de flujos de trabajo a través de archivos de configuración en formato YAML. Cada workflow está compuesto por una serie de pasos organizados en jobs, que pueden ejecutarse en paralelo o en secuencia dependiendo de las necesidades del proyecto.

El runner de GitHub Actions es un servidor que ejecuta estos workflows en un entorno definido, permitiendo la compilación del código para distintos sistemas operativos, la ejecución de pruebas en paralelo, la validación de código con herramientas como linters y analizadores estáticos, y la implementación de código en producción o entornos de staging.

Para definir un workflow, se crea un archivo .yml dentro de la carpeta .github/workflows/:

1
2
3
4
5
src
.github
│   ├── workflows
│   │   ├── workflow_ejemplo.yml


Esquema de un workflow en GitHub Actions

Un pipeline típico en un workflow podría incluir pasos como fusionar (merge) cambios en la rama principal, ejecutar pruebas, realizar un análisis de código (linting), generar una compilación (build) y desplegar en producción o staging.

Estructura de un Workflow en GitHub Actions

Un workflow en GitHub Actions está definido en un archivo de configuración YAML que contiene las instrucciones necesarias para automatizar tareas dentro de un repositorio.

Elementos clave de un workflow

El campo name define un nombre descriptivo para el workflow. Aunque es opcional, se recomienda utilizarlo para mejorar la identificación y reutilización de workflows dentro del repositorio:

name: Nombre del Workflow

Los disparadores (on) determinan cuándo debe ejecutarse el workflow. Pueden activarse mediante eventos como push, pull_request o ejecuciones programadas. También es posible definir permisos a nivel global o dentro de un job específico. Si varios jobs requieren los mismos permisos, es recomendable declararlos a nivel del workflow en lugar de repetirlos en cada job.

Ejemplo

Definición de permisos a nivel de workflow:

1
2
3
4
5
6
7
8
9
name: Nombre del Workflow

on:
  push:
    branches: ["main"]
  workflow_call:

permissions:
  contents: write

Definición de permisos dentro de un job:

name: Nombre del Workflow

on:
  push:
    branches: ["main"]
  workflow_call:

jobs:
  build-mkdocs:
    name: Build MkDocs Wiki
    runs-on: ubuntu-latest
    needs: setup-lint-test

    permissions:
      contents: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

Los jobs representan las unidades de trabajo dentro de un workflow. Cada job se compone de una serie de steps que definen las acciones a ejecutar de manera secuencial. Por defecto, los jobs se ejecutan en paralelo a menos que uno dependa explícitamente de otro mediante la directiva needs. Cada job se ejecuta en una nueva máquina virtual, y se debe especificar un sistema operativo con runs-on, permitiendo elegir entre Linux, macOS y Windows:

Ejemplo
1
2
3
jobs:
  nombre-del-job:
    runs-on: ubuntu-latest

Nota

Consulta la documentación oficial sobre runners de GitHub aquí.

GitHub Actions permite integrar acciones predefinidas disponibles en GitHub Actions y el GitHub Marketplace.

Ejemplos de configuración de workflows

Ejemplo básico

El siguiente ejemplo muestra un workflow que se ejecuta cuando hay un push o un pull_request en la rama main:

name: Workflow básico

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

Nota

Se recomienda incluir la acción checkout al inicio del workflow para asegurarse de que el código más reciente esté disponible antes de ejecutar cualquier otra tarea.

Ejemplo: Configuración de Python, Poetry y Flake8

En este ejemplo, el workflow configura Python, administra dependencias con Poetry y valida el código con Flake8:

name: Verificación con Flake8

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Instalar Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.10"

      - name: Instalar Poetry
        uses: snok/install-poetry@v1

      - name: Instalar dependencias con Poetry
        run: poetry install

      - name: Verificar código con Flake8
        run: poetry run flake8 src/
Ejemplo: Uso de caché para optimización de workflows

Para mejorar el rendimiento, es posible utilizar caché para almacenar dependencias y evitar reinstalaciones innecesarias:

name: Workflow con caché

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Instalar Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.10"

      - name: Instalar Poetry
        uses: snok/install-poetry@v1
        with:
          virtualenvs-in-project: true

      - name: Cargar caché de dependencias
        uses: actions/cache@v4
        id: cached-poetry-dependencies
        with:
          path: .venv
          key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}

      - name: Instalar dependencias con Poetry
        if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
        run: poetry install

Nota

La clave de caché key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} garantiza que el caché solo se actualice cuando cambie el archivo poetry.lock. Utilizar caché reduce significativamente el tiempo de ejecución del workflow, pero es importante monitorearlo para evitar el uso de dependencias obsoletas.

Modularización de workflows y acciones

Para mejorar la reutilización y el mantenimiento del código, se recomienda modularizar los workflows mediante acciones personalizadas. Un ejemplo de la estructura del proyecto podría ser la siguiente:

1
2
3
4
5
6
7
8
src
.github
|   ├── actions
|       ├── build-application
|           ├── action.yml
|   ├── workflows
│       ├── lint.yml

Dentro de la carpeta build-application se define una acción, que siempre debe tener el nombre action.yml:

name: Build Application

runs:
  using: composite

  steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: "3.10.7"

    - name: Instalar Poetry
      uses: snok/install-poetry@v1
      with:
        virtualenvs-in-project: true

    - name: Cargar caché de dependencias
      uses: actions/cache@v4
      id: cached-poetry-dependencies
      with:
        path: .venv
        key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}

    - name: Instalar dependencias con Poetry
      if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
      run: poetry install

La modularización de workflows no solo mejora la reutilización, sino que también facilita el mantenimiento del código y la integración de nuevas funcionalidades sin modificar los workflows principales. Este enfoque modular permite dividir la complejidad, mejorar la eficiencia y permitir la reutilización de configuraciones a lo largo del proyecto.

Uso de estrategias con matrices

Las matrices de estrategia en GitHub Actions permiten ejecutar un mismo workflow en múltiples combinaciones de entornos, lo que resulta útil para probar software en diferentes sistemas operativos, versiones o configuraciones. Por ejemplo:

name: Workflow

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

strategy:
  matrix:
    os: [macos-latest, windows-latest]
    version: [12, 14, 16]
    environment: [staging, production]
    exclude:
      - os: macos-latest
        version: 12
        environment: production
      - os: windows-latest
        version: 16
runs-on: ${{ matrix.os }}

GitHub genera automáticamente todas las combinaciones posibles de los valores definidos en matrix. Las combinaciones resultantes se reflejan en la siguiente tabla:

OS Versión Entorno
macos-latest 12 staging
macos-latest 14 staging
macos-latest 14 production
macos-latest 16 staging
macos-latest 16 production
windows-latest 12 staging
windows-latest 12 production
windows-latest 14 staging
windows-latest 14 production

Gracias al bloque exclude, las siguientes combinaciones no se ejecutan en el workflow:

OS Versión Entorno
macos-latest 12 production
windows-latest 16 Cualquiera

Los beneficios del uso de matrices incluyen la eficiencia al probar múltiples entornos en paralelo, la flexibilidad para excluir combinaciones no necesarias y la automatización escalable, ideal para probar en distintos sistemas sin escribir múltiples workflows. Este enfoque resulta especialmente útil en proyectos que requieren pruebas en múltiples versiones de software, diferentes entornos (staging/producción) o compatibilidad con varios sistemas operativos.

Jenkins

Jenkins es una aplicación basada en servidor, de código abierto, que facilita la integración continua y la automatización de la construcción, pruebas y despliegue de software. Utiliza un sistema de plugins para integrarse con servicios de terceros, como proveedores de la nube, repositorios de código y herramientas de notificación. Su funcionamiento se basa en la detección de cambios en el código fuente: cuando se producen commits, Jenkins los compila y, si la construcción es correcta, procede al despliegue; en caso contrario, genera alertas para que el equipo pueda actuar rápidamente.

Jenkins abarca las principales etapas del ciclo DevOps: control de versiones, integración continua, monitorización continua, testeo continuo, gestión de la configuración y despliegue continuo.

Arquitectura maestro-esclavo

Jenkins se puede configurar en una arquitectura maestro-esclavo. El nodo maestro es el servidor principal que gestiona la interfaz de usuario, la programación de trabajos y la asignación de tareas a los nodos esclavos. Los nodos esclavos son máquinas adicionales, locales o remotas, que ejecutan las tareas asignadas por el maestro. Esta arquitectura ofrece ventajas significativas en cuanto a distribución de la carga, escalabilidad y aislamiento de entornos de ejecución.

Tipos de plugins

Los plugins más destacados de Jenkins se agrupan en varias categorías: los de interfaz, relacionados con la interfaz gráfica de Jenkins; los de plataforma, vinculados al sistema operativo; los administrativos, destinados a la gestión de usuarios y permisos; los de construcción, que permiten notificar el resultado de la compilación de una aplicación; y los de gestión del código fuente, que facilitan la comunicación entre Jenkins y repositorios como GitLab o GitHub.

Creación de pipelines

Para crear un pipeline en Jenkins, la práctica recomendada consiste en definir un archivo conocido como Jenkinsfile en la raíz del proyecto. Cuando se realiza un push al repositorio, Jenkins lo detecta y ejecuta automáticamente el pipeline definido. Si la variedad de entornos de ejecución es alta durante la fase de construcción, resulta conveniente complementar Jenkins con herramientas de aprovisionamiento como Ansible.

Integración con GitHub

Para conectar Jenkins con GitHub es necesario instalar el plugin de GitHub en Jenkins, crear un personal access token desde los ajustes del perfil de GitHub y, a continuación, configurar el enlace en la sección de configuración del sistema de Jenkins, donde aparece la opción para establecer la conexión con GitHub mediante dicho token.