Un assistant IA conversationnel, avec une interface aussi agréable qu'un ChatGPT, qui tourne intégralement dans votre propre projet Google Cloud, sur Kubernetes.

Aucune requête ne sort de votre VPC. Aucune donnée ne transite par un tiers. Le modèle, l'interface, l'authentification — tout est sous votre contrôle, dans votre facturation, avec vos propres règles de sécurité, et portable le jour où vous changez d'infrastructure. Pas de SaaS. Pas de clé API qui part chez un tiers. Juste votre cloud, votre Kubernetes, et un modèle open-source qui tourne chez vous.

L'architecture en trois briques

  • Le cerveau : un serveur d'inférence vLLM, qui charge un modèle open-source (Mistral-7B-Instruct) en mémoire GPU et expose une API compatible OpenAI.
  • L'interface : Open WebUI, une interface de chat web complète — historique, gestion de modèles, formatage markdown — connectée en interne à vLLM.
  • La porte d'entrée : une Gateway Kubernetes qui termine le TLS et restreint l'accès à une liste d'IP autorisées via Cloud Armor, avec Identity-Aware Proxy (IAP) comme évolution naturelle pour un déploiement multi-utilisateurs.

Pourquoi vLLM et Mistral-7B ?

vLLM est un moteur d'inférence open-source pensé pour servir des LLM à haut débit : il gère nativement le batching continu, la pagination de la mémoire GPU (PagedAttention), et surtout expose une API HTTP compatible OpenAI — n'importe quel client qui parle à l'API OpenAI (SDK, Open WebUI, LangChain...) fonctionne avec vLLM sans adaptation.

Mistral-7B est développé par Mistral AI, une entreprise française, et distribué en licence Apache 2.0 — accès libre, aucun token requis, réutilisation commerciale sans restriction. C'est un compromis solide entre légèreté (7 milliards de paramètres, tient sur un seul GPU) et qualité de réponse, largement suffisant pour un usage conversationnel d'entreprise.

Pourquoi un GPU, et pas du CPU ?

Un LLM comme Mistral-7B, c'est ~14 Go de poids à faire transiter à chaque token généré, avec des multiplications matricielles massives. Un CPU peut techniquement le faire — mais au prix d'une latence de plusieurs dizaines de secondes par réponse, inutilisable pour du conversationnel.

Un GPU est conçu pour paralléliser exactement ce type de calcul : des milliers de cœurs qui traitent les opérations matricielles simultanément, plus une mémoire VRAM à très haute bande passante pour garder le modèle chargé en permanence. Résultat : des réponses générées token par token en quasi temps réel, là où le CPU resterait figé.

Le socle réseau et compute

Tout commence par un VPC dédié, en mode custom, pour garder un contrôle total sur le plan d'adressage :

# VPC en mode custom + subnet régional VPC-native
gcloud compute networks create vpc-lab-llm \
  --subnet-mode=custom \
  --bgp-routing-mode=regional

Le cluster GKE, lui, reste régional — un choix délibéré pour la haute disponibilité du control plane, avec la Gateway API activée nativement :

gcloud container clusters create gke-lab-llm \
  --region=europe-west1 \
  --network=vpc-lab-llm \
  --subnetwork=subnet-lab-llm \
  --enable-ip-alias \
  --gateway-api=standard \
  --workload-pool=PROJECT_ID.svc.id.goog

Le GPU, lui, vit dans un node pool séparé — dédié, isolé, et facilement détruit indépendamment du reste du cluster. Un NVIDIA L4 (24 Go de VRAM, largement suffisant pour un modèle 7B) en instance Spot, pour un coût maîtrisé :

gcloud container node-pools create pool-gpu-l4 \
  --cluster=gke-lab-llm \
  --region=europe-west1 \
  --machine-type=g2-standard-8 \
  --accelerator=type=nvidia-l4,count=1,gpu-driver-version=latest \
  --num-nodes=1 \
  --spot

Déployer le modèle

Avant le Deployment, un PVC dédié cache les poids du modèle (~14 Go pour Mistral-7B) sur un disque persistant — sans lui, chaque redémarrage du pod retélécharge l'intégralité du modèle depuis Hugging Face. vLLM est packagé dans une image Docker (vllm/vllm-openai) qui expose directement une API HTTP compatible OpenAI — aucune intégration custom à écrire :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm
  namespace: lab-llm
spec:
  replicas: 1
  template:
    spec:
      nodeSelector:
        cloud.google.com/gke-accelerator: nvidia-l4
      volumes:
        - name: shm                # IPC entre APIServer et EngineCore
          emptyDir:
            medium: Memory
            sizeLimit: "2Gi"
        - name: cache-volume       # cache des poids HF
          persistentVolumeClaim:
            claimName: vllm-model-cache
      containers:
        - name: vllm
          image: vllm/vllm-openai:latest
          args:
            - --model=mistralai/Mistral-7B-Instruct-v0.3
            - --gpu-memory-utilization=0.90
            - --max-model-len=8192
            - --enforce-eager   # démarrage rapide, sans compilation CUDA
          resources:
            limits:
              nvidia.com/gpu: "1"
Deux détails qui coûtent cher à découvrir en prod. Le volume shm (2 Gi) est indispensable : vLLM fait tourner l'APIServer et l'EngineCore comme deux process séparés qui communiquent par mémoire partagée, et les 64 Mo par défaut de Kubernetes bloquent silencieusement ce transfert — pod Running, mais jamais Ready. Et --enforce-eager désactive la compilation CUDA (torch.compile) pour un démarrage en quelques minutes plutôt qu'en dix ou plus au premier lancement sur un L4.

Déployer l'interface

Open WebUI trouve son cerveau via l'URL interne du Service vLLM, résolue par le DNS natif de Kubernetes, et garde ses propres données (conversations, comptes, réglages) sur un PVC dédié :

env:
  - name: OPENAI_API_BASE_URL
    value: "http://vllm-svc.lab-llm.svc.cluster.local:8000/v1"
  - name: OPENAI_API_KEY
    value: "sk-local-key"

Exposer et sécuriser, sans écrire une ligne d'auth

C'est ici que la promesse « zéro ligne de code » prend tout son sens. La Gateway API GKE gère le TLS et l'authentification par simple configuration déclarative — aucun SDK d'auth, aucun middleware à coder. Le certificat est importé directement dans Certificate Manager, référencé par nom :

gcloud certificate-manager certificates create lab-llm-cert \
  --certificate-file=tls.crt \
  --private-key-file=tls.key \
  --location=europe-west1
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: lab-llm-gateway
  namespace: lab-llm
spec:
  gatewayClassName: gke-l7-regional-external-managed
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        options:
          networking.gke.io/cert-manager-certs: lab-llm-cert

Pour cette démo, la restriction d'accès se fait — pas se code — via Cloud Armor : une policy régionale qui n'autorise qu'une IP précise, et rejette tout le reste par défaut, attachée au Service via un GCPBackendPolicy, sans toucher au Deployment ni au code applicatif :

apiVersion: networking.gke.io/v1
kind: GCPBackendPolicy
metadata:
  name: openwebui-security-policy
  namespace: lab-llm
spec:
  default:
    securityPolicy: openwebui-allow-my-ip
  targetRef:
    kind: Service
    name: openwebui-svc

Le flux complet, de bout en bout :

Utilisateur (IP autorisée) → Gateway (TLS) → Cloud Armor (filtre IP) → Open WebUI → vLLM

Une restriction par IP est un bon filtre pour une démo ou un accès mono-utilisateur, mais elle reste une adresse réseau — pas une identité. Pour un déploiement multi-utilisateurs, l'étape naturelle est Identity-Aware Proxy (IAP) : GKE Gateway sait aussi déclarer l'authentification IAP directement sur le Service, via le même mécanisme de GCPBackendPolicy.

Résultat

Une fois le tout appliqué, l'accès se fait par un simple navigateur, depuis l'IP autorisée — le reste d'Internet reçoit un 403 de Cloud Armor avant même d'atteindre le pod.

Souveraineté Aucune requête ne sort du VPC. Modèle, interface et auth restent dans votre projet GCP.
Zéro ligne de code TLS, filtrage et auth pilotés par YAML déclaratif — Gateway API, Cloud Armor, IAP.
Coût maîtrisé GPU L4 en Spot, node pool isolé et destructible indépendamment du reste du cluster.

C'est la démonstration qu'un assistant IA privé n'est plus réservé aux équipes avec un budget MLOps conséquent. Avec GKE, la Gateway API et un peu de YAML, la souveraineté sur ses données conversationnelles devient une question de configuration — pas de développement.