kubespy 用bash实现的k8s动态调试工具

栏目: IT技术 · 发布时间: 4年前

内容简介:Kubernetes调试的最大痛点是精简过的容器镜像里没有日常的调试工具。背后的原因是精简容器镜像本身就是容器技术的最佳实践之一。nginx的容器镜像甚至不包含ps和curl这种最基础的工具。这种完全服务于生产环境的策略无异于过早优化,但受制于immutable infrastructure的基本思想和CI/CD实际操作的双重制约,你无法在生产环境发布一个和开发环境不同的容器镜像。这使得这一过早优化的结果更加灾难化。解决这个问题的关键在于,能否在不侵入式的修改容器镜像的情况下,向目标容器里加载需要的调试工具

背景

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

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

目前官方给出最接近的方案是Ephemeral Containers和shareProcessNamespace。前者允许你在运行时在一个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/kubesp y)是一个完全用bash实现的Kubernetes调试工具,它不但完美的解决了上面提到的如何在运行时向目标容器加载工具的问题,而且并不依赖任何最新版本的Kubernetes的特性。这篇文章稍后会介绍如何使用kubespy来动态调试,以及kubespy是如何通过kubectl,docker以及chrootl来构建这条调试链的,最后会简单分析一下关键的代码实现。

安装

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

$ curl -so kubectl-spy https://raw.githubusercontent. ... bespy

$ 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 ... paces 进行了解。

如何获得目标容器所在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 -it,chroot和docker run -it都是可以attach到目标的tty中的,这些命令链接起来,像一系列跳板,把用户的terminal一层层带入下一个,最终带入目标容器中。

小结

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

原文位于


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java编程思想 (第4版)

Java编程思想 (第4版)

[美] Bruce Eckel / 陈昊鹏 / 机械工业出版社 / 2007-6 / 108.00元

本书赢得了全球程序员的广泛赞誉,即使是最晦涩的概念,在Bruce Eckel的文字亲和力和小而直接的编程示例面前也会化解于无形。从Java的基础语法到最高级特性(深入的面向对象概念、多线程、自动项目构建、单元测试和调试等),本书都能逐步指导你轻松掌握。 从本书获得的各项大奖以及来自世界各地的读者评论中,不难看出这是一本经典之作。本书的作者拥有多年教学经验,对C、C++以及Java语言都有独到......一起来看看 《Java编程思想 (第4版)》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

MD5 加密
MD5 加密

MD5 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试