1.背景
近期在部署k8s集群时,由于业务pod访问中间件网络需要进行调试,但是其pod中又没有安装网络调试工具如:telnet、netstat、traceroute等,极为不便调试,故进行研究。
发现可以使用进入【网络空间】的方式对pod进行调试,并且可以使用宿主机上(在此为node)的网络调试工具进行网络调试。
1.1 一般做法
之前的调试大多数是在同namespace下拉起了一个调试pod。
此处放一下我的dockerfile以做记录备份。
docker push 492969105/wljbox:latest
FROM alpine
# 安装所需的软件包,包括util-linux(包含nsenter)
RUN apk update && \
apk add vim netcat-openbsd iputils traceroute bind-tools curl wget bash busybox-extras util-linux && \
echo "alias ll='ls -l'" >> /etc/profile
# 设置环境变量
ENV TERM xterm
# 容器启动时默认进入 /bin/bash
CMD ["/bin/bash"]
2.法一:使用ip netns进行网络调试
环境前提:
1.环境使用的是container作为底层。
2.node可以直接通过跳板机或者其他方式登录(就算不能直接登录也可以发起一个nsenter去登录,见备注)。
3.控制面托管在云服务商。
其常用的命令为crictl和ctr(一般crictl就够用,ctr更加底层)
本次演示的云环境为火山云
网络命名空间一般存储位置:/var/run/nets
CNI插件一般配置目录:/var/lib/cni
😎该方法的使用需要依赖具体云厂商的cni套件进行变更。
但整体流程大同小异,基本上都是通过pod找到containerid再通过containerid拿到netns(网络命名空间)
首先,我们选取一个测试目标。以我自己的pod为例:
可以看到pod为wljbox-d44bd5895-fcj4s
在namespacespark-system
接下来我们看它最终落在哪个node上。
此处备注一个知识点,每一个容器都会依托于对应的宿主机进行运行,其在宿主机上表现为一个具体的pid,只要我们能找到这个pid就能顺势进入其网络命名空间。
通过命令:
可以看到pod实际运行在nodeA上
接下来我们登录nodeA,进入CNI插件的一般配置路径,并搜索容器名:
cd /var/lib/cni
grep -rnio 'wljbox'
可以看到搜索到了三个文件中含有我的容器信息。
在这三个文件中随便打开一个就能看到容器所属的网络空间了。
cat 任一文件路径|grep netns
我们验证一下当前宿主机上是否存在这个网络空间
ls -l /var/run/netns
存在。
接下来我们使用网络空间来调试该container的网络。
ip netns exec 网络空间 ip -4 addr show eth0
方便演示以获取自身ip举例(但其实例如telnet、ping、traceroute、dig等只要宿主机上安装过的工具都可以使用)。
😎切记:你只是可以通过命令调试容器网络,但像cat、ls此类非网络命令显示的依旧为宿主机内容,不要搞错了
验证成功。
3.法二:使用nsenter通过pid进行网络调试
环境前提:
1.环境使用的是container作为底层。
2.node可以直接通过跳板机或者其他方式登录(就算不能直接登录也可以发起一个nsenter去登录,见备注)。
3.控制面托管在云服务商。
其常用的命令为crictl和ctr(一般crictl就够用,ctr更加底层)
本次演示的云环境为火山云
容器最终会跑在某一node上,自然能在node上拿到它对应的pid。
进而通过nsenter -t pid进入pid进行调试。
还是老样子。
我们登录nodeA,并找到pod对应的container的id:
kubectl get pod wljbox-d44bd5895-fcj4s -n spark-system -o jsonpath='{.status.containerStatuses[0].containerID}'
containerd://a99cbb71d0b218b5a9dab3d1d4f3ebe811f8739816ba3462ae0881f3e9538a91
接下来通过container id来拿到它在宿主机的pid
crictl inspect a99cbb71d0b21|jq '.info.pid'
接下来通过pid去调试【因为本处只演示进入进程空间所以没带-n参数,但如果使用ss、telnet这类网络命令需要带-n参数】
nsenter -t 148115 -n ip -4 addr show eth0
结果与podip一致,验证成功。
4.总结
关键命令及目录。
cni配置目录:/etc/cni
node上正在运行的cni:/var/lib/cni/
node上正在运行的网络空间:/var/run/netns
查找容器所对应的网络空间:grep -rnio 'pod名'
使用ip netns调试:ip netns exec 网络空间 ip -4 addr show eth0
使用nsenter调试pod:nsenter -t pod在node上的pid ip -4 addr show eth0
5.nsenter的一些操作
nsenter
是一个强大的 Linux 命令行工具,用于进入(enter)指定进程的命名空间(namespace)。命名空间是 Linux 内核的一个特性,用于隔离系统资源,使得一组进程看到的系统资源与另一组进程不同。这是容器技术的基础之一。主要用途:
进入容器或进程的命名空间,而不需要在容器内部运行 shell。
调试容器或隔离环境中的网络、挂载点等问题。
在特定的命名空间中执行命令。
基本语法:
复制
nsenter [options] [program [arguments]]
常用选项:
--target PID
或-t PID
:指定要进入其命名空间的目标进程的 PID。
--mount[=file]
或-m
:进入 mount 命名空间。
--uts[=file]
或-u
:进入 UTS (Unix Time-sharing System) 命名空间。
--ipc[=file]
或-i
:进入 IPC (Inter-Process Communication) 命名空间。
--net[=file]
或-n
:进入网络命名空间。
--pid[=file]
或-p
:进入 PID 命名空间。
--user[=file]
或-U
:进入用户命名空间。实际应用示例:
进入容器的网络命名空间并执行网络相关命令:
nsenter -t $(crictl inspect -f '{{.State.Pid}}' container_name) -n ip addr
在容器的挂载命名空间中执行命令:
nsenter -t $(crictl inspect -f '{{.State.Pid}}' container_name) -m ls /
进入容器的所有命名空间并启动一个 shell:
nsenter -t $(crictl inspect -f '{{.State.Pid}}' container_name) -a /bin/bash
在 Kubernetes Pod 的网络命名空间中执行命令:
nsenter -t $(crictl inspect <container_id> | jq '.info.pid') -n ip addr
nsenter
的优势:
无需在容器内安装额外工具就能进行调试。
可以访问宿主机上的工具和命令。
允许在容器可能已经崩溃或无法正常工作的情况下进行故障排除。
使用
nsenter
时需要注意:
通常需要 root 权限。
不当使用可能会影响容器或系统的隔离性和安全性。
在生产环境中使用时要格外小心。
总的来说,
nsenter
提供了一种直接且强大的方式来与各种 Linux 命名空间交互。
当我们拿到容器pid以后,挂载mount,可以直接查看容器进程信息。
nsenter -t 1234 --mount ps aux
一个经典的node-shell,用于在nodeA上开启shell登录。【解决在你不能直接登录node时,但你有kubectl的权限】
apiVersion: v1
kind: Pod
metadata:
name: node-shell-test
namespace: kube-system
spec:
containers:
- args:
- -t
- "1"
- -m
- -u
- -i
- -n
- sleep
- "14000"
command:
- nsenter
image: docker.io/alpine:3.9
imagePullPolicy: IfNotPresent
name: shell
resources: {}
securityContext:
privileged: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
enableServiceLinks: true
hostIPC: true
hostNetwork: true
hostPID: true
nodeSelector:
kubernetes.io/hostname: nodeA
priority: 0
restartPolicy: Never
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 0
tolerations:
- operator: Exists
评论区