nsenter —— 进入 Linux namespace 的"传送门"
一句话定义
nsenter 让你"进入"某个进程的 Linux namespace(网络 / 文件系统 / PID / IPC ...),仿佛在那个容器内执行命令——但不需要容器本身有 shell / 工具。是 K8s pod 排错的核心利器。
典型场景
- pod 没装 tcpdump / curl / netstat —— 节点上用
nsenter -t <PID> -n tcpdump进 pod netns 抓包 - pod 卡 Terminating、
kubectl exec都进不去 ——nsenter仍然能进 - distroless 镜像(没 shell)—— 用节点工具 + nsenter 调试
- 看节点 root netns 的网络全景:
nsenter -t 1 -n ip route
装
通常 Linux 自带(util-linux 包)。看:
nsenter --version
没装:
apt install -y util-linux # 通常已装
基本语法
nsenter -t <PID> [namespace flags] <command>
-t = target PID。指定要进入哪个进程的 namespace。
namespace flag 速查
| flag | 进入的 namespace | 含义 |
|---|---|---|
-n | net | 网络(最常用:网卡 / 路由 / iptables / socket) |
-m | mnt | 文件系统挂载点 |
-p | pid | PID(容器内看到的 PID 1) |
-u | uts | hostname / domainname |
-i | ipc | System V IPC |
-C | cgroup | cgroup 视图 |
-U | user | UID / GID 映射 |
-T | time | 时间 namespace(5.6+) |
-a | all | 进所有 namespace(最大限度模拟容器视角) |
进 K8s pod 的 4 个常用入口
1. 进 pod 网络命名空间(最常用)
# 找 pod 容器的 PID
$ PID=$(crictl inspect $(crictl ps -q --name my-pod) | jq '.info.pid')
# 用节点的工具 + pod 的网络视角
$ nsenter -t $PID -n ip addr # pod 视角看网卡
$ nsenter -t $PID -n ip route # pod 视角看路由
$ nsenter -t $PID -n ss -lntp # pod 视角看监听
$ nsenter -t $PID -n tcpdump -i any -nn # pod 视角抓包
$ nsenter -t $PID -n curl http://... # pod 视角访问
这是关键操作
pod 里没工具时——nsenter 是你的救命稻草。distroless 镜像 / scratch 镜像里啥都没有,但节点上 tcpdump / curl 都装着。用 nsenter -t PID -n <tool> 用节点工具 + pod 视角。
2. 进 pod 文件系统视角
$ nsenter -t $PID -m ls / # pod 视角看根目录
$ nsenter -t $PID -m cat /etc/resolv.conf # pod 的 DNS 配置
$ nsenter -t $PID -m -- bash # 进 pod 的 mount + 启动 bash
注意:bash 是节点的 /bin/bash——pod 的 mount ns 里可能没 bash(distroless)。
要"真正像在 pod 里"——加 -m -n -p -u:
$ nsenter -t $PID -m -n -p -u -- /bin/sh
# 现在 PID 视角、mount 视角、网络视角都是 pod 的
# bash 仍来自节点(但跑在 pod 视角下)
3. 一气全进(最像 kubectl exec)
$ nsenter -t $PID -a /bin/sh
-a = 进所有 namespace。等价于完整模拟 kubectl exec 的体验,但不需要容器有 shell。
-a 需要 --mount 提前指定
新版 nsenter -a 要求显式指定目标的 mount ns(因为 mount ns 决定可执行文件在哪)。如果 nsenter -t $PID -a 报错 mount namespace not provided,明确加:
$ nsenter --target $PID --mount --uts --ipc --net --pid -- /bin/sh
或者用 --mount=/proc/$PID/ns/mnt 这种全路径形式。
4. 进 node root netns(看节点全局视角)
$ nsenter -t 1 -n ip route # PID 1 是 init / systemd
$ nsenter -t 1 -n iptables -L -n -v
K8s 节点 PID 1 在 root netns 里——这是看节点级网络配置的方法(如果你登进了某个 pod 的 shell 看不到节点全貌)。
找 pod 容器 PID 的 N 种方法
nsenter -t $PID 需要先拿到 PID。
方法 1:crictl + jq(推荐)
$ PID=$(crictl inspect $(crictl ps -q --name my-pod) | jq '.info.pid')
$ echo $PID
12345
如果一个 pod 有多容器,--name 匹配的可能不只一个。更精确:
$ crictl ps --name my-pod
CONTAINER IMAGE NAME POD ID POD
abc12345 nginx nginx xyz... my-pod
def67890 sidecar sidecar xyz... my-pod
# 选具体一个 container 的 PID
$ PID=$(crictl inspect abc12345 | jq '.info.pid')
方法 2:从 K8s pod metadata 找
# kubectl 拿到 containerID
$ CID=$(kubectl get pod my-pod -o jsonpath='{.status.containerStatuses[0].containerID}' | sed 's|.*//||')
# 在节点上
$ PID=$(crictl inspect $CID | jq '.info.pid')
方法 3:ps + grep(简易但易错)
$ ps -ef | grep my-pod-image
# 看进程
不可靠——多个 pod 用同一镜像就分不清。
方法 4:bash function 封装
写进 .bashrc:
podpid() {
local pod=$1
crictl inspect $(crictl ps -q --name "$pod" | head -1) | jq -r '.info.pid'
}
podexec() {
local pod=$1
shift
nsenter -t $(podpid "$pod") -n "$@"
}
# 用法
$ podpid my-pod
12345
$ podexec my-pod ip addr
$ podexec my-pod tcpdump -i any -nn
实战场景
场景 1:distroless pod 抓包
# pod 是 distroless(没 shell / curl / tcpdump)
$ kubectl exec my-pod -- /bin/sh
# OCI runtime exec failed: exec failed: unable to start container process: ...
# 用 nsenter 救
$ ssh m4
$ PID=$(crictl inspect $(crictl ps -q --name my-pod) | jq '.info.pid')
$ nsenter -t $PID -n tcpdump -i any -nn -c 50
# 看到 pod 网络流量
场景 2:测试 pod 内的 DNS
$ PID=$(crictl inspect $(crictl ps -q --name my-pod) | jq '.info.pid')
# 看 pod 用的 DNS
$ nsenter -t $PID -m cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
# pod 视角解析 K8s 内部域名
$ nsenter -t $PID -n dig kubernetes.default
# pod 视角解析外网(看 CoreDNS forward 是否工作)
$ nsenter -t $PID -n dig github.com
注意:/etc/resolv.conf 是 mount ns 隔离的,所以要 -m。但实际上 dig 命令读 /etc/resolv.conf 时 —— 如果你只 -n 不 -m,会读节点的 resolv.conf,结果错。
排查 DNS 一定要 -n -m 一起:
$ nsenter -t $PID -n -m dig kubernetes.default
场景 3:pod 卡 Terminating
$ kubectl get pod my-pod
NAME READY STATUS AGE
my-pod 1/1 Terminating 5m
$ kubectl exec my-pod -- ps # 也进不去
# 节点上看
$ PID=$(crictl inspect $(crictl ps -q --name my-pod) | jq '.info.pid')
$ nsenter -t $PID -p ps -ef # 看 pod 内进程
$ nsenter -t $PID -n ss -t # 看活跃 socket
通常发现某个进程卡在 close、或者还有未完成的 syscall。
场景 4:进 Cilium / 网络 pod 看 eBPF / iptables
$ kubectl get pod -n kube-system -l k8s-app=cilium -o wide
NAME READY STATUS NODE
cilium-abc12 1/1 Running m1
$ ssh m1
$ PID=$(crictl inspect $(crictl ps -q --name cilium-agent) | jq '.info.pid')
$ nsenter -t $PID -m -- cilium-dbg endpoint list # cilium 自带工具
$ nsenter -t $PID -n ip route # cilium pod 的网络视角
场景 5:用 cgroup 视角看资源占用
$ nsenter -t $PID -C cat /sys/fs/cgroup/memory/memory.usage_in_bytes
# pod 视角的当前内存使用
通常用 kubectl top 更方便,但深入调试时这个直接。
节点上"模拟 kubectl exec"完整脚本
#!/bin/bash
# kexec - 通过 nsenter 在节点上进入 pod
#
# 用法: ./kexec <pod-name> [container-name] [command...]
POD_NAME=$1
shift
CTR=$(crictl ps -q --name "$POD_NAME" | head -1)
if [ -z "$CTR" ]; then
echo "Pod not found: $POD_NAME"
exit 1
fi
PID=$(crictl inspect "$CTR" | jq '.info.pid')
# 默认进 net + mount + pid + uts
nsenter -t "$PID" -n -m -p -u -- ${@:-/bin/sh}
放到 /usr/local/bin/kexec,节点上:
$ kexec my-pod ip addr
$ kexec my-pod tcpdump -i any -nn
$ kexec my-pod /bin/bash # 如果 pod 有 bash
进阶:用 nsenter 直接读 /proc/PID/ns/
nsenter 本质是个 wrapper、调 setns() 系统调用。namespace 在 /proc/PID/ns/ 下:
$ ls -l /proc/12345/ns/
lrwxrwxrwx ... net -> 'net:[4026532152]'
lrwxrwxrwx ... mnt -> 'mnt:[4026532149]'
lrwxrwxrwx ... pid -> 'pid:[4026532151]'
...
数字(4026532152)是 namespace 的 inode。两个 PID 的同名 ns 数字一样 = 同一个 namespace。
看 pod 的多容器共享 net ns:
$ kubectl get pod my-pod -o jsonpath='{.status.containerStatuses[*].containerID}' | tr ' ' '\n'
containerd://abc123
containerd://def456
$ for cid in abc123 def456; do
pid=$(crictl inspect $cid | jq '.info.pid')
ns=$(readlink /proc/$pid/ns/net)
echo "container=$cid pid=$pid net=$ns"
done
container=abc123 pid=12345 net=net:[4026532152]
container=def456 pid=23456 net=net:[4026532152]
^^^^^^^^^
相同 -> 同 pod,共享 net ns
K8s pod 设计:pod 内所有容器共享同一个 net ns + ipc ns。
反面教材 / 踩坑
❌ 坑 1:忘了对应的 namespace flag,读错配置
$ nsenter -t $PID -n dig kubernetes.default
# /etc/resolv.conf 读的是**节点的**、不是 pod 的
# 结果错
/etc/resolv.conf 在 mount ns 里。要 -n -m 一起:
$ nsenter -t $PID -n -m dig kubernetes.default
经验:网络相关排错除非很确定,一般 -n -m 都加上。
❌ 坑 2:用 PID 1(init)当成"任意容器"
$ nsenter -t 1 -n ip route
# 节点 root netns 的路由表(不是任何 pod 的)
节点 PID 1 是 systemd / init —— 节点视角。pod 的 PID 在 /proc/N/ns/net 下数字和节点 PID 1 的 net ns 不同。
如果想"看节点视角",PID 1 OK。想"看 pod 视角",PID 是 pod 容器 PID。
❌ 坑 3:进了 pod ns 但 cwd 还是节点的
$ nsenter -t $PID -n -m -- bash
[bash]$ pwd
/some/host/path # 节点的 cwd!
nsenter 不改 working directory。要"像在 pod 里":
$ nsenter -t $PID -n -m --wd / -- bash
--wd / 设 cwd 到 /。
❌ 坑 4:用 /bin/sh 但 pod 里没有
$ nsenter -t $PID -m -- /bin/sh
# distroless pod: /bin/sh: No such file or directory
-m 之后命令是从 pod 的 mount ns 里找的——distroless 没 sh。两个办法:
- 不加
-m:用节点的 sh
$ nsenter -t $PID -n -p -- /bin/sh # ← 不加 -m,sh 来自节点
但这样进的 sh 看不到 pod 的文件系统(因为 mount ns 还是节点的)。
- 在 pod ns 里执行节点的 sh(复杂、不推荐)
通常不加 -m 用节点 sh + 用绝对路径访问 /proc/PID/root/ 看 pod 文件:
$ nsenter -t $PID -n -- /bin/bash # bash 来自节点
[bash]$ ls /proc/$PID/root/ # 节点视角看 pod 的文件系统
❌ 坑 5:pod 容器已死、PID 不存在
$ nsenter -t 12345 -n ip route
# Couldn't open ns file: /proc/12345/ns/net: No such file or directory
pod 重启了、PID 变了。重新拿:
$ PID=$(crictl inspect $(crictl ps -q --name my-pod) | jq '.info.pid')
养成"每次都重新拿 PID"的习惯,不要保存。
❌ 坑 6:在 K8s pod 里跑 nsenter
$ kubectl exec test-pod -- nsenter -t 1 -n ip route
# nsenter: failed to execute setns: Operation not permitted
K8s pod 默认没 CAP_SYS_ADMIN,setns() 调用被拒。
要让 pod 能用 nsenter(特权操作、慎用):
securityContext:
privileged: true
# 或
securityContext:
capabilities:
add: ["SYS_ADMIN", "SYS_PTRACE"]
hostPID: true # 看到所有 PID
通常在节点上跑 nsenter 而不是 pod 内。
❌ 坑 7:用 root 之外的用户
$ nsenter -t $PID -n ip route
nsenter: cannot open /proc/12345/ns/net: Permission denied
非 root 没权限读别人的 namespace。sudo nsenter ... 或者用 root。
nsenter vs kubectl exec vs kubectl debug
| 工具 | 优点 | 缺点 |
|---|---|---|
| kubectl exec | 简单 / 不需登节点 | 要 pod 有 shell |
| kubectl debug (1.23+) | 在 pod 旁起 sidecar / 修改 image / 提权 | 创建临时资源、复杂 |
| nsenter (节点上) | 不依赖 pod 镜像 / 灵活到极致 | 要登节点 / 要 root |
kubectl debug 最现代:
$ kubectl debug my-pod -it --image=nicolaka/netshoot
# 起一个含网络工具的 sidecar、共享 my-pod 的网络命名空间
但很多生产场景 kubectl debug 也不够:node 上排错、pod 卡 Terminating、CNI 故障 —— 都得 nsenter。