web-dev-qa-db-ja.com

KubernetesでStatefulSetのヘッドレスサービスを外部に公開する方法

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

制約は、この外部サービスがブローカーに具体的に対処できることです。

これについて正しい(または可能な1つの)方法は何ですか? kafka-x.broker.kafka.svc.cluster.local:9092ごとに外部サービスを公開することは可能ですか?

47
Nadir Muzaffar

これまでのソリューションは、私自身にとって十分に満足のいくものではなかったので、自分の答えを投稿します。私の目標:

  1. ポッドは、可能な限りStatefulSetを使用して動的に管理する必要があります。
  2. プロデューサー/コンシューマークライアント用のポッド(つまり、Kafkaブローカー)ごとに外部サービスを作成し、負荷分散を回避します。
  3. 各ブローカーが互いに通信できるように、内部ヘッドレスサービスを作成します。

Yolean/kubernetes-kafka で始まる、欠けている唯一のことは、サービスを外部に公開することと、そうすることの2つの課題です。

  1. ブローカーポッドごとに一意のラベルを生成して、各ブローカーポッドの外部サービスを作成できるようにします。
  2. Kafkaを構成しながら内部サービスを使用して相互に通信するようにブローカーに指示し、プロデューサー/コンシューマーに外部サービスを介して通信するように指示します。

ポッドごとのラベルと外部サービス:

ポッドごとにラベルを生成するには、 この問題 が非常に役立ちました。これをガイドとして使用して、次の行を 10broker-config.ymlinit.shプロパティに追加します。

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

既存のヘッドレスサービスを保持しますが、ラベルを使用してポッドごとに外部サービスも生成します( 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を構成する

この問題 は、Kafkaの設定方法を理解しようとする際に非常に役立ちました。

この場合も、 10broker-config.ymlinit.shおよびserver.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##*-}))

次に、advertised.listenersおよびEXTERNAL_LISTENERlistenersおよびINTERNAL_LISTENER IPを構成します(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

明らかに、これは本番用の完全なソリューションではありません(たとえば、外部に公開されたブローカーのセキュリティに対処するため)。また、社内の生産者/消費者がブローカーとも通信できるようにする方法についての理解をさらに高めています。

ただし、これまでのところ、これはKubernetesとKafkaを理解するための最良のアプローチです。

18
Nadir Muzaffar

1.7では、ヘッドレスサービスをType=NodePortに変更し、externalTrafficPolicy=Localを設定することでこれを解決しました。これにより、サービスの内部負荷分散がバイパスされ、そのノードポート上の特定のノード宛てのトラフィックは、そのノードにKafkaポッドがある場合にのみ機能します。

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

たとえば、2つのノードnodeAとnodeBがあり、nodeBはkafkaポッドを実行しています。 nodeA:30000は接続しませんが、nodeB:30000はnodeBで実行されているkafkaポッドに接続します。

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ポッドを特定の外部ネットワークIDに結び付けますが、ストレージボリュームがそのネットワークIDに結び付けられることを保証するものではないことに注意してください。 StatefulSetでVolumeClaimTemplatesを使用している場合、ボリュームはポッドに関連付けられますが、kafkaはボリュームがネットワークIDに関連付けられることを期待します。

たとえば、kafka-0ポッドが再起動し、kafka-0がnodeAではなくnodeCで起動した場合、kafka-0のpvc(VolumeClaimTemplatesを使用している場合)にはnodeA用のデータがあり、kafka-0で実行されているブローカーはリクエストを拒否し始めますそれはnodeCではなくnodeAであること。

これを修正するために、Local Persistent Volumesを楽しみにしていますが、現時点ではkafka St​​atefulSetの単一のPVCがあり、そのPVCの$NODENAMEの下にデータを保存して、ボリュームデータを特定のノード。

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

23
Jason Kincl

私は、この質問と回答を3回読んだ後、ヘッドレスサービスとは何か、それらのポイントは何であるかについて頭をかき回そうとしたことを言いたいです。 (そして、私はヘッドレスサービス、またはこのQ&Aが何であったかを完全には理解していませんでした。)
そして4回目の読解(さらに自分自身を教育した後、もう一度読む)で、ついにクリックしました/ついに理解しました。

したがって、この答えの目的は、Nadirの質問/問題/およびそれを小学生に説明するかのように答えることです。これにつまずく他の人がNadirの素晴らしいソリューションの重要性最初の読み取り。

有用な背景知識:

  • タイプがExternalNameのサービスが存在します。
    ExternalNameサービスは、単にDNSアドレスを指します。
    ExternalNameサービスには2つのフレーバーがあります。

    1. クラスターIPなし:
      テストクラスターと運用クラスターができるだけ多くのコードを共有できるようにするのが良いユースケースです。 (および場合によっては簡単に)、テストと実稼働の両方のポッドは、同じサービスの内部クラスターDNSアドレス名を指し、それが予測可能な再利用可能なコードになります。違いは、テスト環境に、クラスター内に存在するSQLサービスを指すサービスがあることです。運用クラスターは、クラウドプロバイダーマネージドSQLソリューションのDNSアドレスにリダイレクト/ポイントするExternalNameサービスを使用します。
    2. クラスターIPの場合:
      これは、ソリューションの鍵となるExternalName Serviceのバージョンです。

  • ステートフルセットには、そのアイデンティティに対して3つの部分があります。

    1. 序数(数値)
    2. 永続的ストレージ
    3. 永続的で予測可能な内部クラスターDNS名(これは、ヘッドレスサービスと共に出荷する必要があるという要件から取得します)

  • Kube-Proxyについて覚えておくべき3つの重要なことがあります。

    1. すべてが一意のIPを持つようにします。
    2. 仮想静的クラスターIPの実装を担当します(仮想静的クラスターIPは、Kube-Proxyのiptables実装のすべてのノードiptables、またはip-vs next-genバージョンのカーネルハッシュテーブルにのみ存在するため、仮想と見なされます。また、クラスタIPを持つ通常のKubernetesサービスで発生する論理負荷分散効果にも責任を負います。
    3. KubeProxyは、静的なクラスターIPを使用して、NodePortに着信するトラフィックを対応するKubernetesサービスにマッピングします。 <-これは、ステートフルサービスを外部に公開する必要があるという要件にとって非常に重要です。外部に公開するサービスに関しては、NodePortが常に関与することになっています。

  • ヘッドレスサービスについて覚えておくべき4つの重要なことがあります。

    1. 予測可能なDNSアドレスを作成します。
    2. 内部クラスターロードバランサーとして機能しません。予測可能なDNSアドレスで識別されるポッドと直接話します。 (ステートフルワークロードに非常に望ましい)
    3. 静的クラスターIPはありません。
    4. 品質2および3の副作用として、Kube-Proxyのレルム(Node Portsに着信するトラフィックをサービスに転送する役割を果たします)の外部にあります。これを言い換えますNodePortsは通常、ヘッドレスサービスにトラフィックを転送できません。クラスターに入る外部トラフィックは、通常、ヘッドレスサービスに転送できません。ヘッドレスサービスを外部に公開する方法は直感的ではありません。


問題の理解が深まったので、次の質問に戻りましょう:ヘッドレスサービス(ステートフルセットの個々のメンバーを指す)を外部に公開するにはどうすればよいですか?

ソリューションパート1:
クラスター内の任意のポッドがステートフルセットのメンバーと通信できます。

ステートフルは、次の形式の予測可能な内部クラスター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:
外部トラフィックを受け入れることができる2つ目のサービスを導入し、そのサービスからのトラフィックをインターネットトラフィックのみを受け入れるヘッドレスサービスにリダイレクトすることにより、外部トラフィックがステートフルセットのメンバーと通信できるようにします。

ステートフルセットの各ポッドに対して、Kube-Proxyによって管理される仮想静的ClusterIPアドレスを持つタイプExternalNameのサービスが作成されます。これらのExternalName Servicesのそれぞれは、ソリューション1で識別された予測可能な静的内部クラスターDNSアドレスをポイント/リダイレクトします。このExternalNameサービスにはKube-Proxyを介して管理される仮想静的ClusterIPがあるため、NodePortsからそれにマッピングすることができます。

12
neokyle

サービスをヘッドレスClusterIPからNodePortに変更します。NodePortは、設定ポート(私の例では30092)のnodesのいずれかに要求を転送し、Kafkasのポート9042に転送します。ポッドの1つをランダムにヒットしますが、それで問題ないと思います。

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

免責事項:2つのサービスが必要になる場合があります。 1つは内部DNS名用のヘッドレス、もう1つは外部アクセス用のNodePortです。私はこれを自分で試したことはありません。

2

kubernetes kafka documentation から:

ホストポートを使用した外部アクセス

別の方法は、外部アクセスにホストポートを使用することです。これを使用する場合、各ホストで実行できるkafkaブローカーは1つだけです。

ホストポートに切り替えるには、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
1
herm