GDC 网闸隔离配置上的开放权重 LLM 参考实现

概览

本文档提供了分步说明,介绍了如何在 Google Distributed Cloud (GDC) 的气隙环境中部署开放权重的大语言模型 (LLM),例如 Gemma、Llama 和 DeepSeek。它涵盖了如何使用 vLLM 实现高吞吐量服务,以及如何使用 Ollama 实现易用性,同时利用 GDC 平台的功能,包括 Kubernetes、Harbor 和 GPU 资源。

架构

该解决方案涉及在用户集群中将容器化的 LLM 服务后端 (vLLM、Ollama) 部署为 Deployment。模型权重存储在 Persistent Volume 上,这些 Persistent Volume 是从 Harbor 注册表中的映像填充的。LoadBalancer 类型的 Kubernetes 服务会公开后端 API。项目网络政策可确保对这些服务的访问安全无虞。

开放权重 LLM 参考实现架构图。

准备工作

确保满足以下前提条件:

  • GDC 网闸隔离配置 1.15.1 或更高版本。
  • 创建的用户集群具有充足的资源(CPU、内存、GPU)。
  • 至少需要 1 个 NVIDIA A100 GPU。
  • Harbor 实例可用且可访问。
  • 已配置 kubectl 和 gdcloud CLI 以访问用户集群。
  • Docker 客户端已安装并配置为推送到 Harbor。
  • 已授予必要的 IAM 权限(例如,Namespace Admin、Cluster Developer)。
  • 如果使用受限模型,则需要配置 Hugging Face 账号和身份验证。

第 1 部分:常见设置

1.1 创建映像拉取 Secret

如需为 GDC 网闸隔离配置中的容器工作负载配置映像拉取 Secret,您需要创建一个 Kubernetes docker-registry Secret,其中包含用于访问您的私有 Harbor 项目的凭据。然后,在部署规范中引用此 Secret。

您应使用 Harbor 机器人账号以编程方式访问私有 Harbor 项目中的映像。

请按以下步骤配置映像拉取 Secret:

创建 Harbor 机器人账号:

  • 前往 Harbor 实例界面。
  • 前往您的 Harbor 项目。
  • 选择机器人账号标签页。
  • 点击 New Robot Account(新建机器人账号)。
  • 为其指定名称(例如,oss-llm-puller),并授予其必要的权限(至少是 pull 访问权限),直到过期时间为止。
  • 安全地存储机器人账号名称(例如 robot$oss-llm-puller)和提供的 Secret 令牌。

向 Harbor 验证 Docker 的身份:

在安装了 Docker 且可访问 Harbor 注册表的机器上,使用机器人账号凭据登录:

export INSTANCE_URL="HARBOR_INSTANCE_URL"
# for example, harbor1-project1.org1.zone1.google.gdc.com

export ROBOT_NAME="ROBOT_ACCOUNT_NAME"
# for example, robot\$oss-llm-puller (note how we escape the $ character)

export ROBOT_SECRET="ROBOT_ACCOUNT_SECRET"

docker login ${INSTANCE_URL} --username ${ROBOT_NAME} --password ${ROBOT_SECRET}

创建 Kubernetes 映像拉取 Secret:

使用 kubectl 在项目命名空间中创建类型为 docker-registry 的 Secret,使用在上一步中更新的 Docker 配置文件:

# Log in into GDC environment using the next commands
gdcloud auth login --login-config-cert WEB_TLS_CERT_PATH
gdcloud clusters get-credentials KUBERNETES_CLUSTER
kubectl config set-context --current --namespace=NAMESPACE

export SECRET_NAME="OSS_LLM_PULL_SECRET"
export NAMESPACE="PROJECT_NAMESPACE"
# Assuming default Docker config path. Adjust if necessary.
export DOCKER_CONFIG_PATH="$HOME/.docker/config.json"

kubectl create secret docker-registry ${SECRET_NAME} \
      --from-file=.dockerconfigjson=${DOCKER_CONFIG_PATH} \
      -n ${NAMESPACE}

第 2 部分:使用 vLLM 进行部署

2.1 获取 vLLM Docker 映像

在具有互联网访问权限的机器上,拉取 vLLM Docker 映像,然后将其转移到您的 Harbor 项目:

# Pull and Tag vLLM (v0.13.0 recommended for stability)
docker pull vllm/vllm-openai:v0.13.0
docker tag vllm/vllm-openai:v0.13.0 HARBOR_URL/PROJECT/vllm-openai:v0.13.0
docker push HARBOR_URL/PROJECT/vllm-openai:v0.13.0

将 HARBOR_URL 和 PROJECT 替换为您的 Harbor 实例网址和项目名称。

2.2 在 PVC 中准备模型权重

创建 YAML 文件(例如 model-pvc.yaml):

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: model-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 500Gi
  storageClassName: standard-rwo
  volumeMode: Filesystem

应用 PVC:kubectl apply -f model-pvc.yaml

从 Hugging Face 下载权重:

hf auth login
hf download google/gemma-3-4b-it

使用辅助 pod(例如 helper-pod.yaml)将权重转移到 PVC。 确保您在 Harbor 中有一个 busybox 映像。

# Push busybox if not present
docker pull busybox:latest
docker tag busybox HARBOR_URL/PROJECT/busybox:latest
docker push HARBOR_URL/PROJECT/busybox:latest

# Contents of helper-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: model-uploader
spec:
  containers:
  - name: uploader
    image: HARBOR_URL/PROJECT/busybox:latest
    command: ["sleep", "3600"]
    volumeMounts:
    - name: model-data
      mountPath: /data
  imagePullSecrets:
  - name: oss-llm-pull-secret
  volumes:
  - name: model-data
    persistentVolumeClaim:
      claimName: model-pvc

应用 pod 并复制文件:

kubectl apply -f helper-pod.yaml
# Wait for pod to be Running
kubectl cp ~/.cache/huggingface/hub/ NAMESPACE/model-uploader:/data/
kubectl delete pod model-uploader

2.3 部署 vLLM 后端

创建部署文件 vllm-gemma-3-4b-it-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gemma-3-4b-it
  labels:
    app: gemma-3-4b-it
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gemma-3-4b-it
  template:
    metadata:
      labels:
        app: gemma-3-4b-it
    spec:
      volumes:
      - name: cache-volume
        persistentVolumeClaim:
          claimName: model-pvc
      - name: shm
        emptyDir:
          medium: Memory
          sizeLimit: "16Gi"
      containers:
      - name: gemma-3-4b-it
        image: HARBOR_URL/PROJECT/vllm-openai:v0.13.0
        command: ["python3"]
        args: [
          "-m",
          "vllm.entrypoints.openai.api_server",
          "--model",
          "google/gemma-3-4b-it",
          "--max-model-len",
          "32768",
          "--enforce-eager"
        ]
        env:
        - name: HF_HUB_OFFLINE
          value: "1"
        - name: HF_HOME
          value: "/model"
        - name: NCCL_P2P_DISABLE
          value: "1"
        - name: NCCL_IB_DISABLE
          value: "1"
        - name: BORINGSSL_FIPS
          value: "0"
        - name: OPENSSL_FIPS
          value: "0"
        - name: OPENSSL_CONF
          value: "/dev/null"
        - name: FIPS_SIG
          value: "off"
        ports:
        - containerPort: 8000
        securityContext:
          privileged: true
          runAsUser: 0
        resources:
          limits:
            nvidia.com/gpu-pod-NVIDIA_A100_80GB_PCIE: 1
            cpu: "8"
            memory: "64Gi"
          requests:
            nvidia.com/gpu-pod-NVIDIA_A100_80GB_PCIE: 1
            cpu: "8"
            memory: "32Gi"
        volumeMounts:
        - name: cache-volume
          mountPath: /model
        - name: shm
          mountPath: /dev/shm
      imagePullSecrets:
      - name: oss-llm-pull-secret

创建服务文件 vllm-gemma-3-4b-it-service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: gemma-3-4b-it
  namespace: NAMESPACE
spec:
  ports:
  - name: http-gemma-3-4b-it
    port: 80
    protocol: TCP
    targetPort: 8000
  selector:
    app: gemma-3-4b-it
  sessionAffinity: None
  type: LoadBalancer

应用配置:

kubectl apply -f vllm-gemma-3-4b-it-deployment.yaml
kubectl apply -f vllm-gemma-3-4b-it-service.yaml

2.4 配置网络政策

应用 ProjectNetworkPolicy 资源以允许入站流量到达 vLLM 服务端口 (8000)。创建 vllm-netpol.yaml:

apiVersion: networking.gdc.goog/v1
kind: ProjectNetworkPolicy
metadata:
  name: allow-vllm-ingress
  namespace: NAMESPACE
spec:
  subject:
    subjectType: UserWorkload
  policyType: Ingress
  ingress:
  - from:
    - ipBlock:
        cidr: 0.0.0.0/0 # Restrict this in production
    ports:
    - protocol: TCP
      port: 8000

应用政策:kubectl apply -f vllm-netpol.yaml

第 3 部分:使用 Ollama 进行部署

3.1 准备 Dockerfile

创建 Dockerfile 以构建预加载了您的模型(一个或多个)的 Ollama 映像:

FROM ubuntu

RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates zstd
RUN curl -fsSL https://ollama.com/install.sh -o install.sh
RUN chmod +x install.sh
RUN ./install.sh && \
    rm -rf /var/lib/apt/lists/*

# Pre-pull gemma3 model
RUN ollama serve & \
    sleep 5 && \
    curl --retry 10 --retry-connrefused -s http://localhost:11434 || true && \
    ollama pull gemma3:latest && \
    pkill ollama || true

EXPOSE 11434
CMD ["ollama", "serve"]

3.2 构建并推送映像

构建并推送映像:

docker build -t ollama-gemma3 .
docker tag ollama-gemma3 HARBOR_URL/PROJECT/ollama-gemma3:latest
docker push HARBOR_URL/PROJECT/ollama-gemma3:latest

3.3 部署 Ollama 后端

创建 ollama-gemma3.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ollama-gemma3
  namespace: NAMESPACE
  labels:
    app: ollama-gemma3
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ollama-gemma3
  template:
    metadata:
      labels:
        app: ollama-gemma3
    spec:
      containers:
      - name: ollama-gemma3
        image: HARBOR_URL/PROJECT/ollama-gemma3:latest
        env:
        - name: OLLAMA_HOST
          value: "0.0.0.0"
        imagePullPolicy: Always
        ports:
        - containerPort: 11434
        securityContext:
          privileged: true
          runAsUser: 0
        resources:
          limits:
            nvidia.com/gpu-pod-NVIDIA_A100_80GB_PCIE: 1
          requests:
            nvidia.com/gpu-pod-NVIDIA_A100_80GB_PCIE: 1
      imagePullSecrets:
      - name: oss-llm-pull-secret
---
apiVersion: v1
kind: Service
metadata:
  name: ollama-gemma3
  namespace: NAMESPACE
spec:
  type: LoadBalancer
  selector:
    app: ollama-gemma3
  ports:
  - name: ollama-gemma3-port
    port: 11434
    protocol: TCP
    targetPort: 11434

应用清单:kubectl apply -f ollama-gemma3.yaml

3.4 配置网络政策

创建 ollama-netpol.yaml:

apiVersion: networking.gdc.goog/v1
kind: ProjectNetworkPolicy
metadata:
  name: allow-ollama-ingress
  namespace: NAMESPACE
spec:
  subject:
    subjectType: UserWorkload
  policyType: Ingress
  ingress:
  - from:
    - ipBlock:
        cidr: 0.0.0.0/0 # Restrict this for production.
    ports:
    - protocol: TCP
      port: 11434

应用政策:kubectl apply -f ollama-netpol.yaml

第 4 部分:验证

通过检查 pod 状态、服务 IP 地址并使用 curl 向 vLLM 和 Ollama 的 LoadBalancer IP 地址发送测试推理请求,来验证部署。

检查所有容器和服务是否都处于 Running 状态:

# Login into GDC air-gapped using the next commands
gdcloud auth login --login-config-cert WEB_TLS_CERT_PATH
gdcloud clusters get-credentials KUBERNETES_CLUSTER
kubectl config set-context --current --namespace=NAMESPACE

# Pods
kubectl get pods

# Services
kubectl get services

测试 vLLM:

export VLLM_IP=$(kubectl get service gemma-3-4b-it -n NAMESPACE -o jsonpath='{.status.loadBalancer.ingress[*].ip}')
curl http://${VLLM_IP}/v1/chat/completion \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-3-4b-it",
    "messages": [
      {"role": "user", "content": "What is Google Distributed Cloud air-gapped?"}
    ],
    "max_tokens": 100
  }'

测试 Ollama:

export OLLAMA_IP=$(kubectl get service ollama-gemma3 -n NAMESPACE -o jsonpath='{.status.loadBalancer.ingress[*].ip}')
# Check if Ollama is running
curl http://${OLLAMA_IP}:11434
# Send a completion request
curl -X POST http://${OLLAMA_IP}:11434/v1/completions \
-H "Content-Type: application/json" \
-d '{
  "model": "gemma3:latest",
  "prompt": "Google Distributed Cloud air-gapped is a",
  "max_tokens": 128,
  "temperature": 0.90,
  "stream": false
}'

第 5 部分:操作和问题排查

5.1 vLLM 操作

检查日志:kubectl logs -f -n NAMESPACE

查询内部状态(从调试 pod):

wget -qO- http://gemma-3-4b-it/v1/models
wget -qO- http://gemma-3-4b-it/health

5.2 Ollama 操作

访问 CLI:kubectl exec -it -n NAMESPACE -- sh

Pod 内:ollama list、ollama ps

5.3 扩缩

使用 Ollama 后端扩缩解决方案

垂直

  • 为已成为瓶颈的 LLM 分配更大的 GPU 切片,直到使用完整的 GPU。
  • 如果您想使用更大的 LLM 来提高回答准确性,例如使用 4,050 亿参数的 LLM 而不是 70 亿参数的 LLM,则可能需要多个 GPU 才能顺畅运行。
  • 如果模型适合多个 GPU,则会因 GPU 间通信而产生一些延迟时间。

横向

  • 根据需要在给定 LLM 上部署尽可能多的 Ollama pod,以实现目标吞吐量。
    • 为此,请增加相应 Ollama 部署 YAML 文件中的 replica 数量。
  • Kubernetes 服务(类型为 LoadBalancer)将在端点(即 pod)之间分配代码辅助请求,并通过公开的外部 IP 返回各自的响应。
  • 请注意,Continue 插件的每个功能仅指向一个 IP 地址。

开放权重 LLM 伸缩架构图。

如需在 GDC 气隙环境中扩缩 vLLM 后端,您可以遵循与 Ollama 类似的策略,同时侧重于硬件资源分配和 pod 复制。

使用 vLLM 后端扩缩解决方案

垂直

  • 升级 GPU 分配:如果推理吞吐量(令牌/秒)成为瓶颈,请分配更大的 GPU 切片,直到使用完整的 NVIDIA A100 GPU。
  • 多 GPU 配置:对于无法放入单个 80GB A100 内存中的大型模型(例如,参数数量为 700 亿到 4050 亿),您必须使用张量并行处理扩展到多个 GPU。
  • 延迟时间注意事项:请注意,跨多个 GPU 的模型可能会因 GPU 间通信(例如 NCCL 同步)而产生少量开销。

横向

  • 通过副本提高吞吐量:为了处理针对同一模型的更多并发用户请求,请在 vLLM 部署 YAML 中增加副本数量。
  • 专用模型实例:由于 vLLM 被设计为单模型服务引擎,并在初始化时固定其 KV 缓存内存,因此您必须为要托管的每个不同的 LLM 部署一组单独的 pod。
  • 负载均衡:GDC Kubernetes 服务(类型为 LoadBalancer)会自动将传入的推理请求分配给与该服务关联的所有运行正常的 vLLM pod 端点。

5.4 问题排查

常见错误和缓解措施。

错误 缓解
FIPS 自检失败 当 BoringSSL 等库缺少完整性签名时,会发生此错误。通过设置 BORINGSSL_FIPS=0 并利用官方 vLLM 映像来修复
权重加载停滞 检查小型 PVC 是否存在 IOPS 限制。性能模型初始化需要 500GiB 的卷。
连接遭拒 确认 PNP 明确允许 targetPort (8000/8080)。GDC 防火墙不会自动授予对负载均衡器 VIP 的后端端口的访问权限。