0x00 前言
随着微服务架构和云计算的普及, 越来越多企业的应用都上了云, 不仅是云基础设施 IaaS , 如 kubernetes 等 PaaS 项目也是越来越热门. 但新的技术会带来新的架构复杂, 同时也会使排查问题更加困难, 因此很多运维和开发同学都觉得用平时用的顺手的工具和手段在容器里排查问题不好使了. 工欲善其事必先利其器, 正是由于这样的情况, 所以我们排查容器问题的时候, 需要引入新的工具和手段.
0x10 容器基础
在学习工具的使用前, 我们首先需要简单的了解下容器的原理. 假如一台机器是一间房子, 那么进程就是住在里面的一个个的人, 在单体应用的时代, 所有人都住在一间房子里, 而容器技术就是通过一些手段把这些人都隔开, 让每个人都以为自己住上有独卫 (网络, IPC,namespace 等资源) 的单间. 而
这些隔离和限制的主要使用的如下技术:
cgroups 资源限制
namespace 资源隔离
rootfs 文件系统隔离
在单体应用的时代, 所有的进程都在同一个命名空间里, 且启动的进程都没有隔离命名空间, 那么自然调试工具进程也在同一个命名空间, 也就可以 debug 其他进程. 而容器技术由于分割成一个个的小房间, 如果想要查看单个房间的情况, 虽然在大管家 (宿主机) 的上帝视角一样可以看到, 但为了减少干扰并更加符合我们平时的使用习惯, 我们就需要进入到房间 (命名空间) 里面查看.
例如我们可以通过 docker inspect CONTAINER_ID 获取到某个容器资源隔离的文件的地址, 如下 "SandboxKey": "/var/run/docker/netns/50def85bf6e2" 就是.
- @Ubuntu k8s-debug docker inspect 0dde03166e02
- ...
- "SandboxKey": "/var/run/docker/netns/50def85bf6e2",
- "SecondaryIPAddresses": null,
- "SecondaryIPv6Addresses": null,
- "EndpointID": "7faa1e764317cdfadf7f31b7ed2fecff62b458211f79fadb3362c8e22755f326",
- "Gateway": "172.17.0.1",
- ...
而容器的诊断工具就是自带了部分调试工具的镜像, 并能根据容器 ID 帮我们自动地进入到房间(网络, IPC 命名空间等).
在初步了解了容器的原理后, 我们便可进入工具的介绍了.
0x20 netshoot
首先介绍的第一个工具是 https://github.com/nicolaka/netshoot ,netshoot 的自我定位就是容器网络诊断的瑞士军刀, 简单来说, netshoot 其实就是一个装满了各种工具的镜像, 他用起来也很简单.
执行
docker run -it --net container:<container_name|container_id> nicolaka/netshoot
就行, 这里的 --net 是 docker 命令指定该容器要联结到哪个容器的网络命名空间
如果要进入宿主机的命名空间则指定 --net host 就行了
如果要诊断 docker NIC 设备的网络 情况, 则可以用工具 nsenter 进入 NIC 设备的命名空间排查, 后面我会介绍这个工具
另外, 如果是在 kubernetes 里面, 我们可以通过执行 kubectl run test-lab --generator=run-pod/v1 --rm -i --tty --overrides='{"spec": {"hostNetwork": true}}' --image nicolaka/netshoot -- /bin/bash 这个命令进入宿主机的网络. 别怕这个命令长, 我来一一解释下这条命令的各个选项的作用
kubectl run test_lab --generator=pod/v1 --rm -i --tty
意思是创建一个一次性的名叫 test_lab 的 Pod 资源并且使用标准输入输出交互
--overrides='{"spec": {"hostNetwork": true}}'
意思是使用宿主机网络, 具体哪台宿主机要看这个 Pod 调度到哪个节点.
--image nicolaka/netshoot
指定 Pod 的镜像
-- 这是 bash 的内置命令选项, 是标志命令的结束的意思, 举个例子: 如果我想要在文件里用 grep 搜索 - v 字符串, grep -v filename 中 - v 会被视为选项, 但我如果使用 grep -- -v filename 那么就可以正常搜索了
0x21 演示
下面我来演示几个例子:
使用 tcpdump 抓容器的包并拷贝 pcap 文件出来, 便于用 wireshark 分析
- @Ubuntu k8s-debug mkdir -p /tmp/netshoot
- @Ubuntu k8s-debug docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- 0dde03166e02 jumpserver/jms_all:1.5.4 "entrypoint.sh" 3 weeks ago Up 3weeks ... mystifying_williamson
- @Ubuntu k8s-debug docker run -it -v /tmp/netshoot:/tmp --net container:0dde03166e02 nicolaka/netshoot
- Welcome to Netshoot! (GitHub.com/nicolaka/netshoot)
- root @ /
[1] → tcpdump -nn -i any -w /tmp/pkg.pcap
[2] → exit
具体命令使用与之前的差不多, 只不过把宿主机上的 / tmp/netshoot 目录 bind-mount 到了容器的 / tmp 目录
有时候我们还需要调试 bridge 或者 overlay 网络, 可以使用 nsenter,nsenter 可以进入任何命名空间
- @Ubuntu ~ docker network ls
- NETWORK ID NAME DRIVER SCOPE
- ...
- 0ipu2p43c6jh ingress overlay swarm
- 697402c52a87 none null local
- @Ubuntu ~ docker run -it --rm -v /var/run/docker/netns:/var/run/docker/netns --privileged=true nicolaka/netshoot
- Welcome to Netshoot! (GitHub.com/nicolaka/netshoot)
- root @ /
[1] → ls /var/run/docker/netns/
- 1-0ipu2p43c6 50def85bf6e2 83f9ffa847d7 default ingress_sbox
- root @ /var/run/docker/netns
[6] → nsenter --net=/var/run/docker/netns/1-0ipu2p43c6 sh
- root @ /run/docker/netns
- [#] → ifconfig
- br0 Link encap:Ethernet HWaddr 02:61:F2:E4:26:3B
- .NET addr:10.255.0.1 Bcast:10.255.255.255 Mask:255.255.0.0
- UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
- ...
- vxlan0 Link encap:Ethernet HWaddr 02:61:F2:E4:26:3B
- UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
- RX packets:0 errors:0 dropped:0 overruns:0 frame:0
- TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
- collisions:0 txqueuelen:0
- RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
- root @ /run/docker/netns
- [#] → bridge fdb show br0
- 33:33:00:00:00:01 dev br0 self permanent
- 01:00:5e:00:00:01 dev br0 self permanent
- 02:61:f2:e4:26:3b dev vxlan0 master br0 permanent
- ...
上面的命令首先我们是进入了 1-0ipu2p43c6 的命名空间, 即那个叫 ingress 的 overlay 网络, 然后可以通过查看这个 NIC 设备上的 fdb 表
我们也可以通过挂载 docker 的 unix sock 文件查看容器的 metrics
docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock nicolaka/netshoot ctop
如下图所示
netshoot 工具非常强大, 还有很多功能可以自行去探索, https://github.com/nicolaka/netshoot 上有详细的说明.
0x30 docker-debug
上面介绍的 netshoot 主要定位于 docker 网络的诊断, 从名字就可以看出来. 而我们现在介绍的工具 https://github.com/zeromake/docker-debug 可以说是 netshoot 的升级版, 他不仅可以进入目标容器的网络命名空间, 还可以进入 pid,user,filesystem,ipc 的命名空间, 所以我们可以操作的空间就更大了. 话不多说, 我们开始演示.
0x31 安装
首先我们要下载 docker-debug 的二进制文件
- @Ubuntu docker-debug wget docker-debug https://github.com/zeromake/docker-debug/releases/download/0.6.2/docker-debug-linux-amd64 -O docker-debug
- @Ubuntu docker-debug chmod +x docker-debug
- @Ubuntu docker-debug mv docker-debug /usr/bin
- @Ubuntu docker-debug docker-debug info
- Version: 0.6.2
- Platform: TravisLinux
- Commit: cf4cc41
- Time: 2019-06-20 05:40:52 +0000
然后我赋予了文件执行权限并移动到 / usr/bin 目录下, 如果执行 docker-debug info 看到有正确输出, 则说明安装成功了
0x32 使用
使用就很简单了, 首先我们获取到容器的名字或者容器 ID
- @Ubuntu docker-debug docker ps
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- 0dde03166e02 jumpserver/jms_all:1.5.4 "entrypoint.sh" 3 weeks ago Up 3 weeks ... mystifying_williamson
然后执行 docker-debug <CONTAINER_ID|CONTAINER_NAME> COMMAND 就可以了
- @Ubuntu docker-debug docker-debug 0dde03166e02 bash
- bash-5.0# netstat -lntp
- Active Internet connections (only servers)
- Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
- ...
- tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 52/python3.6
- tcp 0 0 0.0.0.0:8081 0.0.0.0:* LISTEN 110/java
- ...
- bash-5.0# ifconfig
- eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:02
- .NET addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
- UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
- RX packets:32776325 errors:0 dropped:0 overruns:0 frame:0
- ...
- bash-5.0# ls /mnt/container/
- anaconda-post.log dev lib mnt root srv usr
- bin etc lib64 opt run sys var
- config home media proc sbin tmp
我们可以看到已经进入了目标容器的 ipc,network,filesystem,pid 的命名空间了, 而目标容器的 root 则挂载在了 / mnt/container 目录下.
此外, 我们还可以通过设置 docker-debug 在~/.docker-debug/config.toml 的配置文件使用自定义的诊断镜像
- version = "0.6.1"
- image = "nicolaka/netshoot:latest"
- mount_dir = "/mnt/container"
- timeout = 10000000000
- config_default = "default"
- [config]
- [config.default]
- host = "unix:///var/run/docker.sock"
- tls = false
- cert_dir = "" cert_password =""
其他就不做过多介绍了
0x40 kubectl-debug
在介绍了 docker 的 debug 的工具后, 我们了解了容器诊断工具的原理和使用, 接下来我们要学习 kubernetes 的容器诊断工具. 虽然 kubernetes 上也可以用我上面介绍的那些工具, 但 kubernetes 上的容器毕竟运行在不同的 node 上, 用起来就不太方便, 所以就要用到 https://github.com/aylei/kubectl-debug 这个工具了.
kubectl-debug 其实就是一个 kubectl 的插件, 他的原理和 docker 容器诊断工具大同小异. kubectl-debug 可以帮我们在 某个 Pod 的节点上起一个容器, 并将这个容器加入到目标容器的 pid,network,user,icp 的命名空间. kubectl-debug 架构主要可以分为两部分:
客户端: kubectl-debug 二进制文件
服务端: agent 容器
客户端通过控制 node 上的 agent 服务端与容器运行时通信, 从而启动一个容器并进入到指定 Pod 的命名空间, 可以说 agent 就是一个 debug 容器与客户端之间的中继. 而从 kubectl-debug 的工作模式来看, 可以分为两种模式:
非常驻服务端: agentless
常驻服务端: DaemonSet
简单来说就是 agentless 模式只有在每次 kubectl-debug 进行调试 Pod 的时候才会启动一个 agent 服务端, 调试完成后自动清理 agent, 此模式的优点是不那么占用 kubernetes 集群资源, 而 DaemonSet 模式就是在每个节点上都会常驻一个 DaemonSet 的 agent, 好处就是启动快.
此外针对 node 节点无法直接访问的情况, kubectl-debug 还有一个 port-forward 模式, 这里就不多介绍了.
由于 kubectl-debug 可能还不太完善, agentless 模式我这里用不了, 所以我用的是 DaemonSet 模式, 下面开始演示.
0x41 安装客户端
安装过程和 docker-debug 差不多
下载二进制文件:
wget https://github.com/aylei/kubectl-debug/releases/download/v0.1.0/kubectl-debug_0.1.0_linux_amd64.tar.gz -O kubectl-debug.tar.gz
解压文件:
tar -zxvf kubectl-debug.tar.gz kubectl-debug
0x42 安装 agent 服务端
下载 DaemonSet 的 YAML 文件:
wget -f https://raw.githubusercontent.com/aylei/kubectl-debug/master/scripts/agent_daemonset.yml
修改 agent_daemonset.YAML 文件
- ...
- 18 hostNetwork: true # 需要加上 hostNetwork: true,hostPort:10027 才会生效
- 19 hostPID: true
- 20 tolerations:
- 21 - key: node-role.kubernetes.io/master
- 22 effect: NoSchedule
- 23 containers:
- 24 - name: debug-agent
- 25 image: aylei/debug-agent:v0.1.1 # 老版本镜像有问题, 使用 v0.1.1 新版本
- ...
- 39 ports:
- 40 - containerPort: 10027
- 41 hostPort: 10027
- ...
创建 DaemonSet:kubectl apply -f agent_daemonset.YAML, 接下来我们可以看到每个节点上都创建了 debug-agent 的 DaemonSet, 并且宿主机上都监听了 10027 端口.
- root @ master k8s-debug kubectl get pods
- NAME READY STATUS RESTARTS AGE
- debug-agent-5gfk6 1/1 Running 0 22h
- ...
- root @ master k8s-debug netstat -lntp | grep 10027
- tcp6 0 0 :::10027 :::* LISTEN 15510/debug-agent
执行命令 kubectl-debug <POD_NAME > 就可以进行调试了
- root @ master k8s-debug kubectl get pods
- NAME READY STATUS RESTARTS AGE
- licai-gwapi-77465b4c66-hdjlb 1/1 Running 0 3d
- ...
- root @ master k8s-debug kubectl-debug licai-gwapi-77465b4c66-hdjlb --agentless=false --port-forward=false
- pulling image nicolaka/netshoot:latest...
- ...
- bash-5.0# ps -ef | grep java
- 1 root 23:24 /usr/local/openjdk-8/bin/java -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties...
- 192 root 0:00 grep java
- bash-5.0# exit
- exit
- root @ master k8s-debug
我们可以看到已经进入了目标容器的命名空间了, 而 kubectl-debug 客户端正是与每个 node 上的 10027 端口通信来控制 agent 对 Pod 的调试.
除了这些之外, kubectl-debug 还有很多配置可以自定义, https://github.com/aylei/kubectl-debug 页面也有详细的介绍, 至此从 docker 到 kubernetes 的调试工具介绍完成了.
来源: https://segmentfault.com/a/1190000021162327