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 keycloak/realm-akko.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/index.html Ajouter une carte de service au portail
5 branding/cockpit/app.js Enregistrer le point de contrôle de santé
6 branding/cockpit/nginx.conf Ajouter une route de proxy pour le health check (évite les problèmes CORS)
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 keycloak/realm-akko.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 -- Carte cockpit

Ajoutez une carte dans branding/cockpit/index.html à l'intérieur de la section <div class="grid">. Chaque carte suit cette structure :

<a class="card"
   data-service="<service-id>"
   data-subdomain="<subdomain>"
   data-health="<service-id>"
   data-category="<category>"
   href="#" target="_blank" rel="noopener">
  <div class="card__header">
    <div class="card__icon">
      <svg viewBox="0 0 24 24" aria-hidden="true">
        <!-- Chemins d'icône SVG -->
      </svg>
    </div>
    <div class="card__status">
      <svg class="uptime-ring" width="28" height="28" viewBox="0 0 28 28" aria-hidden="true">
        <circle cx="14" cy="14" r="11"/>
        <circle class="ring-fill" cx="14" cy="14" r="11"/>
      </svg>
      <span class="status-dot status-dot--checking"></span>
    </div>
  </div>
  <h4 class="card__name">Service Name</h4>
  <p class="card__desc">Short description</p>
  <button class="fav-btn" title="Pin" aria-label="Pin Service Name">&#9734;</button>
</a>

Attributs clés :

  • data-service -- identifiant unique du service (utilisé pour les favoris, le filtrage)
  • data-subdomain -- le sous-domaine utilisé dans l'URL (par ex. jupyter pour lab.akko.local, configurable via global.domain)
  • data-health -- doit correspondre à la clé dans l'objet SERVICES de app.js
  • data-category -- utilisé pour le filtrage par catégorie (ui, data, monitoring, infra)

Étape 5 -- Health check dans app.js

Ajoutez une entrée dans l'objet SERVICES de branding/cockpit/app.js :

const SERVICES = {
  // ... services existants ...
  myservice: { name: 'MyService', endpoint: '/api/health/myservice' },
};

Le cockpit interroge ces endpoints toutes les 15 secondes et met à jour le point d'état et l'anneau de disponibilité sur chaque carte.


Étape 6 -- Proxy de santé Nginx

Ajoutez un bloc de reverse proxy dans branding/cockpit/nginx.conf pour transférer la requête de health check du cockpit vers le service réel (évite les problèmes CORS puisque le cockpit s'exécute sur un sous-domaine différent) :

location /api/health/myservice {
    set $upstream_myservice http://myservice:8080;
    proxy_pass $upstream_myservice/health;
    proxy_connect_timeout 3s;
    proxy_read_timeout 3s;
}

Résolution dynamique

Utilisez toujours set $upstream_xxx avec une variable avant proxy_pass. Cela exploite la directive resolver 127.0.0.11 pour que nginx démarre même si le service cible est indisponible. Sans cela, nginx plante au démarrage lorsqu'un service est hors ligne.


É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. keycloak/realm-akko.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/index.html

<a class="card"
   data-service="datahub"
   data-subdomain="datahub"
   data-health="datahub"
   data-category="data"
   href="#" target="_blank" rel="noopener">
  <div class="card__header">
    <div class="card__icon">
      <svg viewBox="0 0 24 24" aria-hidden="true">
        <ellipse cx="12" cy="5" rx="9" ry="3"/>
        <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/>
        <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>
      </svg>
    </div>
    <div class="card__status">
      <svg class="uptime-ring" width="28" height="28" viewBox="0 0 28 28" aria-hidden="true">
        <circle cx="14" cy="14" r="11"/>
        <circle class="ring-fill" cx="14" cy="14" r="11"/>
      </svg>
      <span class="status-dot status-dot--checking"></span>
    </div>
  </div>
  <h4 class="card__name">DataHub</h4>
  <p class="card__desc">Metadata Catalog</p>
  <button class="fav-btn" title="Pin" aria-label="Pin DataHub">&#9734;</button>
</a>

5. branding/cockpit/app.js

const SERVICES = {
  // ... existants ...
  datahub: { name: 'DataHub', endpoint: '/api/health/datahub' },
};

6. branding/cockpit/nginx.conf

location /api/health/datahub {
    set $upstream_datahub http://datahub:8080;
    proxy_pass $upstream_datahub/health;
    proxy_connect_timeout 3s;
    proxy_read_timeout 3s;
}

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

# Tester le proxy de santé du cockpit
curl -sf https://cockpit.akko.local/api/health/datahub

# Ouvrir dans le navigateur
open https://datahub.akko.local