概要
このドキュメントでは、Google Distributed Cloud(GDC)のエアギャップ環境に Gemma、Llama、DeepSeek などのオープン ウェイト大規模言語モデル(LLM)をデプロイする手順について説明します。このチュートリアルでは、高スループット サービングに vLLM を使用し、使いやすさのために Ollama を使用して、Kubernetes、Harbor、GPU リソースなどの GDC プラットフォームの機能を活用する方法について説明します。
アーキテクチャ
このソリューションでは、コンテナ化された LLM サービング バックエンド(vLLM、Ollama)をユーザー クラスタ内の Deployment としてデプロイします。モデルの重みは Persistent Volume に保存され、Harbor レジストリのイメージから入力されます。LoadBalancer タイプの Kubernetes Service は、バックエンドの API を公開します。プロジェクト ネットワーク ポリシーは、これらのサービスへのアクセスを保護します。

始める前に
次の前提条件を満たしていることを確認します。
- GDC エアギャップ バージョン 1.15.1 以降。
- 十分なリソース(CPU、メモリ、GPU)で作成されたユーザー クラスタ。
- NVIDIA A100 GPU が 1 つ以上必要です。
- Harbor インスタンスが利用可能でアクセス可能である。
- ユーザー クラスタにアクセスするように構成された
kubectlCLI とgdcloudCLI。 - Docker クライアントがインストールされ、Harbor に push するように構成されている。
- 必要な IAM 権限が付与されている(Namespace 管理者、クラスタ デベロッパーなど)。
- 制限付きモデルを使用する場合は、Hugging Face アカウントと認証が構成されている。
セクション 1: 共通設定
1.1 イメージ pull シークレットを作成する
GDC エアギャップでコンテナ ワークロードのイメージ pull シークレットを構成するには、限定公開の Harbor プロジェクトにアクセスするための認証情報を含む Kubernetes docker-registry シークレットを作成する必要があります。この Secret は、デプロイ仕様で参照されます。
非公開の Harbor プロジェクト内のイメージにプログラムでアクセスするには、Harbor ロボット アカウントを使用する必要があります。
イメージ pull シークレットを構成する手順は次のとおりです。
Harbor ロボット アカウントを作成します。
- Harbor インスタンスの UI に移動します。
- Harbor プロジェクトに移動します。
- [ロボット アカウント] タブを選択します。
- [New Robot Account] をクリックします。
- 名前(例:
oss-llm-puller)を付け、有効期限まで必要な権限(少なくとも pull アクセス)を付与します。 - ロボット アカウント名(
robot$oss-llm-pullerなど)と提供されたシークレット トークンを安全に保存します。
Docker を Harbor に対して認証します。
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 イメージ プルシークレットを作成します。
kubectl を使用して、前の手順で更新した Docker 構成ファイルを使用して、プロジェクトの名前空間に docker-registry タイプのシークレットを作成します。
# 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 イメージを pull して、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 インスタンスの URL とプロジェクト名に置き換えます。
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 を準備する
モデルがプリロードされた Ollama イメージをビルドする Dockerfile を作成します。
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 イメージをビルドして push する
イメージをビルドして push します。
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(7B パラメータではなく 405B パラメータの LLM など)を使用する場合は、スムーズに実行するために複数の GPU が必要になることがあります。
- 複数の GPU に適合するモデルでは、GPU 間の通信に関連するレイテンシが発生します。
横向き
- 特定の LLM で目標スループットを達成するために必要な数の Ollama Pod をデプロイします。
- これを行うには、対応する Ollama デプロイ YAML ファイルの replica の数を増やします。
- LoadBalancer タイプの Kubernetes Service は、コード アシスタンス リクエストをエンドポイント(Pod)間で分散し、公開された外部 IP を介してそれぞれのレスポンスを返します。
- Continue プラグインは、機能ごとに 1 つの IP アドレスのみを指します。

GDC のエアギャップ環境内で vLLM バックエンドをスケーリングするには、Ollama で使用される戦略と同様の戦略に従い、ハードウェア リソースの割り当てと Pod のレプリケーションの両方に焦点を当てます。
vLLM バックエンドを使用してソリューションをスケーリングする
縦向き
- GPU 割り当てをアップグレードする: 推論スループット(トークン/秒)がボトルネックになった場合は、NVIDIA A100 GPU をすべて使用するまで、より大きな GPU スライスを割り当てます。
- マルチ GPU 構成: 単一の 80 GB A100 のメモリに収まらない大規模なモデル(700 億から 4,050 億のパラメータなど)の場合は、テンソル並列処理を使用して複数の GPU にスケーリングする必要があります。
- レイテンシの考慮事項: 複数の GPU にまたがるモデルでは、GPU 間の通信(NCCL 同期など)に関連するわずかなオーバーヘッドが発生する可能性があります。
横向き
- レプリカを使用してスループットを増やす: 同じモデルに対する同時ユーザー リクエストの量が増えた場合は、vLLM デプロイ YAML でレプリカ数を増やします。
- 専用モデル インスタンス: vLLM は単一モデルのサービング エンジンとして設計されており、初期化時に KV キャッシュ メモリを固定するため、ホストする LLM ごとに個別の Pod セットをデプロイする必要があります。
- ロード バランシング: GDC Kubernetes サービス(タイプ LoadBalancer)は、そのサービスに関連付けられているすべての正常な vLLM Pod エンドポイントに受信推論リクエストを自動的に分散します。
5.4 トラブルシューティング
一般的なエラーと軽減策。
| エラー | 緩和策 |
|---|---|
| FIPS SELFTEST FAILURE | BoringSSL などのライブラリに完全性署名がない場合に発生します。BORINGSSL_FIPS=0 を設定し、公式の vLLM イメージを利用することで修正 |
| 体重の読み込みが停止する | 小規模な PVC で IOPS スロットリングを確認します。パフォーマンス モデルの初期化には 500 GiB のボリュームが必要です。 |
| 接続が拒否されました | PNP で targetPort(8000/8080)が明示的に許可されていることを確認します。GDC ファイアウォールは、ロードバランサ VIP のバックエンド ポートへのアクセスを自動的に許可しません。 |