Panduan pengguna WebUI Terbuka untuk Model Bobot Terbuka di GDC dengan air gap

Dokumen ini memberikan petunjuk langkah demi langkah untuk men-deploy Open WebUI, antarmuka chat berbasis browser yang dihosting sendiri, di lingkungan Google Distributed Cloud (GDC) dengan air gap. Open WebUI terhubung ke backend inferensi yang di-deploy dengan panduan pengguna Ollama dan vLLM melalui API yang kompatibel dengan Ollama dan OpenAI, sehingga developer dan pengguna akhir dapat berinteraksi dengan model open weight tanpa menulis panggilan API.

Arsitektur

Open WebUI berjalan sebagai Deployment replika tunggal di namespace-nya sendiri di cluster standar. Statusnya (akun pengguna, chat, setelan) disimpan di ReadWriteOnce PersistentVolumeClaim. ConfigMap menyimpan setelan koneksi untuk backend inferensi, yang dijangkau melalui nama Service dalam cluster, dan Secret menyimpan kunci penandatanganan sesi. Service Kubernetes dengan jenis LoadBalancer mengekspos antarmuka web, dan objek NetworkPolicy / ProjectNetworkPolicy mengamankan akses ke antarmuka tersebut.

Sebelum memulai

Pastikan setidaknya satu backend inferensi telah di-deploy dengan panduan pengguna model Gemma dengan Ollama, panduan pengguna model Gemma dengan vLLM, atau panduan pengguna model Mistral dengan vLLM, dan Anda memiliki akses ke direktori ${HOME}/gdcag-solutions dari implementasi referensi yang sesuai di workstation.

Identity and Access Management

Pastikan akun, peran, dan izin IAM yang diperlukan telah dikonfigurasi dengan benar.

Peran GDC User di organisasi:

  • Opsional: Standard Cluster Admin (Melihat cluster dari konsol)

Peran GDC User di project:

  • Standard Cluster Admin (standard-cluster-admin)

Peran GDC User di cluster standar:

  • StandardClusterRoleBinding ke StandardClusterRole cluster-admin (dibuat oleh Project IAM Admin, lihat bagian Identity and Access Management pada implementasi referensi)

Workstation

Jalankan semua perintah dalam panduan ini dari workstation dengan alat dan konektivitas yang diperlukan ke lingkungan dan internet, kecuali jika ada langkah yang menyatakan sebaliknya.

  1. Buat struktur direktori panduan pengguna:

    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. Buat kunci penandatanganan sesi:

    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. Buat file konfigurasi lingkungan panduan pengguna:

    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
    

    Ganti kode berikut:

    • OPEN_WEBUI_ENABLE_OLLAMA_API: true jika Anda men-deploy backend Ollama, jika tidak, false.
    • OPEN_WEBUI_OLLAMA_BASE_URLS: URL dalam cluster Layanan Ollama, dipisahkan dengan ;, misalnya http://gemma4-e4b.ollama-gemma.svc.cluster.local. Biarkan kosong jika Anda tidak men-deploy backend Ollama.
    • OPEN_WEBUI_ENABLE_OPENAI_API: true jika Anda men-deploy backend vLLM, jika tidak, false.
    • OPEN_WEBUI_OPENAI_API_BASE_URLS: URL dalam cluster dari Layanan vLLM dengan jalur /v1, dipisahkan dengan ;, misalnya http://mistralai-mistral-7b-instruct-v0-2.vllm-mistral.svc.cluster.local/v1 atau http://google-gemma-4-e2b-it.vllm-gemma.svc.cluster.local/v1. Biarkan kosong jika Anda tidak men-deploy backend vLLM.
  4. Buat file loader lingkungan panduan pengguna:

    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
    

    Ganti kode berikut:

    • IMPLEMENTATION: ollama atau vllm, implementasi referensi yang Anda selesaikan di workstation ini.
  5. Edit dan tinjau file lingkungan dengan editor pilihan Anda:

    ${EDITOR:-vi} ${HOME}/gdcag-solutions/open-weight-models/open-webui/env.d/open-webui.sh
    
  6. Ambil file lingkungan:

    source ${HOME}/gdcag-solutions/open-weight-models/open-webui/env.sh
    

    Outputnya mirip dengan hal berikut ini:

    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
    

Cluster

  1. Konfigurasikan 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. Lakukan autentikasi ke lingkungan GDC:

    gdcloud auth login
    
  3. Ambil kredensial cluster:

    gdcloud clusters get-credentials "${GDC_STANDARD_CLUSTER_NAME}" \
    --project="${GDC_PROJECT}" \
    --standard \
    --zone="${GDC_ZONE}"
    
  4. Pastikan backend inferensi Service yang dirujuk dalam file lingkungan ada:

    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
    

Penerapan

Migrasi artefak

  1. Tentukan daftar image container yang diperlukan untuk solusi:

    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. Isi image container yang diperlukan ke artifact registry dengan skrip implementasi referensi:

    ${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh
    

Namespace

  1. Buat namespace:

    kubectl create namespace "${GDCS_WORKLOAD_NAMESPACE}"
    
  2. Tambahkan 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. Buat Secret dengan kunci penandatanganan sesi:

    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 -
    

Penyimpanan

  1. Buat manifes untuk PersistentVolumeClaim untuk data Open WebUI:

    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. Terapkan manifes untuk PersistentVolumeClaim:

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

Antarmuka web

  1. Buat manifes untuk ConfigMap, Deployment, dan Service Open WebUI. Open WebUI berjalan dalam mode offline (OFFLINE_MODE), mempertahankan ConfigMap ini sebagai sumber kebenaran (ENABLE_PERSISTENT_CONFIG=false), dan menonaktifkan fitur yang memerlukan akses internet:

    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. Terapkan manifes untuk ConfigMap, Deployment, dan Service Open WebUI:

    kubectl apply \
    --filename="${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-deployment.yaml" \
    --namespace="${GDCS_WORKLOAD_NAMESPACE}"
    
  3. Buat manifes untuk NetworkPolicy Open WebUI:

    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. Terapkan manifes untuk NetworkPolicy Open WebUI:

    kubectl apply \
    --filename="${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-netpol.yaml"
    
  1. Tunggu hingga Pod berjalan:

    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. Tunggu hingga Deployment Siap:

    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. Opsional: Alirkan log container untuk memantau progres startup:

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

Validasi

  1. Ambil alamat IP LoadBalancer untuk Open WebUI:

    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. Periksa endpoint health dan version menggunakan load balancer:

    curl --show-error --silent http://${OPEN_WEBUI_LB_IP}/health && echo
    
    curl --show-error --silent http://${OPEN_WEBUI_LB_IP}/api/version && echo
    

    Outputnya mirip dengan hal berikut ini:

    {"status":true}
    {"version":"0.11.3"}
    
  3. Periksa endpoint health dan version menggunakan penerusan port:

    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. Pastikan Open WebUI dapat menjangkau backend inferensi:

    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. Buka http://${OPEN_WEBUI_LB_IP} di browser dan buat akun pertama; akun pertama yang dibuat akan menjadi administrator. Pilih model dalam tampilan chat dan kirim perintah.

Operasi

Menambahkan atau mengubah backend inferensi

  1. Perbarui GDCS_OPEN_WEBUI_OLLAMA_BASE_URLS atau GDCS_OPEN_WEBUI_OPENAI_API_BASE_URLS di env.d/open-webui.sh, ambil file lingkungan, dan jalankan langkah manifes di Antarmuka web lagi. Kemudian, terapkan manifes dan mulai ulang Open WebUI, yang membaca ConfigMap pada setiap startup karena konfigurasi persisten dinonaktifkan:

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

Upgrade

  1. Tetapkan GDCS_OPEN_WEBUI_IMAGE_TAG baru di file lingkungan, ambil sumbernya, semai image baru, dan luncurkan 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}"
    

Cadangan

  1. Buat VolumeSnapshot volume data (standard VolumeSnapshotClass tersedia di cluster standar 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
    

Pemecahan masalah

Gejala Kemungkinan penyebab Tindakan
Tidak ada model yang ditawarkan dalam tampilan chat Alamat Service backend salah, backend tidak Siap, atau NetworkPolicy backend tidak mengizinkan namespace Open WebUI Jalankan langkah verifikasi backend di Validation; periksa Deployment backend dan NetworkPolicy-nya
Pod dimulai ulang saat pengaktifan Peluncuran pertama yang lambat di PersistentVolume Periksa kubectl describe pod; startupProbe mengizinkan 10 menit
Upload dokumen (RAG) gagal Mode offline memblokir download model embedding default Konfigurasikan RAG_EMBEDDING_ENGINE=ollama dengan model embedding yang disajikan oleh Ollama, atau tetap nonaktifkan RAG
IP LoadBalancer tetap <pending> atau tidak dapat dijangkau Load balancer tidak tersedia di lingkungan, atau ProjectNetworkPolicy tidak ada Gunakan langkah validasi penerusan port; minta ProjectNetworkPolicy kepada Project NetworkPolicy Admin
Sesi tidak valid setelah memulai ulang Pod WEBUI_SECRET_KEY diubah Menjaga Secret tetap stabil; memulihkannya dari secrets/open_webui_secret_key

Pembersihan

  1. Hapus resource Open WebUI:

    kubectl delete namespace "${GDCS_WORKLOAD_NAMESPACE}"
    
  2. Hapus file lokal:

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

Materi tambahan