Guía práctica de Kubernetes
Si ya tienes un cluster local, esta guía te deja listo para agrupar recursos, generar manifiestos YAML y aplicarlos con criterio — sin memorizar la API de memoria.
Si ya tienes Kubernetes en tu máquina, el siguiente salto no es “aprender más teoría”. Es abrir un YAML, entenderlo y atreverte a cambiarlo.
Esta guía es ese entrenamiento. El objetivo no es memorizar la API: es tener un método para crear, leer y aplicar manifiestos con calma. Si aún no tienes claro qué problema resuelve Kubernetes, empieza por ¿Qué es Kubernetes?.
Agrupar o aislar recursos con Namespace
Un Namespace es un contenedor lógico. Sirve para agrupar recursos (una app, un entorno, un experimento) y no mezclarlos con el resto del cluster.
Los namespaces de sistema (default, kube-system, kube-public…) ya existen. Para practicar, crea el tuyo:
kubectl get namespaces
kubectl create namespace practica
kubectl delete namespace practica
Borrar un namespace elimina (casi) todo lo que vive dentro. Úsalo con cuidado; en el laboratorio, es justo lo que quieres.
Asegurarte de estar en el namespace correcto
kubectl no “adivina” dónde quieres trabajar. O le pasas -n, o configuras el namespace del contexto actual:
kubectl config set-context --current --namespace=practica
Para ver y cambiar de contexto (otro cluster, otro usuario, otro namespace por defecto):
kubectl config get-contexts
kubectl config use-context <nombre-del-contexto>
Namespace y contexto no son lo mismo. El namespace agrupa objetos dentro de un cluster. El contexto es la entrada de tu kubeconfig: a qué cluster hablas, con qué credenciales y en qué namespace caes si no pones -n.
kubectl config current-context
kubectl get ns
Si el objeto “no aparece”, lo primero que reviso no es el YAML: es si estoy mirando el namespace correcto.
Imperativo vs declarativo
A veces quieres un objeto ya, para probar. Otras veces quieres que quede documentado y versionado.
| Estilo | Cuándo | Comando típico |
|---|---|---|
| Imperativo | Experimento rápido | kubectl create … / kubectl run … |
| Declarativo | Lo que quieres conservar | kubectl apply -f manifiesto.yaml |
El truco de esta guía: usar lo imperativo para generar YAML, y lo declarativo para aplicarlo. Así no escribes el manifiesto desde cero, pero tampoco dejas magia sin archivo.
Generar el manifiesto en vez de inventarlo
Este comando no crea nada en el cluster. Solo imprime el YAML que crearía:
kubectl create deployment hola \
--image=nginx:1.27 \
--port=80 \
--dry-run=client \
-o yaml > hola-deployment.yaml
--dry-run=client valida y renderiza en tu máquina. -o yaml pide el manifiesto. La redirección > lo guarda.
Abre el archivo. Vas a ver campos que no pediste (status, creationTimestamp: null, a veces kubectl.kubernetes.io/last-applied-configuration si vienes de un apply). Puedes borrar lo que no declares tú. Quédate con lo que describe el objeto.
Para un Service:
kubectl create service clusterip hola \
--tcp=80:80 \
--dry-run=client \
-o yaml > hola-service.yaml
El patrón es siempre el mismo:
kubectl create <tipo> <nombre> [flags] --dry-run=client -o yaml > manifiesto.yaml
Aplicar el manifiesto
Cuando el YAML te represente, aplícalo:
kubectl apply -f hola-deployment.yaml
kubectl apply -f hola-service.yaml
También puedes aplicar un directorio (kubectl apply -f ./k8s/) o validar sin tocar el cluster:
kubectl apply -f hola-deployment.yaml --dry-run=client
apply es idempotente: si el objeto ya existe, Kubernetes lo actualiza hacia lo que dice el archivo. Por eso encaja con Git.
Verificar que el objeto exista
kubectl get deployment hola
kubectl get svc hola
kubectl get pods -l app=hola
Si algo falla, el siguiente paso no es reescribir todo: es preguntarle al objeto.
kubectl describe deployment hola
kubectl get events --sort-by='.lastTimestamp'
YAML no es otro idioma: es JSON con menos ruido
Si JSON ya te cae bien, un manifiesto YAML es el mismo árbol. Mismas claves, mismos valores, mismo anidamiento. Cambia la ropa: en JSON hay {}, [] y comillas; en YAML, la indentación hace ese trabajo.
Este objeto y este otro son equivalentes. Kubernetes los acepta los dos:
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "hola",
"labels": {
"app": "hola",
"repo": "https://ejemplo.dev/hola"
}
},
"spec": {
"containers": [
{ "name": "img1", "image": "nginx:1.27" },
{ "name": "img2", "image": "busybox:1.36" }
]
}
}
apiVersion: v1
kind: Pod
metadata:
name: hola
labels:
app: hola
repo: https://ejemplo.dev/hola
spec:
containers:
- name: img1
image: nginx:1.27
- name: img2
image: busybox:1.36
Lee el YAML en voz alta igual que el JSON. Los dos puntos (:) separan clave y valor. Lo que está más indentado es hijo de lo de arriba.
La diferencia que más confunde es mapa vs lista:
labelsen JSON es un objeto ({ ... }). En YAML no lleva-: es un solo diccionario con varias claves.appyreposon campos del mismo mapa, no dos ítems.containersen JSON es un arreglo ([ ... ]). En YAML cada elemento empieza con-. Ese guion dice “aquí nace un ítem nuevo”.
Por eso en containers el - aparece en cada salto de objeto, no en cada línea. name e image de img1 son campos del mismo contenedor: el segundo campo se indenta debajo, sin otro -. El siguiente - es img2, el segundo objeto del arreglo.
labels: # un objeto: claves, sin lista
app: hola
repo: https://ejemplo.dev/hola
containers: # una lista: un "-" por elemento
- name: img1 # empieza el primer objeto
image: nginx:1.27 # sigue siendo img1
- name: img2 # empieza el segundo objeto
image: busybox:1.36 # sigue siendo img2
Si ves un -, estás en un arreglo. Si solo ves clave: valor al mismo nivel, estás dentro de un objeto.
Por eso kubectl habla los dos dialectos:
kubectl get pod hola -o json
kubectl get pod hola -o yaml
El miedo al YAML suele ser visual: se ve “plano” y parece otro lenguaje. No lo es. Si puedes leer un JSON, ya puedes leer un manifiesto. Lo que falta no es sintaxis: es saber qué claves importan. Eso viene ahora.
Lo mínimo de un manifiesto
En YAML o JSON, estos tres campos identifican el objeto:
- apiVersion: la API del cluster para ese tipo. Debe existir. Lista lo disponible con
kubectl api-resources. - kind: el tipo (
Deployment,Service,ConfigMap…). - metadata.name: el nombre con el que lo vas a buscar.
apiVersion: apps/v1
kind: Deployment
metadata:
name: hola
Eso identifica. Casi nunca alcanza para crear. Un Deployment, un Service o un Job necesitan spec: el estado deseado. Sin spec, apply suele rechazar el archivo.
El esqueleto mental, entonces, es:
apiVersion: <versión> # ¿con qué API hablo?
kind: <tipo> # ¿qué objeto es?
metadata:
name: <nombre> # ¿cómo se llama?
namespace: practica # ¿en qué caja vive? (opcional si ya fijaste el contexto)
labels:
app: hola
spec:
# aquí vive la receta
¿Y el resto de campos? kubectl explain
No hace falta un libro para cada objeto. La documentación vive en el cluster:
kubectl explain deployment
kubectl explain deployment.spec
kubectl explain deployment.spec.template.spec.containers
Cada nivel te dice qué es el campo, si es requerido y qué subcampos admite. Si te pierdes en la jerarquía, --recursive lista el árbol:
kubectl explain deployment.spec --recursive
Y para saber qué kinds puedes crear y con qué apiVersion:
kubectl api-resources
Esa terna — dry-run para generar, explain para entender, apply para declarar — es el músculo. El YAML deja de ser un bloque opaco y se vuelve un formulario que ya sabes completar.
Labels: metadatos para seleccionar
Las labels son pares clave/valor en metadata. No “hacen” nada solas: sirven para que otros objetos encuentren a este.
metadata:
name: hola
labels:
app: hola
entorno: lab
Un Deployment las usa dos veces: en el selector (spec.selector.matchLabels) y en el Pod (spec.template.metadata.labels). Si no coinciden, el ReplicaSet no adopta los pods y te quedas mirando 0/1 Ready sin un error ruidoso.
Un Service apunta a pods con el mismo mecanismo: spec.selector. Por eso app: hola en el Deployment y en el Service no es decoración: es el cable.
Un flujo corto para coger confianza
Con el namespace practica activo:
- Genera el Deployment con
--dry-run=client -o yaml. - Deja una label
app: holaen el template del pod. - Genera un Service ClusterIP que seleccione esa misma label.
kubectl apply -fde ambos archivos.kubectl get deploy,svc,podsy confirma que los pods responden al selector.
Si puedes hacer eso sin copiar un tutorial entero, ya estás manipulando manifiestos. El resto de objetos reutiliza el mismo esqueleto.
Mapa de objetos (para no perderte)
No tienes que usarlos todos. Sí conviene saber qué pregunta responde cada uno.
Dónde viven
- Namespace: caja lógica. Agrupa y aísla recursos.
Qué corre
- Pod: la unidad que realmente se ejecuta. Uno o más contenedores con red y volúmenes compartidos. Rara vez lo creas a mano en producción: lo crea un controlador.
- ReplicaSet: mantiene N pods idénticos. Casi siempre lo administra un Deployment.
- Deployment: el controlador habitual de apps sin estado. Réplicas, actualizaciones, rollback.
- StatefulSet: réplicas con identidad estable (nombre, disco, orden). Bases de datos, colas.
- DaemonSet: un pod en cada nodo (o en un subconjunto). Agentes, logs, red.
- Job: corre hasta completar. No “se borra solo” salvo que configures
ttlSecondsAfterFinished. - CronJob: crea Jobs en un horario tipo cron.
Cómo se habla con lo que corre
- Service: dirección estable frente a pods que nacen y mueren. ClusterIP es interno; NodePort y LoadBalancer exponen hacia fuera.
- Ingress: reglas HTTP(S) hacia Services. Necesita un Ingress controller en el cluster; el objeto solo declara las rutas.
- NetworkPolicy: qué tráfico se permite entre pods. Sin políticas, el default suele ser “todo con todo”.
Configuración y datos
- ConfigMap: configuración no secreta (archivos, variables).
- Secret: datos sensibles. Siguen siendo base64 en etcd: cuida RBAC y cifrado en reposo.
- PersistentVolume (PV): trozo de almacenamiento en el cluster.
- PersistentVolumeClaim (PVC): la petición de almacenamiento que hace tu workload. El Pod monta el claim, no el PV a pelo.
Escalar y no tumbar el servicio
- HorizontalPodAutoscaler (HPA): sube o baja réplicas según métricas (CPU, memoria, custom).
- VerticalPodAutoscaler (VPA): ajusta requests/limits de CPU y memoria. No es un HPA: no escala el número de pods.
- PodDisruptionBudget (PDB): durante un drain o una actualización voluntaria, cuántos pods puedes perder sin romper el servicio.
Identidad y permisos
- ServiceAccount: identidad con la que corre un pod dentro del cluster.
- Role: permisos dentro de un namespace (qué verbos sobre qué recursos).
- RoleBinding: une un Role a usuarios, grupos o ServiceAccounts.
- ClusterRole / ClusterRoleBinding: lo mismo, pero a escala de cluster. No los listamos en cada manifiesto de app, pero existen.
PodSecurityPolicy aparecía en muchas guías viejas. Está eliminado desde Kubernetes 1.25. Hoy el equivalente cercano es Pod Security Admission (y, en clusters más estrictos, políticas tipo Kyverno o Gatekeeper).
El YAML no es un idioma aparte. Es el mismo objeto que ya ves con
kubectl get, escrito para que Git yapplypuedan repetirlo.
Resumen
Trabaja siempre en un namespace consciente. Usa create --dry-run=client -o yaml para no partir de una página en blanco. Completa el spec con kubectl explain. Aplica con kubectl apply -f. Verifica con get y describe.
Cuando ese circuito te salga natural, ya no “tienes miedo al manifiesto”: tienes un método. El resto de kinds —HPA, Ingress, PVC, Roles— son el mismo formulario con otra pregunta.
Comunidad abierta
¿Listo para prender el cerebro?
Explora artículos claros, gratuitos y hechos para compartirse. Aprender es gratis. Compartir también.