---
name: kubernetes-orquestacion
description: Kubernetes en producción. Deployments, Services, Ingress, ConfigMaps, Secrets, HPA, PDB, NetworkPolicies, Helm, Kustomize. Patrones de deployment blue-green y canary. Troubleshooting común.
version: "1.0.0"
herramientasPermitidas: [Read]
exclusiones:
  - "No cargar para Dockerfiles y containerización de la imagen — para Docker cargar `contenedores-docker`."
  - "No cargar para Terraform, Pulumi o infraestructura como código — para IaC cargar `terraform-experto`."
  - "No cargar para service mesh (Istio, Linkerd) en profundidad o GitOps (ArgoCD, Flux) — este skill cubre los objetos core de Kubernetes, no plataformas de nivel superior."
  - "No cargar para cloud-specific Kubernetes (EKS, AKS, GKE) y sus integraciones IAM específicas — para cloud cargar `cloud-aws`, `azure-cloud` o `gcp-cloud`."
evolvable: true  # default para skill estandar
---
# Kubernetes — Orquestación en Producción

## Cuándo NO cargar

- La tarea es construir la imagen Docker: Dockerfile, multi-stage build — cargar `contenedores-docker`.
- El trabajo es Terraform o Pulumi para aprovisionar el cluster — cargar `terraform-experto`.
- El tema es service mesh (Istio, Linkerd) o GitOps (ArgoCD, Flux) en profundidad — este skill cubre los objetos core de Kubernetes.
- La tarea es específica de EKS/AKS/GKE: integraciones IAM, node pools, addons — cargar `cloud-aws`, `azure-cloud` o `gcp-cloud`.

Kubernetes es la plataforma estándar de orquestación de containers. Este skill cubre
los objetos principales, patrones de despliegue seguro y troubleshooting efectivo.

---

## Reglas Obligatorias

1. **NUNCA `:latest` en producción** — usar tag de versión exacta.
2. **`resources.requests` y `resources.limits`** en TODOS los containers.
3. **`livenessProbe` + `readinessProbe`** en todos los pods de producción.
4. **`startupProbe`** para apps con arranque lento (>30s).
5. **`runAsNonRoot: true`** + `readOnlyRootFilesystem: true` en securityContext.
6. **`maxUnavailable: 0`** en RollingUpdate para zero-downtime.
7. **PodDisruptionBudget** OBLIGATORIO para servicios críticos.
8. **NetworkPolicy default-deny** + permisos explícitos (zero-trust).
9. **Secrets** gestionados con External Secrets Operator o Sealed Secrets.
10. **NUNCA Service tipo LoadBalancer directamente** — usar Ingress.

---

## Deployment — Configuración Clave

```yaml
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0        # Zero downtime
  template:
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1001
      containers:
        - name: api
          image: registry.com/app:1.5.2   # NUNCA :latest
          resources:
            requests: { cpu: "100m", memory: "256Mi" }
            limits:   { cpu: "500m", memory: "512Mi" }
          livenessProbe:
            httpGet: { path: /health/live, port: http }
          readinessProbe:
            httpGet: { path: /health/ready, port: http }
```

---

## PodDisruptionBudget

```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: api-nomina
```

---

## Deployment Strategies

| Estrategia | Riesgo | Rollback | Complejidad |
|-----------|--------|----------|-------------|
| Rolling Update | Bajo | kubectl rollout undo | Baja |
| Blue-Green | Muy bajo | Cambiar selector del Service | Media |
| Canary | Mínimo | Eliminar canary deployment | Alta |

**Blue-Green**: dos deployments (blue=actual, green=nueva). Cambiar el Service selector
para switch instantáneo. Rollback = volver al selector anterior.

**Canary**: deployment pequeño (1 pod de 10 = 10% tráfico) con Ingress canary annotation
`nginx.ingress.kubernetes.io/canary-weight: "10"`.

---

## Helm — Comandos Esenciales

```bash
helm lint ./charts/mi-app
helm template mi-app ./charts/mi-app -f values-prod.yaml     # Preview
helm upgrade --install mi-app ./charts/mi-app \
  --namespace prod --create-namespace \
  -f values-prod.yaml --set image.tag=1.5.2 --wait --timeout 5m
helm rollback mi-app 3 --namespace prod                      # Rollback
```

---

## Troubleshooting Rápido

| Síntoma | Comando | Causa común |
|---------|---------|-------------|
| Pod Pending | `kubectl describe pod` | Recursos insuficientes |
| CrashLoopBackOff | `kubectl logs --previous` | Error en la app |
| Sin tráfico | `kubectl get endpoints` | Selector no coincide |
| Lento | `kubectl top pods` | Recursos limit muy bajo |

---

## Checklist de Deployment Productivo

- [ ] `resources.requests` y `limits` definidos
- [ ] `livenessProbe` y `readinessProbe` configurados
- [ ] `startupProbe` para apps lentas
- [ ] PodDisruptionBudget para servicios críticos
- [ ] `runAsNonRoot: true` en securityContext
- [ ] `readOnlyRootFilesystem: true`
- [ ] NetworkPolicy default-deny + permisos explícitos
- [ ] HPA con métricas reales
- [ ] `maxUnavailable: 0` en RollingUpdate
- [ ] `podAntiAffinity` para distribuir en nodos distintos
- [ ] Secrets con External Secrets Operator
- [ ] Imagen con tag exacto (nunca `latest`)

Para manifiestos YAML completos (Deployment, Service, Ingress, HPA, NetworkPolicy, Helm, Blue-Green, Canary, troubleshooting), ver [recursos/manifiestos-completos.md](recursos/manifiestos-completos.md).

## Gotchas / Errores comunes no obvios

**`livenessProbe` con un threshold muy bajo mata pods sanos durante GC o inicialización**: si `livenessProbe` tiene `failureThreshold: 1` y `periodSeconds: 5`, una pausa de GC de 6 segundos en la JVM o Python reinicia el pod aunque la aplicación esté sana. Causa: el liveness probe no distingue entre "proceso muerto" y "proceso temporalmente ocupado". Fix: configurar `failureThreshold: 3` con `periodSeconds: 10` como mínimo, y usar `startupProbe` para aplicaciones con arranque lento — el `startupProbe` desactiva liveness/readiness hasta que la app esté lista.

**`resources.limits.memory` demasiado bajo causa OOMKilled silencioso que se ve como crash**: un pod con `limits.memory: 128Mi` y una carga que requiere 130Mi es terminado por el kernel con OOM sin mensaje claro — `kubectl describe pod` muestra `OOMKilled` solo en el campo `lastState`. Causa: los límites de memoria en Kubernetes se implementan con cgroups — exceder el límite termina el proceso inmediatamente. Fix: establecer limits basados en mediciones reales con `kubectl top pods` en carga real, dejando al menos 20% de margen sobre el consumo máximo observado.

**HPA con CPU como métrica no escala correctamente si los `resources.requests.cpu` no están bien calibrados**: el HPA escala cuando el CPU promedio supera el `targetCPUUtilizationPercentage` relativo a `requests.cpu`. Si `requests.cpu: "10m"` y la app usa "500m", el HPA ve 5000% de uso y escala agresivamente aunque haya capacidad. Causa: el HPA calcula utilización como `uso_actual / requests`, no sobre el nodo. Fix: calibrar `requests.cpu` al uso promedio real de la aplicación en carga normal — usar `kubectl top pods` en producción para obtener los valores correctos.

**`kubectl rollout undo` revierte al deployment anterior pero no revierte ConfigMaps ni Secrets referenciados**: si el deployment v2 usa `ConfigMap v2` y se hace rollout undo al deployment v1, el pod v1 puede iniciar con el `ConfigMap v2` si los pods usan el mismo nombre de ConfigMap. Causa: el rollout undo solo cambia la especificación del Deployment, no de los recursos referenciados. Fix: versionar los ConfigMaps con el mismo tag que el deployment (`config-api-v1`, `config-api-v2`) y actualizar la referencia en el Deployment — el rollout undo revertirá a la referencia al ConfigMap correcto.
