mac 上学习k8s系列(13)编译源码阅读

2022-08-02 19:29:35 浏览数 (1)

看到k8s的源码,第一感觉是无从下手,量太大。其实看懂k8s的源码,前提是你对go比较熟悉,另外bash能看懂,源码编译用到了大量bash脚本。首先我们看下源码结构,主要的目录可以分为三类:

  1. 文档类:api、docs、logo
  2. 工具类(build、cluster、Godeps、hack、staging、translations)工具类主要用到的build目录下的文件,自己动手编译的时候会用到
  3. 代码类(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环境上进行编译

0 人点赞