Доступ к нескольким подам 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

дамп сети из 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

© Habrahabr.ru