← Volver al listado de tecnologías

Capítulo 23: Deployment en Kubernetes

Por: SiempreListo
sagakubernetesdeploymenthelmdevops

Capítulo 23: Deployment en Kubernetes

“Kubernetes: orquestando servicios que orquestan sagas”

Introduccion

Hemos construido sagas con persistencia, mensajeria, frontend y observabilidad. Ahora necesitamos desplegar todo esto en produccion. Kubernetes (K8s) es la plataforma de orquestacion de contenedores mas popular, ideal para microservicios.

Kubernetes proporciona:

Este capitulo cubre la configuracion de Kubernetes para nuestro sistema de sagas.

Arquitectura en Kubernetes

El diagrama muestra los componentes principales. El Ingress es el punto de entrada; los Services exponen pods; las bases de datos corren dentro del cluster o como servicios externos.

graph TB
    subgraph "Kubernetes Cluster"
        ING[Ingress] --> API[API Gateway]
        API --> OS[Order Service]
        API --> IS[Inventory Service]
        API --> PS[Payment Service]

        OS --> RMQ[RabbitMQ]
        IS --> RMQ
        PS --> RMQ

        OS --> PG[(PostgreSQL)]
        IS --> PG
        PS --> PG

        OS --> RD[(Redis)]
    end

Deployment de Order Service

Un Deployment define como desplegar y mantener una aplicacion:

Las probes son cruciales:

# k8s/order-service/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  labels:
    app: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9090"
    spec:
      containers:
        - name: order-service
          image: orderflow/order-service:latest
          ports:
            - containerPort: 3000
            - containerPort: 9090
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: order-secrets
                  key: database-url
            - name: RABBITMQ_URL
              valueFrom:
                secretKeyRef:
                  name: order-secrets
                  key: rabbitmq-url
            - name: REDIS_URL
              valueFrom:
                configMapKeyRef:
                  name: order-config
                  key: redis-url
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health/live
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 5

Service y ConfigMap

Un Service expone pods a otros componentes del cluster:

Un ConfigMap almacena configuracion no sensible como pares clave-valor. Los pods pueden montar ConfigMaps como variables de entorno o archivos.

La separacion de configuracion permite cambiarla sin reconstruir la imagen del contenedor.

# k8s/order-service/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - name: http
      port: 80
      targetPort: 3000
    - name: metrics
      port: 9090
      targetPort: 9090
---
# k8s/order-service/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: order-config
data:
  redis-url: "redis://redis:6379"
  log-level: "info"
  saga-timeout: "300"

Secrets

Un Secret almacena datos sensibles (passwords, tokens, certificados) de forma segura. A diferencia de ConfigMaps:

En produccion, considera usar soluciones externas como HashiCorp Vault o AWS Secrets Manager para mayor seguridad.

Nota: El ejemplo usa stringData para facilidad; en produccion, evita commitear secrets al repositorio.

# k8s/order-service/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: order-secrets
type: Opaque
stringData:
  database-url: "postgresql://user:pass@postgres:5432/orders"
  rabbitmq-url: "amqp://user:pass@rabbitmq:5672"

HorizontalPodAutoscaler

El HPA (HorizontalPodAutoscaler) escala automaticamente el numero de replicas basandose en metricas:

El ejemplo escala basandose en:

  1. CPU > 70%: Indica que los pods necesitan ayuda
  2. saga_active_count > 50: Metrica custom de Prometheus

Cuando las metricas exceden los umbrales, HPA crea nuevos pods. Cuando bajan, los elimina gradualmente.

# k8s/order-service/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Pods
      pods:
        metric:
          name: saga_active_count
        target:
          type: AverageValue
          averageValue: "50"

RabbitMQ con Operator

Un Operator es un patron de Kubernetes que extiende la API para manejar aplicaciones complejas. El RabbitMQ Operator:

La configuracion define:

Sin el operator, configurar un cluster RabbitMQ requeriria decenas de manifiestos YAML.

# k8s/rabbitmq/cluster.yaml
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
  name: rabbitmq
spec:
  replicas: 3
  resources:
    requests:
      cpu: "500m"
      memory: "1Gi"
    limits:
      cpu: "1"
      memory: "2Gi"
  rabbitmq:
    additionalConfig: |
      vm_memory_high_watermark.relative = 0.8
  persistence:
    storageClassName: standard
    storage: "10Gi"

PostgreSQL con CloudNativePG

CloudNativePG es un operator para PostgreSQL disenado para Kubernetes. Proporciona:

La configuracion instances: 3 crea un cluster con 1 primary y 2 replicas. Si el primary falla, una replica se promueve automaticamente.

bootstrap.initdb configura la base de datos inicial que se crea al arrancar el cluster.

# k8s/postgres/cluster.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: postgres-cluster
spec:
  instances: 3
  storage:
    size: 20Gi
  postgresql:
    parameters:
      max_connections: "200"
      shared_buffers: "256MB"
  bootstrap:
    initdb:
      database: orderflow
      owner: orderflow

Helm Chart

Helm es el gestor de paquetes de Kubernetes. Un Chart empaqueta todos los manifiestos necesarios para una aplicacion, con valores configurables.

Beneficios de Helm:

El archivo values.yaml define valores por defecto que se pueden sobrescribir por ambiente.

# helm/orderflow/values.yaml
global:
  environment: production

orderService:
  replicaCount: 3
  image:
    repository: orderflow/order-service
    tag: "1.0.0"
  resources:
    requests:
      memory: "256Mi"
      cpu: "250m"
  autoscaling:
    enabled: true
    minReplicas: 2
    maxReplicas: 10

inventoryService:
  replicaCount: 2
  image:
    repository: orderflow/inventory-service
    tag: "1.0.0"

paymentService:
  replicaCount: 2
  image:
    repository: orderflow/payment-service
    tag: "1.0.0"

rabbitmq:
  enabled: true
  replicas: 3

postgresql:
  enabled: true
  replicas: 3

redis:
  enabled: true
  sentinel:
    enabled: true

Ingress

El Ingress expone servicios HTTP/HTTPS al exterior del cluster:

Las annotations configuran el Ingress Controller (nginx en este caso):

Sin Ingress, cada servicio necesitaria su propio LoadBalancer externo (costoso).

# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: orderflow-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - api.orderflow.com
      secretName: orderflow-tls
  rules:
    - host: api.orderflow.com
      http:
        paths:
          - path: /orders
            pathType: Prefix
            backend:
              service:
                name: order-service
                port:
                  number: 80
          - path: /inventory
            pathType: Prefix
            backend:
              service:
                name: inventory-service
                port:
                  number: 80

CI/CD con GitHub Actions

CI/CD (Continuous Integration/Continuous Deployment) automatiza el proceso de construir, probar y desplegar codigo.

El workflow de GitHub Actions:

  1. Trigger: Se ejecuta en cada push a main
  2. Build and push: Construye imagen Docker y la sube al registry
  3. Deploy: Aplica los manifiestos de Kubernetes con la nueva imagen

El uso del SHA del commit (${{ github.sha }}) como tag de imagen garantiza que cada despliegue usa una imagen unica y trazable.

En produccion, considera agregar:

# .github/workflows/deploy.yml
name: Deploy to Kubernetes

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: orderflow/order-service:${{ github.sha }}

      - name: Deploy to K8s
        uses: azure/k8s-deploy@v4
        with:
          manifests: k8s/
          images: orderflow/order-service:${{ github.sha }}

Resumen

Glosario

Kubernetes (K8s)

Definicion: Plataforma open-source de orquestacion de contenedores que automatiza el despliegue, escalado y operacion de aplicaciones containerizadas.

Por que es importante: Es el estandar de facto para ejecutar microservicios en produccion, proporcionando abstracciones para networking, storage, scheduling y self-healing.

Ejemplo practico: Desplegamos 3 replicas del order-service. Kubernetes distribuye los pods entre nodos, reinicia los que fallan, y balancea trafico automaticamente.


Pod

Definicion: Unidad mas pequena en Kubernetes, consistente en uno o mas contenedores que comparten networking y storage.

Por que es importante: Los pods son efimeros - Kubernetes los crea y destruye constantemente. Los Deployments abstraen esta complejidad manteniendo el numero deseado de replicas.

Ejemplo practico: Un pod order-service-abc123 ejecuta el contenedor de la aplicacion. Si el nodo falla, Kubernetes crea un nuevo pod en otro nodo.


Deployment

Definicion: Recurso de Kubernetes que define el estado deseado de una aplicacion (imagen, replicas, configuracion) y lo mantiene automaticamente.

Por que es importante: Proporciona actualizaciones declarativas - describes el estado final y Kubernetes hace la transicion gradualmente, con rollback automatico si falla.

Ejemplo practico: Cambias la imagen de v1.0 a v1.1. Kubernetes crea nuevos pods con v1.1, verifica que estan sanos, y luego termina los pods v1.0.


Service (Kubernetes)

Definicion: Abstraccion que define un conjunto logico de pods y una politica de acceso a ellos, proporcionando una IP estable y balanceo de carga.

Por que es importante: Los pods tienen IPs efimeras que cambian al reiniciarse. El Service proporciona un punto de acceso estable por nombre DNS.

Ejemplo practico: order-service siempre resuelve a la IP del Service. Este distribuye trafico entre todos los pods sanos, sin importar cuantos hay o sus IPs.


ConfigMap

Definicion: Recurso de Kubernetes para almacenar configuracion no sensible como pares clave-valor, que los pods pueden consumir como variables de entorno o archivos.

Por que es importante: Separa la configuracion del codigo, permitiendo cambiar comportamiento sin reconstruir imagenes.

Ejemplo practico: LOG_LEVEL=debug en desarrollo, LOG_LEVEL=info en produccion. Misma imagen, diferente ConfigMap.


Secret

Definicion: Recurso de Kubernetes para almacenar datos sensibles (passwords, tokens, claves) con mecanismos de seguridad adicionales.

Por que es importante: Evita hardcodear secretos en imagenes o codigo, y proporciona control de acceso granular sobre quien puede ver los valores.

Ejemplo practico: La password de PostgreSQL se almacena en un Secret. El pod la recibe como variable de entorno sin que aparezca en logs o manifiestos.


HorizontalPodAutoscaler (HPA)

Definicion: Controlador de Kubernetes que ajusta automaticamente el numero de replicas de un Deployment basandose en metricas observadas.

Por que es importante: Escala la aplicacion segun demanda real, ahorrando recursos en periodos tranquilos y manejando picos de trafico automaticamente.

Ejemplo practico: Durante Black Friday, CPU sube a 80%. HPA escala de 3 a 10 pods en minutos. Despues, trafico baja y HPA reduce a 3 pods.


Operator (Kubernetes)

Definicion: Patron que extiende Kubernetes con logica de dominio especifica, automatizando tareas de operacion complejas para aplicaciones estateful.

Por que es importante: Convierte conocimiento operacional (como escalar PostgreSQL, hacer backups) en codigo que Kubernetes ejecuta automaticamente.

Ejemplo practico: El CloudNativePG Operator detecta que el primary PostgreSQL fallo, promueve una replica, y actualiza las conexiones - todo automatico.


Helm

Definicion: Gestor de paquetes para Kubernetes que empaqueta aplicaciones como “charts” con templating y gestion de releases.

Por que es importante: Simplifica deployments complejos, permite reutilizar configuraciones, y proporciona versionado con rollback facil.

Ejemplo practico: helm upgrade orderflow ./chart -f prod-values.yaml actualiza toda la aplicacion (5 servicios, 3 DBs) con un solo comando.


Ingress

Definicion: Recurso de Kubernetes que gestiona acceso HTTP/HTTPS externo a servicios dentro del cluster, con ruteo basado en host/path.

Por que es importante: Un solo punto de entrada para multiples servicios, con TLS terminacion, reduciendo costos de LoadBalancers externos.

Ejemplo practico: api.example.com/orders va al order-service, /payments al payment-service. Un solo certificado SSL para todo el dominio.


CI/CD

Definicion: Continuous Integration (merge frecuente de codigo con tests automaticos) y Continuous Deployment (despliegue automatico a produccion tras pasar tests).

Por que es importante: Reduce tiempo entre escribir codigo y tenerlo en produccion, detecta problemas temprano, y elimina errores de despliegue manual.

Ejemplo practico: Push a main -> tests corren -> imagen se construye -> se despliega a staging -> tests E2E pasan -> se despliega a produccion. Todo en 15 minutos, sin intervencion.


← Capítulo 22: Observabilidad | Volver al Indice →