GDC エアギャップでのオープンウェイト モデル用 Open WebUI のユーザーガイド

このドキュメントでは、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 ユーザーロール:

  • StandardClusterRole cluster-admin への StandardClusterRoleBinding(プロジェクト IAM 管理者によって作成。リファレンス実装の Identity and Access Management セクションを参照)

ワークステーション

特に記載がない限り、このガイドのすべてのコマンドは、必要なツールと環境およびインターネットへの接続を備えたワークステーションから実行します。

  1. ユーザーガイドのディレクトリ構造を作成します。

    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
    
  2. セッション署名鍵を作成します。

    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
    
  3. ユーザーガイドの環境構成ファイルを作成します。

    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 バックエンドをデプロイしていない場合は、空のままにします。
  4. ユーザーガイドの環境ローダー ファイルを作成します。

    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。このワークステーションで完了したリファレンス実装。
  5. 任意のエディタで環境ファイルを編集して確認します。

    ${EDITOR:-vi} ${HOME}/gdcag-solutions/open-weight-models/open-webui/env.d/open-webui.sh
    
  6. 環境ファイルを 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
    

クラスタ

  1. 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}"
    
  2. GDC 環境に対して認証します。

    gdcloud auth login
    
  3. クラスタの認証情報を取得します。

    gdcloud clusters get-credentials "${GDC_STANDARD_CLUSTER_NAME}" \
    --project="${GDC_PROJECT}" \
    --standard \
    --zone="${GDC_ZONE}"
    
  4. 環境ファイルで参照されている推論バックエンド 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
    

実装

アーティファクトの移行

  1. ソリューションに必要なコンテナ イメージのリストを定義します。

    declare -a GDCS_REGISTRY_IMAGES=(
      "ghcr.io/open-webui/open-webui:${GDCS_OPEN_WEBUI_IMAGE_TAG}"
    )
    export SERIALIZED_IMAGES=$(declare -p GDCS_REGISTRY_IMAGES)
    
  2. リファレンス実装のスクリプトを使用して、必要なコンテナ イメージを Artifact Registry にシードします。

    ${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh
    

名前空間

  1. Namespace を作成します。

    kubectl create namespace "${GDCS_WORKLOAD_NAMESPACE}"
    
  2. 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 -
    
  3. セッション署名鍵を使用して 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 -
    

ストレージ

  1. 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
    EOF
    
  2. PersistentVolumeClaim のマニフェストを適用します。

    kubectl apply \
    --filename="${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-rwo-pvc.yaml" \
    --namespace="${GDCS_WORKLOAD_NAMESPACE}"
    

ウェブ インターフェース

  1. 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
    EOF
    
  2. Open WebUI の ConfigMap、Deployment、Service のマニフェストを適用します。

    kubectl apply \
    --filename="${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-deployment.yaml" \
    --namespace="${GDCS_WORKLOAD_NAMESPACE}"
    
  3. 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
    EOF
    
  4. Open WebUI NetworkPolicy のマニフェストを適用します。

    kubectl apply \
    --filename="${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-netpol.yaml"
    
  1. 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'"
    
  2. 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'"
    
  3. 省略可: コンテナログをストリーミングして、起動の進行状況をモニタリングします。

    kubectl logs deployment/${GDCS_OPEN_WEBUI_NAME} \
    --follow \
    --namespace="${GDCS_WORKLOAD_NAMESPACE}"
    

検証

  1. 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}"
    
  2. ロードバランサを使用して、ヘルス エンドポイントとバージョン エンドポイントを確認します。

    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"}
    
  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}
    
  4. 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'
    
  5. ブラウザで http://${OPEN_WEBUI_LB_IP} を開き、最初のアカウントを作成します。最初に作成したアカウントが管理者になります。チャットビューでモデルを選択し、プロンプトを送信します。

運用

推論バックエンドを追加または変更する

  1. 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}"
    

アップグレード

  1. 環境ファイルに新しい 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}"
    

バックアップ

  1. データ ボリュームの VolumeSnapshot を作成します(standard VolumeSnapshotClass は 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 から復元する

クリーンアップ

  1. Open WebUI リソースを削除します。

    kubectl delete namespace "${GDCS_WORKLOAD_NAMESPACE}"
    
  2. ローカル ファイルを削除します。

    rm -rf ${HOME}/gdcag-solutions/open-weight-models/open-webui
    

その他の教材