La lliçó anterior va acabar amb un problema que ni Helm ni Kustomize resolen. Totes dues eines només actuen quan algú executa una ordre. Si un company fa kubectl edit a rutas-norte-pro a les tres de la matinada per sortir d'un compromís, cap se n'assabenta. Si el portàtil de qui desplega s'espatlla, ningú sap desplegar. I si la canalització ci-rutasnorte necessita credencials d'administrador del clúster per executar kubectl apply, hem obert un forat de seguretat que contradiu el RBAC mínim que tant vam cuidar a 08-01.

GitOps és la resposta, i és la conclusió natural de tot el que portem de curs. La idea és senzilla d'enunciar i transformadora a la pràctica: un agent que viu dins del clúster, mira un repositori de Git de forma contínua, i s'encarrega que el clúster s'assembli al que diu aquest repositori. Sempre. Sense que ningú executi res.

En aquesta lliçó muntarem aquest sistema per a Rutas Norte amb Argo CD, veurem el seu equivalent a Flux, resoldrem per fi el conflicte entre l'HPA i el camp replicas anunciat a 09-01, i tancarem el problema dels secrets al repositori que va quedar pendent des de 03-02.

Contingut

  1. Què és GitOps i els seus quatre principis
  2. Model d'enviament davant de model d'extracció
  3. Deriva de configuració i autocorrecció
  4. Argo CD: arquitectura i instal·lació
  5. El recurs Application camp a camp
  6. App of apps i ApplicationSet
  7. Onades de sincronització i hooks
  8. Estats de salut i sincronització
  9. Flux: els controladors i els seus recursos
  10. Argo CD davant de Flux
  11. El problema dels secrets
  12. Camps que canvien sols: ignoreDifferences
  13. Estructura de repositoris i promoció entre entorns
  14. Errors comuns i consells
  15. Exercicis
  16. Conclusió

  1. Què és GitOps i els seus quatre principis

GitOps és un model operatiu en què l'estat desitjat del sistema viu a Git i un agent automatitzat el fa realitat de forma contínua. El grup de treball OpenGitOps el va formalitzar en quatre principis:

  1. Declaratiu: tot el sistema es descriu pel que ha d'existir, no pels passos a executar. Ja ho tenim des de 01-06: Kubernetes és declaratiu per disseny.
  2. Versionat i immutable: l'estat desitjat s'emmagatzema de manera que l'historial no es pugui alterar. Git és la implementació òbvia. La pregunta "què va canviar en producció el dimarts?" passa de ser una investigació forense a git log.
  3. Aplicat automàticament: els canvis aprovats s'apliquen sense intervenció manual. Ningú executa kubectl apply ni helm upgrade: es fusiona una petició de canvi i el sistema convergeix.
  4. Reconciliat de forma contínua: i aquest és el que ho canvia tot. L'agent no es limita a aplicar quan hi ha un commit nou: comprova contínuament que l'estat real coincideix amb el desitjat i corregeix les diferències.
flowchart LR
    G[(Repositori<br/>de manifestos)] -->|l'agent observa| A[Agent GitOps<br/>al clúster]
    A -->|compara| K[(Estat real<br/>del clúster)]
    K -.->|si difereix| A
    A -->|corregeix| K
    style A fill:#e8f4ff

Aquest bucle no para mai. Cada tres minuts (configurable), l'agent es pregunta: "el clúster és com diu Git?". Si no ho és, actua.

Problema actual de Rutas Norte Com el resol GitOps
"Ningú sap quina versió hi ha en producció" El repositori, a la branca main, és la resposta
"S'aplica a mà des d'un portàtil" Ningú té ni necessita credencials de desplegament
"Els canvis del VPA es porten al manifest a mà i s'obliden" Si no és a Git, no existeix: es reverteix sol
"Qui va canviar això i per què?" git log, git blame, la PR amb la seva discussió
"S'ha perdut el clúster" Es recrea i l'agent el repobla

  1. Model d'enviament davant de model d'extracció

Aquesta és la diferència arquitectònica clau, i té implicacions de seguretat serioses.

flowchart LR
    subgraph push["Model d'ENVIAMENT (avui)"]
        D1[Desenvolupament] --> G1[(Git)] --> CI["ci-rutasnorte"]
        CI -->|"kubectl apply<br/>amb credencials"| K1[(rutas-norte-pro)]
    end
    subgraph pull["Model d'EXTRACCIÓ (GitOps)"]
        D2[Desenvolupament] --> G2[(Git)]
        CI2["ci-rutasnorte"] -->|"commit de<br/>l'etiqueta"| G2
        G2 -.->|"l'agent ESTIRA<br/>(només lectura)"| A2[Agent<br/>al clúster]
        A2 --> K2[(Objectes)]
    end
    style CI fill:#ffe8e8
    style A2 fill:#e8ffe8

Al model d'enviament, la canalització executa kubectl apply contra el clúster i per a això necessita un kubeconfig amb permisos. El problema, en detall:

  1. Aquest kubeconfig és un secret emmagatzemat fora del clúster, sovint gestionat per un altre equip o per un proveïdor extern.
  2. Per poder desplegar qualsevol cosa en qualsevol namespace, sol acabar tenint cluster-admin. És el més fàcil, i per això és el que hi ha a la majoria de les empreses.
  3. Qualsevol que pugui modificar la definició de la canalització —un fitxer YAML del repositori de codi— pot executar ordres arbitràries amb aquestes credencials. Una petició de canvi maliciosa a un fitxer de CI equival a accés d'administrador a producció.
  4. L'apiserver ha de ser abastable des de l'exterior.
  5. Les credencials cal rotar-les, auditar-les i revocar-les, i amb tres clústers, tres jocs.

El RBAC mínim que vam definir per a ci-rutasnorte a 08-01 mitiga el problema, però no l'elimina: la canalització continua tenint una credencial permanent cap a dins del clúster.

Aspecte Model d'enviament Model d'extracció
Qui té credencials del clúster El sistema de CI, permanentment Ningú fora del clúster
Direcció de la connexió CI → clúster (entrant) Clúster → Git (sortint)
L'apiserver accessible des de fora? No
Permisos que necessita CI Desplegar al clúster Només escriure en un repositori Git
Superfície d'atac si CI es compromet El clúster sencer Un repositori (revisable, revertible)
Detecció de canvis manuals Cap Contínua
Clústers en xarxes privades Complicat (túnels, VPN) Natural: només necessita sortida a Git

El canvi és profund: la credencial més perillosa desapareix. Si algú roba el token d'escriptura a Git, pot fer un commit... que quedarà registrat, serà revisable i es podrà revertir.

  1. Deriva de configuració i autocorrecció

La deriva de configuració és la diferència entre el que diuen els teus fitxers i el que hi ha realment al sistema. S'acumula sense que ningú se n'adoni. Casos reals de Rutas Norte:

  • Durant un incident del pont de maig, algú va fer kubectl scale deploy/api-reserves --replicas=12. Va funcionar. Ningú ho va portar al manifest. Tres setmanes després, un desplegament rutinari va tornar el Deployment a 4 rèpliques i el servei es va saturar.
  • Es va canviar un límit de memòria amb kubectl edit per provar una hipòtesi. Va quedar així sis mesos.
  • Es va afegir una anotació a l'Ingress per ajustar un temps d'espera. Quan es va recrear el clúster de pre, va desaparèixer i ningú va saber per què el comportament va canviar.

Amb GitOps, kubectl scale deploy/api-reserves --replicas=12 en producció té un altre desenllaç. Tres minuts després:

level=info msg="Detected out-of-sync resource" application=rutas-norte-pro
  kind=Deployment name=api-reserves
level=info msg="Initiating self-heal"
level=info msg="Sync successful"

Ha tornat a 4. I això és exactament el que volem. No és que la màquina sigui tossuda: és que si 12 rèpliques era la decisió correcta, ha d'estar a Git, on queda revisada, documentada i present la propera vegada que es recreï l'entorn.

El canvi cultural que això provoca és més important que la tecnologia: l'única forma de canviar producció és a través d'una petició de canvi. Al principi genera fricció; al cap de dos mesos, ningú vol tornar enrere.

L'autocorrecció és una decisió, no una obligació. Argo CD permet activar selfHeal per aplicació. A rutas-norte-dev pot convenir deixar-lo desactivat perquè la gent experimenti; a rutas-norte-pro ha d'estar activat.

  1. Argo CD: arquitectura i instal·lació

flowchart TB
    U["Usuari<br/>(web / CLI)"] --> API["**Servidor d'API**<br/>autenticació, RBAC,<br/>interfície web"]
    API --> REPO["**Servidor de repositoris**<br/>clona Git, executa<br/>kustomize build /<br/>helm template, cacheja"]
    API --> CTRL["**Controlador d'aplicacions**<br/>compara desitjat vs real,<br/>sincronitza, avalua salut"]
    REPO -.-> G[(Repositori Git)]
    CTRL --> K8S[(API de Kubernetes)]
    CTRL --> REPO
Component Responsabilitat Símptoma quan falla
argocd-server Interfície web, API, autenticació, RBAC d'Argo CD No entres a la web, però les aplicacions continuen sincronitzant-se
argocd-repo-server Clona Git i renderitza els manifestos "failed to generate manifests"; res no se sincronitza
argocd-application-controller El bucle de reconciliació Les aplicacions es queden congelades
Redis Memòria cau de manifestos i estat Lentitud; cal tornar a renderitzar-ho tot
ApplicationSet controller Genera Applications des de plantilles Els ApplicationSet no produeixen res

Detall clau: el controlador d'aplicacions és l'únic que necessita permisos sobre els objectes del clúster. El servidor d'API només gestiona recursos d'Argo CD. Aquesta separació importa per al RBAC.

La instal·lació coherent amb el que hem après és per Helm (10-03), amb valors versionats:

helm repo add argo https://argoproj.github.io/argo-helm
helm upgrade --install argocd argo/argo-cd -n argocd --create-namespace \
  --version 7.6.12 -f plataforma/argocd/valors-pro.yaml --atomic --timeout 10m
# plataforma/argocd/valors-pro.yaml
global: { domain: argocd.rutasnorte.example }
configs:
  params: { server.insecure: true }      # el TLS el termina l'Ingress
  cm: { timeout.reconciliation: 180s }
redis-ha: { enabled: true }
controller:
  replicas: 2
  resources: { requests: {cpu: 250m, memory: 1Gi}, limits: {memory: 2Gi} }
repoServer: { replicas: 2 }
server:
  replicas: 2
  ingress:
    enabled: true
    ingressClassName: nginx
    hostname: argocd.rutasnorte.example
    annotations: { cert-manager.io/cluster-issuer: letsencrypt-produccio }
    tls: true
# Contrasenya inicial, canviar-la i esborrar el Secret
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
argocd login argocd.rutasnorte.example --username admin
argocd account update-password
kubectl -n argocd delete secret argocd-initial-admin-secret

En producció, l'usuari admin es desactiva i es fa servir un proveïdor d'identitat (OIDC, SSO corporatiu). El RBAC d'Argo CD es configura a part del de Kubernetes, al ConfigMap argocd-rbac-cm:

p, role:desenvolupament, applications, get,  */*, allow
p, role:desenvolupament, applications, sync, rutas-norte/rutas-norte-dev, allow
p, role:plataforma, applications, *, */*, allow
p, role:suport,     applications, get, */*, allow
g, rutasnorte:desenvolupament, role:desenvolupament
g, rutasnorte:plataforma, role:plataforma
g, rutasnorte:suport,     role:suport
policy.default: role:readonly

I es registra el repositori amb una credencial de només lectura: Argo CD mai escriu al repositori de manifestos. És el privilegi mínim de 08-01 aplicat aquí.

argocd repo add [email protected]:plataforma/k8s.git \
  --ssh-private-key-path ~/.ssh/argocd-rutasnorte-lectura

  1. El recurs Application camp a camp

Application és un CRD (06-06). Cada instància diu: "d'aquest repositori, aquesta ruta, a aquest clúster i namespace, amb aquesta política".

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: rutas-norte-pro
  namespace: argocd
  # El finalitzador fa que esborrar l'Application esborri també els objectes
  # que gestionava. SENSE ell, queden tots orfes al clúster.
  finalizers: [resources-finalizer.argocd.argoproj.io]
spec:
  # Projecte: agrupa aplicacions i restringeix què poden desplegar i on
  project: rutas-norte

  source:
    repoURL: [email protected]:plataforma/k8s.git
    # Apuntem a la superposició de Kustomize que vam construir a 10-04
    path: k8s/entorns/pro
    # Per a producció, una ETIQUETA o un commit fix dona més control
    # que 'main', que desplega qualsevol cosa que es fusioni.
    targetRevision: main
    kustomize:
      commonAnnotations: { rutasnorte.example/desplegat-per: argocd }

  destination:
    # 'kubernetes.default.svc' és el mateix clúster on corre Argo CD
    server: https://kubernetes.default.svc
    namespace: rutas-norte-pro

  syncPolicy:
    automated:
      prune: true      # esborra del clúster el que ja no és a Git
      selfHeal: true   # reverteix els canvis manuals: L'AUTOCORRECCIÓ
      # allowEmpty a false protegeix d'un error de ruta que ho esborraria tot
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true          # (01-06) i necessari per a CRDs grans
      - RespectIgnoreDifferences=true
      - PruneLast=true
    retry:
      limit: 5
      backoff: { duration: 10s, factor: 2, maxDuration: 5m }

  ignoreDifferences:                  # veure l'apartat 12
    - group: apps
      kind: Deployment
      jsonPointers: ["/spec/replicas"]

  revisionHistoryLimit: 20

L'AppProject

Un AppProject restringeix què pot fer un grup d'aplicacions. És una capa de seguretat essencial quan diversos equips comparteixen Argo CD.

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata: { name: rutas-norte, namespace: argocd }
spec:
  # Només des del NOSTRE repositori: ningú pot apuntar a un d'extern
  sourceRepos: ["[email protected]:plataforma/k8s.git"]
  # Només als NOSTRES namespaces: ningú pot desplegar a kube-system
  destinations:
    - { server: https://kubernetes.default.svc, namespace: rutas-norte-dev }
    - { server: https://kubernetes.default.svc, namespace: rutas-norte-pre }
    - { server: https://kubernetes.default.svc, namespace: rutas-norte-pro }
  # Prohibit crear objectes d'àmbit de clúster
  clusterResourceWhitelist: []
  namespaceResourceWhitelist: [{ group: '*', kind: '*' }]
  namespaceResourceBlacklist:
    - { group: '', kind: ResourceQuota }
    - { group: '', kind: LimitRange }
  # Finestra de desplegament: res en producció els divendres a la tarda
  syncWindows:
    - kind: deny
      schedule: "0 15 * * 5"
      duration: 9h
      applications: [rutas-norte-pro]
      manualSync: true      # es pot forçar si hi ha una urgència

Aquest clusterResourceWhitelist: [] és important: significa que ni un error en un manifest pot crear un ClusterRole o una StorageClass. Sense ell, qui pugui escriure al repositori pot escalar privilegis al clúster.

Origen amb Helm

spec:
  source:
    repoURL: https://charts.jetstack.io
    chart: cert-manager
    targetRevision: v1.16.1            # versió del CHART
    helm:
      releaseName: cert-manager
      valuesObject:
        crds: { enabled: true, keep: true }
        replicaCount: 2
  syncPolicy:
    syncOptions: [CreateNamespace=true, ServerSideApply=true]

Argo CD executa internament helm template, així que no crea releases de Helm: no veuràs res a helm list. L'estat el porta Argo CD, no Helm. És coherent amb GitOps (l'estat és a Git), però sorprèn la primera vegada.

Un patró molt útil és el de múltiples orígens: el chart ve d'un repositori públic i els valors del repositori de l'empresa, referenciat amb ref: valors i valueFiles: [$valors/plataforma/.../valors-pro.yaml].

  1. App of apps i ApplicationSet

Amb vint aplicacions, crear cada Application a mà i aplicar-la amb kubectl ens torna al problema original. La solució: una Application que gestiona un directori ple d'Applications.

k8s-gitops/
├── arrel/kustomization.yaml        <- l'Application arrel apunta aquí
├── aplicacions/                    <- rutas-norte-{dev,pre,pro}.yaml,
│                                      cert-manager.yaml, ingress-nginx.yaml,
│                                      kube-prometheus-stack.yaml, keda.yaml...
└── projectes/                      <- rutas-norte.yaml, plataforma.yaml
# L'ÚNICA Application que s'aplica a mà, una sola vegada a la vida
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: arrel
  namespace: argocd
  finalizers: [resources-finalizer.argocd.argoproj.io]
spec:
  project: default
  source:
    repoURL: [email protected]:plataforma/k8s-gitops.git
    path: arrel
    targetRevision: main
  destination: { server: https://kubernetes.default.svc, namespace: argocd }
  syncPolicy:
    automated: { prune: true, selfHeal: true }
kubectl apply -f arrel-application.yaml

Aquesta és l'última vegada que algú executa kubectl apply a Rutas Norte. A partir d'aquí, afegir una aplicació nova és afegir un fitxer al directori aplicacions/ i fusionar la petició de canvi.

ApplicationSet: els tres entorns des d'una sola definició

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata: { name: rutas-norte-entorns, namespace: argocd }
spec:
  generators:
    # Generador de LLISTA: el més explícit i llegible
    - list:
        elements:
          - { entorn: dev, namespace: rutas-norte-dev, revision: main,
              selfHeal: "false" }          # a dev deixem experimentar
          - { entorn: pre, namespace: rutas-norte-pre, revision: main,
              selfHeal: "true" }
          - { entorn: pro, namespace: rutas-norte-pro, revision: release-2.4,
              selfHeal: "true" }           # producció va per etiqueta
  template:
    metadata:
      name: 'rutas-norte-{{.entorn}}'
      labels: { entorn: '{{.entorn}}' }
      finalizers: [resources-finalizer.argocd.argoproj.io]
    spec:
      project: rutas-norte
      source:
        repoURL: [email protected]:plataforma/k8s.git
        path: 'k8s/entorns/{{.entorn}}'
        targetRevision: '{{.revision}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{.namespace}}'
      syncPolicy:
        automated: { prune: true, selfHeal: '{{.selfHeal}}' }
        syncOptions: [CreateNamespace=true, ServerSideApply=true]
      ignoreDifferences:
        - { group: apps, kind: Deployment, jsonPointers: ["/spec/replicas"] }

Una definició, tres aplicacions. Hi ha més generadors, i alguns obren possibilitats notables:

Generador Què fa
list Elements explícits, com a dalt
git.directories Una Application per subdirectori: afegir un entorn = crear una carpeta
git.files Llegeix fitxers de configuració del repositori i fa servir el seu contingut com a paràmetres
clusters Una Application per clúster registrat que casi amb l'etiqueta (base del multi-clúster, 11-05)
matrix Producte cartesià: 3 entorns × 4 clústers = 12 Applications
pullRequest Una Application efímera per cada PR oberta: entorns de vista prèvia automàtics

Aquest últim és espectacular a la pràctica: obres una PR amb l'etiqueta vista-previa i apareix un entorn complet desplegat; la tanques i desapareix sola.

  1. Onades de sincronització i hooks

Argo CD aplica els objectes en un ordre per tipus (Namespace, CRDs, ConfigMaps, Secrets i al final les càrregues). Quan necessites un ordre propi, fas servir onades: argocd.argoproj.io/sync-wave: "-5". Van de menor a major, i Argo CD espera que tots els objectes d'una onada estiguin sans abans de passar a la següent.

Onada Objectes Per què en aquest moment
-10 Namespace, ResourceQuota, LimitRange Tota la resta viu a dins
-5 ServiceAccount, Role, RoleBinding, NetworkPolicy Els pods els necessiten en arrencar
-3 ExternalSecret, ConfigMap La configuració abans que els pods
-1 Job de migració d'esquema Abans del codi nou
0 postgres-reserves, redis-cache L'API en depèn
1 api-reserves, worker-notificacions Depenen de la base de dades
2 botiga-web Depèn de l'API
3 Service, Ingress Publicar només quan tot estigui llest
5 HPA, ScaledObject, PDB Quan l'objectiu ja existeix

Els hooks són l'equivalent dels de Helm (10-03): PreSync, Sync, PostSync, SyncFail i Skip.

apiVersion: batch/v1
kind: Job
metadata:
  name: migracio-esquema
  annotations:
    argocd.argoproj.io/hook: PreSync           # abans d'aplicar res
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
    argocd.argoproj.io/sync-wave: "-1"
spec:
  backoffLimit: 2
  activeDeadlineSeconds: 900
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrador
          image: registry.rutasnorte.example/api-reserves-migracions:2.4.0
          command: ["/app/migrar", "--fins", "2.4.0"]
          env:
            - name: BD_PASSWORD
              valueFrom:
                secretKeyRef: { name: api-reserves-credencials, key: bd-password }

Les polítiques d'esborrat són HookSucceeded, HookFailed (no la facis servir: perds els logs) i BeforeHookCreation (recomanada). I un PostSync amb un curl --fail a /salut/preparat és una prova de fum excel·lent: si falla, l'aplicació queda Degraded i Argo CD t'avisa encara que els pods estiguin arrencats.

  1. Estats de salut i sincronització

Argo CD maneja dos estats independents. Confondre'ls és l'error conceptual més comú.

  • Estat de sincronització — el clúster coincideix amb Git?: Synced, OutOfSync, Unknown.
  • Estat de salut — l'aplicació funciona?: Healthy, Progressing, Degraded, Suspended, Missing, Unknown.

Són ortogonals. Les quatre combinacions importants:

Sync Health Què significa
Synced Healthy Tot perfecte
Synced Degraded Git diu la veritat, però l'aplicació està trencada. Problema de l'aplicació, no del desplegament: la imatge no existeix, la sonda falla, falten recursos
OutOfSync Healthy Funciona, però no és el que diu Git: deriva manual o commit pendent
OutOfSync Degraded La pitjor: ni coincideix ni funciona

Argo CD sap avaluar la salut dels tipus estàndard (Deployment, Service, Ingress, StatefulSet, Job, PVC). Per a CRDs s'escriuen avaluadors propis en Lua, al ConfigMap argocd-cm:

resource.customizations.health.keda.sh_ScaledObject: |
  hs = {}
  if obj.status ~= nil and obj.status.conditions ~= nil then
    for i, condition in ipairs(obj.status.conditions) do
      if condition.type == "Ready" and condition.status == "True" then
        hs.status = "Healthy"; return hs
      end
    end
  end
  hs.status = "Progressing"; return hs
argocd app get rutas-norte-pro
argocd app diff rutas-norte-pro                      # Git vs clúster
argocd app sync rutas-norte-pro --dry-run
argocd app sync rutas-norte-pro --resource apps:Deployment:api-reserves
argocd app history rutas-norte-pro
argocd app rollback rutas-norte-pro 12
argocd app wait rutas-norte-pro --health --timeout 600

Compte amb argocd app rollback: reverteix el clúster, però no reverteix Git. Amb selfHeal actiu, en tres minuts l'agent tornarà el clúster al que diu Git i el teu rollback desapareixerà. El rollback d'Argo CD és una mesura d'emergència; el rollback de veritat a GitOps és git revert seguit d'una petició de canvi, i Argo CD sincronitza sol.

  1. Flux: els controladors i els seus recursos

Flux té una filosofia diferent: en comptes d'una aplicació amb interfície web, és un conjunt de controladors especialitzats, cadascun amb el seu CRD.

flowchart TB
    SC["**source-controller**<br/>clona Git, descarrega charts"] --> KC["**kustomize-controller**<br/>construeix i aplica"]
    SC --> HC["**helm-controller**<br/>releases de Helm"]
    IRC["**image-reflector**<br/>escaneja registres"] --> IAC["**image-automation**<br/>fa commit de l'etiqueta"]
    KC --> K[(Clúster)]
    HC --> K
    IAC --> G[(Git)]
curl -s https://fluxcd.io/install.sh | sudo bash
flux check --pre
flux bootstrap git --url=ssh://[email protected]/plataforma/k8s-gitops.git \
  --branch=main --path=clusters/rutas-norte-pro --private-key-file=~/.ssh/flux

Aquest bootstrap és una decisió de disseny interessant: Flux s'instal·la a si mateix mitjançant GitOps. Escriu els seus propis manifestos al repositori i després es gestiona des d'allà. Actualitzar Flux és fer un commit.

apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata: { name: rutas-norte, namespace: flux-system }
spec:
  interval: 1m
  url: ssh://[email protected]/plataforma/k8s.git
  ref: { branch: main }
  secretRef: { name: flux-clau-ssh }
---
# Compte: aquesta Kustomization és de Flux, NO el kustomization.yaml de Kustomize
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata: { name: rutas-norte-pro, namespace: flux-system }
spec:
  interval: 5m
  path: ./k8s/entorns/pro
  prune: true                     # equival al prune d'Argo CD
  wait: true
  timeout: 10m
  sourceRef: { kind: GitRepository, name: rutas-norte }
  # DEPENDÈNCIES EXPLÍCITES: equivalen a les onades, però més clares
  dependsOn: [{ name: infraestructura }]
  healthChecks:
    - { apiVersion: apps/v1, kind: Deployment, name: api-reserves, namespace: rutas-norte-pro }
  postBuild:
    substitute: { entorn: pro }   # el més semblant a plantilles que té Flux

Per als charts de tercers hi ha HelmRepository i HelmRelease, i a diferència d'Argo CD, Flux sí que crea releases de Helm de veritat (apareixen a helm list), amb install.remediation i upgrade.remediation com a equivalent d'--atomic.

Automatització d'imatges

Capacitat que Flux té de sèrie i Argo CD només mitjançant un projecte a part:

apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata: { name: api-reserves, namespace: flux-system }
spec:
  imageRepositoryRef: { name: api-reserves }
  policy:
    semver: { range: "~2.4.0" }    # 2.4.0, 2.4.1... però MAI 2.5.0

I al manifest es marca quin camp cal actualitzar: newTag: 2.4.1 # {"$imagepolicy": "flux-system:api-reserves:tag"}. Flux detecta una etiqueta nova al registre, fa un commit a Git actualitzant-la, i el cicle GitOps normal la desplega. L'estat continua a Git: l'automatització escriu a Git, no al clúster.

flux get kustomizations
flux reconcile kustomization rutas-norte-pro --with-source
flux suspend / resume kustomization rutas-norte-pro
flux diff kustomization rutas-norte-pro --path ./k8s/entorns/pro

  1. Argo CD davant de Flux

Criteri Argo CD Flux
Interfície web Excel·lent: mapa visual, logs, diffs No en té (Weave GitOps com a afegit)
CLI Bona Excel·lent, pensada per treballar sense interfície
Model mental Una aplicació = un recurs Application Diversos controladors, cadascun amb el seu CRD
Corba d'aprenentatge Més suau: es veu el que passa Més abrupta: cal entendre la cadena
Multi-inquilí AppProject amb RBAC propi Namespaces + RBAC de Kubernetes
Helm helm template; no crea releases Releases reals, amb helm list
Automatització d'imatges Projecte a part (Image Updater) Integrada i madura
Multi-clúster Des d'un Argo CD central cap a N clústers Un Flux per clúster
Consum de recursos Més gran (web, Redis, API) Menor: només controladors
Dependències entre aplicacions Onades (anotacions) dependsOn (explícit)
Desplegament progressiu Argo Rollouts Flagger
Generació massiva ApplicationSet amb molts generadors Menys flexible

Tria Argo CD si vols que desenvolupament i suport puguin veure l'estat del desplegament sense aprendre kubectl (la interfície web és una eina de comunicació, no un caprici), gestiones diversos clústers des d'un punt central, o necessites la flexibilitat d'ApplicationSet.

Tria Flux si prefereixes una eina lleugera operada per CLI i per Git, vols l'automatització d'imatges de sèrie, necessites releases de Helm de veritat, o tens molts clústers petits i autònoms.

Per a Rutas Norte triem Argo CD, per dues raons concretes: l'equip de suport necessita veure l'estat dels desplegaments sense ser expert en Kubernetes, i ApplicationSet genera els tres entorns des d'una definició. Cap elecció és irreversible: totes dues llegeixen els mateixos manifestos de Kustomize i els mateixos charts de Helm, així que canviar afecta els fitxers de k8s-gitops/, no els de k8s/.

  1. El problema dels secrets

Aquí arriba l'objecció que apareix sempre: si tot va a Git, on poso la contrasenya de postgres-reserves?

A 03-02 vam dir que els Secrets estan codificats en base64, no xifrats, i que versionar-los requereix una eina específica. Ha arribat el moment. I recorda: un secret que ha estat a Git està compromès per sempre, encara que facis commit per esborrar-lo. L'historial el conserva, i qualsevol que hagi clonat el repositori el té.

Solució A: SOPS (xifratge al fitxer)

SOPS xifra els valors d'un YAML deixant les claus llegibles, fent servir una clau d'un KMS, age o PGP.

# .sops.yaml a l'arrel del repositori
creation_rules:
  - path_regex: k8s/entorns/pro/secrets.*\.yaml$
    age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
sops --encrypt --in-place k8s/entorns/pro/secrets.yaml
stringData:
  # La CLAU es llegeix; el VALOR està xifrat
  bd-password: ENC[AES256_GCM,data:8f2a91bc4d7e...,iv:3c1a...,tag:9e4f...,type:str]

Avantatge enorme: els diffs continuen sent llegibles, veus quina clau ha canviat encara que no el seu valor. Flux porta suport natiu (decryption: { provider: sops, secretRef: ... }); a Argo CD cal un plugin o ksops, cosa que és un punt en contra.

Solució B: Sealed Secrets (xifratge asimètric per clúster)

Un controlador al clúster genera un parell de claus. Tu xifres amb la pública; només aquest clúster pot desxifrar.

kubectl create secret generic api-reserves-credencials \
  --from-literal=bd-password='contrasenya-super-secreta' \
  --namespace=rutas-norte-pro --dry-run=client -o yaml > /tmp/en-clar.yaml
kubeseal --format yaml < /tmp/en-clar.yaml > k8s/entorns/pro/secret-segellat.yaml
rm /tmp/en-clar.yaml           # important!

El SealedSecret resultant, amb el seu encryptedData, és segur en un repositori públic. Contrapartides: el diff no diu res (tot el bloc canvia encara que canviïs una lletra), el secret està lligat a un namespace i nom concrets, i cal fer còpia de la clau mestra del controlador o perdràs la capacitat de desxifrar si recrees el clúster.

Solució C: External Secrets Operator (referència, sense xifratge)

La més neta conceptualment: el secret no és a Git de cap manera, ni xifrat. A Git només hi ha una referència.

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata: { name: api-reserves-credencials, namespace: rutas-norte-pro }
spec:
  # Tornar a comprovar cada hora: si el secret rota a Vault, el Secret
  # de Kubernetes s'actualitza sol.
  refreshInterval: 1h
  secretStoreRef: { name: vault-rutasnorte, kind: ClusterSecretStore }
  target: { name: api-reserves-credencials, creationPolicy: Owner }
  data:
    - secretKey: bd-password
      remoteRef: { key: rutas-norte/pro/postgres, property: password }
    - secretKey: passarella-token
      remoteRef: { key: rutas-norte/pro/pagaments, property: token }
Criteri SOPS Sealed Secrets External Secrets
On viu el secret Xifrat a Git Xifrat a Git Fora de Git
Diffs llegibles No Sí (no hi ha secret)
Rotació Tornar a xifrar i commit Tornar a segellar i commit Automàtica
Infraestructura extra Un KMS o clau age Un controlador Vault/KMS + operador
Risc si Git es filtra Baix Baix Nul
Desxifrar en diversos clústers No (un per clúster)
Integració Argo CD / Flux Regular / Nativa Bona / Bona Bona / Bona
Auditoria d'accessos No No

Recomanació per a Rutas Norte: External Secrets Operator amb Vault. La plataforma maneja dades personals de clients i credencials de la passarel·la de pagaments; la rotació automàtica i l'auditoria d'accessos no són luxes. Sealed Secrets és una alternativa raonable per començar sense infraestructura extra.

Regla que no canvia amb cap de les tres: si un secret ha arribat a Git en clar, cal rotar-lo. Sempre.

  1. Camps que canvien sols: ignoreDifferences

I arribem al problema que vam deixar anunciat a 09-01.

api-reserves té un HPA que ajusta spec.replicas entre 4 i 20 segons la càrrega. El manifest a Git diu replicas: 4. Quan arriba el pont de maig, l'HPA puja a 15. Sense res més:

14:22:11  Detected out-of-sync: Deployment/api-reserves
          Live: spec.replicas = 15  |  Desired: spec.replicas = 4
14:22:13  Sync successful

Argo CD ha baixat a 4 rèpliques en plena hora punta. Trenta segons després, l'HPA les torna a pujar. I al cap de tres minuts, Argo CD les baixa una altra vegada. És un bucle de guerra entre dos controladors, i qui pateix és el client que intenta comprar un bitllet.

La solució té dues parts. Primera: no declarar replicas al manifest, com ja vam fer a 10-04. Segona: ignoreDifferences, perquè encara que el camp no sigui a Git, Argo CD compara l'objecte viu amb el generat i el detecta igualment.

spec:
  ignoreDifferences:
    # 1. L'HPA governa les rèpliques
    - group: apps
      kind: Deployment
      jsonPointers: ["/spec/replicas"]

    # 2. El VPA modifica els recursos (09-02). jqPathExpressions és
    #    més expressiu que jsonPointers quan hi ha condicions.
    - group: apps
      kind: Deployment
      name: worker-notificacions
      jqPathExpressions:
        - '.spec.template.spec.containers[] | select(.name == "worker") | .resources'

    # 3. cert-manager injecta el CA bundle als webhooks (04-05)
    - group: admissionregistration.k8s.io
      kind: ValidatingWebhookConfiguration
      jqPathExpressions: ['.webhooks[]?.clientConfig.caBundle']

    # 4. El controlador de Service assigna clusterIP i nodePort
    - group: ""
      kind: Service
      jsonPointers: ["/spec/clusterIP", "/spec/ports/0/nodePort"]

    # 5. Ignorar TOT el que gestioni un altre controlador. L'opció més
    #    robusta quan es fa servir server-side apply.
    - group: apps
      kind: Deployment
      managedFieldsManagers: [kube-controller-manager, vpa-updater]

Flux ho resol d'una altra manera: traient el camp de l'objecte desitjat abans d'aplicar, amb un patch d'op: remove sobre /spec/replicas, de manera que server-side apply no en reclama la propietat i l'HPA queda lliure.

La llista de camps que canvien sols a Rutas Norte

Camp Qui el canvia Lliçó
Deployment.spec.replicas HPA / KEDA 09-01, 09-04
containers[].resources VPA en mode Auto 09-02
Service.spec.clusterIP, .nodePort Controlador de Services 04-02
PVC.spec.volumeName Aprovisionador dinàmic 05-05
webhooks[].clientConfig.caBundle cert-manager 04-05
metadata.finalizers Diversos operadors 06-07

Consell d'operació: quan una aplicació apareix OutOfSync de forma persistent i no entens per què, argocd app diff t'ho diu en dos segons. Gairebé sempre és un camp d'aquesta llista.

  1. Estructura de repositoris i promoció entre entorns

git.rutasnorte.example/
├── aplicacions/         <- codi font + Dockerfile + proves
└── plataforma/
    ├── k8s/             <- manifestos (base + entorns, de 10-04)
    └── k8s-gitops/      <- Applications, AppProjects, ApplicationSets

Cinc raons per separar codi i manifestos:

Raó Explicació
Cicles diferents Un canvi de codi dispara construcció i proves; un de manifest, només desplegament
Bucle infinit Si la canalització fa commit de l'etiqueta al repositori del codi, es dispara a si mateixa
Permisos diferents Desenvolupament escriu al codi; només plataforma aprova canvis a k8s/entorns/pro
Auditoria neta git log de k8s/ respon "què va canviar en producció" sense soroll de codi
Accés d'Argo CD L'agent només necessita llegir els manifestos, mai el codi font

El flux complet

sequenceDiagram
    participant D as Desenvolupament
    participant CI as ci-rutasnorte
    participant R as registry
    participant CM as Repo k8s
    participant A as Argo CD
    participant K as Clúster

    D->>CI: fusiona el codi a main
    CI->>CI: construeix, prova, escaneja (Trivy, 08-06)
    CI->>R: publica api-reserves:2.4.1@sha256:... i signa amb Cosign (08-05)
    CI->>CM: commit automàtic a k8s/entorns/dev
    CM-->>A: l'agent detecta el commit
    A->>K: desplega a rutas-norte-dev + prova de fum PostSync
    D->>CM: PR: promocionar a pre (MATEIX digest, 1 aprovació)
    CM-->>A: sincronitza -> rutas-norte-pre
    Note over K: proves de càrrega amb k6 (09-06)
    D->>CM: PR: promocionar a pro (2 aprovacions + finestra)
    CM-->>A: sincronitza -> rutas-norte-pro

L'últim pas de la canalització, que només toca dev:

git clone --depth 1 [email protected]:plataforma/k8s.git /tmp/k8s
cd /tmp/k8s/k8s/entorns/dev
kustomize edit set image \
  "registry.rutasnorte.example/api-reserves=registry.rutasnorte.example/api-reserves:${VERSION}@${DIGEST}"
git -c user.name=ci-rutasnorte -c [email protected] \
  commit -am "dev: api-reserves ${VERSION} (${DIGEST:0:19})"
git push origin main

Es promociona el digest, no l'etiqueta. És la garantia que el que es va provar a pre és bit a bit el que va a pro. La petició de canvi que promociona a producció és literalment copiar tres línies de pre/ a pro/: un diff revisable en deu segons.

Canvi Qui proposa Qui aprova Automàtic
Imatge nova a dev ci-rutasnorte Ningú
Configuració de dev Desenvolupament Desenvolupament No
Promoció a pre Desenvolupament Plataforma No
Promoció a pro Plataforma Plataforma + responsable de producte No
Canvi a k8s/base o a k8s-gitops Qualsevol / Plataforma Plataforma No
Revertir producció Qualsevol 1 aprovació (via ràpida) No

Aquesta última fila és important: revertir ha de ser ràpid. Si el procediment de marxa enrere és tan pesat com el de desplegament, la gent evitarà revertir i arreglarà a mà, que és el que volíem eliminar.

Les comprovacions obligatòries a tota PR són les de 10-04: kustomize build de les tres superposicions, kubeconform contra l'esquema, kyverno apply de les polítiques (08-03) i argocd app diff comentat automàticament a la PR.

El primer dia que algú esborra alguna cosa

Aquesta és l'anècdota que convenç els escèptics, i passa a totes les empreses que adopten GitOps. Una persona de suport, depurant un problema, executa kubectl delete deployment api-reserves -n rutas-norte-pro. Se li gela la sang.

14:31:02  Deployment/api-reserves is Missing → Auto-sync (selfHeal)
14:31:04  Deployment/api-reserves created
14:31:39  Application rutas-norte-pro: Healthy

Trenta-set segons. Els pods tornen, la configuració és exactament la correcta, i hi ha un registre del que va passar.

Aquell dia l'equip entén que el repositori no és una còpia de la documentació del clúster: és el clúster. El clúster és només la projecció actual, i és reemplaçable. La prova definitiva, que convé assajar un cop l'any: destruir rutas-norte-pre, crear un clúster nou, instal·lar Argo CD i aplicar una Application, l'arrel. En vint minuts està reconstruït sencer: namespaces, aplicacions, polítiques, monitoratge, certificats. Les dades es restauren a part amb Velero (05-06), però la plataforma es reconstrueix sola.

Errors Comuns i Consells

1. Oblidar el finalitzador resources-finalizer.argocd.argoproj.io. Sense ell, esborrar una Application deixa tots els seus objectes orfes al clúster, sense ningú que els gestioni.

2. Activar prune: true sense entendre l'abast. Si t'equivoques a path i apunta a un directori buit, Argo CD esborrarà tot el que gestionava. Protegeix-te amb allowEmpty: false i prova sempre a dev primer.

3. selfHeal barallant-se amb l'HPA. El bucle de l'apartat 12. Treu replicas del manifest i afegeix ignoreDifferences. Totes dues coses.

4. targetRevision: HEAD o main en producció. Qualsevol commit a main es desplega immediatament a rutas-norte-pro. Fes servir una etiqueta o un commit fix, i promociona conscientment.

5. Posar secrets al repositori "de moment, ja ho arreglarem". No s'arregla, i el secret queda a l'historial per sempre. Munta SOPS, Sealed Secrets o External Secrets abans del primer desplegament.

6. Donar a la credencial d'Argo CD permisos d'escriptura a Git. Només necessita llegir.

7. No configurar AppProject i deixar-ho tot a default. El projecte default permet desplegar qualsevol cosa en qualsevol namespace des de qualsevol repositori: qui pugui escriure al repositori de manifestos pot escalar a administrador del clúster.

8. Confondre Synced amb Healthy. Synced + Degraded significa que el desplegament va funcionar i l'aplicació està trencada: mira els pods, no el repositori.

9. Fer servir argocd app rollback creient que resol el problema. Reverteix el clúster, no Git. Amb selfHeal, en tres minuts torna l'estat dolent. El rollback real és git revert.

10. Tot en una sola Application gegant. Quan alguna cosa falla, tota l'aplicació queda Degraded i no saps què. Divideix per component o per capa, amb onades per a l'ordre.

11. Editar en producció "només aquesta vegada, és una urgència". Es revertirà sol. Si el canvi és correcte, la via ràpida és una PR amb una aprovació, que triga dos minuts. Prepara aquest procediment abans de necessitar-lo.

12. No monitorar el mateix Argo CD. Si el controlador està caigut, res no se sincronitza i no te n'assabentes. Alerta sobre argocd_app_info{sync_status="OutOfSync"} sostingut i sobre la salut dels pods d'argocd (07-04).

13. Onades de sincronització sense comprovacions de salut. Argo CD passa a l'onada següent quan l'anterior està sana; si l'objecte no té avaluador de salut (un CRD sense resource.customizations), passa immediatament i l'ordre no serveix de res.

Exercicis

Exercici 1: ApplicationSet amb polítiques per entorn

Escriu un ApplicationSet que generi les tres aplicacions de Rutas Norte amb aquestes diferències: dev sincronitza automàticament des de main sense selfHeal; pre des de main amb selfHeal i poda; pro des de l'etiqueta release-2.4 amb selfHeal, poda i server-side apply. Tots tres han d'ignorar spec.replicas dels Deployments. Afegeix l'AppProject que restringeixi el desplegament als tres namespaces i prohibeixi crear recursos d'àmbit de clúster.

Exercici 2: diagnosticar i resoldre una deriva persistent

rutas-norte-pro porta dos dies a OutOfSync encara que ningú ha tocat res. Descriu el procediment complet de diagnòstic amb les ordres exactes, identifica almenys tres causes possibles amb el que s'ha estudiat al curs, i escriu l'ignoreDifferences que les resoldria.

Exercici 3: ordenar el desplegament amb onades i hooks

Dissenya les anotacions necessàries perquè un desplegament passi en aquest ordre: (1) namespace i quotes, (2) ExternalSecrets i ConfigMaps, (3) migració d'esquema abans que res més, (4) postgres-reserves i redis-cache, (5) api-reserves, (6) botiga-web, (7) Ingress, (8) HPA i PDB, i una prova de fum final que marqui l'aplicació com a degradada si falla. Explica què passa si la migració falla.

Solucions

Solució 1

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata: { name: rutas-norte, namespace: argocd }
spec:
  sourceRepos: ["[email protected]:plataforma/k8s.git"]
  destinations:
    - { server: https://kubernetes.default.svc, namespace: rutas-norte-dev }
    - { server: https://kubernetes.default.svc, namespace: rutas-norte-pre }
    - { server: https://kubernetes.default.svc, namespace: rutas-norte-pro }
  clusterResourceWhitelist: []          # prohibit crear objectes de clúster
  namespaceResourceWhitelist: [{ group: '*', kind: '*' }]
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata: { name: rutas-norte-entorns, namespace: argocd }
spec:
  generators:
    - list:
        elements:
          - { entorn: dev, rev: main,        selfHeal: "false", prune: "false", ssa: "false" }
          - { entorn: pre, rev: main,        selfHeal: "true",  prune: "true",  ssa: "false" }
          - { entorn: pro, rev: release-2.4, selfHeal: "true",  prune: "true",  ssa: "true"  }
  template:
    metadata:
      name: 'rutas-norte-{{.entorn}}'
      labels: { entorn: '{{.entorn}}' }
      finalizers: [resources-finalizer.argocd.argoproj.io]
    spec:
      project: rutas-norte
      source:
        repoURL: [email protected]:plataforma/k8s.git
        path: 'k8s/entorns/{{.entorn}}'
        targetRevision: '{{.rev}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: 'rutas-norte-{{.entorn}}'
      syncPolicy:
        automated: { prune: '{{.prune}}', selfHeal: '{{.selfHeal}}', allowEmpty: false }
        syncOptions: ['CreateNamespace=true', 'ServerSideApply={{.ssa}}']
      ignoreDifferences:
        - { group: apps, kind: Deployment, jsonPointers: ["/spec/replicas"] }

Solució 2

argocd app get rutas-norte-pro                    # quin objecte està desincronitzat?
argocd app diff rutas-norte-pro                   # en quin camp? L'ORDRE DECISIVA
kubectl get deploy api-reserves -n rutas-norte-pro \
  --show-managed-fields -o yaml | yq '.metadata.managedFields'   # qui el gestiona?
kubectl get events -n rutas-norte-pro --sort-by=.lastTimestamp | tail -20
kubectl logs -n argocd deploy/argocd-repo-server --tail=100 | grep -i error
Causa Camp Solució
HPA escalant (09-01) Deployment.spec.replicas Treure de Git + ignoreDifferences
VPA en mode Auto (09-02) containers[].resources jqPathExpressions
cert-manager injectant el CA (04-05) webhooks[].clientConfig.caBundle jqPathExpressions
ignoreDifferences:
  - { group: apps, kind: Deployment, jsonPointers: ["/spec/replicas"] }
  - group: apps
    kind: Deployment
    name: worker-notificacions
    jqPathExpressions: ['.spec.template.spec.containers[] | select(.name=="worker") | .resources']
  - group: admissionregistration.k8s.io
    kind: ValidatingWebhookConfiguration
    jqPathExpressions: ['.webhooks[]?.clientConfig.caBundle']

Solució 3

Objecte Anotació
Namespace, ResourceQuota sync-wave: "-10"
ExternalSecret, ConfigMap sync-wave: "-5"
Job de migració hook: PreSync, hook-delete-policy: BeforeHookCreation
postgres-reserves, redis-cache sync-wave: "0"
api-reserves / botiga-web sync-wave: "1" / "2"
Service, Ingress sync-wave: "3"
HPA, PDB sync-wave: "5"
Job de prova de fum hook: PostSync

Si la migració falla: el hook PreSync no completa, així que Argo CD avorta la sincronització sense aplicar cap manifest. El clúster queda exactament com estava, servint la versió anterior. L'aplicació apareix Sync Failed i el Job continua existint (per BeforeHookCreation), de manera que kubectl logs job/migracio-esquema -n rutas-norte-pro en dona el motiu. És el mateix comportament protector que els hooks de Helm de 10-03.

Conclusió

GitOps tanca el cercle que vam obrir al principi del mòdul. Rutas Norte ha passat de 120 fitxers aplicats a mà des d'un portàtil a un sistema on el repositori és la font de veritat i el clúster hi convergeix contínuament.

  • Els quatre principis —declaratiu, versionat i immutable, aplicat automàticament, reconciliat de forma contínua— són el contracte. El quart és el que ho canvia tot.
  • El model d'extracció elimina la credencial més perillosa: ci-rutasnorte ja no necessita accés al clúster, només escriure en un repositori Git, i l'apiserver deixa d'haver de ser accessible des de fora.
  • La deriva de configuració es detecta i es corregeix sola. El canvi cultural que provoca —"l'única forma de canviar producció és una petició de canvi"— val més que la tecnologia.
  • Argo CD amb la seva Application, els seus AppProject que limiten l'abast, el patró app of apps que redueix la feina manual a un únic kubectl apply en tota la vida del clúster, i els ApplicationSet que generen els tres entorns des d'una definició. Les onades i els hooks ordenen el desplegament i protegeixen la migració d'esquema.
  • Flux ofereix el mateix amb controladors especialitzats, releases de Helm reals i automatització d'imatges integrada. L'elecció no és irreversible: llegeixen els mateixos manifestos.
  • Els secrets tenen tres solucions pràctiques: SOPS (diffs llegibles), Sealed Secrets (sense infraestructura extra) i External Secrets Operator (el secret no arriba mai a Git, amb rotació automàtica). Per a Rutas Norte, la tercera.
  • I el conflicte entre l'HPA i replicas que arrossegàvem des de 09-01 queda resolt: fora del manifest i ignoreDifferences a l'Application, juntament amb tota la família de camps que altres controladors modifiquen.

Ens queda una peça de l'ecosistema. Tot el que hem muntat ha de córrer en algun lloc, i a 10-02 vam veure el que costa operar un clúster propi. A la lliçó següent, Kubernetes Gestionat: EKS, AKS i GKE, veurem què t'estalvia un proveïdor de núvol i què continua sent teu: el model de responsabilitat compartida, la comparació honesta dels tres grans, la federació d'identitat que vam deixar pendent a 03-06, l'ús d'instàncies interrompibles per a worker-notificacions i informes-ocupacio, i el criteri de decisió final per a Rutas Norte.

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