TecnologíaEscrito por persona

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.

Harol Restrepo

10 min de lectura

kuberneteskubectlyamldevopsmanifiestos

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:

  • labels en JSON es un objeto ({ ... }). En YAML no lleva -: es un solo diccionario con varias claves. app y repo son campos del mismo mapa, no dos ítems.
  • containers en 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:

  1. Genera el Deployment con --dry-run=client -o yaml.
  2. Deja una label app: hola en el template del pod.
  3. Genera un Service ClusterIP que seleccione esa misma label.
  4. kubectl apply -f de ambos archivos.
  5. kubectl get deploy,svc,pods y 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 y apply puedan 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.