Aller au contenu

Ajouter un service

L'ajout d'un nouveau service à AKKO implique de modifier plusieurs fichiers afin de garantir que le service est correctement routé, sécurisé, supervisé et visible dans le portail cockpit. Cette page fournit la checklist complète ainsi qu'un exemple concret.


Checklist

Étape Fichier Objectif
1 helm/akko/charts/<sub-chart>/ Sous-chart Helm (templates, values, helpers)
2 helm/examples/realm-akko-k3d.json Client OAuth2 (si le service nécessite le SSO)
3 scripts/generate-secrets.sh Ajouter les nouveaux secrets à la génération .env
4 branding/cockpit-react/src/platform/catalog.ts Déclarer le service comme capacité dans une couche
5 branding/cockpit-react/src/features/<x>/ Ajouter une page (uniquement si le service est rendu dans le cockpit)
6 branding/cockpit-react/src/app/App.tsx Ajouter la route (uniquement pour une page intégrée)
7 scripts/start.sh Afficher l'URL du service au démarrage
8 Documentation Mettre à jour la documentation d'architecture, le README, etc.

Chaque étape est détaillée ci-dessous, suivie d'un exemple complet et concret.


Étape 1 -- Sous-chart Helm

Créez un sous-chart Helm dans helm/akko/charts/<nom-du-service>/ et ajoutez la dépendance dans helm/akko/Chart.yaml. Chaque service AKKO suit un modèle cohérent :

  • Tag d'image épinglé (jamais latest)
  • Nom de conteneur préfixé par akko-
  • IngressRoute Traefik pour le routage HTTPS
  • Healthcheck avec sondes de vivacité et de disponibilité
  • Limites de ressources appropriées au service
  • Contexte de sécurité (runAsNonRoot, drop: [ALL])

Modèle de labels Traefik

Tous les services exposés via Traefik utilisent ces quatre labels :

labels:
  traefik.enable: "true"
  traefik.http.routers.<name>.rule: "Host(`<subdomain>.{{ .Values.global.domain }}`)"
  traefik.http.routers.<name>.entrypoints: "websecure"
  traefik.http.routers.<name>.tls: "true"

Si le service écoute sur un port non standard (autre que 80), ajoutez :

  traefik.http.services.<name>.loadbalancer.server.port: "<port>"

Pour protéger le service derrière le SSO Keycloak via oauth2-proxy, ajoutez le middleware :

  traefik.http.routers.<name>.middlewares: "oauth2-auth-chain@file"
  # Plus le routeur de callback oauth2 :
  traefik.http.routers.<name>-oauth2.rule: "Host(`<subdomain>.{{ .Values.global.domain }}`) && PathPrefix(`/oauth2/`)"
  traefik.http.routers.<name>-oauth2.entrypoints: "websecure"
  traefik.http.routers.<name>-oauth2.tls: "true"
  traefik.http.routers.<name>-oauth2.service: "oauth2-proxy"

Modèle de healthcheck

livenessProbe:
  httpGet:
    path: /<health-path>
    port: <port>
  initialDelaySeconds: 30
  periodSeconds: 30
  timeoutSeconds: 10
  failureThreshold: 3
readinessProbe:
  httpGet:
    path: /<health-path>
    port: <port>
  initialDelaySeconds: 10
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3

Conseils pour les healthchecks

  • Utilisez les sondes httpGet pour les endpoints de santé HTTP.
  • Utilisez les sondes exec avec la commande native de healthcheck du service lorsqu'elle est disponible (par ex. ["traefik", "healthcheck"] ou ["mc", "ready", "local"]).
  • Certaines images minimales (comme celle d'Ollama) ne contiennent ni curl ni wget. Utilisez les sondes tcpSocket comme solution de repli.

Étape 2 -- Client OAuth Keycloak

Si le service supporte OpenID Connect, ajoutez un client dans helm/examples/realm-akko-k3d.json dans le tableau clients. L'identifiant du client doit correspondre au nom du service. Définissez publicClient: false pour les flux côté serveur (confidentiels).


Étape 3 -- Génération des secrets

Si le service a besoin d'identifiants, ajoutez-les dans scripts/generate-secrets.sh à l'intérieur du heredoc qui écrit .env :

# --- MyService ---
MYSERVICE_ADMIN_PASSWORD=$(gen_password)
KC_CLIENT_SECRET_MYSERVICE=$(gen_hex)

Après modification, supprimez .env et relancez le script pour intégrer les nouvelles variables :

rm .env && ./scripts/generate-secrets.sh

Étape 4 -- Capacité cockpit

Le cockpit est une SPA React (branding/cockpit-react/). Déclarez le service comme capacité dans sa couche, au sein de src/platform/catalog.ts. Chaque capacité porte les rôles autorisés à la voir et un kind :

  • kind: 'external' : le service a sa propre UI dédiée, ouverte dans un nouvel onglet (la plupart des services : Trino, Superset, JupyterHub, ...)
  • kind: 'integrated' : le service est rendu comme page dans le cockpit (une fonctionnalité React), atteinte via une route interne to: '/x/<slug>'
src/platform/catalog.ts
{ id: 'governance', fr: 'Gouvernance', en: 'Governance', icon: 'shield', color: '--cl-gov', caps: [
  // outil externe : ouvre sa propre UI dans un nouvel onglet
  { id: 'myservice', tool: 'myservice', fr: 'Mon service', en: 'My service',
    dfr: 'Description courte', den: 'Short description',
    vendor: 'myservice', icon: 'database', roles: ENG, kind: 'external', home: true },
] },

Le champ roles est la porte de visibilité : canSeeCap(cap, role, toolAccess) dans src/session/SessionProvider lit la même matrice d'accès aux outils que celle qui pilote la porte OPA (ADR-075) ; une carte n'apparaît donc que pour un utilisateur ayant droit à l'outil. Aucun proxy de santé à configurer : la santé par tuile vient du backend du cockpit.


Étape 5 -- Page (services intégrés uniquement)

Sautez cette étape pour un service external. Si le service est rendu dans le cockpit, ajoutez une fonctionnalité sous src/features/<x>/ :

src/features/myservice/
├── MyServicePage.tsx   # le composant de page
└── i18n.ts             # dictionnaire FR/EN (jamais de chaîne codée en dur dans le JSX)

La page lit ses chaînes depuis le dictionnaire i18n.ts via useLang() / un helper t(), à l'image des fonctionnalités existantes (RAG, NORA, ...).


Étape 6 -- Route (services intégrés uniquement)

Enregistrez la route de la page dans src/app/App.tsx, avant le catch-all x/:slug :

src/app/App.tsx
{ path: 'x/myservice', element: <MyServicePage /> },

Le cockpit utilise un HashRouter (ADR-074) : aucune règle de réécriture nginx n'est nécessaire pour servir l'îlot statique.


Étape 7 -- URL de démarrage

Ajoutez l'URL du service dans la bannière de démarrage dans scripts/start.sh :

echo "  MyService:     https://myservice.$AKKO_DOMAIN"

Étape 8 -- Documentation

Mettez à jour la documentation d'architecture et tout guide pertinent pour refléter le nouveau service.


Exemple complet : ajout de « DataHub »

Voici un exemple complet et concret d'ajout d'un service hypothétique DataHub (catalogue de métadonnées) à AKKO.

1. Sous-chart Helm

Créez helm/akko/charts/akko-datahub/ avec la structure standard de sous-chart :

helm/akko/charts/akko-datahub/
├── Chart.yaml
├── values.yaml
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   └── _helpers.tpl

Ajoutez la dépendance dans helm/akko/Chart.yaml :

- name: akko-datahub
  version: 0.1.0
  condition: akko-datahub.enabled

Exemple de values.yaml :

enabled: true
image:
  repository: acryldata/datahub-gms
  tag: "v0.13.0"
  pullPolicy: IfNotPresent
resources:
  requests:
    memory: 512Mi
    cpu: 250m
  limits:
    memory: 1Gi
    cpu: 500m

2. helm/examples/realm-akko-k3d.json

Ajoutez un client confidentiel dans le tableau clients :

{
  "clientId": "datahub",
  "enabled": true,
  "publicClient": false,
  "secret": "${KC_CLIENT_SECRET_DATAHUB}",
  "redirectUris": ["https://datahub.akko.local/*"],
  "webOrigins": ["https://datahub.akko.local"],
  "protocol": "openid-connect",
  "standardFlowEnabled": true,
  "directAccessGrantsEnabled": false
}

3. scripts/generate-secrets.sh

Ajoutez dans le heredoc :

# --- DataHub ---
DATAHUB_ADMIN_PASSWORD=$(gen_password)
KC_CLIENT_SECRET_DATAHUB=$(gen_hex)

4. branding/cockpit-react/src/platform/catalog.ts

DataHub est un catalogue de métadonnées avec sa propre UI : c'est donc une capacité external dans la couche Gouvernance :

{ id: 'datahub', tool: 'datahub', fr: 'Catalogue de métadonnées',
  en: 'Metadata catalog', dfr: 'Découverte & lignage',
  den: 'Discovery & lineage', vendor: 'datahub', icon: 'database',
  roles: ENG, kind: 'external', home: true },

5 & 6. Page et route (inutiles ici)

DataHub s'ouvre dans son propre onglet (kind: 'external') : ni page React ni route App.tsx ne sont nécessaires. Ces deux étapes ne concernent qu'un service integrated rendu dans le cockpit.

7. scripts/start.sh

echo "  DataHub:       https://datahub.$AKKO_DOMAIN"

Vérification

Après avoir complété toutes les étapes :

# Regénérer les secrets
rm .env && ./scripts/generate-secrets.sh

# Déployer avec Helm
helm upgrade akko helm/akko/ -n akko -f helm/examples/values-dev.yaml \
  --set-file akko-keycloak.realm.data=helm/examples/realm-akko-k3d.json

# Vérifier que DataHub est en bonne santé
kubectl get pods -n akko | grep datahub
kubectl logs -f deploy/akko-datahub -n akko

# Ouvrir le cockpit et confirmer que la tuile DataHub apparaît (pour un rôle habilité)
open https://demo.akko.local

# Ouvrir le service directement
open https://datahub.akko.local