Héberger Ollama en tant que pool de nœuds de calcul pour l'inférence

1. Introduction

Présentation

Dans cet atelier de programmation, vous allez apprendre à créer un pipeline de traitement d'IA asynchrone et axé sur les événements. Vous allez déployer un modèle Open Source à l'aide d'Ollama sur un pool de nœuds de calcul Cloud Run. Le pool de nœuds de calcul extrait les messages d'un sujet Pub/Sub et les traite à l'aide d'un modèle gemma3:4b.

Points abordés

  • Utiliser des pools de nœuds de calcul avec un abonnement pull Pub/Sub
  • Utiliser Ollama pour effectuer l'inférence en tant que pool de nœuds de calcul

2. Avant de commencer

Activer les API

Avant de commencer à utiliser cet atelier de programmation, activez les API suivantes en exécutant la commande :

gcloud services enable run.googleapis.com \
    cloudbuild.googleapis.com \
    artifactregistry.googleapis.com \
    pubsub.googleapis.com \
    storage.googleapis.com

3. Préparation

Pour configurer les ressources requises, procédez comme suit :

  1. Définissez les variables d'environnement pour cet atelier de programmation :
export PROJECT_ID=<YOUR_PROJECT_ID>
export REGION=<YOUR_REGION>

export BUCKET_NAME=$PROJECT_ID-gemma3-4b
export SERVICE_ACCOUNT_NAME=ollama-worker-sa
export SERVICE_ACCOUNT_EMAIL=${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com
export TOPIC_NAME=ollama-prompts
export SUBSCRIPTION_NAME=ollama-prompts-sub
export AR_REPO_NAME=ollama-worker-repo
export PULL_MSG_IMAGE_NAME=pubsub-pull-msg
export OLLAMA_IMAGE_NAME=ollama-coordinator
  1. Créer un compte de service pour le pool de nœuds de calcul
gcloud iam service-accounts create ${SERVICE_ACCOUNT_NAME} \
  --display-name="Ollama Worker Service Account"
  1. Accorder l'accès à Pub/Sub au compte de service
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
  --member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
  --role="roles/pubsub.subscriber"
  1. Créer un dépôt AR pour l'image du pool de nœuds de calcul
gcloud artifacts repositories create ${AR_REPO_NAME} \
  --repository-format=docker \
  --location=${REGION}
  1. Créer le sujet et l'abonnement Pub/Sub
gcloud pubsub topics create $TOPIC_NAME
gcloud pubsub subscriptions create $SUBSCRIPTION_NAME --topic $TOPIC_NAME

4. Télécharger et héberger le modèle sur GCS

Au lieu d'extraire le modèle directement dans le conteneur pendant le processus de compilation, ce qui peut être lent et inefficace, nous allons l'extraire sur une machine locale à l'aide de l'interface de ligne de commande Ollama, puis importer les fichiers du modèle dans un bucket GCS. Le pool de nœuds de calcul montera ensuite ce bucket pour accéder au modèle.

  1. Installez Ollama sur votre ordinateur local :

Exécutez la commande suivante pour installer Ollama sur Linux. Pour les autres systèmes d'exploitation, veuillez consulter le site Web d'Ollama.

curl -fsSL https://ollama.com/install.sh | sh
  1. Démarrez le service Ollama et extrayez le modèle :

Commencez par démarrer le service Ollama en arrière-plan.

ollama serve &
ollama pull gemma3:4b
  1. Créez un bucket GCS :

Créez le bucket GCS à l'aide de la variable d'environnement BUCKET_NAME que vous avez définie précédemment.

gsutil mb gs://${BUCKET_NAME}
  1. Importez les fichiers du modèle dans votre bucket GCS :

Ollama stocke les fichiers de modèle dans le répertoire ~/.ollama/models. Importez le contenu de ce répertoire dans votre bucket GCS. Tous les modèles que vous avez téléchargés seront copiés.

gsutil -m cp -r ~/.ollama/models/* gs://${BUCKET_NAME}/
  1. Accorder au compte de service l'accès au bucket Cloud Storage
gcloud storage buckets add-iam-policy-binding gs://${BUCKET_NAME} \
     --member=serviceAccount:${SERVICE_ACCOUNT_EMAIL} \
     --role=roles/storage.objectViewer

5. Créer le job Cloud Run

Le job Cloud Run utilise deux conteneurs :

  • ollama-coordinator : pour héberger Ollama et diffuser le modèle Gemma 3 4B
  • pubsub-pull-msg : pour extraire les messages d'un abonnement Pub/Sub et les transmettre au conteneur ollama-coordinator

Commencez par créer le conteneur ollama-coordinator.

  1. Créez un répertoire parent pour l'atelier de programmation :
mkdir codelab-ollama-wp
cd codelab-ollama-wp
  1. Créez un répertoire pour le conteneur ollama-coordinator.
mkdir ollama-coordinator
cd ollama-coordinator
  1. Créez un fichier Dockerfile avec le contenu suivant :
# Use the official Ollama image as a base image
FROM ollama/ollama

# Expose the port that Ollama listens on
EXPOSE 11434

# Set the entrypoint to start the Ollama server
ENTRYPOINT ["ollama", "serve"]
  1. Créer le conteneur Ollama
gcloud builds submit --tag ${REGION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPO_NAME}/${OLLAMA_IMAGE_NAME} --timeout=20m

Vous allez ensuite créer le conteneur pubsub-pull-msg.

  1. Créez un répertoire pour le conteneur pubsub-pull-msg.
cd ..
mkdir pubsub-pull-msg
cd pubsub-pull-msg
  1. Créer un objet Dockerfile
# Use the official Python image as a base image
FROM python:3.9-slim

# Set the working directory in the container
WORKDIR /app

# Copy the requirements file into the container
COPY requirements.txt .

# Install the required Python packages
RUN pip install --no-cache-dir -r requirements.txt

# Copy the Python script into the container
COPY main.py .

# Set the entrypoint to run the Python script
CMD ["python", "main.py"]
  1. Créez un fichier requirements.txt avec le contenu suivant :
google-cloud-pubsub
requests
  1. Créez un fichier main.py avec le contenu suivant :
import os
import sys
import requests
import json
from google.cloud import pubsub_v1

# --- Main Application Logic ---
print("--- Sidecar container script started ---")

# --- Environment and Configuration ---
project_id = os.environ.get("PROJECT_ID")
subscription_name = os.environ.get("SUBSCRIPTION_NAME")
ollama_api_url = "http://localhost:11434/api/generate"

if not project_id or not subscription_name:
    print("FATAL: PROJECT_ID and SUBSCRIPTION_NAME must be set.")
    sys.exit(1)

print(f"PROJECT_ID: {project_id}")
print(f"SUBSCRIPTION_NAME: {subscription_name}")

def callback(message):
    """Processes a single Pub/Sub message."""
    print(f"Received message ID: {message.message_id}")
    try:
        prompt = message.data.decode("utf-8")
        print(f"Decoded prompt: '{prompt}'")
        
        data = {"model": "gemma3:4b", "prompt": prompt, "stream": False}
        
        print("Sending request to Ollama...")
        response = requests.post(ollama_api_url, json=data, timeout=300)
        response.raise_for_status()
        
        print("Successfully received response from Ollama.")
        ollama_response = response.json()
        print(f"Ollama response: {json.dumps(ollama_response)[:200]}...")

        message.ack()
        print(f"Message {message.message_id} acknowledged.")

    except requests.exceptions.RequestException as e:
        print(f"Error calling Ollama API: {e}")
        message.nack()
        print(f"Message {message.message_id} not acknowledged.")
    except Exception as e:
        print(f"An unexpected error occurred in callback: {e}")
        message.nack()
        print(f"Message {message.message_id} not acknowledged.")

def main():
    """Starts the Pub/Sub subscriber."""
    subscriber = pubsub_v1.SubscriberClient()
    subscription_path = subscriber.subscription_path(project_id, subscription_name)
    
    streaming_pull_future = subscriber.subscribe(subscription_path, callback=callback)
    print(f"Subscribed to {subscription_path}. Listening for messages...")

    try:
        # .result() will block indefinitely.
        streaming_pull_future.result()
    except Exception as e:
        print(f"A fatal error occurred in the subscriber: {e}")
        streaming_pull_future.cancel()
        streaming_pull_future.result()

if __name__ == "__main__":
    main()
  1. Créez maintenant le conteneur pubsub-pull-msg.
gcloud builds submit --tag ${REGION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPO_NAME}/${PULL_MSG_IMAGE_NAME}

6. Déployer et exécuter le job

Dans cette étape, vous allez créer le job Cloud Run en déployant un fichier YAML.

Accédez au dossier racine pour créer le fichier YAML.

cd ..
  1. Créez un fichier worker-pool.template.yaml avec le contenu suivant :
apiVersion: run.googleapis.com/v1
kind: WorkerPool
metadata:
  name: codelab-ollama-wp
  labels:
    cloud.googleapis.com/location: europe-west1
  annotations:
    run.googleapis.com/launch-stage: BETA
    run.googleapis.com/scalingMode: manual
    run.googleapis.com/manualInstanceCount: '1'
    run.googleapis.com/gcs-fuse-mounter-enabled: "true"
spec:
  template:
    metadata:
      annotations:
        run.googleapis.com/gpu: "1"
        run.googleapis.com/gpu-zonal-redundancy-disabled: 'true'        
    spec:
      serviceAccountName: ${SERVICE_ACCOUNT_EMAIL}
      nodeSelector:
        run.googleapis.com/accelerator: nvidia-l4
      volumes:
      - name: gcs-bucket
        csi:
          driver: gcsfuse.run.googleapis.com
          readOnly: true
          volumeAttributes: 
            bucketName: ${BUCKET_NAME}
      containers:
      - image: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPO_NAME}/${PULL_MSG_IMAGE_NAME}
        name: pubsub-pull-msg
        env:
        - name: PROJECT_ID
          value: ${PROJECT_ID}
        - name: SUBSCRIPTION_NAME
          value: "ollama-prompts-sub"
        - name: PYTHONUNBUFFERED
          value: "1"
        resources:
          limits:
            cpu: '1'
            memory: 1Gi
      - image: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPO_NAME}/${OLLAMA_IMAGE_NAME}
        name: ollama-coordinator
        env:
        - name: OLLAMA_MODELS
          value: /mnt/models
        volumeMounts:
        - name: gcs-bucket
          mountPath: /mnt/models
        resources:
          limits:
            cpu: '6'
            nvidia.com/gpu: '1'
            memory: 16Gi

Définissez ensuite les URL complètes des images et utilisez sed pour remplacer les variables dans le fichier modèle, ce qui créera le fichier worker-pool.yaml final.

sed -e "s|\${SERVICE_ACCOUNT_EMAIL}|${SERVICE_ACCOUNT_EMAIL}|g" \
     -e "s|\${BUCKET_NAME}|${BUCKET_NAME}|g" \
     -e "s|\${PULL_MSG_IMAGE_NAME}|${PULL_MSG_IMAGE_NAME}|g" \
     -e "s|\${OLLAMA_IMAGE_NAME}|${OLLAMA_IMAGE_NAME}|g" \
     -e "s|\${PROJECT_ID}|${PROJECT_ID}|g" \
     -e "s|\${REGION}|${REGION}|g" \
     -e "s|\${AR_REPO_NAME}|${AR_REPO_NAME}|g" \
     worker-pool.template.yaml > worker-pool.yaml

Vous pouvez maintenant déployer

gcloud beta run worker-pools replace worker-pool.yaml

Et test

gcloud pubsub topics publish ${TOPIC_NAME} --message="What is 1 + 1?"

Affichez ensuite les journaux. Vous devrez peut-être attendre une minute ou accéder à la page du pool de nœuds de calcul Cloud Console et regarder les journaux en temps réel.

gcloud alpha run worker-pools logs read "codelab-ollama-wp" --limit 10

et vous devriez voir un message indiquant

Ollama response: {"model": "gemma3:4b", "created_at": "2025-11-06T23:48:39.572079369Z", "response": "1 + 1 = 2\n", ...

7. Félicitations !

Bravo ! Vous avez terminé cet atelier de programmation.

Nous vous recommandons de consulter la documentation Cloud Run.

Points abordés

  • Utiliser des pools de nœuds de calcul Cloud Run avec un abonnement Pub/Sub par extraction
  • Utiliser Ollama pour effectuer l'inférence en tant que pool de nœuds de calcul Cloud Run

8. Effectuer un nettoyage

Pour éviter que les ressources utilisées lors de ce tutoriel soient facturées sur votre compte Google Cloud, supprimez le projet contenant les ressources, ou conservez le projet et supprimez chaque ressource individuellement.

Supprimer le projet

Le moyen le plus simple d'empêcher la facturation est de supprimer le projet que vous avez créé pour ce tutoriel.

Pour supprimer le projet :

  1. Dans la console Google Cloud, accédez à la page Gérer les ressources.
  2. Dans la liste des projets, sélectionnez le projet que vous souhaitez supprimer, puis cliquez sur Supprimer.
  3. Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur Arrêter pour supprimer le projet.

Supprimer des ressources individuelles

Pour supprimer les ressources individuelles, exécutez les commandes suivantes :

  1. Supprimez le pool de nœuds de calcul Cloud Run :
gcloud beta run worker-pools delete codelab-ollama-wp --region ${REGION}
  1. Supprimez le bucket GCS :
gsutil -m rm -r gs://${BUCKET_NAME}
  1. Supprimez l'abonnement et le sujet Pub/Sub :
gcloud pubsub subscriptions delete ${SUBSCRIPTION_NAME}
gcloud pubsub topics delete ${TOPIC_NAME}
  1. Supprimez le dépôt Artifact Registry :
gcloud artifacts repositories delete ${AR_REPO_NAME} --location=${REGION} --quiet
  1. Supprimez le compte de service :
gcloud iam service-accounts delete ${SERVICE_ACCOUNT_EMAIL} --quiet

Nettoyer les fichiers locaux

Pour nettoyer les fichiers locaux, procédez comme suit :

  1. Arrêtez le service Ollama local : si vous avez démarré Ollama avec ollama serve &, vous pouvez l'arrêter en trouvant son ID de processus (PID), puis en utilisant la commande kill.
    # Find the process ID of the Ollama server
    pgrep ollama
    
    # Replace <PID> with the actual process ID obtained from the previous command
    kill <PID>
    
  2. Supprimez les modèles téléchargés :
rm -rf ~/.ollama/models
  1. Désinstallez Ollama :

Suivez les instructions sur le site Web d'Ollama pour désinstaller Ollama de votre ordinateur local.