随着Cloud Native逐渐深入人心以及kubernetes的流行,国内外出现大量的kubernetes服务提供商,如红帽,阿里,腾讯等, 同时许多互联网公司也在定制kubernetes以满足自身需求,kubernetes平台开发者这个岗位的需求也逐渐增大, 那作为一名合格的kubernetes平台开发者其实需要具备一些特殊技能的。

在这个系列博文中,我将结合自身的开发经历给Kubernetes平台开发者分享一些开发小技巧,帮助kubernetes平台开发者少走一些弯路。

在你的项目使用依赖k8s.io/kubernetes主仓模块

Kubernetes提供了很多公共库供开发者使用, 比如client-goapimachinery,但是官方不推荐直接依赖主仓k8s.io/kubernetes,虽然其代码完全是开源的, 这样做最主要原因是直接依赖k8s.io/kubernetes会导致你的项目过大,包含太多不必要的文件。

但是除了公共库已经拆分的通用模块,主仓还有很多有意义的API接口(函数),比如包pkg/core/validationValidatePodCreate函数可以用来创建POD时, 做前校验处理(cli-runtime库定义了ContentValidator接口,建议使用该接口和dry-run机制),包pkg/apis/core/v1Convert_v1_Pod_To_Core_Pod函数可以用来将corev1.Pod转换为core.Pod等。 这些包目前还没有被抽离成单独模块。

如果我们直接go get k8s.io/kubernetes@v1.19.2下载依赖,会出现以下错误:

1
2
3
4
➜  go-get-kubernetes go get k8s.io/kubernetes
go: k8s.io/kubernetes upgrade => v1.19.3
go get: k8s.io/kubernetes@v1.19.3 requires
k8s.io/api@v0.0.0: reading k8s.io/api/go.mod at revision v0.0.0: unknown revision v0.0.0

错误的原因是在kubernetes主仓中,也使用了公共库,不过go.mod文件中所有公共库版本都指定为了v0.0.0(显然这个版本不存在), 然后通过Go Module的replace机制,将版本替换为子目录./staging/src/k8s.io对应的依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module k8s.io/kubernetes
go 1.15
require (
...
k8s.io/api v0.0.0
k8s.io/apiextensions-apiserver v0.0.0
k8s.io/apimachinery v0.0.0
k8s.io/apiserver v0.0.0
k8s.io/cli-runtime v0.0.0
k8s.io/client-go v0.0.0
...
)

replace(
...
k8s.io/client-go => ./staging/src/k8s.io/client-go
k8s.io/cloud-provider => ./staging/src/k8s.io/cloud-provider
k8s.io/cluster-bootstrap => ./staging/src/k8s.io/cluster-bootstrap
k8s.io/code-generator => ./staging/src/k8s.io/code-generator
k8s.io/component-base => ./staging/src/k8s.io/component-base
...
)

那么如何解决呢,只需要复制保存以下脚本(来自社区相关Issue),在项目中执行以下脚本:bash hack/go-get-kubernetes.sh v1.19.2, 注意替换你需要的版本。这个脚本通过修改go.mod文件保证能够获取相关依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh
set -euo pipefail

VERSION=${1#"v"}
if [ -z "$VERSION" ]; then
echo "Must specify version!"
exit 1
fi
MODS=($(
curl -sS https://raw.githubusercontent.com/kubernetes/kubernetes/v${VERSION}/go.mod |
sed -n 's|.*k8s.io/\(.*\) => ./staging/src/k8s.io/.*|k8s.io/\1|p'
))
for MOD in "${MODS[@]}"; do
V=$(
go mod download -json "${MOD}@kubernetes-${VERSION}" |
sed -n 's|.*"Version": "\(.*\)".*|\1|p'
)
go mod edit "-replace=${MOD}=${MOD}@${V}"
done
go get "k8s.io/kubernetes@v${VERSION}"

不过建议大家不要直接依赖主仓

Goland如何调试Kubernetes相关组件

学会调试kubernetes,对于我们学习kubernetes源码及定制化kubernetes十分有帮助,其实刚开始接触kubernetes项目,我和许多开发者一样, 面对kubernetes这一复杂庞大的项目,不知道从何看起,也不知道如何调试它(当然Kubectl除外),其实kubernetes的本地调试并没有想象中的复杂。

首先假设你已经在虚拟机通过kubeadm安装了一套kubernetes集群,克隆了kubernetes代码仓到本地和虚拟机上。 这里我以APIServer组件为例,其他组件类似。

首先,查看安装Kubernetes集群的版本,并在虚机上checkout对应版本的代码仓分支,然后编译Kubernetes相关组件, 编译时需要打开调试选项(参照Makefile的说明),这个编译时间可能有点长,对应的二进制产物在_output/bin目录中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 编译
➜ kubernetes git:(release-1.18) ✗ make all GOGCFLAGS=”-N -l”
➜ kubernetes git:(release-1.18) ✗ cd _output/bin
➜ bin git:(release-1.18) ✗ ll
总用量 1.8G
-rwxr-xr-x 1 root root 65M 9月 11 11:20 apiextensions-apiserver
-rwxr-xr-x 1 root root 6.0M 9月 10 16:56 conversion-gen
-rwxr-xr-x 1 root root 6.0M 9月 10 16:56 deepcopy-gen
-rwxr-xr-x 1 root root 6.0M 9月 10 16:56 defaulter-gen
-rwxr-xr-x 1 root root 172M 9月 11 11:21 e2e_node.test
-rwxr-xr-x 1 root root 157M 9月 11 11:21 e2e.test
-rwxr-xr-x 1 root root 59M 9月 11 11:21 gendocs
-rwxr-xr-x 1 root root 192M 9月 11 11:21 genkubedocs
-rwxr-xr-x 1 root root 200M 9月 11 11:21 genman
-rwxr-xr-x 1 root root 9.4M 9月 11 11:21 genswaggertypedocs
-rwxr-xr-x 1 root root 59M 9月 11 11:21 genyaml
-rwxr-xr-x 1 root root 11M 9月 11 11:21 ginkgo
-rwxr-xr-x 1 root root 3.6M 9月 10 16:07 go2make
-rwxr-xr-x 1 root root 2.0M 9月 10 16:57 go-bindata
-rwxr-xr-x 1 root root 2.8M 9月 11 11:21 go-runner
-rwxr-xr-x 1 root root 55M 9月 11 11:20 kubeadm
-rwxr-xr-x 1 root root 149M 9月 11 11:20 kube-apiserver
-rwxr-xr-x 1 root root 141M 9月 11 11:20 kube-controller-manager
-rwxr-xr-x 1 root root 60M 9月 11 11:20 kubectl
-rwxr-xr-x 1 root root 142M 9月 11 11:21 kubelet
-rwxr-xr-x 1 root root 139M 9月 11 11:21 kubemark
-rwxr-xr-x 1 root root 52M 9月 11 11:20 kube-proxy
-rwxr-xr-x 1 root root 59M 9月 11 11:20 kube-scheduler
-rwxr-xr-x 1 root root 7.2M 9月 11 11:21 linkcheck
-rwxr-xr-x 1 root root 2.3M 9月 11 11:20 mounter
-rwxr-xr-x 1 root root 9.9M 9月 10 16:56 openapi-gen
-rwxr-xr-x 1 root root 5.7M 9月 10 16:07 prerelease-lifecycle-gen

调试需要使用到Go调试工具delve,在kubernetes集群的控制节点上安装delve, 这里使用go get的方式安装。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 在Module目录外执行,并将GOBIN目录添加环境变量PATH中
➜ ~ go get github.com/go-delve/delve/cmd/dlv
➜ bin git:(release-1.18) ✗ dlv -h
Delve is a source level debugger for Go programs.

Delve enables you to interact with your program by controlling the execution of the process,
evaluating variables, and providing information of thread / goroutine state, CPU register state and more.

The goal of this tool is to provide a simple yet powerful interface for debugging Go programs.

Pass flags to the program you are debugging using `--`, for example:

`dlv exec ./hello -- server --config conf/config.toml`

通过Kubeadm安装的集群,kubernetes控制平面的组件是以Static pod形式运行的, 对应的yaml文件保存在/etc/kubernetes/manifests,查看APIServer对应的配置文件如下,我们需要拷贝APIServer的启动参数。 接下来,我们重命名/etc/kubernetes/manifests/kube-apiserver.yamlkube-apiserver.yaml.old,观察容器列表, 等待APIServer对应的容器停止,通过delve启动APIServer进行调试,注意相关参数配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
dlv exec _output/bin/kube-apiserver --headless -l 192.168.5.82:2345 --api-version=2 \\
--advertise-address=192.168.5.82 \\
--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-key-file=/etc/kubernetes/pki/sa.pub\\
--service-cluster-ip-range=10.96.0.0/16\\
--tls-cert-file=/etc/kubernetes/pki/apiserver.crt\\
--tls-private-key-file=/etc/kubernetes/pki/apiserver.key
server listening at: 192.168.5.82:2345

在本地开发环境也切到与虚机对应的kubernetes分支,然后添加一个远程调试的Configuration,配置对应的主机和端口,并在相关位置打上断点,开始调试。

到这里,就已经成功对Kubernetes的APIServer进行调试啦。