Cómo alojar Ollama como un grupo de trabajadores para la inferencia

1. Introducción

Descripción general

En este codelab, aprenderás a compilar una canalización de procesamiento de IA asíncrona y basada en eventos. Implementarás un modelo de código abierto con Ollama en un grupo de trabajadores de Cloud Run. El grupo de trabajadores extrae mensajes de un tema de Pub/Sub y los procesa con un modelo de gemma3:4b.

Qué aprenderás

  • Cómo usar grupos de trabajadores con una suscripción de extracción de Pub/Sub
  • Cómo usar Ollama para realizar inferencias como un grupo de trabajadores

2. Antes de comenzar

Habilita las APIs

Antes de comenzar a usar este codelab, habilita las siguientes APIs ejecutando el siguiente comando:

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

3. Configuración y requisitos

Para configurar los recursos necesarios, sigue estos pasos:

  1. Configura las variables de entorno para este codelab:
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. Crea una cuenta de servicio para el grupo de trabajadores
gcloud iam service-accounts create ${SERVICE_ACCOUNT_NAME} \
  --display-name="Ollama Worker Service Account"
  1. Otorga acceso a Pub/Sub a la SA
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
  --member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
  --role="roles/pubsub.subscriber"
  1. Crea un repositorio de RA para la imagen del grupo de trabajadores
gcloud artifacts repositories create ${AR_REPO_NAME} \
  --repository-format=docker \
  --location=${REGION}
  1. Crea el tema y la suscripción de Pub/Sub
gcloud pubsub topics create $TOPIC_NAME
gcloud pubsub subscriptions create $SUBSCRIPTION_NAME --topic $TOPIC_NAME

4. Descarga y aloja el modelo en GCS

En lugar de extraer el modelo directamente dentro del contenedor durante el proceso de compilación, lo que puede ser lento e ineficiente, extraeremos el modelo a una máquina local con la CLI de Ollama y, luego, subiremos los archivos del modelo a un bucket de GCS. Luego, el grupo de trabajadores activará el bucket para acceder al modelo.

  1. Instala Ollama en tu equipo local:

Ejecuta el siguiente comando para instalar Ollama en Linux. Para otros sistemas operativos, consulta el sitio web de Ollama.

curl -fsSL https://ollama.com/install.sh | sh
  1. Inicia el servicio de Ollama y extrae el modelo:

Primero, inicia el servicio de Ollama en segundo plano.

ollama serve &
ollama pull gemma3:4b
  1. Crea un bucket de GCS:

Crea el bucket de GCS con la variable de entorno BUCKET_NAME que configuraste antes.

gsutil mb gs://${BUCKET_NAME}
  1. Sube los archivos del modelo a tu bucket de GCS:

Ollama almacena los archivos de modelo en el directorio ~/.ollama/models. Sube el contenido de este directorio a tu bucket de GCS. Se copiarán todos los modelos que descargaste.

gsutil -m cp -r ~/.ollama/models/* gs://${BUCKET_NAME}/
  1. Otorga acceso a la SA al bucket de Cloud Storage
gcloud storage buckets add-iam-policy-binding gs://${BUCKET_NAME} \
     --member=serviceAccount:${SERVICE_ACCOUNT_EMAIL} \
     --role=roles/storage.objectViewer

5. Crea el trabajo de Cloud Run

El trabajo de Cloud Run usa 2 contenedores:

  • ollama-coordinator: Para alojar Ollama y entregar el modelo Gemma 3 4B
  • pubsub-pull-msg: Para extraer de la suscripción de Pub/Sub y pasar el mensaje al contenedor de ollama-coordinator

Primero, crearás el contenedor ollama-coordinator.

  1. Crea un directorio principal para el codelab:
mkdir codelab-ollama-wp
cd codelab-ollama-wp
  1. Crea un directorio para el contenedor de ollama-coordinator
mkdir ollama-coordinator
cd ollama-coordinator
  1. Crea un Dockerfile con el siguiente contenido:
# 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. Compila el contenedor de Ollama
gcloud builds submit --tag ${REGION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPO_NAME}/${OLLAMA_IMAGE_NAME} --timeout=20m

A continuación, crearás el contenedor pubsub-pull-msg.

  1. Crea un directorio para el contenedor pubsub-pull-msg
cd ..
mkdir pubsub-pull-msg
cd pubsub-pull-msg
  1. Cómo crear un 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. Crea un archivo requirements.txt con el siguiente contenido:
google-cloud-pubsub
requests
  1. Crea un archivo main.py con el siguiente contenido:
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. Ahora compila el contenedor pubsub-pull-msg
gcloud builds submit --tag ${REGION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPO_NAME}/${PULL_MSG_IMAGE_NAME}

6. Implementa y ejecuta el trabajo

En este paso, crearás el trabajo de Cloud Run implementando un archivo YAML.

Muévete a la carpeta raíz para crear el archivo YAML.

cd ..
  1. Crea un archivo worker-pool.template.yaml con el siguiente contenido:
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

Luego, define las URLs de las imágenes completas y usa sed para sustituir las variables en el archivo de plantilla, lo que creará el 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

Ahora puedes implementar

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

Y prueba

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

Luego, visualiza los registros. Es posible que debas esperar un minuto o puedes ir a la página del grupo de trabajadores de Cloud Console y mirar los registros en tiempo real.

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

y deberías ver algo que diga

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

7. ¡Felicitaciones!

¡Felicitaciones por completar el codelab!

Te recomendamos que revises la documentación de Cloud Run.

Temas abordados

  • Cómo usar grupos de trabajadores de Cloud Run con una suscripción de extracción de Pub/Sub
  • Cómo usar Ollama para realizar inferencias como un grupo de trabajadores de Cloud Run

8. Limpia

Para evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos usados en este instructivo, borra el proyecto que contiene los recursos o conserva el proyecto y borra los recursos individuales.

Borra el proyecto

La manera más fácil de eliminar la facturación es borrar el proyecto que creaste para el instructivo.

Para borrar el proyecto, haz lo siguiente:

  1. En la consola de Google Cloud, ve a la página Administrar recursos.
  2. En la lista de proyectos, elige el proyecto que deseas borrar y haz clic en Borrar.
  3. En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrarlo.

Borra recursos individuales

Para borrar los recursos individuales, ejecuta los siguientes comandos:

  1. Borra el grupo de trabajadores de Cloud Run:
gcloud beta run worker-pools delete codelab-ollama-wp --region ${REGION}
  1. Borra el bucket de GCS:
gsutil -m rm -r gs://${BUCKET_NAME}
  1. Borra el tema y la suscripción de Pub/Sub:
gcloud pubsub subscriptions delete ${SUBSCRIPTION_NAME}
gcloud pubsub topics delete ${TOPIC_NAME}
  1. Borra el repositorio de Artifact Registry:
gcloud artifacts repositories delete ${AR_REPO_NAME} --location=${REGION} --quiet
  1. Borra la cuenta de servicio:
gcloud iam service-accounts delete ${SERVICE_ACCOUNT_EMAIL} --quiet

Cómo limpiar archivos locales

Para limpiar los archivos locales, haz lo siguiente:

  1. Detén el servicio local de Ollama:Si iniciaste Ollama con ollama serve &, puedes detenerlo buscando su ID de proceso (PID) y, luego, usando el comando 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. Borra los modelos descargados:
rm -rf ~/.ollama/models
  1. Desinstala Ollama:

Sigue las instrucciones del sitio web de Ollama para desinstalar Ollama de tu equipo local.