A la lliçó anterior vam fer servir kubectl apply com si fos una ordre òbvia i vam veure manifests YAML de passada, sense desmenuçar-los. Toca saldar aquest deute, perquè el manifest és la unitat de treball real de Kubernetes: tot el que desplegis durant els onze mòduls que queden serà un fitxer YAML que descriu un objecte. Aquesta lliçó t'ensenya l'anatomia exacta de qualsevol objecte de l'API —els quatre camps que hi són sempre, facis el que facis—, com descobrir quina apiVersion correspon a cada tipus, el subconjunt de YAML que has de dominar, la diferència real entre apply, create i replace i per què importa, com validar un manifest abans d'aplicar-lo, i com organitzar els fitxers del projecte Rutas Norte al directori k8s/ perquè el repositori sigui la font de veritat.

Contingut

  1. Anatomia d'un objecte de Kubernetes
  2. Grups, versions i nivells d'estabilitat de l'API
  3. El YAML que cal saber
  4. Estat desitjat i estat observat
  5. apply enfront de create i replace
  6. Server-side apply i la propietat de camps
  7. Validar abans d'aplicar
  8. Organització dels manifests del projecte

  1. Anatomia d'un objecte de Kubernetes

Absolutament tots els objectes de Kubernetes —un pod, un secret, un permís RBAC, un CRD d'un operador— comparteixen la mateixa estructura de quatre camps de primer nivell. Aprendre aquesta estructura una vegada serveix per sempre.

# k8s/base/api-reserves-pod.yaml
apiVersion: v1                          # 1. QUINA versió de l'API interpreta això
kind: Pod                               # 2. QUIN tipus d'objecte és
metadata:                               # 3. QUI és: identitat i metadades
  name: api-reserves
  namespace: rutas-norte-dev
  labels:
    app: api-reserves
    app.kubernetes.io/part-of: rutas-norte
    entorn: dev
  annotations:
    rutasnorte.example/responsable: [email protected]
spec:                                   # 4. COM ha de ser: l'estat desitjat
  containers:
    - name: api
      image: registry.rutasnorte.example/api-reserves:2.4.0
      ports:
        - containerPort: 3000
      env:
        - name: DB_HOST
          value: postgres-reserves

1.1. apiVersion

Indica el grup i la versió de l'API que ha d'interpretar l'objecte. Determina quins camps són vàlids i com es comporten. Exemples: v1 (grup core), apps/v1, networking.k8s.io/v1, batch/v1. És el camp que més errors provoca quan es copien manifests antics d'internet.

1.2. kind

El tipus concret: Pod, Deployment, Service, Ingress, ConfigMap. S'escriu sempre en CamelCase, mentre que el recurs a la URL de l'API va en minúscules i plural (pods, deployments). La parella apiVersion + kind identifica unívocament l'esquema de l'objecte.

1.3. metadata

La identitat de l'objecte. Els seus camps més rellevants:

Camp Qui l'escriu Per a què serveix
name Tu Nom únic dins del namespace i del tipus. Ha de ser un nom DNS vàlid: minúscules, números i guions
namespace Tu (o el context) Namespace on viu. Si s'omet, es fa servir el del context de kubectl
labels Tu Metadades consultables: són la base de selectors, Services i controladors
annotations Tu i les eines Metadades no consultables: configuració de tercers, traçabilitat
uid El sistema Identificador únic i irrepetible a tot el clúster
resourceVersion El sistema Versió interna de l'objecte; serveix per detectar escriptures concurrents
creationTimestamp El sistema Data de creació
ownerReferences El sistema Qui "posseeix" l'objecte. Un pod creat per un ReplicaSet l'apunta aquí, i per això en esborrar el ReplicaSet s'esborren els seus pods (recol·lecció en cascada)
finalizers El sistema o les eines Tasques que s'han de completar abans que l'objecte es pugui esborrar

Un detall que explica un comportament desconcertant: si un namespace o un PVC es queda "penjat" en estat Terminating, gairebé sempre és per un finalizer el responsable del qual no ha acabat la seva feina.

1.4. spec

L'estat desitjat. És el que tu descrius i el seu contingut depèn completament del kind: l'spec d'un Pod té containers, el d'un Service té selector i ports, el d'un PVC té resources.requests.storage. Tot el curs consisteix, en el fons, a aprendre a escriure spec de tipus diferents.

1.5. status

No apareix al manifest de dalt perquè no l'escrius tu: l'escriu el clúster. És l'estat observat. Si consultes l'objecte ja creat el veuràs:

kubectl get pod api-reserves -n rutas-norte-dev -o yaml
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-reserves:2.4.0
      imageID: registry.rutasnorte.example/api-reserves@sha256:9f3a1c2...

Fixa't en dues coses. Primera: conditions és el format estàndard amb què Kubernetes expressa "com va" un objecte, i és el que consulten kubectl wait i les eines de desplegament. Segona: imageID conté el digest real de la imatge que s'està executant, mentre que spec.image conté l'etiqueta que tu vas demanar. Quan algú reutilitza una etiqueta, aquests dos valors deixen de correspondre's; hi tornarem en parlar d'etiquetes immutables a la lliçó següent.

  1. Grups, versions i nivells d'estabilitat de l'API

L'API de Kubernetes està dividida en grups per poder evolucionar per parts i permetre extensions.

Grup apiVersion Tipus que conté
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 grup core és l'històric i per això la seva apiVersion no porta prefix: és v1, no core/v1. Tots els altres segueixen el format grup/versió.

Com descobrir-ho al teu propi clúster

No cal memoritzar aquesta taula: es consulta.

# Tots els grups i versions que admet aquest clúster
kubectl api-versions

# Tots els recursos, amb la seva apiVersion, abreviatura i àmbit
kubectl api-resources

# Filtrar per grup
kubectl api-resources --api-group=networking.k8s.io
NAME              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         NetworkPolicy

Aquest és el reflex que cal adquirir: abans de copiar una apiVersion d'un blog, comprova-la amb kubectl api-resources. La versió correcta és sempre la que diu el teu clúster.

Alpha, beta i estable

Nivell Exemple Què significa Recomanació
Alpha v1alpha1 Pot desaparèixer o canviar sense avís. Desactivada per defecte. Hi pot haver pèrdua de dades Mai en producció
Beta v2beta2 Ben provada, però els camps encara poden canviar. Des de la 1.24 les APIs beta noves vénen desactivades per defecte Només amb pla de migració
Estable v1, v2 Compatibilitat garantida durant moltes versions La que has de fer servir

Kubernetes retira versions antigues amb cada actualització, i aquest és el motiu que manifests copiats de tutorials vells fallin:

error: unable to recognize "deploy.yaml": no matches for kind "Deployment"
in version "extensions/v1beta1"

El grup extensions/v1beta1 va desaparèixer fa moltes versions. Avui un Deployment és apps/v1 i un Ingress és networking.k8s.io/v1, sense excepcions. Abans d'actualitzar un clúster, eines com kubent (kube-no-trouble) revisen els teus manifests a la recerca d'APIs a punt de retirar-se.

  1. El YAML que cal saber

YAML és simple però té paranys. Aquest és el subconjunt que necessites.

3.1. Mapes, llistes i indentació

# Un mapa (clau: valor)
metadata:
  name: api-reserves          # indentació amb ESPAIS, mai tabuladors
  namespace: rutas-norte-dev

# Una llista de cadenes
args:
  - "--port=3000"
  - "--mode=produccio"

# Una llista de mapes: el guionet marca l'inici de cada element
containers:
  - name: api                 # aquest 'name' pertany al primer element
    image: nginx:1.27
    ports:
      - containerPort: 3000
  - name: sidecar-metriques   # segon element
    image: prom/statsd-exporter:v0.26.0

Regles innegociables:

  • Espais, mai tabuladors. Un tabulador és un error de sintaxi en YAML. Configura el teu editor per expandir tabuladors a 2 espais en fitxers .yaml.
  • La indentació defineix la jerarquia. Dos espais per nivell és la convenció a Kubernetes.
  • El guionet - indica element de llista i el seu contingut s'indenta al mateix nivell que el guionet o més.

3.2. Tipus i cometes: el parany clàssic

data:
  repliques_text: "3"      # cadena
  repliques_numero: 3      # enter
  actiu: true              # booleà
  actiu_text: "true"       # cadena
  versio: "1.30"           # cadena; sense cometes seria el número 1.30
  port_noruega: "NO"       # IMPRESCINDIBLE! sense cometes, YAML 1.1 ho llegeix com a false

El cas NO és cèlebre (es coneix com the Norway problem): YAML 1.1 interpreta yes, no, on, off, y, n com a booleans. Regla pràctica: en un ConfigMap o Secret, posa sempre els valors entre cometes, perquè aquests camps exigeixen cadenes i un valor mal tipat produeix un error tan clar com aquest:

error: error validating data: ValidationError(ConfigMap.data.port_noruega):
invalid type for io.k8s.api.core.v1.ConfigMap.data: got "boolean", expected "string"

3.3. Cadenes multilínia

Fonamentals per incrustar fitxers de configuració en un ConfigMap:

data:
  # | conserva els salts de línia (literal). És el que voldràs gairebé sempre
  nginx.conf: |
    server {
      listen 80;
      root /usr/share/nginx/html;
      location /api/ {
        proxy_pass http://api-reserves:80/;
      }
    }

  # > plega els salts en espais (folded): útil per a textos llargs
  descripcio: >
    Configuracio de la botiga web de Rutas Norte
    per a l'entorn de desenvolupament.
Indicador Efecte Ús típic
| Conserva salts de línia; elimina l'últim Fitxers de configuració, scripts
|- Conserva salts; elimina el salt final Quan la línia final sobra
> Converteix salts en espais Textos descriptius llargs

3.4. Diversos documents en un fitxer

El separador --- permet posar diversos objectes en un mateix fitxer, i kubectl apply els processa en ordre:

# 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-alpine

Criteri recomanat: agrupa en un fitxer els objectes que formen una unitat desplegable (un component amb el seu ConfigMap i el seu Service) i separa en fitxers diferents els components diferents. Un únic fitxer gegant amb tota la plataforma és difícil de revisar i d'aplicar parcialment.

  1. Estat desitjat i estat observat

Aquí és on el model declaratiu es torna concret. Ja coneixes el bucle de reconciliació de la lliçó Arquitectura de Kubernetes; vegem-lo des del punt de vista del fitxer.

flowchart LR
    Y["Manifest YAML<br/>a k8s/"] -->|kubectl apply| API["kube-apiserver"]
    API -->|persisteix spec| E[("etcd")]
    C["Controlador"] -->|llegeix spec| API
    C -->|observa| R["Món real<br/>pods, nodes"]
    C -->|escriu status| API
    R -->|diferència| C

Quatre conseqüències pràctiques que convé tenir molt clares:

  1. Tu escrius spec; el sistema escriu status. Editar un status a mà no serveix de res: el controlador el sobreescriurà en el seu cicle següent.
  2. Aplicar no és executar. kubectl apply acaba quan l'objecte està desat, no quan l'aplicació està llesta. Per esperar de debò:
kubectl apply -f k8s/base/api-reserves.yaml
kubectl wait --for=condition=Ready pod/api-reserves -n rutas-norte-dev --timeout=90s
  1. És idempotent. Aplicar deu vegades el mateix fitxer dona el mateix resultat que aplicar-lo una. Això és el que fa possible que una canalització de CI/CD executi kubectl apply a cada desplegament sense comprovar abans què existeix.
kubectl apply -f k8s/base/api-reserves.yaml
pod/api-reserves created
kubectl apply -f k8s/base/api-reserves.yaml   # segona vegada, sense canvis
pod/api-reserves unchanged
  1. El fitxer és la font de veritat, no el clúster. Si algú canvia alguna cosa amb kubectl edit, el següent apply des del repositori ho reverteix. Aquesta és exactament la propietat sobre la qual es construeix GitOps (mòdul 10).

  1. apply enfront de create i replace

Tres ordres que semblen intercanviables i no ho són gens.

Ordre Si l'objecte NO existeix Si l'objecte JA existeix Conserva canvis d'altres
kubectl create -f El crea Error: AlreadyExists
kubectl replace -f Error: NotFound El substitueix sencer No: esborra el que no sigui al fitxer
kubectl apply -f El crea El fusiona amb l'existent Sí, si són d'un altre gestor de camps
kubectl create -f k8s/base/api-reserves.yaml
Error from server (AlreadyExists): pods "api-reserves" already exists
kubectl apply -f k8s/base/api-reserves.yaml
pod/api-reserves configured

La conclusió operativa és simple: fes servir apply sempre. create serveix per a ordres imperatives ràpides (kubectl create namespace) i replace per a casos molt concrets de substitució total.

Per què apply sap què esborrar: l'anotació last-applied

Imagina que apliques un pod amb dues etiquetes, després edites el fitxer i en deixes només una, i tornes a aplicar. Com sap kubectl que ha d'eliminar la segona etiqueta, en lloc de deixar-la perquè "no és al fitxer però tampoc he demanat esborrar-la"?

La resposta clàssica és que apply desa una còpia de l'últim que vas aplicar en una anotació:

kubectl get pod api-reserves -n rutas-norte-dev \
  -o jsonpath='{.metadata.annotations.kubectl\.kubernetes\.io/last-applied-configuration}'
{"apiVersion":"v1","kind":"Pod","metadata":{"labels":{"app":"api-reserves","entorn":"dev"},...

Amb aquesta informació, kubectl fa una fusió de tres vies: compara (a) el que vas aplicar l'última vegada, (b) el que apliques ara i (c) el que hi ha al clúster. El que era a (a) i desapareix a (b) s'esborra; el que només és a (c) —perquè ho va posar un controlador o un webhook— es respecta.

Això explica un error freqüent: si vas crear un objecte amb kubectl create i després el modifiques amb apply, no existeix l'anotació de referència i kubectl avisa que la fusió pot ser incorrecta. Per coherència, fes servir apply des del principi de la vida de cada objecte.

  1. Server-side apply i la propietat de camps

Des de Kubernetes 1.22, la fusió la pot fer el servidor en lloc del client, i és el mecanisme cap al qual està migrant tot l'ecosistema.

kubectl apply --server-side -f k8s/base/api-reserves.yaml

La diferència és conceptual: en lloc d'una anotació amb l'últim estat, el servidor registra quin gestor és propietari de cada camp a metadata.managedFields.

kubectl get pod api-reserves -n rutas-norte-dev --show-managed-fields -o yaml | head -20
metadata:
  managedFields:
    - manager: kubectl
      operation: Apply
      apiVersion: v1
      fieldsV1:
        f:spec:
          f:containers:
            k:{"name":"api"}:
              f:image: {}
    - manager: kubelet
      operation: Update
      subresource: status

Avantatges respecte a la fusió en client:

  • Detecta conflictes. Si intentes modificar un camp que pertany a un altre gestor (un HPA que controla replicas, per exemple), la petició falla amb un avís clar en lloc de provocar una baralla silenciosa entre eines:
error: Apply failed with 1 conflict: conflict with "hpa-controller":
.spec.replicas

Si de debò vols apropiar-te del camp, es força explícitament:

kubectl apply --server-side --force-conflicts -f k8s/base/api-reserves.yaml
  • No depèn d'una anotació que en objectes grans pot ser enorme.
  • Diversos gestors poden coexistir sobre el mateix objecte de manera ordenada: la teva canalització, un operador i un autoescalador, cadascun amo dels seus camps.

Per al curs continuarem fent servir kubectl apply a seques, que és l'habitual, però convé que reconeguis managedFields quan aparegui en un -o yaml i que sàpigues què significa un error de conflicte.

  1. Validar abans d'aplicar

Aplicar sense comprovar és la recepta de l'incident. Kubernetes ofereix quatre nivells de verificació, del més barat al més complet.

7.1. --dry-run=client: validació local

kubectl apply -f k8s/base/api-reserves.yaml --dry-run=client
pod/api-reserves configured (dry run)

kubectl construeix l'objecte i valida l'esquema sense enviar res. Detecta errors de sintaxi YAML i camps inexistents. No detecta problemes de permisos, quotes ni webhooks. És també la bandera que vam fer servir a la lliçó anterior per generar manifests:

kubectl create configmap botiga-web-config \
  --from-literal=api_url=http://api-reserves \
  -n rutas-norte-dev --dry-run=client -o yaml > k8s/base/botiga-web-configmap.yaml

7.2. --dry-run=server: validació real sense persistir

kubectl apply -f k8s/base/api-reserves.yaml --dry-run=server

La petició que arriba a l'apiserver i recorre tot el camí de la lliçó 01-02 —autenticació, autorització RBAC, admissió, validació— però no s'escriu a etcd. Detecta el que el client no pot veure: manca de permisos, quotes superades, polítiques de seguretat de pods, webhooks d'admissió propis. És la validació que ha d'executar la teva canalització de CI abans de desplegar.

7.3. kubectl diff: veure el canvi abans de fer-lo

kubectl diff -f k8s/base/api-reserves.yaml
diff -u -N /tmp/LIVE-1234/v1.Pod.rutas-norte-dev.api-reserves /tmp/MERGED-5678/...
--- LIVE
+++ MERGED
@@ -18,7 +18,7 @@
   containers:
   - name: api
-    image: registry.rutasnorte.example/api-reserves:2.3.1
+    image: registry.rutasnorte.example/api-reserves:2.4.0

Aquesta és l'ordre que converteix un desplegament en una cosa revisable: et diu exactament què canviarà al clúster abans de canviar-ho. Hauria de ser un pas obligatori en qualsevol desplegament manual a producció. El seu codi de sortida és diferent de zero quan hi ha diferències, la qual cosa permet fer-lo servir en scripts.

7.4. kubectl explain: consultar l'esquema

kubectl explain pod.spec.containers.livenessProbe
kubectl explain deployment.spec.strategy.rollingUpdate
kubectl explain ingress.spec.rules --recursive

Ja el vam veure a la lliçó anterior; aquí convé subratllar que és la resposta correcta a "existeix aquest camp i quin tipus té?" quan estàs escrivint un manifest.

Flux de treball recomanat

# 1. El YAML és vàlid i els camps existeixen?
kubectl apply -f k8s/base/ --dry-run=client

# 2. L'acceptaria el clúster amb els meus permisos i les seves polítiques?
kubectl apply -f k8s/base/ --dry-run=server

# 3. Què canviarà exactament?
kubectl diff -f k8s/base/

# 4. Aplicar
kubectl apply -f k8s/base/

# 5. Esperar que l'estat real assoleixi el desitjat
kubectl wait --for=condition=Ready pod -l app.kubernetes.io/part-of=rutas-norte \
  -n rutas-norte-dev --timeout=120s

  1. Organització dels manifests del projecte

Els manifests de Rutas Norte viuen al directori k8s/ del repositori del projecte, versionats a Git. Aquesta és l'estructura que farem servir durant tot el curs:

rutas-norte/
├── src/
├── Dockerfile
└── k8s/
    ├── base/                          # definició comuna a tots els entorns
    │   ├── namespace.yaml
    │   ├── botiga-web.yaml
    │   ├── api-reserves.yaml
    │   ├── postgres-reserves.yaml
    │   ├── redis-cache.yaml
    │   ├── worker-notificacions.yaml
    │   └── informes-ocupacio.yaml
    ├── entorns/
    │   ├── dev/                       # allò específic de rutas-norte-dev
    │   ├── pre/
    │   └── pro/
    ├── entorn-local/
    │   └── kind-rutas-norte.yaml      # de la lliçó 01-04
    └── README.md

Convencions que seguirem:

Convenció Regla
Un fitxer per component api-reserves.yaml conté el Deployment, el seu Service i el seu ConfigMap, separats per ---
Nom del fitxer = nom del component Trobar el manifest d'alguna cosa és immediat
Namespace explícit Cada objecte declara el seu metadata.namespace, per no dependre del context actiu
Etiquetes comunes a tots els objectes app, app.kubernetes.io/part-of: rutas-norte, entorn
Mai secrets reals a Git Els Secrets van amb valors d'exemple o xifrats (mòduls 3 i 8)
Ordre d'aplicació Namespaces i ConfigMaps abans que les càrregues que els consumeixen

Sobre l'ordre: aplicar un directori complet funciona perquè el model és reconciliador —un pod que no troba el seu ConfigMap es queda esperant i arrenca quan apareix—, però per evitar errors transitoris convé aplicar primer el namespace:

kubectl apply -f k8s/base/namespace.yaml
kubectl apply -f k8s/base/

Un avís important per no envair mòduls posteriors: aquesta estructura de base/ i entorns/ és deliberadament YAML pla, sense plantilles. Quan la duplicació entre entorns es torni molesta, existeixen dues solucions estàndard —Helm (plantilles i empaquetat) i Kustomize (superposicions sense plantilles)— que s'estudien a les lliçons Helm i Kustomize. Aprendre primer el YAML pla no és temps perdut: és el que et permetrà entendre què generen aquestes eines.

Errors Comuns i Consells

  • Fer servir tabuladors en YAML. Error de sintaxi pur. Configura l'editor: expandtab, 2 espais, i una extensió de YAML que validi en desar.
  • Copiar apiVersion de tutorials antics. extensions/v1beta1 i apps/v1beta2 no existeixen. Verifica-ho sempre amb kubectl api-resources.
  • Confondre el nom del kind amb el del recurs. kind: Deployment (CamelCase, singular) al YAML; kubectl get deployments (minúscules, plural) a la CLI.
  • Valors sense cometes en ConfigMaps i Secrets. "3", "true", "NO". Aquests camps exigeixen cadenes i YAML converteix tipus alegrement.
  • Editar el status a mà. No té cap efecte: l'escriu el controlador.
  • Barrejar create i apply sobre el mateix objecte. Sense l'anotació last-applied, la fusió es pot comportar de manera inesperada. Comença sempre amb apply.
  • Aplicar sense diff en producció. kubectl diff costa dos segons i evita desplegaments sorpresa.
  • Desar un -o yaml com a manifest. La sortida del clúster inclou status, uid, resourceVersion, managedFields i creationTimestamp, que no s'han de versionar. Neteja-la (a mà o amb el plugin kubectl neat) abans de desar-la a k8s/.
  • Consell: tracta el directori k8s/ com a codi. Revisió per petició de canvi, validació a CI amb --dry-run=server, i la regla d'or: si un canvi no és a Git, no existeix.

Exercicis

Exercici 1: Anatomia i descobriment de l'API

  1. Escriu el manifest d'un ConfigMap anomenat botiga-web-config al namespace rutas-norte-dev, amb les etiquetes del projecte i dues claus: api_url amb valor http://api-reserves i mode_manteniment amb valor "NO". Explica per què el segon valor necessita cometes.
  2. Esbrina amb kubectl, sense buscar a internet, a quina apiVersion pertanyen CronJob, Ingress i HorizontalPodAutoscaler, i quins d'ells són d'àmbit de namespace.
  3. Comprova amb kubectl explain si el camp spec.containers.imagePullPolicy existeix en un Pod i quins valors admet.

Exercici 2: Validació i idempotència

Partint del manifest del pod api-reserves de la secció 1:

  1. Valida'l en local sense tocar el clúster.
  2. Aplica'l, i aplica'l una altra vegada sense canviar res. Què diu kubectl la segona vegada i per què?
  3. Canvia la imatge a la versió 2.5.0 al fitxer i, abans d'aplicar, mostra exactament què canviaria al clúster.
  4. Mostra el status.phase i la IP del pod fent servir jsonpath.
  5. Explica quina diferència hi hauria si al pas 2 haguessis fet servir kubectl create en lloc d'apply.

Exercici 3: Fitxer multidocument i organització

Crea el fitxer k8s/base/redis-cache.yaml que contingui, en un sol fitxer i en l'ordre correcte, tres objectes:

  1. Un ConfigMap redis-cache-config amb una clau redis.conf que contingui diverses línies de configuració (fes servir l'indicador literal).
  2. Un Pod redis-cache amb la imatge redis:7.2-alpine.
  3. Tot això a rutas-norte-dev, amb les etiquetes del projecte.

Després, indica quina ordre aplicaria el directori complet i per què convé aplicar abans namespace.yaml.

Solucions

Solució 1

# k8s/base/botiga-web-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: botiga-web-config
  namespace: rutas-norte-dev
  labels:
    app: botiga-web
    app.kubernetes.io/part-of: rutas-norte
    entorn: dev
data:
  api_url: "http://api-reserves"
  mode_manteniment: "NO"

"NO" necessita cometes perquè YAML 1.1 interpreta NO (igual que no, yes, on, off) com el booleà false. El camp data d'un ConfigMap només admet cadenes, així que sense cometes l'apiserver rebutjaria l'objecte amb un error de tipus.

# 2
kubectl api-resources | grep -E 'cronjobs|ingresses|horizontalpodautoscalers'
cronjobs                    cj       batch/v1              true    CronJob
horizontalpodautoscalers    hpa      autoscaling/v2        true    HorizontalPodAutoscaler
ingresses                   ing      networking.k8s.io/v1  true    Ingress

Tots tres són d'àmbit de namespace (NAMESPACED = true).

# 3
kubectl explain pod.spec.containers.imagePullPolicy
FIELD: imagePullPolicy <string>
DESCRIPTION:
    Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always
    if :latest tag is specified, or IfNotPresent otherwise.

Solució 2

# 1
kubectl apply -f k8s/base/api-reserves-pod.yaml --dry-run=client

# 2
kubectl apply -f k8s/base/api-reserves-pod.yaml   # pod/api-reserves created
kubectl apply -f k8s/base/api-reserves-pod.yaml   # pod/api-reserves unchanged

# 3
kubectl diff -f k8s/base/api-reserves-pod.yaml

# 4
kubectl get pod api-reserves -n rutas-norte-dev \
  -o jsonpath='{.status.phase}{"\t"}{.status.podIP}{"\n"}'

Al pas 2, la segona execució diu unchanged perquè apply és idempotent: kubectl compara l'anotació last-applied-configuration, el fitxer actual i l'objecte viu, no hi troba diferències i no envia cap modificació. Aquesta propietat és la que permet executar apply a cada desplegament d'una canalització sense efectes secundaris.

Amb kubectl create al pas 2, la primera vegada hauria funcionat i la segona hauria fallat amb Error from server (AlreadyExists), perquè create no fusiona: només sap crear objectes nous.

Solució 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
    entorn: 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
    entorn: dev
spec:
  containers:
    - name: redis
      image: redis:7.2-alpine
      ports:
        - containerPort: 6379
kubectl apply -f k8s/base/namespace.yaml
kubectl apply -f k8s/base/

El ConfigMap va primer perquè el pod el consumirà i així s'evita un error transitori. Convé aplicar namespace.yaml per separat i abans perquè tots els altres objectes declaren namespace: rutas-norte-dev i, si aquest namespace encara no existeix, les seves creacions fallen amb namespaces "rutas-norte-dev" not found. El namespace és l'única dependència dura del conjunt.

Conclusió

Ja domines la unitat de treball de Kubernetes. Tot objecte té la mateixa anatomia —apiVersion, kind, metadata, spec que escrius tu i status que escriu el clúster—, pertany a un grup d'API la versió del qual has de verificar al teu propi clúster amb kubectl api-resources, i s'expressa en un YAML amb regles estrictes sobre indentació, tipus i cadenes multilínia. Has vist per què apply és superior a create i replace, com funciona la fusió de tres vies i la seva anotació last-applied, cap a on evoluciona el model amb server-side apply i la propietat de camps, i quina seqüència de validació —--dry-run=client, --dry-run=server, kubectl diff, kubectl explain— converteix un desplegament en una cosa predictible. I tens l'estructura del directori k8s/ que sostindrà el projecte durant la resta del curs.

Tens clúster, tens CLI i tens el llenguatge dels manifests. Només falta el pacient. A la lliçó següent, El Projecte del Curs: la Plataforma Rutas Norte, coneixeràs en detall l'empresa, cadascun dels sis components de la plataforma, l'arquitectura objectiu i les convencions del projecte; i acabaràs desplegant de debò el teu primer tros de Rutas Norte al clúster, veient-lo funcionar al teu navegador.

Curs de Kubernetes

Mòdul 1: Introducció a Kubernetes

Mòdul 2: Components Principals de Kubernetes

Mòdul 3: Gestió de Configuració i Secrets

Mòdul 4: Xarxes a Kubernetes

Mòdul 5: Emmagatzematge a Kubernetes

Mòdul 6: Conceptes Avançats de Kubernetes

Mòdul 7: Monitoratge i Registre

Mòdul 8: Seguretat a Kubernetes

Mòdul 9: Escalat i Rendiment

Mòdul 10: Ecosistema i Eines de Kubernetes

Mòdul 11: Estudis de Cas i Aplicacions del Món Real

Mòdul 12: Preparació per a la Certificació de Kubernetes

© Copyright 2026. Tots els drets reservats