コンテンツにスキップ

ネットワーク@Kubernetes

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. Nodeネットワーク

Nodeネットワークとは

同じサブネットマスク内にあるNodeのNIC間を接続するネットワーク。

Nodeネットワークの作成は、Kubernetesの実行環境のネットワークが担う。

kubernetes_node-network


02. Serviceネットワーク

Serviceネットワークとは

Podのアウトバウンド通信に割り当てられたホスト名を認識し、そのホスト名を持つServiceまでリクエストを送信する。

Serviceネットワークの作成は、Kubernetesが担う。

kubernetes_service-network


03. Clusterネットワーク

Clusterネットワークとは

同じClusterネットワーク内にあるPodの仮想NIC (veth) 間を接続するネットワーク。

Clusterネットワークの作成は、CNIアドオンが担う。

kubernetes_cluster-network


04. Podネットワーク

Podネットワークとは

Pod内のネットワークのみを経由して、他のコンテナにリクエストを送信する。

Podごとにネットワークインターフェースが付与され、またIPアドレスが割り当てられる。


名前空間

▼ 名前空間の種類

Podのネットワークは複数の種類の名前空間から構成される。

ネットワークの範囲に応じて、コンテナが同じNode内のいずれのコンポーネントのプロセスと通信できるようになるのかが決まる。

注意点として、Dockerとは名前空間の種類が異なる。

kubernetes_pod-network_namespace

▼ IPC名前空間

プロセスは、同じIPC名前空間に属する他のプロセスと通信できる。

Kubernetesのセキュリティ上の理由から、デフォルトではPod内のコンテナはホスト (Node) とは異なるIPC名前空間を使用し、ネットワークを分離している。

そのため、コンテナのプロセスはNodeのプロセスと通信できないようになっている。

Network名前空間


PID名前空間


Hostname名前空間


cgroup名前空間


05. ネットワークレイヤー

Ingressコントローラー由来のL7ロードバランサーの場合

Ingressコントローラーの場合、L7ロードバランサーをプロビジョニングする。

IngressコントローラーによるL7ロードバランサーは、受信した通信をServiceにルーティングする。

ServiceはL4ロードバランサーとして、インバウンド通信をPodにルーティングする。

kubernetes_network_l4-l7


LoadBalancer Service由来のL4ロードバランサーの場合

LoadBalancer Serviceの場合、L4ロードバランサーをプロビジョニングする。


06. Pod間通信

Pod間通信の経路

Pod内のコンテナから宛先のPodにリクエストを送信する。

この時、PodをスケジューリングさせているNodeが同じ/異なるかのいずれの場合で、経由するネットワークが異なる。

条件 経由するネットワーク
Nodeが異なる場合 Nodeネットワーク + Clusterネットワーク + Serviceネットワーク
Nodeが同じ場合 Clusterネットワーク + Serviceネットワーク


通信方法

同じPod内のコンテナ間は『localhost:<ポート番号>』で通信できる。

# Pod内のコンテナに接続する。
$ kubectl exec -it <Pod名> -c <コンテナ名> -- bash

[root@<Pod名>:~] $ curl -X GET http://127.0.0.1:<ポート番号>


PodのIPアドレスを指定する場合

▼ 仕組み

Pod内のコンテナで、宛先のPodのIPアドレスやポート番号を直接的に指定する。

ただし、PodのIPアドレスは動的に変化するため、現実的な方法ではない。

*例*

foo-podから、IPアドレス (11.0.0.1) とポート番号 (80) を指定して、bar-podにパケットを送信してみる。

$ kubectl exec \
    -it foo-pod \
    -n foo-namespace \
    -- bash -c "traceroute 11.0.0.1 -p 80"

traceroute to 11.0.0.1 (11.0.0.1), 30 hops max, 46 byte packets
 1  *-*-*-*.prometheus-kube-proxy.kube-system.svc.cluster.local (*.*.*.*)    0.007 ms  0.022 ms  0.005 ms
 2  *-*-*-*.prometheus-node-exporter.prometheus.svc.cluster.local (*.*.*.*)  1.860 ms  1.846 ms  1.803 ms
 3  11.0.0.1.bar-service.bar-namespace.svc.cluster.local (11.0.0.1)          1.848 ms  1.805 ms  1.834 ms # 宛先のPod
$ kubectl exec \
    -it foo-pod \
    -n foo-namespace \
    -- bash -c "traceroute -n 11.0.0.1 -p 80"

traceroute to 11.0.0.1 (11.0.0.1), 30 hops max, 46 byte packets
 1  *.*.*.*   0.007 ms  0.022 ms  0.005 ms
 2  *.*.*.*   1.860 ms  1.846 ms  1.803 ms
 3  11.0.0.1  1.848 ms  1.805 ms  1.834 ms # 宛先のPod


ServiceのIPアドレスを指定する場合

▼ 仕組み

kubeletは、Pod内のコンテナにServiceの宛先情報 (プロトコル、IPアドレス、ポート番号) を出力する。

Pod内のコンテナは、これを使用し、Serviceを介してPodにリクエストを送信する。

*実装例*

foo-serviceというServiceを作成した場合の環境変数を示す。

$ kubectl exec -it foo-pod -- printenv | sort -n

FOO_APP_SERVICE_PORT=tcp://10.110.235.51:80
FOO_APP_SERVICE_PORT_80_TCP=tcp://10.110.235.51:80
FOO_APP_SERVICE_PORT_80_TCP_ADDR=10.110.235.51
FOO_APP_SERVICE_PORT_80_TCP_PORT=80
FOO_APP_SERVICE_PORT_80_TCP_PROTO=tcp
FOO_APP_SERVICE_SERVICE_HOST=10.110.235.51
FOO_APP_SERVICE_SERVICE_PORT=80
FOO_APP_SERVICE_SERVICE_PORT_HTTP_ACCOUNT=80


Serviceの完全修飾ドメイン名を指定する場合

▼ 仕組み

Kubernetesに採用できる権威DNSサーバー (kube-dns、CoreDNS、HashiCorp Consul、など) は、ServiceのNSレコードを管理し、Serviceの完全修飾ドメイン名で名前解決できるようになる。

Podのスケジューリング時に、kubeletはPod内のコンテナの/etc/resolv.confファイルに権威DNSサーバーのIPアドレスを設定する。

Pod内のコンテナは、自身の/etc/resolv.confファイルで権威DNSサーバーのIPアドレスを確認し、DNSサーバーにPodのIPアドレスを正引きする。

レスポンスに含まれる宛先のPodのIPアドレスを使用して、Podにリクエストを送信する。

# Pod内のコンテナに接続する。
$ kubectl exec -it <Pod名> -c <コンテナ名> -- bash

# コンテナのresolv.confファイルの中身を確認する
[root@<Pod名>] $ cat /etc/resolv.conf

# 権威DNSサーバーのIPアドレス
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
# 名前解決時のローカルドメインの優先度
options ndots:5

▼ ndots

宛先コンテナの名前解決時のドット数を設定する。

ドット数が多ければ多いほど、ローカルドメインから網羅的に名前解決を実施するため、ハードウェアリソースの消費が高くなる。

例えば、ndots:5としたPodがexample.comを名前解決する場合、ドット数は5になる。

そのため、最初はexample.com.default.svc.cluster.local.から名前解決を始め、example.com.で終わる。

(1)

example.com.default.svc.cluster.local.

(2)

example.com.svc.cluster.local.

(3)

example.com.cluster.local.

(4)

example.com.ec2.internal.

(5)

example.com.


07. 通信のデバッグ

Podのアウトバウンド通信のデバッグ

kubectl runコマンド

kubectl execコマンドが運用的に禁止されているような状況がある。

そのような状況下で、シングルNodeの場合は、kubectl runコマンドで、--rmオプションを有効化し、Clusterネットワーク内にcurlコマンドによる検証用のPodを一時的に新規作成する。

# シングルNodeの場合

# curl送信用のコンテナを作成する。
# rmオプションを指定し、使用後に自動的に削除されるようにする。
$ kubectl run \
    -n default \
    -it multitool \
    --image=praqma/network-multitool \
    --rm \
    --restart=Never \
    -- /bin/bash

# curlコマンドでデバッグする。
[root@<Pod名>:~] $ curl -X GET https://<Serviceの完全修飾ドメイン名やIPアドレス>

# tcptracerouteコマンドでデバッグする。
[root@<Pod名>:~] $ tcptraceroute <Serviceの完全修飾ドメイン名やIPアドレス>

# mtrコマンドでデバッグする。
[root@<Pod名>:~] $ mtr <Serviceの完全修飾ドメイン名やIPアドレス>

kubectl debug nodeコマンド

マルチNodeの場合は、指定したNode上でPodを作成できない。

(たぶん) 名前が一番昇順のNode上でPodが作成されてしまい、Nodeを指定できない。

そのため、代わりにkubectl debugコマンドを使用する。

ただし、kubectl debugコマンドで作成されたPodは、使用後に手動で削除する必要がある。

# マルチNodeの場合

# Podが稼働するNodeを確認する。
$ kubectl get pod <Pod名> -o wide

# 指定したNode上で、curl送信用のコンテナを作成する。
# rmオプションはない。
$ kubectl debug node/<Node名> \
    -n default \
    -it \
    --image=praqma/network-multitool

[root@<Pod名>:~] $exit

# 使用後は手動で削除する。
$ kubectl delete -n default node-debugger-*****

▼ デバッグ用Podを起動しておく

デバッグ用Podを起動しておく方法もある。

curlコマンド専用イメージを使用する場合、コンテナ起動後にcurlコマンドを実行し、すぐに終了してしまう。

そのため、CrashLoopBackOffになってしまう。

これを防ぐために、sleep infinityコマンドを実行し、ずっとスリープするようにしておく。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: foo
  template:
    metadata:
      labels:
        app: foo
    spec:
      containers:
        - name: foo-curl
          image: curlimages/curl:8.5.0
          imagePullPolicy: IfNotPresent
          command:
            - sleep
            - infinity