🎓 Cómo usar este documento: Cada sección explica el concepto desde cero, con ejemplos prácticos, comparativas y comandos clave. Ideal para entender el ecosistema completo de infraestructura moderna.
Imagina que desarrollas una aplicación en tu portátil con Python 3.11, una base de datos PostgreSQL 15 y una versión específica de una librería. Funciona perfectamente. Subes el código al servidor de producción y... no funciona. El servidor tiene Python 3.8, otra versión de PostgreSQL y las librerías difieren.
Este problema clásico se llama "en mi máquina funciona" (works on my machine). Las tecnologías de contenedores nacieron para resolverlo.
La evolución fue así:
ANTES (años 90-2000) HOY
┌─────────────────┐ ┌─────────────────────────────────────┐
│ Servidor físico │ │ Cluster de orquestación │
│ ├─ App A │ │ ├─ Contenedor App A (réplica x3) │
│ ├─ App B │ ──▶ │ ├─ Contenedor App B (réplica x2) │
│ └─ App C │ │ ├─ Contenedor BD │
│ (todo mezclado) │ │ └─ Autoescalado, balanceo, etc. │
└─────────────────┘ └─────────────────────────────────────┘
Una máquina virtual emula hardware completo. Cada VM incluye su propio sistema operativo completo (kernel incluido), lo que la hace muy aislada pero también muy pesada.
┌────────────────────────────────────────────┐
│ SERVIDOR FÍSICO │
│ ┌──────────────────────────────────────┐ │
│ │ HYPERVISOR (VMware/KVM) │ │
│ ├──────────────┬──────────────────────┤ │
│ │ VM 1 │ VM 2 │ │
│ │ ┌──────────┐ │ ┌──────────────────┐│ │
│ │ │Guest OS │ │ │ Guest OS ││ │
│ │ │ (Linux) │ │ │ (Windows) ││ │
│ │ ├──────────┤ │ ├──────────────────┤│ │
│ │ │ App A │ │ │ App B + App C ││ │
│ │ └──────────┘ │ └──────────────────┘│ │
│ └──────────────┴──────────────────────┘ │
└────────────────────────────────────────────┘
Características de las VMs:
- Arranque lento (minutos)
- Tamaño grande (GBs por VM)
- Aislamiento total (cada una tiene su propio kernel)
- Ideal para aislar sistemas operativos completos
Un contenedor no emula hardware ni incluye un sistema operativo completo. Comparte el kernel del host pero aísla el proceso y su sistema de ficheros mediante tecnologías del propio Linux (namespaces y cgroups).
┌─────────────────────────────────────────────────────┐
│ SERVIDOR FÍSICO │
│ ┌───────────────────────────────────────────────┐ │
│ │ SISTEMA OPERATIVO HOST │ │
│ │ (Kernel compartido) │ │
│ ├───────────┬───────────┬───────────────────────┤ │
│ │ │ DOCKER │ │ │
│ │ Container1│ Container2│ Container 3 │ │
│ │ ┌───────┐ │ ┌───────┐ │ ┌──────────────────┐ │ │
│ │ │Libs A │ │ │Libs B │ │ │ Libs C │ │ │
│ │ ├───────┤ │ ├───────┤ │ ├──────────────────┤ │ │
│ │ │ App A │ │ │ App B │ │ │ App C │ │ │
│ │ └───────┘ │ └───────┘ │ └──────────────────┘ │ │
│ └───────────┴───────────┴───────────────────────┘ │
└─────────────────────────────────────────────────────┘
Características de los contenedores:
- Arranque rapidísimo (segundos o menos)
- Tamaño pequeño (MBs)
- Aislamiento a nivel de proceso (comparten kernel)
- Portables: el mismo contenedor funciona en cualquier máquina con Docker
| Característica | Máquina Virtual | Contenedor |
|---|---|---|
| Arranque | Minutos | Milisegundos–segundos |
| Tamaño en disco | GBs | MBs |
| Uso de RAM | Alto (SO completo) | Bajo (solo la app) |
| Aislamiento | Total (kernel propio) | Proceso (kernel compartido) |
| Portabilidad | Media | Muy alta |
| Caso de uso | SO diferente, aislamiento duro | Microservicios, apps |
| Tecnologías | VMware, VirtualBox, KVM, Hyper-V | Docker, Podman, containerd |
💡 En la práctica: No se excluyen. Muchas empresas corren contenedores dentro de VMs para obtener lo mejor de ambos mundos: aislamiento de VM + ligereza de contenedor.
Docker es la plataforma más popular para crear, distribuir y ejecutar contenedores. Lanzado en 2013, democratizó el uso de contenedores que antes eran muy complejos de manejar.
Docker tiene tres conceptos clave:
IMAGEN CONTENEDOR REGISTRO
(plantilla/blueprint) (instancia en ejecución) (almacén de imágenes)
┌──────────────────┐ ┌──────────────────┐ ┌────────────────┐
│ Dockerfile │ │ Imagen en RAM │ │ Docker Hub │
│ │ ───▶ │ + proceso vivo │ │ (nube pública)│
│ FROM ubuntu │ │ │ ├────────────────┤
│ RUN apt install │ │ puerto: 8080 │ │ Registry propio│
│ COPY app/ . │ │ red: bridge │ │ (empresa) │
│ CMD ["node",..] │ │ volumen: /data │ └────────────────┘
└──────────────────┘ └──────────────────┘
│ │
docker build docker run
Un Dockerfile es el recetario para construir una imagen. Ejemplo real de una app Node.js:
# Imagen base (sistema operativo + runtime)
FROM node:18-alpine
# Directorio de trabajo dentro del contenedor
WORKDIR /app
# Copiar dependencias primero (aprovecha la caché de capas)
COPY package*.json ./
RUN npm install --production
# Copiar el resto del código
COPY . .
# Exponer el puerto
EXPOSE 3000
# Comando que se ejecuta al arrancar el contenedor
CMD ["node", "server.js"]
El sistema de capas (layers): Cada instrucción en el Dockerfile crea una capa inmutable. Docker reutiliza capas que no han cambiado, lo que hace las builds muy rápidas.
Capa 5: CMD ["node", "server.js"]
Capa 4: COPY . .
Capa 3: RUN npm install ← si solo cambia el código, esta capa se reutiliza
Capa 2: COPY package*.json ./
Capa 1: FROM node:18-alpine ← base siempre reutilizada si no cambia
docker create ──▶ CREADO ──▶ docker start ──▶ EN EJECUCIÓN
│
docker run (crea + inicia de una vez) │
────────────────────────────────────▶ │
│
docker stop ◀────┤
│
docker pause ◀───┤ (congela)
docker unpause ──▶
│
docker kill ◀────┘ (SIGKILL)
│
docker rm ──▶ ELIMINADO
# Crear sin iniciar
docker create nginx
# Crear e iniciar en una sola orden (el más usado)
docker run nginx
# Crear con opciones comunes
docker run -d \ # background (detached)
-p 8080:80 \ # puerto host:puerto_contenedor
-v /datos:/app/datos \ # volumen host:contenedor
--name mi-nginx \ # nombre del contenedor
--rm \ # borrarse al terminar
nginx:latest
# Gestión básica
docker start mi-nginx
docker stop mi-nginx
docker restart mi-nginx
docker pause mi-nginx
docker unpause mi-nginx
docker kill mi-nginx # SIGKILL (fuerza)
docker rm mi-nginx # eliminar (debe estar parado)
docker rm -f mi-nginx # forzar eliminación aunque esté corriendo
docker rename mi-nginx nginx-prod
docker update --memory 512m mi-nginx # actualizar recursos
docker ps # contenedores en ejecución
docker ps -a # todos (incluidos parados)
docker logs mi-nginx # logs del contenedor
docker logs -f mi-nginx # logs en tiempo real (follow)
docker logs --tail 100 mi-nginx # últimas 100 líneas
docker inspect mi-nginx # toda la información (JSON)
docker inspect mi-nginx | grep IPAddress # obtener IP
docker stats # uso de recursos en tiempo real
docker top mi-nginx # procesos dentro del contenedor
docker events # eventos en tiempo real
docker port mi-nginx # puertos expuestos
docker diff mi-nginx # ficheros modificados en el FS
# Ejecutar un comando en contenedor en marcha
docker exec mi-nginx ls /etc/nginx
# Abrir una shell interactiva
docker exec -it mi-nginx bash
docker exec -it mi-nginx sh # si no tiene bash
# Como root aunque el contenedor use otro usuario
docker exec -it -u root mi-nginx bash
# Limitar CPU (0.5 = 50% de un core)
docker run --cpus="0.5" nginx
# Limitar memoria
docker run -m 300M nginx
docker run --memory=512m --memory-swap=1g nginx
# Usar cores específicos
docker run --cpuset-cpus="0,2" nginx # solo cores 0 y 2
# Shares de CPU (relativo, 1024 = 100%)
docker run --cpu-shares=512 nginx # 50% de CPU
# Copiar ficheros entre host y contenedor
docker cp mi-nginx:/etc/nginx/nginx.conf ./nginx.conf
docker cp ./mi-config.conf mi-nginx:/etc/nginx/nginx.conf
# Exportar sistema de ficheros del contenedor a tar
docker export mi-nginx > contenedor.tar
docker images # listar imágenes locales
docker pull nginx:latest # descargar imagen
docker pull nginx:1.25 # versión específica
docker build -t mi-app:1.0 . # construir desde Dockerfile en .
docker build -t mi-app:1.0 -f Dockerfile.prod . # dockerfile específico
docker rmi nginx # eliminar imagen
docker rmi -f nginx # forzar eliminación
docker tag nginx mi-registro/nginx:v1 # etiquetar imagen
# Guardar imagen a fichero tar (incluye capas)
docker save nginx:latest | gzip > nginx.tar.gz
docker save -o nginx.tar nginx:latest
# Cargar imagen desde fichero
docker load < nginx.tar.gz
docker load -i nginx.tar
# Importar contenedor como imagen
cat contenedor.tar | docker import - mi-imagen:tag
# Ver historial de capas
docker history nginx
docker rmi $(docker images -q) # eliminar todas las imágenes
docker rmi $(docker images -q -f dangling=true) # eliminar dangling (sin tag)
docker image prune # limpiar imágenes sin usar
docker system prune # limpiar TODO (contenedores, imágenes, redes, volúmenes)
docker system prune -a # limpiar TODO incluso imágenes no usadas
Docker gestiona redes virtuales. Los contenedores en la misma red pueden comunicarse por nombre.
# Tipos de red
docker network ls
# bridge (por defecto) → contenedores en la misma máquina
# host → el contenedor usa directamente la red del host
# none → sin red
# overlay → para Docker Swarm / multi-host
docker network create mi-red
docker network rm mi-red
docker network connect mi-red mi-nginx
docker network disconnect mi-red mi-nginx
docker network inspect mi-red
# Ejecutar en una red específica
docker run --network mi-red nginx
# Exponer puertos
docker run -p 8080:80 nginx # host:contenedor
docker run -p 127.0.0.1:8080:80 nginx # solo localhost
docker run -P nginx # puertos aleatorios del host
Los contenedores son efímeros: cuando se borran, pierden sus datos. Los volúmenes persisten los datos fuera del contenedor.
# Tipos de almacenamiento
# 1. Volumen gestionado por Docker (recomendado)
docker volume create mis-datos
docker run -v mis-datos:/app/datos nginx
# 2. Bind mount (directorio del host)
docker run -v /host/ruta:/contenedor/ruta nginx
docker run -v $(pwd)/config:/etc/nginx/conf.d nginx # directorio actual
# 3. tmpfs (solo en RAM, efímero)
docker run --tmpfs /app/tmp nginx
# Gestión de volúmenes
docker volume ls
docker volume inspect mis-datos
docker volume rm mis-datos
docker volume prune # eliminar volúmenes sin usar
Docker Compose permite definir y ejecutar aplicaciones multi-contenedor con un fichero YAML.
# docker-compose.yml — Ejemplo: app web + base de datos + cache
version: '3.8'
services:
web:
build: . # construir desde Dockerfile local
ports:
- "8080:3000"
environment:
- DATABASE_URL=postgresql://postgres:secret@db:5432/miapp
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
volumes:
- ./src:/app/src # hot-reload en desarrollo
restart: unless-stopped
db:
image: postgres:15
environment:
POSTGRES_DB: miapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
volumes:
- postgres_data:/var/lib/postgresql/data # persistencia
cache:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
docker compose up -d # levantar todo en background
docker compose down # parar y eliminar contenedores
docker compose down -v # también eliminar volúmenes
docker compose logs -f # logs de todos los servicios
docker compose ps # estado de los servicios
docker compose exec web bash # shell en el servicio web
docker compose build # reconstruir imágenes
docker compose pull # descargar imágenes actualizadas
Un registry es donde se almacenan y distribuyen las imágenes.
# Docker Hub (público)
docker login
docker tag mi-app usuario/mi-app:v1.0
docker push usuario/mi-app:v1.0
docker pull usuario/mi-app:v1.0
# Registry privado (self-hosted)
docker run -d -p 5000:5000 --name registry registry:2
docker tag mi-app localhost:5000/mi-app:v1.0
docker push localhost:5000/mi-app:v1.0
# Registries populares en la nube
# AWS ECR: 123456789.dkr.ecr.eu-west-1.amazonaws.com/mi-app
# GCP GCR: gcr.io/mi-proyecto/mi-app
# Azure ACR: miregistro.azurecr.io/mi-app
# GitLab: registry.gitlab.com/grupo/proyecto/mi-app
# Obtener IP de un contenedor
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mi-nginx
# Obtener mapeo de puertos
docker inspect -f '{{range $p, $conf := .NetworkSettings.Ports}}{{$p}} -> {{(index $conf 0).HostPort}}{{"\n"}}{{end}}' mi-nginx
# Matar todos los contenedores en ejecución
docker kill $(docker ps -q)
# Eliminar todos los contenedores parados
docker rm $(docker ps -a -q -f status=exited)
# Eliminar imágenes "dangling" (sin nombre/tag)
docker rmi $(docker images -q -f dangling=true)
# Eliminar volúmenes dangling
docker volume rm $(docker volume ls -q -f dangling=true)
# Ver espacio usado por Docker
docker system df
# Buscar contenedores por expresión regular
for i in $(docker ps -a | grep "PATRÓN" | cut -f1 -d " "); do echo $i; done
Docker resuelve el problema de ejecutar un contenedor en una máquina. Pero en producción real surgen problemas nuevos:
Un orquestador resuelve todos estos problemas. Es como un "gerente de contenedores" que gestiona un clúster de máquinas.
Sin orquestador: Con orquestador (Kubernetes):
┌────────┐ ┌────────┐ ┌─────────────────────────────────┐
│ Host 1 │ │ Host 2 │ │ KUBERNETES CLUSTER │
│ │ │ │ │ │
│ App │ │ App │ │ Tu declaras: "quiero 5 réplicas │
│ (tú │ │ (tú │ ──▶ │ de mi app, con 1GB RAM cada una │
│ lo │ │ lo │ │ y que se actualicen sin corte" │
│ gesti-│ │ gesti-│ │ │
│ onas) │ │ onas) │ │ K8s se encarga del resto ✓ │
└────────┘ └────────┘ └─────────────────────────────────┘
Creado por Google en 2014, donado a la CNCF. Es el orquestador más usado en el mundo.
Conceptos clave de Kubernetes:
CLUSTER
└─ NODOS (máquinas físicas o VMs)
├─ Control Plane (el "cerebro")
│ ├─ API Server → punto de entrada de todo
│ ├─ etcd → base de datos del estado del cluster
│ ├─ Scheduler → decide en qué nodo va cada Pod
│ └─ Controller Mgr → mantiene el estado deseado
└─ Worker Nodes (donde corren las apps)
├─ kubelet → agente que habla con el Control Plane
├─ kube-proxy → gestiona el networking
└─ Container Runtime (containerd, CRI-O)
Objetos principales de Kubernetes:
| Objeto | Qué es | Ejemplo de uso |
|---|---|---|
| Pod | Uno o más contenedores que comparten red y almacenamiento | La unidad mínima desplegable |
| Deployment | Gestiona réplicas de Pods con rolling updates | Tu app web con 3 réplicas |
| Service | Expone Pods con una IP estable y balanceo de carga | Punto de acceso a tu app |
| Ingress | Reglas HTTP/HTTPS para enrutar tráfico externo | dominio.com/api → servicio-api |
| ConfigMap | Configuración no sensible | URL de la base de datos |
| Secret | Datos sensibles cifrados | Contraseñas, tokens, certificados |
| PersistentVolume | Almacenamiento persistente | Disco para la base de datos |
| Namespace | Entornos virtuales dentro del cluster | produccion, staging, dev |
| DaemonSet | Un Pod en CADA nodo del cluster | Agente de logs, monitorización |
| StatefulSet | Pods con identidad estable y orden de arranque | Bases de datos, ZooKeeper |
Integrado en Docker, mucho más sencillo que Kubernetes pero menos potente.
# Crear un Swarm
docker swarm init --advertise-addr 192.168.1.10
# Añadir un nodo worker
docker swarm join --token SWMTKN-1-xxx 192.168.1.10:2377
# Desplegar un servicio
docker service create --replicas 3 --name mi-web -p 80:80 nginx
# Escalar
docker service scale mi-web=5
# Ver estado
docker service ls
docker service ps mi-web
Más simple que Kubernetes, soporta no solo contenedores sino también apps binarias y VMs. Popular en empresas que ya usan Terraform/Vault.
Kubernetes con añadidos empresariales: CI/CD integrado, gestión de usuarios, seguridad reforzada. Muy usado en grandes corporaciones.
| Kubernetes | Docker Swarm | Nomad | OpenShift | |
|---|---|---|---|---|
| Curva aprendizaje | Alta | Baja | Media | Alta |
| Escalabilidad | Muy alta | Media | Alta | Muy alta |
| Comunidad | Enorme | Pequeña | Media | Media |
| Apps no-contenedor | No | No | Sí | No |
| Gestión | Compleja | Simple | Media | Compleja |
| Ideal para | Grandes producciones | Pequeños proyectos | Multi-workload | Empresas grandes |
| Coste operativo | Alto | Bajo | Medio | Muy alto |
kubectl get nodes # listar nodos
kubectl get nodes -o wide # con más info (IP, OS, etc.)
kubectl describe node [nombre_nodo] # detalle completo del nodo
kubectl get nodes -o yaml # en formato YAML
kubectl top node [nombre_nodo] # uso de CPU/RAM del nodo
kubectl get node --selector=[label] # filtrar por etiqueta
kubectl get pods # pods en el namespace actual
kubectl get pods -o wide # con nodo y IP
kubectl get pods --all-namespaces # todos los namespaces
kubectl get pods -n kube-system # namespace específico
kubectl describe pod [nombre_pod] # detalle completo
kubectl get pod [nombre_pod] -o yaml # YAML del pod
kubectl get pods -l app=nginx # filtrar por label
kubectl get pods --field-selector status.phase=Running # filtrar por estado
kubectl logs [pod_name] # logs del pod
kubectl logs [pod_name] -f # logs en tiempo real
kubectl logs --since=1h [pod_name] # última hora
kubectl logs --tail=50 [pod_name] # últimas 50 líneas
kubectl logs -f -c [container_name] [pod_name] # logs de contenedor específico
kubectl logs [pod_name] > pod.log # guardar en fichero
# Deployments
kubectl get deploy
kubectl describe deploy [nombre]
kubectl get deploy -o wide
kubectl get deploy -o yaml
# Services
kubectl get svc
kubectl describe svc [nombre]
kubectl get svc --show-labels
# Namespaces
kubectl get ns
kubectl describe ns [nombre]
# Múltiples recursos a la vez
kubectl get svc,po
kubectl get deploy,no
kubectl get all
kubectl get all --all-namespaces
# DaemonSets, ReplicaSets, etc.
kubectl get ds
kubectl get rs
kubectl get cm # ConfigMaps
kubectl get secrets
kubectl get pv # PersistentVolumes
kubectl get pvc # PersistentVolumeClaims
kubectl get ing # Ingress
kubectl get sa # ServiceAccounts
kubectl get roles --all-namespaces
# Etiquetas (labels)
kubectl label node [nodo] disktype=ssd
kubectl label pod [pod] env=prod
# Anotaciones
kubectl annotate pod [pod] [anotacion]
kubectl annotate node [nodo] descripcion="nodo GPU"
# Taints (restricciones en nodos)
kubectl taint node [nodo] [taint_name]
kubectl taint node [nodo] key=value:NoSchedule # no programar pods aquí
# Cordon/Uncordon (impedir/permitir nuevos pods en un nodo)
kubectl cordon [nodo] # no nuevos pods
kubectl uncordon [nodo] # volver a aceptar pods
# Drain (evacuar nodo para mantenimiento)
kubectl drain [nodo] # mover todos los pods
kubectl drain [nodo] --ignore-daemonsets --delete-emptydir-data
# Editar en vivo (abre editor)
kubectl edit deploy [nombre]
kubectl edit svc [nombre]
kubectl edit node [nombre]
kubectl edit ns [nombre]
kubectl edit ds [nombre] -n kube-system
# Eliminar recursos
kubectl delete node [nodo]
kubectl delete pod [pod]
kubectl delete svc [svc]
kubectl delete ds [ds]
kubectl delete sa [sa]
kubectl delete ns [ns] # elimina TODOS los recursos dentro
# Escalar
kubectl scale deploy [nombre] --replicas=5
kubectl scale deploy [nombre] --replicas=0 # apagar sin eliminar
# Exponer
kubectl expose deploy [nombre] --port=80 --type=NodePort
# Desde un fichero YAML (la forma recomendada)
kubectl create -f mi-deployment.yaml
kubectl apply -f mi-deployment.yaml # create o update
kubectl apply -f ./directorio/ # aplicar todos los YAML
# Crear directamente (imperativo, para pruebas)
kubectl run mi-pod --image=nginx --restart=Never
kubectl create deploy mi-deploy --image=nginx
kubectl create deploy mi-deploy --image=nginx --replicas=3
# Pod interactivo temporal (muy útil para debugging)
kubectl run debug --image=busybox --rm -it --restart=Never -- sh
# Crear servicio
kubectl create svc nodeport mi-svc --tcp=8080:80
# Ver el YAML que generaría un comando
kubectl create deploy mi-app --image=nginx --dry-run=client -o yaml > deploy.yaml
kubectl run mi-pod --image=nginx --dry-run=client -o yaml > pod.yaml
# Exportar YAML de un recurso existente
kubectl get deploy [nombre] -o yaml > deploy.yaml
kubectl get pod [nombre] -o yaml --export > pod.yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mi-app
labels:
app: mi-app
spec:
replicas: 3
selector:
matchLabels:
app: mi-app
template:
metadata:
labels:
app: mi-app
spec:
containers:
- name: mi-app
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: mi-app-svc
spec:
selector:
app: mi-app
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer
kubectl apply -f deployment.yaml
kubectl get pods -w # watch: ver cómo arrancan
kubectl get svc mi-app-svc # ver IP externa asignada
kubectl rollout status deploy/mi-app # ver progreso del despliegue
kubectl rollout history deploy/mi-app # historial de versiones
kubectl rollout undo deploy/mi-app # rollback a versión anterior
kubectl config # configuración del cluster
kubectl cluster-info # info del cluster
kubectl get componentstatuses # estado de componentes
kubectl get --raw /apis/metrics.k8s.io/ # API call directa
kubectl api-resources # todos los tipos de recursos disponibles
kubectl explain deploy.spec # documentación del campo spec de Deployment
kubectl -h # ayuda general
kubectl create -h # ayuda sobre create
SaltStack (ahora simplemente Salt) es una herramienta de gestión de configuración y automatización de infraestructura. Si Docker/Kubernetes gestionan contenedores, Salt gestiona servidores (físicos, VMs, instancias cloud).
Analogía: Imagina 500 servidores en producción. Salt es el "mando a distancia universal" que te permite instalar software, cambiar configuraciones, reiniciar servicios o ejecutar cualquier comando en todos ellos a la vez desde un único punto.
SALT MASTER
(servidor central)
puerto 4505 (pub)
puerto 4506 (rtn)
│
┌────────────┼────────────┐
│ │ │
MINION 1 MINION 2 MINION N
(servidor 1) (servidor 2) (servidor N)
Comunicación: ZeroMQ (muy rápido, asíncrono)
Autenticación: clave pública RSA
Flujo de comunicación:
1. Minion arranca y busca el Master (por DNS salt o configuración)
2. Minion genera par de claves RSA y envía la pública al Master
3. Administrador acepta la clave en el Master: salt-key -A
4. A partir de ahí, Master puede enviar comandos a los Minions
# Master
systemctl start salt-master # systemd (moderno)
service salt-master start # upstart (Ubuntu antiguo)
/etc/init.d/salt-master start # SysV init
salt-master -d # en background
salt-master -l debug # en foreground con debug
# Minion
salt-minion -d # en background
salt-minion -l debug # en foreground con debug
# Configuración del minion: /etc/salt/minion
master: saltmaster.example.com # apuntar al master
id: servidor-web-01 # nombre del minion (por defecto: hostname)
salt-key -L # listar todas las claves (accepted/denied/rejected/unaccepted)
salt-key -A # aceptar TODAS las claves pendientes
salt-key -a servidor-web-01 # aceptar una clave específica
salt-key -d servidor-web-01 # eliminar una clave
salt-key -r servidor-web-01 # rechazar una clave
salt-key -F master # ver fingerprint de la clave del master
salt-key -f servidor-web-01 # ver fingerprint de un minion
La sintaxis general es: salt '[TARGET]' [MÓDULO].[FUNCIÓN] [ARGUMENTOS]
# Ejecutar en TODOS los minions
salt '*' test.ping
# Ejecutar en un minion específico
salt 'servidor-web-01' test.ping
# Ejecutar en varios con glob
salt 'web*' cmd.run 'uptime' # todos los que empiezan por "web"
salt '*prod*' cmd.run 'df -h' # todos los que contienen "prod"
# Ejemplos de módulos básicos
salt '*' test.version # versión de Salt instalada
salt '*' disk.usage # uso de discos
salt '*' network.interfaces # interfaces de red
salt '*' cmd.run 'ls -l /etc' # ejecutar comando bash
salt '*' cmd.run_all 'systemctl status nginx' # stdout + stderr + retcode
salt '*' pkg.install nginx # instalar paquete
salt '*' pkg.remove nginx # eliminar paquete
salt '*' pkg.upgrade # actualizar todos los paquetes
salt '*' service.start nginx # iniciar servicio
salt '*' service.stop nginx # detener servicio
salt '*' service.restart nginx # reiniciar servicio
salt '*' service.status nginx # estado del servicio
salt '*' service.get_all # listar todos los servicios
# Glob (por nombre)
salt 'web*' test.ping
salt '*prod*' test.ping
# Expresiones regulares (-E)
salt -E 'web[0-9]+' test.ping
# Lista de minions (-L)
salt -L 'web01,web02,db01' test.ping
# Por Grain (-G) — datos del sistema del minion
salt -G 'os:Ubuntu' pkg.upgrade
salt -G 'roles:webserver' service.restart nginx
salt -G 'datacenter:madrid' cmd.run 'hostname'
# Por Pillar (-I)
salt -I 'environment:production' test.ping
# Por IP o subred
salt -S '192.168.1.0/24' test.ping
# Compound (combinado)
salt -C 'G@os:Ubuntu and web*' test.ping
salt -C 'G@os:CentOS or G@os:RedHat' pkg.upgrade
# Node Groups (grupos definidos en el master)
salt -N webservers test.ping
# Batch mode (por lotes, para evitar sobrecargar)
salt --batch-size 10 '*' state.apply
salt -b 25% '*' pkg.upgrade
cmd — Ejecución de comandossalt '*' cmd.run 'ls -la /var/log'
salt '*' cmd.run_all 'systemctl status nginx' # stdout + stderr + retcode
salt '*' cmd.run_stderr 'comando_con_error' # solo stderr
salt '*' cmd.run_stdout 'echo hola' # solo stdout
salt '*' cmd.retcode 'test -f /etc/nginx.conf' # código de retorno
salt '*' cmd.has_exec nginx # ¿existe el binario?
salt '*' cmd.shell 'for i in 1 2 3; do echo $i; done' shell=/bin/bash
pkg — Paquetessalt '*' pkg.install nginx
salt '*' pkg.install 'nginx,curl,vim' # varios paquetes
salt '*' pkg.remove nginx
salt '*' pkg.latest nginx # actualizar a la última versión
salt '*' pkg.upgrade # actualizar todo el sistema
salt '*' pkg.list_pkgs # listar paquetes instalados
salt '*' pkg.version nginx # versión instalada
service — Serviciossalt '*' service.start nginx
salt '*' service.stop nginx
salt '*' service.restart nginx
salt '*' service.reload nginx # reload sin reiniciar
salt '*' service.status nginx # running/stopped
salt '*' service.get_all # todos los servicios
salt '*' service.enable nginx # habilitar al arranque
salt '*' service.disable nginx # deshabilitar al arranque
file — Ficherossalt '*' file.exists /etc/nginx/nginx.conf # ¿existe?
salt '*' file.remove /tmp/fichero.txt # eliminar
salt '*' file.mkdir /opt/mi-directorio # crear directorio
salt '*' file.replace /etc/nginx/nginx.conf 'worker_processes 1' 'worker_processes 4'
user y groupsalt '*' user.add deploy # crear usuario
salt '*' user.delete deploy # eliminar usuario
salt '*' user.info deploy # info del usuario
salt '*' user.chpasswd deploy 'nueva_contraseña'
salt '*' group.add developers # crear grupo
grainssalt '*' grains.items # todos los grains
salt '*' grains.get os # SO del minion
salt '*' grains.get id # ID del minion
salt '*' grains.get fqdn # FQDN
salt '*' grains.get ipv4 # IPs del minion
salt '*' grains.setval role webserver # establecer grain personalizado
pillarsalt '*' pillar.items # todos los pillar data
salt '*' pillar.get environment # valor específico
salt '*' pillar.refresh_pillar # refrescar datos de pillar
Los States son la forma de declarar cómo debe estar un sistema. Se escriben en YAML en ficheros .sls bajo /srv/salt/.
# /srv/salt/nginx.sls
# Asegurar que nginx está instalado, configurado y corriendo
nginx:
pkg.installed: [] # paquete instalado
nginx_config:
file.managed:
- name: /etc/nginx/nginx.conf
- source: salt://nginx/nginx.conf # fichero en el salt master
- user: root
- group: root
- mode: 644
- require:
- pkg: nginx # requiere que nginx esté instalado
nginx_service:
service.running:
- name: nginx
- enable: True
- watch:
- file: nginx_config # reinicia si cambia la config
# Aplicar un state a todos los minions
salt '*' state.apply nginx
# Aplicar varios states
salt '*' state.apply nginx,php,mysql
# Highstate: aplicar todos los states asignados al minion
salt '*' state.apply
# equivalente a:
salt '*' state.highstate
# Verificar sin aplicar (dry-run)
salt '*' state.apply nginx test=True
# Ver qué states tiene asignados un minion
salt-call state.show_top
# Verificar un fichero SLS (sintaxis)
salt '*' state.show_sls nginx
GRAINS PILLAR
──────────────────────────────── ────────────────────────────────
Datos DEL minion Datos PARA el minion
Generados automáticamente Definidos por el administrador
Datos del sistema: OS, IP, CPU... Datos de negocio: passwords, env
Públicos (visibles desde master) Privados (solo el minion los ve)
No sensibles Sensibles (contraseñas, tokens)
Ejemplo: os=Ubuntu Ejemplo: db_password=s3cr3t0
ipv4=[192.168.1.10] environment=production
cpu_count=4 deploy_user=deploy
| SaltStack | Ansible | Puppet | Chef | |
|---|---|---|---|---|
| Arquitectura | Master/Minion | Agentless (SSH) | Master/Agent | Master/Agent |
| Protocolo | ZeroMQ | SSH | HTTPS | HTTPS |
| Velocidad | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| Escala | Miles de nodos | Cientos | Miles | Miles |
| Lenguaje config | YAML + Jinja2 | YAML | Puppet DSL | Ruby |
| Curva aprendizaje | Media-Alta | Baja | Alta | Alta |
| Tiempo real | ✅ (event bus) | ❌ | ❌ | ❌ |
| Sin agente | Opcional (SSH) | Por defecto | No | No |
| Orquestación | ✅ | ✅ | Limitada | Limitada |
| Comunidad | Media | Muy grande | Grande | Media |
INFRAESTRUCTURA CONFIGURACIÓN CONTENEDORES ORQUESTACIÓN
DE SERVIDORES DE SERVIDORES (packaging) DE CONTENEDORES
Terraform ──▶ SaltStack ──▶ Docker ──▶ Kubernetes
(Ansible IaC) (Ansible) Podman Docker Swarm
Pulumi Puppet Buildah Nomad
Chef
"Crea la VM" "Configura la VM" "Empaqueta la app" "Gestiona los contenedores"
Desarrollador hace push a Git
│
▼
CI/CD (GitLab CI / GitHub Actions)
├─ Tests automáticos
├─ docker build → imagen
└─ docker push → registro (ECR, Harbor...)
│
▼
Kubernetes (EKS / GKE / AKS / on-premise)
├─ Deployment actualizado con nueva imagen
├─ Rolling update (sin downtime)
└─ Monitorización (Prometheus + Grafana)
│
▼
SaltStack / Ansible (para los servidores subyacentes)
├─ Configurar los nodos del cluster
├─ Actualizar el OS de los workers
└─ Gestionar certificados y secrets
| Necesito... | Usa... |
|---|---|
| Empaquetar mi app con sus dependencias | Docker |
| Desarrollar con varias apps relacionadas (app + DB + cache) | Docker Compose |
| Desplegar mi app en producción con alta disponibilidad | Kubernetes |
| Gestionar configuración de 50 servidores | SaltStack / Ansible |
| Provisionar infraestructura en la nube (VMs, redes, etc.) | Terraform |
| Orquestación sencilla sin Kubernetes | Docker Swarm |
| Entorno de desarrollo reproducible con VMs | Vagrant |
| Clúster Kubernetes gestionado sin operar el control plane | EKS / GKE / AKS |
| Acción | Comando |
|---|---|
| Crear e iniciar contenedor | docker run -d -p 8080:80 nginx |
| Ver contenedores en marcha | docker ps |
| Shell en contenedor | docker exec -it NOMBRE bash |
| Ver logs | docker logs -f NOMBRE |
| Parar contenedor | docker stop NOMBRE |
| Eliminar contenedor | docker rm NOMBRE |
| Construir imagen | docker build -t nombre:tag . |
| Listar imágenes | docker images |
| Eliminar imagen | docker rmi IMAGEN |
| Subir imagen | docker push usuario/imagen:tag |
| Limpiar todo | docker system prune -a |
| Levantar compose | docker compose up -d |
| Bajar compose | docker compose down |
| Acción | Comando |
|---|---|
| Ver pods | kubectl get pods |
| Ver todo | kubectl get all |
| Detalle de un pod | kubectl describe pod NOMBRE |
| Logs | kubectl logs -f NOMBRE |
| Shell en pod | kubectl exec -it NOMBRE -- bash |
| Aplicar YAML | kubectl apply -f fichero.yaml |
| Escalar | kubectl scale deploy NOMBRE --replicas=5 |
| Eliminar | kubectl delete pod NOMBRE |
| Editar en vivo | kubectl edit deploy NOMBRE |
| Rollback | kubectl rollout undo deploy/NOMBRE |
| Ver namespaces | kubectl get ns |
| Info del cluster | kubectl cluster-info |
| Acción | Comando |
|---|---|
| Listar claves | salt-key -L |
| Aceptar claves | salt-key -A |
| Ping a todos | salt '*' test.ping |
| Ejecutar comando | salt '*' cmd.run 'uptime' |
| Instalar paquete | salt '*' pkg.install nginx |
| Reiniciar servicio | salt '*' service.restart nginx |
| Aplicar state | salt '*' state.apply NOMBRE |
| Aplicar highstate | salt '*' state.apply |
| Por sistema operativo | salt -G 'os:Ubuntu' cmd.run 'uname -a' |
| Por lotes | salt --batch-size 10 '*' state.apply |