14 KiB
Установка 3X-UI как под в K3s (Kubernetes)
3x-ui — это симпатичный веб-интерфейс для управления VPN-серверами, такими как WireGuard, Shadowsocks, Xray, V2Ray и тому подобное. Он позволяет настраивать и мониторить VPN-соединения и клиентов через браузер. Мы будем запускать его как контейнер (под) внутри K3s кластера на Orange Pi 5.
Мне нужен 3x-ui, для безопасного доступа к домашней сети из любой точки мира, а также для безопасного доступа к интернету через домашний сервер.
Создание namespace (не обязательно)
Для удобства организации рекомендую создать отдельное пространство имён (namespace
) для 3x-ui. Пространство имен --
это способ организовать ресурсы в кластере. Оно работает как виртуальная "папка", которая помогает разделять
(изолировать) и управлять объектами, такими как поды, сервисы, конфигурации и т.д. Объекты в одном namespace не видят
объекты из другого namespace (если не настроено обратное), что помогает избежать путаницы. Несколько приложений
с одинаковыми именами могут без проблем существовать в разных пространствах имен. Кроме того, можно настроить
права доступа (RBAC) отдельно для каждого namespace.
Выполним в терминале:
sudo kubectl create namespace x-ui
Проверим, что пространство имён создано:
kubectl get namespaces
Увидим x-ui в списке:
NAME STATUS AGE
... ... ...
... ... ...
x-ui Active 6s
Простое развёртывание 3X-UI в поде
Cоздадим манифест развертывания пода (этого YAML-файл с инструкциями для K3s, что и как запустить). Мы будем
использовать SQLite как внутреннюю базу данных 3x-ui, и пока эта бызы будет храниться внутри пода. Позже сможем
переключиться на Longhorn
(опционально).
Создадим deployment.yaml
в каталоге ~/k3s/vpn/x-ui/
(см. структуру каталогов для хранения конфигураций и манифестов
принятую в моем проекте):
mkdir -p ~/k3s/vpn/x-ui
nano ~/k3s/vpn/x-ui/deployment.yaml
Вставим в него следующий код:
apiVersion: apps/v1
kind: Deployment
metadata:
name: x-ui # имя развертывания (имя пода)
namespace: x-ui # пространство имён, в котором будет создан под
spec:
replicas: 1
selector:
matchLabels:
app: x-ui
template:
metadata:
labels:
app: x-ui
spec:
hostNetwork: true # использовать сетевой стек хоста
containers:
- name: x-ui # имя контейнера
image: ghcr.io/mhsanaei/3x-ui:latest
# image: enwaiax/x-ui:latest # альтернативный облегчённый: меньше способов шифрования и китайский интерфейс
В этом манифесте примечательно следующее:
hostNetwork: true
— позволяет контейнеру использовать сетевой стек хоста и значит работать с сетевыми интерфейсами и портами хоста напрямую. Это полезно для приложений, которые требуют прямого доступа к сети, например, VPN-серверы.spec.replicas: 1
— количество реплик (экземпляров) пода, которые будут запущены. В данном случае -- оин под.spec.selector
— селектор, который используется для выбора подов, которые будут управляться этим развертыванием. Он определяет, какие поды будут обновлены или удалены при изменении конфигурации развертывания.matchLabels
— метки, которые должны совпадать с метками подов, чтобы они были выбраны селектором. В данном случае мы используем меткуapp: x-ui
, чтобы выбрать поды, которые относятся к приложению x-ui.
Применим манифест:
sudo kubectl apply -f ~/k3s/vpn/x-ui/deployment.yaml
Проверим, что под запустился:
sudo k3s kubectl get pods -n x-ui -o wide
Увидим что-то вроде:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
x-ui-bb97f6894-h7zj8 1/1 Running 0 11s 10.42.1.50 opi5plus-3 <none> <none>
Видим, что нода на которой запустился 3x-ui это `opi5plus-3`, а имя пода `x-ui-bb97f6894-h7zj8`. Проверим логи пода,
используя его имя:
```bash
sudo kubectl logs -n x-ui x-ui-bb97f6894-h7zj8
Увидим что-то вроде:
Server ready
(0x291e4e8,0x40001657b0)
2025/03/28 13:28:34 Starting x-ui 2.5.6
(0x291e4e8,0x40001658e0)
INFO - Web server running HTTP on [::]:2053
INFO - XRAY: infra/conf/serial: Reading config: &{Name:bin/config.json Format:json}
WARNING - XRAY: core: Xray 25.3.6 started
Теперь мы знаем порт, на котором работает 3x-ui (2053
), и значит можем получить доступ к веб-интерфейсу через браузер
по адресу http://opi5plus-3:2053
или http://<IP_адрес_вашего_узла>:2053
.

После первого логирования (по умолчанию логин и пароль admin
/admin
) можно настаивать VPN-подключения, создавать
пользователей, менять логин и пароль на вход и т.д. Веб-интерфейс 3x-ui интуитивно понятен, так что разбираться
не составит труда.
Развертывание Kubernetes пода 3x-ui с постоянным хранилищем (PVC)
Есть, конечно, у 3x-ui под k3s минусы. В частности, внутри пода (sudo kubectl exec -it -n x-ui x-ui-... -- /bin/sh
)
не будет работать командный интерфейс 3x-ui (x-ui admin
). Поды k3s работают на Alpine, а там некоторые команды
отличаются (например, нет bash
, а только ash
). Но web-панель работает как положено, и всё управление удобнее
делать через веб-интерфейс, так что лезть в консоль подов не обязательно.
Но есть ещё другой минус, более критичный. При рестарте пода, все настройки будут сброшены, так как они хранятся во внутреннем хранилище пода, а при остановке пода хранилище удаляется.
Чтобы этого избежать нужно использовать постоянное хранилище (Persistent Volume). Для его работы требуется установить
Longhorn
(или другой менеджер хранилищ). K3s поддерживает Longhorn
из коробки, так как в операционной системе на
Orange Pi 5 нет поддержки iSCSI
, включение его потребует компиляции ядра (если вы этого еще не сделали, смотрите
инструкцию.
Если Longhorn
уже установлен, создадим не его базе постоянное хранилище для -- PersistentVolumeClaim (PVC).
Манифест PVC создадим в каталоге ~/k3s/vpn/x-ui/
, рядом с deployment.yaml
:
nano ~/k3s/vpn/x-ui/pvc-db.yaml
Вставим в него следующий код:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: x-ui-db-pvc
namespace: x-ui
spec:
storageClassName: longhorn # Указываем Longhorn как класс хранилища
accessModes:
- ReadWriteOnce # Доступ для чтения и записи одним подом
resources:
requests:
storage: 512Mi # Запрашиваемое хранилище, размер можно увеличить, если нужно
Обратите внимание:
metadata.name
иmetadata.namespace
— имя хранилища (и это имя мы должны использовать в манифесте развертывания пода, чтобы указать, какое хранилище использовать) и пространство имён, в котором оно будет создано.spec.storageClassName
— класс хранилища, который будет использоваться для создания постоянного хранилища. В данном случае --longhorn
.spec.accessModes
— режим доступа к хранилищу.ReadWriteOnce
означает, что хранилище может быть смонтировано только одним подом для чтения и записи. У нас один под и база на SQLite, так что этого достаточно.spec.resources.requests.storage
— запрашиваемый размер хранилища. Мы запрашиваем 1 ГБ и не означает, что хранилище будет занимать 1 ГБ на диске. Это предельный размер, который сможет занять хранилище.
Применим pvc-манифест:
sudo kubectl apply -f ~/k3s/vpn/x-ui/pvc-db.yaml
После этого Longhorn создаст том, который будет привязан к этому PVC.
Теперь нам нужно изменить манифест развертывания пода, и подключить к нему созданный PVC. Теперь наш
~/k3s/vpn/x-ui/deployment.yaml
будет выглядеть так:
apiVersion: apps/v1
kind: Deployment
metadata:
name: x-ui
namespace: x-ui
spec:
replicas: 1
selector:
matchLabels:
app: x-ui
template:
metadata:
labels:
app: x-ui
spec:
hostNetwork: true
containers:
- name: x-ui
image: ghcr.io/mhsanaei/3x-ui:latest
# image: enwaiax/x-ui:latest # альтернативный облегчённый: меньше способов шифрования и китайский интерфейс
volumeMounts:
- name: db-storage # Имя тома, в который будет смонтирован...
mountPath: /etc/x-ui # ...в путь к базе данных внутри контейнера
volumes:
- name: db-storage # Имя тома, которое...
persistentVolumeClaim: # ...должно быть постоянным хранилищем
claimName: x-ui-db-pvc # ...и размещаться в PVC с именем 'x-ui-db-pvc'
Применим обновлённый манифест:
sudo kubectl apply -f ~/k3s/vpn/x-ui/deployment.yaml
Под перезапустится, и теперь база данных будет храниться в постоянном хранилище Longhorn. При перезапуске пода или его "переезде" на другой узел, база данных останется доступной и не потеряется. Следует отметить, что при сбое узла процесс перемещения пода занимает некоторое время. В кластере на Orange Pi 5, где проверки связности не очень агрессивные, это может занять до 5 минут. В общем, это нормально.