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
- Què és GitOps i els seus quatre principis
- Model d'enviament davant de model d'extracció
- Deriva de configuració i autocorrecció
- Argo CD: arquitectura i instal·lació
- El recurs Application camp a camp
- App of apps i ApplicationSet
- Onades de sincronització i hooks
- Estats de salut i sincronització
- Flux: els controladors i els seus recursos
- Argo CD davant de Flux
- El problema dels secrets
- Camps que canvien sols: ignoreDifferences
- Estructura de repositoris i promoció entre entorns
- Errors comuns i consells
- Exercicis
- Conclusió
- 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:
- 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.
- 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. - Aplicat automàticament: els canvis aprovats s'apliquen sense intervenció manual. Ningú executa
kubectl applynihelm upgrade: es fusiona una petició de canvi i el sistema convergeix. - 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 |
- 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:
- Aquest kubeconfig és un secret emmagatzemat fora del clúster, sovint gestionat per un altre equip o per un proveïdor extern.
- 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. - 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ó.
- L'apiserver ha de ser abastable des de l'exterior.
- 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? | Sí | 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.
- 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 editper 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
selfHealper aplicació. Arutas-norte-devpot convenir deixar-lo desactivat perquè la gent experimenti; arutas-norte-proha d'estar activat.
- 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-secretEn 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:readonlyI 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
- 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: 20L'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ènciaAquest 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].
- 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 }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.
- 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.
- 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 hsargocd 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 600Compte 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.
- 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/fluxAquest 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é FluxPer 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.0I 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
- 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/.
- 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: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8pstringData:
# 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 | Sí | 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 | Sí | No (un per clúster) | Sí |
| Integració Argo CD / Flux | Regular / Nativa | Bona / Bona | Bona / Bona |
| Auditoria d'accessos | No | No | Sí |
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.
- 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 successfulArgo 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.
- 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, ApplicationSetsCinc 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 mainEs 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ú | Sí |
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: HealthyTrenta-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-rutasnorteja 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 seusAppProjectque limiten l'abast, el patró app of apps que redueix la feina manual a un únickubectl applyen tota la vida del clúster, i elsApplicationSetque 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
replicasque arrossegàvem des de 09-01 queda resolt: fora del manifest iignoreDifferencesa 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
- Què és Kubernetes?
- Arquitectura de Kubernetes
- Conceptes i Terminologia Clau
- Configuració d'un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objectes, Manifests YAML i el Model Declaratiu
- El Projecte del Curs: la Plataforma Rutas Norte
Mòdul 2: Components Principals de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualitzacions, Rollbacks i Estratègies de Desplegament
- Serveis
- Namespaces
- Etiquetes, Selectors i Anotacions
Mòdul 3: Gestió de Configuració i Secrets
- ConfigMaps
- Secrets
- Variables d'Entorn
- Quotes i Límits de Recursos
- LimitRanges i Classes de Qualitat de Servei (QoS)
- ServiceAccounts i Accés a l'API des dels Pods
Mòdul 4: Xarxes a Kubernetes
- Xarxes de Clúster
- Tipus de Serveis
- DNS Intern i Descobriment de Serveis
- Controladors d'Ingress
- TLS i Gestió de Certificats amb cert-manager
- Polítiques de Xarxa
Mòdul 5: Emmagatzematge a Kubernetes
- Volums
- Volums Persistents
- Reclamacions de Volums Persistents
- Classes d'Emmagatzematge
- Aprovisionament Dinàmic, Expansió i Snapshots
- Còpies de Seguretat i Restauració de Dades
Mòdul 6: Conceptes Avançats de Kubernetes
- StatefulSets
- DaemonSets
- Treballs i CronJobs
- Init Containers, Sidecars i Patrons Multicontenidor
- Planificació: Afinitat, Taints i Toleracions
- Definicions de Recursos Personalitzats (CRDs)
- Operadors i el Patró Controlador
Mòdul 7: Monitoratge i Registre
- Verificacions de Salut i Sondes
- Servidor de Mètriques i kubectl top
- Monitoratge amb Prometheus
- Visualització i Alertes amb Grafana i Alertmanager
- Registre Centralitzat amb Elasticsearch, Fluentd i Kibana (EFK)
- Depuració d'Aplicacions i Esdeveniments del Clúster
Mòdul 8: Seguretat a Kubernetes
- Control d'Accés Basat en Rols (RBAC)
- Contextos de Seguretat i Enduriment del Contenidor
- Polítiques de Seguretat de Pods i Pod Security Standards
- Seguretat de Xarxa
- Seguretat d'Imatges
- Auditoria, Escaneig i Gestió de Vulnerabilitats
Mòdul 9: Escalat i Rendiment
- Autoescalat Horitzontal de Pods
- Autoescalat Vertical de Pods
- Autoescalat de Clúster
- Escalat per Esdeveniments i Mètriques Personalitzades amb KEDA
- Alta Disponibilitat: PodDisruptionBudgets i Topologia
- Ajust de Rendiment
Mòdul 10: Ecosistema i Eines de Kubernetes
- Minikube i Entorns Locals amb kind
- Kubeadm
- Helm
- Kustomize
- GitOps amb Argo CD i Flux
- Kubernetes Gestionat: EKS, AKS i GKE
Mòdul 11: Estudis de Cas i Aplicacions del Món Real
- Desplegament d'una Aplicació Web
- Execució d'Aplicacions amb Estat
- CI/CD amb Kubernetes
- Estratègies de Desplegament: Blue-Green i Canary
- Gestió Multi-Clúster
- Operació en Producció: Incidències, Runbooks i Costos
