このドキュメントでは、Google Distributed Cloud(GDC)エアギャップ環境に、セルフホスト型のブラウザベースのチャット インターフェースである Open WebUI をデプロイする手順について説明します。Open WebUI は、Ollama と vLLM のユーザーガイドでデプロイされた推論バックエンドに、Ollama と OpenAI 互換の API を介して接続します。これにより、デベロッパーとエンドユーザーは、API 呼び出しを記述することなく、オープン ウェイト モデルを操作できます。
アーキテクチャ
Open WebUI は、標準クラスタの独自の Namespace で単一レプリカの Deployment として実行されます。その状態(ユーザー アカウント、チャット、設定)は ReadWriteOnce PersistentVolumeClaim に保存されます。ConfigMap には、クラスタ内の Service 名を介してアクセスされる推論バックエンドの接続設定が保持され、Secret にはセッション署名鍵が保持されます。LoadBalancer タイプの Kubernetes Service はウェブ インターフェースを公開し、NetworkPolicy / ProjectNetworkPolicy オブジェクトはウェブ インターフェースへのアクセスを保護します。
始める前に
Ollama を使用した Gemma モデルのユーザーガイド、vLLM を使用した Gemma モデルのユーザーガイド、または vLLM を使用した Mistral モデルのユーザーガイドに沿って、少なくとも 1 つの推論バックエンドがデプロイされていること、およびワークステーション上の対応するリファレンス実装の ${HOME}/gdcag-solutions ディレクトリにアクセスできることを確認します。
Identity and Access Management
必要な IAM アカウント、ロール、権限が正しく構成されていることを確認します。
組織に対する GDC ユーザーロール:
- 省略可: 標準クラスタ管理者(コンソールからクラスタを表示する)
プロジェクトに対する GDC ユーザーロール:
- 標準クラスタ管理者(
standard-cluster-admin)
標準クラスタに対する GDC ユーザーロール:
StandardClusterRolecluster-adminへのStandardClusterRoleBinding(プロジェクト IAM 管理者によって作成。リファレンス実装の Identity and Access Management セクションを参照)
ワークステーション
特に記載がない限り、このガイドのすべてのコマンドは、必要なツールと環境およびインターネットへの接続を備えたワークステーションから実行します。
ユーザーガイドのディレクトリ構造を作成します。
mkdir -p ${HOME}/gdcag-solutions/open-weight-models/open-webui/env.d mkdir -p ${HOME}/gdcag-solutions/open-weight-models/open-webui/secrets chmod u=rwx,go= ${HOME}/gdcag-solutions/open-weight-models/open-webui/secretsセッション署名鍵を作成します。
openssl rand -hex 32 > ${HOME}/gdcag-solutions/open-weight-models/open-webui/secrets/open_webui_secret_key chmod u=rw,go= ${HOME}/gdcag-solutions/open-weight-models/open-webui/secrets/open_webui_secret_keyユーザーガイドの環境構成ファイルを作成します。
cat << 'EOF' > ${HOME}/gdcag-solutions/open-weight-models/open-webui/env.d/open-webui.sh && echo "Successfully created." || echo "Failed to create!" # Open WebUI export GDCS_OPEN_WEBUI_IMAGE_TAG="v0.11.3" export GDCS_OPEN_WEBUI_NAME="open-webui" export GDCS_OPEN_WEBUI_PVC_SIZE="20Gi" export GDCS_OPEN_WEBUI_SECRET_KEY="$(cat ${GDCS_USER_GUIDE_HOME}/secrets/open_webui_secret_key)" # Inference backends (in-cluster Service addresses, several backends separated by ';'). # The user guides name the Service after the model: Ollama uses GDCS_OLLAMA_MODEL_KUBERNETES # (gemma4:e4b -> gemma4-e4b) and vLLM uses GDCS_HF_MODEL_KUBERNETES (such as vllm-gemma or vllm-mistral) # (mistralai/Mistral-7B-Instruct-v0.2 -> mistralai-mistral-7b-instruct-v0-2); kubectl get service # in the workload namespace (ollama-gemma, vllm-gemma or vllm-mistral) lists them. export GDCS_OPEN_WEBUI_ENABLE_OLLAMA_API="OPEN_WEBUI_ENABLE_OLLAMA_API" export GDCS_OPEN_WEBUI_OLLAMA_BASE_URLS="OPEN_WEBUI_OLLAMA_BASE_URLS" export GDCS_OPEN_WEBUI_ENABLE_OPENAI_API="OPEN_WEBUI_ENABLE_OPENAI_API" export GDCS_OPEN_WEBUI_OPENAI_API_BASE_URLS="OPEN_WEBUI_OPENAI_API_BASE_URLS" export GDCS_OPEN_WEBUI_OPENAI_API_KEYS="none" # vLLM does not check API keys unless started with --api-key # Workload export GDCS_WORKLOAD_NAMESPACE="open-webui" # CPU resources export GDCS_WORKLOAD_CPU_LIMIT="2" export GDCS_WORKLOAD_CPU_REQUEST="1" # Memory resources export GDCS_WORKLOAD_MEMORY_LIMIT="4Gi" export GDCS_WORKLOAD_MEMORY_REQUEST="2Gi" EOF次のように置き換えます。
OPEN_WEBUI_ENABLE_OLLAMA_API: Ollama バックエンドをデプロイした場合はtrue。それ以外の場合はfalse。OPEN_WEBUI_OLLAMA_BASE_URLS: Ollama Service のクラスタ内 URL。;で区切ります(例:http://gemma4-e4b.ollama-gemma.svc.cluster.local)。Ollama バックエンドをデプロイしていない場合は、空のままにします。OPEN_WEBUI_ENABLE_OPENAI_API: vLLM バックエンドをデプロイした場合はtrue。それ以外の場合はfalse。OPEN_WEBUI_OPENAI_API_BASE_URLS:/v1パスを含む vLLM Service のクラスタ内 URL。;で区切ります(例:http://mistralai-mistral-7b-instruct-v0-2.vllm-mistral.svc.cluster.local/v1、http://google-gemma-4-e2b-it.vllm-gemma.svc.cluster.local/v1)。vLLM バックエンドをデプロイしていない場合は、空のままにします。
ユーザーガイドの環境ローダー ファイルを作成します。
cat << 'EOF' > ${HOME}/gdcag-solutions/open-weight-models/open-webui/env.sh && echo "Successfully created." || echo "Failed to create!" source "${HOME}/gdcag-solutions/open-weight-models/IMPLEMENTATION/env.sh" export GDCS_USER_GUIDE_HOME="${HOME}/gdcag-solutions/open-weight-models/open-webui" echo "GDCS_USER_GUIDE_HOME=${GDCS_USER_GUIDE_HOME}" # Sourced in dependency order source "${GDCS_USER_GUIDE_HOME}/env.d/open-webui.sh" EOF次のように置き換えます。
IMPLEMENTATION:ollamaまたはvllm。このワークステーションで完了したリファレンス実装。
任意のエディタで環境ファイルを編集して確認します。
${EDITOR:-vi} ${HOME}/gdcag-solutions/open-weight-models/open-webui/env.d/open-webui.sh環境ファイルを source コマンドで実行します。
source ${HOME}/gdcag-solutions/open-weight-models/open-webui/env.sh出力は次のようになります。
GDCS_ROOT_HOME=HOME_DIRECTORY_PATH/gdcag-solutions GDCS_IMPLEMENTATION_HOME=HOME_DIRECTORY_PATH/gdcag-solutions/open-weight-models/IMPLEMENTATION GDCS_USER_GUIDE_HOME=HOME_DIRECTORY_PATH/gdcag-solutions/open-weight-models/open-webui
クラスタ
gdcloudを構成します。gdcloud config set core/account "default-user" gdcloud config set core/organization_console_url "${GDC_ZONAL_CONSOLE_URL}" gdcloud config set core/project "${GDC_PROJECT}" gdcloud config set core/zone "${GDC_ZONE}"GDC 環境に対して認証します。
gdcloud auth loginクラスタの認証情報を取得します。
gdcloud clusters get-credentials "${GDC_STANDARD_CLUSTER_NAME}" \ --project="${GDC_PROJECT}" \ --standard \ --zone="${GDC_ZONE}"環境ファイルで参照されている推論バックエンド
Serviceが存在することを確認します。for url in $(echo "${GDCS_OPEN_WEBUI_OLLAMA_BASE_URLS};${GDCS_OPEN_WEBUI_OPENAI_API_BASE_URLS}" | tr ';' ' '); do host=$(echo "${url}" | sed -E 's#https?://([^/:]+).*#\1#') kubectl get service "${host%%.*}" --namespace="$(echo "${host}" | cut -d. -f2)" --output=wide done
実装
アーティファクトの移行
ソリューションに必要なコンテナ イメージのリストを定義します。
declare -a GDCS_REGISTRY_IMAGES=( "ghcr.io/open-webui/open-webui:${GDCS_OPEN_WEBUI_IMAGE_TAG}" ) export SERIALIZED_IMAGES=$(declare -p GDCS_REGISTRY_IMAGES)リファレンス実装のスクリプトを使用して、必要なコンテナ イメージを Artifact Registry にシードします。
${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh
名前空間
Namespace を作成します。
kubectl create namespace "${GDCS_WORKLOAD_NAMESPACE}"imagePullSecretを追加します。kubectl create secret docker-registry "${GDCS_HARBOR_K8S_PULL_SECRET}" \ --dry-run=client \ --from-file=.dockerconfigjson=${GDCS_HARBOR_K8S_DOCKER_CONFIG}/config.json \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ --output=yaml | kubectl apply -f -セッション署名鍵を使用して
Secretを作成します。kubectl create secret generic "${GDCS_OPEN_WEBUI_NAME}" \ --dry-run=client \ --from-literal=WEBUI_SECRET_KEY="${GDCS_OPEN_WEBUI_SECRET_KEY}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ --output=yaml | kubectl apply -f -
ストレージ
Open WebUI データの
PersistentVolumeClaimのマニフェストを作成します。cat <<EOF > "${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-rwo-pvc.yaml" && echo "Successfully created." || echo "Failed to create!" apiVersion: v1 kind: PersistentVolumeClaim metadata: name: ${GDCS_OPEN_WEBUI_NAME}-rwo spec: accessModes: - ReadWriteOnce resources: requests: storage: ${GDCS_OPEN_WEBUI_PVC_SIZE} storageClassName: standard-rwo volumeMode: Filesystem EOFPersistentVolumeClaimのマニフェストを適用します。kubectl apply \ --filename="${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-rwo-pvc.yaml" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}"
ウェブ インターフェース
Open WebUI の
ConfigMap、Deployment、Serviceのマニフェストを作成します。Open WebUI はオフライン モード(OFFLINE_MODE)で実行され、このConfigMapを信頼できる情報源(ENABLE_PERSISTENT_CONFIG=false)として保持し、インターネット アクセスが必要な機能を無効にします。cat <<EOF > "${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-deployment.yaml" && echo "Successfully created." || echo "Failed to create!" apiVersion: v1 kind: ConfigMap metadata: name: ${GDCS_OPEN_WEBUI_NAME} data: DATA_DIR: "/app/backend/data" ENABLE_COMMUNITY_SHARING: "false" ENABLE_IMAGE_GENERATION: "false" ENABLE_OLLAMA_API: "${GDCS_OPEN_WEBUI_ENABLE_OLLAMA_API}" ENABLE_OPENAI_API: "${GDCS_OPEN_WEBUI_ENABLE_OPENAI_API}" ENABLE_PERSISTENT_CONFIG: "false" ENABLE_SIGNUP: "true" ENABLE_VERSION_UPDATE_CHECK: "false" ENABLE_WEB_SEARCH: "false" OFFLINE_MODE: "true" OLLAMA_BASE_URLS: "${GDCS_OPEN_WEBUI_OLLAMA_BASE_URLS}" OPENAI_API_BASE_URLS: "${GDCS_OPEN_WEBUI_OPENAI_API_BASE_URLS}" OPENAI_API_KEYS: "${GDCS_OPEN_WEBUI_OPENAI_API_KEYS}" PORT: "8080" --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: ${GDCS_OPEN_WEBUI_NAME} name: ${GDCS_OPEN_WEBUI_NAME} spec: replicas: 1 selector: matchLabels: app: ${GDCS_OPEN_WEBUI_NAME} strategy: type: Recreate template: metadata: labels: app: ${GDCS_OPEN_WEBUI_NAME} spec: containers: - envFrom: - configMapRef: name: ${GDCS_OPEN_WEBUI_NAME} - secretRef: name: ${GDCS_OPEN_WEBUI_NAME} image: ${GDCS_HARBOR_PROJECT_URI}/open-webui/open-webui:${GDCS_OPEN_WEBUI_IMAGE_TAG} imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 3 httpGet: path: /health port: 8080 periodSeconds: 30 timeoutSeconds: 10 name: open-webui ports: - containerPort: 8080 name: http readinessProbe: failureThreshold: 3 httpGet: path: /health port: 8080 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 5 resources: limits: cpu: "${GDCS_WORKLOAD_CPU_LIMIT}" memory: "${GDCS_WORKLOAD_MEMORY_LIMIT}" requests: cpu: "${GDCS_WORKLOAD_CPU_REQUEST}" memory: "${GDCS_WORKLOAD_MEMORY_REQUEST}" securityContext: allowPrivilegeEscalation: false startupProbe: failureThreshold: 60 httpGet: path: /health port: 8080 periodSeconds: 10 timeoutSeconds: 5 volumeMounts: - mountPath: /app/backend/data name: data imagePullSecrets: - name: ${GDCS_HARBOR_K8S_PULL_SECRET} volumes: - name: data persistentVolumeClaim: claimName: ${GDCS_OPEN_WEBUI_NAME}-rwo --- apiVersion: v1 kind: Service metadata: name: ${GDCS_OPEN_WEBUI_NAME} spec: ports: - name: http port: 80 protocol: TCP targetPort: 8080 selector: app: ${GDCS_OPEN_WEBUI_NAME} type: LoadBalancer EOFOpen WebUI の
ConfigMap、Deployment、Serviceのマニフェストを適用します。kubectl apply \ --filename="${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-deployment.yaml" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}"Open WebUI
NetworkPolicyのマニフェストを作成します。cat <<EOF > "${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-netpol.yaml" && echo "Successfully created." || echo "Failed to create!" apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-${GDCS_OPEN_WEBUI_NAME}-ingress namespace: ${GDCS_WORKLOAD_NAMESPACE} spec: ingress: - from: - ipBlock: cidr: 0.0.0.0/0 - namespaceSelector: {} ports: - port: 8080 protocol: TCP podSelector: {} policyTypes: - Ingress EOFOpen WebUI
NetworkPolicyのマニフェストを適用します。kubectl apply \ --filename="${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-netpol.yaml"
Podが Running になるまで待ちます。watch --color --interval 5 --no-title \ "kubectl get pods --selector=app=${GDCS_OPEN_WEBUI_NAME} \ --namespace=${GDCS_WORKLOAD_NAMESPACE} | GREP_COLORS='mt=01;92' grep -E --color=always -e '^' -e 'Running'"Deploymentが Ready になるまで待ちます。watch --color --interval 5 --no-title \ "kubectl get deployment/${GDCS_OPEN_WEBUI_NAME} \ --namespace=${GDCS_WORKLOAD_NAMESPACE} | GREP_COLORS='mt=01;92' grep -E --color=always -e '^' -e '1/1 1 1'"省略可: コンテナログをストリーミングして、起動の進行状況をモニタリングします。
kubectl logs deployment/${GDCS_OPEN_WEBUI_NAME} \ --follow \ --namespace="${GDCS_WORKLOAD_NAMESPACE}"
検証
Open WebUI の LoadBalancer IP アドレスを取得します。
export OPEN_WEBUI_LB_IP=$(kubectl get service/${GDCS_OPEN_WEBUI_NAME} --namespace="${GDCS_WORKLOAD_NAMESPACE}" --output=jsonpath='{.status.loadBalancer.ingress[*].ip}') echo "OPEN_WEBUI_LB_IP=${OPEN_WEBUI_LB_IP}"ロードバランサを使用して、ヘルス エンドポイントとバージョン エンドポイントを確認します。
curl --show-error --silent http://${OPEN_WEBUI_LB_IP}/health && echo curl --show-error --silent http://${OPEN_WEBUI_LB_IP}/api/version && echo出力は次のようになります。
{"status":true} {"version":"0.11.3"}ポート転送を使用して、ヘルス エンドポイントとバージョン エンドポイントを確認します。
kubectl port-forward service/${GDCS_OPEN_WEBUI_NAME} \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" 8080:80 & PF_PID=$! sleep 2 curl --show-error --silent http://127.0.0.1:8080/health && echo curl --show-error --silent http://127.0.0.1:8080/api/version && echo kill ${PF_PID}Open WebUI が推論バックエンドに到達できることを確認します。
kubectl exec deployment/${GDCS_OPEN_WEBUI_NAME} \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ -- sh -c 'for url in $(echo "${OLLAMA_BASE_URLS};${OPENAI_API_BASE_URLS}" | tr ";" " "); do case "${url}" in *"/v1") echo "${url}/models"; curl --max-time 10 --show-error --silent "${url}/models" | head -c 300; echo;; *) echo "${url}/api/tags"; curl --max-time 10 --show-error --silent "${url}/api/tags" | head -c 300; echo;; esac; done'ブラウザで
http://${OPEN_WEBUI_LB_IP}を開き、最初のアカウントを作成します。最初に作成したアカウントが管理者になります。チャットビューでモデルを選択し、プロンプトを送信します。
運用
推論バックエンドを追加または変更する
env.d/open-webui.shのGDCS_OPEN_WEBUI_OLLAMA_BASE_URLSまたはGDCS_OPEN_WEBUI_OPENAI_API_BASE_URLSを更新し、環境ファイルをソースとして、ウェブ インターフェースでマニフェスト ステップを再度実行します。次に、マニフェストを適用して Open WebUI を再起動します。永続的な構成が無効になっているため、起動するたびにConfigMapが読み取られます。kubectl apply \ --filename="${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-deployment.yaml" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" kubectl rollout restart deployment/${GDCS_OPEN_WEBUI_NAME} \ --namespace="${GDCS_WORKLOAD_NAMESPACE}"
アップグレード
環境ファイルに新しい
GDCS_OPEN_WEBUI_IMAGE_TAGを設定し、これをソースにして、新しいイメージをシードし、Deploymentをロールします。source ${HOME}/gdcag-solutions/open-weight-models/open-webui/env.sh declare -a GDCS_REGISTRY_IMAGES=( "ghcr.io/open-webui/open-webui:${GDCS_OPEN_WEBUI_IMAGE_TAG}" ) export SERIALIZED_IMAGES=$(declare -p GDCS_REGISTRY_IMAGES) ${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh kubectl set image deployment/${GDCS_OPEN_WEBUI_NAME} \ open-webui="${GDCS_HARBOR_PROJECT_URI}/open-webui/open-webui:${GDCS_OPEN_WEBUI_IMAGE_TAG}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}"
バックアップ
データ ボリュームの
VolumeSnapshotを作成します(standardVolumeSnapshotClassは GDC 標準クラスタで使用できます)。cat <<EOF | kubectl apply --namespace="${GDCS_WORKLOAD_NAMESPACE}" -f - apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshot metadata: name: ${GDCS_OPEN_WEBUI_NAME}-$(date +%Y%m%d-%H%M) spec: source: persistentVolumeClaimName: ${GDCS_OPEN_WEBUI_NAME}-rwo volumeSnapshotClassName: standard EOF
トラブルシューティング
| 症状 | 考えられる原因 | アクション |
|---|---|---|
| チャットビューにモデルが表示されない | バックエンド Service のアドレスが間違っている、バックエンドが準備完了していない、またはバックエンド NetworkPolicy が Open WebUI Namespace を許可していない |
[検証] でバックエンド検証ステップを実行します。バックエンド Deployment とその NetworkPolicy を確認します。 |
Pod が起動中に再起動する |
PersistentVolume での最初の起動が遅い |
kubectl describe pod を確認します。startupProbe では 10 分間許可されます |
| ドキュメント アップロード(RAG)が失敗する | オフライン モードでデフォルトのエンベディング モデルのダウンロードがブロックされる | Ollama が提供するエンベディング モデル提供を使用して RAG_EMBEDDING_ENGINE=ollama を構成するか、RAG を無効のままにします。 |
LoadBalancer IP が <pending> のままになるか、到達できない |
ロードバランサが環境で使用できないか、ProjectNetworkPolicy がない |
ポート転送の検証ステップを使用します。プロジェクトの NetworkPolicy 管理者に ProjectNetworkPolicy をリクエストします。 |
Pod の再起動後にセッションが無効になる |
WEBUI_SECRET_KEYを変更しました |
Secret を安定した状態に保ち、secrets/open_webui_secret_key から復元する |
クリーンアップ
Open WebUI リソースを削除します。
kubectl delete namespace "${GDCS_WORKLOAD_NAMESPACE}"ローカル ファイルを削除します。
rm -rf ${HOME}/gdcag-solutions/open-weight-models/open-webui