kubectl port-forwardの仕組み

port-forwardを何気なく使っていてどう動くのか調べたのでそのまとめ。

port-forwardを試してみる

とりあえず、中身を見る前にどんなことが出来るのか見る。そのために、webサーバとなるpodを作成。

$ kubectl run nginx --image=nginx

Podが作成されたことを確認。

$ kubectl get pod -o wide
NAME    READY   STATUS    RESTARTS   AGE    IP            NODE                 NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          6m1s   172.28.1.24   k8s-worker1   <none>           <none>

kubectlを利用するLinuxノードはk8sクラスタの外部にいるため、PodのIPには直接アクセスできない。

$ ping  -c 3 172.28.1.24
PING 172.28.1.24 (172.28.1.24) 56(84) bytes of data.

--- 172.28.1.24 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2015ms

$ curl -m 1 -vvv http://172.28.1.24
* Rebuilt URL to: http://172.28.1.24/
*   Trying 172.28.1.24...
* Connection timed out after 1001 milliseconds
* Closing connection 0
curl: (28) Connection timed out after 1001 milliseconds

この状態で、port-forwardをコマンドを利用して、podにアクセスしてみる。

$ kubectl port-forward nginx 10080:80
Forwarding from 127.0.0.1:10080 -> 80
Forwarding from [::1]:10080 -> 80

別のターミナルからlocalhost:10080にcurlすると、podのIPへ届かなくても、アクセスできるというものである。

$ curl -vvv http://localhost:10080
* Rebuilt URL to: http://localhost:10080/
*   Trying ::1...
* Connected to localhost (::1) port 10080 (#0)
> GET / HTTP/1.1
> Host: localhost:10080
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.19.4
< Date: Fri, 06 Nov 2020 06:17:02 GMT
< Content-Type: text/html
< Content-Length: 612
< Last-Modified: Tue, 27 Oct 2020 15:09:20 GMT
< Connection: keep-alive
< ETag: "5f983820-264"
< Accept-Ranges: bytes
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host localhost left intact

上記の動作はざっくり以下のような図になる。下記から各プロセスについて詳しくみていく

kubectlクライアントからkube-api-server

kubectlのクライアントからkube-api-serverへは下記のURIにPOSTすることで、ストリームのセッションを作成する。

/api/v1/namespaces/<namespace name>/pods/<pod name>/portforward

これにより、port番号10080に対するリクエストはkube-api-serverに対して転送される。

kube-api-serverからworkerのkubelet

Requestを受け取ったkube-api-serverは該当のworkerノードにいるpodに対してリダイレクトを行う。リダイレクトを行う際には、workerノードのkubeletに対して下記のリクエストを投げる

https://<worker node>:10250/portForward/default/nginx

kube-api-serverのログレベルあげることで、実際にリクエストを送っていることが分かる。

I1106 06:31:11.487573       1 upgradeaware.go:275] Connecting to backend proxy (intercepting redirects) https://10.16.181.22:10250/portForward/default/nginx
  Headers: map[Connection:[Upgrade] Content-Length:[0] Upgrade:[SPDY/3.1] User-Agent:[kubectl/v1.18.2 (linux/amd64) kubernetes/38ac483] X-Forwarded-For:[10.16.181.15] X-Stream-Protocol-Version:[portforward.k8s.io]]

workerノード上では新規のセッションがkube-api-serverからはられていることが下記コマンドでわかる

$ ss -tn
State Recv-Q   Send-Q              Local Address:Port               Peer Address:Port
<snip>
ESTAB 0        0           [::ffff:10.16.181.22]:10250     [::ffff:10.16.181.21]:52154

これにて準備は完了。kubectlを使用しているノードの10080ポートにアクセスした際には、workerノードまでkube-api-serverを経由してリダイレクトされる。

Podまで

workerのkubeletまでリダイレクトされることは分かったと思うが、実際にpodにどのようにリダイレクトされるのか見ていく。

kubectを叩くクライアントからリクエストを送ると、workerノード上でsocatプロセスが立ち上がり、実際にクライアントから来たリクエストが転送される。今回は軽量のwebサーバを用いているため、リクエスト完了まで一瞬である。実際に、workerノード上でリダイレクトしているプロセスも一瞬しか立ち上がらない。
ちなみに、下記のようなプロセスが立ち上がる。

root       876  0.0  0.0  27416  3696 ?        S    15:42   0:00  \_ /usr/bin/socat - TCP4:localhost:80

このプロセスはnsenterにてpodと同じLinux namespaceから実施されている。実際に手動で同じことをすると以下のようになる。

$ sudo docker ps | grep nginx
c1da4d32b3a2        nginx                   "/docker-entrypoint.…"   4 hours ago         Up 3 hours                              k8s_nginx_nginx_default_d8e05bc6-e49f-4743-ac05-9e91493dc0bd_0
048c4c138f4a        k8s.gcr.io/pause:3.2    "/pause"                 4 hours ago         Up 4 hours                              k8s_POD_nginx_default_d8e05bc6-e49f-4743-ac05-9e91493dc0bd_0

$  sudo docker inspect --format {{.State.Pid}} 048c4c138f4a
3740

$ sudo nsenter --target 3740 --net

# echo -e "GET / HTTP/1.0\r\n" | /usr/bin/socat - TCP4:localhost:80
HTTP/1.1 200 OK
Server: nginx/1.19.4
Date: Fri, 06 Nov 2020 09:37:12 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 27 Oct 2020 15:09:20 GMT
Connection: close
ETag: "5f983820-264"
Accept-Ranges: bytes

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

このようにリクエストが転送されるため、podのeth0上でtcpdumpなどでキャプチャしても実際にはリクエストのパケットが見えないのである。代わりにloインタフェースでキャプチャするとパケットが見える。

# tcpdump -v -n -i lo
tcpdump: listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
18:41:20.261137 IP (tos 0x0, ttl 64, id 47223, offset 0, flags [DF], proto TCP (6), length 60)
    127.0.0.1.34748 > 127.0.0.1.80: Flags [S], cksum 0xfe30 (incorrect -> 0xbcb5), seq 161563555, win 65495, options [mss 65495,sackOK,TS val 2012694020 ecr 0,nop,wscale 7], length 0
18:41:20.261176 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
    127.0.0.1.80 > 127.0.0.1.34748: Flags [S.], cksum 0xfe30 (incorrect -> 0x740f), seq 3288057513, ack 161563556, win 65483, options [mss 65495,sackOK,TS val 2012694020 ecr 2012694020,nop,wscale 7], length 0
18:41:20.261217 IP (tos 0x0, ttl 64, id 47224, offset 0, flags [DF], proto TCP (6), length 52)
    127.0.0.1.34748 > 127.0.0.1.80: Flags [.], cksum 0xfe28 (incorrect -> 0x9acb), ack 1, win 512, options [nop,nop,TS val 2012694020 ecr 2012694020], length 0
18:41:20.261469 IP (tos 0x0, ttl 64, id 47225, offset 0, flags [DF], proto TCP (6), length 131)
    127.0.0.1.34748 > 127.0.0.1.80: Flags [P.], cksum 0xfe77 (incorrect -> 0xb638), seq 1:80, ack 1, win 512, options [nop,nop,TS val 2012694021 ecr 2012694020], length 79: HTTP, length: 79
	GET / HTTP/1.1
	Host: localhost:10080
	User-Agent: curl/7.47.0
	Accept: */*

18:41:20.261495 IP (tos 0x0, ttl 64, id 25399, offset 0, flags [DF], proto TCP (6), length 52)
    127.0.0.1.80 > 127.0.0.1.34748: Flags [.], cksum 0xfe28 (incorrect -> 0x9a7b), ack 80, win 511, options [nop,nop,TS val 2012694021 ecr 2012694021], length 0
18:41:20.261661 IP (tos 0x0, ttl 64, id 25400, offset 0, flags [DF], proto TCP (6), length 290)
    127.0.0.1.80 > 127.0.0.1.34748: Flags [P.], cksum 0xff16 (incorrect -> 0xf729), seq 1:239, ack 80, win 512, options [nop,nop,TS val 2012694021 ecr 2012694021], length 238: HTTP, length: 238
	HTTP/1.1 200 OK
	Server: nginx/1.19.4
	Date: Fri, 06 Nov 2020 09:41:20 GMT
	Content-Type: text/html
	Content-Length: 612
	Last-Modified: Tue, 27 Oct 2020 15:09:20 GMT
	Connection: keep-alive
	ETag: "5f983820-264"
	Accept-Ranges: bytes

18:41:20.261728 IP (tos 0x0, ttl 64, id 25401, offset 0, flags [DF], proto TCP (6), length 664)
    127.0.0.1.80 > 127.0.0.1.34748: Flags [P.], cksum 0x008d (incorrect -> 0xdb54), seq 239:851, ack 80, win 512, options [nop,nop,TS val 2012694021 ecr 2012694021], length 612: HTTP
18:41:20.261788 IP (tos 0x0, ttl 64, id 47226, offset 0, flags [DF], proto TCP (6), length 52)
    127.0.0.1.34748 > 127.0.0.1.80: Flags [.], cksum 0xfe28 (incorrect -> 0x998d), ack 239, win 511, options [nop,nop,TS val 2012694021 ecr 2012694021], length 0
18:41:20.261813 IP (tos 0x0, ttl 64, id 47227, offset 0, flags [DF], proto TCP (6), length 52)
    127.0.0.1.34748 > 127.0.0.1.80: Flags [.], cksum 0xfe28 (incorrect -> 0x972d), ack 851, win 507, options [nop,nop,TS val 2012694021 ecr 2012694021], length 0
18:41:20.265748 IP (tos 0x0, ttl 64, id 47228, offset 0, flags [DF], proto TCP (6), length 52)
    127.0.0.1.34748 > 127.0.0.1.80: Flags [F.], cksum 0xfe28 (incorrect -> 0x9723), seq 80, ack 851, win 512, options [nop,nop,TS val 2012694025 ecr 2012694021], length 0
18:41:20.265797 IP (tos 0x0, ttl 64, id 25402, offset 0, flags [DF], proto TCP (6), length 52)
    127.0.0.1.80 > 127.0.0.1.34748: Flags [F.], cksum 0xfe28 (incorrect -> 0x971e), seq 851, ack 81, win 512, options [nop,nop,TS val 2012694025 ecr 2012694025], length 0
18:41:20.265813 IP (tos 0x0, ttl 64, id 47229, offset 0, flags [DF], proto TCP (6), length 52)
    127.0.0.1.34748 > 127.0.0.1.80: Flags [.], cksum 0xfe28 (incorrect -> 0x971e), ack 852, win 512, options [nop,nop,TS val 2012694025 ecr 2012694025], length 0

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください