Доступ к нескольким подам Kubernetes по протоколу TCP и единственному внешнему IP
В облаке Амвера микросервисы и базы данных пользователей крутятся в кластере Kuberneres. Для доступа к приложениям обычно достаточно использовать nginx ngress controller который чудесно работает с HTTP(S)
трафиком и позволяет получить доступ к сотням сервисов с использованием только одного внешнего IP
адреса. Но, что если пользователь хочет получить доступ к развернутой СУБД не только изнутри кластера, но и извне? Конечно, мы могли бы выдавать каждой СУБД свой белый IP
и создать ClusterIP
, но это привело бы к дополнительным затратам на аренду адресов. В этой статье я бы хотел поделиться элегантным методом проксирования TCP
трафика на основе SNI
сообщений, который позволяет использовать один белый IP
на сотни СУБД.
Постановка задачи
Имеется кластер Kubernetes в котором развернуто некоторое количество СУБД различного типа: Postgres, MongoDB, Redis итд.
Требуется:
Предоставить возможность удаленного подключения к этим базам данных.
Включение/отключение внешнего доступа для конкретной СУБД не должно затрагивать другие работающие соединения.
Количество выделенных белых
IP
адресов существенно меньше количества СУБД.
Рассмотренные варианты решения
Nginx
Сам по себе nginx по умолчанию работает только с
HTTP(S)
трафиком, соответсвенно просто развернуть nginx ingress controller у Вас не получится, так как СУБД используют свои протоколы прикладного уровня и нам придется работать на транспортном уровне сTCP
пакетами. В отличие от протоколов более верхнего уровня тут мы не знаем ничего про хост, к которому хотим подключиться и проксирование на основе доменного имени в классическом смысле сделать не выйдет.Nginx поддерживает проксирование
TCP
пакетов, но настраивается это дело на уровне общей конфигурации. Т.е. если нужно будет добавить внешний доступ к новой СУБД придется вносить изменние вConfigMap
, что приведет к перезагрузке контроллера и разрыву установленныхTCP
соединений. Более того, придется каждому приложению выдавать свой собственный уникальный порт, что может повлечь за собой переконфигурацию firewall.apiVersion: v1 kind: ConfigMap metadata: name: udp-services namespace: ingress-nginx data: 8001: "my-db/postgres-1:8001" 8002: "my-db/postgres-2:8002"
Проксирование на основе
SNI
с определением имени хостаServer Name Indication (
SNI
) — это расшерение протоколаTLC
, который позволяет клиенту по время установки защищенного соединения явно передать имя хоста, с которым он хочет соединиться. Т.е. если мы используем защищенное соединение, то благодаряSNI
мы можем вписать домен, к которому хотим достучаться. К слову, большая часть современных СУБД клиентов поддерживает установку соединения поSSL
.
Kong Proxy
Спойлер: Данный метод не подошел для работы с PostgreSQL.
Kong умеет работать с TCP
трафиком двумя способами:
На основании порта — весь трафик, который пришел на данный порт будет перенаправлен на указанный Kubernetes Service.
TCP
соединения при этом будут распределяться на все запущенные поды.Используя SNI — Kong принимает шифрованный
TLC
поток и перенаправляет трафик в Kubernetes Service на основанииSNI
сообщения, которое было получено во времяTLC
рукопожатия. Стоит отметить, что за установкой защищенного соединения занимается именно Kong и далее в приложение уходит уже дешифрованныйTCP
поток.
В официальном примере подробно указан процесс установки и пример использования, поэтому в данной статье мы его опустим.
Отметим, однако, что получить доступ к СУБД используя только PGAdmin со включенным SSL
у нас не получилось. Если сначала настроить защищенный тунель через stunel, а потом через него выполнить подключение, то PGAdmin успешно его устанавливает. Чтобы лучше разобраться в происходящем, стоит взглянуть на сетевой дамп из Wireshark:
дамп сети из Wireshark
Из него видно, что когда идет подключение к обычному приложению из примера, то на 3-м шаге явно видно ClientHello
сообщение, в то время как при подключении к СУБД (нижние строчки) такого сообщения в явном виде нет. Изучив форумы, стало понятно, что это особенности установления защищенного соединения PostgreSQL, а именно проблема кроется в STARTTLS
.
Traefik
Наконец-то мы подобрались к тому решению, которое в итоге и было взято на вооружение.
Traefik в отличие от Kong справился с работой с СУБД через SNI
без использования stunel
. Для установки к кластер использовался Helm чарт со следующими значениями:
ingressClass:
enabled: true
isDefaultClass: false
additionalArguments:
- "--entryPoints.postgres.address=:5432/tcp"
providers:
kubernetesCRD:
enabled: true
allowCrossNamespace: true
listeners:
postgres:
port: 5432
protocol: HTTP
ports:
postgres:
port: 5432
expose:
default: true
exposedPort: 5432
protocol: TCP
А чтобы создать доступ для конкретной СУБД Postgres
, развернутой через оператор CNPG достаточно применить следующий TCPIngress
:
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: my-postgress-ingress
namespace: dbs
spec:
entryPoints:
- postgres
routes:
- match: HostSNI(`my-postgres.mydomain.ru`)
services:
- name: my-postgress-service-rw
port: 5432
tls:
secretName: tlsSecretName
Стоит отметить, что в entryPoints указано то название, которое прописано в listeners у чарта. Так, если нам нужно будет добавить mongoDB на другом порту, то мы пропишем его в listeners и ports в чарте и в entryPoints укажем mongoDB.
В серете tlsSecretName хранится обычный самоподписанный сертификат, выданный на указанный домен. Который можно получить через команду:
openssl req -new -x509 -keyout cert-key.pem -out cert.pem -days 365 -nodes
Добавив его в Kubernetes через kubectl
:
kubectl create secret tls tlsSecretName --cert=./cert.pem --key=./cert-key.pem