En la lección anterior usamos kubectl apply como si fuera un comando obvio y vimos manifiestos YAML de pasada, sin desmenuzarlos. Toca saldar esa deuda, porque el manifiesto es la unidad de trabajo real de Kubernetes: todo lo que despliegues durante los once módulos que quedan será un fichero YAML que describe un objeto. Esta lección te enseña la anatomía exacta de cualquier objeto de la API —los cuatro campos que siempre están, hagas lo que hagas—, cómo descubrir qué apiVersion corresponde a cada tipo, el subconjunto de YAML que necesitas dominar, la diferencia real entre apply, create y replace y por qué importa, cómo validar un manifiesto antes de aplicarlo, y cómo organizar los ficheros del proyecto Rutas Norte en el directorio k8s/ para que el repositorio sea la fuente de verdad.
Contenido
- Anatomía de un objeto de Kubernetes
- Grupos, versiones y niveles de estabilidad de la API
- El YAML que hace falta saber
- Estado deseado y estado observado
applyfrente acreateyreplace- Server-side apply y la propiedad de campos
- Validar antes de aplicar
- Organización de los manifiestos del proyecto
- Anatomía de un objeto de Kubernetes
Absolutamente todos los objetos de Kubernetes —un pod, un secreto, un permiso RBAC, un CRD de un operador— comparten la misma estructura de cuatro campos de primer nivel. Aprender esa estructura una vez sirve para siempre.
# k8s/base/api-reservas-pod.yaml
apiVersion: v1 # 1. QUÉ versión de la API interpreta esto
kind: Pod # 2. QUÉ tipo de objeto es
metadata: # 3. QUIÉN es: identidad y metadatos
name: api-reservas
namespace: rutas-norte-dev
labels:
app: api-reservas
app.kubernetes.io/part-of: rutas-norte
entorno: dev
annotations:
rutasnorte.example/responsable: [email protected]
spec: # 4. CÓMO debe ser: el estado deseado
containers:
- name: api
image: registry.rutasnorte.example/api-reservas:2.4.0
ports:
- containerPort: 3000
env:
- name: DB_HOST
value: postgres-reservas1.1. apiVersion
Indica el grupo y la versión de la API que debe interpretar el objeto. Determina qué campos son válidos y cómo se comportan. Ejemplos: v1 (grupo core), apps/v1, networking.k8s.io/v1, batch/v1. Es el campo que más errores provoca cuando se copian manifiestos antiguos de internet.
1.2. kind
El tipo concreto: Pod, Deployment, Service, Ingress, ConfigMap. Se escribe siempre en CamelCase, mientras que el recurso en la URL de la API va en minúsculas y plural (pods, deployments). La pareja apiVersion + kind identifica unívocamente el esquema del objeto.
1.3. metadata
La identidad del objeto. Sus campos más relevantes:
| Campo | Quién lo escribe | Para qué sirve |
|---|---|---|
name |
Tú | Nombre único dentro del namespace y del tipo. Debe ser un nombre DNS válido: minúsculas, números y guiones |
namespace |
Tú (o el contexto) | Namespace donde vive. Si se omite, se usa el del contexto de kubectl |
labels |
Tú | Metadatos consultables: son la base de selectores, Services y controladores |
annotations |
Tú y las herramientas | Metadatos no consultables: configuración de terceros, trazabilidad |
uid |
El sistema | Identificador único e irrepetible en todo el clúster |
resourceVersion |
El sistema | Versión interna del objeto; sirve para detectar escrituras concurrentes |
creationTimestamp |
El sistema | Fecha de creación |
ownerReferences |
El sistema | Quién "posee" el objeto. Un pod creado por un ReplicaSet lo apunta aquí, y por eso al borrar el ReplicaSet se borran sus pods (recolección en cascada) |
finalizers |
El sistema o herramientas | Tareas que deben completarse antes de que el objeto pueda borrarse |
Un detalle que explica un comportamiento desconcertante: si un namespace o un PVC se queda "colgado" en estado Terminating, casi siempre es por un finalizer cuyo responsable no ha terminado su trabajo.
1.4. spec
El estado deseado. Es lo que tú describes y su contenido depende por completo del kind: el spec de un Pod tiene containers, el de un Service tiene selector y ports, el de un PVC tiene resources.requests.storage. Todo el curso consiste, en el fondo, en aprender a escribir spec de distintos tipos.
1.5. status
No aparece en el manifiesto de arriba porque no lo escribes tú: lo escribe el clúster. Es el estado observado. Si consultas el objeto ya creado lo verás:
status:
phase: Running
podIP: 10.244.0.17
hostIP: 192.168.49.2
startTime: "2026-08-05T09:14:22Z"
conditions:
- type: Initialized
status: "True"
- type: Ready
status: "True"
- type: ContainersReady
status: "True"
containerStatuses:
- name: api
ready: true
restartCount: 0
image: registry.rutasnorte.example/api-reservas:2.4.0
imageID: registry.rutasnorte.example/api-reservas@sha256:9f3a1c2...Fíjate en dos cosas. Primera: conditions es el formato estándar con el que Kubernetes expresa "cómo va" un objeto, y es lo que consultan kubectl wait y las herramientas de despliegue. Segunda: imageID contiene el digest real de la imagen que se está ejecutando, mientras que spec.image contiene la etiqueta que tú pediste. Cuando alguien reutiliza una etiqueta, esos dos valores dejan de corresponderse; volveremos a ello al hablar de etiquetas inmutables en la próxima lección.
- Grupos, versiones y niveles de estabilidad de la API
La API de Kubernetes está dividida en grupos para poder evolucionar por partes y permitir extensiones.
| Grupo | apiVersion |
Tipos que contiene |
|---|---|---|
| core (o legacy) | v1 |
Pod, Service, ConfigMap, Secret, Namespace, PersistentVolume, PersistentVolumeClaim, ServiceAccount, Node |
| apps | apps/v1 |
Deployment, ReplicaSet, StatefulSet, DaemonSet |
| batch | batch/v1 |
Job, CronJob |
| networking.k8s.io | networking.k8s.io/v1 |
Ingress, NetworkPolicy, IngressClass |
| rbac.authorization.k8s.io | rbac.authorization.k8s.io/v1 |
Role, RoleBinding, ClusterRole, ClusterRoleBinding |
| autoscaling | autoscaling/v2 |
HorizontalPodAutoscaler |
| storage.k8s.io | storage.k8s.io/v1 |
StorageClass, VolumeAttachment |
| policy | policy/v1 |
PodDisruptionBudget |
El grupo core es el histórico y por eso su apiVersion no lleva prefijo: es v1, no core/v1. Todos los demás siguen el formato grupo/versión.
Cómo descubrirlo en tu propio clúster
No hay que memorizar esta tabla: se consulta.
# Todos los grupos y versiones que soporta este clúster
kubectl api-versions
# Todos los recursos, con su apiVersion, abreviatura y ámbito
kubectl api-resources
# Filtrar por grupo
kubectl api-resources --api-group=networking.k8s.ioNAME SHORTNAMES APIVERSION NAMESPACED KIND
ingressclasses networking.k8s.io/v1 false IngressClass
ingresses ing networking.k8s.io/v1 true Ingress
networkpolicies netpol networking.k8s.io/v1 true NetworkPolicyEste es el reflejo que hay que adquirir: antes de copiar un apiVersion de un blog, compruébalo con kubectl api-resources. La versión correcta es siempre la que tu clúster dice.
Alpha, beta y estable
| Nivel | Ejemplo | Qué significa | Recomendación |
|---|---|---|---|
| Alpha | v1alpha1 |
Puede desaparecer o cambiar sin aviso. Desactivada por defecto. Puede haber pérdida de datos | Nunca en producción |
| Beta | v2beta2 |
Bien probada, pero los campos aún pueden cambiar. Desde 1.24 las APIs beta nuevas vienen desactivadas por defecto | Solo con plan de migración |
| Estable | v1, v2 |
Compatibilidad garantizada durante muchas versiones | La que debes usar |
Kubernetes retira versiones antiguas con cada actualización, y ese es el motivo de que manifiestos copiados de tutoriales viejos fallen:
error: unable to recognize "deploy.yaml": no matches for kind "Deployment"
in version "extensions/v1beta1"Grupo extensions/v1beta1 desapareció hace muchas versiones. Hoy un Deployment es apps/v1 y un Ingress es networking.k8s.io/v1, sin excepciones. Antes de actualizar un clúster, herramientas como kubent (kube-no-trouble) revisan tus manifiestos en busca de APIs a punto de retirarse.
- El YAML que hace falta saber
YAML es simple pero tiene trampas. Este es el subconjunto que necesitas.
3.1. Mapas, listas e indentación
# Un mapa (clave: valor)
metadata:
name: api-reservas # indentación con ESPACIOS, nunca tabuladores
namespace: rutas-norte-dev
# Una lista de cadenas
args:
- "--puerto=3000"
- "--modo=produccion"
# Una lista de mapas: el guion marca el inicio de cada elemento
containers:
- name: api # este 'name' pertenece al primer elemento
image: nginx:1.27
ports:
- containerPort: 3000
- name: sidecar-metricas # segundo elemento
image: prom/statsd-exporter:v0.26.0Reglas innegociables:
- Espacios, nunca tabuladores. Un tabulador es un error de sintaxis en YAML. Configura tu editor para expandir tabuladores a 2 espacios en ficheros
.yaml. - La indentación define la jerarquía. Dos espacios por nivel es la convención en Kubernetes.
- El guion
-indica elemento de lista y su contenido se indenta al mismo nivel que el guion o más.
3.2. Tipos y comillas: la trampa clásica
data:
replicas_texto: "3" # cadena
replicas_numero: 3 # entero
activo: true # booleano
activo_texto: "true" # cadena
version: "1.30" # cadena; sin comillas sería el número 1.30
puerto_noruega: "NO" # ¡IMPRESCINDIBLE! sin comillas, YAML 1.1 lo lee como falseEl caso NO es célebre (se conoce como the Norway problem): YAML 1.1 interpreta yes, no, on, off, y, n como booleanos. Regla práctica: en un ConfigMap o Secret, entrecomilla siempre los valores, porque esos campos exigen cadenas y un valor mal tipado produce un error tan claro como este:
error: error validating data: ValidationError(ConfigMap.data.puerto):
invalid type for io.k8s.api.core.v1.ConfigMap.data: got "boolean", expected "string"3.3. Cadenas multilínea
Fundamentales para incrustar ficheros de configuración en un ConfigMap:
data:
# | conserva los saltos de línea (literal). Es el que querrás casi siempre
nginx.conf: |
server {
listen 80;
root /usr/share/nginx/html;
location /api/ {
proxy_pass http://api-reservas:80/;
}
}
# > pliega los saltos en espacios (folded): útil para textos largos
descripcion: >
Configuracion de la tienda web de Rutas Norte
para el entorno de desarrollo.| Indicador | Efecto | Uso típico |
|---|---|---|
| |
Conserva saltos de línea; elimina el último | Ficheros de configuración, scripts |
|- |
Conserva saltos; elimina el salto final | Cuando la línea final sobra |
> |
Convierte saltos en espacios | Textos descriptivos largos |
3.4. Varios documentos en un fichero
El separador --- permite poner varios objetos en un mismo fichero, y kubectl apply los procesa en orden:
# k8s/base/redis-cache.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-cache-config
namespace: rutas-norte-dev
data:
maxmemory: "256mb"
---
apiVersion: v1
kind: Pod
metadata:
name: redis-cache
namespace: rutas-norte-dev
labels:
app: redis-cache
spec:
containers:
- name: redis
image: redis:7.2-alpineCriterio recomendado: agrupa en un fichero los objetos que forman una unidad desplegable (un componente con su ConfigMap y su Service) y separa en ficheros distintos los componentes diferentes. Un único fichero gigante con toda la plataforma es difícil de revisar y de aplicar parcialmente.
- Estado deseado y estado observado
Aquí es donde el modelo declarativo se vuelve concreto. Ya conoces el bucle de reconciliación de la lección Arquitectura de Kubernetes; veámoslo desde el punto de vista del fichero.
flowchart LR
Y["Manifiesto YAML<br/>en k8s/"] -->|kubectl apply| API["kube-apiserver"]
API -->|persiste spec| E[("etcd")]
C["Controlador"] -->|lee spec| API
C -->|observa| R["Mundo real<br/>pods, nodos"]
C -->|escribe status| API
R -->|diferencia| C
Cuatro consecuencias prácticas que conviene tener muy claras:
- Tú escribes
spec; el sistema escribestatus. Editar unstatusa mano no sirve de nada: el controlador lo sobrescribirá en su siguiente ciclo. - Aplicar no es ejecutar.
kubectl applytermina cuando el objeto está guardado, no cuando la aplicación está lista. Para esperar de verdad:
kubectl apply -f k8s/base/api-reservas.yaml
kubectl wait --for=condition=Ready pod/api-reservas -n rutas-norte-dev --timeout=90s- Es idempotente. Aplicar diez veces el mismo fichero da el mismo resultado que aplicarlo una. Esto es lo que hace posible que un pipeline de CI/CD ejecute
kubectl applyen cada despliegue sin comprobar antes qué existe.
- El fichero es la fuente de verdad, no el clúster. Si alguien cambia algo con
kubectl edit, el siguienteapplydesde el repositorio lo revierte. Esa es exactamente la propiedad sobre la que se construye GitOps (módulo 10).
apply frente a create y replace
apply frente a create y replaceTres comandos que parecen intercambiables y no lo son en absoluto.
| Comando | Si el objeto NO existe | Si el objeto YA existe | Conserva cambios de otros |
|---|---|---|---|
kubectl create -f |
Lo crea | Error: AlreadyExists |
— |
kubectl replace -f |
Error: NotFound |
Lo sustituye entero | No: borra lo que no esté en el fichero |
kubectl apply -f |
Lo crea | Lo fusiona con lo existente | Sí, si son de otro gestor de campos |
La conclusión operativa es simple: usa apply siempre. create sirve para comandos imperativos rápidos (kubectl create namespace) y replace para casos muy concretos de sustitución total.
Por qué apply sabe qué borrar: la anotación last-applied
Imagina que aplicas un pod con dos etiquetas, luego editas el fichero y dejas solo una, y vuelves a aplicar. ¿Cómo sabe kubectl que debe eliminar la segunda etiqueta, en lugar de dejarla porque "no está en el fichero pero tampoco he pedido borrarla"?
La respuesta clásica es que apply guarda una copia de lo último que aplicaste en una anotación:
kubectl get pod api-reservas -n rutas-norte-dev \
-o jsonpath='{.metadata.annotations.kubectl\.kubernetes\.io/last-applied-configuration}'Con esa información, kubectl hace una fusión de tres vías: compara (a) lo que aplicaste la última vez, (b) lo que aplicas ahora y (c) lo que hay en el clúster. Lo que estaba en (a) y desaparece en (b) se borra; lo que solo está en (c) —porque lo puso un controlador o un webhook— se respeta.
Esto explica un error frecuente: si creaste un objeto con kubectl create y después lo modificas con apply, no existe la anotación de referencia y kubectl avisa de que la fusión puede ser incorrecta. Por coherencia, usa apply desde el principio de la vida de cada objeto.
- Server-side apply y la propiedad de campos
Desde Kubernetes 1.22, la fusión puede hacerla el servidor en lugar del cliente, y es el mecanismo hacia el que se está migrando todo el ecosistema.
La diferencia es conceptual: en lugar de una anotación con el último estado, el servidor registra qué gestor es propietario de cada campo en metadata.managedFields.
metadata:
managedFields:
- manager: kubectl
operation: Apply
apiVersion: v1
fieldsV1:
f:spec:
f:containers:
k:{"name":"api"}:
f:image: {}
- manager: kubelet
operation: Update
subresource: statusVentajas frente a la fusión en cliente:
- Detecta conflictos. Si intentas modificar un campo que pertenece a otro gestor (un HPA que controla
replicas, por ejemplo), la petición falla con un aviso claro en lugar de provocar una pelea silenciosa entre herramientas:
Si de verdad quieres apropiarte del campo, se fuerza explícitamente:
- No depende de una anotación que en objetos grandes puede ser enorme.
- Varios gestores pueden coexistir sobre el mismo objeto de forma ordenada: tu pipeline, un operador y un autoescalador, cada uno dueño de sus campos.
Para el curso seguiremos usando kubectl apply a secas, que es lo habitual, pero conviene que reconozcas managedFields cuando aparezca en un -o yaml y que sepas qué significa un error de conflicto.
- Validar antes de aplicar
Aplicar sin comprobar es la receta del incidente. Kubernetes ofrece cuatro niveles de verificación, de más barato a más completo.
7.1. --dry-run=client: validación local
kubectl construye el objeto y valida el esquema sin enviar nada. Detecta errores de sintaxis YAML y campos inexistentes. No detecta problemas de permisos, cuotas ni webhooks. Es también la bandera que usamos en la lección anterior para generar manifiestos:
kubectl create configmap tienda-web-config \
--from-literal=api_url=http://api-reservas \
-n rutas-norte-dev --dry-run=client -o yaml > k8s/base/tienda-web-configmap.yaml7.2. --dry-run=server: validación real sin persistir
La petición sí llega al apiserver y recorre todo el camino de la lección 01-02 —autenticación, autorización RBAC, admisión, validación— pero no se escribe en etcd. Detecta lo que el cliente no puede ver: falta de permisos, cuotas superadas, políticas de seguridad de pods, webhooks de admisión propios. Es la validación que debe ejecutar tu pipeline de CI antes de desplegar.
7.3. kubectl diff: ver el cambio antes de hacerlo
diff -u -N /tmp/LIVE-1234/v1.Pod.rutas-norte-dev.api-reservas /tmp/MERGED-5678/...
--- LIVE
+++ MERGED
@@ -18,7 +18,7 @@
containers:
- name: api
- image: registry.rutasnorte.example/api-reservas:2.3.1
+ image: registry.rutasnorte.example/api-reservas:2.4.0Este es el comando que convierte un despliegue en algo revisable: te dice exactamente qué va a cambiar en el clúster antes de cambiarlo. Debería ser un paso obligatorio en cualquier despliegue manual a producción. Su código de salida es distinto de cero cuando hay diferencias, lo que permite usarlo en scripts.
7.4. kubectl explain: consultar el esquema
kubectl explain pod.spec.containers.livenessProbe
kubectl explain deployment.spec.strategy.rollingUpdate
kubectl explain ingress.spec.rules --recursiveYa lo vimos en la lección anterior; aquí conviene subrayar que es la respuesta correcta a "¿existe este campo y qué tipo tiene?" cuando estás escribiendo un manifiesto.
Flujo de trabajo recomendado
# 1. ¿El YAML es válido y los campos existen?
kubectl apply -f k8s/base/ --dry-run=client
# 2. ¿Lo aceptaría el clúster con mis permisos y sus políticas?
kubectl apply -f k8s/base/ --dry-run=server
# 3. ¿Qué va a cambiar exactamente?
kubectl diff -f k8s/base/
# 4. Aplicar
kubectl apply -f k8s/base/
# 5. Esperar a que el estado real alcance al deseado
kubectl wait --for=condition=Ready pod -l app.kubernetes.io/part-of=rutas-norte \
-n rutas-norte-dev --timeout=120s
- Organización de los manifiestos del proyecto
Los manifiestos de Rutas Norte viven en el directorio k8s/ del repositorio del proyecto, versionados en Git. Esta es la estructura que usaremos durante todo el curso:
rutas-norte/
├── src/
├── Dockerfile
└── k8s/
├── base/ # definición común a todos los entornos
│ ├── namespace.yaml
│ ├── tienda-web.yaml
│ ├── api-reservas.yaml
│ ├── postgres-reservas.yaml
│ ├── redis-cache.yaml
│ ├── worker-notificaciones.yaml
│ └── informes-ocupacion.yaml
├── entornos/
│ ├── dev/ # lo específico de rutas-norte-dev
│ ├── pre/
│ └── pro/
├── entorno-local/
│ └── kind-rutas-norte.yaml # de la leccion 01-04
└── README.mdConvenciones que seguiremos:
| Convención | Regla |
|---|---|
| Un fichero por componente | api-reservas.yaml contiene el Deployment, su Service y su ConfigMap, separados por --- |
| Nombre del fichero = nombre del componente | Encontrar el manifiesto de algo es inmediato |
| Namespace explícito | Cada objeto declara su metadata.namespace, para no depender del contexto activo |
| Etiquetas comunes en todos los objetos | app, app.kubernetes.io/part-of: rutas-norte, entorno |
| Nunca secretos reales en Git | Los Secrets van con valores de ejemplo o cifrados (módulo 3 y 8) |
| Orden de aplicación | Namespaces y ConfigMaps antes que las cargas que los consumen |
Sobre el orden: aplicar un directorio completo funciona porque el modelo es reconciliador —un pod que no encuentra su ConfigMap se queda esperando y arranca cuando aparece—, pero para evitar errores transitorios conviene aplicar primero el namespace:
Un aviso importante para no invadir módulos posteriores: esta estructura de base/ y entornos/ es deliberadamente YAML plano, sin plantillas. Cuando la duplicación entre entornos se vuelva molesta, existen dos soluciones estándar —Helm (plantillas y empaquetado) y Kustomize (superposiciones sin plantillas)— que se estudian en las lecciones Helm y Kustomize. Aprender primero el YAML plano no es tiempo perdido: es lo que te permitirá entender qué generan esas herramientas.
Errores Comunes y Consejos
- Usar tabuladores en YAML. Error de sintaxis puro. Configura el editor:
expandtab, 2 espacios, y una extensión de YAML que valide al guardar. - Copiar
apiVersionde tutoriales antiguos.extensions/v1beta1yapps/v1beta2no existen. Verifica siempre conkubectl api-resources. - Confundir el nombre del
kindcon el del recurso.kind: Deployment(CamelCase, singular) en el YAML;kubectl get deployments(minúsculas, plural) en la CLI. - Valores sin comillar en ConfigMaps y Secrets.
"3","true","NO". Esos campos exigen cadenas y YAML convierte tipos alegremente. - Editar el
statusa mano. No tiene ningún efecto: lo escribe el controlador. - Mezclar
createyapplysobre el mismo objeto. Sin la anotaciónlast-applied, la fusión puede comportarse de forma inesperada. Empieza siempre conapply. - Aplicar sin
diffen producción.kubectl diffcuesta dos segundos y evita despliegues sorpresa. - Guardar un
-o yamlcomo manifiesto. La salida del clúster incluyestatus,uid,resourceVersion,managedFieldsycreationTimestamp, que no deben versionarse. Límpialo (a mano o con el pluginkubectl neat) antes de guardarlo enk8s/. - Consejo: trata el directorio
k8s/como código. Revisión por pull request, validación en CI con--dry-run=server, y la regla de oro: si un cambio no está en Git, no existe.
Ejercicios
Ejercicio 1: Anatomía y descubrimiento de la API
- Escribe el manifiesto de un ConfigMap llamado
tienda-web-configen el namespacerutas-norte-dev, con las etiquetas del proyecto y dos claves:api_urlcon valorhttp://api-reservasymodo_mantenimientocon valor"NO". Explica por qué el segundo valor necesita comillas. - Averigua con kubectl, sin buscar en internet, a qué
apiVersionpertenecenCronJob,IngressyHorizontalPodAutoscaler, y cuáles de ellos son de ámbito de namespace. - Comprueba con
kubectl explainsi el campospec.containers.imagePullPolicyexiste en un Pod y qué valores admite.
Ejercicio 2: Validación e idempotencia
Partiendo del manifiesto del pod api-reservas de la sección 1:
- Valídalo en local sin tocar el clúster.
- Aplícalo, y aplícalo otra vez sin cambiar nada. ¿Qué dice kubectl la segunda vez y por qué?
- Cambia la imagen a la versión
2.5.0en el fichero y, antes de aplicar, muestra exactamente qué cambiaría en el clúster. - Muestra el
status.phasey la IP del pod usandojsonpath. - Explica qué diferencia habría si en el paso 2 hubieras usado
kubectl createen lugar deapply.
Ejercicio 3: Fichero multi-documento y organización
Crea el fichero k8s/base/redis-cache.yaml que contenga, en un solo fichero y en el orden correcto, tres objetos:
- Un ConfigMap
redis-cache-configcon una claveredis.confque contenga varias líneas de configuración (usa el indicador literal). - Un Pod
redis-cachecon la imagenredis:7.2-alpine. - Todo ello en
rutas-norte-dev, con las etiquetas del proyecto.
Después, indica qué comando aplicaría el directorio completo y por qué conviene aplicar antes namespace.yaml.
Soluciones
Solución 1
# k8s/base/tienda-web-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: tienda-web-config
namespace: rutas-norte-dev
labels:
app: tienda-web
app.kubernetes.io/part-of: rutas-norte
entorno: dev
data:
api_url: "http://api-reservas"
modo_mantenimiento: "NO""NO" necesita comillas porque YAML 1.1 interpreta NO (igual que no, yes, on, off) como el booleano false. El campo data de un ConfigMap solo admite cadenas, así que sin comillas el apiserver rechazaría el objeto con un error de tipo.
cronjobs cj batch/v1 true CronJob
horizontalpodautoscalers hpa autoscaling/v2 true HorizontalPodAutoscaler
ingresses ing networking.k8s.io/v1 true IngressLos tres son de ámbito de namespace (NAMESPACED = true).
FIELD: imagePullPolicy <string>
DESCRIPTION:
Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always
if :latest tag is specified, or IfNotPresent otherwise.Solución 2
# 1
kubectl apply -f k8s/base/api-reservas-pod.yaml --dry-run=client
# 2
kubectl apply -f k8s/base/api-reservas-pod.yaml # pod/api-reservas created
kubectl apply -f k8s/base/api-reservas-pod.yaml # pod/api-reservas unchanged
# 3
kubectl diff -f k8s/base/api-reservas-pod.yaml
# 4
kubectl get pod api-reservas -n rutas-norte-dev \
-o jsonpath='{.status.phase}{"\t"}{.status.podIP}{"\n"}'En el paso 2, la segunda ejecución dice unchanged porque apply es idempotente: kubectl compara la anotación last-applied-configuration, el fichero actual y el objeto vivo, no encuentra diferencias y no envía ninguna modificación. Esa propiedad es la que permite ejecutar apply en cada despliegue de un pipeline sin efectos secundarios.
Con kubectl create en el paso 2, la primera vez habría funcionado y la segunda habría fallado con Error from server (AlreadyExists), porque create no fusiona: solo sabe crear objetos nuevos.
Solución 3
# k8s/base/redis-cache.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-cache-config
namespace: rutas-norte-dev
labels:
app: redis-cache
app.kubernetes.io/part-of: rutas-norte
entorno: dev
data:
redis.conf: |
maxmemory 256mb
maxmemory-policy allkeys-lru
appendonly no
save ""
---
apiVersion: v1
kind: Pod
metadata:
name: redis-cache
namespace: rutas-norte-dev
labels:
app: redis-cache
app.kubernetes.io/part-of: rutas-norte
entorno: dev
spec:
containers:
- name: redis
image: redis:7.2-alpine
ports:
- containerPort: 6379El ConfigMap va primero porque el pod lo consumirá y así se evita un error transitorio. Conviene aplicar namespace.yaml por separado y antes porque todos los demás objetos declaran namespace: rutas-norte-dev y, si ese namespace todavía no existe, sus creaciones fallan con namespaces "rutas-norte-dev" not found. El namespace es la única dependencia dura del conjunto.
Conclusión
Ya dominas la unidad de trabajo de Kubernetes. Todo objeto tiene la misma anatomía —apiVersion, kind, metadata, spec que escribes tú y status que escribe el clúster—, pertenece a un grupo de API cuya versión debes verificar en tu propio clúster con kubectl api-resources, y se expresa en un YAML con reglas estrictas sobre indentación, tipos y cadenas multilínea. Has visto por qué apply es superior a create y replace, cómo funciona la fusión de tres vías y su anotación last-applied, hacia dónde evoluciona el modelo con server-side apply y la propiedad de campos, y qué secuencia de validación —--dry-run=client, --dry-run=server, kubectl diff, kubectl explain— convierte un despliegue en algo predecible. Y tienes la estructura del directorio k8s/ que sostendrá el proyecto durante el resto del curso.
Tienes clúster, tienes CLI y tienes el lenguaje de los manifiestos. Solo falta el paciente. En la siguiente lección, El Proyecto del Curso: la Plataforma Rutas Norte, conocerás en detalle la empresa, cada uno de los seis componentes de la plataforma, la arquitectura objetivo y las convenciones del proyecto; y terminarás desplegando de verdad tu primer trozo de Rutas Norte en el clúster, viéndolo funcionar en tu navegador.
Curso de Kubernetes
Módulo 1: Introducción a Kubernetes
- ¿Qué es Kubernetes?
- Arquitectura de Kubernetes
- Conceptos y Terminología Clave
- Configuración de un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objetos, Manifiestos YAML y el Modelo Declarativo
- El Proyecto del Curso: la Plataforma Rutas Norte
Módulo 2: Componentes Principales de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualizaciones, Rollbacks y Estrategias de Despliegue
- Servicios
- Namespaces
- Etiquetas, Selectores y Anotaciones
Módulo 3: Gestión de Configuración y Secretos
- ConfigMaps
- Secrets
- Variables de Entorno
- Cuotas y Límites de Recursos
- LimitRanges y Clases de Calidad de Servicio (QoS)
- ServiceAccounts y Acceso a la API desde los Pods
Módulo 4: Redes en Kubernetes
- Redes de Clúster
- Tipos de Servicios
- DNS Interno y Descubrimiento de Servicios
- Controladores de Ingress
- TLS y Gestión de Certificados con cert-manager
- Políticas de Red
Módulo 5: Almacenamiento en Kubernetes
- Volúmenes
- Volúmenes Persistentes
- Reclamaciones de Volúmenes Persistentes
- Clases de Almacenamiento
- Aprovisionamiento Dinámico, Expansión y Snapshots
- Copias de Seguridad y Restauración de Datos
Módulo 6: Conceptos Avanzados de Kubernetes
- StatefulSets
- DaemonSets
- Trabajos y CronJobs
- Init Containers, Sidecars y Patrones Multi-Contenedor
- Planificación: Afinidad, Taints y Tolerations
- Definiciones de Recursos Personalizados (CRDs)
- Operadores y el Patrón Controlador
Módulo 7: Monitoreo y Registro
- Verificaciones de Salud y Sondas
- Servidor de Métricas y kubectl top
- Monitoreo con Prometheus
- Visualización y Alertas con Grafana y Alertmanager
- Registro Centralizado con Elasticsearch, Fluentd y Kibana (EFK)
- Depuración de Aplicaciones y Eventos del Clúster
Módulo 8: Seguridad en Kubernetes
- Control de Acceso Basado en Roles (RBAC)
- Contextos de Seguridad y Endurecimiento del Contenedor
- Políticas de Seguridad de Pods y Pod Security Standards
- Seguridad de Red
- Seguridad de Imágenes
- Auditoría, Escaneo y Gestión de Vulnerabilidades
Módulo 9: Escalado y Rendimiento
- Autoescalado Horizontal de Pods
- Autoescalado Vertical de Pods
- Autoescalado de Clúster
- Escalado por Eventos y Métricas Personalizadas con KEDA
- Alta Disponibilidad: PodDisruptionBudgets y Topología
- Ajuste de Rendimiento
Módulo 10: Ecosistema y Herramientas de Kubernetes
- Minikube y Entornos Locales con kind
- Kubeadm
- Helm
- Kustomize
- GitOps con Argo CD y Flux
- Kubernetes Gestionado: EKS, AKS y GKE
Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real
- Despliegue de una Aplicación Web
- Ejecución de Aplicaciones con Estado
- CI/CD con Kubernetes
- Estrategias de Despliegue: Blue-Green y Canary
- Gestión Multi-Clúster
- Operación en Producción: Incidencias, Runbooks y Costes
