refactor: переименовать аддон ext-proxy → ingress-proxypass

- addons/ext-proxy/ → addons/ingress-proxypass/ (git mv, история сохранена)
- Все переменные Ansible: ext_proxy_* → ingress_proxypass_*
- Все имена ресурсов K8s: ext-proxy → ingress-proxypass (namespace, chart, release)
- Helm-хелперы: "ext-proxy.*" → "ingress-proxypass.*"
- Makefile: addon-ext-proxy → addon-ingress-proxypass
- group_vars/all/addons.yml: addon_ext_proxy → addon_ingress_proxypass
- playbooks/addons.yml: обновлена ссылка на роль
- docs/addons.md, README.md: обновлены все упоминания
This commit is contained in:
Sergey Antropoff
2026-04-26 07:32:02 +03:00
parent 12578dda27
commit e9e0ffa3c2
19 changed files with 136 additions and 136 deletions

View File

@@ -0,0 +1,725 @@
# 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. Включи аддон и определи сервисы:**
```yaml
# group_vars/all/addons.yml
addon_ingress_proxypass: true
ingress_proxypass_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. Разверни:**
```bash
make addon-ingress-proxypass
# с явным VIP в сводке:
make addon-ingress-proxypass ARGS="-e ingress_proxypass_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`.
### Глобальные значения по умолчанию
```yaml
ingress_proxypass_namespace: "ingress-proxypass" # Kubernetes namespace
ingress_proxypass_release_name: "ingress-proxypass" # имя Helm release
ingress_proxypass_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
username: "" # логин (пароль хэшируется автоматически)
password: "" # пароль в открытом виде — задавать через vault!
credentials: "" # готовая htpasswd-строка (приоритет над username/password)
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
```
### Поля определения прокси
```yaml
ingress_proxypass_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
username: admin # логин — хэш генерируется автоматически
password: "{{ vault_mypass }}" # пароль из vault (рекомендуется)
# ИЛИ готовая строка: credentials: "admin:$apr1$..."
# ИЛИ существующий Secret: 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:
```yaml
ingress_proxypass_defaults:
tls:
enabled: true
secretName: wildcard-tls # должен существовать в ingress_proxypass_namespace
ingress_proxypass_proxies:
- name: plex
hosts: [plex.home.ru]
ips: [192.168.1.50]
port: 32400
```
Переопределить TLS для конкретного прокси:
```yaml
ingress_proxypass_proxies:
- name: router
hosts: [router.home.ru]
ips: [192.168.1.1]
port: 8080
tls:
enabled: false # отключить TLS только для этого прокси
```
### TLS — автоматический выпуск через cert-manager
```yaml
ingress_proxypass_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
Просто укажи логин и пароль — хэш генерируется автоматически:
```yaml
# group_vars/all/addons.yml
ingress_proxypass_defaults:
auth:
enabled: true
username: admin
password: "{{ vault_ingress_proxypass_password }}" # пароль из vault
```
```yaml
# group_vars/all/vault.yml
vault_ingress_proxypass_password: "мойсекретныйпароль"
```
Ansible автоматически вызовет `openssl passwd -apr1` и запишет хэш в Kubernetes Secret. Пароль в открытом виде **не попадает** в Helm values, логи или конфиги.
Выборочно для конкретного прокси (остальные без auth):
```yaml
ingress_proxypass_proxies:
- name: router
hosts: [router.home.ru]
ips: [192.168.1.1]
port: 8080
auth:
enabled: true
username: admin
password: "{{ vault_router_password }}"
- name: plex # без auth
hosts: [plex.home.ru]
ips: [192.168.1.50]
port: 32400
```
Разные пароли для разных прокси — каждый прокси создаёт свой отдельный Secret.
**Если уже есть готовая htpasswd-строка** (поле `credentials` имеет приоритет над `username`/`password`):
```yaml
auth:
enabled: true
credentials: "admin:$apr1$Rh0Ycxl9$rPTH7gRHfMBkS.7.Q1BxM/"
# Сгенерировать вручную: htpasswd -nb admin 'пароль'
```
**Использование существующего Secret** (ключ должен называться `auth`, тип `Opaque`):
```yaml
auth:
enabled: true
secretName: my-shared-htpasswd # создать вручную через kubectl
```
**Приоритет:** `secretName` > `credentials` > `username` + `password`
> **Примечание:** `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. Отключи для конкретного прокси если не нужно:
```yaml
- name: router
...
websocket: false
```
### Несколько хостов для одного сервиса
```yaml
- name: plex
hosts:
- plex.home.ru
- plex.internal
- plex.lan
ips: [192.168.1.50]
port: 32400
```
Для каждого хоста создаётся отдельное правило Ingress, все ссылаются на один и тот же backend.
### Несколько backend IP (round-robin / резервирование)
```yaml
- name: homeassistant
hosts: [ha.home.ru]
ips:
- 192.168.1.100 # основной
- 192.168.1.101 # резервный
port: 8123
```
K8s создаёт два адреса в объекте `Endpoints`. kube-proxy распределяет трафик между ними по round-robin. Для активно-пассивного резервирования необходим внешний механизм проверки работоспособности.
### Маршрутизация по пути
```yaml
- 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
```yaml
- 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 и сгенерированные аннотации.
---
## Как добавить новый внешний сервис
1. Добавь запись в `ingress_proxypass_proxies` в файле `group_vars/all/addons.yml`:
```yaml
ingress_proxypass_proxies:
# ... существующие записи ...
- name: homeassistant
hosts: [ha.home.ru]
ips: [192.168.1.150]
port: 8123
websocket: true
```
2. Запусти аддон повторно:
```bash
make addon-ingress-proxypass
```
Helm upgrade идемпотентен — существующие ресурсы обновляются, новые добавляются.
3. Добавь 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:
```yaml
# configmap coredns — добавить в Corefile
rewrite name plex.home.ru ingress-nginx.ingress-nginx.svc.cluster.local
```
### Узнать VIP kube-vip
```bash
# EXTERNAL-IP сервиса ingress-nginx LoadBalancer — это и есть VIP:
kubectl -n ingress-nginx get svc ingress-nginx-controller
```
---
## Проверка
### 1. Убедись что ресурсы созданы
```bash
kubectl -n ingress-proxypass 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 заполнены
```bash
kubectl -n ingress-proxypass describe endpoints plex
# Должно показать "Addresses: 192.168.1.50" и "Ports: http 32400/TCP"
```
### 3. Проверь соединение изнутри кластера
```bash
kubectl run curl --rm -it --image=curlimages/curl -- \
curl -v http://plex.ingress-proxypass.svc.cluster.local:32400
```
### 4. Проверь внешний доступ
```bash
# Замени IP на kube-vip VIP
curl -H "Host: plex.home.ru" http://192.168.1.100/
# Через DNS:
curl http://plex.home.ru/
```
### 5. Убедись что ingress-nginx применил правила
```bash
kubectl -n ingress-proxypass 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
```
---
## Пример сгенерированных манифестов
При такой конфигурации:
```yaml
ingress_proxypass_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:**
```yaml
apiVersion: v1
kind: Service
metadata:
name: plex
namespace: ingress-proxypass
spec:
type: ClusterIP
ports:
- name: http
port: 32400
targetPort: 32400
protocol: TCP
```
**Endpoints:**
```yaml
apiVersion: v1
kind: Endpoints
metadata:
name: plex
namespace: ingress-proxypass
subsets:
- addresses:
- ip: "192.168.1.50"
ports:
- name: http
port: 32400
protocol: TCP
```
**Ingress:**
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: plex
namespace: ingress-proxypass
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/ingress-proxypass/role/chart/`. Развернуть напрямую:
```bash
# Из корня проекта:
helm upgrade --install ingress-proxypass addons/ingress-proxypass/role/chart \
--namespace ingress-proxypass \
--create-namespace \
--values my-values.yaml
```
Сгенерировать манифесты без деплоя (для проверки):
```bash
helm template ingress-proxypass addons/ingress-proxypass/role/chart \
--values my-values.yaml
```
---
## Устранение неисправностей
### 502 Bad Gateway
**Причина:** ingress-nginx достигает Service, но Service не может достучаться до внешнего IP.
```bash
# 1. Проверь Endpoints заполнены:
kubectl -n ingress-proxypass 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.
```bash
# Проверь наличие endpoints:
kubectl -n ingress-proxypass describe endpoints plex
# Убедись что у service есть ClusterIP:
kubectl -n ingress-proxypass get svc plex
# Проверь логи ingress-nginx:
kubectl -n ingress-nginx logs -l app.kubernetes.io/name=ingress-nginx --tail=100
```
### 404 Not Found
**Причина:** правило Ingress не совпало — ошибка в имени хоста или пути.
```bash
# 1. Проверь заголовок Host при запросе:
curl -v -H "Host: plex.home.ru" http://192.168.1.100/
# 2. Посмотри описание Ingress:
kubectl -n ingress-proxypass describe ingress plex
# Раздел "Rules" — хост и путь должны совпадать точно.
# 3. Проверь класс Ingress:
kubectl -n ingress-proxypass get ingress plex -o jsonpath='{.spec.ingressClassName}'
# Должен совпадать с классом ingress-nginx (обычно "nginx")
```
### DNS не резолвится
```bash
# Узнай куда резолвится хост:
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
```bash
# Проверь наличие TLS Secret в нужном namespace:
kubectl -n ingress-proxypass get secret wildcard-tls
# Проверь что cert-manager выпустил сертификат:
kubectl -n ingress-proxypass get certificate
kubectl -n ingress-proxypass describe certificate plex
# Логи cert-manager:
kubectl -n cert-manager logs -l app=cert-manager --tail=50
```
### Basic Auth возвращает 401
```bash
# Проверь с учётными данными:
curl -u admin:мойпароль http://plex.home.ru/
# Убедись что в Secret есть ключ "auth":
kubectl -n ingress-proxypass get secret plex-auth -o jsonpath='{.data.auth}' | base64 -d
# Должно вывести: admin:$apr1$...
# Проверь аннотации в Ingress:
kubectl -n ingress-proxypass get ingress plex -o yaml | grep auth
```
### Обрывы WebSocket-соединения
```bash
# Проверь аннотацию proxy-http-version:
kubectl -n ingress-proxypass 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
```bash
# Смотреть логи 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
```
---
## Удаление
```bash
helm -n ingress-proxypass uninstall ingress-proxypass
kubectl delete namespace ingress-proxypass
```
Удалить только один прокси без полного сноса релиза:
1. Убери запись из `ingress_proxypass_proxies` в `group_vars/all/addons.yml`
2. Запусти `make addon-ingress-proxypass` — Helm upgrade удалит убранные ресурсы
---
## Интеграция с проектом
| Зависимость | Примечание |
|---|---|
| `ingress-nginx` (`addon_ingress_nginx: true`) | Должен быть установлен первым |
| `kube-vip` | Предоставляет VIP для LoadBalancer сервиса ingress-nginx |
| `cert-manager` (опционально) | Нужен только при `tls.certManager.enabled: true` |
### Команды Makefile
```bash
make addon-ingress-proxypass # развернуть / обновить
make addon-ingress-proxypass ARGS="-e ingress_proxypass_vip=..." # с явным VIP в сводке
```
### Переменные Ansible
| Переменная | По умолчанию | Описание |
|---|---|---|
| `ingress_proxypass_namespace` | `ingress-proxypass` | Kubernetes namespace |
| `ingress_proxypass_release_name` | `ingress-proxypass` | Имя Helm release |
| `ingress_proxypass_proxies` | `[]` | Список определений внешних сервисов |
| `ingress_proxypass_defaults.*` | см. defaults | Глобальные значения по умолчанию |
| `ingress_proxypass_vip` | `""` | kube-vip VIP — отображается в сводке после установки |