1、构建方式

Kubernetes 构建方式可以分为 3 种,分别是本地环境构建、容器环境构建、Bazel环境构建。

1.1 本地环境

make 
make all

执行上述语句会编译 Kubernetes 的所有组件,组件二进制文件输出的相对路径是 _output/bin/。如果我们需要对 Makefile 的执行过程进行调试,可以在命令后面加 -n 参数,输出但不执行所有执行命令,这样可以展示更详细的构建过程。

如果只需要构建单个组件,需要添加 WHAT 参数指定:

make WHAT=cmd/kubectl
  • Makefile 文件

    使用自动化工具来构建和测试程序是一个良好的约束规范,在 Kubernetes 的源码根目录中,有两个与 Makefile 相关的文件:

    Makefile:描述了整个项目所有代码文件的编译顺序、编译规则及编译后的二进制输出等;

    Makefile.generated_files:描述了代码生成的逻辑。

下面是本地环境构建的 makefile 文件定义,执行命令会去调用 hack/make-rules/build.sh 脚本完成构建。

define ALL_HELP_INFO
# Build code.
endef
.PHONY: all
ifeq ($(PRINT_HELP),y)
all:
    echo "$$ALL_HELP_INFO"
else
all:
    hack/make-rules/build.sh $(WHAT)
endif

1.2 容器环境

make release
make quick-release

前者构建所有的目标平台(Darwin、Linux、Windows),构建过程会比较久,并同时执行单元测试过程;后者只构建当前平台,并略过单元测试过程。

define RELEASE_HELP_INFO
# Build a release
endef
.PHONY: release release-in-a-container
ifeq ($(PRINT_HELP),y)
release release-in-a-container:
    echo "$$RELEASE_HELP_INFO"
else
release release-in-a-container: KUBE_BUILD_CONFORMANCE = y
release:
    build/release.sh
release-in-a-container:
    build/release-in-a-container.sh
endif
​
define RELEASE_SKIP_TESTS_HELP_INFO
# Build a release, but skip tests
endef
.PHONY: release-skip-tests quick-release
ifeq ($(PRINT_HELP),y)
release-skip-tests quick-release:
    echo "$$RELEASE_SKIP_TESTS_HELP_INFO"
else
release-skip-tests quick-release: KUBE_RELEASE_RUN_TESTS = n # 将其设为 n 则跳过运行单元测试
release-skip-tests quick-release: KUBE_FASTBUILD = true # 将其设为 true 则跳过跨平台交叉编译
release-skip-tests quick-release:
    build/release.sh
endif

整个构建过程主要分为五步:

  1. verify_prereqs

    进行构建环境的配置及验证。该过程会检查本机是否安装了 Docker 容器环境,而对于 Darwin(一种类 Unix 作业系统,是 macOS 和 iOS 操作环境的作业系统部份)平台,该过程会检查本机是否安装了 docker-machine 环境。

  2. build_image

    使用 build/build-image/Dockerfile 文件来构建镜像。

    # Set up the context directory for the kube-build image and build it.
    function kube::build::build_image() {
      # 创建构建镜像的文件夹
      mkdir -p "${LOCAL_OUTPUT_BUILD_CONTEXT}"
      # Make sure the context directory owned by the right user for syncing sources to container.
      chown -R "${USER_ID}":"${GROUP_ID}" "${LOCAL_OUTPUT_BUILD_CONTEXT}"
    ​
      cp /etc/localtime "${LOCAL_OUTPUT_BUILD_CONTEXT}/"
      chmod u+w "${LOCAL_OUTPUT_BUILD_CONTEXT}/localtime"
      # 复制构建所需的文件信息
      cp "${KUBE_ROOT}/build/build-image/Dockerfile" "${LOCAL_OUTPUT_BUILD_CONTEXT}/Dockerfile"
      cp "${KUBE_ROOT}/build/build-image/rsyncd.sh" "${LOCAL_OUTPUT_BUILD_CONTEXT}/"
      dd if=/dev/urandom bs=512 count=1 2>/dev/null | LC_ALL=C tr -dc 'A-Za-z0-9' | dd bs=32 count=1 2>/dev/null > "${LOCAL_OUTPUT_BUILD_CONTEXT}/rsyncd.password"
      chmod go= "${LOCAL_OUTPUT_BUILD_CONTEXT}/rsyncd.password"
      # 构建容器镜像
      kube::build::docker_build "${KUBE_BUILD_IMAGE}" "${LOCAL_OUTPUT_BUILD_CONTEXT}" 'false' "--build-arg=KUBE_CROSS_IMAGE=${KUBE_CROSS_IMAGE} --build-arg=KUBE_CROSS_VERSION=${KUBE_CROSS_VERSION}"
    ​
      # Clean up old versions of everything
      kube::build::docker_delete_old_containers "${KUBE_BUILD_CONTAINER_NAME_BASE}" "${KUBE_BUILD_CONTAINER_NAME}"
      kube::build::docker_delete_old_containers "${KUBE_RSYNC_CONTAINER_NAME_BASE}" "${KUBE_RSYNC_CONTAINER_NAME}"
      kube::build::docker_delete_old_containers "${KUBE_DATA_CONTAINER_NAME_BASE}" "${KUBE_DATA_CONTAINER_NAME}"
      kube::build::docker_delete_old_images "${KUBE_BUILD_IMAGE_REPO}" "${KUBE_BUILD_IMAGE_TAG_BASE}" "${KUBE_BUILD_IMAGE_TAG}"
      # 运行存储容器并挂载卷
      kube::build::ensure_data_container
      # ,运行同步容器并挂载存储容器的卷,然后通过 rsync 命令同步 Kubernetes 源码到存储容器的卷中
      kube::build::sync_to_container
    }
  3. kube::build::run_build_command make cross

    运行构建容器并在构建容器内部执行构建 Kubernetes 源码的操作。

  4. copy_output

    使用同步容器,将编译后的代码文件复制到主机上。

  5. package_tarballs

    最终,代码文件以 tar.gz 压缩包的形式输出至 _output/release-tars 文件夹。

1.3 Bazel环境

Bazel 是 Google 公司开源的一个自动化软件构建和测试工具,其使用分布式缓存和增量构建方法,使构建更加快速,并且支持包括运行编译器和链接器以生成可执行程序和库。Bazel 在构建速度、可扩展性、灵活性及跨语言和对不同平台的支持上比其他例如 maven 等构建工具更加出色。

2、代码生成器

# TODO(thockin): Remove this in v1.29.
.PHONY: generated_files
generated_files:
    echo "'make generated_files' is deprecated.  Please use hack/update-codegen.sh instead."
ifneq ($(PRINT_HELP), y)
    false
endif

update-codegen 文件中有 13 个 codegen:: 开头的方法,用于代码的生成,其中 codegen::conversionscodegen::deepcopycodegen::defaultscodegen::openapi 用于源码的构建。

2.1 Tags

代码生成器通过 Tags 来识别一个包是否需要生成代码及确定生成代码的方式,Kubernetes 提供的 Tags 可以分为如下两种:

  • 全局:定义在每个包的 doc.go(pkg/apis/<group>/<version>/doc.go) 文件中,对整个包中的类型自动生成代码。

    // +k8s:deepcopy-gen=package
    package core // import "k8s.io/kubernetes/pkg/apis/core"
  • 局部:定义在 Go 语言的类型声明上方(要与类型声明至少空格一行)。

    // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 为 PersistentVolumeList 生成返回类型为 runtime.Object 的 DeepCopyObject 函数
    ​
    // PersistentVolumeList represents a list of PVs
    type PersistentVolumeList struct {
        metav1.TypeMeta
        // +optional
        metav1.ListMeta
        Items []PersistentVolume
    }

2.2 deepcopy-gen

一个自动生成 DeepCopy 函数的代码生成器。给定一个包的目录路径作为输入源,它可以为其生成 DeepCopy 相关函数,这些函数可以有效地执行每种类型的深复制操作。

deepcopy-gen /
--v 2 / # 日志等级
--bounding-dirs . / # 依赖的包并为其生成深复制的类型
-i github.com/lt90s/deepcopy-gen-demo/types / # 需要进行代码生成的包
-O zz_generated.deepcopy # 生成的文件名称

2.3 defaulter-gen

一个自动生成 Defaulter 函数的代码生成器。给定一个包的目录路径作为输入源,它可以为其生成 Defaulter 相关函数,这些函数可以为资源对象生成默认值(初始值)。属于全局 Tags,没有局部 Tags,其值可以为 TypeMeta 等类型,表示为当前类型生成初始值。

# // +k8s:defaulter-gen=TypeMeta
defaulter-gen /
--v 2 / # 日志等级
--bounding-dirs . / # 依赖的包并为其生成深复制的类型
-i github.com/lt90s/deepcopy-gen-demo/types / # 需要进行代码生成的包
-O zz_generated.defaults # 生成的文件名称

2.4 conversion-gen

一个自动生成 Convert 函数的代码生成器。给定一个包的目录路径作为输入源,它可以为其生成 Convert 相关函数,这些函数可以为对象在内部和外部类型之间提供转换函数。例如从v1beta1转换为internal内部版本,从internal内部版本转换为v1版本。

// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/core 转换包
// +k8s:conversion-gen-external-types=k8s.io/api/core/v1 目标包

2.5 openapi-gen

一个自动生成 OpenAPI 定义文件(OpenAPI Definition File)的代码生成器,给定一个包的目录路径作为输入源,它可以为其生成 OpenAPI 定义文件,该文件用于 kube-apiserver 服务上的 OpenAPI 规范的生成。

3、gengo

Kubernetes 的代码生成器都是在 k8s.io/gengo 包的基础上实现的,通过一个输入包路径(--input-dirs)参数,根据 gengo 的词法分析、抽象语法树等操作,最终生成代码并输出(--output-file-base)。通过 tree 命令可以很好的查看目录结构(需要注意的是使用 apt 安装而非 snap 安装,不然会导致切出用户目录报错无法使用)。

tree vendor/k8s.io/gengo -L 1
vendor/k8s.io/gengo
├── args # 代码生成器的通用命令行参数
├── examples # 包含 deepcopy-gen、defaulter-gen 等代码生成器的生成逻辑
├── generator # 代码生成器通用接口Generator
├── LICENSE
├── namer # 命名管理,支持创建不同类型的名称
├── parser # 代码解析器,用来构造抽象语法树
└── types # 类型系统,用于数据类型的定义及类型检查算法的实现

gengo生成流程.png

如上,其生成逻辑分为五步,下面逐个分析。

3.1 Gather The Info

GO 语言标准库提供了 go/build 工具,该工具支持 Go 语言的构建标签机制来构建约束条件。类似于 //+build linux darwin 的包注释信息,这就是 Go 语言编译时的约束条件,其也被称为条件编译。

  • 构建标签:在源码里添加注释信息,比如 //+build linux,表示源码文件只在 Linux 平台上才会被编译;

  • 文件后缀:改变代码文件的后缀,比如 foo_linux.go,该后缀决定了源码文件只在 Linux 平台上才会被编译。

该过程就是通过指定的包路径,将代码文件全部加载进内存当中。

3.2 Lexer/Parser

3.3 AST Generator

3.4 Type Checker

3.5 Code Generation