Los comandos que no salen en los manuales
Todo lo que has usado hasta ahora sale en los manuales. Esta lección recorre los trece comandos que no: los que se aprenden mirando por encima del hombro de alguien que lleva años operando clústeres. Cinco de ellos vienen con misión; el resto, léelos despacio, porque el día que los necesites no habrá tiempo de buscarlos.
El escenario: un Pod objetivo esperando su destino y una ServiceAccount worker recién creada. Trabaja desde la pestaña dev-machine.
Misión 1: un token efímero
Un proceso externo necesita autenticarse como la ServiceAccount worker durante los próximos 10 minutos, ni uno más. La forma antigua era crear un Secret de larga duración; la forma moderna (desde Kubernetes 1.24) no toca Secrets:
¿Cómo genero un token de corta duración para una ServiceAccount?
kubectl create token worker --duration=10m > /home/laborant/token.txt
Ábrelo: es un JWT (tres bloques en base64 separados por puntos) con caducidad incorporada. Conecta directamente con el módulo de seguridad: identidad efímera en vez de credencial eterna.
¿Y ahora qué hago con él? Usarlo. Un token es una credencial: se presenta en la cabecera Authorization de cualquier petición a la API. Pregúntale al clúster quién eres cuando lo llevas puesto:
SERVIDOR=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
curl -sk -H "Authorization: Bearer $(cat /home/laborant/token.txt)" "${SERVIDOR}/apis/authentication.k8s.io/v1/selfsubjectreviews" -X POST -H 'Content-Type: application/json' -d '{"apiVersion":"authentication.k8s.io/v1","kind":"SelfSubjectReview"}' | jq .status.userInfo
La API responde con la identidad que ha reconocido: system:serviceaccount:default:worker, sus grupos y la caducidad. El token funciona.
Ahora pídele algo de verdad:
curl -sk -H "Authorization: Bearer $(cat /home/laborant/token.txt)" "${SERVIDOR}/api/v1/namespaces/default/pods" | jq -r '.message // .kind'
pods is forbidden. Y ahí está la lección que se lleva mucha gente por delante: el token dice quién eres, no qué puedes hacer. Autenticación y autorización son dos puertas distintas, y acabas de cruzar solo la primera. Dale permiso de lectura a esa identidad y repite la llamada:
kubectl create rolebinding worker-lector --clusterrole=view --serviceaccount=default:worker
curl -sk -H "Authorization: Bearer $(cat /home/laborant/token.txt)" "${SERVIDOR}/api/v1/namespaces/default/pods" | jq -r '.items[].metadata.name'
La lista de Pods, con el mismo token de antes y sin reiniciar nada. Lo que cambió no fue la credencial: fue el RoleBinding.
El -k del curl se salta la validación del certificado del API server. En el laboratorio vale; en producción se pasa la CA con --cacert, como hiciste desde dentro de un Pod en el módulo de seguridad.
Misión 2: un kubeconfig de bolsillo
Tienes que pasarle acceso a este clúster a una herramienta externa, y tu ~/.kube/config puede acumular contextos de varios clústeres y referencias a ficheros de certificados que la herramienta no tendrá.
¿Cómo genero un kubeconfig limpio y autocontenido con solo el contexto activo?
kubectl config view --minify --flatten > /home/laborant/kubeconfig-lab.yaml
--minify recorta todo lo que no sea el contexto actual; --flatten embebe los certificados dentro del propio fichero. El resultado funciona en cualquier sitio tal cual.
Pero fíjate en lo que acabas de meter en ese fichero: tus credenciales de administrador. Entregarlo así es dar las llaves del clúster entero. Cámbialas por el token de la misión anterior, que caduca en diez minutos y solo sabe leer:
kubectl --kubeconfig=/home/laborant/kubeconfig-lab.yaml config set-credentials worker --token="$(cat /home/laborant/token.txt)"
kubectl --kubeconfig=/home/laborant/kubeconfig-lab.yaml config set-context --current --user=worker
Y ahora úsalo, que es la prueba de que sirve para algo:
kubectl --kubeconfig=/home/laborant/kubeconfig-lab.yaml auth whoami
kubectl --kubeconfig=/home/laborant/kubeconfig-lab.yaml get pods
El primero responde system:serviceaccount:default:worker: kubectl deja de ser tú y pasa a ser la ServiceAccount. El segundo lista los Pods, porque le diste view hace un momento. Prueba a borrar algo con ese kubeconfig y verás el Forbidden desde el otro lado del mostrador.
Eso es lo que se le entrega a una herramienta externa, a un runner de CI o a un compañero: un fichero, una identidad acotada y una caducidad. Cuando pasen los diez minutos, el fichero seguirá ahí y dejará de funcionar solo.
Misión 3: entrar en un nodo sin SSH
Necesitas mirar algo en el sistema de archivos de node-01 y no tienes (ni quieres montar) acceso SSH.
¿Cómo depuro un nodo entero, no solo un Pod?
kubectl debug node/node-01 -it --image=ghcr.io/iximiuz/labs/nginx:alpine -- sh
Kubernetes crea un Pod privilegiado en ese nodo con su sistema de archivos montado en /host. Y ya que has entrado, entra a algo: estas son las tres preguntas que se responden dentro de un nodo y no se pueden responder desde fuera.
df -h /host
Cuánto disco le queda. Es la causa del DiskPressure que expulsa Pods de un nodo, y no aparece en ningún kubectl get.
ls /host/var/lib/kubelet/pods | wc -l
Cuántos directorios de Pod ha dejado el kubelet en el disco. Si ese número no baja nunca, tienes volúmenes que no se desmontan.
ls /host/etc/rancher/k3s
La configuración del propio k3s en ese nodo: los ficheros que el kubelet lee al arrancar y que explican por qué el nodo se comporta como se comporta.
Sal con exit. El Pod de depuración (node-debugger-node-01-...) queda atrás como evidencia; en el mundo real lo borrarías al terminar.
Misión 4: esperar a que algo muera de verdad
Los scripts que borran recursos suelen cometer el mismo pecado: asumir que delete es instantáneo, cuando en realidad dispara una terminación con período de gracia. La espera correcta, sin bucles manuales:
¿Cómo espero a que un recurso desaparezca de verdad?
kubectl delete pod objetivo --wait=false
kubectl wait pod objetivo --for=delete --timeout=60s
El primer comando pide la eliminación sin bloquearse; el segundo no vuelve hasta que el Pod ha desaparecido del todo. Es el compañero de --for=condition=Available que usaste en la lección declarativa, para el viaje de vuelta.
Misión 5: el esquema completo, sin salir de la terminal
Última misión, de pura erudición. La documentación de cada campo de cada recurso vive dentro del propio clúster:
¿Cómo vuelco el esquema completo de un recurso de golpe?
kubectl explain pod.spec --recursive | less
kubectl explain pod.spec.hostNetwork
La forma recursiva dibuja el árbol entero de campos con sus tipos; la puntual explica un campo concreto. Pregunta de examen: ¿de qué tipo es pod.spec.hostNetwork?
Los otros ocho, para tu libreta
¿Cómo hablo con la API REST directamente, sin el modelo de recursos de kubectl?
kubectl get --raw /metrics | head
kubectl get --raw /api/v1/namespaces/tienda/pods
get --raw es la puerta trasera honesta: cualquier endpoint del API server, en crudo. El primero vuelca las métricas Prometheus del propio API server.
¿Sabías que logs, exec y port-forward aceptan controladores directamente?
kubectl logs deploy/coredns -n kube-system
Ya lo usaste en la lección imperativa casi sin darte cuenta: deploy/, sts/ y rs/ funcionan en los tres comandos, y kubectl elige un Pod por ti.
¿Cómo veo las peticiones HTTP reales que manda kubectl?
kubectl get pods --v=9
Verbosidad 9: cada petición y respuesta contra la API, con URLs y cuerpos. La herramienta definitiva para entender qué hace kubectl por debajo (y para copiar esas llamadas en tus propios scripts).
¿Cómo sigo los eventos de un objeto de forma legible?
kubectl events --for pod/<nombre> --watch
El sustituto moderno del get events --field-selector que usaste en la lección imperativa: mismo resultado, sintaxis humana.
¿Cómo cambio la herramienta que usa kubectl diff?
Para verlo hace falta algo que comparar, así que crea un objeto y un manifiesto que difiera de él en un campo:
kubectl create configmap ajustes --from-literal=modo=lectura
cat << 'EOF' > manifiesto.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: ajustes
data:
modo: escritura
EOF
KUBECTL_EXTERNAL_DIFF="diff -u --color=always" kubectl diff -f manifiesto.yaml
Una línea en rojo (modo: lectura, lo que hay) y otra en verde (modo: escritura, lo que propones). La variable admite el comando y sus argumentos, y lo que recibe son dos directorios: el estado actual y el propuesto. Cualquier herramienta que compare dos rutas sirve (colordiff, delta, vimdiff), siempre que esté instalada en la máquina.
Y un detalle que sorprende en el primer pipeline: kubectl diff sale con código 1 cuando encuentra diferencias. No es un error, es su forma de responder "sí, hay cambios"; el 0 significa "esto ya está aplicado". Un set -e mal puesto convierte esa respuesta en un despliegue abortado.
¿Cómo leo o modifico solo el status de un recurso?
kubectl get deployment <nombre> --subresource=status -o yaml
El status como subrecurso independiente: lo que escriben los controllers, separado de lo que escribes tú.
¿Cómo descubro quién es dueño de cada campo de un recurso?
kubectl get deployment <nombre> -o yaml --show-managed-fields
Los managedFields que kubectl esconde por defecto, y que todo el mundo da por ruido, son en realidad el registro de propiedad de Server-Side Apply: qué gestor (kubectl, un controller, un operador) escribió cada campo. Clave cuando dos herramientas se pelean por un valor.
¿Cómo inyecto un PodSpec arbitrario en un Pod efímero, sin manifiesto?
kubectl run debug --image=ghcr.io/iximiuz/labs/nginx:alpine \
--overrides='{"spec":{"nodeSelector":{"kubernetes.io/hostname":"node-01"}}}' -it --rm -- sh
--overrides fusiona JSON crudo sobre el Pod generado. Te sonará: es el truco que usó la lección de cuotas para fabricar el Pod tragón que la ResourceQuota rechazó.
Resumen
- Identidad y acceso:
create token(efímero, sin Secrets) yconfig view --minify --flatten(kubeconfig portátil). - Bajo el capó:
get --raw,--v=9,--subresource=status,--show-managed-fields. - Operación fina:
debug node/,wait --for=delete,events --for --watch,logs deploy/,run --overrides. - Y la joya humilde:
explain --recursive, la documentación viva de tu propia versión del clúster.
- Previous lesson
- Helm
- Next lesson
- Un tipo de recurso propio (CRD)