首页 文章

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

提问于
浏览
36

使用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 公开外部服务?

5 回答

  • 4

    我们已经在1.7中通过将无头服务更改为 Type=NodePort 并设置 externalTrafficPolicy=Local 来解决了这个问题 . 这会绕过服务的内部负载 balancer ,并且只有当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中也可用作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/kubernetes/features/issues/121 https://kubernetes.io/docs/concepts/storage/volumes/#local

  • 2

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

    • Pod仍应尽可能通过StatefulSet动态管理 .

    • 为 生产环境 者/消费者客户端为每个Pod(即Kafka Broker)创建外部服务,并避免负载 balancer .

    • 创建内部无头服务,以便每个代理可以相互通信 .

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

    • 为每个Broker pod生成唯一标签,以便我们可以为每个Broker pod创建外部服务 .

    • 告诉经纪人使用内部服务相互通信,同时配置Kafka告诉 生产环境 者/消费者通过外部服务进行通信 .

    Per pod labels and external services:

    要为每个pod生成标签,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
    

    Configure Kafka with internal/external listeners

    我发现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##*-}))
    

    然后为 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的最佳方法 .

  • 21

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

    So the purpose of this answer is to restate Nadir's question/problem/and answer as if explaining it to a grade schooler. So that others who stumble upon this will get the significance of Nadir's awesome solution on the first read.

    Useful Background Knowledge:

    • 存在类型为:ExternalName的服务 .
      ExternalName服务只是指向DNS地址 .
      有2种ExternalName服务风味:

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

    • 使用群集IP:
      这是解决方案关键的ExternalName服务的版本 .

    • 有状态集有三个部分:

    • 一个序数

    • 持久存储

    • 持久且可预测的内部群集DNS名称(它必须与无头服务一起提供此要求)

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

    • 确保所有内容都具有唯一的IP .

    • It 's responsible for implementing the Virtual Static Cluster IP' s(虚拟静态群集IP 's are considered virtual because they only exist in every nodes iptables in the iptables implementation of Kube-Proxy, or in a Kernel Hash Table in the ip-vs next-gen version of Kube-Proxy) and it'也负责具有群集IP的普通Kubernetes服务发生的逻辑负载 balancer 效果 .

    • KubeProxy负责将NodePorts上的流量映射到具有静态群集IP的相应Kubernetes服务 . < - 这对于有状态服务应该在外部暴露的要求非常重要,NodePorts总是应该涉及外部暴露服务 .

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

    • 它创建一个可预测的DNS地址 .

    • 它不充当内部群集Load Balancer . 您可以直接与可预测的DNS地址标识的pod进行通信 . (这对于有状态工作负载非常有用)

    • 它没有静态群集IP .

    • 作为质量2和3的副作用,它位于Kube-Proxy领域之外(它负责将节点端口上的流量引导到服务 . ) I'll paraphrase this a few times so the problem sinks in: NodePorts can't usually forward traffic to Headless Services. External traffic entering the cluster can't usually be forwarded to Headless Services. It's not intuitive how to externally expose a Headless Service.

    Now that we understand the problem better, lets go back to the question: How can a Headless Service (which points to an individual member of a stateful set) be externally exposed?

    Solution Part 1: Any pod in the cluster can talk to the members of the statefulset.
    因为有状态生成无头服务,具有可预测的内部群集DNS地址的形式:
    . statefulsetname - #associatedheadlessservice.namespace.svc.cluster.local:端口
    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,也可用于指代哪一个可用 .

    Solution Part 2: You allow external traffic to talk to members of the stateful set, by introducing a 2nd service that can accept external traffic, and then redirecting traffic from that service to the headless service that can only accept internet traffic.
    对于有状态集中的每个窗格,将创建一个类型为ExternalName的服务,其中包含由Kube-Proxy管理的虚拟静态ClusterIP地址 . 这些ExternalName服务中的每一个都指向/重定向流量到解决方案1中标识的可预测的静态内部群集DNS地址,并且因为此ExternalName服务具有通过Kube-Proxy管理的虚拟静态ClusterIP,所以可以存在从NodePorts到其的映射 .

  • 18

    将服务从无头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 . 我没有试过这个我自己 .

  • 1

    来自kubernetes kafka documentation

    使用hostport进行外部访问另一种方法是使用hostport进行外部访问 . 使用它时,每个主机上只能运行一个kafka代理,这无论如何都是个好主意 . 为了切换到hostport,需要将kafka广告地址切换到运行代理的节点的ExternalIP或ExternalDNS名称 . 在kafka / 10broker-config.yml中切换到OUTSIDE_HOST = $(kubectl get节点“$ NODE_NAME”-o jsonpath ='{ . status.addresses [?(@ . type ==“ExternalIP”)] . address}')
    OUTSIDE_PORT = $ {} OutsidePort
    并在kafka / 50kafka.yml中添加hostport: - name:outside
    containerPort:9094
    hostPort:9094

相关问题