Helm chart (один чарт создаёт Service + Endpoints + Ingress на каждый прокси):
- _helpers.tpl: хелперы ext-proxy.resourceName, ext-proxy.labels
- service.yaml: ClusterIP без selector — имя совпадает с Endpoints
- endpoints.yaml: внешние IP(s) + порт; несколько IP → round-robin через kube-proxy
- ingress.yaml: слияние аннотаций (defaults → сгенерированные → уровень прокси);
поддержка TLS, basic auth, WebSocket, несколько хостов, маршрутизация по пути
- secret-auth.yaml: htpasswd Secret создаётся только при auth.enabled=true + credentials
- NOTES.txt: список прокси + команды проверки после установки
Ansible роль:
- defaults/main.yml: ext_proxy_namespace, ext_proxy_defaults, ext_proxy_proxies
- tasks/main.yml: валидация → namespace → копировать chart → lint → helm upgrade --install --atomic
- templates/values.yaml.j2: преобразование Ansible-переменных в Helm values через to_yaml
Интеграция: Makefile addon-ext-proxy, флаг addons.yml, playbooks/addons.yml,
docs/addons.md, README.md (счётчик 37 аддонов)
README.md на русском языке с полной документацией:
архитектура, настройка, функции, DNS, проверка, примеры манифестов, устранение неисправностей
Дополнительно: splitgw_deploy_mode изменён на k8s
23 KiB
External Services Ingress Proxy
Проксирует сервисы, работающие вне Kubernetes, через существующий стек ingress-nginx + kube-vip с одним общим VIP. Для каждого внешнего сервиса аддон автоматически создаёт:
- Service (ClusterIP, без selector) — стабильный адрес внутри кластера
- Endpoints — указывает на внешний IP(s) и порт
- Ingress — правила маршрутизации по хосту/пути через ingress-nginx
- Secret (опционально) — htpasswd-учётные данные для basic auth
Маршрут трафика:
Интернет ──► kube-vip VIP ──► ingress-nginx ──► Service ──► Endpoints ──► Внешний IP:PORT
Все прокси-домены резолвятся в один IP kube-vip. Дополнительный LoadBalancer не создаётся.
Быстрый старт
1. Включи аддон и определи сервисы:
# group_vars/all/addons.yml
addon_ext_proxy: true
ext_proxy_proxies:
- name: plex
hosts: [plex.home.ru]
ips: [192.168.1.50]
port: 32400
- name: router
hosts: [router.home.ru]
ips: [192.168.1.1]
port: 8080
2. Разверни:
make addon-ext-proxy
# с явным VIP в сводке:
make addon-ext-proxy ARGS="-e ext_proxy_vip=192.168.1.100"
3. Направь DNS на kube-vip:
plex.home.ru IN A 192.168.1.100 # kube-vip VIP
router.home.ru IN A 192.168.1.100
4. Открой в браузере: http://plex.home.ru
Архитектура
┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌────────────────────┐ │
│ │ Ingress │ │ Service │ │ Endpoints │ │
│ │ (nginx) │───►│ ClusterIP │───►│ 192.168.1.50:32400│ │
│ │ plex.home.ru│ │ без sel-ra │ └────────────────────┘ │
│ └─────────────┘ └─────────────┘ │
│ ▲ │
│ │ │
│ ┌──────────────┐ │
│ │ kube-vip │ VIP: 192.168.1.100 │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
▲
│
Клиент: curl http://plex.home.ru
Почему Service + Endpoints, а не ExternalName?
Сервисы типа ExternalName используют DNS CNAME, что обходит kube-proxy и ломает TLS SNI. ClusterIP + Endpoints маршрутизирует трафик через обычный service mesh и поддерживает все возможности ingress-nginx (auth, терминация TLS, rewrite и т.д.).
Справочник по конфигурации
Все настройки задаются в group_vars/all/addons.yml.
Глобальные значения по умолчанию
ext_proxy_namespace: "ext-proxy" # Kubernetes namespace
ext_proxy_release_name: "ext-proxy" # имя Helm release
ext_proxy_defaults:
ingressClass: nginx # класс Ingress (должен совпадать с именем в ingress-nginx)
tls:
enabled: false # включить TLS-терминацию на Ingress
secretName: "" # имя существующего TLS Secret (wildcard cert и т.п.)
certManager:
enabled: false # автоматически выпускать/обновлять сертификат через cert-manager
issuer: "" # имя ClusterIssuer (например, letsencrypt-prod)
issuerKind: ClusterIssuer # ClusterIssuer | Issuer
auth:
enabled: false # включить nginx basic authentication
credentials: "" # строка htpasswd: htpasswd -nb admin 'пароль'
secretName: "" # использовать существующий Secret вместо генерации нового
websocket: true # включить WebSocket (заголовки HTTP/1.1 upgrade)
path: / # путь Ingress по умолчанию
pathType: Prefix # Prefix | Exact | ImplementationSpecific
proxyConnectTimeout: 60 # nginx proxy_connect_timeout (секунды)
proxyReadTimeout: 3600 # nginx proxy_read_timeout
proxySendTimeout: 3600 # nginx proxy_send_timeout
proxyBodySize: "1g" # nginx client_max_body_size (0 = без ограничений)
annotations: {} # дополнительные аннотации для каждого Ingress
Поля определения прокси
ext_proxy_proxies:
- name: myservice # (обязательно) уникальное имя → имя ресурса в K8s
hosts: # (обязательно) список хостов
- myservice.home.ru
ips: # (обязательно) внешние IP(s)
- 192.168.1.100
port: 8080 # (обязательно) внешний порт
# --- Все поля ниже опциональны (переопределяют глобальные defaults) ---
path: / # путь Ingress
pathType: Prefix # Prefix | Exact
websocket: true # поддержка WebSocket
ingressClass: nginx # переопределить класс Ingress для этого прокси
tls:
enabled: true
secretName: wildcard-cert
certManager:
enabled: false
auth:
enabled: true
credentials: "admin:$apr1$abc..." # строка htpasswd
# ИЛИ: secretName: my-existing-auth-secret
annotations: # аннотации уровня прокси (переопределяют всё)
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "7200"
Руководство по функциям
TLS — существующий Secret (wildcard-сертификат)
Если есть wildcard-сертификат, управляемый cert-manager и хранящийся в Secret:
ext_proxy_defaults:
tls:
enabled: true
secretName: wildcard-tls # должен существовать в ext_proxy_namespace
ext_proxy_proxies:
- name: plex
hosts: [plex.home.ru]
ips: [192.168.1.50]
port: 32400
Переопределить TLS для конкретного прокси:
ext_proxy_proxies:
- name: router
hosts: [router.home.ru]
ips: [192.168.1.1]
port: 8080
tls:
enabled: false # отключить TLS только для этого прокси
TLS — автоматический выпуск через cert-manager
ext_proxy_defaults:
tls:
enabled: true
certManager:
enabled: true
issuer: letsencrypt-prod # должен существовать как ClusterIssuer
issuerKind: ClusterIssuer
cert-manager автоматически выпустит сертификат для каждого хоста и сохранит его в Secret с именем <имя-прокси>-tls.
Важно: ACME-челлендж cert-manager требует публичной доступности домена. Для локальных/LAN-доменов используй DNS-01 challenge или wildcard-сертификат.
Basic Authentication
Сгенерируй учётные данные htpasswd (установи apache2-utils или httpd-tools):
htpasswd -nb admin 'мойсекретныйпароль'
# выводит: admin:$apr1$Rh0Ycxl9$rPTH7gRHfMBkS.7.Q1BxM/
ext_proxy_defaults:
auth:
enabled: true
credentials: "admin:$apr1$Rh0Ycxl9$rPTH7gRHfMBkS.7.Q1BxM/"
Или выборочно по конкретным прокси:
ext_proxy_proxies:
- name: router
hosts: [router.home.ru]
ips: [192.168.1.1]
port: 8080
auth:
enabled: true
credentials: "admin:$apr1$..."
- name: plex # без auth
hosts: [plex.home.ru]
ips: [192.168.1.50]
port: 32400
Использование существующего Secret (ключ должен называться auth, тип Opaque):
auth:
enabled: true
secretName: my-shared-htpasswd # создать вручную через kubectl
Примечание:
configuration-snippetдолжен быть разрешён в ingress-nginx (allow-snippet-annotations: true), если добавляешь кастомные сниппеты. Стандартные аннотации работают без этого.
WebSocket (Plex, Grafana, Home Assistant и т.д.)
WebSocket включён по умолчанию (websocket: true). Это устанавливает:
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
ingress-nginx автоматически проксирует заголовки Upgrade / Connection при использовании HTTP/1.1. Отключи для конкретного прокси если не нужно:
- name: router
...
websocket: false
Несколько хостов для одного сервиса
- name: plex
hosts:
- plex.home.ru
- plex.internal
- plex.lan
ips: [192.168.1.50]
port: 32400
Для каждого хоста создаётся отдельное правило Ingress, все ссылаются на один и тот же backend.
Несколько backend IP (round-robin / резервирование)
- name: homeassistant
hosts: [ha.home.ru]
ips:
- 192.168.1.100 # основной
- 192.168.1.101 # резервный
port: 8123
K8s создаёт два адреса в объекте Endpoints. kube-proxy распределяет трафик между ними по round-robin. Для активно-пассивного резервирования необходим внешний механизм проверки работоспособности.
Маршрутизация по пути
- name: grafana
hosts: [tools.home.ru]
ips: [192.168.1.60]
port: 3000
path: /grafana
pathType: Prefix
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # убрать префикс /grafana
Кастомные аннотации nginx
- name: plex
hosts: [plex.home.ru]
ips: [192.168.1.50]
port: 32400
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0" # без ограничений на загрузку
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" # 1 час
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
Аннотации уровня прокси имеют наивысший приоритет и переопределяют все defaults и сгенерированные аннотации.
Как добавить новый внешний сервис
- Добавь запись в
ext_proxy_proxiesв файлеgroup_vars/all/addons.yml:
ext_proxy_proxies:
# ... существующие записи ...
- name: homeassistant
hosts: [ha.home.ru]
ips: [192.168.1.150]
port: 8123
websocket: true
- Запусти аддон повторно:
make addon-ext-proxy
Helm upgrade идемпотентен — существующие ресурсы обновляются, новые добавляются.
- Добавь DNS-запись для нового хоста (см. раздел DNS ниже).
Настройка DNS
Все прокси-хосты должны резолвиться в kube-vip VIP.
Вариант A — роутер / домашний DNS-сервер
В настройках DHCP/DNS роутера (Keenetic, pfSense, AdGuard Home, Pi-hole и т.п.):
plex.home.ru → 192.168.1.100 (kube-vip VIP)
router.home.ru → 192.168.1.100
grafana.home.local → 192.168.1.100
Вариант B — /etc/hosts (на конкретной машине, для тестирования)
192.168.1.100 plex.home.ru router.home.ru grafana.home.local
Вариант C — rewrite в CoreDNS (только внутри кластера)
Если прокси доступен только из кластера, добавь rewrite в CoreDNS:
# configmap coredns — добавить в Corefile
rewrite name plex.home.ru ingress-nginx.ingress-nginx.svc.cluster.local
Узнать VIP kube-vip
# EXTERNAL-IP сервиса ingress-nginx LoadBalancer — это и есть VIP:
kubectl -n ingress-nginx get svc ingress-nginx-controller
Проверка
1. Убедись что ресурсы созданы
kubectl -n ext-proxy get all,ingress,endpoints
# Ожидаемый вывод:
# NAME TYPE CLUSTER-IP ...
# service/plex ClusterIP 10.43.x.x ...
# service/router ClusterIP 10.43.x.x ...
#
# NAME ENDPOINTS ...
# endpoints/plex 192.168.1.50:32400 ...
# endpoints/router 192.168.1.1:8080 ...
2. Проверь Endpoints заполнены
kubectl -n ext-proxy describe endpoints plex
# Должно показать "Addresses: 192.168.1.50" и "Ports: http 32400/TCP"
3. Проверь соединение изнутри кластера
kubectl run curl --rm -it --image=curlimages/curl -- \
curl -v http://plex.ext-proxy.svc.cluster.local:32400
4. Проверь внешний доступ
# Замени IP на kube-vip VIP
curl -H "Host: plex.home.ru" http://192.168.1.100/
# Через DNS:
curl http://plex.home.ru/
5. Убедись что ingress-nginx применил правила
kubectl -n ext-proxy describe ingress plex
# Должно показать Rules → host → plex.home.ru → plex:32400
# Проверь что конфиг nginx обновился:
kubectl -n ingress-nginx exec -it \
$(kubectl -n ingress-nginx get pod -l 'app.kubernetes.io/name=ingress-nginx' -o name | head -1) \
-- nginx -T | grep plex
Пример сгенерированных манифестов
При такой конфигурации:
ext_proxy_proxies:
- name: plex
hosts:
- plex.home.ru
ips:
- 192.168.1.50
port: 32400
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
Создаются следующие ресурсы Kubernetes:
Service:
apiVersion: v1
kind: Service
metadata:
name: plex
namespace: ext-proxy
spec:
type: ClusterIP
ports:
- name: http
port: 32400
targetPort: 32400
protocol: TCP
Endpoints:
apiVersion: v1
kind: Endpoints
metadata:
name: plex
namespace: ext-proxy
subsets:
- addresses:
- ip: "192.168.1.50"
ports:
- name: http
port: 32400
protocol: TCP
Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: plex
namespace: ext-proxy
annotations:
nginx.ingress.kubernetes.io/proxy-connect-timeout: "60"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-body-size: "0" # переопределено на уровне прокси
nginx.ingress.kubernetes.io/proxy-http-version: "1.1" # websocket=true
spec:
ingressClassName: nginx
rules:
- host: plex.home.ru
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: plex
port:
number: 32400
Helm Chart — самостоятельное использование без Ansible
Чарт находится в addons/ext-proxy/role/chart/. Развернуть напрямую:
# Из корня проекта:
helm upgrade --install ext-proxy addons/ext-proxy/role/chart \
--namespace ext-proxy \
--create-namespace \
--values my-values.yaml
Сгенерировать манифесты без деплоя (для проверки):
helm template ext-proxy addons/ext-proxy/role/chart \
--values my-values.yaml
Устранение неисправностей
502 Bad Gateway
Причина: ingress-nginx достигает Service, но Service не может достучаться до внешнего IP.
# 1. Проверь Endpoints заполнены:
kubectl -n ext-proxy get endpoints plex
# "Addresses" не должен быть пустым. Если "<none>" — Endpoints отсутствует или некорректен.
# 2. Проверь доступность внешнего IP с ноды кластера:
ssh master01
curl -v http://192.168.1.50:32400
# 3. Проверь файрвол на внешнем хосте — он должен разрешать входящие из CIDR кластера.
503 Service Temporarily Unavailable
Причина: у ingress-nginx нет здоровых Endpoints.
# Проверь наличие endpoints:
kubectl -n ext-proxy describe endpoints plex
# Убедись что у service есть ClusterIP:
kubectl -n ext-proxy get svc plex
# Проверь логи ingress-nginx:
kubectl -n ingress-nginx logs -l app.kubernetes.io/name=ingress-nginx --tail=100
404 Not Found
Причина: правило Ingress не совпало — ошибка в имени хоста или пути.
# 1. Проверь заголовок Host при запросе:
curl -v -H "Host: plex.home.ru" http://192.168.1.100/
# 2. Посмотри описание Ingress:
kubectl -n ext-proxy describe ingress plex
# Раздел "Rules" — хост и путь должны совпадать точно.
# 3. Проверь класс Ingress:
kubectl -n ext-proxy get ingress plex -o jsonpath='{.spec.ingressClassName}'
# Должен совпадать с классом ingress-nginx (обычно "nginx")
DNS не резолвится
# Узнай куда резолвится хост:
nslookup plex.home.ru
dig plex.home.ru
# Должен вернуть kube-vip VIP, а не реальный IP сервера Plex.
# Проверь с явным IP:
curl -H "Host: plex.home.ru" http://<kube-vip-IP>/
Проблемы с TLS / HTTPS
# Проверь наличие TLS Secret в нужном namespace:
kubectl -n ext-proxy get secret wildcard-tls
# Проверь что cert-manager выпустил сертификат:
kubectl -n ext-proxy get certificate
kubectl -n ext-proxy describe certificate plex
# Логи cert-manager:
kubectl -n cert-manager logs -l app=cert-manager --tail=50
Basic Auth возвращает 401
# Проверь с учётными данными:
curl -u admin:мойпароль http://plex.home.ru/
# Убедись что в Secret есть ключ "auth":
kubectl -n ext-proxy get secret plex-auth -o jsonpath='{.data.auth}' | base64 -d
# Должно вывести: admin:$apr1$...
# Проверь аннотации в Ingress:
kubectl -n ext-proxy get ingress plex -o yaml | grep auth
Обрывы WebSocket-соединения
# Проверь аннотацию proxy-http-version:
kubectl -n ext-proxy get ingress plex -o yaml | grep proxy-http
# Для сервисов где нужен длинный таймаут (стриминг):
# Добавь в annotations:
# nginx.ingress.kubernetes.io/proxy-read-timeout: "86400"
# nginx.ingress.kubernetes.io/proxy-send-timeout: "86400"
Логи ingress-nginx
# Смотреть логи nginx в реальном времени:
kubectl -n ingress-nginx logs \
-l app.kubernetes.io/name=ingress-nginx \
--tail=100 -f
# Проверить nginx.conf применился:
kubectl -n ingress-nginx exec \
$(kubectl -n ingress-nginx get pod -l 'app.kubernetes.io/name=ingress-nginx' -o name | head -1) \
-- cat /etc/nginx/nginx.conf | grep -A5 plex
Удаление
helm -n ext-proxy uninstall ext-proxy
kubectl delete namespace ext-proxy
Удалить только один прокси без полного сноса релиза:
- Убери запись из
ext_proxy_proxiesвgroup_vars/all/addons.yml - Запусти
make addon-ext-proxy— Helm upgrade удалит убранные ресурсы
Интеграция с проектом
| Зависимость | Примечание |
|---|---|
ingress-nginx (addon_ingress_nginx: true) |
Должен быть установлен первым |
kube-vip |
Предоставляет VIP для LoadBalancer сервиса ingress-nginx |
cert-manager (опционально) |
Нужен только при tls.certManager.enabled: true |
Команды Makefile
make addon-ext-proxy # развернуть / обновить
make addon-ext-proxy ARGS="-e ext_proxy_vip=..." # с явным VIP в сводке
Переменные Ansible
| Переменная | По умолчанию | Описание |
|---|---|---|
ext_proxy_namespace |
ext-proxy |
Kubernetes namespace |
ext_proxy_release_name |
ext-proxy |
Имя Helm release |
ext_proxy_proxies |
[] |
Список определений внешних сервисов |
ext_proxy_defaults.* |
см. defaults | Глобальные значения по умолчанию |
ext_proxy_vip |
"" |
kube-vip VIP — отображается в сводке после установки |