看到k8s的源码,第一感觉是无从下手,量太大。其实看懂k8s的源码,前提是你对go比较熟悉,另外bash能看懂,源码编译用到了大量bash脚本。首先我们看下源码结构,主要的目录可以分为三类:
- 文档类:api、docs、logo
- 工具类(build、cluster、Godeps、hack、staging、translations)工具类主要用到的build目录下的文件,自己动手编译的时候会用到
- 代码类(cmd、pkg、plugin、test、third_party)核心代码集中在cmd和pkg中。
- cmd内部包含各个组件的入口,具体核心的实现部分在pkg目录下
- plugin目录之前的版本包括scheduler部分的代码,plugin主要包含的是认证与鉴权部分的代码。
源码编译可以有两种方式:容器编译和主机编译。主机编译和主机编译的差别在于,容器编译会先拉取编译用的镜像,启动一个容器内的编译环境,然后在容器内部运行主机编译的脚本。
主机编译
主机编译需要安装基本环境(Go、gcc)
- 使用kubernetes自带的Makefile,使用make即可编译
- 对不同的模块可以进行单独的编译,例如编译kubelet:make all WHAT=cmd/kubelet GOFLAGS=-v命令编译kubelet模块
- 编译生成的二进制包在_output/bin目录下
配置编译环境:
老版本需要将下载好的kubernetes源码拷贝到GOPATH/src/k8s.io目录,进入GOPATH/src/k8s.io/kubernetes目录,GOPATH为/root/kube,新版本使用了go module,直接编译就行。
尝试编译kubelet:
代码语言:javascript复制 make all WHAT=cmd/kubelet GOFLAGS=-v
报错了:
代码语言:javascript复制This script requires a minimum bash version of 4.2, but got version of 3.2
直接brew install bash会失败,arm不支持,需要配置iterm 的rossta环境。
代码语言:javascript复制 %arch -x86_64 brew install bash
然后make
代码语言:javascript复制 Kubernetes requires go1.17.0 or greater.
我本机的go 是1.16.2,查找对应版本的分支
代码语言:javascript复制 git pull origin release-1.21:release-1.21 --depth=1
git checkout release-1.21
make all WHAT=cmd/kubelet GOFLAGS=-v
终于编译成功了,看下生成的文件
代码语言:javascript复制% ls _output/local/bin/darwin/arm64/
conversion-gen defaulter-gen
go2make openapi-gen
deepcopy-gen go-bindata
kubelet prerelease-lifecycle-gen
如果直接执行make 生成所有文件如下:
代码语言:javascript复制ls _output/bin
apiextensions-apiserver genman kube-aggregator kubectl-convert
conversion-gen genswaggertypedocs kube-apiserver kubelet
deepcopy-gen genyaml kube-controller-manager kubemark
defaulter-gen ginkgo kube-proxy linkcheck
e2e.test go-bindata kube-scheduler mounter
gendocs go-runner kubeadm openapi-gen
genkubedocs go2make kubectl prerelease-lifecycle-gen
先看下Makefile
源码位置:kubernetes/Makefile
首先设置了输出文件的地址
代码语言:javascript复制OUT_DIR ?= _output
BIN_DIR := $(OUT_DIR)/bin
make执行的指令是
代码语言:javascript复制all: generated_files
hack/make-rules/build.sh $(WHAT)
Makefile还有一些其他指令可以自行研究:
代码语言:javascript复制 ginkgo:
hack/make-rules/build.sh vendor/github.com/onsi/ginkgo/ginkgo
verify:
KUBE_VERIFY_GIT_BRANCH=$(BRANCH) hack/make-rules/verify.sh
update: generated_files
CALLED_FROM_MAIN_MAKEFILE=1 hack/make-rules/update.sh
check test: generated_files
hack/make-rules/test.sh $(WHAT) $(TESTS)
test-integration: generated_files
hack/make-rules/test-integration.sh $(WHAT)
test-e2e-node: ginkgo generated_files
hack/make-rules/test-e2e-node.sh
test-e2e-kubeadm:
hack/make-rules/test-e2e-kubeadm.sh
test-cmd: generated_files
hack/make-rules/test-cmd.sh
clean: clean_meta
build/make-clean.sh
hack/make-rules/clean.sh
clean_meta:
rm -rf $(META_DIR)
clean_generated:
find . -type f -name '$(GENERATED_FILE_PREFIX)*' | xargs rm -f
vet: generated_files
CALLED_FROM_MAIN_MAKEFILE=1 hack/make-rules/vet.sh $(WHAT)
release-in-a-container:
build/release-in-a-container.sh
release-images:
build/release-images.sh
release-skip-tests quick-release:
build/release.sh
quick-release-images:
build/release-images.sh
package package-tarballs:
build/package-tarballs.sh
else
hack/make-rules/cross.sh
$(filter-out %$(EXCLUDE_TARGET),$(notdir $(abspath $(wildcard cmd/*/)))): generated_files
hack/make-rules/build.sh cmd/$@
generated_files gen_openapi:
$(MAKE) -f Makefile.generated_files $@ CALLED_FROM_MAIN_MAKEFILE=1
help:
hack/make-rules/make-help.sh
1,首先看下generated_files指令
代码语言:javascript复制generated_files gen_openapi:
$(MAKE) -f Makefile.generated_files $@ CALLED_FROM_MAIN_MAKEFILE=1
执行子文件:kubernetes/Makefile.generated_files
主要是完成openapi 和香港文档的生成工作:
代码语言:javascript复制 .PHONY: generated_files
generated_files: gen_prerelease_lifecycle gen_deepcopy gen_defaulter gen_conversion gen_openapi gen_bindata
gen_openapi: $(OPENAPI_GEN) $(KUBE_OPENAPI_OUTFILE) $(AGGREGATOR_OPENAPI_OUTFILE) $(APIEXTENSIONS_OPENAPI_OUTFILE) $(CODEGEN_OPENAPI_OUTFILE) $(SAMPLEAPISERVER_OPENAPI_OUTFILE)
OPENAPI_GEN := $(BIN_DIR)/openapi-gen
2,然后看下 kubernetes/hack/make-rules/build.sh
这个脚本主要完成编译工作:
代码语言:javascript复制 KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/../..
KUBE_VERBOSE="${KUBE_VERBOSE:-1}"
source "${KUBE_ROOT}/hack/lib/init.sh"
kube::golang::build_binaries "$@"
kube::golang::place_bins
先初始化编译环境,设置环境变量,加载依赖脚本kubernetes/hack/lib/init.sh
代码语言:javascript复制 export GO111MODULE=off
source "${KUBE_ROOT}/hack/lib/util.sh"
source "${KUBE_ROOT}/hack/lib/logging.sh"
source "${KUBE_ROOT}/hack/lib/version.sh"
source "${KUBE_ROOT}/hack/lib/golang.sh"
source "${KUBE_ROOT}/hack/lib/etcd.sh"
# list of all available group versions. This should be used when generated code
# or when starting an API server that you want to have everything.
# most preferred version for a group should appear first
KUBE_AVAILABLE_GROUP_VERSIONS="${KUBE_AVAILABLE_GROUP_VERSIONS:-
kubernetes/hack/lib/golang.sh这个脚本完成主要的编译工作,这里会检查支持的系统和对应的cpu架构,可以看到不支持mac M1,所以我们能编译成功,但是运行不了
代码语言:javascript复制readonly KUBE_SUPPORTED_SERVER_PLATFORMS=(
linux/amd64
linux/arm
linux/arm64
linux/s390x
linux/ppc64le
)
readonly KUBE_SUPPORTED_NODE_PLATFORMS=(
linux/amd64
linux/arm
linux/arm64
linux/s390x
linux/ppc64le
windows/amd64
)
readonly KUBE_SUPPORTED_CLIENT_PLATFORMS=(
linux/amd64
linux/386
linux/arm
linux/arm64
linux/s390x
linux/ppc64le
darwin/amd64
darwin/arm64
windows/amd64
windows/386
)
然后是我们的编译目标:
代码语言:javascript复制kube::golang::server_targets() {
local targets=(
cmd/kube-proxy
cmd/kube-apiserver
cmd/kube-controller-manager
cmd/kubelet
cmd/kubeadm
cmd/kube-scheduler
vendor/k8s.io/kube-aggregator
vendor/k8s.io/apiextensions-apiserver
cluster/gce/gci/mounter
)
echo "${targets[@]}"
}
代码语言:javascript复制readonly KUBE_ALL_TARGETS=(
"${KUBE_SERVER_TARGETS[@]}"
"${KUBE_CLIENT_TARGETS[@]}"
"${KUBE_TEST_TARGETS[@]}"
"${KUBE_TEST_SERVER_TARGETS[@]}"
)
IFS=" " read -ra KUBE_SERVER_TARGETS <<< "$(kube::golang::server_targets)"
readonly KUBE_SERVER_TARGETS
readonly KUBE_SERVER_BINARIES=("${KUBE_SERVER_TARGETS[@]##*/}")
readonly KUBE_CLIENT_TARGETS=(
cmd/kubectl
cmd/kubectl-convert
)
readonly KUBE_CLIENT_BINARIES=("${KUBE_CLIENT_TARGETS[@]##*/}")
readonly KUBE_CLIENT_BINARIES_WIN=("${KUBE_CLIENT_BINARIES[@]/%/.exe}")
A,然后我们看下完成编译的函数
代码语言:javascript复制kube::golang::build_binaries() {
kube::golang::setup_env
if [[ ${#targets[@]} -eq 0 ]]; then
targets=("${KUBE_ALL_TARGETS[@]}")
fi
kube::golang::build_binaries_for_platform "${platform}"
先设置好环境变量,然后 通过下面函数获取目标
代码语言:javascript复制kube::golang::server_targets()
编译对应目标平台版本的二进制。
代码语言:javascript复制kube::golang::build_binaries_for_platform() {
build_args=(
-installsuffix static
${goflags: "${goflags[@]}"}
-gcflags "${gogcflags:-}"
-asmflags "${goasmflags:-}"
-ldflags "${goldflags:-}"
-tags "${gotags:-}"
)
CGO_ENABLED=0 kube::golang::build_some_binaries "${statics[@]}"
我们可以看到最终调用了go install 完成了最终的编译
代码语言:javascript复制kube::golang::build_some_binaries() {
for package in "$@"; do
go test -c -o "$(kube::golang::outfile_for_binary "${package}" "${platform}")"
-covermode count
-coverpkg k8s.io/...,k8s.io/kubernetes/vendor/k8s.io/...
"${build_args[@]}"
-tags coverage
"${package}"
uncovered =("${package}")
go install "${build_args[@]}" "$@"
B,最后把二进制拷贝到目标位置
代码语言:javascript复制kube::golang::place_bins() {
容器编译
代码语言:javascript复制make quick-release
或者
执行./build/run.sh hack/build-go.sh cmd/kubelet单独编译kubelet模块
1,
看Makefile对应指令
代码语言:javascript复制release-skip-tests quick-release:
build/release.sh
看下kubernetes/build/release.sh对应源码
代码语言:javascript复制kube::build::verify_prereqs
kube::build::build_image
kube::build::run_build_command make cross
在kubernetes/build/common.sh里
代码语言:javascript复制function kube::build::build_image()
cp "${KUBE_ROOT}/build/build-image/Dockerfile" "${LOCAL_OUTPUT_BUILD_CONTEXT}/Dockerfile"
kube::build::docker_build "${KUBE_BUILD_IMAGE}" "${LOCAL_OUTPUT_BUILD_CONTEXT}" 'false' "--build-arg=KUBE_BUILD_IMAGE_CROSS_TAG=${KUBE_BUILD_IMAGE_CROSS_TAG} --build-arg=KUBE_BASE_IMAGE_REGISTRY=${KUBE_BASE_IMAGE_REGISTRY}"
最终调用的是docker build 命令完成镜像的编译
代码语言:javascript复制function kube::build::docker_build()
local -ra build_cmd=("${DOCKER[@]}" build -t "${image}" "--pull=${pull}" "${build_args[@]}" "${context_dir}")
然后执行代码的编译
代码语言:javascript复制function kube::build::run_build_command()
kube::build::run_build_command_ex "${KUBE_BUILD_CONTAINER_NAME}" -- "$@"
代码语言:javascript复制function kube::build::run_build_command_ex()
local -ra docker_cmd=("${DOCKER[@]}" run "${docker_run_opts[@]}" "${KUBE_BUILD_IMAGE}")
2,kubernetes/build/run.sh和kubernetes/build/run.sh差不多
代码语言:javascript复制source "$KUBE_ROOT/build/common.sh"
kube::build::verify_prereqs
kube::build::build_image
kube::build::run_build_command "$@"
既然是通过镜像编译,我们看下Dockerfile:build/build-image/Dockerfile
基础镜像是
代码语言:javascript复制FROM ${KUBE_BASE_IMAGE_REGISTRY}/kube-cross:${KUBE_BUILD_IMAGE_CROSS_TAG}
我们需要下载镜像然后和重新打tag,目前M1上没有成功
代码语言:javascript复制1、docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-cross:v1.11.5-1
2、docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-cross:v1.11.5-1 k8s.gcr.io/kube-cross:v1.11.5-1
Dockerfile里执行的脚本
代码语言:javascript复制ADD rsyncd.sh /
RUN chmod a rx /rsyncd.sh
位置:build/build-image/rsyncd.sh
设置好docker环境后执行 make cross 命令在docker环境上进行编译