web-dev-qa-db-fra.com

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

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

Ceci utilise un StatefulSet et un service sans en-tête pour la découverte du service dans le cluster.

L’objectif est d’exposer la personne Kafka Courtiers externes auxquels l’adresse interne est adressée 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?

47
Nadir Muzaffar

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 (c'est-à-dire Kafka Courtier)] 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.

À partir de Yolean/kubernetes-kafka , la seule chose qui manque, c’est d’exposer le service de façon externe, ce qui pose deux problèmes.

  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 en utilisant le service interne lors de la configuration Kafka) pour indiquer au producteur/consommateur de communiquer via le service externe.

Étiquettes de 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 au 10broker-config.ymlinit.sh propriété 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

Configurez Kafka avec des écouteurs internes/externes

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

Cela nécessite à nouveau de mettre à jour le init.sh et server.properties propriétés dans 10broker-config.yml avec ce qui suit:

Ajouter ce qui suit au server.properties pour mettre à jour les protocoles de sécurité (en 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 du système init.sh:

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

Puis configurez listeners et advertised.listeners IPs pour EXTERNAL_LISTENER et INTERNAL_LISTENER (également dans le init.sh propriété):

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.

18
Nadir Muzaffar

Nous avons résolu ce problème en 1.7 en modifiant le service sans tête en Type=NodePort et régler le 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 kafka pod. NodeA: 30000 ne se connectera pas, mais nodeB: 30000 se connectera à kafka = pod fonctionnant 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/# préservation-le-client-source-ip

Notez également que bien que 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 alors vos volumes sont liés au pod alors 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 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 locaux persistants, mais nous disposons actuellement d’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 noeud particulier.

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

23
Jason Kincl

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 mes connaissances), il a finalement cliqué/j'ai enfin compris.

Le but de cette réponse est donc de reformuler la question/le problème/de Nadir et de répondre comme si elle l’expliquait à un élève du primaire. Ainsi, ceux qui trébuchent auront l’importance de l’impressionnant Nadir. solution à la première lecture.

Connaissance pratique utile:

  • Il existe un service de type: NomExterne.
    ExternalName Service pointe simplement sur une adresse DNS.
    Il existe 2 versions du service ExternalName:

    1. Sans une adresse IP de cluster:
      Un bon exemple 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:
      Il s'agit de 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 l'implémentation des adresses IP du cluster virtuel virtuel (les adresses IP du cluster virtuel virtuel 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 domaine de Kube-Proxy (qui est chargé de diriger le trafic entrant sur Node Ports to Services.) I Je vais paraphraser cela quelques fois pour que le problème disparaisse: NodePorts ne peut généralement pas transférer le trafic vers des services sans tête. Le trafic externe entrant dans le cluster ne peut généralement pas être transféré vers des services sans tête. La manière d'exposer un service sans tête de manière externe n'est pas intuitive.


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é en 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 en-tête, 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 Part 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 ne pouvant accepter que le trafic Internet.

Pour chaque pod de la fonction Stateful, un service de type NomExterne avec une adresse IP de cluster statique virtuelle gérée par Kube-Proxy est créé. 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.

12
neokyle

Remplacez le service d'un ClusterIP sans tête par un NodePort qui transmettrait la demande à l'un des nœuds sur un port défini (30092 dans mon exemple) sur le port 9042 du Kafkas. Vous frapperiez une des gousses au hasard, mais je suppose que ça va.

20dns.yml devient (quelque chose comme ceci):

# 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

Avertissement: Vous aurez peut-être besoin de deux services. Un sans tête pour les noms DNS internes et un NodePort pour l'accès externe. Je n'ai pas essayé cela moi-même.

2
Andreas Wederbrand

À partir de la kubernetes kafka documentation :

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 kafka courtier peut s'exécuter sur chaque hôte, ce qui est néanmoins une bonne idée.).

Pour passer à hostport, l'adresse de publicité kafka doit être basculée sur le nom ExternalIP ou ExternalDNS du noeud exécutant le courtier.) Dans le commutateur kafka/10broker-config.yml

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
1
herm