La lliçó anterior va treure la configuració de botiga-web de la imatge i la va posar en un ConfigMap, però acabava amb un advertiment: un ConfigMap no protegeix res. Qualsevol que pugui llistar ConfigMaps a rutas-norte-pro en llegeix el contingut íntegre en text pla. I el deute que arrosseguem des del mòdul 2 és precisament dels que no admeten això: la contrasenya de postgres-reserves està escrita en clar en un manifest versionat a Git, i aquesta base de dades desa el nom, el DNI, el telèfon i el correu de cada client de Rutas Norte. Aquesta lliçó el salda amb l'objecte Secret, però sobretot t'ensenya a no refiar-te'n més del que mereix: veuràs per què base64 no és xifratge, per què el xifratge en repòs d'etcd no ve activat per defecte, qui pot llegir un secret sense que te n'assabentis, i què fa servir l'ecosistema quan el Secret natiu es queda curt.

Advertiment important. Aquesta lliçó explica els mecanismes que Kubernetes ofereix per gestionar credencials i et dona un punt de partida sòlid, però no substitueix un disseny de seguretat professional. postgres-reserves emmagatzema dades personals de clients reals (nom, DNI, telèfon, correu), cosa que a la Unió Europea entra de ple en l'àmbit del RGPD i, a Espanya, de la LOPDGDD. El disseny concret de la gestió de credencials, el xifratge en repòs, la política de rotació, els registres d'auditoria i les mesures de seguretat aplicables l'ha de revisar i aprovar un professional de seguretat o el responsable de compliment normatiu de la teva organització. Tracta tot el que segueix com a coneixement tècnic necessari, no com una recomanació de compliment.

Contingut

  1. El deute del mòdul 2 i per què fa mal
  2. Què és un Secret i en què es diferencia d'un ConfigMap
  3. Base64 no és xifratge: la demostració
  4. Els tipus de Secret
  5. Crear Secrets: imperatiu i declaratiu amb stringData
  6. Consumir un Secret com a volum
  7. El secret postgres-reserves-credencials a Rutas Norte
  8. imagePullSecrets per al registre privat
  9. Xifratge en repòs a etcd
  10. Qui pot llegir un secret
  11. Rotació de credencials
  12. Què no s'ha de fer mai
  13. Les solucions reals de l'ecosistema

  1. El deute del mòdul 2 i per què fa mal

Aquest és el manifest que tenim avui al repositori de Rutas Norte:

# k8s/base/postgres-reserves/deployment.yaml   <-- AIXO ESTA MALAMENT
    spec:
      containers:
        - name: postgres
          image: postgres:16.4
          env:
            - name: POSTGRES_PASSWORD
              value: "R3s3rv3s2026!"          # en clar, a Git, per sempre
            - name: POSTGRES_USER
              value: "rutasnorte"
            - name: POSTGRES_DB
              value: "reserves"

El problema no és només estètic. Enumerem el dany real:

  1. Git no oblida. Encara que esborris la línia avui i facis commit, la contrasenya continua a l'historial. Treure-la d'allà exigeix reescriure l'historial del repositori (git filter-repo) i forçar el push a tots els clons. A la pràctica, una credencial que ha estat a Git es considera compromesa i cal rotar-la, no esborrar-la.
  2. Es propaga sense control. Cada desenvolupador que va clonar el repositori té la contrasenya de producció al seu portàtil. També la tenen el CI, el sistema de còpies del repositori, el cercador de codi intern i qualsevol anàlisi estàtica que desi memòria cau.
  3. No hi ha traçabilitat. No hi ha manera de saber qui ha llegit aquesta contrasenya ni quan.
  4. Rotar-la és un desplegament de codi. Canviar la contrasenya exigeix un commit, una revisió, un merge i un desplegament. Això empeny a no rotar-la mai.
  5. Bots automatitzats la trobaran. Si el repositori arriba a ser públic un sol minut, hi ha rastrejadors que localitzen credencials en segons.

L'objectiu d'aquesta lliçó és que aquest manifest quedi així:

          env:
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-reserves-credencials
                  key: password

El manifest es pot publicar sense filtrar res. Aquest és el llistó dels dotze factors que vèiem a la lliçó anterior.

  1. Què és un Secret i en què es diferencia d'un ConfigMap

Un Secret és, estructuralment, gairebé idèntic a un ConfigMap: metadata més dades, sense spec ni status, amb namespace, i amb el mateix límit d'1 MiB. La diferència és en el tractament que Kubernetes li dona i en les expectatives que crea.

Aspecte ConfigMap Secret
apiVersion v1 v1
Camp de dades data (text), binaryData data (base64), stringData (només escriptura)
Codificació de data Text pla UTF-8 Base64 obligatori
Camp type No existeix Sí, i determina validacions
En muntar-se com a volum Escrit al disc del node Escrit en tmpfs (memòria RAM)
Apareix a kubectl describe El contingut complet Només la mida en bytes de cada clau
Xifratge en repòs a etcd No Només si es configura (no per defecte)
Pot ser immutable Sí Sí
Límit de mida 1 MiB 1 MiB
Pot portar imagePullSecrets No Sí
Es pot restringir amb RBAC Sí Sí, i és imprescindible

Les tres diferències que de debò importen:

Primera: stringData. És un camp només d'escriptura. Hi escrius text pla i l'apiserver el codifica en base64 i el mou a data abans de desar-lo. És el que fa tolerable escriure Secrets a mà.

Segona: el muntatge en tmpfs. Quan un Secret es munta com a volum, el kubelet crea un sistema de fitxers a memòria RAM, no al disc del node. Conseqüència pràctica: si el node s'apaga bruscament, la credencial no queda escrita en cap disc recuperable. És una protecció real, encara que limitada.

Tercera: describe no mostra el contingut.

kubectl describe secret postgres-reserves-credencials -n rutas-norte-dev
Name:         postgres-reserves-credencials
Namespace:    rutas-norte-dev
Labels:       app=postgres-reserves
              app.kubernetes.io/part-of=rutas-norte
              entorn=dev
Annotations:  <none>

Type:  Opaque

Data
====
password:  13 bytes
username:  10 bytes
database:  8 bytes

Només les mides. Això evita l'accident clàssic de mostrar la contrasenya en una sessió compartida o deixar-la al log d'una consola. Però és un detall de presentació, no un control de seguretat, com demostra l'apartat següent.

  1. Base64 no és xifratge: la demostració

Aquesta és la idea més important de la lliçó i cal interioritzar-la fent-la.

Creem el Secret:

kubectl create secret generic postgres-reserves-credencials \
  --from-literal=username=rutasnorte \
  --from-literal=password='R3s3rv3s2026!' \
  --from-literal=database=reserves \
  -n rutas-norte-dev
secret/postgres-reserves-credencials created

El mirem en YAML:

kubectl get secret postgres-reserves-credencials -n rutas-norte-dev -o yaml
apiVersion: v1
data:
  database: cmVzZXJ2ZXM=
  password: UjNzM3J2M3MyMDI2IQ==
  username: cnV0YXNub3J0ZQ==
kind: Secret
metadata:
  creationTimestamp: "2026-08-05T11:02:47Z"
  name: postgres-reserves-credencials
  namespace: rutas-norte-dev
  resourceVersion: "191455"
  uid: 8a3f7d21-5c6b-4e09-b1d7-4f2a9c8e3105
type: Opaque

A primera vista sembla xifrat. No ho és. Base64 és una codificació reversible sense clau, dissenyada als anys 80 per transportar binaris per canals de text. Qualsevol la pot desfer:

kubectl get secret postgres-reserves-credencials -n rutas-norte-dev \
  -o jsonpath='{.data.password}' | base64 -d; echo
R3s3rv3s2026!

Tres segons i una ordre. Ni contrasenya, ni clau, ni permisos especials més enllà de poder llegir el Secret.

Podem treure tot el contingut de cop:

kubectl get secret postgres-reserves-credencials -n rutas-norte-dev \
  -o go-template='{{range $k, $v := .data}}{{$k}}={{$v | base64decode}}{{"\n"}}{{end}}'
database=reserves
password=R3s3rv3s2026!
username=rutasnorte

O, des de Kubernetes 1.30, amb el visor incorporat:

kubectl get secret postgres-reserves-credencials -n rutas-norte-dev -o jsonpath='{.data}' \
  | jq -r 'to_entries[] | "\(.key)=\(.value|@base64d)"'

Què implica això exactament:

Creença falsa Realitat
"Està codificat, és segur" Base64 es desfà sense clau, en una ordre
"Com que és en un Secret, el puc pujar a Git" No. És equivalent a pujar-lo en clar
"Només els administradors ho veuen" Ho veu qui tingui permís get secrets al namespace
"A etcd està xifrat" Per defecte no (apartat 9)
"Els logs no ho mostren" Un kubectl get secret -o yaml en un pipeline sí

Aleshores, per a què serveix base64? Per a dues coses legítimes i cap d'elles és seguretat:

  1. Permetre contingut binari. Un certificat en format DER o una clau privada amb bytes no imprimibles no cap en un camp de text YAML. Base64 ho fa possible.
  2. Evitar problemes d'escapament. Una contrasenya amb cometes, salts de línia o $ trencaria el YAML. Codificada, no.

La regla que has de memoritzar: un Secret de Kubernetes no protegeix la dada; el que fa és donar-li un lloc on aplicar controls (RBAC, xifratge en repòs, auditoria, muntatge en memòria) i separar-la del manifest de l'aplicació. La protecció l'aporten aquests controls, no l'objecte.

  1. Els tipus de Secret

El camp type no és decoratiu: determina quines claus exigeix Kubernetes i quins components saben fer servir el Secret.

type Claus obligatòries Per a què serveix Ordre de creació
Opaque Cap Ús general: contrasenyes, tokens d'API, claus de xifratge kubectl create secret generic
kubernetes.io/dockerconfigjson .dockerconfigjson Credencials d'un registre privat d'imatges kubectl create secret docker-registry
kubernetes.io/tls tls.crt, tls.key Certificat i clau privada per a TLS (Ingress) kubectl create secret tls
kubernetes.io/basic-auth username, password Autenticació bàsica HTTP generic amb --type
kubernetes.io/ssh-auth ssh-privatekey Clau SSH (clonar repositoris privats) generic amb --type
kubernetes.io/service-account-token token, ca.crt, namespace Token permanent d'una ServiceAccount Manifest amb anotació
bootstrap.kubernetes.io/token token-id, token-secret Unir nodes nous al clúster (kubeadm) Manifest

Notes d'ús a Rutas Norte:

  • Opaque és el que farem servir per a postgres-reserves-credencials, per a la clau de la passarel·la de pagament i per al token del proveïdor de correu de worker-notificacions. És el tipus per defecte si no hi poses res.
  • kubernetes.io/dockerconfigjson serà el que permeti descarregar imatges de registry.rutasnorte.example (apartat 8).
  • kubernetes.io/tls serà el que sostingui el certificat de www.rutasnorte.example. El veuràs a TLS i Certificats, on cert-manager el generarà i renovarà tot sol.
  • kubernetes.io/service-account-token: compte amb aquest. Abans de Kubernetes 1.24, cada ServiceAccount tenia associat un Secret d'aquest tipus amb un token sense caducitat. Ja no es creen automàticament i crear-los a mà és una mala idea llevat de casos molt concrets: un token etern és un token que mai no es pot revocar de manera neta. El mecanisme actual el veuràs a ServiceAccounts.

El tipus també es valida. Això falla:

kubectl create secret generic tipus-incorrecte --type=kubernetes.io/basic-auth \
  --from-literal=usuari=rutasnorte -n rutas-norte-dev
The Secret "tipus-incorrecte" is invalid: data[username]: Required value

El tipus basic-auth exigeix exactament la clau username, no usuari. Aquesta validació és útil: evita que un Ingress o un controlador rebi un Secret amb la forma equivocada.

  1. Crear Secrets: imperatiu i declaratiu amb stringData

5.1. kubectl create secret generic

kubectl create secret generic postgres-reserves-credencials \
  --from-literal=username=rutasnorte \
  --from-literal=password='R3s3rv3s2026!' \
  -n rutas-norte-dev

Compte amb l'historial del shell. Aquesta ordre queda escrita a ~/.bash_history amb la contrasenya a dins. Dues maneres d'evitar-ho:

# Opcio A: un espai al davant (amb HISTCONTROL=ignorespace)
 kubectl create secret generic ... --from-literal=password='R3s3rv3s2026!'

# Opcio B (millor): des de fitxer, i esborrar el fitxer despres
printf '%s' 'R3s3rv3s2026!' > /tmp/pw
kubectl create secret generic postgres-reserves-credencials \
  --from-file=password=/tmp/pw -n rutas-norte-dev
shred -u /tmp/pw

El printf '%s' en lloc d'echo és deliberat: echo afegeix un salt de línia final que acabaria dins de la contrasenya. És un error clàssic i desconcertant: la contrasenya "sembla correcta" però PostgreSQL la rebutja. Si fas servir echo, posa-hi -n.

5.2. kubectl create secret docker-registry

kubectl create secret docker-registry registry-rutasnorte \
  --docker-server=registry.rutasnorte.example \
  --docker-username=desplegaments \
  --docker-password='<token-de-desplegament>' \
  [email protected] \
  -n rutas-norte-pro

5.3. kubectl create secret tls

kubectl create secret tls botiga-web-tls \
  --cert=certs/www.rutasnorte.example.crt \
  --key=certs/www.rutasnorte.example.key \
  -n rutas-norte-pro

5.4. Manifest amb stringData

Si escrius el manifest a mà, fes servir sempre stringData, mai data:

apiVersion: v1
kind: Secret
metadata:
  name: postgres-reserves-credencials
  namespace: rutas-norte-dev
  labels:
    app: postgres-reserves
    app.kubernetes.io/name: postgres-reserves
    app.kubernetes.io/component: base-dades
    app.kubernetes.io/part-of: rutas-norte
    entorn: dev
type: Opaque
stringData:                         # text pla; l'apiserver el codifica en desar-lo
  username: rutasnorte
  password: "R3s3rv3s2026!"
  database: reserves

Avantatges de stringData davant de data:

  • S'escriu i es llegeix sense codificar ni descodificar res.
  • Elimina l'error de codificar amb echo sense -n, que fica un \n al valor.
  • Un diff en una pull request és llegible... cosa que, atenció, també és el motiu pel qual aquest fitxer no pot anar a Git tal qual.

Si una clau apareix a stringData i a data, guanya stringData.

I aquí arriba la pregunta òbvia: si aquest fitxer no pot anar a Git, on viu? Tres respostes legítimes, en ordre de maduresa:

  1. Només al clúster. El fitxer s'aplica una vegada des d'un lloc segur i es destrueix. Simple, però es perd la traçabilitat i cal documentar a part quines claus existeixen.
  2. A Git, xifrat. Sealed Secrets o SOPS permeten versionar un fitxer que només el clúster pot desxifrar (apartat 13).
  3. Fora de Kubernetes. Un gestor de secrets extern (Vault, AWS Secrets Manager) és la font de veritat i un operador el sincronitza (apartat 13).

Mentrestant, el mínim imprescindible al repositori de Rutas Norte:

# .gitignore
k8s/**/secret-*.yaml
k8s/**/*-secret.yaml
*.key
*.pem

I un secret-postgres.yaml.exemple amb valors ficticis, versionat, perquè l'equip sàpiga quines claus calen.

  1. Consumir un Secret com a volum

El mecanisme és idèntic al dels ConfigMaps de la lliçó anterior, canviant configMap per secret i name per secretName:

    spec:
      containers:
        - name: api
          image: registry.rutasnorte.example/api-reserves:2.5.0
          volumeMounts:
            - name: credencials-bd
              mountPath: /etc/secrets/postgres    # ruta propia, no un directori del sistema
              readOnly: true
      volumes:
        - name: credencials-bd
          secret:
            secretName: postgres-reserves-credencials
            defaultMode: 0400                      # nomes el propietari llegeix
            items:                                 # opcional: nomes aquestes claus
              - key: password
                path: password

Dins del contenidor:

kubectl exec -n rutas-norte-dev deploy/api-reserves -- ls -la /etc/secrets/postgres
kubectl exec -n rutas-norte-dev deploy/api-reserves -- mount | grep secrets
total 0
drwxrwxrwt 3 root root  100 Aug  5 11:31 .
drwxr-xr-x 3 root root 4096 Aug  5 11:31 ..
lrwxrwxrwx 1 root root   15 Aug  5 11:31 password -> ..data/password

tmpfs on /etc/secrets/postgres type tmpfs (ro,relatime,size=...)

Dues coses a destacar:

  • type tmpfs: el volum és a memòria RAM. No toca mai el disc del node. En morir el pod, desapareix.
  • La mateixa cadena d'enllaços a ..data que als ConfigMaps, amb la mateixa conseqüència: si el Secret canvia, el fitxer muntat s'actualitza (amb el mateix retard del kubelet, i amb la mateixa excepció de subPath, que no propaga).

Aquesta capacitat de refresc és la raó principal per preferir el volum davant de la variable d'entorn quan es tracta de credencials. Una aplicació ben escrita pot rellegir el fitxer i adoptar una contrasenya rotada sense reiniciar-se. Hi tornarem a l'apartat 11.

El consum com a variable d'entorn (secretKeyRef, envFrom amb secretRef) és igual de comú i sovint més pràctic, sobretot amb imatges de tercers com postgres:16, que esperen POSTGRES_PASSWORD a l'entorn. El veuràs amb tot el detall a Variables d'Entorn. N'avanço la comparació, perquè condiciona el disseny:

Volum Variable d'entorn
Es refresca en rotar Sí No: exigeix recrear el pod
Visible a kubectl describe pod No La referència sí, el valor no
Visible a /proc/<pid>/environ No Sí, per a qualsevol procés del contenidor
Pot filtrar-se en un bolcat d'error Poc probable Sí: molts frameworks bolquen l'entorn
Compatible amb imatges de tercers De vegades (*_FILE) Gairebé sempre

Moltes imatges serioses admeten la variant _FILE: POSTGRES_PASSWORD_FILE=/etc/secrets/postgres/password. Quan existeixi, prefereix-la: combina la compatibilitat de la variable amb la seguretat del fitxer.

  1. El secret postgres-reserves-credencials a Rutas Norte

Anem a saldar el deute de debò. Disseny complet:

flowchart LR
    S["Secret<br/>postgres-reserves-credencials<br/>type: Opaque"]
    S -->|"env: POSTGRES_PASSWORD"| PG["postgres-reserves<br/>(crea l'usuari en inicialitzar-se)"]
    S -->|"volum /etc/secrets/postgres"| API["api-reserves<br/>(es connecta a la BD)"]
    S -->|"volum /etc/secrets/postgres"| W["worker-notificacions<br/>(llegeix reserves pendents)"]
    S2["Secret<br/>smtp-notificacions"] -->|"env: SMTP_PASSWORD"| W
    RBAC["RBAC (08-01)<br/>nomes aquestes SA llegeixen el Secret"] -.controla.-> S

El Secret, a k8s/entorns/dev/secret-postgres.yaml (fora de Git):

apiVersion: v1
kind: Secret
metadata:
  name: postgres-reserves-credencials
  namespace: rutas-norte-dev
  labels:
    app: postgres-reserves
    app.kubernetes.io/name: postgres-reserves
    app.kubernetes.io/part-of: rutas-norte
    entorn: dev
type: Opaque
stringData:
  username: rutasnorte
  password: "d3v-C4nvi4m3-2026"
  database: reserves
  # URL completa: comoda per a l'app, pero duplica la contrasenya.
  # Si l'app la pot compondre, millor no tenir-la aqui.
  url: "postgresql://rutasnorte:d3v-C4nvi4m3-2026@postgres-reserves:5432/reserves"

El Deployment de postgres-reserves, ja sense contrasenyes en clar:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres-reserves
  namespace: rutas-norte-dev
  labels:
    app: postgres-reserves
    app.kubernetes.io/part-of: rutas-norte
    entorn: dev
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres-reserves
      entorn: dev
  strategy:
    type: Recreate                     # una BD no admet dos pods sobre el mateix disc
  template:
    metadata:
      labels:
        app: postgres-reserves
        app.kubernetes.io/name: postgres-reserves
        app.kubernetes.io/component: base-dades
        app.kubernetes.io/part-of: rutas-norte
        entorn: dev
    spec:
      containers:
        - name: postgres
          image: postgres:16.4
          ports:
            - name: postgres
              containerPort: 5432
          env:
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: postgres-reserves-credencials
                  key: username
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-reserves-credencials
                  key: password
            - name: POSTGRES_DB
              valueFrom:
                secretKeyRef:
                  name: postgres-reserves-credencials
                  key: database
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata

I api-reserves, que la consumeix com a fitxer:

        - name: api
          image: registry.rutasnorte.example/api-reserves:2.5.0
          env:
            - name: DB_HOST
              value: postgres-reserves          # el Service del modul 2
            - name: DB_PORT
              value: "5432"
            - name: DB_PASSWORD_FILE
              value: /etc/secrets/postgres/password
          volumeMounts:
            - name: credencials-bd
              mountPath: /etc/secrets/postgres
              readOnly: true
      volumes:
        - name: credencials-bd
          secret:
            secretName: postgres-reserves-credencials
            defaultMode: 0400
            items:
              - key: password
                path: password
              - key: username
                path: username

Fixa't en el detall de disseny: api-reserves rep només username i password gràcies a items. No rep la clau url, que conté la contrasenya duplicada, ni cap altra clau que es pugui afegir al Secret en el futur. És el principi de mínim privilegi aplicat al contingut d'un objecte.

Verificació que el deute està saldat:

grep -r "R3s3rv3s2026" k8s/ ; echo "sortida: $?"
kubectl exec -n rutas-norte-dev deploy/api-reserves -- cat /etc/secrets/postgres/password; echo
sortida: 1

d3v-C4nvi4m3-2026

grep no troba res al repositori i l'aplicació té la seva credencial. Aquest és l'objectiu.

  1. imagePullSecrets per al registre privat

Les imatges de Rutas Norte viuen a registry.rutasnorte.example, que és privat. Sense credencials, el pod falla així:

kubectl describe pod api-reserves-7d9f8c6b4-x2mkl -n rutas-norte-pro | tail -6
Events:
  Type     Reason     Age                From     Message
  ----     ------     ----               ----     -------
  Normal   Pulling    45s                kubelet  Pulling image "registry.rutasnorte.example/api-reserves:2.5.0"
  Warning  Failed     43s                kubelet  Failed to pull image: rpc error: code = Unknown
             desc = failed to resolve reference: unexpected status from HEAD request: 401 Unauthorized
  Warning  Failed     43s                kubelet  Error: ErrImagePull
  Normal   BackOff    18s (x3 over 42s)  kubelet  Back-off pulling image
  Warning  Failed     18s                kubelet  Error: ImagePullBackOff

ImagePullBackOff amb un 401 és sempre el mateix: falten credencials de registre.

Es crea el Secret del tipus adequat:

kubectl create secret docker-registry registry-rutasnorte \
  --docker-server=registry.rutasnorte.example \
  --docker-username=desplegaments \
  --docker-password='<token-de-desplegament-de-nomes-lectura>' \
  -n rutas-norte-pro
secret/registry-rutasnorte created

El que desa a dins és un fitxer .dockerconfigjson:

kubectl get secret registry-rutasnorte -n rutas-norte-pro \
  -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq .
{
  "auths": {
    "registry.rutasnorte.example": {
      "username": "desplegaments",
      "password": "<token-de-desplegament-de-nomes-lectura>",
      "auth": "ZGVzcGxlZ2FtZW50czo8dG9rZW4uLi4+"
    }
  }
}

Una altra demostració que base64 no protegeix res: el camp auth és simplement usuari:contrasenya codificat.

I es referencia al pod:

    spec:
      imagePullSecrets:
        - name: registry-rutasnorte      # a nivell de POD, no de contenidor
      containers:
        - name: api
          image: registry.rutasnorte.example/api-reserves:2.5.0

Tres detalls importants:

  1. imagePullSecrets va a spec del pod, no dins de containers. Aplica a totes les imatges del pod, inclosos els init containers.
  2. Se'n poden llistar diversos, un per registre. El kubelet tria el que casa amb el servidor de la imatge.
  3. Cal repetir-ho a cada pod del namespace, cosa que és tediosa i fàcil d'oblidar. La solució elegant és declarar-ho a la ServiceAccount: tots els pods que la facin servir l'heretaran sense escriure'l. Ho veuràs a ServiceAccounts.

Bona pràctica de seguretat: el token del registre ha de ser de només lectura d'imatges (pull), mai un amb permís de pujada. Si el clúster es veu compromès, un token de pull limita el dany a poder descarregar imatges; un de push permetria substituir-les per versions malicioses. La lliçó de Seguretat d'Imatges hi aprofundeix.

  1. Xifratge en repòs a etcd

Arribem al punt que més gent desconeix.

Per defecte, els Secrets es desen a etcd sense xifrar. Codificats en base64, sí; xifrats, no. Qui tingui accés al disc d'un node del pla de control, a una còpia de seguretat d'etcd o a un bolcat del sistema de fitxers, té totes les credencials del clúster.

Demostració (en un clúster on tinguis accés al pla de control):

sudo ETCDCTL_API=3 etcdctl \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  get /registry/secrets/rutas-norte-dev/postgres-reserves-credencials | hexdump -C | head -8
00000000  2f 72 65 67 69 73 74 72  79 2f 73 65 63 72 65 74  |/registry/secret|
00000010  73 2f 72 75 74 61 73 2d  6e 6f 72 74 65 2d 64 65  |s/rutas-norte-de|
00000020  76 2f 70 6f 73 74 67 72  65 73 0a 6b 38 73 00 0a  |v/postgres.k8s..|
...
000000a0  64 33 76 2d 43 34 6e 76  69 34 6d 33 2d 32 30 32  |d3v-C4nvi4m3-202|
000000b0  36 12 0a 72 75 74 61 73  6e 6f 72 74 65           |6..rutasnorte|

La contrasenya es llegeix directament al bolcat. Ni tan sols cal descodificar base64: etcd desa l'objecte serialitzat amb els valors ja en binari.

La solució és una EncryptionConfiguration a l'apiserver:

# /etc/kubernetes/enc/encryption-config.yaml (nomes als nodes del pla de control)
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets                        # quins objectes es xifren
      - configmaps                     # opcional, te cost
    providers:
      - aescbc:                        # el PRIMER es fa servir per ESCRIURE
          keys:
            - name: clau-2026-08
              secret: <32 bytes aleatoris en base64>
      - identity: {}                   # sense xifrar: necessari per LLEGIR el que hi havia

I s'activa arrencant l'apiserver amb --encryption-provider-config=/etc/kubernetes/enc/encryption-config.yaml.

Els proveïdors disponibles:

Proveïdor Xifratge Velocitat Fortalesa Comentari
identity Cap — Nul·la El comportament per defecte
secretbox XSalsa20 + Poly1305 Molt ràpida Forta Bona opció si no hi ha KMS
aescbc AES-CBC 32 bytes Ràpida Acceptable Vulnerable a padding oracle en teoria
aesgcm AES-GCM Molt ràpida Forta Exigeix rotar la clau amb freqüència
kms v2 Delegat en un KMS extern Ràpida La millor La clau mai no és al disc del node

Quatre regles crítiques:

  1. L'ordre importa. El primer proveïdor de la llista és el que xifra en escriure. Tots els de la llista es proven en llegir. Per això identity ha d'anar al final durant la migració: permet llegir els Secrets que ja existien sense xifrar.
  2. Activar-ho no xifra el que ja existeix. Només es xifra el que s'escriu a partir d'aquell moment. Cal forçar una reescriptura de tot:
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
  1. La clau del fitxer és la joia de la corona. Si fas servir aescbc o secretbox, la clau és en text pla al disc del node del pla de control. Qui tingui aquest fitxer i una còpia d'etcd ho té tot. Per això kms v2 és l'única opció realment sòlida en producció: la clau de xifratge de dades s'embolcalla amb una clau que viu en un HSM o al KMS del núvol i mai no s'escriu al node.
  2. A Kubernetes gestionat (EKS, AKS, GKE) això s'activa amb una casella, delegant en el KMS del proveïdor. És una de les raons de pes per fer servir un clúster gestionat, com veuràs a 10-06.

Per a Rutas Norte, que desa DNI i dades de contacte de clients, el xifratge en repòs amb KMS no és opcional: és una mesura tècnica de les que el RGPD espera veure documentada. I un cop més: la decisió concreta l'ha de validar el responsable de compliment.

  1. Qui pot llegir un secret

Un Secret no té cap protecció pròpia. Tot depèn de RBAC, que s'estudia a fons a Control d'Accés Basat en Rols. Aquí n'hi ha prou d'entendre per què és inseparable.

Qui pot llegir postgres-reserves-credencials avui:

Qui Com Com es restringeix
Qualsevol amb get secrets al namespace kubectl get secret -o yaml RBAC: no concedir aquest verb
Qualsevol amb list secrets al namespace kubectl get secrets -o yaml (llista els continguts!) RBAC: list és tan perillós com get
Qualsevol que pugui crear un pod al namespace Munta el Secret en un pod seu i el llegeix RBAC sobre create pods
Qualsevol amb exec en un pod que el munti kubectl exec ... -- cat /etc/secrets/... RBAC sobre pods/exec
El kubelet dels nodes on corren aquests pods Necessita el Secret per muntar-lo NodeRestriction limita als dels seus pods
Qui tingui accés a etcd o a les seves còpies Bolcat directe Xifratge en repòs + seguretat del node
Un administrador del clúster Tot Auditoria

Les dues entrades que sorprenen i cal subratllar:

list filtra els continguts. Molta gent concedeix list secrets pensant que només permet veure els noms. No és així: kubectl get secrets -o yaml amb permís de list retorna els objectes complets, amb les seves dades. A RBAC, list sobre secrets és equivalent a get.

Poder crear pods equival a poder llegir tots els secrets del namespace. Qui pugui desplegar un pod pot muntar qualsevol Secret del namespace i bolcar-lo. No hi ha manera d'evitar-ho amb RBAC sobre secrets: cal restringir la creació de pods. És la raó principal per la qual el namespace és la unitat real d'aïllament de credencials i per la qual Rutas Norte separa dev, pre i pro: ningú amb accés de desenvolupament no ha de poder crear pods a rutas-norte-pro.

Un bon exercici d'auditoria, que anticipa el mòdul 8:

kubectl auth can-i get secrets -n rutas-norte-pro
kubectl auth can-i list secrets -n rutas-norte-pro --as=system:serviceaccount:rutas-norte-pro:default
yes
no

  1. Rotació de credencials

Rotar una credencial és canviar-la de manera periòdica i, obligatòriament, quan se sospita que s'ha filtrat. I aquí apareix un problema que Kubernetes no resol tot sol.

El problema: un pod que va arrencar ahir té la contrasenya vella. Si la canvies al Secret:

  • Si la consumeix com a volum: el fitxer s'actualitza en un parell de minuts. Però l'aplicació només se n'assabentarà si rellegeix el fitxer. La majoria llegeix en arrencar i manté la connexió oberta.
  • Si la consumeix com a variable d'entorn: no s'actualitza mai. Les variables d'entorn d'un procés es fixen en executar-lo i no hi ha manera de canviar-les des de fora.

La seqüència correcta de rotació, amb doble credencial vàlida, que és el que evita el tall de servei:

sequenceDiagram
    participant OP as Operador
    participant PG as postgres-reserves
    participant K8S as Secret
    participant API as pods d'api-reserves

    OP->>PG: 1. Crear la contrasenya NOVA (la vella segueix valida)
    OP->>K8S: 2. Actualitzar el Secret amb la nova
    Note over API: 3. Els pods vells segueixen amb la vella: SEGUEIXEN FUNCIONANT
    OP->>API: 4. kubectl rollout restart (progressiu, sense tall)
    Note over API: 5. Els pods nous arrenquen amb la nova
    OP->>PG: 6. Revocar la contrasenya VELLA
    OP->>PG: 7. Verificar als logs que ningu no la fa servir

En ordres:

# 1. A PostgreSQL, crear la credencial nova sense treure la vella
kubectl exec -n rutas-norte-pro deploy/postgres-reserves -- \
  psql -U postgres -c "ALTER USER rutasnorte PASSWORD 'nova-2026-08';"

# 2. Actualitzar el Secret
kubectl create secret generic postgres-reserves-credencials \
  --from-literal=username=rutasnorte \
  --from-literal=password='nova-2026-08' \
  --from-literal=database=reserves \
  -n rutas-norte-pro --dry-run=client -o yaml | kubectl apply -f -

# 3. Reiniciar de manera progressiva els consumidors
kubectl rollout restart deploy/api-reserves -n rutas-norte-pro
kubectl rollout restart deploy/worker-notificacions -n rutas-norte-pro
kubectl rollout status deploy/api-reserves -n rutas-norte-pro
secret/postgres-reserves-credencials configured
deployment.apps/api-reserves restarted
deployment.apps/worker-notificacions restarted
deployment "api-reserves" successfully rolled out

Un avís sobre ALTER USER: a PostgreSQL un usuari té una sola contrasenya, així que el pas 1 la substitueix i els pods vells es trencaran tan bon punt obrin una connexió nova. En un sistema amb rotació seriosa es fan servir dos usuaris (rutasnorte_a i rutasnorte_b) i s'alterna entre ells, que és el que fan els motors de secrets dinàmics de Vault. Dissenya-ho abans de necessitar-ho.

Punts que convé fixar:

  • Automatitza la detecció del canvi. La tècnica del hash del Secret en una anotació del template, que veuràs a 03-03, fa que actualitzar el Secret dispari el desplegament tot sol, sense rollout restart manual.
  • Rota sempre després d'una exposició. Si la contrasenya va estar a Git, rotar-la és obligatori encara que hagis reescrit l'historial.
  • Vigila els pods resseguits. Un CronJob o un pod que no forma part d'un Deployment no es reinicia amb rollout restart. Fes-te una llista de consumidors abans de rotar.

  1. Què no s'ha de fer mai

Pràctica Per què és greu
Pujar el Secret a Git, encara que sigui en base64 Base64 no és xifratge. Equival a pujar-lo en clar i queda a l'historial per sempre
Posar una credencial en una anotació Les anotacions es copien a objectes derivats, surten a describe i als logs dels controladors
Passar-la a args o command Apareix a kubectl describe pod, a ps aux dins del contenidor i als logs del kubelet
Coure-la a la imatge Queda en una capa del registre; docker history la mostra encara que l'esborris en una capa posterior
Escriure-la en un ConfigMap Sense describe protegit, sense tmpfs, sense xifratge en repòs, i RBAC de ConfigMaps sol ser lax
Registrar-la en un log Els logs es centralitzen, es copien i es conserven mesos (07-05)
Reutilitzar la mateixa credencial als tres entorns Un incident a dev compromet pro
Fer servir el Secret default o compartir-ne un entre components Impedeix revocar l'accés d'un sol component
Concedir list secrets "perquè només llista noms" list retorna els continguts complets
Crear tokens de ServiceAccount permanents Un token sense caducitat no es pot revocar netament
Dependre només del .gitignore Un git add -f o un fitxer amb un altre nom se'l salten. Afegeix un pre-commit hook que detecti credencials

Sobre el punt d'args, una demostració del que queda exposat:

# NO FACIS MAI AIXO
        - name: worker
          image: registry.rutasnorte.example/worker-notificacions:1.8.0
          args: ["--smtp-password=Env10-2026!"]
kubectl describe pod worker-notificacions-6f7d9-abcde -n rutas-norte-pro | grep -A2 Args
    Args:
      --smtp-password=Env10-2026!

Qualsevol amb permís de lectura sobre pods —un permís que es concedeix a molta gent, inclosos els sistemes de monitoratge— acaba de llegir la contrasenya del correu.

  1. Les solucions reals de l'ecosistema

El Secret natiu resol el on viu la credencial dins del clúster. No resol com hi arriba de manera segura i auditable, ni com es versiona, ni com es rota sola. Per a això existeixen aquestes quatre famílies. No farem un tutorial de cap —cadascuna dona per a un curs—, però has de saber quan mirar cap a cada costat.

Solució Idea central Quan triar-la
Sealed Secrets (Bitnami) Un controlador al clúster té una clau privada; tu xifres amb la pública i puges el fitxer xifrat a Git. Només aquell clúster el pot desxifrar Equips petits, GitOps pur, sense infraestructura externa
SOPS (+ age/KMS) Xifra només els valors del YAML, deixant les claus llegibles. S'integra amb Flux, Helm i Kustomize Quan vols diffs llegibles i ja fas servir GitOps
External Secrets Operator Un CRD ExternalSecret declara d'on treure la credencial; l'operador la llegeix de Vault/AWS/Azure/GCP i manté sincronitzat un Secret natiu L'estàndard de facto a empreses amb un gestor de secrets ja muntat
HashiCorp Vault Gestor de secrets complet: credencials dinàmiques de vida curta, motor de PKI, auditoria detallada, polítiques pròpies Organitzacions grans, requisits d'auditoria forts
KMS dels núvols (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager) El gestor del núvol és la font de veritat; s'hi accedeix amb identitat federada, sense credencials estàtiques Ja ets en aquell núvol

Un exemple de com es veu un ExternalSecret, només perquè reconeguis el patró quan te'l trobis:

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: postgres-reserves-credencials
  namespace: rutas-norte-pro
spec:
  refreshInterval: 1h                     # resincronitza cada hora: rotacio automatica
  secretStoreRef:
    name: vault-rutasnorte
    kind: SecretStore
  target:
    name: postgres-reserves-credencials   # el Secret NATIU que es creara
    creationPolicy: Owner
  data:
    - secretKey: password                 # clau al Secret de Kubernetes
      remoteRef:
        key: rutasnorte/pro/postgres      # ruta a Vault
        property: password

L'elegant del patró: aquest fitxer sí que pot anar a Git. No conté la credencial, només diu on trobar-la. I refreshInterval: 1h significa que si algú rota la contrasenya a Vault, el Secret del clúster s'actualitza sol en menys d'una hora.

On encaixa cada cosa en la maduresa d'una plataforma com Rutas Norte:

  1. Avui (on som): Secrets natius creats a mà, fora de Git, amb .gitignore i un fitxer d'exemple. Acceptable a dev.
  2. Pas següent: xifratge en repòs amb KMS al clúster i RBAC estricte sobre secrets a rutas-norte-pro.
  3. Maduresa: External Secrets Operator apuntant al gestor de secrets corporatiu, amb rotació automàtica i auditoria de cada lectura.

Errors Comuns i Consells

Error Símptoma Solució
Creure que base64 protegeix Es puja el Secret a Git És codificació, no xifratge. Mai a Git sense xifrar
echo sense -n en codificar L'app rebutja la contrasenya "correcta" Fes servir printf '%s' o echo -n, o millor stringData
Tipus incorrecte de Secret data[username]: Required value Revisa la taula de tipus de l'apartat 4
Secret en un altre namespace Pod en ContainerCreating, FailedMount Els Secrets no creuen namespaces
Esperar refresc amb variables d'entorn La rotació no arriba als pods Les variables són immutables: rollout restart
subPath amb un Secret La rotació no es propaga mai Igual que amb ConfigMaps: evita subPath
defaultMode: 0400 amb usuari no root Permission denied Fes servir 0440 amb fsGroup, o 0444
Oblidar imagePullSecrets ImagePullBackOff amb 401 Afegeix-lo al pod o, millor, a la ServiceAccount
imagePullSecrets dins de containers Error de validació del YAML Va a spec del pod
Concedir list secrets Fuita silenciosa de totes les credencials list retorna continguts. Tracta'l com get
No activar xifratge en repòs Credencials llegibles a les còpies d'etcd EncryptionConfiguration amb kms v2
Rotar sense doble credencial Tall de servei durant la rotació Credencial nova vàlida abans de revocar la vella
Contrasenya a args o en un log Visible a describe pod i al sistema de logs Volum o variable d'entorn, mai argument

Consells finals:

  1. Tracta tot Secret com si s'hagués de filtrar. Dissenya assumint que algú el llegirà: credencials diferents per entorn i per component, amb el mínim privilegi a la base de dades.
  2. Instal·la un detector de credencials al pre-commit. gitleaks o detect-secrets costen deu minuts de configuració i eviten el 90 % dels accidents.
  3. Tingues un inventari de secrets. Què existeix, qui el consumeix, quan es va rotar per última vegada, qui n'és el responsable.
  4. Documenta les claus, mai els valors. Un secret-postgres.yaml.exemple amb password: CANVIAM versionat és útil i segur.
  5. I el que s'ha dit al principi: que el disseny el revisi un professional de seguretat o el responsable de compliment. Amb dades personals de clients, això no és negociable.

Exercicis

Exercici 1: Demostrar que base64 no és xifratge

  1. Crea un Secret passarella-pagament a rutas-norte-dev amb les claus api_key (valor sk_test_4eC39HqLyjWDarjtT1zdp7dc) i entorn (valor sandbox).
  2. Mostra l'objecte en YAML i comprova que no es llegeix el valor.
  3. Descodifica api_key amb una sola ordre encadenada.
  4. Comprova què mostra kubectl describe d'aquest Secret i explica per què això no és una mesura de seguretat.
  5. Respon: si un company té el verb list sobre secrets en aquell namespace però no get, pot llegir la clau? Demostra-ho amb kubectl auth can-i.

Exercici 2: Saldar el deute de postgres-reserves

  1. Escriu el manifest del Secret postgres-reserves-credencials per a rutas-norte-dev fent servir stringData, amb username, password i database, i amb l'esquema d'etiquetes de Rutas Norte.
  2. Modifica el Deployment de postgres-reserves perquè prengui les tres variables del Secret.
  3. Modifica api-reserves perquè munti només username i password com a fitxers a /etc/secrets/postgres amb permisos de només lectura.
  4. Verifica que el volum és a tmpfs.
  5. Demostra amb grep que a k8s/ no queda cap contrasenya en clar, i afegeix les regles necessàries al .gitignore.

Exercici 3: Rotació sense tall de servei

api-reserves corre amb 4 rèpliques a rutas-norte-pro i cal rotar la contrasenya de la base de dades perquè un antic membre de l'equip la coneixia.

  1. Escriu la seqüència completa de passos, indicant en quin d'ells hi ha risc de tall i com l'evites.
  2. Executa l'actualització del Secret amb una sola ordre que funcioni tant si el Secret existeix com si no.
  3. Fes que els pods adoptin la credencial nova sense quedar-te sense servei en cap moment.
  4. Verifica que els 4 pods nous són amunt i que cap no conserva la credencial anterior.
  5. Identifica quins consumidors del Secret no es reinicien amb kubectl rollout restart i què faries amb ells.

Solucions

Solució 1

kubectl create secret generic passarella-pagament \
  --from-literal=api_key=sk_test_4eC39HqLyjWDarjtT1zdp7dc \
  --from-literal=entorn=sandbox \
  -n rutas-norte-dev

kubectl get secret passarella-pagament -n rutas-norte-dev -o yaml
secret/passarella-pagament created

apiVersion: v1
data:
  api_key: c2tfdGVzdF80ZUMzOUhxTHlqV0Rhcmp0VDF6ZHA3ZGM=
  entorn: c2FuZGJveA==
kind: Secret
metadata:
  name: passarella-pagament
  namespace: rutas-norte-dev
type: Opaque

La descodificació:

kubectl get secret passarella-pagament -n rutas-norte-dev \
  -o jsonpath='{.data.api_key}' | base64 -d; echo
sk_test_4eC39HqLyjWDarjtT1zdp7dc
kubectl describe secret passarella-pagament -n rutas-norte-dev
Name:         passarella-pagament
Namespace:    rutas-norte-dev
Type:         Opaque

Data
====
api_key:  32 bytes
entorn:   7 bytes

describe només mostra la mida. No és una mesura de seguretat, sinó d'higiene visual: evita mostrar la credencial per accident en una pantalla compartida o a la sortida d'un script. Qui té permís per fer describe té el verb get, i amb get -o yaml n'obté el valor. La protecció real la donen RBAC i el xifratge en repòs, no el format de sortida.

Sobre list:

kubectl auth can-i list secrets -n rutas-norte-dev [email protected]
kubectl get secrets -n rutas-norte-dev -o yaml [email protected] | grep api_key
yes
  api_key: c2tfdGVzdF80ZUMzOUhxTHlqV0Rhcmp0VDF6ZHA3ZGM=

Sí que la pot llegir. list retorna els objectes complets, no només els seus noms. És un error de concessió de permisos molt estès.

Solució 2

# k8s/entorns/dev/secret-postgres.yaml  (NO es versiona a Git)
apiVersion: v1
kind: Secret
metadata:
  name: postgres-reserves-credencials
  namespace: rutas-norte-dev
  labels:
    app: postgres-reserves
    app.kubernetes.io/name: postgres-reserves
    app.kubernetes.io/component: base-dades
    app.kubernetes.io/part-of: rutas-norte
    entorn: dev
type: Opaque
stringData:
  username: rutasnorte
  password: "d3v-C4nvi4m3-2026"
  database: reserves

El Deployment de postgres-reserves és el de l'apartat 7. El fragment d'api-reserves:

          volumeMounts:
            - name: credencials-bd
              mountPath: /etc/secrets/postgres
              readOnly: true
      volumes:
        - name: credencials-bd
          secret:
            secretName: postgres-reserves-credencials
            defaultMode: 0400
            items:
              - key: username
                path: username
              - key: password
                path: password

Verificació del tmpfs i que només hi ha dos fitxers:

kubectl exec -n rutas-norte-dev deploy/api-reserves -- sh -c \
  'ls /etc/secrets/postgres && mount | grep secrets'
password
username
tmpfs on /etc/secrets/postgres type tmpfs (ro,relatime)

Dos fitxers, no tres: database no s'ha projectat perquè no és a items. I el volum és tmpfs, és a dir, RAM.

grep -rniE "password.*[:=].*[a-z0-9]{8}" k8s/ --include=*.yaml | grep -v secret-

Sense resultats. El .gitignore:

# Secrets: mai al repositori
k8s/**/secret-*.yaml
k8s/**/*-secret.yaml
!k8s/**/*.yaml.exemple
*.key
*.pem
*.p12

La línia !k8s/**/*.yaml.exemple permet versionar les plantilles amb valors ficticis, que són les que documenten quines claus calen.

Solució 3

La seqüència, amb el risc assenyalat:

  1. Crear la contrasenya nova a PostgreSQL. Risc alt si el motor només admet una contrasenya per usuari: els pods vius es trencaran a la connexió següent. S'evita creant un usuari nou (rutasnorte_b) en lloc de canviar el de l'actual, deixant tots dos vàlids.
  2. Actualitzar el Secret. Sense risc: els pods vius ja tenen la credencial carregada a memòria.
  3. rollout restart. Sense risc si maxUnavailable està ben posat: el mòdul 2 ens garanteix substitució progressiva.
  4. Revocar la credencial vella. Risc si queda algun consumidor sense reiniciar; per això va després de verificar.
kubectl create secret generic postgres-reserves-credencials \
  --from-literal=username=rutasnorte_b \
  --from-literal=password='R0t4d4-2026-08!' \
  --from-literal=database=reserves \
  -n rutas-norte-pro --dry-run=client -o yaml | kubectl apply -f -
secret/postgres-reserves-credencials configured

El truc --dry-run=client -o yaml | kubectl apply -f - és l'idiomàtic: genera l'objecte i l'aplica, funcionant tant per crear com per actualitzar, cosa que kubectl create sol no fa (fallaria amb AlreadyExists).

kubectl rollout restart deploy/api-reserves -n rutas-norte-pro
kubectl rollout status deploy/api-reserves -n rutas-norte-pro
kubectl get pods -l app=api-reserves -n rutas-norte-pro
deployment.apps/api-reserves restarted
Waiting for deployment "api-reserves" rollout to finish: 2 of 4 updated replicas are available...
deployment "api-reserves" successfully rolled out

NAME                            READY   STATUS    RESTARTS   AGE
api-reserves-7f4b8c9d6-2xkqp    1/1     Running   0          48s
api-reserves-7f4b8c9d6-8mvtr    1/1     Running   0          62s
api-reserves-7f4b8c9d6-j9wzl    1/1     Running   0          35s
api-reserves-7f4b8c9d6-qh4nc    1/1     Running   0          21s

Els quatre pods són nous (mateix pod-template-hash, AGE menor d'un minut i RESTARTS 0). Verificació de la credencial efectiva:

kubectl exec -n rutas-norte-pro deploy/api-reserves -- cat /etc/secrets/postgres/username; echo
rutasnorte_b

Consumidors que no es reinicien amb rollout restart dels Deployments:

  • CronJobs com informes-ocupacio (mòdul 6): els pods es creen a cada execució, així que prendran la credencial nova tots sols; però si hi ha una execució en curs durant la rotació, fallarà. Cal mirar kubectl get jobs abans de revocar.
  • Pods solts creats a mà per depurar. Es localitzen amb kubectl get pods -n rutas-norte-pro -o json | jq '.items[] | select(.metadata.ownerReferences == null) | .metadata.name'.
  • StatefulSets (mòdul 6): rollout restart funciona, però és ordinal i més lent; cal esperar que acabi.
  • Clients fora del clúster: eines d'informes, còpies de seguretat, quadres de comandament. No els veu Kubernetes i són els que provoquen l'incident en revocar. Per això l'inventari de consumidors de l'apartat 11 no és burocràcia.

Conclusió

Has saldat el deute més greu del mòdul 2. La contrasenya de postgres-reserves ja no és a cap manifest de Git: viu en un Secret, api-reserves la rep com a fitxer muntat en memòria amb només les claus que necessita, i postgres-reserves la pren de secretKeyRef per inicialitzar-se. Un grep sobre k8s/ no retorna res.

Però l'important d'aquesta lliçó no és l'objecte, sinó el criteri. Saps que un Secret es diferencia d'un ConfigMap en stringData, en el muntatge en tmpfs, en què describe amaga el contingut i en què existeix un camp type que valida la forma. I saps, perquè ho has demostrat amb una ordre, que base64 no és xifratge: la protecció real l'aporten RBAC, el xifratge en repòs i l'aïllament per namespaces, no l'objecte en si. Coneixes els set tipus de Secret i per a què serveix cadascun, les tres formes de crear-los, i per què stringData és sempre preferible a data en escriure a mà.

Saps que el xifratge en repòs a etcd no ve activat, has vist la contrasenya llegible en un bolcat d'etcd, coneixes els cinc proveïdors d'EncryptionConfiguration i per què kms v2 és l'únic realment sòlid, i que activar-lo no xifra retroactivament el que ja existeix. Tens clar qui pot llegir un secret —amb les dues sorpreses: list filtra continguts i poder crear pods equival a poder-ho llegir tot—, com es rota una credencial sense tallar el servei i per què les variables d'entorn no es refresquen mai. Tens la llista del que no s'ha de fer mai i un mapa de l'ecosistema real —Sealed Secrets, SOPS, External Secrets Operator, Vault, els KMS dels núvols— per quan el Secret natiu es quedi curt. I no oblidis l'advertiment de l'inici: amb les dades personals que desa Rutas Norte, el disseny definitiu l'ha de validar un professional de seguretat o el responsable de compliment.

Les dues lliçons han deixat un cap solt comú. Hem vist els objectes que guarden la configuració i els secrets, i una de les formes de portar-los al contenidor: el fitxer muntat. Falta l'altra, la que apareix al 80 % dels manifests del món real i que totes dues lliçons han esmentat sense desenvolupar. La següent, Variables d'Entorn, la cobreix sencera: env, envFrom amb prefix, valueFrom amb configMapKeyRef i secretKeyRef, el flag optional, la Downward API perquè api-reserves registri a les seves traces quin pod i quin node va atendre cada petició, l'expansió $(VAR) i les seves regles, les col·lisions quan la mateixa clau arriba per diverses vies, i la tècnica del hash de la configuració en una anotació que resol d'una vegada el problema que les variables d'entorn no es refresquin mai.

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