Saltar al contenido principal

Plataforma

Los servicios de plataforma que FluxCD despliega antes que cualquier aplicación, nivel a nivel.

Nivel 1: red​

Calico​

La CNI del clúster. Flux no puede instalarla (sin CNI no arranca ningún pod, ni siquiera los de Flux), así que la instala el stack de Kubernetes al construir el clúster, y Flux adopta ese Helm release.

Charttigera-operator, release calico en tigera-operator
Red de pods172.16.0.0/16, IP-in-IP
Valoresinfrastructure/base/10-networking/controllers/calico/values.yaml

El HelmRelease tiene el mismo nombre y namespace que la instalación inicial, de forma que Flux lo actualiza en lugar de instalar uno nuevo. Además, eliminarlo de Git no desinstala Calico y Flux nunca es dueño de su namespace.

peligro

Calico no tiene rollback automático: volver atrás la red del clúster es una decisión manual. Si una actualización rompe la red, Flux cae con ella. La API funciona en la red del host, así que desde un equipo con acceso:

helm rollback calico -n tigera-operator
flux suspend helmrelease calico -n tigera-operator

Una versión menor nueva requiere aplicar antes sus CRDs a mano y comprobar que soporta la versión de Kubernetes del clúster.

MetalLB​

Sin balanceador de la nube, MetalLB da a los servicios LoadBalancer una dirección de la LAN y la anuncia por ARP. Cada entorno tiene dos pools:

Entornoingress (a mano)default (automático)
testing192.168.1.170 – .171192.168.1.172 – .179
production192.168.1.180 – .181192.168.1.182 – .189

Las dos direcciones de ingress están reservadas para Traefik (intranet y extranet). Cualquier otro servicio recibe la siguiente libre de default.

aviso

Estos rangos deben quedar fuera del DHCP, y dos clústeres nunca deben anunciar el mismo rango a la vez.

Reloader​

Un pod lee sus Secrets y ConfigMaps al arrancar. Reloader reinicia los workloads cuando cambian, de forma que una contraseña rotada en Git llega a la aplicación sin pasos manuales. Solo actúa sobre los workloads que lo piden:

metadata:
annotations:
reloader.stakater.com/auto: "true"

Metrics Server​

Recoge el uso actual de CPU y memoria de nodos y pods; es lo que lee kubectl top. Usa --kubelet-insecure-tls porque los kubelets usan los certificados autofirmados de kubeadm. El histórico vive en la monitorización externa.

Nivel 2: ingress​

cert-manager​

Pide los certificados a Let's Encrypt, los guarda como Secrets y los renueva 30 días antes de caducar. Un Ingress lo pide con una anotación.

ClusterIssuerRetoUso
letsencrypt-dnsDNS-01 con la API de CloudflareCualquier nombre, accesible desde Internet o no
letsencrypt-httpHTTP-01 a través del Traefik extranetNombres publicados en Internet
letsencrypt-staging-dnsDNS-01 contra el entorno de pruebasProbar la configuración sin gastar el límite de Let's Encrypt

En testing todo usa letsencrypt-dns. El token de Cloudflare (permiso Zone › DNS › Edit) se guarda cifrado:

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​

El ingress controller. Hay dos instancias separadas, de forma que un servicio pensado para la LAN no se pueda publicar en Internet por error:

InstanciaIngressClassAccesible desdetesting
traefik-intranetintranetLa LAN192.168.1.170
traefik-extranetextranetInternet, a través de Cloudflare (en producción)192.168.1.171

Las dos tienen dos réplicas en nodos diferentes, redirigen HTTP a HTTPS, conservan la IP real del cliente y no tienen clase por defecto: un Ingress sin clase no lo sirve ninguna. En testing ninguna está publicada; las dos clases existen para replicar producción.

Publicar un servicio:

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 registro DNS del nombre debe apuntar a la dirección de la instancia elegida. En Sora, se crea como alias en la red de Horizon.

Observabilidad​

Dentro del clúster no se guarda nada. Un único colector, Grafana Alloy, recoge métricas, logs y trazas y los envía a la máquina de monitorización. Todo lleva la etiqueta cluster (tanya en testing).

SeñalOrigenCómo se selecciona
Métricaskubelet, cAdvisor, kube-state-metricsSiempre
MétricasWorkloads que las exponenAnotación k8s.grafana.com/scrape: "true"
LogsPodsEtiqueta monitoring/logs: "true"
Logskubelet.service y crio.service de cada nodoSiempre
Logs, trazas y métricasAplicaciones con OpenTelemetryLo que envíen

Las métricas de CPU, memoria y disco de cada nodo no se recogen aquí: las publica el Node Exporter de cada VM, que sigue informando aunque Kubernetes esté caído.

Las aplicaciones envían OTLP por HTTP al receptor del clúster, sin credenciales:

OTEL_EXPORTER_OTLP_ENDPOINT=http://k8s-monitoring-alloy-receiver.monitoring-system.svc:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
consejo

Una aplicación con SDK de OpenTelemetry envía sus logs por OpenTelemetry y no lleva la etiqueta monitoring/logs; con ambas, cada línea se guarda dos veces. La etiqueta es para lo que no tiene OpenTelemetry (Traefik, cert-manager, Keycloak, ...).

Las credenciales de envío son las salidas del trabajo traefik del stack de monitorización, guardadas cifradas:

sops infrastructure/testing/30-observability/configs/monitoring-ingest.yaml
apiVersion: v1
kind: Secret
metadata:
name: monitoring-ingest
namespace: monitoring-system
stringData:
username: <usuario de ingesta>
password: <contraseña de ingesta>

Para comprobar qué llega, en Grafana › Explore: up{cluster="tanya"} == 0 en Prometheus (targets caídos) o {cluster="tanya", namespace="traefik-system"} en Loki.

Keycloak​

El proveedor de identidad: las aplicaciones delegan el login en él mediante OpenID Connect. Se instala con el Keycloak Operator oficial, siguiendo los tags de su repositorio.

testing
Hostnameauth.test.lb.moon.cat
Ingressextranet, certificado de letsencrypt-dns
Base de datoskeycloak en mio-akiyama.horizon.moon.cat:6432 (PgBouncer)

FluxCD gestiona el operador, el servidor, su base de datos, su nombre y su ingress. Lo que hay dentro (realms, clientes, usuarios, ...) lo configura Horizon. La base de datos empieza vacía y Keycloak crea sus tablas al arrancar; se crea antes con la acción Create User del stack de PostgreSQL, y sus credenciales se guardan cifradas:

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: <contraseña>

Al primer arranque, el operador crea un administrador temporal en el secret keycloak-initial-admin, que es el que se usa para conectar Horizon.

Tema moon​

La página de login usa el tema moon, guardado en el repositorio y montado sobre la imagen oficial: sin imagen propia ni volúmenes. Muestra una imagen de fondo junto a la tarjeta de login, se adapta a PC, tablet y móvil, sigue el modo claro u oscuro del navegador y, en testing, añade una cinta TESTING ENVIRONMENT. Solo extiende el tema de Keycloak, sin sobrescribir plantillas, para que sus actualizaciones sigan funcionando. Cada realm lo elige como tema de login desde 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

El issuer debe ser exactamente el nombre público por https. Cualquier otra cosa significa que el hostname o las cabeceras del proxy son incorrectos, y los logins fallarán.