对于理解k8s相关机制、漏洞利用细节来说源码debug调试的作用不言而喻,附上k8s源码编译再到远程debug调试笔记过程。
环境搭建
- 宿主机goland+远程/虚拟机k8s集群
- 通过GoLand和dlv远程调试Linux虚拟机中的Kubernetes进程
kubeadm安装k8s集群
这个网上很多教程,官方也有文档,但是有很多坑所以还是记了笔记。
拿环境debian10来安装,arch为arm64。
docker安装:
docker是kubeadm的前置依赖,安装docker的步骤参考官方教程:https://docs.docker.com/engine/install/debian/
更新deb源,安装必要软件
$ apt-get update
$ apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release -y
添加docker官方GPG key和添加docker amd64 deb源
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo \
"deb [arch=arm64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null

安装docker:


kubeadm安装:
kubeadm是Kubernetes官方提供的安装工具,可用于快速部署Kubernetes集群。
源码调试环境中,需要一个基准Kubernetes集群,后续再基于该集群需要调试的CMD用Debug版本替换。
安装kubeadm的步骤参考官方教程:https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
必要依赖,包括添加Google Cloud GPG key、添加Kubernetes deb源:
apt-get install -y apt-transport-https ca-certificates curl
curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | tee /etc/apt/sources.list.d/kubernetes.list
安装kubeadm:
我这里装的是1.20.5,可以指定版本来安装调试:
apt-get install kubeadm kubectl kubelet
kubeadm查看是否安装成功:

使用kubeadm安装k8s集群
kubeadm命令很简单,很容易便能搭建可用的Kubernete集群。
- 前提要关闭swap、防火墙等
swap是为了当内存不足时,linux会自动使用swap,将部分内存数据存放到磁盘中,这个这样会使性能下降,为了性能考虑推荐关掉。
free -h
swapoff -a
- 修改docker默认cgroup driver
vim /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"]
}
systemctl daemon-reload
systemctl restart docker
3.替换镜像
这时候可以使用kubeadm init
来初始化集群,你很有可能得到如下错误:
Error response from daemon: Get "https://k8s.gcr.io/v2/"

因为一些原因默认k8s.gcr.io访问不了,可以使用替换阿里云镜像打tag的方式来解决:
kubeadm config images list |sed -e 's/^/docker pull /g' -e 's#k8s.gcr.io#docker.io/mirrorgooglecontainers#g' |sh -x 下载需要的镜像
docker images |grep mirrorgooglecontainers |awk '{print "docker tag ",$1":"$2,$1":"$2}' |sed -e 's#mirrorgooglecontainers#k8s.gcr.io#2' |sh -x 重命名镜像
docker images |grep mirrorgooglecontainers |awk '{print "docker rmi ", $1":"$2}' |sh -x 删除mirrorgooglecontainers镜像
如果是较新版本也可以在init的时候指定--image-repository
参数为阿里云镜像,更简单。
- kubeadm安装时候的一些问题
1)遇到组件之间版本不匹配的问题
一般报错为
this version of kubeadm only supports deploying clusters with the control plane version >= 1.xxx.0. Current version: v1.xxx
这是因为你默认安装的时候是直接:
apt install kubelet kubeadm kubectl
这个命令确实能安装kubernetes的这3个组件,但是这有一个问题,没有指定kubelet kubeadm kubectl的版本,所以该命令一般会默认下载最新高版本的组件。与之类似的还有docker的安装命令apt install docker-ce ,这条命令会默认安装最新版的docker。
因为需要考虑各组件之间的版本兼容性,比如安装k8s的这3个组件的时候就要考虑k8s组件的版本是否与docker,Centos的版本相匹配,最好指定版本来避免这个问题。
所以可以先看kubeadm组件要求的镜像版本,安装时候尽量保持和自己docker仓库中镜像的版本一致
kubeadm config images list

2)提示kubelet有错误
This error is likely caused by:
- The kubelet is not running
- The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)

这个问题就是kubelet连接出错了,排错:
journalctl -xeu kubelet
结果定位到出错原因:
err="failed to run Kubelet: misconfiguration: kubelet cgroup driver: \"systemd\" is
different from docker cgroup driver: \"cgroupfs\""

其实还是kubelet cgroup driver跟docker cgroup driver的值不一致导致的启动失败,修改docker或者kubelet其中一个就行了。
编辑kubeadm-flags.env的cgroup值 修改为docker一样的值vim /var/lib/kubelet/kubeadm-flags.env

然后再重启:
systemctl daemon-reload
systemctl restart kubelet
kubeadm启动k8s集群成功:


工作节点加入集群
这里master是ok了,可以使用其他机器kueadm join加入集群,复制上面的带有token的命令加入即可:

go环境和远程调试环境
k8s集群环境算是部署完成,需要再来部署调试环境。
宿主机和集群机器安装go环境
wget -c https://go.dev/dl/go1.17.5.linux-arm64.tar.gz
tar -C /usr/local -xzf go1.17.5.linux-arm64.tar.gz
vim ~/.profile
export GOPATH=$PATH:/usr/local/go/
export PATH=PATH
PATH:GOPATH/go/bin
安装delve:
go install github.com/go-delve/delve/cmd/dlv@latest
dlv
安装完成:

最后一步调试源码:
宿主机拉取k8s源码然后同样也是git checkout到指定的版本:
$ mkdir -p $GOPATH/src/github.com/kubernetes
$ cd $GOPATH/src/github.com/kubernetes
$ git clone https://github.com/kubernetes/kubernetes.git
$ git check v1.20.5

编译包含debug信息的cmd
这步很重要,虽然k8s官方源码提供了一些能快速启动的脚本,但是默认情况下是去除了符号表(symbol table)的,Kubernetes v1.18.3在k8s.io/kubernetes/hack/lib/golang.sh中设置了-s -w选项来禁用符号表以及debug信息,因此在编译Kubernetes组件进行远程调试时需要去掉这两个限制,如下:
goldflags="${GOLDFLAGS:-} $(kube::version::ldflags)"
编译参数
KUBE_GIT_TREE_STATE=clean make all GOGCFLAGS="all=-N -l"

正常启动以后,就可以用dlv来启动,然后远程调试了,先把原本的kube-apiserver停了,因为默认的apiserver是static pod,kubelet会直接停止不在manifest目录下的static pod ,所以停掉它只需要:
mv /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/
停掉以后再来看启动apiserver的参数:
cat /etc/kubernetes/manifests/kube-apiserver.yaml

上面把启动的参数拼接到dlv中启动即可,端口为2345:
dlv exec --listen=:2345 --headless=true --api-version=2 _output/bin/kube-apiserver -- --advertise-address=10.211.55.10 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/etc/kubernetes/pki/ca.crt --enable-admission-plugins=NodeRestriction --enable-bootstrap-token-auth=true --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key --etcd-servers=https://127.0.0.1:2379 --insecure-port=0 --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key --requestheader-allowed-names=front-proxy-client --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-group-headers=X-Remote-Group --requestheader-username-headers=X-Remote-User --secure-port=6443 --service-account-issuer=https://kubernetes.default.svc.cluster.local --service-account-key-file=/etc/kubernetes/pki/sa.pub --service-account-signing-key-file=/etc/kubernetes/pki/sa.key --service-cluster-ip-range=10.96.0.0/12 --tls-cert-file=/etc/kubernetes/pki/apiserver.crt --tls-private-key-file=/etc/kubernetes/pki/apiserver.key

最以后一步宿主机使用goland连接远程调试的端口:

发现可以正常调试下断点了:

