Plataforma
Els serveis de plataforma que FluxCD desplega abans de qualsevol aplicació, nivell a nivell.
Nivell 1: xarxa
Calico
La CNI del clúster. Flux no la pot instal·lar (sense CNI no arrenca cap pod, ni tan sols els de Flux), així que la instal·la l'stack de Kubernetes en construir el clúster, i Flux adopta aquell Helm release.
| Chart | tigera-operator, release calico a tigera-operator |
| Xarxa de pods | 172.16.0.0/16, IP-in-IP |
| Valors | infrastructure/base/10-networking/controllers/calico/values.yaml |
El HelmRelease té el mateix nom i namespace que la instal·lació inicial, de manera que Flux l'actualitza en lloc d'instal·lar-ne un de nou. A més, eliminar-lo de Git no desinstal·la Calico i Flux no és mai propietari del seu namespace.
Calico no té rollback automàtic: tornar enrere la xarxa del clúster és una decisió manual. Si una actualització trenca la xarxa, Flux cau amb ella. L'API funciona a la xarxa del host, així que des d'un equip amb accés:
helm rollback calico -n tigera-operator
flux suspend helmrelease calico -n tigera-operator
Una versió menor nova requereix aplicar abans les seves CRDs a mà i comprovar que suporta la versió de Kubernetes del clúster.
MetalLB
Sense balancejador del núvol, MetalLB dona als serveis LoadBalancer una adreça de la LAN i l'anuncia per ARP. Cada entorn té dos pools:
| Entorn | ingress (a mà) | default (automàtic) |
|---|---|---|
| testing | 192.168.1.170 – .171 | 192.168.1.172 – .179 |
| production | 192.168.1.180 – .181 | 192.168.1.182 – .189 |
Les dues adreces d'ingress estan reservades per a Traefik (intranet i extranet). Qualsevol altre servei rep la següent lliure de default.
Aquests rangs han de quedar fora del DHCP, i dos clústers no han d'anunciar mai el mateix rang alhora.
Reloader
Un pod llegeix els seus Secrets i ConfigMaps en arrencar. Reloader reinicia els workloads quan canvien, de manera que una contrasenya rotada a Git arriba a l'aplicació sense passos manuals. Només actua sobre els workloads que ho demanen:
metadata:
annotations:
reloader.stakater.com/auto: "true"
Metrics Server
Recull l'ús actual de CPU i memòria de nodes i pods; és el que llegeix kubectl top. Fa servir --kubelet-insecure-tls perquè els kubelets fan servir els certificats autosignats de kubeadm. L'històric viu a la monitorització externa.
Nivell 2: ingress
cert-manager
Demana els certificats a Let's Encrypt, els desa com a Secrets i els renova 30 dies abans que caduquin. Un Ingress ho demana amb una anotació.
| ClusterIssuer | Repte | Ús |
|---|---|---|
letsencrypt-dns | DNS-01 amb l'API de Cloudflare | Qualsevol nom, accessible des d'Internet o no |
letsencrypt-http | HTTP-01 a través del Traefik extranet | Noms publicats a Internet |
letsencrypt-staging-dns | DNS-01 contra l'entorn de proves | Provar la configuració sense gastar el límit de Let's Encrypt |
A testing tot fa servir letsencrypt-dns. El token de Cloudflare (permís Zone › DNS › Edit) es desa xifrat:
sops infrastructure/testing/20-ingress/configs/cloudflare-api-token-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token-secret
namespace: cert-manager-system
stringData:
api-token: <token>
Traefik
L'ingress controller. Hi ha dues instàncies separades, de manera que un servei pensat per a la LAN no es pugui publicar a Internet per error:
| Instància | IngressClass | Accessible des de | testing |
|---|---|---|---|
traefik-intranet | intranet | La LAN | 192.168.1.170 |
traefik-extranet | extranet | Internet, a través de Cloudflare (a producció) | 192.168.1.171 |
Totes dues tenen dues rèpliques en nodes diferents, redirigeixen HTTP a HTTPS, conserven la IP real del client i no tenen classe per defecte: un Ingress sense classe no el serveix cap. A testing cap no està publicada; les dues classes existeixen per replicar producció.
Publicar un servei:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
annotations:
cert-manager.io/cluster-issuer: letsencrypt-dns
spec:
ingressClassName: intranet # o extranet
tls:
- hosts: [app.test.intranet.moon.cat]
secretName: app-tls
rules:
- host: app.test.intranet.moon.cat
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app
port:
number: 80
El registre DNS del nom ha d'apuntar a l'adreça de la instància escollida. A Sora, es crea com a àlies a la xarxa d'Horizon.
Observabilitat
Dins del clúster no es guarda res. Un únic col·lector, Grafana Alloy, recull mètriques, logs i traces i els envia a la màquina de monitorització. Tot porta l'etiqueta cluster (tanya a testing).
| Senyal | Origen | Com se selecciona |
|---|---|---|
| Mètriques | kubelet, cAdvisor, kube-state-metrics | Sempre |
| Mètriques | Workloads que les exposen | Anotació k8s.grafana.com/scrape: "true" |
| Logs | Pods | Etiqueta monitoring/logs: "true" |
| Logs | kubelet.service i crio.service de cada node | Sempre |
| Logs, traces i mètriques | Aplicacions amb OpenTelemetry | El que enviïn |
Les mètriques de CPU, memòria i disc de cada node no es recullen aquí: les publica el Node Exporter de cada VM, que continua informant encara que Kubernetes estigui caigut.
Les aplicacions envien OTLP per HTTP al receptor del clúster, sense credencials:
OTEL_EXPORTER_OTLP_ENDPOINT=http://k8s-monitoring-alloy-receiver.monitoring-system.svc:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
Una aplicació amb SDK d'OpenTelemetry envia els seus logs per OpenTelemetry i no porta l'etiqueta monitoring/logs; amb totes dues, cada línia es guarda dues vegades. L'etiqueta és per al que no té OpenTelemetry (Traefik, cert-manager, Keycloak, ...).
Les credencials d'enviament són les sortides del treball traefik de l'stack de monitorització, desades xifrades:
sops infrastructure/testing/30-observability/configs/monitoring-ingest.yaml
apiVersion: v1
kind: Secret
metadata:
name: monitoring-ingest
namespace: monitoring-system
stringData:
username: <usuari d'ingesta>
password: <contrasenya d'ingesta>
Per comprovar què arriba, a Grafana › Explore: up{cluster="tanya"} == 0 a Prometheus (targets caiguts) o {cluster="tanya", namespace="traefik-system"} a Loki.
Keycloak
El proveïdor d'identitat: les aplicacions hi deleguen el login mitjançant OpenID Connect. S'instal·la amb el Keycloak Operator oficial, seguint els tags del seu repositori.
| testing | |
|---|---|
| Hostname | auth.test.lb.moon.cat |
| Ingress | extranet, certificat de letsencrypt-dns |
| Base de dades | keycloak a mio-akiyama.horizon.moon.cat:6432 (PgBouncer) |
FluxCD gestiona l'operador, el servidor, la seva base de dades, el seu nom i el seu ingress. El que hi ha dins (realms, clients, usuaris, ...) ho configura Horizon. La base de dades comença buida i Keycloak crea les seves taules en arrencar; es crea abans amb l'acció Create User de l'stack de PostgreSQL, i les seves credencials es desen xifrades:
sops infrastructure/testing/40-identity/configs/keycloak-pg-auth.yaml
apiVersion: v1
kind: Secret
metadata:
name: keycloak-pg-auth
namespace: keycloak-system
stringData:
username: keycloak
password: <contrasenya>
A la primera arrencada, l'operador crea un administrador temporal al secret keycloak-initial-admin, que és el que es fa servir per connectar Horizon.
Tema moon
La pàgina de login fa servir el tema moon, desat al repositori i muntat sobre la imatge oficial: sense imatge pròpia ni volums. Mostra una imatge de fons al costat de la targeta de login, s'adapta a PC, tauleta i mòbil, segueix el mode clar o fosc del navegador i, a testing, afegeix una cinta TESTING ENVIRONMENT. Només estén el tema de Keycloak, sense sobreescriure plantilles, perquè les seves actualitzacions continuïn funcionant. Cada realm l'escull com a tema de login des d'Horizon.
Validar:
kubectl -n keycloak-system get pods # keycloak-operator-…, keycloak-0 Running
curl -s https://auth.test.lb.moon.cat/realms/master/.well-known/openid-configuration | jq -r .issuer
# https://auth.test.lb.moon.cat/realms/master
L'issuer ha de ser exactament el nom públic per https. Qualsevol altra cosa vol dir que el hostname o les capçaleres del proxy són incorrectes, i els logins fallaran.