Как вывести безрецептурный сервис для StatefulSet извне в Кубернете
Используя kubernetes-kafka в качестве отправной точки с минибуком.
Для обнаружения службы внутри кластера используется служба StatefulSet и служба безгласных.
Цель состоит в том, чтобы разоблачить отдельных Kafka Brokers извне, которые внутренне адресованы как:
kafka-0.broker.kafka.svc.cluster.local:9092
kafka-1.broker.kafka.svc.cluster.local:9092
kafka-2.broker.kafka.svc.cluster.local:9092
Ограничение состоит в том, что эта внешняя служба может адресовать брокеров конкретно.
Каков правильный (или один возможный) способ обойти это? Возможно ли открыть внешнюю службу за kafka-x.broker.kafka.svc.cluster.local:9092
?
Ответы
Ответ 1
Решения до сих пор были недостаточно удовлетворительными для меня, поэтому я собираюсь опубликовать свой ответ. Мои цели:
- Домены должны по-прежнему динамически управляться через StatefulSet как можно больше.
- Создайте внешнюю услугу на Pod (т.е. Kafka Broker) для клиентов Producer/Consumer и избегайте балансировки нагрузки.
- Создайте внутреннюю службу безглавых, чтобы каждый Брокер мог общаться друг с другом.
Начиная с Yolean/kubernetes-kafka, единственное, чего не хватает, это разоблачение услуги извне и две проблемы при этом.
- Создание уникальных ярлыков для каждого брокерского модуля, чтобы мы могли создать внешнюю службу для каждого из брокерских модулей.
- Сообщание брокерам обмениваться информацией друг с другом с использованием внутренней службы при настройке Kafka, чтобы сообщить производителю/потребителям обмениваться информацией через внешнюю службу.
На этикетках и внешних услугах:
Чтобы генерировать метки в папке, эта проблема была действительно полезна. Используя его в качестве руководства, мы добавляем следующую строку в свойство 10broker-config.yml init.sh
с:
kubectl label pods ${HOSTNAME} kafka-set-component=${HOSTNAME}
Мы сохраняем существующее безгласное обслуживание, но мы также генерируем внешнюю услугу на модуль с использованием метки (я добавил их в 20dns.yml):
apiVersion: v1
kind: Service
metadata:
name: broker-0
namespace: kafka
spec:
type: NodePort
ports:
- port: 9093
nodePort: 30093
selector:
kafka-set-component: kafka-0
Настройка Kafka с помощью внутренних/внешних слушателей
Я нашел эту проблему невероятно полезной, пытаясь понять, как настроить Kafka.
Это снова требует обновления свойств init.sh
и server.properties
в файле 10broker-config.yml со следующим:
Добавьте на server.properties
чтобы обновить протоколы безопасности (в настоящее время используется PLAINTEXT
):
listener.security.protocol.map=INTERNAL_PLAINTEXT:PLAINTEXT,EXTERNAL_PLAINTEXT:PLAINTEXT
inter.broker.listener.name=INTERNAL_PLAINTEXT
Динамически определять внешний IP и внешний порт для каждого Pod в init.sh
:
EXTERNAL_LISTENER_IP=<your external addressable cluster ip>
EXTERNAL_LISTENER_PORT=$((30093 + ${HOSTNAME##*-}))
Затем настройте listeners
и advertised.listeners
IP - адрес для EXTERNAL_LISTENER
и INTERNAL_LISTENER
(также в init.sh
собственности):
sed -i "s/#listeners=PLAINTEXT:\/\/:9092/listeners=INTERNAL_PLAINTEXT:\/\/0.0.0.0:9092,EXTERNAL_PLAINTEXT:\/\/0.0.0.0:9093/" /etc/kafka/server.properties
sed -i "s/#advertised.listeners=PLAINTEXT:\/\/your.host.name:9092/advertised.listeners=INTERNAL_PLAINTEXT:\/\/$HOSTNAME.broker.kafka.svc.cluster.local:9092,EXTERNAL_PLAINTEXT:\/\/$EXTERNAL_LISTENER_IP:$EXTERNAL_LISTENER_PORT/" /etc/kafka/server.properties
Очевидно, что это не полное решение для производства (например, для обеспечения безопасности для внешних брокеров), и я все еще уточняю свое понимание того, как также позволить внутреннему производителю/потребителям также общаться с брокерами.
Однако до сих пор это лучший подход для моего понимания Кубернеса и Кафки.
Ответ 2
Мы решили это в 1.7, изменив Type=NodePort
службу на Type=NodePort
и установив Type=NodePort
externalTrafficPolicy=Local
. Это обходит внутреннюю балансировку нагрузки Сервиса и трафик, предназначенный для определенного узла на этом узле, порт будет работать, только если на этом узле находится модуль Kafka.
apiVersion: v1
kind: Service
metadata:
name: broker
spec:
externalTrafficPolicy: Local
ports:
- nodePort: 30000
port: 30000
protocol: TCP
targetPort: 9092
selector:
app: broker
type: NodePort
Например, у нас есть два узла nodeA и nodeB, nodeB работает под управлением kafka. nodeA: 30000 не будет подключаться, но nodeB: 30000 будет подключаться к kafka pod, работающему на узле B.
https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typenodeport
Обратите внимание, что это также было доступно в версиях 1.5 и 1.6 в качестве аннотации к бетам, здесь можно найти дополнительную информацию о доступности функций: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/# сохраняющих-на-клиент-источник-IP
Заметим также, что, хотя это связывает модуль kafka с конкретным внешним сетевым идентификатором, он не гарантирует, что ваш объем хранилища будет привязан к этому сетевому идентификатору. Если вы используете VolumeClaimTemplates в StatefulSet, то ваши тома привязаны к модулю, тогда как kafka ожидает, что том будет привязан к идентификатору сети.
Например, если перезапуск kafka-0 и kafka-0 появляется на nodeC вместо nodeA, kafka-0 pvc (при использовании VolumeClaimTemplates) имеет данные, что это для nodeA, а брокер, работающий на kafka-0, начинает отклонять запросы, думающие что это nodeA not nodeC.
Чтобы исправить это, мы с нетерпением ждем локальных постоянных томов, но прямо сейчас у нас есть один ПВХ для нашего состояния kafka StatefulSet, и данные хранятся в $NODENAME
на этом ПВХ для привязки данных объема к определенному узлу.
https://github.com/kubernetes/features/issues/121 https://kubernetes.io/docs/concepts/storage/volumes/#local
Ответ 3
Я хотел бы сказать, что я прочитал этот Вопрос и Ответ 3 раза прежде, чем попытался обернуть мою голову тем, что было Безголовыми Службами/в чем смысл их. (и я никогда полностью не понимал Headless Services или что это за вопросы и ответы.)
И в 4-м чтении (пересмотрев его после дальнейшего обучения себя) оно наконец щелкнуло/я наконец понял.
Таким образом, цель этого ответа состоит в том, чтобы переформулировать вопрос/проблему/надира и ответить так, как если бы он объяснял это школьнику. Так что другие, кто наткнется на это, получат значимость Надира в первом чтении.
Полезные базовые знания:
-
Существует служба типа: ExternalName.
Служба ExternalName просто указывает на адрес DNS.
Существует два вида сервиса ExternalName:
- Без кластерного IP:
Хорошим вариантом использования было бы использование кластера тестирования и производственного кластера для совместного использования как можно большего количества кода. (и для простого удобства в некоторых случаях) Контроллеры как в тестировании, так и в производстве будут указывать на одно и то же имя DNS-адреса внутреннего кластера службы, которое будет предсказуемым кодом многократного использования. Разница будет в том, что среда тестирования будет иметь службу, которая указывает на службу SQL, которая существует внутри кластера. Рабочий кластер будет использовать Службу ExternalName, которая будет перенаправлять/указывать на DNS-адрес решения SQL, управляемого облачными провайдерами. - С IP-адресом кластера:
Это версия службы ExternalName, которая является ключом к решению.
-
Набор Stateful состоит из 3 частей:
- Порядковый номер (число)
- Постоянное хранение
- Постоянное и предсказуемое DNS-имя внутреннего кластера (оно вытекает из требования, что оно должно поставляться со службой Headless)
-
О Kube-Proxy нужно помнить 3 важных момента:
- Это гарантирует, что у всего есть уникальный IP.
- Он отвечает за реализацию IP виртуального статического кластера (IP-адрес виртуального статического кластера считается виртуальным, поскольку он существует только в каждом узле iptables в реализации iptables Kube-Proxy или в хэш-таблице ядра в версии ip-vs следующего поколения Kube-Proxy), а также отвечает за эффект логической балансировки нагрузки, возникающий при работе с обычными сервисами Kubernetes, имеющими IP-адрес кластера.
- KubeProxy отвечает за сопоставление трафика, поступающего через NodePorts, с соответствующей службой Kubernetes со статическим IP-адресом кластера. <- Это очень важно для требования о том, что сервисы с отслеживанием состояния должны быть доступны извне, предполагается, что NodePorts всегда участвует, когда дело касается внешних служб.
-
Есть четыре важных момента, которые нужно помнить о службе безголовых:
- Это создает предсказуемый адрес DNS.
- Он не действует как внутренний кластер Load Balancer. Вы говорите напрямую с модулем, идентифицированным предсказуемым адресом DNS. (что очень желательно для рабочих нагрузок с состоянием)
- У него нет IP статического кластера.
- Как побочный эффект качеств 2 и 3, он выходит за пределы области Kube-Proxy (который отвечает за направление трафика, поступающего на порты узлов в службы.) Я перефразирую это несколько раз, чтобы проблема усваивалась: NodePorts может обычно не перенаправляют трафик в Headless Services. Внешний трафик, поступающий в кластер, обычно не может быть перенаправлен в Headless Services. Это не интуитивно понятно, как внешне выставить безголовую службу.
Теперь, когда мы лучше понимаем проблему, давайте вернемся к вопросу: как внешняя защита может предоставлять служба без головы (которая указывает на отдельного члена набора с состоянием)?
Решение Часть 1:
Любой модуль в кластере может общаться с членами Statefulset.
Поскольку с сохранением состояния генерирует безголовую службу с предсказуемым внутренним кластерным DNS-адресом в форме:
statefulsetname- # associatedheadlessservice.namespace.svc.cluster.local:. Порт
Кафка-0.broker.kafka.svc.cluster.local: 9092
Кафка-1.broker.kafka.svc.cluster.local: 9092
Кафка-2.broker.kafka.svc.cluster.local: 9092
broker.kafka.svc.cluster.local: 9092, также может использоваться для ссылки на какой-либо доступный.
Решение Часть 2:
Вы разрешаете внешнему трафику общаться с членами набора с сохранением состояния, вводя вторую службу, которая может принимать внешний трафик, а затем перенаправляя трафик из этой службы в безголовую службу, которая может принимать только интернет-трафик.
Для каждого модуля в Stateful Set создается служба типа ExternalName с виртуальным статическим IP-адресом ClusterIP, управляемым Kube-Proxy. Каждая из этих Служб ExternalName указывает на/перенаправляет трафик на предсказуемый статический DNS-адрес внутреннего кластера, определенный в Решении 1, и поскольку эта служба ExternalName имеет виртуальный статический кластер IP, управляемый через Kube-Proxy, может существовать сопоставление с NodePorts.
Ответ 4
Измените службу из безголового ClusterIP в узел NodePort, который будет перенаправлять запрос на любой из узлов на установленном порту (30092 в моем примере) на порт 9042 на Kafkas. Вы попали бы в один из стручков, случайно, но я думаю, что все в порядке.
20dns.yml становится (что-то вроде этого):
# A no longer headless service to create DNS records
---
apiVersion: v1
kind: Service
metadata:
name: broker
namespace: kafka
spec:
type: NodePort
ports:
- port: 9092
- nodePort: 30092
# [podname].broker.kafka.svc.cluster.local
selector:
app: kafka
Отказ от ответственности: вам могут потребоваться две службы. Один безголовый для внутренних имен DNS и один NodePort для внешнего доступа. Я не пробовал это сам.
Ответ 5
Из документации kubernetes kafka:
Внешний доступ с хостом
Альтернативой является использование хоста для внешнего доступа. При использовании этого только один кафка-брокер может работать на каждом хосте, что в любом случае является хорошей идеей.
Чтобы переключиться на hostport, адрес рекламы kafka необходимо переключить на имя ExternalIP или ExternalDNS узла, на котором запущен брокер. в kafka/10broker-config.yml переключиться на
OUTSIDE_HOST=$(kubectl get node "$NODE_NAME" -o jsonpath='{.status.addresses[?(@.type=="ExternalIP")].address}')
OUTSIDE_PORT=${OutsidePort}
и в kafka/50kafka.yml добавьте hostport:
- name: outside
containerPort: 9094
hostPort: 9094