你知道 kubectl exec 的运行机制是什么吗?
看到一篇关于 Kubectl 运行的机制,觉得写得非常不错,图文并茂很形象,就翻译成了中文记录一下,原文地址:
https://erkanerol.github.io/post/how-kubectl-exec-works/[1]
上周五,我的一位同事问了一个有关如何使用 go-client 在 Pod 中执行命令的问题。我不知道答案,我注意到我从未想过“ kubectl exec”中的机制。我有一些想法,但是我不 100%确定。我需要通过实践来找到答案,在阅读了一些博客,文档和源代码后,我学到了很多东西。在这篇博客中,我将分享我的理解和发现。
Setup
我克隆了https://github.com/ecomm-integration-ballerina/kubernetes-cluster,以便在MacBook中创建k8s集群。我修改Kubelet配置中节点的IP地址,因为默认配置不允许我运行kubectl exec
。您可以在这里[2]找到根本原因。
下文提到的关于机器的对应关系如下:
any machine = MacBook master IP = 192.168.205.10 work IP = 192.168.205.11 API server 端口 = 6443
Component
![](https://filescdn.proginn.com/0b921c5e8f9de39fc8380526e2c22904/9e35ee218b789d16926dfbd81d60dd7c.webp)
kubectl exec:当我们在机器上运行“ kubectl exec…”时,可以在任何有权限访问 K8s API 服务上运行。 API Server[3]:API Server上的组件,用于公开 Kubernetes API。它是 Kubernetes 控制平面的前端。 kubelet[4]:在集群中每个节点上运行的代理。确保容器在容器中运行。 container runtime[5]:负责运行容器的软件。例如:docker,cri-o,containerd… kernel:工作节点中操作系统的内核,负责管理进程。 target container:作为 Pod 的一部分并在其中一个工作程序节点上运行的容器。
Findings
1. Client 端
在默认名称空间中创建容器
# 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 请求。
![](https://filescdn.proginn.com/2e5159e6328e23dfb3a35de05666d5ff/8bc5bb142b160a39e436f4182c9bdc33.webp)
2. API Server 端
我们可以在 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 9h v1.15.3 192.168.205.11 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
...
![](https://filescdn.proginn.com/48833e4bc2cbc909b5ddd51453d67709/64aacb21225858bd1032e9bc6f1c8720.webp)
现在,kubectl 和 API Server 之间的连接仍然打开,并且 API Server 和 kubelet 之间还有另一个连接。
3. Worker 节点
让我们通过连接到 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 如何做到的? kubelet 有一个守护程序,该守护程序通过 10250 端口与 API Server 通信。 kubelet 计算执行请求的响应端点。
不要混淆。它不返回命令的结果。它返回一个通信端点。
kubelet 实现的RuntimeServiceClient
接口是 Container Runtime Interface 的一部分。
它仅使用 gRPC 通过 Container Runtime Interface 调用方法。
container runtime 负责实施 RuntimeServiceServer
![](https://filescdn.proginn.com/5582ca198b5e225e9e75a713d45d0041/af3f470baa7af6d0085151b9ac99e27d.webp)
如果是这样,我们需要观察 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
...
4. Docker runtime
让我们检查 cri-o 的源代码以了解它如何发生。逻辑在 docker 中相似。
它具有一个实现 RuntimeServiceServer 的服务器。
在链的最后, container runtime 在 work 节点中执行命令。
![](https://filescdn.proginn.com/f0dd1b35df42b964b531340b6ac9a333/16c087c43c3ed0555554919fd69f4303.webp)
最后,kernel 执行命令
![](https://filescdn.proginn.com/428a3982ffc4ad8670a0270691e62b46/4fdd44a94be325dfa9cbc3e1f3d546b0.webp)
注意事项
API Server 也可以初始化与 kubelet 的连接。 这些连接将一直持续到交互式执行程序结束。 kubectl 和 API Server 之间的连接 API Server 和 kubelet 之间的连接 kubelet 与 container runtime 之间的连接 kubectl 或 API Server 无法在 work 节点中运行任何内容。kubelet 可以运行,但也可以与 container runtime 时交互以进行此类操作。
参考
https://groups.google.com/forum/#!topic/kubernetes-dev/Cjia36v39vM[8] https://medium.com/@joatmon08/playing-with-kubeadm-in-vagrant-machines-part-2-bac431095706[9] https://serverfault.com/questions/252723/how-to-find-other-end-of-unix-socket-connection[10]
引用链接
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/
你可能还喜欢
点击下方图片即可阅读
云原生是一种信仰 🤘
关注公众号
后台回复◉k8s◉获取史上最方便快捷的 Kubernetes 高可用部署工具,只需一条命令,连 ssh 都不需要!
![](https://filescdn.proginn.com/2d6ca2c8731dad4d32d93de0f25e7029/aba60d62a9d32641b026b69af30f0bf3.webp)
点击 "阅读原文" 获取更好的阅读体验!
发现朋友圈变“安静”了吗?
![](https://filescdn.proginn.com/e6c61d1374fd8fc249c8c83848f558b7/68add85d762023dd16257d70f324756b.webp)