看到一篇关于 Kubectl 运行的机制,觉得写得非常不错,图文并茂很形象,就翻译成了中文记录一下,原文地址:
上周五,我的一位同事问了一个有关如何使用 go-client 在 Pod 中执行命令的问题。我不知道答案,我注意到我从未想过“ kubectl exec”中的机制。我有一些想法,但是我不 100%确定。我需要通过实践来找到答案,在阅读了一些博客,文档和源代码后,我学到了很多东西。在这篇博客中,我将分享我的理解和发现。
我克隆了https://github.com/ecomm-integration-ballerina/kubernetes-cluster,以便在MacBook中创建k8s集群。我修改Kubelet配置中节点的IP地址,因为默认配置不允许我运行kubectl exec
。您可以在这里[2]找到根本原因。
下文提到的关于机器的对应关系如下:
在默认名称空间中创建容器
# any machine
$ kubectl run exec-test-nginx --image=nginx
然后运行 exec 命令并sleep 5000
进行观察
# any machine
$ kubectl exec -it exec-test-nginx-6558988d5-fgxgg -- sh
# sleep 5000
我们可以观察到 kubectl 过程(在这种情况下为 pid = 8507)
# any machine
$ ps -ef |grep kubectl
501 8507 8409 0 7:19PM ttys000 0:00.13 kubectl exec -it exec-test-nginx-6558988d5-fgxgg -- sh
当我们检查该进程的网络活动时,我们可以看到它与 API Server(192.168.205.10.6443)有连接
# any machine
$ netstat -atnv |grep 8507
tcp4 0 0 192.168.205.1.51673 192.168.205.10.6443 ESTABLISHED 131072 131768 8507 0 0x0102 0x00000020
tcp4 0 0 192.168.205.1.51672 192.168.205.10.6443 ESTABLISHED 131072 131768 8507 0 0x0102 0x00000028
让我们检查一下代码。kubectl 使用子资源创建一个 POST 请求,exec
并发送一个 rest 请求。
我们可以在 API 服务端观察请求。
handler.go:143] kube-API Server: POST "/api/v1/namespaces/default/pods/exec-test-nginx-6558988d5-fgxgg/exec" satisfied by gorestful with webservice /api/v1
upgradeaware.go:261] Connecting to backend proxy (intercepting redirects) https://192.168.205.11:10250/exec/default/exec-test-nginx-6558988d5-fgxgg/exec-test-nginx?command=sh&input=1&output=1&tty=1
Headers: map[Connection:[Upgrade] Content-Length:[0] Upgrade:[SPDY/3.1] User-Agent:[kubectl/v1.12.10 (darwin/amd64) kubernetes/e3c1340] X-Forwarded-For:[192.168.205.1] X-Stream-Protocol-Version:[v4.channel.k8s.io v3.channel.k8s.io v2.channel.k8s.io channel.k8s.io]]
请注意,http 请求包括协议升级请求。SPDY[6]允许将单独的 stdin / stdout / stderr / spdy-error 流通过单个 TCP 连接进行多路复用。
API 服务收到请求并将其绑定到 PodExecOptions
为了能够采取必要的措施,API Server 需要知道应该请求哪个 node。
当然,端点是从节点信息派生的。
发现了,Kubelet 具有可node.Status.DaemonEndpoints.KubeletEndpoint.Port
连接 API 服务的端口。
master/node 之间的通信 > master 集群通信 > API Server 到 kubelet[7] 这些连接在 kubelet 的 HTTPS 端点处终止。默认情况下,API 服务不验证 kubelet 的服务证书,这使得连接容易遭受中间人攻击,不安全的公共网络。
现在,API 服务知道了端点并打开了连接。
让我们检查 master 上发生了什么。
首先,得到 worker 节点的 ip。正是192.168.205.11
在这种情况下。
# any machine
$ kubectl get nodes k8s-node-1 -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-node-1 Ready <none> 9h v1.15.3 192.168.205.11 <none> Ubuntu 16.04.6 LTS 4.4.0-159-generic docker://17.3.3
然后获取 kubelet 端口。在这个例子中是10250
。
# any machine
$ kubectl get nodes k8s-node-1 -o jsonpath='{.status.daemonEndpoints.kubeletEndpoint}'
map[Port:10250]
然后检查网络。是否存在到工作节点(192.168.205.11)的连接?可以看到,当我杀死 exec 进程时,它消失了,所以我知道它正是由于 exec 命令而由 API Server 设置的
# master node
$ netstat -atn |grep 192.168.205.11
tcp 0 0 192.168.205.10:37870 192.168.205.11:10250 ESTABLISHED
...
API 服务到 Kubelet
现在,kubectl 和 API Server 之间的连接仍然打开,并且 API Server 和 kubelet 之间还有另一个连接。
让我们通过连接到 Worker 节点并检查正在发生的事情。
首先,我们也可以在此处观察连接。第二行。192.168.205.10
是 master 的 IP。
# worker node
$ netstat -atn |grep 10250
tcp6 0 0 :::10250 :::* LISTEN
tcp6 0 0 192.168.205.11:10250 192.168.205.10:37870 ESTABLISHED
那我们的 sleep 命令呢?可以通过 ps 命令找到!!!
# worker node
$ ps -afx
...
31463 ? Sl 0:00 \_ docker-containerd-shim 7d974065bbb3107074ce31c51f5ef40aea8dcd535ae11a7b8f2dd180b8ed583a /var/run/docker/libcontainerd/7d974065bbb3107074ce31c51
31478 pts/0 Ss 0:00 \_ sh
31485 pts/0 S+ 0:00 \_ sleep 5000
...
不要混淆。它不返回命令的结果。它返回一个通信端点。
kubelet 实现的RuntimeServiceClient
接口是 Container Runtime Interface 的一部分。
它仅使用 gRPC 通过 Container Runtime Interface 调用方法。
container runtime 负责实施 RuntimeServiceServer
Kubelet到容器运行时
如果是这样,我们需要观察 kubelet 与容器运行时之间的联系。对?让我们检查。
在运行 exec 命令之前和之后运行此命令,并检查 diff。
# worker node
$ ss -a -p |grep kubelet
...
u_str ESTAB 0 0 * 157937 * 157387 users:(("kubelet",pid=5714,fd=33))
...
嗯 在 kubelet(pid = 5714)与某个组件通过 UNIX 套接字建立了新连接。正是 DOCKER(pid = 1186)。
# worker node
$ ss -a -p |grep 157387
...
u_str ESTAB 0 0 * 157937 * 157387 users:(("kubelet",pid=5714,fd=33))
u_str ESTAB 0 0 /var/run/docker.sock 157387 * 157937 users:(("dockerd",pid=1186,fd=14))
...
这是运行我们的命令的 docker 守护进程(pid = 1186)。
# worker node.
$ ps -afx
...
1186 ? Ssl 0:55 /usr/bin/dockerd -H fd://
17784 ? Sl 0:00 \_ docker-containerd-shim 53a0a08547b2f95986402d7f3b3e78702516244df049ba6c5aa012e81264aa3c /var/run/docker/libcontainerd/53a0a08547b2f95986402d7f3
17801 pts/2 Ss 0:00 \_ sh
17827 pts/2 S+ 0:00 \_ sleep 5000
...
让我们检查 cri-o 的源代码以了解它如何发生。逻辑在 docker 中相似。
它具有一个实现 RuntimeServiceServer 的服务器。
在链的最后, container runtime 在 work 节点中执行命令。
容器运行时到内核
最后,kernel 执行命令
内核输入
[1]
https://erkanerol.github.io/post/how-kubectl-exec-works/: https://erkanerol.github.io/post/how-kubectl-exec-works/
[2]
这里: https://medium.com/@joatmon08/playing-with-kubeadm-in-vagrant-machines-part-2-bac431095706
[3]
API Server: https://kubernetes.io/docs/concepts/overview/components/#kube-apiserver
[4]
kubelet: https://kubernetes.io/docs/concepts/overview/components/#kubelet
[5]
container runtime: https://kubernetes.io/docs/concepts/overview/components/#container-runtime
[6]
SPDY: https://www.wikiwand.com/en/SPDY
[7]
master/node 之间的通信 > master 集群通信 > API Server 到 kubelet: https://kubernetes.io/docs/concepts/architecture/master-node-communication/#apiserver-to-kubelet
[8]
https://groups.google.com/forum/#!topic/kubernetes-dev/Cjia36v39vM: https://groups.google.com/forum/#!topic/kubernetes-dev/Cjia36v39vM
[9]
https://medium.com/@joatmon08/playing-with-kubeadm-in-vagrant-machines-part-2-bac431095706: https://medium.com/@joatmon08/playing-with-kubeadm-in-vagrant-machines-part-2-bac431095706
[10]
https://serverfault.com/questions/252723/how-to-find-other-end-of-unix-socket-connection: https://serverfault.com/questions/252723/how-to-find-other-end-of-unix-socket-connection
原文链接:https://izsk.me/2021/01/10/Kubernetes-how-does-kubectl-work/