Aller au contenu

Stockage objet

AKKO embarque une couche de stockage objet S3-compatible comme socle pour le lac de données Iceberg, les event logs Moteur de calcul, les artefacts MLflow, les uploads de PDF d'akko-rag, et tout autre besoin de stockage de blobs.

La surface publique est uniquement l'API S3, donc l'engine sous-jacent peut être remplacé (ou pointé vers AWS S3, Wasabi, OVH cloud-object- storage, etc.) sans changement de code dans la plateforme. Le déploiement vit dans le sub-chart akko-storage et tourne dans le même namespace akko que le reste de la plateforme.


Architecture

  Polaris ──────┐
                │ API S3 (signature v4)
  Trino ────────┼──→ akko-storage:<port-s3> ──→ /data PVC
  Spark ────────┤
  MLflow ───────┘

  akko-rag ─────→ /uploads/* via boto3
  Dashboards ADEN ─→ /reports/*

Tous les moteurs de calcul (Moteur de requête (Trino), Moteur de calcul, akko-rag, MLflow) accèdent à la couche de stockage via l'API S3 standard ; ils ne savent pas quel est l'engine derrière. Pour swap vers un autre fournisseur S3-compatible, changer l'URL endpoint + les credentials dans values.yaml — aucun changement côté consommateur.


Ports

Port Rôle Exposé
8333 API S3 (REST, signature v4) ClusterIP
8888 UI admin ClusterIP uniquement — port-forward pour ops
9091 /metrics Couche Métriques scrapé par ServiceMonitor

Accès externe via Traefik : https://storage.akko-ai.com (ou la valeur de global.functionalAliases.storage dans vos overrides) — gardé par oauth2-proxy + SSO Identité (Keycloak).


Buckets seedés à l'installation

Le Job post-install akko-storage-bucket-init crée les buckets dont la plateforme a besoin :

Bucket Service propriétaire Rôle
akko-iceberg Catalogue Iceberg (Polaris) / Moteur de requête / Moteur de calcul Données + métadonnées tables Iceberg
akko-spark-logs Moteur de calcul master + workers Event logs Moteur de calcul
akko-mlflow MLflow Artefacts du model registry
akko-rag akko-rag PDFs / DOCX / chunks uploadés
akko-aden ADEN Fichiers de dashboards persistés
akko-airflow-logs Moteur d'orchestration (Airflow) Logs de tâches

Pour ajouter un bucket, ajouter une entrée à helm/akko/charts/akko-storage/values.yaml :

storage:
  buckets:
    - name: mon-equipe
      versioning: false

Puis helm upgrade — le Job d'init est idempotent et ne crée que les buckets manquants.


Isolation par identité & matrice d'accès

Le stockage objet n'est pas un bucket partagé unique avec une clé admin. L'accès est segmenté sur deux axes, tous deux appliqués par le moteur IAM S3 de SeaweedFS (defaultEffect: Deny — fail-closed) :

1. Comptes de service scopés — chaque service plateforme a sa propre identité S3 restreinte aux buckets dont il a besoin (helm/akko/charts/akko-storage/values.yaml → seaweedfs.serviceAccounts) :

Identité Buckets Actions
svc-trino akko-warehouse Read, List
svc-spark akko-warehouse Read, Write, List, Tagging
svc-polaris akko-warehouse, polaris Read, Write, List, Tagging
svc-mlflow mlflow Read, Write, List, Tagging
svc-airflow airflow, logs Read, Write, List, Tagging

2. Espace personnel par utilisateur (STS / OIDC) — au spawn du notebook, l'ID token Keycloak de l'utilisateur est échangé (AssumeRoleWithWebIdentity) contre des credentials S3 temporaires (préfixe de clé ASIA… + un token de session) scopés par la politique templatée AkkoUserSpacePolicy à akko-users/${aws:username}/*. Un utilisateur ne peut lire/écrire/lister que son propre préfixe.

Matrice observée (live, vérifiée E2E)

Pilotée depuis un vrai notebook en tant que bob_engineer (SSO), avec les credentials STS injectés dans le pod — captures dans tests/artifacts/C_bob_seaweedfs_separation*.png :

Opération Cible Résultat
écriture + lecture akko-users/bob_engineer/… (le sien) ✅ OK
liste akko-users/bob_engineer/ (son préfixe) ✅ OK — uniquement ses objets
lecture akko-users/alice_admin/… (un autre user) AccessDenied
liste akko-users/alice_admin/ (un autre user) AccessDenied
écriture akko-users/alice_admin/… (un autre user) AccessDenied
liste akko-warehouse, mlflow, polaris, airflow (buckets de service) AccessDenied

Un utilisateur ne peut donc pas lire l'espace d'un autre, ni atteindre les buckets de service — l'identité STS personnelle est confinée à son propre préfixe.

La clé admin akko additive

Une identité admin akko avec accès complet existe encore comme repli transitoire tant que chaque consommateur n'est pas recâblé sur son compte de service scopé (durcissement gouvernance T1b → T8). C'est un item connu, suivi, pas un fail-open : les identités utilisateur et service sont déjà pleinement scopées comme ci-dessus.


Credentials

L'access key + secret S3 sont stockés dans le Secret akko-storage-creds et projetés dans chaque pod consommateur via les env vars :

AKKO_S3_ENDPOINT=http://akko-storage:8333
AKKO_S3_ACCESS_KEY=<via secret>
AKKO_S3_SECRET_KEY=<via secret>
AKKO_S3_REGION=akko-local

Référence rapide — boto3 depuis un notebook

import os, boto3
s3 = boto3.client(
    "s3",
    endpoint_url=os.environ["AKKO_S3_ENDPOINT"],
    aws_access_key_id=os.environ["AKKO_S3_ACCESS_KEY"],
    aws_secret_access_key=os.environ["AKKO_S3_SECRET_KEY"],
    region_name=os.environ.get("AKKO_S3_REGION", "akko-local"),
)
# Lister les buckets
print([b["Name"] for b in s3.list_buckets()["Buckets"]])
# Uploader un CSV
s3.upload_file("./customers.csv", "akko-iceberg", "raw/customers.csv")
# Le relire via Spark
df = spark.read.csv(
    "s3a://akko-iceberg/raw/customers.csv", header=True, inferSchema=True
)
df.show()

Référence rapide — CLI aws

export AWS_ACCESS_KEY_ID=$AKKO_S3_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=$AKKO_S3_SECRET_KEY
export AWS_DEFAULT_REGION=akko-local

aws --endpoint-url $AKKO_S3_ENDPOINT s3 ls
aws --endpoint-url $AKKO_S3_ENDPOINT s3 cp ./report.pdf \
    s3://akko-rag/uploads/report.pdf

Smoke test

Pour vérifier l'installation de bout en bout :

kubectl -n akko exec deploy/akko-akko-init-probe -- \
  curl -s http://akko-storage:8333/ | head
# Attendu : XML ListAllMyBucketsResult

kubectl -n akko logs job/akko-storage-bucket-init --tail=20
# Attendu : "[+] bucket <nom> ready" pour chaque bucket seedé

Notes opérationnelles

  • Réplication — déploiement single-node par défaut. Pour répliquer, poser storage.replication: "001" (une copie supplémentaire sur un rack différent — adapter à la topologie de votre cluster) et ajouter au moins 2 workers dans storage.workerCount.
  • Chiffrement au repos — chiffrement chart-level supporté mais désactivé par défaut. Activer storage.encryptionAtRest.enabled: true et provisionner la clé KMS selon le runbook de chiffrement.
  • Backup — l'approche recommandée est un CronJob aws s3 sync vers un endpoint S3-compatible off-site. Le log de DR drill capture la procédure de bout en bout.

Liens