Aprende orquestación de contenedores desde cero hasta producción. Arquitectura, despliegues, redes, seguridad y más.
Un viaje completo desde los fundamentos hasta producción avanzada
Qué es, por qué usarlo y conceptos fundamentales de orquestación.
Control Plane, Worker Nodes, etcd, API Server y componentes clave.
La unidad básica de despliegue. Ciclo de vida, configuración y multi-container.
Deployments, StatefulSets, DaemonSets, Jobs y CronJobs explicados.
ClusterIP, NodePort, LoadBalancer, Ingress y DNS interno.
PersistentVolumes, PVC, StorageClass y gestión de datos.
Roles, ClusterRoles, ServiceAccounts, NetworkPolicies y PodSecurity.
HPA, VPA, Node Affinity, Taints/Tolerations y Pod Disruption.
Gestión de configuración y datos sensibles de forma segura.
Charts, Templates, Repositorios y despliegues reproducibles.
Prometheus, Grafana, ELK Stack y alertas en producción.
GitOps, ArgoCD, estrategias de despliegue y mejores prácticas.
Kubernetes (K8s) es una plataforma open-source de orquestación de contenedores, originalmente desarrollada por Google y ahora mantenida por la CNCF. Su nombre proviene del griego "timonel" o "piloto". Se ocupa de automatizar el despliegue, escalado y gestión de aplicaciones en contenedores.
Antes de Kubernetes, desplegar aplicaciones en múltiples servidores era una tarea manual y propensa a errores. K8s resuelve problemas reales:
# Instalar minikube en Linux curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 sudo install minikube-linux-amd64 /usr/local/bin/minikube # Instalar kubectl curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl # Iniciar cluster local minikube start --driver=docker --cpus=2 --memory=4096 # Verificar kubectl cluster-info kubectl get nodes
# Ver recursos del cluster kubectl get nodes kubectl get pods --all-namespaces kubectl get services # Información detallada kubectl describe node <nombre-nodo> kubectl describe pod <nombre-pod> # Ver logs kubectl logs <pod-name> kubectl logs -f <pod-name> # en tiempo real # Ejecutar comando dentro de un pod kubectl exec -it <pod-name> -- /bin/bash
Un cluster de Kubernetes está compuesto por dos tipos de nodos: el Control Plane (cerebro del cluster) y los Worker Nodes (donde corren las aplicaciones).
| Componente | Función |
|---|---|
| kube-apiserver | Punto de entrada de todas las solicitudes REST. Frontend del Control Plane. |
| etcd | Base de datos key-value distribuida. Almacena todo el estado del cluster. |
| kube-scheduler | Decide en qué nodo se ejecuta cada Pod nuevo. |
| controller-manager | Ejecuta controladores que regulan el estado del cluster. |
| cloud-controller | Integra el cluster con el proveedor de nube (AWS, GCP, Azure). |
| Componente | Función |
|---|---|
| kubelet | Agente que corre en cada nodo. Asegura que los contenedores estén corriendo. |
| kube-proxy | Mantiene las reglas de red. Permite la comunicación entre Pods y Servicios. |
| Container Runtime | Software que ejecuta contenedores (containerd, CRI-O, Docker). |
El Pod es la unidad más pequeña en Kubernetes. Representa uno o más contenedores que comparten red y almacenamiento. Los Pods son efímeros: si mueren, no se recrean solos (para eso existen los Deployments).
apiVersion: v1 kind: Pod metadata: name: mi-pod labels: app: web version: "1.0" spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80 resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m"
Las probes permiten a Kubernetes saber si tu aplicación está sana y lista para recibir tráfico:
livenessProbe: # reinicia el pod si falla httpGet: path: /healthz port: 8080 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 3 readinessProbe: # retira del balanceo si falla httpGet: path: /ready port: 8080 initialDelaySeconds: 5 periodSeconds: 5 startupProbe: # para apps lentas al arrancar httpGet: path: /healthz port: 8080 failureThreshold: 30 periodSeconds: 10
| Fase | Descripción |
|---|---|
| Pending | Fue aceptado pero aún no está corriendo (descargando imagen, etc.) |
| Running | Al menos un contenedor está corriendo |
| Succeeded | Todos los contenedores terminaron con éxito (exit 0) |
| Failed | Al menos un contenedor falló (exit != 0) |
| Unknown | No se puede obtener el estado (problema de red con el nodo) |
El Deployment es el recurso más común. Gestiona ReplicaSets para garantizar que siempre haya N copias de tu Pod corriendo.
apiVersion: apps/v1 kind: Deployment metadata: name: mi-app namespace: production spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 selector: matchLabels: app: mi-app template: metadata: labels: app: mi-app spec: containers: - name: app image: myrepo/mi-app:v2.0 ports: - containerPort: 3000
# Escalar deployment kubectl scale deployment mi-app --replicas=5 # Rolling update de imagen kubectl set image deployment/mi-app app=myrepo/mi-app:v3.0 # Ver historial de rollouts kubectl rollout history deployment/mi-app # Rollback a versión anterior kubectl rollout undo deployment/mi-app # Rollback a revisión específica kubectl rollout undo deployment/mi-app --to-revision=2
Para aplicaciones que necesitan identidad estable y almacenamiento persistente (bases de datos, caches distribuidos).
apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres spec: serviceName: "postgres" replicas: 3 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:15 volumeMounts: - name: data mountPath: /var/lib/postgresql/data volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 10Gi
Garantiza que todos los nodos ejecuten una copia del Pod. Ideal para agentes de monitoreo, logging y networking.
apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd-logging spec: selector: matchLabels: name: fluentd template: metadata: labels: name: fluentd spec: containers: - name: fluentd image: fluent/fluentd-kubernetes-daemonset:v1 volumeMounts: - name: varlog mountPath: /var/log volumes: - name: varlog hostPath: path: /var/log
apiVersion: batch/v1 kind: CronJob metadata: name: backup-db spec: schedule: "0 2 * * *" # cada día a las 2am jobTemplate: spec: template: spec: containers: - name: backup image: postgres:15 command: ["/bin/sh", "-c", "pg_dump $DATABASE_URL | gzip > /backup/db.sql.gz"] restartPolicy: OnFailure
Un Service es una abstracción que expone una aplicación corriendo en Pods. Proporciona una dirección IP estable y balanceo de carga.
| Tipo | Acceso | Caso de uso |
|---|---|---|
| ClusterIP | Solo interno al cluster | Comunicación entre microservicios |
| NodePort | IP del nodo + puerto | Desarrollo, pruebas |
| LoadBalancer | IP pública (cloud) | Exponer app al exterior |
| ExternalName | Alias DNS externo | Acceder a servicios externos |
# ClusterIP (por defecto) apiVersion: v1 kind: Service metadata: name: mi-servicio spec: type: ClusterIP selector: app: mi-app ports: - protocol: TCP port: 80 targetPort: 3000
Ingress gestiona el acceso externo HTTP/HTTPS a los servicios del cluster, típicamente con un Ingress Controller (nginx, traefik, etc.).
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: mi-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / cert-manager.io/cluster-issuer: letsencrypt-prod spec: ingressClassName: nginx tls: - hosts: [app.midominio.com] secretName: tls-secret rules: - host: app.midominio.com http: paths: - path: /api pathType: Prefix backend: service: name: api-service port: number: 80 - path: / pathType: Prefix backend: service: name: frontend-service port: number: 80
minikube addons enable ingress
Un PV es una pieza de almacenamiento en el cluster. Un PVC es una solicitud de almacenamiento por parte de un usuario.
# PersistentVolume apiVersion: v1 kind: PersistentVolume metadata: name: pv-datos spec: capacity: storage: 20Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: fast-ssd hostPath: path: /mnt/datos --- # PersistentVolumeClaim apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mi-pvc spec: accessModes: - ReadWriteOnce storageClassName: fast-ssd resources: requests: storage: 5Gi
Define cómo se aprovisiona el almacenamiento dinámicamente. Cada proveedor de nube tiene sus propias StorageClasses.
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast-ssd provisioner: ebs.csi.aws.com # AWS EBS parameters: type: gp3 iops: "3000" throughput: "125" reclaimPolicy: Delete allowVolumeExpansion: true volumeBindingMode: WaitForFirstConsumer
RBAC controla quién puede hacer qué en el cluster. Los componentes principales son: Role, ClusterRole, RoleBinding y ClusterRoleBinding.
# Role (namespace-scoped) apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: pod-reader namespace: production rules: - apiGroups: [""] resources: ["pods", "pods/log"] verbs: ["get", "list", "watch"] --- # RoleBinding apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: read-pods namespace: production subjects: - kind: ServiceAccount name: mi-app-sa namespace: production roleRef: kind: Role name: pod-reader apiGroup: rbac.authorization.k8s.io
Controla el tráfico de red entre Pods. Por defecto, Kubernetes permite todo el tráfico entre Pods.
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-all-ingress spec: podSelector: {} # aplica a todos los pods policyTypes: - Ingress # sin reglas = denegar todo ingress --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-frontend-to-api spec: podSelector: matchLabels: app: api ingress: - from: - podSelector: matchLabels: app: frontend ports: - protocol: TCP port: 8080
Escala automáticamente el número de réplicas basándose en métricas (CPU, memoria, métricas personalizadas).
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: mi-app-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: mi-app minReplicas: 2 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80
spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/arch operator: In values: [amd64] - key: node-type operator: In values: [gpu] tolerations: - key: gpu operator: Equal value: "true" effect: NoSchedule
Almacena configuración no sensible en formato key-value o archivos de configuración completos.
apiVersion: v1 kind: ConfigMap metadata: name: app-config data: DATABASE_HOST: postgres-service APP_PORT: "3000" LOG_LEVEL: info nginx.conf: | server { listen 80; location / { proxy_pass http://app:3000; } } --- # Usar ConfigMap en un Pod envFrom: - configMapRef: name: app-config # O variable específica: env: - name: DB_HOST valueFrom: configMapKeyRef: name: app-config key: DATABASE_HOST
# Crear secret desde literales kubectl create secret generic db-credentials \ --from-literal=username=admin \ --from-literal=password=S3cur3P@ss! # Crear secret desde archivo kubectl create secret generic tls-secret \ --from-file=tls.crt=./cert.pem \ --from-file=tls.key=./key.pem
apiVersion: v1 kind: Secret metadata: name: db-credentials type: Opaque stringData: # se encripta automáticamente en base64 username: admin password: S3cur3P@ss!
Helm es el gestor de paquetes de Kubernetes. Un Chart es un paquete de recursos Kubernetes con plantillas y valores configurables.
# Instalar Helm curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash # Agregar repositorio helm repo add stable https://charts.helm.sh/stable helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update # Buscar charts helm search repo nginx # Instalar un chart helm install mi-nginx bitnami/nginx \ --namespace web \ --create-namespace \ --set service.type=LoadBalancer \ --set replicaCount=3 # Listar releases helm list --all-namespaces # Actualizar release helm upgrade mi-nginx bitnami/nginx --set replicaCount=5 # Rollback helm rollback mi-nginx 1 # Desinstalar helm uninstall mi-nginx
helm create mi-app # Estructura generada: # mi-app/ # ├── Chart.yaml # metadata del chart # ├── values.yaml # valores por defecto # ├── templates/ # plantillas YAML # │ ├── deployment.yaml # │ ├── service.yaml # │ └── ingress.yaml # └── charts/ # dependencias # Validar chart helm lint mi-app # Ver los YAMLs generados helm template mi-app ./mi-app --values mi-app/values.yaml
replicaCount: 2 image: repository: myrepo/mi-app tag: "1.0.0" pullPolicy: IfNotPresent service: type: ClusterIP port: 80 ingress: enabled: true host: app.midominio.com resources: limits: cpu: 500m memory: 512Mi requests: cpu: 100m memory: 128Mi autoscaling: enabled: true minReplicas: 2 maxReplicas: 10 targetCPUUtilizationPercentage: 70
# Instalar kube-prometheus-stack via Helm helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update helm install monitoring prometheus-community/kube-prometheus-stack \ --namespace monitoring \ --create-namespace \ --set grafana.adminPassword=admin123 \ --set prometheus.prometheusSpec.retention=30d # Ver pods del stack kubectl get pods -n monitoring # Acceder a Grafana (port-forward) kubectl port-forward -n monitoring svc/monitoring-grafana 3000:80
apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: mi-app-monitor namespace: monitoring spec: selector: matchLabels: app: mi-app endpoints: - port: metrics interval: 15s path: /metrics
helm repo add grafana https://grafana.github.io/helm-charts helm install loki grafana/loki-stack \ --namespace monitoring \ --set grafana.enabled=false \ --set promtail.enabled=true # En Grafana, agregar datasource Loki: # URL: http://loki:3100 # Query ejemplo LogQL: # {namespace="production", app="mi-app"} |= "error"
apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: alertas-app spec: groups: - name: app.rules rules: - alert: PodCrashLooping expr: rate(kube_pod_container_status_restarts_total[5m]) > 0.1 for: 5m labels: severity: critical annotations: summary: "Pod {{ $labels.pod }} en CrashLoopBackOff"
kubectl create namespace argocd kubectl apply -n argocd \ -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml # Obtener contraseña inicial kubectl get secret argocd-initial-admin-secret \ -n argocd -o jsonpath="{.data.password}" | base64 -d # Port-forward para acceder a la UI kubectl port-forward svc/argocd-server -n argocd 8080:443
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: mi-app-prod namespace: argocd spec: project: default source: repoURL: https://github.com/mi-org/mi-app-k8s targetRevision: HEAD path: manifests/production destination: server: https://kubernetes.default.svc namespace: production syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true
| Estrategia | Downtime | Rollback | Costo |
|---|---|---|---|
| Recreate | Sí | Lento | Bajo |
| Rolling Update | No | Medio | Bajo |
| Blue/Green | No | Inmediato | Alto (2x recursos) |
| Canary | No | Rápido | Medio |
apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: name: mi-app-rollout spec: replicas: 10 strategy: canary: steps: - setWeight: 10 # 10% del tráfico - pause: {duration: 5m} - setWeight: 25 - pause: {duration: 10m} - setWeight: 50 - pause: {} # aprobación manual - setWeight: 100
Herramientas y referencias para profundizar
kubernetes.io — La fuente de verdad. Reference API, guides, tutoriales oficiales.
k9s, Lens, Helm, kustomize, kubectx/kubens para productividad diaria.
CKA, CKAD, CKS — Las certificaciones oficiales de Kubernetes de la CNCF.