Skip to main content

Command Palette

Search for a command to run...

Automatizando CI/CD de una API Flask en Kubernetes con GitLab y DigitalOcean

Updated
6 min read
Automatizando CI/CD de una API Flask en Kubernetes con GitLab y DigitalOcean
R

Ingeniero en Telecomunicaciones con enfoque en redes, conectividad y automatización. Actualmente estudiando AWS, DevOps y CI/CD. Comparto mi camino con proyectos prácticos y artículos técnicos desde Chile 🇨🇱.

https://robertoespana.hashnode.dev/portafolio

Proyecto para aplicar conceptos y aprender nuevas tecnologías mediante la creación de una pipeline CI/CD completa, desde el commit hasta el despliegue


Introducción

En este proyecto muestro cómo diseñé una pipeline CI/CD completa que construye, prueba y despliega una API desarrollada con Flask. Utilicé herramientas como GitLab CI/CD, Docker, DockerHub y un clúster de Kubernetes en DigitalOcean.

El objetivo principal fue automatizar todo el proceso desde el commit hasta el despliegue en producción, garantizando que cada cambio pase por etapas definidas de:

  • Construcción de la imagen

  • Ejecución de pruebas

  • Publicación de la imagen

  • Despliegue automatizado en el clúster

🔗 Al final del artículo encontrarás el enlace al repositorio con todo el código y configuración detallada.


Contexto

Este mini proyecto te dará una pequeña mirada a la integración continua, conociendo procesos y configuraciones básicas.
Permite automatizar los commits, construir, probar y desplegar en producción sin intervención manual. Además, ayuda a familiarizarse con el flujo real de trabajo de DevOps y Kubernetes.


Tecnologías y herramientas

  • 🐍 Python 3.11 + Flask + Gunicorn

  • 🐳 Docker + DockerHub

  • 🤖 GitLab CI/CD con runners personalizados

  • ☁️ Kubernetes (2 nodos en DigitalOcean)

  • 🔄 Service tipo LoadBalancer


Arquitectura e infraestructura

La infraestructura se compone de:

  • 🖥️ VM dedicada (DigitalOcean): ejecuta los jobs de build y test, tiene un runner shell con Docker instalado.

  • ☸️ Clúster Kubernetes: con 2 nodos, donde se hace el deploy de la aplicación.

  • 🌐 Service LoadBalancer: expone la API mediante una IP pública accesible desde internet.

Diagrama de flujo

Flujo general:

  1. Push de código: El desarrollador sube cambios al repositorio GitLab.

  2. GitLab CI activa un pipeline: Usando el archivo .gitlab-ci.yml, se definen las etapas de build, test y deploy.

  3. Runner ejecuta el pipeline: El runner (basado en Ubuntu) construye la imagen Docker, corre pruebas y publica la imagen en DockerHub.

  4. Despliegue en Kubernetes: Kubernetes obtiene la imagen desde DockerHub y la despliega en sus nodos (nodo1, nodo2).

  5. Balanceo de carga: Un Service LoadBalancer distribuye las peticiones entre los nodos. El cliente accede a la aplicación vía HTTP sin preocuparse de dónde está corriendo.


Pipeline CI/CD paso a paso

🔨 Build

¿Qué hace?
Construye la imagen Docker del proyecto Flask y la sube a DockerHub, asegurando trazabilidad con el hash del commit ($CI_COMMIT_SHORT_SHA) y disponibilidad rápida con la etiqueta latest.

build_job:
  stage: build              # Etapa de construcción de imagen
  tags: 
    - test                  # Se ejecuta en el runner de test (VM)

  script:
    - echo "Build"

    # Autenticación con DockerHub usando variables protegidas
    - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USER" --password-stdin

    # Construcción de la imagen con dos tags: el hash corto del commit y latest
    - docker build -t $DOCKER_IMAGE:$CI_COMMIT_SHORT_SHA -t $DOCKER_IMAGE:latest .

    # Subida de ambas versiones a DockerHub
    - docker push $DOCKER_IMAGE:$CI_COMMIT_SHORT_SHA
    - docker push $DOCKER_IMAGE:latest

    - echo "Imagen $DOCKER_IMAGE:$CI_COMMIT_SHORT_SHA y latest subida"

🧪 Test

¿Qué hace?
Ejecuta pruebas funcionales contra la imagen recién construida, sin exponer puertos públicamente, validando que los endpoints funcionen correctamente.

test_job:
  stage: test                   # Etapa de pruebas
  tags: 
    - test                      # Se ejecuta en el runner de test (VM DigitalOcean)

  script:
    - docker pull $DOCKER_IMAGE:latest  # Descarga la imagen desde DockerHub
    - docker run -d --name $APP_NAME $DOCKER_IMAGE:latest  # Corre el contenedor en segundo plano

    # Obtiene la IP interna del contenedor
    - IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $APP_NAME)

    # Espera a que la app esté lista
    - |
      for i in {1..10}; do
        if curl --silent --fail http://$IP:5000/; then
          echo "App lista!"
          break
        else
          echo "Esperando la app..."
          sleep 3
        fi
      done

    # Verifica endpoint raíz
    - |
      echo "Validando endpoint raíz..."
      curl --fail http://$IP:5000/ | grep "Hola" || (echo "Test falló" && exit 1)

    # Verifica endpoint con parámetro
    - |
      echo "Validando endpoint saludo..."
      curl --fail http://$IP:5000/saludo/Sady | grep "Hola, Sady" || (echo "Test falló" && exit 1)

    - echo "Test OK"

    # Limpieza
    - docker stop $APP_NAME || true 
    - docker rm $APP_NAME || true

🚀 Deploy

¿Qué hace?
Este job conecta con tu cluster Kubernetes usando un kubeconfig cargado desde una variable de CI/CD, y luego despliega tu aplicación aplicando el manifiesto deployment.yaml.

deploy_job:
  stage: deploy                 # Etapa del pipeline
  tags:
    - deploy                    # Runner asignado (Kubernetes)

  image: bitnami/kubectl:latest # Imagen con kubectl para usar en el job

  script:
    # Cargar configuración del cluster desde variable segura
    - echo "$KUBECONFIG_CONTENT" > kubeconfig
    - export KUBECONFIG=$PWD/kubeconfig

    # Aplicar el Deployment en Kubernetes
    - kubectl apply -f deployment.yaml

Kubernetes Deployment y Service

Para desplegar la app, registré previamente en DigitalOcean:

  • Una VM Ubuntu para correr el runner de build/test.

  • Un clúster Kubernetes con 2 nodos para el entorno de producción.

Antes de aplicar el deployment, configuré el Service tipo LoadBalancer para el namespace gitlab-runner.

deployment.yaml

apiVersion: apps/v1             # Versión de la API de Kubernetes para Deployments
kind: Deployment                # Tipo de recurso: Deployment

metadata:
  name: helloapp-deployment     # Nombre del Deployment
  namespace: gitlab-runner      # Namespace donde se despliega
  labels:
    app: helloapp               # Etiqueta para agrupar recursos

spec:
  replicas: 2                   # Número de réplicas (2 pods en paralelo)
  selector:
    matchLabels:
      app: helloapp             # Selección de pods por etiqueta

  template:
    metadata:
      labels:
        app: helloapp           # Etiqueta aplicada a los pods generados
    spec:
      containers:
      - name: helloapp
        image: bespana/helloapp:latest  # Imagen Docker a usar
        imagePullPolicy: Always         # Siempre intenta descargar la última versión
        ports:
        - containerPort: 5000           # Puerto expuesto por la app Flask

        # Límites y requests de recursos
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "250m"
            memory: "256Mi"

        # Probes para salud y disponibilidad
        readinessProbe:                # Verifica si el pod está listo para recibir tráfico
          httpGet:
            path: /
            port: 5000
          initialDelaySeconds: 5
          periodSeconds: 10

        livenessProbe:                 # Verifica si el pod sigue vivo
          tcpSocket:
            port: 5000
          initialDelaySeconds: 15
          periodSeconds: 20

service.yaml

apiVersion: v1                  # Versión de la API para Service
kind: Service                   # Tipo de recurso: Service

metadata:
  name: helloapp-service        # Nombre del servicio
  namespace: gitlab-runner      # Namespace donde se encuentra

spec:
  selector:
    app: helloapp               # Asocia este servicio a los pods con esta etiqueta
  type: LoadBalancer            # Expone la app con una IP pública (ideal para producción)
  ports:
  - protocol: TCP
    port: 80                    # Puerto accesible desde el exterior
    targetPort: 5000            # Puerto interno de la app Flask

Verificación del balanceo de carga

Después del despliegue, puedes hacer pruebas públicas usando la IP del LoadBalancer asignada por DigitalOcean:

kubectl get svc -n gitlab-runner

curl (ip-asignada):80

Esto muestra cómo el LoadBalancer distribuye el tráfico entre los pods.


Resultados y aprendizajes

  • Aprendí lo necesario para levantar y operar un clúster Kubernetes básico.

  • Comprendí cómo conectar un runner GitLab con un clúster K8s.

  • Practiqué cómo escribir pipelines funcionales.

  • Vi el poder del LoadBalancer en acción balanceando tráfico entre réplicas.


Conclusión

Este tipo de proyecto no toma demasiado tiempo y es ideal para aprender conceptos clave de DevOps, CI/CD y Kubernetes.
¡Te animo a que lo intentes por tu cuenta!
Empieza por algo pequeño y verás cuánto puedes avanzar.


Referencias y enlaces