原文位于 https://github.com/huazhihao/kubespy/blob/master/implement-a-k8s-debug-plugin-in-bash.md

背景

Kubernetes调试的最大痛点是精简过的容器镜像里没有日常的调试工具。背后的原因是精简容器镜像本身就是容器技术的最佳实践之一。nginx的容器镜像甚至不包含ps和curl这种最基础的工具。这种完全服务于生产环境的策略无异于过早优化,但受制于immutable infrastructure的基本思想和CI/CD实际操作的双重制约,你无法在生产环境发布一个和开发环境不同的容器镜像。这使得这一过早优化的结果更加灾难化。解决这个问题的关键在于,能否在不侵入式的修改容器镜像的情况下,向目标容器里加载需要的调试工具。例如,类似于istio之类的解决方案可以向目标pod插入一个sidecar容器。当然这里的权限要求是高于sidecar容器的,因为pod中的各个容器虽然共享network,但pid和ipc是不共享的。此外,sidecar容器是无法被加入一个已经创建出来的pod,而我们希望工具容器可以在运行时被动态插入,因为问题的产生是随机的,你不能完全预测需要加载哪些工具。

Kubernetes社区很早有相关的issueproposal但并没有最后最终被upstream接受。

目前官方给出最接近的方案是Ephemeral ContainersshareProcessNamespace。前者允许你在运行时在一个pod里插入一个短生命周期的容器(无法拥有livenessProbe, readinessProbe),而后者允许你共享目标pod内容器的network,pid,ipc等cgroups namespace(注意,此namespace非Kubernetes的namespace)甚至修改其中的环境。目前Ephemeral Containers还在v1.17 alpha阶段。而且shareProcessNamespace这个spec要求在创建pod的时候就必须显式启用,否则运行时无法修改。

kubespy (https://github.com/huazhihao/kubespy)是一个完全用bash实现的Kubernetes调试工具,它不但完美的解决了上面提到的如何在运行时向目标容器加载工具的问题,而且并不依赖任何最新版本的Kubernetes的特性。这篇文章稍后会介绍如何使用kubespy来动态调试,以及kubespy是如何通过kubectl,docker以及chrootl来构建这条调试链的,最后会简单分析一下关键的代码实现。

安装

你可以直接从代码安装,因为kubespy是完全bash实现的,所以可以直接拷贝文件来执行。

$ curl -so kubectl-spy https://raw.githubusercontent.com/huazhihao/kubespy/master/kubespy
$ sudo install kubectl-spy /usr/local/bin/

你也可以从krew来安装。krew是一个kubernetes-sigs孵化中的kubectl插件包管理工具,带有准官方性质。

$ kubectl krew install spy

使用

安装过后,kubespy可以成为一个kubectl的子命令被执行。你可以指定目标pod为参数。如果目标pod有多个容器,你可以通过-c指定具体的容器。你也可以指定加载的工具容器的镜像,默认是busybox:latest

$ kubectl spy POD [-c CONTAINER] [--spy-image SPY_IMAGE]

你可以通过以下这个demo来快速体验如何调试一个镜像为nginx的pod。nginx镜像不包含ps或者任何网络工具。而加载的工具容器的镜像为busybox,不但可以访问原容器的文件系统和进程树,甚至可以杀进程,修改文件。当然发http请求更是没有问题。

工作原理

kubespy的工作原理大致可以用以下这个流程图来展示。

local machine: kubectl spy [1]
|
v
master node: kube-apiserver [2]
|
v
worker node: kubelet [3]
|
v
spy pod (eg. busybox) [4]
| (chroot)
v
docker runtime [5]
| (run)
v
spy container [6]
| (join docker namespace: pid/net/ipc)
v
application pod (eg. nginx) [7]

概要的看,kubespy是通过以下这些步骤构建调试连的,上图的步骤数字可以与下文对应

[1] `kubespy`作为`kubectl`的插件被执行,可以向`master node`上的`kube-apiserver`发出api请求,会先取得目标容器的关键信息,如其所在的`worker node`和pid/net/ipc 等cgroups namespace
[2] `kube-apiserver`将具体的命令分发给目标容器所在的`worker node`上的agent`kubelet`执行
[3] `kubelet`创建一个`busybox`作为`spy pod`
[4] `spy pod`mount了`worker node`的根目录,并通过`chroot`取得了worker node的控制权
[5] `spy pod`控制了docker cli创建了工具容器(`spy container `)
[6] 工具容器被加入目标容器的pid/net/ipc等cgroups namespace
[7] 用户通过`kubectl`被attach到工具容器的tty里,可以对目标容器进行调试甚至是修改

关键代码

如何取得目标容器的pid/net/ipc等cgroups namespace

  if [[ "${co}" == "" ]]; then
cid=$(kubectl get pod "${po}" -o='jsonpath={.status.containerStatuses[0].containerID}' | sed 's/docker:\/\///')
else
cid=$(kubectl get pod "${po}" -o='jsonpath={.status.containerStatuses[?(@.name=="'"${co}"'")].containerID}' | sed 's/docker:\/\///')
fi

根据kubectl的convention,如果用户未指定容器,我们默认以目标pod的第一个容器作为目标容器。kubelet在为kubernetes集群创建容器时,会相应的创建以containerID命名的pid/net/ipc等cgroups namespace。cgroups namespace是docker实现user space隔离的基础原理,可以参考https://docs.docker.com/engine/docker-overview/#namespaces 进行了解。

如何获得目标容器所在worker node以及其docker cli的控制权

"volumes": [
{
"name": "node",
"hostPath": {
"path": "/"
}
}
]

spy pod会将worker node的根目录作为volume来mount在/host

{
"name": "spy",
"image": "busybox",
"command": [ "/bin/chroot", "/host"],
"args": [
"docker",
"run",
"-it",
"--network=container:'"${cid}"'",
"--pid=container:'"${cid}"'",
"--ipc=container:'"${cid}"'",
"'"${ep}"'"
],
"stdin": true,
"stdinOnce": true,
"tty": true,
"volumeMounts": [
{
"mountPath": "/host",
"name": "node"
}
]
}

然后在通过busybox里的chroot,将worker node的根目录作为自己的根目录。而docker cli也将被直接暴露出来。

在获取了目标容器的pid/net/ipc等cgroups namespace之后,即可直接创建工具容器共享目标容器的cgroups namespace。此时,目标容器和目标容器在进程树,网络空间,内部进程通讯等,都是没有任何隔离的。

如何将用户的terminal带入目标容器中

kubectl run -itchrootdocker run -it都是可以attach到目标的tty中的,这些命令链接起来,像一系列跳板,把用户的terminal一层层带入下一个,最终带入目标容器中。

小结

kubespy (https://github.com/huazhihao/kubespy) 用bash实现对kubernetes集群中的pod通过动态加载工具容器来调试,弥补了目前kubernetes版本上功能的缺失,并展示了一些对kubernetes本身有深度的技巧。

kubespy 用bash实现的k8s动态调试工具的更多相关文章

  1. k8s动态存储管理GlusterFS

    1. 在node上安装Gluster客户端(Heketi要求GlusterFS集群至少有三个节点) 删除master标签 kubectl taint nodes --all node-role.kub ...

  2. [Codeforces757G]Can Bash Save the Day?——动态点分治(可持久化点分树)

    题目链接: Codeforces757G 题目大意:给出一棵n个点的树及一个1~n的排列pi,边有边权,有q次操作: 1 l r x 求 $\sum\limits_{i=l}^{r}dis(p_{i} ...

  3. GDB调试工具、动态加载、内存管理(day04)

    一.程序中的错误处理 在系统中定义了一个全局变量errno.在这个全局变量中存放着系统调用或者库函数出错的信息(错误编号).然后根据错误编号获取错误信息. 举例说明: 打开一个文件,如果这个文件不存在 ...

  4. k8s Kubernetes v1.10 最简易安装 shell

    k8s Kubernetes v1.10 最简易安装 shell # Master 单节点快速安装 # 最简单的安装shell,只为快速部署k8s测试环境 #环境centos 7.4 #1 初始化环境 ...

  5. glusterfs+heketi为k8s提供共享存储

    背景 近来在研究k8s,学习到pv.pvc .storageclass的时候,自己捣腾的时候使用nfs手工提供pv的方式,看到官方文档大量文档都是使用storageclass来定义一个后端存储服务, ...

  6. linux上静态库和动态库的编译和使用(附外部符号错误浅谈)

    主要参考博客gcc创建和使用静态库和动态库 对于熟悉windows的同学,linux上的静态库.a相当于win的.lib,动态库.so相当于win的.dll. 首先简要地解释下这两种函数库的区别,参考 ...

  7. apktool + eclipse 动态调试APK

    用了会AndBug,尽管挺强大的可是作为习惯了OD.EDB作为动态调试工具的人,自然有些不习惯,于是乎寻求新的动态调试解决方式.但大多数都是NetBeans + apktool.想着还得多下一个IDE ...

  8. k8s master init and add node

    目录 一. add google apt-key 二. k8s master init 三. k8s node add to master cluster(use this command when ...

  9. bash builtin eval

    1 在开始执行eval后面的命令之前eval主要做了哪些事情 1.1 去掉反斜杠的quoting 比如\$ac_optarg,会变成$ac_optarg. 1.2 去掉单引号的quoting 比如: ...

随机推荐

  1. Laravel实现找回密码及密码重置的例子

    https://mp.weixin.qq.com/s/PO5f5OJPt5FzUZr-7Xz8-g Laravel实现找回密码及密码重置功能在php实现与在这里实现会有什么区别呢,下面我们来看看Lar ...

  2. xUtils框架的介绍(三)

    接上回,继续介绍xUtils的最后两个模块:DbUtils和HttpUtils.首先先介绍第一个SQLite数据库操纵的简单ORM框架,只要能理解xUtils为我们提供的api,相信你也能熟练的把Db ...

  3. ThinkPHP商城实战

    ThinkPHP3.2.3商城实战教程,需要的联系我,QQ:1844912514 千万级php电商秒杀项目实战  ,需要的联系我,QQ:1844912514

  4. 【CSS3 + 原生JS】上升的方块动态背景

    GIF图有点大,网速慢的或将稍等片刻或可浏览本人的制作的demo. Demo : 点击查看 HTML: <!DOCTYPE html> <html lang="en&quo ...

  5. Python--day19--time模块

    时间戳转化成结构化时间: 本地时间转换成时间字符串: 时间模块 和时间有关系的我们就要用到时间模块.在使用模块之前,应该首先导入这个模块. #常用方法 1.time.sleep(secs) (线程)推 ...

  6. 最小生成树prim、

    过年那几天确实没好好学习.在老家闲着也是闲着.可是就是没看书. 回来这几天又一直在弄个人博客.买域名云服务器备案什么的- -. 麻烦死了呢. 在腾讯花1块钱备案了一个网站www.goodgoodstu ...

  7. HDU 1326

    题意:给出一堆高度不一的砖头,现在使他们高度一样,问最少的移动次数,(每减少1就是移动一次) 思路:求出平均高度,然后模拟最后平均高度的数组,也就是说,每个数组对应每一个平均高度,也就是说比平均高度大 ...

  8. Cookie内不能直接存入中文,cookie转码以及解码

    如果在cookie中存入中文,极易出现问题. js在存入cookie时,利用escape() 函数可对字符串进行编码, 用unescape()进行解码 顺序是先把cookie用escape()函数编码 ...

  9. vue调用兄弟组件的方法使用vueBus调用$emit、$on(只需触发方法即可,不需要考虑传值或参数的问题)

    触发方: vueBus.$emit('queryAll') 被触发方: created() { vueBus.$on('queryAll', () => { this.getList() // ...

  10. Js中“==”和“===”的区别

    从字面上来讲,‘==’代表相等,‘===’代表严格相等. 具体来讲,比较过程如下: 比较过程: ‘==’:      1. 首先判断两个值的类型是否相同,如果相同,进行‘===’判断.      2. ...