doc_memo/kubernetes/k3s-3xui-pod.md

42 KiB
Raw Blame History

Установка 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.

3x-ui welcome page (RU)

После первого логирования (по умолчанию логин и пароль 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 минут. В общем, это нормально.

Единая точка входа VPN-соединений через под 3x-ui

Под с 3x-ui может быть запущен k3s на произвольном узле, и может быть произвольно перемещён в кластере на другой узел. Таким образом, если мы хотим предоставить доступ к VPN-соединениям из интернета, нам нужно настроить доступ через единый IP-адрес. Это можно сделать несколькими способами.

Доступ через VIP (виртуальный IP) c перенаправлял трафика через Keepalived на узел с подом с 3x-ui

При развертывании k3s на Orange Pi 5 Plus мы уже настраивали Keepalived. Теперь надо настроить его так, чтобы узел с подом 3x-ui получал больший приоритет в Keepalived, и тогда виртуальный IP будет получать трафик с этого узла.

Лучшим решением будет динамическая настройка приоритета в Keepalived.

Лучший способ — настроить так, чтобы приоритет Keepalived ноды автоматически повышался, если под 3x-ui запущен на ней. Это можно сделать с помощью механизма track_script, который будет проверять наличие пода и динамически менять приоритет. Такой подход сохранит текущую работу K3s API и подов Shadowsocks, добавив поддержку 3x-ui.

Создадим проверочный скрипт (на каждом узле), который будет проверять наличие пода 3x-ui. Скрипт будет расположен в ~/scripts/check_xui.sh:

mkdir -p ~/scripts
nano ~/scripts/check_xui.sh

И вставим в него следующий код (на каждой ноде):

#!/usr/bin/bash
NODE_NAME=$(hostname)  # Получаем имя текущей ноды
POD_NAME=$(kubectl get pods -n x-ui -o jsonpath="{.items[?(@.spec.nodeName=='$NODE_NAME')].metadata.name}")
if [ -n "$POD_NAME" ]; then
    exit 0  # Под есть на этой ноде
else
    exit 1  # Пода нет
fi

Скрипт использует kubectl, чтобы проверить, есть ли под 3x-ui в namespace x-ui на текущей ноде. Использование sudo не требуется, так как скрипт будет запускаться keepalived, который работает от root. Убедись, что kubectl доступен на всех нодах и настроен для работы с кластером (например, через kubeconfig).

Сделаем скрипт исполняемым (на каждой ноде):

sudo chmod +x ~/scripts/check_xui.sh

Обновим конфиг reepalived, добавив vrrp_script и привязку к нему через track_script. Теперь мы переведем все ноды в BACKUP (чтобы избежать конфликтов), а приоритет будет динамически меняться в зависимости от наличия пода.

На перовой мастер-ноде:

sudo nano /etc/keepalived/keepalived.conf

И теперь там будет вот такой конфиг (не забудь указать правильное имя пользователя <user> в пути к скрипту):

vrrp_script check_xui {
    script "/home/<user>/scripts/check_xui.sh"
    interval 2          # Проверять каждые 2 секунды
    weight 50           # Добавить 50 к приоритету, если под есть
}

vrrp_instance VI_1 {
    # state MASTER
    state BACKUP        # Все ноды стартуют как BACKUP
    interface enP4p65s0
    virtual_router_id 51
    priority 100        # Базовый приоритет
    advert_int 1
    unicast_src_ip 192.168.1.26
    unicast_peer {
        192.168.1.27
        192.168.1.28
    }
    virtual_ipaddress {
        192.168.1.200
    }
    track_script {
        check_xui       # Привязка к скрипту
    }
}

Перезапустим Keepalived:

sudo service keepalived restart

Аналогичным образом настроим конфиги на других узлах (добавить блок vrrp_script сверху, и добавить track_script в vrrp_instance). Не забудь указать проверить unicast_src_ip для каждой ноды и перезапустить Keepalived на всех узлах.

Теперь на каждой ноде cкрипт ~/scripts/check_xui.sh проверяет наличие пода x-ui каждые 2 секунды. Если под есть, Keepalived добавляет 50 к базовому приоритету ноды (например, 100 → 150). Если пода нет, приоритет остаётся базовым (100, 90 или 80). Нода с наивысшим приоритетом становится MASTER и получает виртуальный IP. Таким образом, VIP всегда будет указывать на ноду с подом 3x-ui.

Теперь панель 3x-ui будет доступна с виртуального IP (192.168.1.200). Все VPN-соединения будут работать через него. Так что если на домашнем роутере настроить перенаправление портов (для 2053-порта веб-панели 3x-ui, и портов которые будем выбирать для VPN-соединений), то можно будет подключаться к 3x-ui и VPN-соединениям из любой точки мира.

Доступ через Ingress Controller по имени домена (http).

Сейчас web-панель 3x-ui доступна через VIP по порту 2053 по http. В принципе, так можно и оставить. Но если мы хотим иметь доступ по https, да еще чтобы это работало через доменное имя, и чтобы k3s автоматически получал и обновлял сертификаты, то можно использовать Ingress-контроллер. Он будет брать трафик с порта VIP, по порту 2055, через балансировщик svclb-traefik направлять его на Ingress-контроллер Traefik и перенаправлять его на под с 3x-ui (тоже через VIP но уже по порту 2053). Дополнительно, есть заметка про настройку Traefik в качестве прокси.

Манифест для Ingress-контроллера Traefik

По умолчанию Ingress-контроллер Traefik в k3s слушает на портах 80 и 443 (HTTP и HTTPS) и перенаправляет трафик на соответствующие поды. В моем случае порты 80 и 443 на моем роутере уже перенаправляются на другой хост. В будущем я это, возможно, изменю, и сейчас я не могу перенаправить эти порты на VIP. Поэтому мне нужно настроить Traefik так, чтобы он слушал http/https на другом порту (например, 2055, и порт, обратите внимание, стандартный 443-порт от продолжит слушать как и раньше) и перенаправлял трафик на под с 3x-ui (это только для http/https, то есть для доступа в веб-интерфейсу 3x-ui, а не для VPN-соединений). Этот манифест задаёт глобальную конфигурацию Traefik для всего кластера, а не только к 3x-ui, и потому лучше положить его в "общую" папку для Traefik, например: ~/k3s/traefik/traefik-config.yaml:

mkdir -p ~/k3s/traefik
nano ~/k3s/traefik/traefik-config.yaml

И вставим в него следующий код:

apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: traefik
  namespace: kube-system
spec:
  valuesContent: |-
    additionalArguments:
      - --entrypoints.web-custom.address=:2055    # Слушаем HTTP на 2055
      - --log.level=DEBUG

Что тут происходит: Для изменения настройки Traefik, создаётся HelmChartConfig (этот такой аналог пакетного менеджера для Kubernetes, который позволяет управлять приложениями и сервисами в кластере). Этот манифест указывает Traefik, в пространство имён kube-system, а аргумент --entrypoints.web-custom.address=:2055 в конфигурацию -- инструкция: Слушай порт 2055 и назови эту точку входа web-custom). После применения Traefik начнёт принимать запросы на порту 2055. Поскольку мой роутер пробрасывает 2055 на VIP-адрес (тоже 2055 порт), Traefik на ноде с VIP увидит этот трафик.

Применим манифест:

sudo kubectl apply -f ~/k3s/traefik/traefik-config.yaml

Теперь Traefik будет слушать http еще и на порту 2055.

Манифест для маршрутизации трафика на под с 3x-ui через Ingress-контроллер

Теперь нужно сказать Traefik, что запросы на домен v.home.cube2.ru через порт 2055 — это HTTP, и их надо перенаправить на порт 2053, где работает 3x-ui. Для этого в каталоге с манифестами 3x-ui ~/k3s/vpn/x-ui/ (ведь это касается подa с 3x-ui) создадим манифест IngressRoute:

nano ~/k3s/vpn/x-ui/ingressroute.yaml

И вставим в него следующий код (не забудь указать свой домен):

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: x-ui-ingress
  namespace: x-ui
spec:
  entryPoints:
    - web-custom  # ендпоинт, который "слушает" порт 2055
  routes:
    - match: Host("v.home.cube2.ru")
      kind: Rule
      services:
        - name: x-ui-external   # имя сервиса, на который будет перенаправлен трафик
          port: 2053            # порт, на который будет перенаправлен трафик

Что тут происходит? Мы создаём объект IngressRoute, который определяет маршрут для входящего трафика. Параметры:

  • kind — тип объекта, который мы создаём. В данном случае это IngressRoute, который используется для маршрутизации трафика в Traefik.
  • metadata — метаданные объекта, такие как имя и пространство имён. Мы указываем имя x-ui-ingress и пространство имён x-ui, в котором будет создан объект (то же пространство, что и у пода с 3x-ui).
  • entryPoints — точка входа, которая будет использоваться для маршрутизации трафика. В данном случае это web-custom, который мы настроили в предыдущем шаге.
  • routes — определяет правила маршрутизации. В данном случае мы указываем, что если запрос приходит на домен v.home.cube2.ru (match — условие, которое должно быть выполнено для маршрутизации), то он будет перенаправлен на сервис x-ui-external (который мы создадим ниже) на порт 2053.

Теперь создадим сервис x-ui-external, который будет использоваться для маршрутизации трафика на под с 3x-ui.

Создадим манифест сервиса в каталоге ~/k3s/vpn/x-ui/:

nano ~/k3s/vpn/x-ui/x-ui-service.yaml

И вставим в него следующий код:

# Service для 3x-ui с hostNetwork: true, использующего VIP 192.168.1.200
apiVersion: v1
kind: Service           # Тип объекта, который мы создаём. В данном случае это Service
metadata:
  name: x-ui-external   
  namespace: x-ui       
spec:
  ports:
    - port: 2053        
      targetPort: 2053  
      protocol: TCP
---
# Endpoints указывает на VIP, так как под не в сетевом пространстве Kubernetes
apiVersion: v1
kind: Endpoints         # Тип объекта, который мы создаём. В данном случае это Endpoints
metadata:
  name: x-ui-external   
  namespace: x-ui       
subsets:
  - addresses:
      - ip: 192.168.1.200   # IP-адрес (VIP), на который будет перенаправлен трафик
    ports:
      - port: 2053
        protocol: TCP

Что тут происходит? Мы создаём два объекта: Service и Endpoints. Service — это абстракция, которая предоставляет единый IP-адрес и DNS-имя для доступа к группе подов. Endpoints — это объект, который указывает конечные точки для перенаправления трафика. В нашем случае это VIP:2053, так как под 3x-ui использует hostNetwork: true и недоступен через внутренние IP Kubernetes. Но обычно Endpoints указывают на имена подов, на которые отправляется трафик.

Для Service мы указываем:

  • kind — тип объекта, который мы создаём. В данном случае это Service.
  • metadata — метаданные объекта, такие как имя и пространство имён. Мы указываем имя x-ui-external и пространство имён x-ui, в котором будет создан объект (то же пространство, что и у пода с 3x-ui).
  • spec — спецификация объекта, которая определяет его поведение. Мы указываем, что сервис будет слушать внешний трафик на порту 2053 и перенаправлять на тот же порт внутри кластера.
  • ports — определяет порты, на которых будет слушать сервис. Мы указываем, что сервис будет слушать на порту 2053 и перенаправлять трафик на тот же порт внутри кластера, и будем использоваться TCP.

Для Endpoints мы указываем:

  • kind — тип объекта, который мы создаём. В данном случае это Endpoints.
  • metadata — метаданные объекта, такие как имя и пространство имён. Мы указываем имя x-ui-external (то же, что и у сервиса) и пространство имён x-ui (то же, что и у пода с 3x-ui).
  • subsets — подмножество конечных точек, которые будут использоваться для маршрутизации трафика. Мы указываем, что в подмножестве есть одна конечная точка с IP 192.168.1.200 и портом 2053 (TCP).

Применим манифесты:

sudo kubectl apply -f ~/k3s/vpn/x-ui/ingressroute.yaml
sudo kubectl apply -f ~/k3s/vpn/x-ui/x-ui-service.yaml

Перезагрузим Traefik, чтобы он увидел изменения:

kubectl rollout restart deployment traefik -n kube-system

Или для надёжности вовсе удалим поды с traefik и svclb-traefik, тогда они должны создастся заново, и гарантированно примут новые настройки:

kubectl delete pod -n kube-system -l app.kubernetes.io/name=traefik
kubectl delete pod -n kube-system -l svccontroller.k3s.cattle.io/svcname=traefik

Проверим, что поды создались и запустились:

sudo kubectl get pods -n kube-system -o wide | grep traefik

Увидим что-то вроде (поды стартовали недавно):

helm-install-traefik-c4vlp                      0/1     Completed   0             148m   10.42.0.84   opi5plus-2   <none>           <none>
svclb-traefik-4f8c2580-8pfdg                    4/4     Running     0             4m     10.42.2.62   opi5plus-1   <none>           <none>
svclb-traefik-4f8c2580-9tldj                    4/4     Running     0             4m     10.42.1.93   opi5plus-3   <none>           <none>
svclb-traefik-4f8c2580-pmbqj                    4/4     Running     0             4m     10.42.0.83   opi5plus-2   <none>           <none>
traefik-5db7d4fd45-45gj6                        1/1     Running     0             4m     10.42.0.82   opi5plus-2   <none>           <none>

Проверим, что сервисы создались и запустились:

sudo kubectl get svc -n kube-system -o wide | grep traefik

Увидим что-то вроде (есть обработка через порт 2055:ххххх):

traefik                LoadBalancer   10.43.164.48    192.168.1.26,192.168.1.27,192.168.1.28   80:31941/TCP,443:30329/TCP,9000:32185/TCP,2055:32627/TCP   53d   app.kubernetes.io/instance=traefik-kube-system,app.kubernetes.io/name=traefik

Проверим, что созданный сервис x-ui-external доступен:

sudo kubectl get svc -n x-ui -o wide

Увидим что-то вроде (сервис создан и слушает на порту 2053):

NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE   SELECTOR
x-ui-external   ClusterIP   10.43.73.106   <none>        2053/TCP   2h    <none>

Проверим, что созданный IngressRoute доступен:

sudo kubectl get ingressroutes -n x-ui -o wide

Увидим что-то вроде (IngressRoute создан):

NAME           AGE
x-ui-ingress   14h

Проверим логи Traefik (не зря же мы включали отладку в манифесте)

kubectl get pods -n kube-system | grep traefik
sudo kubectl logs -n kube-system traefik-<hash> --since=5m

Ищем: "web-custom": {"address": ":2055"} и маршрут x-ui-x-ui-ingress с Host("v.home.cube2.ru"),

И наконец, проверим, что под с 3x-ui доступен по нашему доменному на порту 2055 через VIP-адрес (возможно, придется сделать запись в /etc/hosts, если ваш роутер не может разрешить внешний домен внутрь домашней сети, и поставить в соответствие домен и VIP):

curl -v http://v.home.cube2.ru:2055

Все заработало, мы видим, что запросы на домен v.home.cube2.ru через порт 2055 перенаправляются на под с 3x-ui

Если не получилось, то можно дополнительно проверить, что с сервисом traefik всё в порядке. Посмотрим его текущие настройки:

sudo  kubectl get service -n kube-system traefik -o yaml

Мы должны увидеть в блоке spec:ports что-то типа:

  - name: web-custom
    nodePort: тут-будет-номер-порта-внутри-балансировщика
    port: 2055
    protocol: TCP
    targetPort: 2055

Если блока нет, добавьте его через редактор (по умолчанию откроется vim, используйте :wq для сохранения и выхода):

sudo kubectl edit service -n kube-system traefik -o yaml

Найти в spec:ports блок:

  - name: web                                                              
    nodePort: 31941                                                                       
    port: 80                          
    protocol: TCP        
    targetPort: web      
  - name: websecure      
    nodePort: 30329                                
    port: 443                                      
    protocol: TCP                                  
    targetPort: websecure  

И добавить под ним новый блок:

  - name: web-custom
    port: 2055
    protocol: TCP
    targetPort: 2055

После сохранения изменений и выхода из редактора, сервис будет обновлён автоматически. Можно проверить, что ему присвоен новый номер порта внутри балансировщика (см. выше) и возможно все заработает. Но скорее всего придется удалить манифесты ingressroute.yaml, x-ui-service.yaml и все настраивать заново, проверять логи и т.д.

Доступ через Ingress Controller c https и перенаправлением трафика на узел с подом с 3x-ui

Установим Cert-Manager для автоматического получения сертификатов Let's Encrypt. Это позволит нам использовать HTTPS для доступа к 3x-ui (и другим подам). Cert-Manager автоматически обновляет сертификаты, когда они истекают.

sudo kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.1/cert-manager.yaml

В результате у нас появится три новых пода в пространстве имён cert-manager:

sudo k3s kubectl get pods -n cert-manager -o wide

Увидим что-то вроде:

NAME                                       READY   STATUS    RESTARTS   AGE     IP           NODE         NOMINATED NODE   READINESS GATES
cert-manager-64478b89d5-p4msl              1/1     Running   0          8m36s   10.42.1.55   opi5plus-3   <none>           <none>
cert-manager-cainjector-65559df4ff-t7rj4   1/1     Running   0          8m36s   10.42.1.54   opi5plus-3   <none>           <none>
cert-manager-webhook-544c988c49-zxdxc      1/1     Running   0          8m36s   10.42.1.56   opi5plus-3   <none>           <none>

Cert-Manager состоит из трёх основных компонентов, каждый из которых запускается в своём поде:

  • cert-manager -- основной контроллер. Он следит за ресурсами вроде Certificate и Issuer, запрашивает сертификаты у провайдеров (например, Lets Encrypt) и обновляет их при необходимости.
  • cert-manager-cainjector -- внедряет CA (Certificate Authority) в вебхуки и другие ресурсы Kubernetes, чтобы они могли доверять сертификатам, выданным Cert-Manager.
  • cert-manager-webhook -- отвечает за валидацию и мутацию запросов на создание или обновление ресурсов, связанных с сертификатами. Он проверяет их на соответствие правилам.

Манифест для ClusterIssuer

Создадим манифест ClusterIssuer (эмитент кластера) для Cert-Manager и относится ко всему кластеру. В нем описываются правила для получения сертификатов от внешнего поставщика (в нашем случае Let's Encrypt) и укажем твм адрес сервера ACME, email для уведомлений, способы подтверждения владения доменом (например, через HTTP-01 или DNS-01).

mkdir ~/k3s/cert-manager
nano ~/k3s/cert-manager/clusterissuer.yaml

И вставим в него следующий код (не забудь указать свой email):

piVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: ваш@емейл.где-то
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: traefik  # Ingress-контроллер, например, traefik

Применим манифест, чтобы cert-manager принял конфигурацию:

sudo kubectl apply -f ~/k3s/cert-manager/clusterissuer.yaml 

Это будет работать для всего кластера и для всех подов (текущих и будущих). Cert-Manager будет автоматически запрашивать и обновлять сертификаты. Let's Encrypt при проверке прав владения доменом посредством правила HTTP-01 использует http (порт 80) и нужно настроить в роутере перенаправление трафика на кластер (лучше через VIP) для этого порта.


У меня развернут k3s-кластер из трех узлов (Orange Pi 5 Plus). Там есть под 3x-ui для внешнего доступа к VPN-соединениям. Под настроен как hostNetwork: true и использует Longhorn для хранения базы данных. На всех узлах настроен keepalived так, чтобы виртуальный IP-адрес (VIP) всегда указывал на узел с подом 3x-ui. Это позволяет поду ловить VPN-соединения из интернета (благодаря пробросу портов на роутере). Веб-интерфейс 3x-ui по порту 2053 тоже доступен по VIP-адресу (192.168.1.200) благодаря hostNetwork: true и виден из интернета по адресу http://v.home.cube2.ru:2053.

Я хочу настроить доступ к веб-интерфейсу 3x-ui через svclb-traefik по порту 2055 так, чтобы он стал доступен через VIP и по 2055 порту и будущем можно было настроить доступ по https.

Кроме того у меня еще есть внешний хост с audiobookshelf по адресу http://192.168.1.15:8000 и я хотелбы настроить доступ к нему через svclb-traefik по адресу http://zvuk.cube2.ru:2055.

Как это сделать? Какие конфиги тебе нужны чтобы ты смог мне помочь?