Manifiestos YAML
En Kubernetes no se crean las cosas con clics ni con comandos sueltos: se describen en archivos de texto (manifiestos) en formato YAML y se aplican con kubectl apply -f <archivo>. Esta página explica cómo se escribe cada tipo de objeto, con plantillas para copiar basadas en una app Node llamada chanchito y su base de datos monguito.
Términos de esta página
- Manifiesto
- Un archivo
.yamlque describe uno o más objetos. Es el “estado deseado” en texto. - YAML
- Formato de texto con sangría de espacios: lo que está más a la derecha pertenece a la línea de arriba.
clave: valores un dato y-marca un elemento de lista. - apiVersion / kind
- Qué tipo de objeto es (
kind: Deployment) y qué versión de la API de Kubernetes lo define (apps/v1). - metadata
- Datos de identificación: el nombre, el namespace y las labels.
- spec
- La especificación: lo que querés que pase (cuántas réplicas, qué imagen, qué puertos).
- status
- Lo que realmente está pasando. Lo escribe Kubernetes, vos nunca.
- Label / selector
- Una label es una etiqueta en un objeto (
app: chanchito); un selector es un filtro que busca objetos por sus labels. Así se conectan, por ejemplo, un Service y sus Pods. - template
- Dentro de un Deployment, el “molde” con el que se crea cada Pod.
- Probe
- Un chequeo automático (por ejemplo, pedir
/health) que Kubernetes hace para saber si el contenedor está vivo o listo. - requests / limits
- Cuánta CPU y memoria se reserva para el contenedor y cuánto puede usar como máximo.
100m= una décima de núcleo de CPU;128Mi= 128 megabytes. - Base64
- Una forma de escribir datos como texto. No es cifrado: cualquiera puede decodificarlo.
Anatomía de un manifiesto
Siempre los mismos 4 bloques.
Los 4 campos raíz
apiVersion: apps/v1 # versión de la API para este tipo
kind: Deployment # QUÉ objeto es
metadata: # CÓMO se llama y cómo se etiqueta
name: chanchito
namespace: default
labels:
app: chanchito
spec: # LO QUE QUERÉS (estado deseado)
replicas: 2
# ...
# status: # lo que HAY: lo completa Kubernetes, no se escribe
Indentación con espacios (2 es lo habitual). Un error de sangría cambia el significado sin dar error de sintaxis.
apiVersion de cada kind
| kind | apiVersion |
|---|---|
| Pod, Service, ConfigMap, Secret, PersistentVolumeClaim, Namespace | v1 |
| Deployment, StatefulSet, DaemonSet, ReplicaSet | apps/v1 |
| Job, CronJob | batch/v1 |
| Ingress | networking.k8s.io/v1 |
| HorizontalPodAutoscaler | autoscaling/v2 |
¿No te acordás? kubectl explain <kind> muestra la versión y los campos. O generá la base con --dry-run=client -o yaml.
Labels y selectors: cómo se encuentran los objetos
Nada se conecta por nombre: se conecta por labels. Si no coinciden, no funciona (y no da error).
selector.matchLabels del Deployment tiene que ser igual a template.metadata.labels. Si no, apply lo rechaza.
El selector del Service elige a qué Pods mandar tráfico. Verificalo con kubectl get endpoints chanchito.
El selector de un Deployment no se puede cambiar después de creado: hay que borrarlo y recrearlo.
Deployment
Tu app corriendo con N réplicas.
chanchito-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: chanchito
labels:
app: chanchito
spec:
replicas: 2
selector:
matchLabels:
app: chanchito # debe = template.metadata.labels
template: # molde de cada Pod
metadata:
labels:
app: chanchito
spec:
containers:
- name: chanchito
image: chanchito:1 # tag fijo, nunca latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: production
envFrom:
- configMapRef:
name: chanchito-config
- secretRef:
name: mongo-secret
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { memory: 256Mi }
readinessProbe:
httpGet: { path: /, port: 3000 }
initialDelaySeconds: 5
Campo por campo
| Campo | Qué es |
|---|---|
replicas | Cuántos Pods iguales |
selector | Qué Pods “son suyos” (por label) |
template | El molde del Pod: labels + contenedores |
containers[].image | Imagen (≈ image: del compose) |
imagePullPolicy | IfNotPresent: usa la local si existe. Always: siempre baja. Never: solo local |
containerPort | Puerto donde escucha la app (informativo, como EXPOSE) |
env | Variables sueltas |
envFrom | Todas las claves de un ConfigMap/Secret como variables |
resources | CPU/RAM reservada (requests) y máxima (limits) |
readinessProbe | Cuándo el Pod está listo para recibir tráfico |
strategy | RollingUpdate (por defecto) o Recreate |
Con image: chanchito:latest Kubernetes usa imagePullPolicy: Always y va a intentar bajarla de Docker Hub → ImagePullBackOff con imágenes locales. Usá tags con versión.
Service
Nombre DNS e IP fija delante de los Pods. Detalle de tipos y puertos en Red, config y datos.
ClusterIP (interno, por defecto)
apiVersion: v1
kind: Service
metadata:
name: chanchito # nombre DNS: http://chanchito
spec:
type: ClusterIP
selector:
app: chanchito # a qué Pods manda tráfico
ports:
- port: 80 # puerto del Service
targetPort: 3000 # puerto del contenedor
NodePort (accesible desde tu PC)
apiVersion: v1
kind: Service
metadata:
name: chanchito
spec:
type: NodePort
selector:
app: chanchito
ports:
- port: 80
targetPort: 3000
nodePort: 30080 # 30000-32767 → localhost:30080
En Docker Desktop entrás por http://localhost:30080. En minikube: minikube service chanchito --url.
ConfigMap y Secret
Sacan la configuración de la imagen (≈ environment: / .env del compose).
ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: chanchito-config
data:
MONGO_HOST: monguito # nombre del Service de Mongo
MONGO_DB: miapp
LOG_LEVEL: info
Secret
apiVersion: v1
kind: Secret
metadata:
name: mongo-secret
type: Opaque
stringData: # texto plano; se guarda en base64
MONGO_USER: mati
MONGO_PASSWORD: <password>
kubectl create secret generic mongo-secret \
--from-literal=MONGO_USER=mati \
--from-literal=MONGO_PASSWORD=<password>--from-literal agrega una clave con su valor. Así la contraseña no queda escrita en ningún archivo del repositorio; la contra es que hay que repetir el comando en cada cluster nuevo.No subas YAML de Secrets con valores reales a git: base64 no es cifrado.
PersistentVolumeClaim
Disco que sobrevive a los Pods (≈ volumes: mongo-data:).
Pedir el disco
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongo-data
spec:
accessModes:
- ReadWriteOnce # un nodo a la vez
resources:
requests:
storage: 1Gi
# storageClassName: standard # opcional: usa la default
Montarlo en el contenedor
spec:
template:
spec:
containers:
- name: monguito
image: mongo:7
volumeMounts:
- name: datos # nombre interno
mountPath: /data/db # mismo path que en Docker
volumes:
- name: datos
persistentVolumeClaim:
claimName: mongo-data # el PVC
Dos pasos: volumes (a nivel Pod) declara de dónde sale; volumeMounts (en el contenedor) dice dónde se monta. Se unen por name.
Ingress
Entrada HTTP por dominio/path. Requiere un Ingress controller instalado (ej. Traefik).
chanchito.localhost → Service chanchito
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: chanchito
spec:
ingressClassName: traefik
rules:
- host: chanchito.localhost
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: chanchito
port:
number: 80
Notas
*.localhostresuelve a tu PC sin tocar el archivohostsen la mayoría de navegadores.- Varios
pathsohosts→ varios Services detrás de una sola entrada (ej./apial backend y/al frontend). - Para HTTPS se agrega
tls:con un Secret de tipokubernetes.io/tls. - Gateway API (
Gateway+HTTPRoute) es el reemplazo moderno de Ingress; el controller ingress-nginx fue retirado en 2026.
kubectl get ingress
kubectl get ingressclassProbes y recursos
Cómo sabe Kubernetes si tu app está viva y cuánto le puede dar.
Probes
| Probe | Si falla… |
|---|---|
readinessProbe | Se saca el Pod del Service (no recibe tráfico). No lo reinicia. |
livenessProbe | Reinicia el contenedor. |
startupProbe | Da tiempo extra a apps lentas antes de evaluar las otras dos. |
livenessProbe:
httpGet: { path: /health, port: 3000 } # o tcpSocket / exec
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
Una livenessProbe mal configurada (path inexistente, poco delay) mata la app en loop → CrashLoopBackOff.
Recursos
| Campo | Significado |
|---|---|
requests.cpu | CPU reservada para ubicar el Pod. 100m = 0,1 núcleo |
requests.memory | RAM reservada. 128Mi |
limits.cpu | Tope de CPU (si lo pasa, se ralentiza) |
limits.memory | Tope de RAM (si lo pasa → OOMKilled) |
Si los requests son más de lo que tiene libre algún nodo, el Pod queda Pending.
Organizar los archivos
Un archivo por objeto o varios en uno separados con ---.
Estructura sugerida
mi-proyecto/
├── Dockerfile
├── docker-compose.yml
└── k8s/
├── chanchito-config.yaml # ConfigMap
├── mongo-secret.yaml # Secret (fuera de git)
├── mongo-pvc.yaml
├── monguito.yaml # Deployment + Service
├── chanchito.yaml # Deployment + Service
└── ingress.yaml
kubectl apply -f k8s/Varios objetos en un archivo
apiVersion: apps/v1
kind: Deployment
metadata:
name: monguito
# ...
--- # separador de documentos
apiVersion: v1
kind: Service
metadata:
name: monguito
# ...
Juntá en un archivo lo que va siempre unido (Deployment + su Service). La migración completa está en De Compose a K8s.