如何在Kubernetes外部公开StatefulSet的无头服务

Nad*_*far 49 apache-kafka kubernetes

使用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
Run Code Online (Sandbox Code Playgroud)

限制是该外部服务能够专门解决代理.

什么是正确的(或一种可能的)方式?是否可以公开外部服务kafka-x.broker.kafka.svc.cluster.local:9092

Jas*_*ncl 24

我们已经通过改变无头服务Type=NodePort和设置来解决这个问题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
Run Code Online (Sandbox Code Playgroud)

例如,我们有两个节点nodeA和nodeB,nodeB正在运行一个kafka pod.nodeA:30000将无法连接,但nodeB:30000将连接到nodeB上运行的kafka pod.

https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typenodeport

请注意,这也可以在1.5和1.6中作为beta注释使用,更多内容可以在功能可用性中找到:https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#保存最客户端源IP

另请注意,虽然这会将kafka pod与特定的外部网络标识绑定,但并不能保证您的存储卷与该网络标识相关联.如果您在StatefulSet中使用VolumeClaimTemplates,那么您的卷将绑定到pod,而kafka期望该卷与网络标识绑定.

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

为了解决这个问题,我们期待Local Persistent Volumes,但是现在我们的kafka StatefulSet只有一个PVC,数据存储在$NODENAME该PVC上,以便将卷数据绑定到特定节点.

https://github.com/kubernetes/features/issues/121 https://kubernetes.io/docs/concepts/storage/volumes/#local


Nad*_*far 20

到目前为止,解决方案对我自己来说还不够令人满意,所以我将发布一个自己的答案.我的目标:

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

Yolean/kubernetes-kafka开始,唯一缺少的是外部公开服务以及这样做的两个挑战.

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

每个pod标签和外部服务:

要为每个pod生成标签,此问题非常有用.使用它作为指导,我们将以下行 添加到10broker-config.ymlinit.sh属性中:

kubectl label pods ${HOSTNAME} kafka-set-component=${HOSTNAME}
Run Code Online (Sandbox Code Playgroud)

我们保留现有的无头服务,但我们还使用标签为每个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
Run Code Online (Sandbox Code Playgroud)

使用内部/外部侦听器配置Kafka

我发现这个问题非常有用,试图了解如何配置Kafka.

这又需要使用以下内容更新10broker-config.yml中init.shserver.properties属性:

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

listener.security.protocol.map=INTERNAL_PLAINTEXT:PLAINTEXT,EXTERNAL_PLAINTEXT:PLAINTEXT
inter.broker.listener.name=INTERNAL_PLAINTEXT
Run Code Online (Sandbox Code Playgroud)

动态确定以下内容中每个Pod的外部IP和外部端口init.sh:

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

然后配置listenersadvertised.listenersIP EXTERNAL_LISTENERINTERNAL_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
Run Code Online (Sandbox Code Playgroud)

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

然而,到目前为止,这是我理解Kubernetes和Kafka的最佳方法.


neo*_*yle 14

我想说我之前已经读过这个问题和答案3次,试图围绕无头服务是什么/它们的意义.(而且我从来没有完全理解无头服务,或者这个Q&A的内容.)
在第4次阅读(在进一步教育自己之后重新审视它)它终于点击了/我终于理解了.

因此,这个答案的目的是重申Nadir的问题/问题/答案,好像向小学生解释一样.因此,偶然发现这一点的其他人将会在第一次阅读时获得纳迪尔非常棒的解决方案的重要性.

有用的背景知识:

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

    1. 没有群集IP:
      一个好的用例是允许测试群集和生产群集共享尽可能多的代码.(并且在某些情况下为了简单方便)测试和生产中的Pod将指向相同的服务内部群集DNS地址名称,这将是可预测的可重用代码.不同之处在于测试环境将具有指向群集内存在的SQL服务的服务.生产群集将使用ExternalName服务,该服务将重定向/指向云提供程序托管SQL解决方案的DNS地址.
    2. 使用群集IP:
      这是解决方案关键的ExternalName服务的版本.

  • 有状态集有三个部分:

    1. 序数(数量)
    2. 持久存储
    3. 一个持久且可预测的内部群集DNS名称(它必须与无头服务一起提供此要求)

  • Kube-Proxy有三个重要的事项要记住:

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

  • 关于无头服务有四个重要的事项需要记住:

    1. 它创建了一个可预测的DNS地址.
    2. 它不作为内部群集Load Balancer.您可以直接与可预测DNS地址标识的pod进行通信.(这对于有状态工作负载非常有用)
    3. 它没有静态群集IP.
    4. 作为质量2和3的副作用,它位于Kube-Proxy领域之外(它负责将节点端口上的流量引导到服务.)我会解释这几次,因此问题陷入困境:NodePorts可以通常将流量转发到无头服务.进入群集的外部流量通常无法转发到无头服务.如何外部暴露无头服务并不直观.


现在我们更好地理解了这个问题,让我们回到一个问题:无头服务(指向有状态集的个体成员)如何在外部暴露?

解决方案第1部分:
群集中的任何pod都可以与statefulset的成员通信.

因为有状态生成无头服务,具有可预测的内部群集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部分:
您允许外部流量与有状态集的成员通信,方法是引入可接受外部流量的第二个服务,然后将流量从该服务重定向到只能接受互联网流量的无头服务.

对于有状态集中的每个窗格,将创建一个类型为ExternalName的服务,其中包含由Kube-Proxy管理的虚拟静态ClusterIP地址.这些ExternalName服务中的每一个都指向/重定向流量到解决方案1中标识的可预测的静态内部群集DNS地址,并且因为此ExternalName服务具有通过Kube-Proxy管理的虚拟静态ClusterIP,所以可以存在从NodePorts到其的映射.