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

时间:2017-09-27 19:57:15

标签: apache-kafka kubernetes

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

这会在群集中使用StatefulSet和headless service进行服务发现。

目标是在外部公开个人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公开外部服务?

6 个答案:

答案 0 :(得分:22)

我们已在1.7中通过将无头服务更改为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

例如,我们有两个节点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中也可用作测试版注释,更多信息可在此处找到: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下,用于将卷数据绑定到特定节点。 / p>

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

答案 1 :(得分:18)

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

  1. 仍应尽可能通过StatefulSet动态管理Pod。
  2. 为每个Pod(即Kafka Broker)为Producer / Consumer客户端创建一个外部服务,并避免负载平衡。
  3. 创建内部无头服务,以便每个经纪人可以相互通信。
  4. Yolean/kubernetes-kafka开始,唯一缺少的是外部公开服务以及这样做的两个挑战。

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

      要为每个广告单元生成标签,this issue确实非常有用。使用它作为指南,我们将以下行添加到10broker-config.yml init.sh属性中:

      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

      我发现this issue非常有用,可以帮助您了解如何配置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
      

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

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

      然后为listenersadvertised.listeners(也在EXTERNAL_LISTENER属性中)配置INTERNAL_LISTENERinit.sh 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的最佳方法。

答案 2 :(得分:11)

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

因此,此答案的目的是重申Nadir的问题/问题/答案,就像向一年级学生解释该问题一样。这样一来,那些偶然发现它的人就会在第一次阅读时获得Nadir出色解决方案的意义。

有用的背景知识:

  • 存在类型为:外部名称的服务。
    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中的内核哈希表中)一代版本的Kube-Proxy),它还负责具有群集IP的普通Kubernetes服务发生的逻辑负载平衡效果。
    3. KubeProxy负责将NodePorts上的流量映射到具有静态集群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到其的映射。

答案 3 :(得分:2)

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

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。我没有试过这个我自己。

答案 4 :(得分:1)

来自kubernetes kafka documentation

  

通过主机端口进行外部访问

     

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

     

要切换到主机端口,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中添加主机端口:

    - name: outside
      containerPort: 9094
      hostPort: 9094

答案 5 :(得分:0)

我通过为每个代理创建单独的statefulset和为每个代理创建单独的NodePort类型的服务来解决此问题。内部通信可以在每个单独的服务名称上进行。外部通信可以在NodePort地址上进行。