Comprendre la démarche d'exposition (Grafana public)
Ce guide raconte comment on a exposé Grafana sur grafana.devopsyouss.com
(issue #76), et surtout pourquoi dans cet ordre. C'est la recette
réutilisable pour exposer n'importe quel service du cluster.
ADR liés : Exposition ownership, Observabilité. Voir aussi Comprendre ArgoCD.
Le point de départ
Grafana tournait, mais seulement accessible en port-forward (tunnel local). On
veut le rendre accessible publiquement, en HTTPS, sans réinventer la chaîne
d'exposition : on réutilise exactement celle de l'API (Sprint 3). Réutiliser une
archi sans la modifier, c'est la preuve qu'elle est bien conçue (ADR 010).
Les 4 briques de la chaîne d'exposition
Utilisateur
│ https://grafana.devopsyouss.com
▼
Cloudflare DNS ◄── (3) ExternalDNS crée le CNAME automatiquement
│
▼
ELB ──► Envoy Gateway ──► (1) HTTPRoute ──► Service Grafana
(2) TLS via cert wildcard
- Gateway API (le routage) : un
Gateway(point d'entrée, avec deslisteners80/443) + uneHTTPRoute(quelle URL va vers quel service). - cert-manager (le TLS) : un
Certificatequi obtient un certificat Let's Encrypt et le stocke dans un Secret, présenté par le listener HTTPS. - ExternalDNS (le DNS) : lit le hostname de la HTTPRoute et crée le CNAME dans Cloudflare, automatiquement.
- ESO (le secret) : le mot de passe admin Grafana vient d'AWS Secrets Manager, pas du défaut du chart.
Brique 1 — le routage (Gateway API)
Le Gateway (fastapi-gateway) existait déjà pour l'API. On a juste ajouté une
HTTPRoute pour Grafana :
kind: HTTPRoute
metadata:
name: grafana
namespace: monitoring
spec:
parentRefs:
- name: fastapi-gateway
namespace: fastapi # le Gateway est dans un AUTRE namespace
hostnames:
- grafana.devopsyouss.com
rules:
- backendRefs:
- name: kube-prometheus-stack-grafana
port: 80
Le piège : le cross-namespace
Le Gateway est dans le namespace fastapi, mais Grafana (et sa route) sont dans
monitoring. Par défaut, un Gateway n'accepte que les routes de son propre
namespace. Il a fallu l'autoriser explicitement :
# dans le Gateway
listeners:
- name: https
...
allowedRoutes:
namespaces:
from: All # accepte les routes de tous les namespaces
Le Gateway devient ainsi un ingress partagé : un seul point d'entrée (un seul ELB) pour plusieurs applications dans plusieurs namespaces.
Brique 2 — le TLS (certificat wildcard)
Le certificat de l'API ne couvrait que api.devopsyouss.com. Plutôt que d'ajouter
grafana à la main, on est passé à un certificat wildcard :
kind: Certificate
spec:
secretName: wildcard-devopsyouss-tls
dnsNames:
- "*.devopsyouss.com" # couvre api, grafana, et tout futur sous-domaine
issuerRef:
name: letsencrypt-prod
- Un seul certificat pour tous les sous-domaines présents et futurs (argocd, etc.) sans jamais le re-éditer.
- Le wildcard impose le challenge DNS-01 (Let's Encrypt ne valide pas un wildcard par HTTP-01). On l'avait déjà, via le token Cloudflare.
Brique 3 — le DNS (ExternalDNS)
Rien à coder : ExternalDNS lit le hostname de la HTTPRoute et crée le CNAME
grafana.devopsyouss.com dans Cloudflare en moins d'une minute (pattern déjà en
place pour l'API).
Brique 4 — le secret (ESO) et la sécurité
Exposer Grafana publiquement avec le mot de passe par défaut du chart serait une faille. On a donc d'abord géré le mot de passe proprement :
- un secret dédié dans AWS Secrets Manager (
fastapi-eks/grafana), généré par Terraform ; - lu par ESO (ExternalSecret) qui crée un Secret Kubernetes
grafana-admin; - Grafana configuré pour utiliser ce secret (
grafana.admin.existingSecret).
Le rôle IAM de l'ESO (IRSA) a été étendu au seul ARN supplémentaire (moindre privilège : deux secrets ciblés, pas un accès large).
La démarche : sécurité d'abord, en 2 MRs
C'est le point méthodo le plus important. On a découpé #76 en deux MRs, dans cet ordre :
- MR1 — le mot de passe géré (ESO + Terraform). Grafana n'est pas encore public, mais son mot de passe n'est plus celui par défaut.
- MR2 — l'exposition (cert wildcard + Gateway + HTTPRoute).
Règle : on ne rend jamais un service public tant que son authentification n'est pas durcie. Faire l'inverse, c'est ouvrir une porte avant d'avoir mis la serrure. Le découpage en 2 MRs matérialise cet ordre.
L'ordre des opérations (et un piège)
Certaines pièces sont posées par GitOps (HTTPRoute, ESO : ArgoCD les applique au
merge), d'autres par le bootstrap (Gateway, Certificate : appliqués à la main
ou au prochain aws-start).
L'ordre critique au moment d'activer l'exposition :
- Appliquer le
Certificatewildcard et attendreReady: True(le secretwildcard-devopsyouss-tlsdoit exister). - Ensuite seulement basculer le
Gatewaysur ce secret.
Piège : si on bascule le Gateway sur un secret qui n'existe pas encore, le listener HTTPS casse... y compris pour l'API qui partage le même listener. Donc cert prêt d'abord, gateway ensuite.
Qui possède quoi (ADR 010)
| Ressource | Propriétaire | Pourquoi |
|---|---|---|
| GatewayClass, Gateway, Certificate | Plateforme (bootstrap) | Point d'entrée stable, change rarement |
| HTTPRoute | Application (manifests, ns du service) | Les routes bougent au rythme des releases |
| Secret admin (ESO) | Plateforme monitoring | Lié au service exposé |
Mettre la route côté application permet de la faire évoluer (canary, nouveaux hostnames) sans toucher au socle.
Le résultat
Trois URLs publiques servies par le même Gateway et le même certificat wildcard :
https://api.devopsyouss.com(l'API)https://grafana.devopsyouss.com(l'observabilité)https://doc.devopsyouss.com(la doc, via GitLab Pages)
Pour exposer un prochain service (ex: l'UI ArgoCD) : une HTTPRoute de plus, le wildcard couvre déjà le TLS, ExternalDNS gère le DNS. La chaîne est réutilisable.
SSO : connexion via GitLab (#106)
Une fois Grafana exposé, on remplace le login par mot de passe admin par le SSO
GitLab (même logique qu'ArgoCD, cf. ADR 024).
Grafana a un provider [auth.gitlab] natif : pas besoin de Dex côté Grafana,
il parle directement à gitlab.com.
La config vit dans les values (grafana.ini, section auth.gitlab) :
client_id (public, en clair), allowed_groups: yk-devops (filtre d'accès) et
role_attribute_path: "'Admin'" (tout membre du groupe → Admin). Le client_secret
n'est jamais dans les values : il est injecté en variable d'environnement
GF_AUTH_GITLAB_CLIENT_SECRET depuis le Secret K8s grafana-admin (clé matérialisée
par ESO). Grafana mappe automatiquement l'env GF_<SECTION>_<KEY> vers la config, donc
GF_AUTH_GITLAB_CLIENT_SECRET alimente [auth.gitlab] client_secret sans rien écrire
en clair.
C'est la même application OAuth GitLab que pour ArgoCD (une seule app, plusieurs redirect URIs), donc le même client_secret partagé. Le mot de passe admin Grafana (#76) devient un accès de secours.