apache kafka Comment exposer un service sans accès pour un StatefulSet en externe dans Kubernetes




kubernetes stop statefulset (4)

Les solutions proposées jusqu'à présent n'étaient pas assez satisfaisantes pour moi, alors je vais poster une réponse personnelle. Mes objectifs:

  1. Les pods doivent toujours être gérés de manière dynamique via un StatefulSet autant que possible.
  2. Créez un service externe par pod (par exemple, Kafka Broker) pour les clients producteurs / consommateurs et évitez l'équilibrage de la charge.
  3. Créez un service interne sans tête de sorte que chaque courtier puisse communiquer entre eux.

À commencer par kubernetes-kafka , la seule chose qui manque, c’est d’exposer le service à l’extérieur, ce qui pose deux défis.

  1. Génération d'étiquettes uniques par pod Broker afin que nous puissions créer un service externe pour chacun des pods Broker.
  2. Dire aux courtiers de communiquer entre eux à l'aide du service interne lors de la configuration de Kafka afin d'indiquer au producteur / consommateur de communiquer via le service externe.

Étiquettes par pod et services externes:

Pour générer des étiquettes par pod, ce problème était vraiment utile. En l’utilisant comme guide, nous ajoutons la ligne suivante à la propriété init.sh 10broker-config.yml avec:

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

Nous conservons le service sans tête existant, mais nous générons également un service externe par pod à l'aide de l'étiquette (je les ai ajoutés à 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

Configurer Kafka avec des écouteurs internes / externes

J'ai trouvé ce problème extrêmement utile pour essayer de comprendre comment configurer Kafka.

Cela nécessite à nouveau la mise à jour des propriétés init.sh et server.properties dans 10broker-config.yml avec les éléments suivants:

Ajoutez les éléments suivants au server.properties pour mettre à jour les protocoles de sécurité (utilisant actuellement PLAINTEXT ):

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

Déterminez dynamiquement l'adresse IP externe et le port externe de chaque pod dans le init.sh :

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

Configurez ensuite advertised.listeners adresses IP des listeners et advertised.listeners pour EXTERNAL_LISTENER et INTERNAL_LISTENER (également dans la propriété 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

De toute évidence, il ne s’agit pas d’une solution complète pour la production (par exemple, la sécurité des courtiers exposés à l’extérieur) et j’affine encore ma compréhension de la façon de laisser également les producteurs / consommateurs internes communiquer également avec les courtiers.

Cependant, jusqu’à présent, c’est la meilleure approche pour ma compréhension de Kubernetes et de Kafka.

Utilisation de kubernetes-kafka comme point de départ avec minikube.

Cela utilise un StatefulSet et un service sans en - tête pour la découverte de service au sein du cluster.

L’objectif est d’exposer les courtiers Kafka individuels à l’extérieur, qui sont traités en interne comme suit:

kafka-0.broker.kafka.svc.cluster.local:9092
kafka-1.broker.kafka.svc.cluster.local:9092 
kafka-2.broker.kafka.svc.cluster.local:9092

La contrainte est que ce service externe puisse s'adresser spécifiquement aux courtiers.

Quelle est la bonne (ou une possible) façon de s'y prendre? Est-il possible d'exposer un service externe par kafka-x.broker.kafka.svc.cluster.local:9092 ?


Answer #1

De la documentation kubka kubernetes :

Accès extérieur avec hostport

Une alternative consiste à utiliser le hostport pour l'accès extérieur. Lors de l'utilisation de ce dernier, un seul courtier kafka peut s'exécuter sur chaque hôte, ce qui est une bonne idée.

Afin de passer à hostport, l'adresse de publication kafka doit être basculée sur le nom ExternalIP ou ExternalDNS du nœud exécutant le courtier. dans kafka / 10broker-config.yml, passez à

OUTSIDE_HOST=$(kubectl get node "$NODE_NAME" -o jsonpath='{.status.addresses[?(@.type=="ExternalIP")].address}')
OUTSIDE_PORT=${OutsidePort}

et dans kafka / 50kafka.yml, ajoutez le hostport:

    - name: outside
      containerPort: 9094
      hostPort: 9094

Answer #2

J'aimerais dire que j'avais déjà lu cette question et cette réponse trois fois auparavant pour essayer de comprendre ce qu'étaient les services Headless / quel était leur but. (Et je n'ai jamais bien compris Headless Services, ni le sujet de ce Q & A.)
Et à la 4ème lecture (revisitant après avoir approfondi ma formation), il a finalement cliqué / j'ai enfin compris.

Donc, le but de cette réponse est de reformuler la question / le problème / de Nadir et de répondre comme s'il l'expliquait à un élève du primaire. Pour que les autres qui tombent sur cela obtiennent l’importance de la formidable solution de Nadir dès la première lecture.

Connaissances de fond utiles:

  • Il existe un service de type: NomExterne.
    Le service ExternalName pointe simplement vers une adresse DNS.
    Il existe 2 types de service ExternalName:

    1. Sans une adresse IP de cluster:
      Un bon cas d'utilisation serait de permettre à un cluster de test et à un cluster de production de partager autant de code que possible. (et, dans certains cas, par simple conviencence) Les pods, tant en test qu'en production, pointeraient vers le même service Nom de l'adresse DNS du cluster interne, ce serait le code réutilisable prévisible. La différence serait que l'environnement de test aurait un service qui pointe vers un service SQL existant dans le cluster. Le cluster de production utiliserait un service ExternalName, qui redirigerait / pointerait vers l'adresse DNS d'une solution SQL gérée par les fournisseurs de services cloud.
    2. Avec une adresse IP de cluster:
      Ceci est la version d'un service ExternalName qui est la clé de la solution.

  • Un Stateful Set se compose de 3 parties:

    1. Un ordinale (nombre)
    2. Stockage persistant
    3. Un nom DNS de cluster interne persistant et prévisible (il tire son origine de la condition selon laquelle il doit être livré avec un service Headless)

  • Il y a 3 choses importantes à retenir à propos de Kube-Proxy:

    1. Il s'assure que tout a une adresse IP unique.
    2. Il est responsable de la mise en œuvre des adresses IP de cluster statiques virtuelles (les adresses IP de cluster statiques virtuelles sont considérées comme virtuelles car elles n'existent que dans tous les nœuds. Iptables dans l'implémentation iptables de Kube-Proxy, ou dans une table de hachage du noyau dans la version ip-vs next-gen de Kube-Proxy) et est également responsable de l’effet d’équilibrage de la charge logique qui se produit avec les services Kubernetes normaux ayant une adresse IP de cluster.
    3. KubeProxy est chargé de mapper le trafic provenant de NodePorts vers le service Kubernetes correspondant avec une adresse IP de cluster statique. <- Ceci est très important pour exiger que les services avec état soient exposés de manière externe. NodePorts est toujours supposé être impliqué lorsqu'il s'agit d'exposer des services de manière externe.

  • Il y a 4 choses importantes à retenir à propos d'un service sans tête:

    1. Cela crée une adresse DNS prévisible.
    2. Il n'agit pas comme un équilibreur de charge interne de cluster. Vous parlez directement au pod identifié par l'adresse DNS prévisible. (ce qui est très souhaitable pour les charges de travail avec état)
    3. Il n'a pas d'adresse IP de cluster statique.
    4. En tant qu'effet secondaire des qualités 2 et 3, il se situe en dehors du royaume de Kube-Proxy (qui est chargé de diriger le trafic entrant sur les ports de nœud vers les services). Je vais le paraphraser quelques fois pour que le problème disparaisse: NodePorts peut ne transférez généralement pas le trafic vers des services sans tête. Le trafic externe entrant dans le cluster ne peut généralement pas être transféré aux services sans tête. Ce n'est pas intuitif comment exposer de manière externe un service Headless.


Maintenant que nous comprenons mieux le problème, revenons à la question: comment un service sans tête (qui pointe vers un membre individuel d'un ensemble avec état) peut-il être exposé de manière externe?

Solution Partie 1:
Tous les pods du cluster peuvent parler aux membres de statefulset.
Parce que le stateful génère un service sans interface utilisateur, avec une adresse DNS de cluster interne prévisible de la forme:
statefulsetname - #. associateheadlessservice.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, peut également être utilisé pour désigner celui qui est disponible.

Solution Partie 2:
Vous autorisez le trafic externe à parler aux membres de l'ensemble avec état, en introduisant un deuxième service pouvant accepter le trafic externe, puis en redirigeant le trafic de ce service vers le service sans en-tête qui ne peut accepter que le trafic Internet.
Définissez un service de type NomExterne avec une adresse IP de cluster statique virtuelle gérée par Kube-Proxy pour chaque pod dans l'état. Chacun de ces services ExternalName pointe vers / redirige le trafic vers une adresse DNS de cluster interne statique prévisible identifiée dans la solution 1 et, du fait que ce service ExternalName possède un cluster IP statique virtuel géré via Kube-Proxy, il peut y avoir un mappage de NodePorts à celui-ci.


Answer #3

Nous avons résolu ce problème en 1.7 en Type=NodePort le service sans en-tête par Type=NodePort et en définissant externalTrafficPolicy=Local . Cela contourne l'équilibrage de la charge interne d'un service et le trafic destiné à un nœud spécifique sur ce port de nœud ne fonctionnera que si un pod Kafka se trouve sur ce nœud.

apiVersion: v1
kind: Service
metadata:
  name: broker
spec:
  externalTrafficPolicy: Local
  ports:
  - nodePort: 30000
    port: 30000
    protocol: TCP
    targetPort: 9092
  selector:
    app: broker
  type: NodePort

Par exemple, nous avons deux nœuds nodeA et nodeB, nodeB exécute un pod kafka. nodeA: 30000 ne se connecte pas, mais nodeB: 30000 se connecte au pod kafka exécuté sur nodeB.

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

Notez que ceci était également disponible dans les annotations bêta de 1.5 et 1.6. Vous trouverez plus d'informations ici sur la disponibilité des fonctionnalités: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip

Notez également que, même si cela lie un pod kafka à une identité réseau externe spécifique, cela ne garantit pas que votre volume de stockage sera lié à cette identité réseau. Si vous utilisez VolumeClaimTemplates dans un StatefulSet, vos volumes sont liés au pod, tandis que kafka s'attend à ce que le volume soit lié à l'identité du réseau.

Par exemple, si le pod kafka-0 redémarre et que kafka-0 s'affiche sur nodeC au lieu de nodeA, le pvc de kafka-0 (si vous utilisez VolumeClaimTemplates) contient des données qu'il est pour nodeA et le courtier qui s'exécute sur kafka-0 commence à rejeter les demandes en pensant qu'il s'agit de nodeA et non de nodeC.

Pour résoudre ce problème, nous attendons avec impatience les volumes persistants locaux, mais nous avons actuellement un seul PVC pour notre kafka StatefulSet et les données sont stockées sous $NODENAME sur ce PVC pour lier les données de volume à un nœud particulier.

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







kubernetes