Двойной HAProxy на Proxmox: как безопасно публиковать сервисы Kubernetes из приватной сети
Развертывание собственного кластера Kubernetes на физическом выделенном сервере (bare metal) под управлением гипервизора Proxmox VE — популярное решение для снижения затрат на облачную инфраструктуру. Однако в отличие от управляемых облачных платформ (AWS, Google Cloud или Yandex Cloud), где для публикации приложений достаточно создать сервис типа LoadBalancer, на выделенном сервере инженеру обычно доступен всего один публичный IP-адрес, а все виртуальные машины кластера находятся в изолированной приватной сети за механизмом трансляции сетевых адресов (NAT).
Попытки пробросить входящий трафик через простые правила iptables или выставить порт API-сервера (6443) наружу создают прямые угрозы безопасности и не позволяют гибко управлять SSL/TLS-сертификатами для сотен сервисов. Надежным решением становится двухуровневая архитектура граничного шлюза (edge routing) на базе двух экземпляров балансировщика HAProxy: внешний экземпляр работает на самом хосте Proxmox, а внутренний — внутри кластера Kubernetes в роли Ingress Controller.
Топология двойного шлюза: разделение обязанностей
Главный принцип архитектуры — строгое разделение сетевой ответственности между хостом виртуализации и кластером:
- Внешний HAProxy на хосте Proxmox (Edge Router). Принимает входящие TCP/HTTP-пакеты из интернета на публичном IP. Его задача — фильтровать трафик по белому списку доменов, защищать control-plane кластера и перенаправлять пакеты на статические порты рабочих узлов без расшифровки пользовательского трафика (режим TLS Passthrough).
- Внутренний HAProxy Ingress Controller (Cluster Gateway). Развернут внутри Kubernetes как DaemonSet (по экземпляру на каждом рабочем узле). Он слушает статические порты NodePort (
30080для HTTP и30443для HTTPS), завершает (терминирует) шифрование TLS, читает правила Ingress и маршрутизирует запросы к подам приложений.
Сетевой маршрут пакета выглядит следующим образом:
Клиент -> Публичный IP хоста Proxmox (HAProxy) -> Рабочие узлы K8s (NodePort 30080/30443) -> HAProxy Ingress Controller -> Сервисы и Поды приложений.
Защита API Kubernetes (kube-apiserver) организована локально: внешний HAProxy привязывает порт 6443 исключительно к интерфейсу 127.0.0.1, распределяя запросы по алгоритму leastconn между узлами control-plane. Прямой доступ к API из интернета полностью заблокирован: администраторы подключаются через SSH-туннелирование (port-forwarding) или доверенный VPN.
Механика фильтрации: ACME HTTP-01 и сквозной TLS по SNI
Особое внимание уделено обработке портов 80 и 443 на внешнем балансировщике:
- Порт 80 (HTTP). Внешний HAProxy удаляет небезопасный заголовок
Proxy(защита от уязвимости httpoxy), добавляет заголовкиX-Real-IPиX-Forwarded-Proto, после чего сверяет заголовокHostсо списком разрешенных доменов в/etc/haproxy/allow_domains.txt. Запросы к служебному пути/.well-known/acme-challenge/беспрепятственно передаются в кластер для автоматического подтверждения прав на домен центрами сертификации (Let's Encrypt). Все остальные HTTP-запросы возвращают постоянный редирект на защищенный протокол HTTPS (HTTP 301). - Порт 443 (HTTPS). Внешний HAProxy работает в чистом режиме TCP-проксирования. Директива
tcp-request inspect-delay 5sдает балансировщику время проинспектировать начальный пакет рукопожатия TLS (ClientHello) и извлечь имя запрашиваемого сервера через параметр SNI (req_ssl_sni). Если домен совпадает с белым списком или wildcard-шаблоном (.example.com), зашифрованный TCP-поток направляется на NodePort30443рабочих узлов. Любые соединения без SNI или с чужими именами немедленно сбрасываются.
Такой подход защищает внутренний кластер от сканеров уязвимостей и не требует хранения приватных SSL-ключей на хосте Proxmox.
Автоматизация периметра: шаблонизация в Terraform и валидация в Ansible
Чтобы исключить ошибки ручной правки конфигураций при добавлении или пересоздании виртуальных машин, генерация haproxy.cfg полностью автоматизирована:
- Terraform. С помощью встроенной функции
templatefileи ресурсаlocal_fileгенерирует актуальный конфигурационный файл на основе IP-адресов созданных виртуальных машин из пуловkube_api_backend,worker_http_backendиworker_https_backend. - Ansible. Реализует безопасный пайплайн доставки: файл загружается на хост под временным именем
haproxy.cfg.tmp, после чего выполняется валидация синтаксиса официальной утилитой:haproxy -c -f /etc/haproxy/haproxy.cfg.tmpТолько при успешном коде возврата временный файл заменяет рабочий, и сервис перезагружается без разрыва существующих соединений (graceful reload).
Пошаговое руководство: развертывание Ingress Controller и cert-manager
После подготовки внешнего шлюза внутри кластера Kubernetes разворачиваются компоненты приема трафика и автоматического выпуска сертификатов.
1. Установка HAProxy Ingress Controller
По официальной инструкции из репозитория Helm-чартов HAProxy добавьте репозиторий и обновите индексы:
helm repo add haproxytech https://haproxytech.github.io/helm-charts
helm repo update
Создайте файл параметров values.yaml, настроив режим DaemonSet и указав статические порты:
controller:
kind: DaemonSet
service:
type: NodePort
nodePorts:
http: 30080
https: 30443
externalTrafficPolicy: Local
config:
ssl-redirect: "false"
Параметр ssl-redirect: "false" критически важен: он предотвращает циклическую переадресацию запросов ACME HTTP-01, поскольку первичный редирект уже выполнен внешним HAProxy.
Выполните установку в выделенный namespace:
helm install haproxy-ingress haproxytech/kubernetes-ingress \
--namespace haproxy-controller \
--create-namespace \
-f values.yaml
Проверьте статус подов и сервиса:
kubectl get pods -n haproxy-controller -o wide
kubectl get svc -n haproxy-controller
2. Установка cert-manager и выпуск сертификатов Let's Encrypt
Установите контроллер сертификатов по документации cert-manager:
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true
Настройте манифест ClusterIssuer для проверки по протоколу HTTP-01 согласно руководству cert-manager по HTTP-01. Рекомендуется всегда начинать со staging-сервера Let's Encrypt во избежание блокировки по лимитам запросов:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-staging-key
solvers:
- http01:
ingress:
class: haproxy
Примените манифест командой kubectl apply -f cluster-issuer-staging.yaml и создайте Ingress-ресурс с аннотацией cert-manager.io/cluster-issuer: letsencrypt-staging.
Послойная проверка и ограничения архитектуры
Контроль работоспособности схемы выполняется послойно:
- Проверка DNS. Убедитесь, что А-запись домена указывает строго на публичный IP хоста Proxmox.
- Проверка фильтрации портов. Запрос
curl -I http://unauthorized.domainдолжен сбрасываться или отклоняться внешним HAProxy без передачи в кластер. - Проверка статуса сертификата. Выполните команду
kubectl get certificate,challenge,order -A. После успешного перехода заказа в статусReadyпереключите ClusterIssuer на production-сервер Let's Encrypt.
Ограничения безопасности: Режим TLS Passthrough не позволяет внешнему HAProxy анализировать содержимое зашифрованных HTTPS-пакетов, поэтому базовый балансировщик не заменяет полноценный WAF (Web Application Firewall). При необходимости защиты от объемных распределенных атак (DDoS) перед хостом Proxmox рекомендуется использовать внешние облачные сервисы фильтрации трафика.

