Tanquem el mòdul amb la lliçó que arregla el forat que arrosseguem des del mòdul 3: qualsevol pod del clúster es pot connectar a postgres-reserves:5432. Un pod compromès, una dependència maliciosa en una imatge o un desplegament equivocat a rutas-norte-dev tenen avui accés directe a la base de dades amb el nom, el DNI, el telèfon i el correu de tots els clients de Rutas Norte. Les NetworkPolicies són el tallafocs natiu de Kubernetes, i en aquesta lliçó construirem el model d'aïllament de la plataforma pas a pas: denegar-ho tot, obrir el DNS —la fallada que tomba el clúster sencer i que gairebé tothom comet un cop—, i autoritzar una per una únicament les converses que la plataforma necessita, verificant cada regla amb pods efímers.

Contingut

  1. Demostrar el problema: la xarxa és plana
  2. El requisit imprescindible: un CNI que les implementi
  3. Anatomia d'una NetworkPolicy
  4. El model additiu: només es permet, mai es denega
  5. from/to: podSelector, namespaceSelector i ipBlock
  6. L'error més car: I davant d'O
  7. Pas 1: denegar-ho tot a rutas-norte-pro
  8. Pas 2: permetre el DNS (o trencar-ho tot)
  9. Pas 3: les converses de la plataforma
  10. Pas 4: l'Ingress i la passarel·la de pagaments externa
  11. Verificació sistemàtica
  12. Limitacions reals

  1. Demostrar el problema: la xarxa és plana

Abans d'arreglar res, vegem-ho. Llancem en producció un pod que no té cap relació amb la plataforma: sense etiquetes de Rutas Norte, sense ServiceAccount dedicada, sense res.

kubectl run intrus --rm -it --restart=Never -n rutas-norte-pro \
  --image=nicolaka/netshoot -- bash

# I des de dins del pod intrus, tot respon:
nc -zv postgres-reserves 5432                  # la base de dades amb les dades personals
nc -zv redis-cache 6379                        # la memoria cau
nc -zv postgres-reserves.rutas-norte-dev 5432  # UN ALTRE entorn: els namespaces no aillen
curl -s -o /dev/null https://pagos.proveedorexterno.example/   # i sortida a internet

Tot funciona. És la regla 2 del model de xarxa de 04-01: tot pod pot parlar amb tot pod sense NAT. No és una fallada: és el comportament especificat. I les seves implicacions són serioses:

Escenari Conseqüència avui
S'explota una vulnerabilitat a botiga-web (nginx exposat a internet) Des d'aquell pod s'arriba directament a la base de dades
Una dependència de worker-notificacions resulta maliciosa Pot exfiltrar dades a qualsevol destinació d'internet
Una prova mal configurada a dev Pot escriure a postgres-reserves de producció

Ja hem posat capes —identitat mínima amb ServiceAccounts (03-06) i quotes per entorn (03-04)—; falta la capa de xarxa.

  1. El requisit imprescindible: un CNI que les implementi

Aquí hi ha el perill silenciós d'aquesta lliçó. Kubernetes defineix l'objecte NetworkPolicy, però no l'aplica. Qui l'aplica és el plugin CNI, i si el plugin no l'implementa l'objecte es crea sense cap error ni avís, kubectl get networkpolicy el llista amb normalitat, kubectl describe mostra les regles perfectament… i el trànsit continua passant exactament igual. Tindràs una política de seguretat que no protegeix res i una falsa sensació d'estar cobert. Com vam veure a 04-01, Flannel no implementa NetworkPolicy, i és el CNI per defecte de moltíssimes instal·lacions de laboratori, inclosa la de minikube.

Comprovar quin CNI tens i activar Calico

kubectl get pods -n kube-system -o name | grep -Ei "flannel|calico|cilium|weave"
# kube-flannel-ds-h4k2p   -> Flannel: les politiques NO faran res

# Perfil nou amb Calico (el CNI no es canvia en calent amb seguretat)
minikube start -p rutas-norte --cni=calico \
  --addons=ingress,metrics-server,storage-provisioner
kubectl get pods -n kube-system -l k8s-app=calico-node

Alternativa: --cni=cilium, que a més dona polítiques L7 i observabilitat amb Hubble. Però no confiïs mai en el nom del plugin: verifica-ho amb una prova real (exercici 1). La idea és crear un pod diana en un namespace d'usar i llençar, comprovar que respon, aplicar un deny-all d'ingress i comprovar que deixa de respondre; si continua responent, el teu CNI ignora les polítiques. Fes-ho sempre en un clúster nou, abans d'escriure una sola política de debò.

  1. Anatomia d'una NetworkPolicy

apiVersion: networking.k8s.io/v1     # v1 estable; no facis servir extensions/v1beta1
kind: NetworkPolicy
metadata:
  name: exemple
  namespace: rutas-norte-pro         # SEMPRE te namespace
spec:
  podSelector:                       # 1. A QUINS PODS s'aplica (al seu namespace)
    matchLabels: { app: postgres-reserves }
  policyTypes: [Ingress, Egress]     # 2. QUINES DIRECCIONS regula
  ingress:                           # 3. Regles d'entrada
    - from:
        - podSelector:
            matchLabels: { app: api-reserves }
      ports:
        - { protocol: TCP, port: 5432 }
  egress:                            # 4. Regles de sortida
    - to:
        - namespaceSelector:
            matchLabels: { kubernetes.io/metadata.name: kube-system }
      ports:
        - { protocol: UDP, port: 53 }
Camp Significat Detall crític
podSelector Els pods protegits per aquesta política {} (buit) significa tots els pods del namespace
policyTypes Quines direccions regula Si omets Egress, la sortida no es restringeix
ingress[].from / egress[].to Orígens permesos per entrar / destinacions permeses per sortir Llistes; cada element és un "O"
ports Ports i protocols permesos Si s'omet, tots els ports

Tres avisos sobre policyTypes, que és on més es falla: si l'omets, Kubernetes l'infereix (inclou Ingress sempre i Egress només si hi ha bloc egress), així que escriu-lo explícitament; policyTypes: [Ingress] amb ingress: [] significa "denegar tota l'entrada", diferent de no tenir el camp; i policyTypes: [Ingress, Egress] sense regles és l'aïllament total. I el punt clau: una NetworkPolicy té namespace i el seu podSelector només mira dins del seu. Per protegir els tres entorns calen les polítiques als tres.

  1. El model additiu: només es permet, mai es denega

El model mental que cal fixar abans d'escriure res:

flowchart LR
    A["Alguna NetworkPolicy selecciona<br/>aquest pod en aquesta direccio?"] -->|NO| B["TOT PERMES<br/>(el pod no esta aillat)"]
    A -->|SI| C["Pod AILLAT:<br/>per defecte es denega tot"]
    C --> D["Alguna d'aquestes politiques<br/>permet aquest transit?"]
    D -->|"SI (n'hi ha prou amb UNA)"| E["PERMES"]
    D -->|NO| F["DENEGAT"]

D'aquí surten quatre regles d'or: sense polítiques, tot passa (l'estat de Rutas Norte fins ara); tan bon punt UNA política selecciona un pod en una direcció, aquell pod queda aïllat en aquella direcció i només passa allò explícitament permès; les polítiques són additives i no existeix "denegar" —no hi ha camp deny, la unió de totes les que seleccionen el pod defineix el que és permès i n'hi ha prou que una ho autoritzi—; i no hi ha prioritats ni ordre, ni order ni "l'última guanya".

Conseqüència pràctica: no pots fer una excepció restrictiva. Si una política permet a tot rutas-norte-pro arribar a postgres-reserves, no en pots afegir una altra que digui "menys aquest pod": cal eliminar el permís ampli i enumerar els permesos. Un altre detall que confon: ingress i egress s'avaluen per separat i als dos extrems, així que perquè api-reserves parli amb postgres-reserves calen dues autoritzacions —l'egress de l'emissor i l'ingress del receptor—, i si en falta qualsevol no hi ha connexió; és la causa número u de "he permès el trànsit i continua sense funcionar". Finalment, les polítiques actuen sobre connexions i els CNI que les implementen segueixen l'estat, així que la resposta torna sense necessitar una regla de tornada.

  1. from/to: podSelector, namespaceSelector i ipBlock

Hi ha exactament tres maneres d'identificar l'altre extrem.

podSelector: pods del MATEIX namespace

  ingress:
    - from:
        - podSelector:
            matchLabels: { app: api-reserves }   # nomes pods d'AQUEST namespace

Un podSelector dins de from/to no pot arribar a altres namespaces; per a això hi ha el següent.

namespaceSelector: tots els pods de certs namespaces

  ingress:
    - from:
        - namespaceSelector:
            matchLabels: { kubernetes.io/metadata.name: ingress-nginx }

Kubernetes afegeix a cada namespace l'etiqueta kubernetes.io/metadata.name amb el seu nom, cosa que evita etiquetar-los a mà; tot i així convé posar etiquetes pròpies i estables (kubectl label namespace rutas-norte-pro entorn=pro).

ipBlock: rangs CIDR, per a allò que és fora del clúster

  egress:
    - to:
        - ipBlock:
            cidr: 0.0.0.0/0            # tot internet...
            except: [10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16]  # ...menys xarxes privades
      ports:
        - { protocol: TCP, port: 443 }

Dues advertències: no serveix per a pods —encara que tècnicament una IP de pod sigui en un CIDR, la implementació ho tracta com a trànsit extern i no és fiable; per a pods, selectors— i except només resta del cidr d'aquell mateix bloc, no és una denegació global. El patró 0.0.0.0/0 amb except de les xarxes privades és l'estàndard per dir "pot sortir a internet, però no pot pivotar cap a la xarxa interna de l'empresa".

  1. L'error més car: I davant d'O

Aquest apartat és el que més incidents de seguretat reals evita. Comparem dos fragments que es diferencien en un guionet:

# VERSIO A -- COMPTE: aixo es un O (unio)
  ingress:
    - from:
        - podSelector:
            matchLabels: { app: api-reserves }
        - namespaceSelector:              # <-- guionet propi: UN ALTRE element de la llista
            matchLabels: { entorn: pro }

# VERSIO B -- aixo es un I (interseccio)
  ingress:
    - from:
        - podSelector:
            matchLabels: { app: api-reserves }
          namespaceSelector:              # <-- SENSE guionet: mateix element
            matchLabels: { entorn: pro }
Versió A (dos guionets) Versió B (un guionet)
Semàntica podSelector O namespaceSelector podSelector I namespaceSelector
Qui entra Pods app=api-reserves d'aquest namespace, més TOTS els pods de qualsevol namespace amb entorn=pro Només els pods app=api-reserves que siguin en un namespace entorn=pro
Risc Enorme: qualsevol pod de producció entra a la base de dades Correcte

La versió A obre la base de dades a namespaces sencers. És una fallada que s'escola amb facilitat en una revisió de codi perquè el YAML sembla dir el contrari, i el clúster no dona cap avís: la política s'aplica i funciona, només que permet molt més del previst. Regla mnemotècnica: cada guionet de la llista from és un "O"; els camps dins d'un mateix element són un "I". La mateixa distinció s'aplica als elements d'ingress (llista de regles unides per O) i a ports dins d'una regla.

  1. Pas 1: denegar-ho tot a rutas-norte-pro

Comencem pel fonament: tancar-ho tot, i després obrir el just, encara que durant uns minuts la plataforma quedi trencada.

# k8s/entorns/pro/np-00-deny-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: rutas-norte-pro
  labels: { app.kubernetes.io/part-of: rutas-norte, entorn: pro }
spec:
  podSelector: {}                # {} = TOTS els pods del namespace
  policyTypes: [Ingress, Egress]
  # sense blocs ingress ni egress = no es permet res
kubectl apply -f k8s/entorns/pro/np-00-deny-all.yaml

kubectl exec -n rutas-norte-pro deploy/api-reserves -- \
  timeout 5 nc -zv postgres-reserves 5432 || echo "DENEGAT (esperat)"

La plataforma està completament trencada, i és el que volíem: a partir d'aquí cada regla serà una decisió conscient i documentada. És el mínim privilegi aplicat a la xarxa. Nota important: kubectl exec, kubectl logs i les sondes de salut del kubelet (07-01) no es veuen afectats, perquè vénen del node i no d'un altre pod; per això pots continuar depurant amb un clúster totalment tancat.

  1. Pas 2: permetre el DNS (o trencar-ho tot)

Aquesta és la fallada clàssica de les NetworkPolicies, i mereix apartat propi perquè el seu símptoma despista moltíssim. Amb deny-all actiu cap pod no pot parlar amb CoreDNS, i als logs d'api-reserves es veu això:

Error: getaddrinfo EAI_AGAIN postgres-reserves
Error: getaddrinfo EAI_AGAIN pagos.proveedorexterno.example

El símptoma no és "connexió rebutjada": és una fallada de resolució de noms després de diversos segons d'espera, la fallada tipus 1 de 04-03. La confusió típica és investigar CoreDNS, que està perfectament sa, en lloc de mirar la política d'egress acabada d'aplicar. Agreujant: un deny-all d'egress a kube-system deixa sense DNS tot el clúster; no apliquis mai polítiques àmplies als namespaces del sistema.

# k8s/entorns/pro/np-01-allow-dns.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-dns-egress, namespace: rutas-norte-pro }
spec:
  podSelector: {}                    # tots els pods del namespace necessiten DNS
  policyTypes: [Egress]
  egress:
    - to:
        - namespaceSelector:
            matchLabels: { kubernetes.io/metadata.name: kube-system }
          podSelector:               # SENSE guionet: I logic -> CoreDNS DINS de kube-system
            matchLabels: { k8s-app: kube-dns }
      ports:
        - { protocol: UDP, port: 53 }
        - { protocol: TCP, port: 53 }   # imprescindible: respostes grans, NodeLocal DNSCache

Tres detalls que cal respectar:

  1. podSelector: {}: el DNS el necessiten tots els pods, sense excepció. És la primera regla que s'escriu sempre.
  2. UDP i TCP al 53. El TCP s'oblida constantment. El DNS cau a TCP quan la resposta no cap en un paquet UDP, i en un Service headless amb molts pods això passa: el símptoma és demolidor, funciona gairebé sempre i falla de manera aparentment aleatòria.
  3. namespaceSelector + podSelector sense guionet, perquè sigui la intersecció: CoreDNS dins de kube-system. Amb guionet obriries tot kube-system. Verifica a més l'etiqueta real d'aquells pods (kubectl get pods -n kube-system --show-labels | grep dns); en algunes distribucions és k8s-app: coredns.
kubectl apply -f k8s/entorns/pro/np-01-allow-dns.yaml
kubectl exec -n rutas-norte-pro deploy/api-reserves -- nslookup postgres-reserves
# resol correctament
kubectl exec -n rutas-norte-pro deploy/api-reserves -- timeout 5 nc -zv postgres-reserves 5432
# continua DENEGAT: resoldre no es connectar ([04-03](04-03-dns-intern-i-descobriment-de-serveis))

  1. Pas 3: les converses de la plataforma

Enumerem, una a una, les connexions legítimes de Rutas Norte:

Origen Destinació Port Motiu
api-reserves postgres-reserves 5432 Consultar disponibilitat i crear reserves
worker-notificacions postgres-reserves 5432 Llegir les dades de la reserva per al correu
api-reserves redis-cache 6379 Memòria cau de disponibilitat de places
Controlador d'Ingress botiga-web 8080 Trànsit públic de la botiga
Controlador d'Ingress api-reserves 3000 Trànsit públic de l'API
api-reserves Passarel·la de pagaments 443 Cobrar els bitllets
Tots CoreDNS 53 Ja autoritzat

Res més: botiga-web no parla amb la base de dades (serveix una SPA estàtica), redis-cache no inicia connexions i postgres-reserves no surt enlloc.

La base de dades: només dos clients

# k8s/entorns/pro/np-02-postgres.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: postgres-reserves-ingress, namespace: rutas-norte-pro }
spec:
  podSelector:
    matchLabels: { app: postgres-reserves }   # nomes app i entorn als selectors
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector: { matchLabels: { app: api-reserves } }           # guionet 1
        - podSelector: { matchLabels: { app: worker-notificacions } }   # guionet 2 (O)
      ports:
        - { protocol: TCP, port: 5432 }
---
# k8s/entorns/pro/np-03-api-reserves-egress.yaml -- i ara el costat de l'EMISSOR,
# que es la meitat que s'oblida: sense aquest egress, la connexio tampoc s'estableix
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: api-reserves-egress, namespace: rutas-norte-pro }
spec:
  podSelector:
    matchLabels: { app: api-reserves }
  policyTypes: [Egress]
  egress:
    - to: [ { podSelector: { matchLabels: { app: postgres-reserves } } } ]
      ports: [ { protocol: TCP, port: 5432 } ]
    - to: [ { podSelector: { matchLabels: { app: redis-cache } } } ]
      ports: [ { protocol: TCP, port: 6379 } ]

La primera és la política que justifica tota la lliçó: cap altre pod del clúster no pot ja intentar obrir una connexió a la base de dades amb les dades personals. I recorda que allow-dns-egress i la segona se sumen: api-reserves surt al DNS, a PostgreSQL i a Redis, i a res més. L'egress de worker-notificacions és anàleg, amb PostgreSQL i el correu corporatiu.

  1. Pas 4: l'Ingress i la passarel·la de pagaments externa

Des del controlador d'Ingress

El controlador viu a ingress-nginx, així que necessitem namespaceSelector:

# k8s/entorns/pro/np-04-des-de-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-ingress-controller, namespace: rutas-norte-pro }
spec:
  podSelector:
    matchExpressions:
      - { key: app, operator: In, values: ["botiga-web", "api-reserves"] }
  policyTypes: [Ingress]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels: { kubernetes.io/metadata.name: ingress-nginx }
      ports:
        - { protocol: TCP, port: 8080 }   # targetPort de botiga-web
        - { protocol: TCP, port: 3000 }   # targetPort d'api-reserves

Dos detalls: els ports són els del contenidor (targetPort), no els del Service, perquè les polítiques veuen el trànsit real que arriba al pod i kube-proxy ja va traduir el port al node d'origen (04-01) —posar el 80 en lloc del 8080 produeix un 503 a l'Ingress que costa molt de relacionar amb una política—; i matchExpressions de 02-07 resol l'"un o l'altre" al podSelector.

Cap a la passarel·la de pagaments

És fora del clúster, a pagos.proveedorexterno.example: els selectors no serveixen, cal fer servir ipBlock.

# k8s/entorns/pro/np-05-passarella-pagaments.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: api-reserves-egress-pagaments, namespace: rutas-norte-pro }
spec:
  podSelector:
    matchLabels: { app: api-reserves }
  policyTypes: [Egress]
  egress:
    - to:
        - ipBlock:
            cidr: 198.51.100.0/24     # rang publicat pel proveidor de pagaments
      ports:
        - { protocol: TCP, port: 443 }

Punts delicats: un ExternalName no ajuda aquí, perquè el Service passarella-pagaments de 04-02 només crea un CNAME i el trànsit real va a una IP externa que cal autoritzar per ipBlock; les polítiques treballen amb IP, no amb noms, així que si el proveïdor canvia de rang la connexió es trenca sense avís —fes servir el rang que publica i documenta, no la IP que retorni un dig avui—; el DNS ja està permès per la política del pas 2, sense la qual api-reserves ni podria resoldre el nom; i si no hi ha rangs estables, l'alternativa és un proxy de sortida amb llista blanca per domini, o Cilium, amb polítiques FQDN.

El resultat complet

flowchart LR
    NET["Internet"] --> IC["ingress-nginx"]
    IC -->|":8080"| TW["botiga-web"]
    IC -->|":3000"| API["api-reserves"]
    API -->|":5432"| PG["postgres-reserves"]
    API -->|":6379"| RC["redis-cache"]
    API -->|":443 ipBlock"| PAY["pagos.proveedorexterno.example"]
    WK["worker-notificacions"] -->|":5432"| PG
    ALL["Tots els pods"] -->|":53 UDP/TCP"| DNS["CoreDNS (kube-system)"]
    X["Qualsevol altre pod"] -.->|"DENEGAT"| PG

Tot el que no apareix en aquell diagrama està denegat.

  1. Verificació sistemàtica

Una política sense verificar és una suposició: cal comprovar les dues cares.

#!/bin/bash
# verificar-politiques.sh -- comprovacio de rutas-norte-pro
NS=rutas-norte-pro
provar() {   # provar <descripcio> <deploy> <desti> <port> <esperat:OK|KO>
  RES=$(kubectl exec -n $NS deploy/$2 -- timeout 5 nc -z $3 $4 2>&1 && echo OK || echo KO)
  [ "$RES" = "$5" ] && echo "  PASSA  $1" || echo "  FALLA  $1 (esperat $5)"
}
# Ha de FUNCIONAR
provar "api-reserves -> postgres" api-reserves         postgres-reserves 5432 OK
provar "api-reserves -> redis"    api-reserves         redis-cache       6379 OK
provar "worker -> postgres"       worker-notificacions postgres-reserves 5432 OK
# Ha de FALLAR
provar "botiga-web -> postgres"   botiga-web           postgres-reserves 5432 KO
provar "worker -> redis"          worker-notificacions redis-cache       6379 KO
provar "botiga-web -> redis"      botiga-web           redis-cache       6379 KO

I la prova que dona sentit a tot, amb el pod intrús de l'apartat 1:

kubectl run intrus --rm -it --restart=Never -n rutas-norte-pro \
  --image=nicolaka/netshoot -- timeout 5 nc -zv postgres-reserves 5432
# nc: connect to postgres-reserves port 5432 (tcp) timed out: Operation now in progress

Temps d'espera esgotat. El forat que arrossegàvem des del mòdul 3 està tancat. Fixa't en el detall: és temps esgotat, no "connexió rebutjada", perquè les NetworkPolicies descarten els paquets en silenci, sense enviar un RST. Aquesta signatura serveix per diagnosticar:

Símptoma Causa probable
connection refused (immediat) Hi ha connectivitat; el procés no escolta en aquell port
timed out (5-30 s) NetworkPolicy descartant, o un problema de xarxa
EAI_AGAIN / no such host DNS bloquejat o caigut
503 a l'Ingress Endpoints buits, o política bloquejant el controlador

  1. Limitacions reals

Són imprescindibles i també clarament limitades. Cal saber què no fan.

Limitació Detall Què ho cobreix
Operen en L3/L4 Només IP, port i protocol: no entenen HTTP, rutes, mètodes ni capçaleres Cilium (L7) o una malla
Sense identitat criptogràfica La "identitat" és una etiqueta: qui pugui crear pods amb ella suplanta el component mTLS amb una malla (08-04)
No registren denegacions No hi ha log de paquets descartats; es depura a base de temps d'espera Hubble (Cilium), fluxos de Calico
Sense "denegar" explícit Només se suma permís, no hi caben excepcions restrictives Polítiques pròpies de Calico (order, Deny)
Sense DNS/FQDN ni xifratge ipBlock amb IP que canvien sense avisar; restringeixen qui parla, no protegeixen el contingut FQDN de Cilium, proxy de sortida, WireGuard
No s'apliquen a hostNetwork Un pod amb xarxa d'amfitrió escapa del model Pod Security Standards (08-03)

El que aportarien Cilium o una malla a Rutas Norte: permetre només GET /disponibilitat i POST /reserves en lloc de "el port 3000 sencer"; identitat basada en certificats, de manera que crear un pod amb app: api-reserves no n'hi hagi prou per suplantar el component; visibilitat de fluxos permesos i denegats, que converteix "això es queda penjat" en "aquesta política concreta ho va descartar"; i polítiques per nom de domini per a la passarel·la de pagaments.

Res d'això treu valor al que hem construït: les NetworkPolicies són la capa base i la que exigeix qualsevol auditoria. L'estratègia completa s'estudia a 08-04.

Errors Comuns i Consells

  • Escriure polítiques amb un CNI que les ignora. S'apliquen sense error i no protegeixen res. Verifica-ho sempre amb una prova real, no amb el nom del plugin.
  • Oblidar el DNS. El primer deny-all d'egress trenca la resolució a tot el namespace i el símptoma (EAI_AGAIN) no apunta a la política: la regla de DNS és sempre la primera que s'escriu. I permetre només UDP al 53 falla de manera intermitent: autoritza UDP i TCP.
  • El guionet de més a from. Converteix un I en un O i obre namespaces sencers. Rellegeix-ho a cada revisió de codi.
  • Autoritzar només una direcció (calen l'egress de l'emissor i l'ingress del receptor), o fer servir el port del Service en lloc del targetPort: les polítiques veuen el port del contenidor.
  • Aplicar deny-all a kube-system. Pots deixar sense DNS, sense mètriques i sense Ingress tot el clúster.
  • Esperar un "denegar" explícit (no existeix: per restringir cal treure el permís ampli) o confondre timed out amb connection refused: el primer fa olor de política, el segon de procés caigut.
  • Consell: aplica les polítiques primer a rutas-norte-dev, verifica l'script de comprovació i només llavors promociona a pre i pro. Un deny-all mal calculat en producció és una caiguda total.
  • Consell: anomena els fitxers amb prefix numèric (np-00-deny-all, np-01-allow-dns, ...) perquè l'ordre de lectura reflecteixi l'ordre de construcció.
  • Consell: documenta cada política amb la conversa de negoci que autoritza. "worker-notificacions llegeix la reserva per enviar el correu" envelleix molt millor que "permet 5432".

Exercicis

Exercici 1: Verificar que el CNI aplica les polítiques

Determina quin CNI hi ha instal·lat i demostra amb una prova pràctica —no amb el nom del plugin— si les NetworkPolicies s'apliquen. Si no, recrea el perfil amb Calico i repeteix.

Exercici 2: Construir el model complet de rutas-norte-pro

Aplica en ordre deny-all, la regla de DNS i les polítiques dels apartats 9 i 10, documentant després de cada pas què funciona i què deixa de funcionar. En acabar, executa l'script de verificació i comprova que el pod intrus ja no arriba a la base de dades.

Exercici 3: Detectar l'error d'I davant d'O en una revisió

T'arriba aquesta política per revisar. Identifica la fallada, explica què permet de més i corregeix-la.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: redis-nomes-api, namespace: rutas-norte-pro }
spec:
  podSelector:
    matchLabels: { app: redis-cache }
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector:
            matchLabels: { app: api-reserves }
        - namespaceSelector:
            matchLabels: { entorn: pro }
      ports:
        - { protocol: TCP, port: 6379 }

Solucions

Exercici 1

kubectl get pods -n kube-system -o name | grep -Ei "flannel|calico|cilium|weave"

kubectl create ns prova-np
kubectl run diana --image=nginx -n prova-np --labels=app=diana
kubectl expose pod diana --port=80 -n prova-np
kubectl wait --for=condition=ready pod diana -n prova-np --timeout=60s
kubectl apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: deny-all-ingress, namespace: prova-np }
spec: { podSelector: {}, policyTypes: [Ingress] }
EOF
kubectl run p --rm -it --restart=Never -n prova-np --image=nicolaka/netshoot \
  -- curl -s -o /dev/null --max-time 5 http://diana \
  && echo "EL CNI IGNORA LES POLITIQUES" || echo "EL CNI LES APLICA (correcte)"

kubectl delete ns prova-np

Amb Flannel el curl respondrà (les ignora); amb Calico o Cilium esgotarà el temps. Si cal reconstruir, minikube delete -p rutas-norte i recrear el perfil amb --cni=calico.

Exercici 2

Pas Funciona després d'aplicar-lo Continua sense funcionar
1. deny-all Només kubectl exec/logs i les sondes Tot el trànsit: DNS, base de dades, memòria cau, Ingress
2. allow-dns-egress Resolució de noms Totes les connexions
3. PostgreSQL i Redis api-reserves i worker arriben a les seves dependències L'entrada des de l'Ingress
4. Ingress i pagaments La plataforma completa, inclosos els cobraments Tot allò no autoritzat
for f in np-00-deny-all np-01-allow-dns np-02-postgres \
         np-03-api-reserves-egress np-04-des-de-ingress np-05-passarella-pagaments; do
  kubectl apply -f k8s/entorns/pro/$f.yaml
  kubectl exec -n rutas-norte-pro deploy/api-reserves -- \
    timeout 5 nc -z postgres-reserves 5432 && echo "$f: postgres OK" || echo "$f: postgres KO"
done

bash verificar-politiques.sh
kubectl run intrus --rm -it --restart=Never -n rutas-norte-pro \
  --image=nicolaka/netshoot -- timeout 5 nc -zv postgres-reserves 5432
# nc: connect to postgres-reserves port 5432 (tcp) timed out

Exercici 3

La fallada són els dos guionets a from: el podSelector i el namespaceSelector són elements diferents de la llista, així que es combinen amb O. El que permet de més és enorme: qualsevol pod, de qualsevol namespace etiquetat entorn: pro, pot connectar a redis-cache:6379. La intenció era "només api-reserves" i el resultat és "tot l'entorn de producció"; un pod compromès en qualsevol namespace de producció podria llegir i enverinar la memòria cau de disponibilitat de places.

Correcció: unir tots dos selectors en un sol element (un I), o simplement deixar el podSelector, atès que la política ja viu a rutas-norte-pro.

  ingress:
    - from:
        - podSelector:
            matchLabels: { app: api-reserves }
          namespaceSelector:            # SENSE guionet: interseccio
            matchLabels: { entorn: pro }
      ports:
        - { protocol: TCP, port: 6379 }
kubectl exec -n rutas-norte-pro deploy/api-reserves -- timeout 5 nc -z redis-cache 6379 \
  && echo "api-reserves: OK (ha de funcionar)"
kubectl exec -n rutas-norte-pro deploy/botiga-web -- timeout 5 nc -z redis-cache 6379 \
  || echo "botiga-web: DENEGAT (correcte)"

Conclusió

Has tancat el forat: vas començar demostrant amb un pod intrús que sense NetworkPolicies la xarxa és plana i que qualsevol contenidor del clúster arriba a la base de dades amb les dades personals dels clients, i has acabat amb aquella mateixa prova esgotant el temps d'espera. Pel camí has fixat el que més importa. Primer, que Kubernetes defineix l'objecte però no l'aplica: ho fa el CNI, i un plugin com Flannel accepta les teves polítiques sense una sola advertència i no filtra res, per la qual cosa l'única garantia acceptable és una prova pràctica a cada clúster. Segon, el model additiu: mentre cap política no seleccioni un pod, tot passa; tan bon punt una ho fa, aquell pod queda aïllat en aquella direcció i només passa allò explícitament permès; les polítiques se sumen, no hi ha prioritats i no existeix denegar, cosa que significa que no hi caben excepcions restrictives. Tercer, que calen les dues direccions: l'egress de l'emissor i l'ingress del receptor. I domines els tres selectors —podSelector per al mateix namespace, namespaceSelector per creuar-los i ipBlock amb except per a allò extern— i sobretot la diferència entre I i O, aquell guionet de més que converteix "només api-reserves" en "tot l'entorn de producció" sense que el clúster digui res.

Has construït el model complet de rutas-norte-pro en l'ordre correcte: deny-all primer, encara que trenqui la plataforma; DNS després, amb UDP i TCP cap a CoreDNS, evitant la fallada clàssica el símptoma de la qual (EAI_AGAIN) apunta a tot arreu menys a la política que acabes d'aplicar; i després cada conversa de negoci autoritzada una a una: api-reserves i worker-notificacions cap a PostgreSQL, api-reserves cap a Redis, el controlador d'Ingress cap a la botiga i l'API pels seus targetPort, i api-reserves cap a la passarel·la de pagaments per ipBlock. Tot verificat per totes dues cares, sabent que un timed out fa olor de política i un connection refused de procés caigut. I coneixes els límits: L3/L4, sense identitat criptogràfica, sense registre de denegacions i sense noms de domini, allà on entren Cilium o una malla de servei (08-04).

Amb això acaba el mòdul 4 i Rutas Norte és per primera vegada una plataforma real i accessible. Els clients entren per https://www.rutasnorte.example, les agències consumeixen https://api.rutasnorte.example, els certificats es renoven sols, el descobriment entre components va per DNS i la xarxa està segmentada de manera que només les converses legítimes són possibles.

Queda una peça que arrosseguem des del mòdul 2 amb una nota de "PROVISIONAL" al manifest: postgres-reserves no té emmagatzematge persistent. El seu Deployment desa les dades dins del contenidor, així que cada reinici del pod —un rollout, un desallotjament, la caiguda d'un node— esborra totes les reserves i tots els clients. El mòdul 5, Emmagatzematge a Kubernetes, resol exactament això: volums, PersistentVolumes i PersistentVolumeClaims, classes d'emmagatzematge, aprovisionament dinàmic amb expansió i snapshots, i còpies de seguretat i restauració. És el mòdul que converteix Rutas Norte en una plataforma en què es pot confiar.

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