Skip to content

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
  1. Gateway API (le routage) : un Gateway (point d'entrée, avec des listeners 80/443) + une HTTPRoute (quelle URL va vers quel service).
  2. cert-manager (le TLS) : un Certificate qui obtient un certificat Let's Encrypt et le stocke dans un Secret, présenté par le listener HTTPS.
  3. ExternalDNS (le DNS) : lit le hostname de la HTTPRoute et crée le CNAME dans Cloudflare, automatiquement.
  4. 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 :

  1. 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.
  2. 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 :

  1. Appliquer le Certificate wildcard et attendre Ready: True (le secret wildcard-devopsyouss-tls doit exister).
  2. Ensuite seulement basculer le Gateway sur 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.