ネットワーク@Kubernetes¶
はじめに¶
本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。
01. Nodeネットワーク¶
Nodeネットワークとは¶
同じサブネットマスク内にあるNodeのNIC間を接続するネットワーク。
Nodeネットワークの作成は、Kubernetesの実行環境のネットワークが担う。
02. Serviceネットワーク¶
Serviceネットワークとは¶
Podのアウトバウンド通信に割り当てられたホスト名を認識し、そのホスト名を持つServiceまでリクエストを送信する。
Serviceネットワークの作成は、Kubernetesが担う。
03. Clusterネットワーク¶
Clusterネットワークとは¶
同じClusterネットワーク内にあるPodの仮想NIC (veth) 間を接続するネットワーク。
Clusterネットワークの作成は、CNIが担う。
04. Podネットワーク¶
Podネットワークとは¶
Pod内のネットワークのみを経由して、他のコンテナにリクエストを送信する。
Podごとにネットワークインターフェースが付与され、またIPアドレスが割り当てられる。
名前空間¶
▼ 名前空間の種類¶
Podのネットワークは複数の種類の名前空間から構成される。
ネットワークの範囲に応じて、コンテナが同じNode内のいずれのコンポーネントのプロセスと通信できるようになるのかが決まる。
注意点として、Dockerとは名前空間の種類が異なる。
▼ IPC名前空間¶
プロセスは、同じIPC名前空間に属する他のプロセスと通信できる。
Kubernetesのセキュリティ上の理由から、デフォルトではPod内のコンテナはホスト (Node) とは異なるIPC名前空間を使用し、ネットワークを分離している。
そのため、コンテナのプロセスはNodeのプロセスと通信できないようになっている。
Network名前空間¶
PID名前空間¶
Hostname名前空間¶
cgroup名前空間¶
05. ネットワークレイヤー¶
Ingress Controller由来のL7
ロードバランサーの場合¶
Ingress Controllerの場合、L7
ロードバランサーをプロビジョニングする。
Ingress ControllerによるL7
ロードバランサーは、受信した通信をServiceにルーティングする。
ServiceはL4
ロードバランサーとして、インバウンド通信をPodにルーティングする。
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