Neste documento, apresentamos instruções detalhadas para implantar a Open WebUI, uma interface de chat auto-hospedada e baseada em navegador, em ambientes com isolamento físico do Google Distributed Cloud (GDC). A Open WebUI se conecta aos back-ends de inferência implantados com os guias do usuário do Ollama e do vLLM pelas APIs compatíveis com o Ollama e a OpenAI. Assim, os desenvolvedores e usuários finais podem interagir com os modelos de pesos abertos sem escrever chamadas de API.
Arquitetura
A Open WebUI é executada como um Deployment de réplica única no próprio namespace do
cluster padrão. O estado (contas de usuário, chats, configurações) é armazenado em um
ReadWriteOnce PersistentVolumeClaim. Um ConfigMap contém as configurações de conexão
para os back-ends de inferência, que são acessados pelos nomes Service
no cluster, e um Secret contém a chave de assinatura da sessão. Um Service do Kubernetes do tipo LoadBalancer expõe a interface da Web, e os objetos NetworkPolicy/ProjectNetworkPolicy protegem o acesso a ela.
Antes de começar
Verifique se pelo menos um back-end de inferência foi implantado com o
guia do usuário de modelos do Gemma com o Ollama, o
guia do usuário de modelos do Gemma com o vLLM ou o guia do usuário de modelos do Mistral
com o vLLM e se você
tem acesso ao diretório ${HOME}/gdcag-solutions da implementação de referência
correspondente na estação de trabalho.
Identity and Access Management
Verifique se as contas, os papéis e as permissões necessários do IAM estão configurados corretamente.
Papéis de usuário do GDC na organização:
- Opcional: administrador de cluster padrão (visualizar clusters no console)
Papéis de usuário do GDC no projeto:
- Administrador de cluster padrão (
standard-cluster-admin)
Papel de usuário do GDC no cluster padrão:
StandardClusterRoleBindingpara oStandardClusterRolecluster-admin(criado por um administrador do IAM do projeto, consulte a seção Identity and Access Management da implementação de referência)
Estação de trabalho
Execute todos os comandos deste guia em uma estação de trabalho com as ferramentas e a conectividade necessárias para o ambiente e a Internet, a menos que uma etapa indique o contrário.
Crie a estrutura de diretórios do guia do usuário:
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/secretsCrie a chave de assinatura da sessão:
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_keyCrie o arquivo de configuração do ambiente do guia do usuário:
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" EOFSubstitua:
OPEN_WEBUI_ENABLE_OLLAMA_API:truese você implantou um back-end do Ollama. Caso contrário,false.OPEN_WEBUI_OLLAMA_BASE_URLS: os URLs no cluster dos serviços do Ollama, separados por;, por exemplo,http://gemma4-e4b.ollama-gemma.svc.cluster.local. Deixe em branco se você não implantou nenhum back-end do Ollama.OPEN_WEBUI_ENABLE_OPENAI_API:truese você implantou um back-end vLLM. Caso contrário,false.OPEN_WEBUI_OPENAI_API_BASE_URLS: os URLs no cluster dos serviços vLLM com o caminho/v1, separados por;, por exemplo,http://mistralai-mistral-7b-instruct-v0-2.vllm-mistral.svc.cluster.local/v1ouhttp://google-gemma-4-e2b-it.vllm-gemma.svc.cluster.local/v1. Deixe em branco se você não tiver implantado nenhum back-end do vLLM.
Crie o arquivo do carregador de ambiente do guia do usuário:
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" EOFSubstitua:
IMPLEMENTATION:ollamaouvllm, a implementação de referência que você concluiu nesta estação de trabalho.
Edite e revise o arquivo de ambiente com seu editor preferido:
${EDITOR:-vi} ${HOME}/gdcag-solutions/open-weight-models/open-webui/env.d/open-webui.shUse o comando source no arquivo de ambiente:
source ${HOME}/gdcag-solutions/open-weight-models/open-webui/env.shO resultado será o seguinte:
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
Configurar
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}"Autentique-se no ambiente do GDC:
gdcloud auth loginRecupere as credenciais do cluster:
gdcloud clusters get-credentials "${GDC_STANDARD_CLUSTER_NAME}" \ --project="${GDC_PROJECT}" \ --standard \ --zone="${GDC_ZONE}"Verifique se os
Services de back-end de inferência referenciados no arquivo de ambiente existem: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
Implementação
Migração de artefatos
Defina a lista de imagens de contêiner necessárias para a solução:
declare -a GDCS_REGISTRY_IMAGES=( "ghcr.io/open-webui/open-webui:${GDCS_OPEN_WEBUI_IMAGE_TAG}" ) export SERIALIZED_IMAGES=$(declare -p GDCS_REGISTRY_IMAGES)Propague as imagens de contêiner necessárias para o Artifact Registry com o script da implementação de referência:
${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh
Namespace
Crie o namespace:
kubectl create namespace "${GDCS_WORKLOAD_NAMESPACE}"Adicione o
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 -Crie o
Secretcom a chave de assinatura da sessão: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 -
Armazenamento
Crie o manifesto para o
PersistentVolumeClaimdos dados da 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 EOFAplique o manifesto para o
PersistentVolumeClaim:kubectl apply \ --filename="${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-rwo-pvc.yaml" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}"
Interface da Web
Crie o manifesto para o
ConfigMap,DeploymenteServicedo Open WebUI. A Open WebUI é executada no modo off-line (OFFLINE_MODE), mantém esseConfigMapcomo a fonte de verdade (ENABLE_PERSISTENT_CONFIG=false) e desativa os recursos que precisam de acesso à 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 EOFAplique o manifesto para o
ConfigMap,DeploymenteServiceda Open WebUI:kubectl apply \ --filename="${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-deployment.yaml" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}"Crie o manifesto para o 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 EOFAplique o manifesto para a Open WebUI
NetworkPolicy:kubectl apply \ --filename="${GDCS_USER_GUIDE_HOME}/${GDCS_OPEN_WEBUI_NAME}-netpol.yaml"
Aguarde até que o
Podesteja em execução: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'"Aguarde até que o
Deploymentesteja pronto: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'"Opcional: transmita os registros do contêiner para monitorar o progresso da inicialização:
kubectl logs deployment/${GDCS_OPEN_WEBUI_NAME} \ --follow \ --namespace="${GDCS_WORKLOAD_NAMESPACE}"
Validação
Recupere o endereço IP do LoadBalancer para o 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}"Verifique os endpoints de integridade e versão usando o balanceador de carga:
curl --show-error --silent http://${OPEN_WEBUI_LB_IP}/health && echo curl --show-error --silent http://${OPEN_WEBUI_LB_IP}/api/version && echoO resultado será o seguinte:
{"status":true} {"version":"0.11.3"}Verifique os endpoints de integridade e versão usando o encaminhamento de portas:
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}Verifique se a Open WebUI pode acessar os back-ends de inferência:
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'Abra
http://${OPEN_WEBUI_LB_IP}em um navegador e crie a primeira conta. A primeira conta criada se torna o administrador. Selecione um modelo na visualização de chat e envie um comando.
Operações
Adicionar ou mudar back-ends de inferência
Atualize
GDCS_OPEN_WEBUI_OLLAMA_BASE_URLSouGDCS_OPEN_WEBUI_OPENAI_API_BASE_URLSemenv.d/open-webui.sh, use o arquivo de ambiente como origem e execute a etapa do manifesto em Interface da Web novamente. Em seguida, aplique o manifesto e reinicie a Open WebUI, que lê oConfigMapem cada inicialização porque a configuração persistente está desativada: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}"
Fazer upgrade
Defina o novo
GDCS_OPEN_WEBUI_IMAGE_TAGno arquivo de ambiente, use o comando source, propague a nova imagem e faça o upgrade doDeployment: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}"
Backup
Crie um
VolumeSnapshotdo volume de dados (ostandardVolumeSnapshotClassestá disponível nos clusters padrão do 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
Solução de problemas
| Sintoma | Causa provável | Ação |
|---|---|---|
| Nenhum modelo é oferecido na visualização de chat | O endereço de back-end Service está incorreto, o back-end não está pronto ou o back-end NetworkPolicy não aceita o namespace do Open WebUI |
Execute a etapa de verificação de back-end em Validação. Verifique o back-end Deployment e o NetworkPolicy dele. |
Pod reinicializações durante a inicialização |
Primeira inicialização lenta no PersistentVolume |
Verifique kubectl describe pod. O startupProbe permite 10 minutos. |
| Falha no envio de documentos (RAG) | O modo off-line bloqueia o download do modelo de embedding padrão | Configurar o RAG_EMBEDDING_ENGINE=ollama com um modelo de embedding disponibilizado pelo Ollama ou manter a RAG desativada |
O IP do LoadBalancer permanece <pending> ou não está acessível |
O balanceador de carga não está disponível no ambiente ou falta ProjectNetworkPolicy |
Use a etapa de validação de encaminhamento de portas. Peça ao administrador da NetworkPolicy do projeto um ProjectNetworkPolicy. |
Sessão inválida após uma reinicialização do Pod |
WEBUI_SECRET_KEY mudou |
Manter o Secret estável; restaurar de secrets/open_webui_secret_key |
Limpar
Exclua os recursos do Open WebUI:
kubectl delete namespace "${GDCS_WORKLOAD_NAMESPACE}"Exclua os arquivos locais:
rm -rf ${HOME}/gdcag-solutions/open-weight-models/open-webui
Outros recursos
- Abrir a documentação da WebUI
- Referência variável de ambiente da WebUI aberta
- Compatibilidade do Ollama com a OpenAI
- Servidor compatível com OpenAI vLLM