Open WebUI в Kubernetes через Helm

Поможем установить Open WebUI через Helm в готовом Kubernetes-кластере, открыть интерфейс и проверить сохранение аккаунта после замены Pod.

После урока вы сможете

  • Различать chart, release и ресурсы Open WebUI в Kubernetes
  • Настраивать одну реплику с постоянным томом и стабильным ключом
  • Открывать интерфейс через локальный port-forward
  • Проверять сохранение аккаунта при замене Pod с прежним PVC

Проверяемый результат: Новый экземпляр Open WebUI доступен через личный port-forward; понятно, как проверить сохранение аккаунта при замене Pod.

Установим отдельный экземпляр Open WebUI в готовый кластер Kubernetes через Helm и откроем его в браузере по адресу http://127.0.0.1:13000. Создадим первый аккаунт администратора, штатно заменим под и проверим, что с прежним хранилищем вход в этот аккаунт продолжает работать.

Это новая установка. Существующий экземпляр из урока про установку через Docker Compose или с отдельного сервера сюда не переносится.

Понадобится кластер Kubernetes, с которым вам разрешено работать. На компьютере с браузером должны быть настроены kubectl, Helm 3 и доступ к нужному контексту kubeconfig. Подробные требования приведены в официальном руководстве по Kubernetes для Open WebUI.

До начала работы согласуйте с администратором или оператором кластера:

  • новое пространство имён (namespace) open-webui-course и одноимённый релиз;
  • права на установку учебных ресурсов и штатный перезапуск rollout restart;
  • класс хранилища StorageClass, подходящий для SQLite;
  • стабильный объект Secret с именем open-webui-auth и ключом WEBUI_SECRET_KEY. Оператор создаст его после появления пространства имён, до установки приложения.

Если готового кластера нет, вернитесь к выбору способа установки и выберите локальный или серверный вариант. Поднимать кластер специально ради урока не нужно.

Продолжайте после подтверждения StorageClass и Secret оператором. Если при проверке найдутся прежний релиз или незнакомые ресурсы в выбранном пространстве имён, остановитесь и согласуйте новые свободные имена. Учебный экземпляр не должен устанавливаться поверх существующих объектов.

Команды рассчитаны на Bash или Zsh: Терминал macOS или Linux либо Ubuntu в WSL2 на Windows. PowerShell использует другой синтаксис. Подготовьте на компьютере с браузером отдельную учебную папку для файла values-course.yaml.

Откройте два локальных окна терминала. В первом зададим переменные, установим приложение и проверим замену пода. Во втором запустим перенаправление портов на 127.0.0.1:13000. В Windows адрес открываем в обычном браузере Windows на том же компьютере. Если настройки WSL2 ограничивают локальный проброс портов, проверьте маршрут до 127.0.0.1.

Мы проверили конфигурацию офлайн командами helm lint и helm template в Helm 3.14.2. Запуск в кластере и проверку сохранения аккаунта вы выполняете в своей среде.

Как устроена установка и где хранятся данные

Чарт Helm (chart) содержит шаблоны ресурсов Kubernetes. Установка этого чарта с нашими настройками из values-course.yaml создаёт релиз (release), то есть конкретный экземпляр приложения в кластере.

Закрепим две независимые версии: чарт 17.0.0 и образ приложения ghcr.io/open-webui/open-webui:v0.12.0. Они указаны в описании Chart.yaml. Так установка не зависит от плавающего тега latest. Параметры ниже проверены для чарта 17.0.0 и отличаются от его настроек по умолчанию.

Контейнер Open WebUI запускается внутри пода (Pod). При обновлении, перезапуске или переносе между узлами Kubernetes может заменить старый под новым. У нового пода будет другой уникальный идентификатор UID. В нашем примере заменой управляет контроллер Deployment.

По умолчанию чарт выбирает StatefulSet и включает дополнительные компоненты. Для учебной установки используем схему из официального руководства по Kubernetes: Deployment, одна реплика (replicaCount: 1), один рабочий процесс (UVICORN_WORKERS=1) и стратегия Recreate.

При штатном обновлении Deployment стратегия Recreate останавливает прежние поды перед запуском новых, как описано в документации Kubernetes. Интерфейс на это время станет недоступен. В выбранной схеме одна реплика и один процесс используют SQLite. Настройка Recreate не даёт той же гарантии при ручном удалении подов и других способах запуска.

Дополнительные компоненты ollama, pipelines, tika, terminals и встроенный redis отключим. Внешние маршруты ingress и route тоже не понадобятся. Поддержка websocket останется включённой и будет работать в памяти одного процесса без Redis.

Чтобы аккаунт пережил замену пода, вынесем данные из временной файловой системы контейнера. Чарт создаст PersistentVolumeClaim (PVC), запрос на постоянное хранилище. Подключённый том будет смонтирован в /app/backend/data. В конфигурации Open WebUI v0.12.0 в этом каталоге находятся файл SQLite webui.db и папки загрузок. При замене пода прежние PVC и связанный с ним том PersistentVolume (PV) подключаются к новому поду.

Параметр provider: local означает файловое хранение приложения в подключённом томе, в отличие от объектного хранилища вроде S3. Он не означает, что диск физически расположен на узле кластера. Способ выделения тома определяет StorageClass.

Для SQLite нужны низкая задержка и надёжные файловые блокировки. Согласно рекомендациям Open WebUI, NFS и SMB для такой базы использовать нельзя. Поэтому подходящий класс хранилища выбираем вместе с оператором.

В примере запросим 10Gi в режиме ReadWriteOnce. Kubernetes определяет этот режим как монтирование тома на чтение и запись одним узлом. Два пода на одном узле он сам по себе не запрещает; для штатного перезапуска мы задаём одну реплику, один процесс и Recreate.

Объём 10Gi, запросы 500m CPU / 1Gi памяти и лимиты 2 CPU / 4Gi памяти выбраны для примера. Они не гарантируют производительность в любой инфраструктуре.

Аннотация helm.sh/resource-policy: keep просит Helm сохранять PVC при операциях с релизом. Она не заменяет резервное копирование и не защищает от ручного удаления тома или всего пространства имён. Команды удаления в уроке не используются.

Для сохранения доступа нужен также постоянный WEBUI_SECRET_KEY. Без явно переданного ключа стартовый скрипт создаёт случайный ключ в файле .webui_secret_key в рабочем каталоге /app/backend. Этот файл находится вне постоянного тома /app/backend/data и пропадёт при замене пода.

Чарт сам не создаёт секрет. Поэтому в extraEnvVars укажем ссылку secretKeyRef на подготовленный оператором Secret open-webui-auth.

Проверки состояния помогут дождаться запуска. startupProbe обращается к /health и проверяет, что веб-процесс отвечает. readinessProbe обращается к /ready: в версии 0.12.0 этот адрес проверяет завершение инициализации и ответ базы данных. После прохождения проверки готовности внутренний Service типа ClusterIP начнёт направлять трафик с порта 80 на порт контейнера 8080.

Перед созданием конфигурации проверим целевой кластер и убедимся, что выбранные имена свободны.

Подготовим конфигурацию и установим Open WebUI

Работаем в первом терминале, в учебной папке для values-course.yaml. Сохраним имя нужного контекста kubeconfig в CTX и будем явно передавать его командам через --context и --kube-context. Переключать глобальный контекст не потребуется.

Пространство имён open-webui-course группирует ресурсы урока. Само по себе оно не обеспечивает полной сетевой или административной изоляции.

Проверим контекст и свободные имена

Посмотрим текущий контекст, зададим переменные первого терминала и проверим доступ к кластеру и хранилищу. Замените REPLACE_WITH_APPROVED_CONTEXT точным именем согласованного существующего контекста:

kubectl config current-context
CTX="REPLACE_WITH_APPROVED_CONTEXT"
NS="open-webui-course"
RELEASE="open-webui-course"
SELECTOR="app.kubernetes.io/instance=$RELEASE,app.kubernetes.io/component=open-webui"
helm version --short
kubectl --context "$CTX" cluster-info
kubectl --context "$CTX" get storageclass

Команда current-context показывает текущее подключение, ничего не переключая. В выводе helm version --short должна быть версия v3.x. Команды cluster-info и get storageclass должны показать знакомый кластер и согласованный класс хранилища для SQLite.

Затем проверим выбранные имена:

kubectl --context "$CTX" get namespace "$NS"
helm list --kube-context "$CTX" -n "$NS" --all

Для новой установки ожидаем NotFound от get namespace и пустой список helm list. Если пространство имён уже существует, найден старый релиз или команда вернула Forbidden, остановитесь. Согласуйте свободные имена или недостающие права с администратором. Отдельное разрешение при необходимости можно уточнить командой вида kubectl --context "$CTX" auth can-i ....

Если имена свободны и доступ разрешён, создадим пространство имён:

kubectl --context "$CTX" create namespace "$NS"

После создания пространства имён сделайте паузу: оператор должен подготовить в нём Secret open-webui-auth с ключом WEBUI_SECRET_KEY. Ключ не генерируем в командной строке урока и не записываем в текстовые файлы.

Когда оператор подтвердит, что секрет создан и сохранён в системе управления секретами, проверим наличие объекта:

kubectl --context "$CTX" -n "$NS" get secret open-webui-auth

Команда покажет сводку объекта без секретного значения. Строка open-webui-auth подтверждает только его наличие в выбранном пространстве имён. Наличие точного ключа WEBUI_SECRET_KEY и неизменность значения подтверждает оператор. Флаги -o yaml и -o json здесь не нужны.

Проверим шаблон и выполним установку

Сохраните файл values-course.yaml в учебной папке. В поле storageClass замените REPLACE_WITH_SQLITE_SUITABLE_STORAGE_CLASS точным именем согласованного класса из вывода get storageclass. Остальной файл оставьте без изменений:

# One new release in its own namespace; chart 17.0.0 only.
replicaCount: 1
workload:
  kind: Deployment
strategy:
  type: Recreate
image:
  repository: ghcr.io/open-webui/open-webui
  tag: v0.12.0
ollama:
  enabled: false
pipelines:
  enabled: false
tika:
  enabled: false
terminals:
  enabled: false
websocket:
  enabled: true
  manager: ""
  redis:
    enabled: false
service:
  type: ClusterIP
  port: 80
  containerPort: 8080
ingress:
  enabled: false
route:
  enabled: false
persistence:
  enabled: true
  provider: local
  storageClass: REPLACE_WITH_SQLITE_SUITABLE_STORAGE_CLASS
  accessModes:
    - ReadWriteOnce
  size: 10Gi
  annotations:
    helm.sh/resource-policy: keep
# No model endpoint is installed or tested by this lesson.
openaiApiKey: ""
resources:
  requests:
    cpu: 500m
    memory: 1Gi
  limits:
    cpu: "2"
    memory: 4Gi
startupProbe:
  httpGet:
    path: /health
    port: http
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 60
readinessProbe:
  httpGet:
    path: /ready
    port: http
  periodSeconds: 10
  timeoutSeconds: 5
extraEnvVars:
  - name: WEBUI_SECRET_KEY
    valueFrom:
      secretKeyRef:
        name: open-webui-auth
        key: WEBUI_SECRET_KEY
  - name: UVICORN_WORKERS
    value: "1"

В той же папке подключим официальный репозиторий, сверим версию чарта и сохраним итоговые описания ресурсов в rendered-open-webui.yaml. Эти команды ещё не устанавливают приложение:

helm repo add open-webui https://open-webui.github.io/helm-charts
helm repo update open-webui
helm show chart open-webui/open-webui --version 17.0.0
helm template "$RELEASE" open-webui/open-webui --version 17.0.0 --namespace "$NS" -f values-course.yaml > rendered-open-webui.yaml

Команды helm repo add и helm repo update подключают репозиторий и обновляют его локальный индекс. В выводе helm show chart сверьте version: 17.0.0 и appVersion: 0.12.0.

helm template собирает манифест локально. В файл попадает ссылка secretKeyRef, а не значение секрета из кластера. Откройте rendered-open-webui.yaml и проверьте:

  • один контроллер kind: Deployment со стратегией type: Recreate;
  • образ ghcr.io/open-webui/open-webui:v0.12.0;
  • PersistentVolumeClaim с вашим storageClassName и подключение тома в /app/backend/data;
  • ссылку на open-webui-auth с ключом WEBUI_SECRET_KEY;
  • Service типа ClusterIP с портом 80, ведущим к порту контейнера 8080.

После проверки манифеста установим релиз:

helm install "$RELEASE" open-webui/open-webui --version 17.0.0 --kube-context "$CTX" --namespace "$NS" -f values-course.yaml --wait --timeout 15m

С флагом --wait Helm ожидает готовности ресурсов до 15m. Первое скачивание образа и инициализация могут занять несколько минут. При таймауте или ошибке не повторяйте helm install и не удаляйте ресурсы наугад. Проверьте статус ниже и найдите нужную проверку в таблице в конце урока.

Проверим готовность релиза и тома

Проверим релиз, под и том в первом терминале:

helm status "$RELEASE" --kube-context "$CTX" -n "$NS"
kubectl --context "$CTX" -n "$NS" get deployment,service,pvc -l "$SELECTOR"
kubectl --context "$CTX" -n "$NS" rollout status deployment/open-webui-course --timeout=15m
kubectl --context "$CTX" -n "$NS" get pods -l "$SELECTOR" -o wide
kubectl --context "$CTX" -n "$NS" get pvc open-webui-course

Имя релиза open-webui-course содержит строку open-webui, поэтому шаблон чарта даёт основным объектам Deployment, Service и PersistentVolumeClaim то же имя. Подтвердите его в выводе команды с фильтром -l "$SELECTOR".

Статус тома Bound проверяем после создания Deployment. Если класс хранилища использует WaitForFirstConsumer, привязка тома ждёт, пока планировщик выберет узел для пода.

Ожидаем успешное завершение rollout status, один под с готовностью 1/1 в get pods и статус Bound у PVC. В нашей конфигурации готовность 1/1 означает, что прошли проверки /health и /ready: приложение инициализировалось и получает ответ SQLite на подключённом томе. Работу языковой модели эти проверки не тестируют.

Когда эти условия выполнены, можно открыть локальный доступ к Service, не меняя релиз.

Откроем интерфейс и создадим первый аккаунт

У Service open-webui-course внутренний адрес типа ClusterIP. Чтобы открыть приложение в своём браузере, используем kubectl port-forward. Путь соединения будет таким: локальный 127.0.0.1:13000 → порт 80 сервиса → порт 8080 контейнера в выбранном поде.

Это личный доступ для проверки и настройки. Он не создаёт общего адреса для команды и не заменяет настройку сетевой безопасности кластера.

Откройте второй локальный терминал на компьютере с браузером. Переменные первого окна в нём недоступны, поэтому зададим CTX и NS заново:

CTX="REPLACE_WITH_APPROVED_CONTEXT"
NS="open-webui-course"
kubectl --context "$CTX" -n "$NS" port-forward --address 127.0.0.1 service/open-webui-course 13000:80

Подставьте тот же CTX, что и в первом терминале. Переменные RELEASE и SELECTOR здесь не нужны.

port-forward удерживает терминал занятым, пока работает перенаправление. Оставьте окно открытым. Нажатие Ctrl+C завершит пересылку трафика; под и данные в кластере продолжат работать.

Откройте http://127.0.0.1:13000 в браузере этого компьютера. На новой установке создайте первую учётную запись администратора. Подробности ролей и начальных настроек есть в уроке «Первый вход и базовые настройки доступа».

Выйдите из аккаунта и войдите снова с теми же данными. Так проверим исходное состояние: запись пользователя сохранена в SQLite на постоянном томе, а вход работает. Ответ модели для этой проверки не нужен. Проверяем одну запись пользователя, не остальные данные.

Теперь заменим под и сравним идентификаторы ресурсов до и после перезапуска.

Проверим аккаунт после замены Pod

Выполним замену через kubectl rollout restart, чтобы проверить аккаунт с новым подом и прежним томом. Команда обновляет шаблон Deployment, и стратегия Recreate останавливает старый под перед запуском нового. Ручное или принудительное удаление пода для этой проверки не используем.

Вернитесь в первый терминал, где заданы CTX, NS и SELECTOR. Выведем имя и UID пода, а также UID объекта PVC и имя его тома в колонке VOLUME. Затем перезапустим Deployment, дождёмся завершения и запросим те же сведения:

kubectl --context "$CTX" -n "$NS" get pods -l "$SELECTOR" -o custom-columns=NAME:.metadata.name,UID:.metadata.uid
kubectl --context "$CTX" -n "$NS" get pvc open-webui-course -o custom-columns=NAME:.metadata.name,UID:.metadata.uid,VOLUME:.spec.volumeName,STATUS:.status.phase
kubectl --context "$CTX" -n "$NS" rollout restart deployment/open-webui-course
kubectl --context "$CTX" -n "$NS" rollout status deployment/open-webui-course --timeout=15m
kubectl --context "$CTX" -n "$NS" get pods -l "$SELECTOR" -o custom-columns=NAME:.metadata.name,UID:.metadata.uid
kubectl --context "$CTX" -n "$NS" get pvc open-webui-course -o custom-columns=NAME:.metadata.name,UID:.metadata.uid,VOLUME:.spec.volumeName,STATUS:.status.phase

Сравните несекретные метаданные до и после перезапуска:

  • у объекта Pod изменились и имя (NAME), и уникальный идентификатор (UID), то есть приложение работает в новом поде;
  • у объекта PVC open-webui-course значение UID, статус Bound и имя привязанного тома в колонке VOLUME (PV) остались прежними.

Старый процесс port-forward был связан с завершившимся подом, поэтому соединение оборвётся. Во втором терминале остановите его через Ctrl+C, если он ещё не завершился, и запустите перенаправление снова.

Переменные CTX и NS в этом окне уже заданы. Если окно закрыли, повторите полный блок из трёх строк из предыдущего раздела:

kubectl --context "$CTX" -n "$NS" port-forward --address 127.0.0.1 service/open-webui-course 13000:80

Откройте http://127.0.0.1:13000 в новом приватном окне браузера, чтобы исключить влияние старого кеша страницы. Ожидаем обычную форму входа. Войдите в аккаунт, созданный до замены пода.

Если приложение снова предлагает создать первого администратора, остановитесь и не создавайте второй аккаунт. Вместе с оператором проверьте выбранный экземпляр, подключение прежнего тома и ключ.

Новый UID пода, прежние UID объекта PVC и имя VOLUME, а также успешный вход в приватном окне подтверждают выбранный сценарий: аккаунт пережил штатную замену пода при доступном прежнем хранилище.

Эта проверка не заменяет тестирование резервной копии и восстановления после потери диска или узла. Она не проверяет все пользовательские файлы и не подтверждает высокую доступность. Вход также не доказывает неизменность WEBUI_SECRET_KEY: за сохранение ключа в open-webui-auth отвечает оператор.

Если результат отличается от ожидаемого, найдём причину по таблице ниже. Переустанавливать релиз поверх существующего не нужно.

Если запуск или повторный вход не удался

Выберите наблюдаемую ошибку и соответствующую проверку:

НаблюдениеЧто проверить и сделать
PVC или Pod долго остаётся в статусе PendingПроверьте события через describe pvc и describe pod: хватает ли квот в пространстве имён, доступен ли провижинер выбранного StorageClass, нет ли ограничений топологии узлов или нехватки CPU/памяти. Помните, что при режиме WaitForFirstConsumer том привязывается только вместе с планированием пода. Не меняйте StorageClass или права доступа наугад, согласуйте причину с администратором.
Статус ImagePullBackOff или ошибка инициализации в copy-app-dataДля ImagePullBackOff проверьте в событиях пода доступность реестра ghcr.io из кластера и точность тега v0.12.0. Если не завершился init-контейнер copy-app-data, посмотрите его журнал и уточните у оператора политику прав файловой системы на томе CSI (не используйте chmod 777 для обхода проблем с правами).
Статус CreateContainerConfigError или жалоба на отсутствующий SecretПопросите оператора сверить наличие объекта Secret open-webui-auth и точное имя ключа WEBUI_SECRET_KEY в пространстве имён open-webui-course. Не выводите секретное значение на экран и не генерируйте новый ключ для уже созданной базы данных.
CrashLoopBackOff, не проходит readinessProbe или истёк таймаут helm installПроверьте helm status, события пода и последние строки журнала контейнера open-webui. При первом старте приложение может подгружать вспомогательные ресурсы. Статус контейнера Running сам по себе не заменяет готовность /ready (проверку завершения старта и ответа базы данных).
Ошибка Address already in use, Service не найден или после rollout restart перестал открываться интерфейсПроверьте, не остался ли во втором терминале старый процесс port-forward, сверьте переменные CTX, NS и имя сервиса open-webui-course. После каждой замены пода перезапускайте команду port-forward во втором терминале. Если порт 13000 на компьютере занят другой программой, измените только левое число в паре портов (например, 13001:80) и откройте соответствующий локальный адрес.
После замены пода снова открылась страница создания первого администратораОстановитесь и не создавайте новую учётную запись. Проверьте вместе с оператором, что вы подключены к тому же контексту CTX и пространству имён open-webui-course, что к поду смонтирован прежний PVC open-webui-course с тем же томом VOLUME, а объект Secret open-webui-auth не изменялся.

Диагностические команды выполняем в первом терминале, где заданы CTX, NS и SELECTOR. Выбирайте команды по своей ошибке, весь блок подряд запускать не нужно. Вместо POD_NAME_FROM_GET подставьте имя текущего пода из вывода первой команды:

kubectl --context "$CTX" -n "$NS" get pods,pvc -l "$SELECTOR"
kubectl --context "$CTX" -n "$NS" describe pvc open-webui-course
kubectl --context "$CTX" -n "$NS" describe pod POD_NAME_FROM_GET
kubectl --context "$CTX" -n "$NS" logs deployment/open-webui-course -c open-webui --tail=100
kubectl --context "$CTX" -n "$NS" logs POD_NAME_FROM_GET -c copy-app-data --tail=100

Урок завершён, если под получил новый UID, у PVC сохранились прежние UID и VOLUME, а после повторного запуска port-forward вы вошли в прежний аккаунт через http://127.0.0.1:13000. При этих условиях проверен повторный доступ к выбранной учётной записи после штатной замены пода с прежним хранилищем.

Подключение модели, общий адрес для команды и регулярное резервное копирование томов требуют отдельных действий.

Материалы урока

Отметить прохождение

Отметьте урок после того, как получили проверяемый результат из практической части.