Artículos

Un baseline de producción para workloads en Kubernetes

Requests, probes, disruption budgets, apagado ordenado y seguridad de pods: la configuración que un Deployment de producción debería tener antes de su primer incidente.

Los ejemplos son genéricos e ilustrativos. No provienen del entorno de ningún cliente: adapte umbrales y configuraciones a su propio sistema.

Por qué un baseline

Kubernetes ejecuta casi cualquier cosa con casi ninguna configuración, y ese es el problema: los valores por defecto están pensados para empezar, no para seguir en pie. La configuración de abajo cuesta poco el primer día y mucho si se descubre durante una caída. Se aplica a Deployments sin estado; las cargas con estado necesitan más.

Requests y limits

El scheduler ubica los pods según sus requests, y el Horizontal Pod Autoscaler mide la utilización contra ellos. Un pod sin requests se ubica a ciegas y no puede escalar por CPU.

La memoria no se puede comprimir: un contenedor que supera su límite de memoria es terminado. Fije el límite de memoria igual o cercano al request, para que un pod nunca se programe con memoria que no puede conservar. La CPU es distinta — un contenedor por encima de su límite de CPU sufre throttling, que se nota como latencia. Muchos equipos definen requests de CPU y omiten el límite de CPU en servicios sensibles a la latencia; elija lo que elija, mida el throttling.

Tres probes, tres funciones

  • Readiness decide si el pod recibe tráfico. Cuidado con las dependencias compartidas: si todos los pods fallan la readiness a la vez, el servicio se queda sin ningún endpoint.
  • Liveness decide si el contenedor se reinicia. Limítela al propio proceso: si comprueba la base de datos, una caída de la base reinicia todos los pods a la vez.
  • Startup retiene las otras probes mientras arranca una aplicación lenta, en lugar de inflar el retraso de la liveness.

Sobrevivir a drenajes de nodos y caídas de zona

Las actualizaciones de nodos y el autoescalado desalojan pods constantemente. Un PodDisruptionBudget limita cuántas réplicas puede tumbar a la vez una interrupción voluntaria, y las topology spread constraints mantienen las réplicas en zonas distintas, para que la caída de una zona no arrastre todo el servicio.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api
spec:
  maxUnavailable: 1
  selector:
    matchLabels: { app: api }

Apagado ordenado

Cuando se elimina un pod, su retirada de los endpoints del servicio y el apagado de sus contenedores (el hook preStop y después SIGTERM) empiezan a la vez. Una aplicación que termina de inmediato pierde solicitudes que todavía se le están enrutando. Añada una pausa corta en preStop para que los balanceadores se pongan al día, gestione SIGTERM terminando el trabajo en curso y mantenga terminationGracePeriodSeconds por encima de la pausa más el drenaje.

Contexto de seguridad e imágenes

Ejecute con un usuario no root, elimine todas las capabilities de Linux, bloquee la escalada de privilegios y monte el sistema de archivos raíz en solo lectura; el Pod Security Standard restricted impone la mayor parte de esto por namespace. Referencie las imágenes por digest y no por una etiqueta mutable, para que lo probado sea exactamente lo que se ejecuta.

Todo junto

Un Deployment con el baseline aplicado. Los valores son ilustrativos: dimensione los requests a partir del uso medido, no de este ejemplo.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels: { app: api }
  template:
    metadata:
      labels: { app: api }
    spec:
      terminationGracePeriodSeconds: 45
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels: { app: api }
      securityContext:
        runAsNonRoot: true
        seccompProfile: { type: RuntimeDefault }
      containers:
        - name: api
          image: registry.example.com/api@sha256:<digest>
          resources:
            requests: { cpu: 250m, memory: 256Mi }
            limits: { memory: 256Mi }
          readinessProbe:
            httpGet: { path: /ready, port: 8080 }
            periodSeconds: 5
          livenessProbe:
            httpGet: { path: /live, port: 8080 }
            periodSeconds: 10
            failureThreshold: 3
          startupProbe:
            httpGet: { path: /live, port: 8080 }
            periodSeconds: 5
            failureThreshold: 30
          lifecycle:
            preStop:
              sleep: { seconds: 5 }   # native sleep action, Kubernetes 1.30+
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities: { drop: ["ALL"] }
Contacto

¿Quiere una segunda opinión sobre su entorno?

Envíe una descripción breve de su sistema. Un ingeniero sénior responde por escrito en un día hábil.

Hablemos de su infraestructura