apache kafka-如何在Kubernetes中从外部公开StatefulSet的无头服务

使用kubernetes-kafka作为minikube的起点。

这使用StatefulSet和无头服务在群集内进行服务发现。

目标是在外部公开各个Kafka经纪人,这些经纪人在内部可以解决:

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是否可以公开外部服务?

Nadir Muzaffar asked 2020-06-16T23:28:09Z
5个解决方案
24 votes

我们在1.7中解决了此问题,将无头服务更改为$NODENAME并设置了externalTrafficPolicy=Local。这绕过了服务的内部负载平衡,并且仅当该节点上有Kafka pod时,发往该节点端口上特定节点的流量才有效。

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 pod。 nodeA:30000将不会连接,但是nodeB:30000将连接到在nodeB上运行的kafka pod。

[HTTPS://苦逼儿呢特殊.IO/docs/tutorials/services/source-IP/#source-IP-佛如-services-with-type no的port]

请注意,该功能也可以在1.5和1.6中作为Beta注释使用,有关功能可用性的更多信息,请参见:[https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/ #preserving-the-client-source-ip]

还要注意,尽管这将kafka pod绑定到特定的外部网络标识,但并不能保证您的存储卷将与该网络标识绑定。 如果您在StatefulSet中使用VolumeClaimTemplates,则您的卷将绑定到Pod,而kafka则希望该卷被绑定到网络标识。

例如,如果kafka-0 pod重新启动并且kafka-0出现在nodeC而不是nodeA上,则kafka-0的pvc(如果使用VolumeClaimTemplates)具有用于nodeA的数据,并且在kafka-0上运行的代理开始拒绝请求思考 它是nodeA而不是nodeC。

为了解决这个问题,我们期待本地持久卷,但是现在我们为kafka StatefulSet提供了一个PVC,数据存储在该PVC的$NODENAME下,以将卷数据绑定到特定节点。

[HTTPS://GitHub.com/苦逼儿呢特殊/features/issues/121][HTTPS://苦逼儿呢特殊.IO/docs/concepts/storage/volumes/#local]

Jason Kincl answered 2020-06-16T23:31:39Z
19 votes

到目前为止,解决方案还不足以让我自己满意,所以我将发表自己的答案。 我的目标:

  1. 仍应通过StatefulSet尽可能动态地管理Pod。
  2. 为生产者/消费者客户端为每个Pod(即Kafka经纪人)创建外部服务,并避免负载平衡。
  3. 创建内部无头服务,以便每个Broker可以相互通信。

从Yolean / kubernetes-kafka开始,唯一缺少的是在外部公开服务,这样做有两个挑战。

  1. 为每个Broker容器生成唯一的标签,以便我们可以为每个Broker容器创建一个外部服务。
  2. 告诉代理使用内部服务相互通信,同时配置Kafka告诉生产者/消费者通过外部服务进行通信。

每个吊舱标签和外部服务:

要为每个吊舱生成标签,此问题确实很有帮助。 以此为指导,我们将以下行添加到10broker-config.yml listeners属性中:

kubectl label pods ${HOSTNAME} kafka-set-component=${HOSTNAME}

我们保留了现有的无头服务,但是我们还使用标签为每个pod生成了一个外部服务(我将它们添加到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方面非常有用。

这再次需要使用以下命令更新10broker-config.yml中的listenersadvertised.listeners属性:

将以下内容添加到listeners以更新安全协议(当前使用advertised.listeners):

listener.security.protocol.map=INTERNAL_PLAINTEXT:PLAINTEXT,EXTERNAL_PLAINTEXT:PLAINTEXT
inter.broker.listener.name=INTERNAL_PLAINTEXT

动态确定listeners中每个Pod的外部IP和外部端口:

EXTERNAL_LISTENER_IP=<your external addressable cluster ip>
EXTERNAL_LISTENER_PORT=$((30093 + ${HOSTNAME##*-}))

然后为EXTERNAL_LISTENERINTERNAL_LISTENER(同样在init.sh属性中)配置listenersadvertised.listeners IP:

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

显然,这不是完整的生产解决方案(例如,解决外部暴露的经纪人的安全问题),并且我仍在加深对如何让内部生产者/消费者也与经纪人进行沟通的理解。

但是,到目前为止,这是我了解Kubernetes和Kafka的最佳方法。

Nadir Muzaffar answered 2020-06-16T23:33:14Z
14 votes

我想说的是,在尝试围绕无头服务是什么/它们的目的是什么之前,我已经阅读了3遍此问答。 (而且我从来没有完全理解无头服务,或者这是什么问答。)
读完四本(在进一步自我学习后重温),它最终获得了点击/我终于明白了。

因此,此答案的目的是重述Nadir的问题/问题/答案,就像向一年级学生解释它一样。 这样一来,其他对此一无所知的人将在初读时获得Nadir出色解决方案的意义。

有用的背景知识:

  • 存在一个类型为Service的服务:ExternalName。
    外部名称服务只是指向DNS地址。
    有2种口味外部名称服务:

    1. 没有群集IP:
      一个好的用例是允许测试集群和生产集群共享尽可能多的代码。可能。 (并且在某些情况下为方便起见)两种都包含豆荚测试和生产将指向相同的服务内部群集DNS地址名称,那将是可预测的可重用代码。 区别就是测试环境将提供一项服务,指向群集中存在的SQL服务。 的生产集群将使用外部名称服务,该服务将重定向/指向云提供商托管SQL的DNS地址解。
    2. 使用群集IP:
      这是外部名称服务的版本,是解决方案的关键。

  • 有状态集的身份包含3个部分:

    1. 序数(数字)
    2. 永久储存
    3. 持久且可预测的内部群集DNS名称(它是必须随Headless服务一起提供的要求而获得)

  • 关于Kube-Proxy,有3件重要的事情要记住:

    1. 确保所有内容都有唯一的IP。
    2. 它负责实现虚拟静态群集IP(虚拟静态群集IP被认为是虚拟的,因为它们仅存在于Kube-Proxy的iptables实现中的每个节点iptables中,或者存在于ip-vs下一代版本的内核哈希表中) 它也负责具有群集IP的普通Kubernetes服务发生的逻辑负载平衡效果。
    3. KubeProxy负责将NodePort上的流量映射到具有静态集群IP的相应Kubernetes服务。 <-这非常对于必须提供有状态服务的要求很重要在外部暴露时,总是应该在以下情况下涉及NodePorts它涉及到在外部公开服务。

  • 关于无头服务,要记住以下四点要点:

    1. 它创建一个可预测的DNS地址。
    2. 它不充当内部群集负载均衡器。 您直接与可预测的DNS地址标识的Pod对话。 (哪一个非常适合有状态工作负载)
    3. 它没有静态群集IP。
    4. 作为质量2和3的副作用,它在Kube-Proxy领域之外(负责引导传入的流量节点端口到服务。)我将对此重复几遍,以便问题陷入:NodePort通常无法将流量转发到Headless服务。 进入群集的外部流量通常不能转发给无头服务。 如何从外部看是不直观的公开无头服务。


现在我们更好地理解了问题,让我们回到问题:如何将无头服务(指向有状态集合的单个成员)从外部公开?

解决方案第1部分:
集群中的任何Pod都可以与有状态集的成员交谈。
由于有状态生成无头服务,因此可预测的内部集群DNS地址的形式为:
statefulsetname-#。associatedheadlessservice.namespace.svc.cluster.local:port
kafka-0.broker.kafka.svc.cluster.local:9092
kafka-1.broker.kafka.svc.cluster.local:9092
kafka-2.broker.kafka.svc.cluster.local:9092
broker.kafka.svc.cluster.local:9092,也可以用来指代可用的那个。

解决方案第2部分:
通过引入可以接受外部流量的第二项服务,然后将流量从该服务重定向到仅接受互联网流量的无头服务,可以允许外部流量与有状态集的成员进行对话。
对于有状态集中的每个Pod,将创建一个类型为ExternalName的服务,该服务具有由Kube-Proxy管理的虚拟静态ClusterIP地址。 这些外部名称服务中的每一个都将流量指向/重定向到解决方案1中标识的可预测的静态内部群集DNS地址,并且由于此外部名称服务具有通过Kube-Proxy管理的虚拟静态ClusterIP,因此可能存在从NodePorts到其的映射。

neokyle answered 2020-06-16T23:36:11Z
2 votes

将服务从无头的ClusterIP更改为NodePort,该节点端口会将请求转发到设置端口(在我的示例中为30092)上的任何节点到Kafkas上的端口9042。 您会随机击中其中一个豆荚,但是我想那很好。

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用于外部访问。 我还没有尝试过这个。

Andreas Wederbrand answered 2020-06-16T23:36:40Z
1 votes

从kubernetes kafka文档中:

主机端口的外部访问

一种替代方法是使用主机端口进行外部访问。 什么时候 使用这个,每个主机上只能运行一个kafka代理,这很好 反正这个主意。

为了切换到主机端口,kafka广告地址必须为 切换到运行该节点的节点的ExternalIP或ExternalDNS名称 经纪人。 在kafka / 10broker-config.yml中切换到

    - name: outside
      containerPort: 9094
      hostPort: 9094

并在kafka / 50kafka.yml中添加主机端口:

    - name: outside
      containerPort: 9094
      hostPort: 9094
herm answered 2020-06-16T23:37:26Z
translate from https://stackoverflow.com:/questions/46456239/how-to-expose-a-headless-service-for-a-statefulset-externally-in-kubernetes