1. kubernetes 构建方式

1.1 构建方式简介

Kubernetes构建方式可以分为3种:

  1. 本地环境构建(make, make all)
  2. 容器环境构建(make release, make quick-release)
  3. Bazel环境构建(make bazel-build)
    • Bazel 其实是一个构建工具,根maven、gradle这些构建工具类似。

在 kubernetes 的根目录下,有两个 Makefile 文件,分别是:

  • Makefile:顶层 Makefile 文件,描述了整个项目所有代码文件的编译顺序、编译规则及编译后的二进制输出等。
  • Makefile.generated_files:描述了代码生成的逻辑。

注意到,根目录下的这两个文件是一个软连接,连接到:

  • build/root/Makefile
  • build/root/Makefile.generated_files

1.1 构建方式 Makefile 过程分析

上图比较形象的把 Makefile 的各个依赖过程,以及主要的执行指令以流程图的方式展现出来。下面独立的方框是 prerelease-lifestyle-gen 代码生成器的构建过程,其他五个代码生成器的过程与这个相同,我就没有在画。可以对照这个流程图与后面的 Makefile 和 构建脚本的源码一同分析。

2. 本地环境构建

本地环境构建比较简单,我这里在 ubuntu server 上执行的构建命令。这里 make 或者 make all 有一些可选的参数:

  • 可以在 make 命令后面加 -n 参数,输出但不执行所有执行命令,这样可以展示更详细的构建过程。
  • 如果需要单独构建某一个组件,则需要指定WHAT参数,例如:make WHAT=cmd/kubectl

下面是我直接 make all 的结果:

Copy to Clipboard

执行 make 或 make all 命令,会编译 Kubernetes 的所有组件,组件二进制文件输出的相对路径是 _output/bin/。使用 make clean 会清理构建环境

Copy to Clipboard

2.1 make all 源码分析( Makefile 文件)

2.2 make generated_files 源码分析(Makefile.generated_files文件)

Makefile.generated_files 文件是通过 Main Makefile 中的 target : generated_files 调用的,总体的依赖顺序为:

  1. make all(Makefile) ->
  2. make  generated_files(Makefile) ->
  3. make generated_files(Makefile.generated_files) ->
  4. make {gen_prerelease_lifecycle gen_deepcopy gen_defaulter gen_conversion gen_openapi gen_bindata}(Makefile.generated_files)

总结 :generated_files (Makefile.generated_files文件)目标的处理过程是:

  • 先处理依赖的六个 target
  • 然后利用 go2make 工具生成 .make/go-pkgdeps.mk 文件
  • 将 go-pkgdeps.mk 文件直接引入

下面介绍这六个依赖的 target,分别是:

  • gen_prerelease_lifecycle
  • gen_deepcopy
  • gen_defaulter
  • gen_conversion
  • gen_openapi
  • gen_bindata

这六个依赖的 target 其实就是自动生成对应的文件。

2.3 代码生成器(Makefile.generated_files文件)

kubernetes 项目中一共有六个代码生成器,分别是:

  • conversion-gen : 自动生成 Convert 函数的代码生成器,用于资源对象的版本转换函数。
  • deepcopy-gen : 自动生成 DeepCopy 函数的代码生成器,用于资源对象的深复制函数
  • defaulter-gen : 自动生成 Defaulter 函数的代码生成器,用于资源对象的深复制函数
  • openapi-gen :  自动生成 OpenAPI 定义文件(OpenAPI Definition File)的代码生成器
  • prerelease-lifecycle-gen : 自动生成 api-status.csv文件的工具,将为所有 beta API 创建一个 zz_api_status.go文件,该文件指示种类、引入的版本、不推荐使用的版本、将删除的版本。
  • go-bindata : 是一个第三方工具。它能够将静态资源嵌入 Go 语言中,例如,在 Web 开发中将静态的 HTML、Javascript 等静态文件嵌入 Go 语言代码中并提供一些操作方法。

这些代码生成器工具,在 make all 编译后,都会生成在 _output/bin/ 目录下。这些文件都是 Makefile.generated_files文件中定义的 target 目标文件。

Tags:

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

  • 全局Tags:定义在每个包的 doc.go文件中,对整个包中的类型自动生成代码。例如:pkg/kubelet/apis/config/v1beta1/doc.go 文件中,标记了:// +k8s:deepcopy-gen=package 、// +k8s:defaulter-gen=TypeMeta 等等。
  • 局部Tags:定义在 Go 语言的类型声明上方,只对指定的类型自动生成代码。例如:vendor/k8s.io/kubelet/config/v1beta1/types.go 文件中,在定义结构体上方,标记了:// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

Tags 格式:

Tags的规则被定义在注释中,通常为:

  • //+tag-name 或
  • //+tag-name=value

其中,tag-name 可以为各个代码生成器的名称,例如 deepcopy-gen、defaulter-gen 等,还可以为 groupName , groupName 表示资源组名称,资源组名称一般使用域名形式命名。

Tags 位置:

Tags的位置,

  • 局部 Tags 一般定义在类型声明的上方,但如果该类型有注释信息,则局部 Tags 的定义需要与类型声明的注释信息之间至少有一个空行。
  • 全局 Tags 一般定义在 doc.go文件中。

下面就介绍一下 各个生成器在 编译时是如何生成的,如何运行的(Makefile.generated_files文件)。下面这幅图比较清楚的展示了 makefile 各个阶段的依赖关系,大家可以对照这幅图,来看下面的代码。

2.3.1. 寻找所有带有 // +k8s: 的Tags的文件列表

2.3.2. prerelease-lifecycle 代码生成器

下面简单介绍一下 prerelease-lifecycle-gen 代码生成器

  1. prerelease-lifecycle-gen 是一个自动生成 zz_generated.prerelease-lifecycle.go 文件的代码生成器。
  2. zz_generated.prerelease-lifecycle.go 这个文件中主要包括一些 API prerelease-lifecycle 代码,我的理解就是 API 的生命周期,例如:对于 certificates/v1beta1 API,这个 API 接口是从哪一个版本中出现的,将要在哪个版本中丢弃。
  3. 生成 prerelease-lifecycle 相关函数时,其 Tags 形式可以为:
    • // +k8s:prerelease-lifecycle-gen:deprecated=1.21  -> 表示 生成的 APILifecycleDeprecated 函数返回的版本为 1.21
    • // +k8s:prerelease-lifecycle-gen:replacement=<group>,<version>,<kind> -> 表示 生成的 APILifecycleReplacement  函数返回 schema.GroupVersionKind 结构体
  4. prerelease-lifecycle-gen 生成器一共有这几个函数:
函数名Tags说明
APILifecycleIntroduced()k8s:prerelease-lifecycle-gen:introduced返回这个 API 接口是从哪个版本开始的
APILifecycleDeprecated()k8s:prerelease-lifecycle-gen:deprecated返回这个 API 接口将在哪个版本丢弃,但 API 仍旧会提供服务
APILifecycleReplacement()k8s:prerelease-lifecycle-gen:replacement=,,表示这个 API 接口有更新,返回应该使用的类型结构
APILifecycleRemoved()k8s:prerelease-lifecycle-gen:removed返回这个 API 接口会在哪个把版本不再提供服务

prerelease-lifecycle-gen 的自动生成代码示例:

代码路径:vendor/k8s.io/api/networking/v1beta1/zz_generated.prerelease-lifecycle.go。代码为:

Copy to Clipboard

2.3.3. deepcopy-gen 代码生成器

上面介绍了 prerelease-lifecycle-gen 的 Makefile 构建方法,其他的五个代码生成器构建规则是相同的,都是这几个步骤。后面我不会再次分析makefile源码,而是简单介绍生成器的作用和功能。

deepcopy-gen 代码生成器:

  1. deepcopy-gen 是一个自动生成 zz_generated.deepcopy.go 文件的代码生成器。
  2. zz_generated.deepcopy.go 文件主要包括两个函数,DeepCopy() 和 DeepCopyInto(),功能就是深度拷贝,也就是生成一个相同的结构体,两个内存空间。
  3. 生成 deepcopy 函数时,其 Tags 形式可以为:
    • +k8s:deepcopy-gen=true : 为单个类型生成DeepCopy相关函数
    • +k8s:deepcopy-gen=package : 为整个包生成DeepCopy相关函数
    • +k8s:deepcopy-gen=false : 为整个包生成DeepCopy相关函数时,可以忽略单个类型
  4. deepcopy-gen 生成器生成的函数介绍:
函数说明Tags
DeepCopy()复制接收者,并创建一个新的与接收者相同的深度拷贝返回// +k8s:deepcopy-gen=true
DeepCopyInto(*type)复制接收者,并创建一个新的与接收者相同的深度拷贝写入到函数参数中// +k8s:deepcopy-gen=package
DeepCopyObject()复制接收者,并创建一个新的runtime.Object// +k8s:deepcopy-gen:interfaces=xxx/runtime.Object

deepcopy-gen 生成器自动生成代码文件示例:

代码路径:vendor/k8s.io/api/core/v1/zz_generated.deepcopy.go。代码为:

Copy to Clipboard

2.3.4. defaulter-gen 代码生成器

defaulter-gen 代码生成器:

  1. defaulter-gen 是一个自动生成 zz_generated.defaults.go 文件的代码生成器。
  2. zz_generated.defaults.go 文件主要功能就是为资源对象生成默认值。每一个对象会生成一个专门的函数,xxxTypeDefaults() ,用于给对象幅默认值。
  3. 生成 Defaulter 函数时,其 Tags 形式可以为:
    • +k8s:defaulter-gen=TypeMeta: 为拥有 TypeMeta 属性的类型生成Defaulter相关函数
    • +k8s:defaulter-gen=ListMeta : 为拥有 ListMeta 属性的类型生成Defaulter相关函数
    • +k8s:defaulter-gen=ObjectMeta : 为拥有 ObjectMeta 属性的类型生成Defaulter相关函数
    • +k8s:defaulter-gen-input=path/to/package : 当前包会依赖于指定的路径包
  4. defaulter-gen 生成器生成的函数介绍:
函数名Tags说明
RegisterDefaults()k8s:defaulter-gen:TypeMeta将该函数内的 Setxxx 方法注册到 runtime.schema 中,并且公开这个 schema
SetObjectDefaults_XxxxNone设置具体某个类型对象 Xxxx 的默认值

defaulter-gen 生成器自动生成代码文件示例:

代码路径:pkg/apis/batch/v1beta1/zz_generated.defaults.go。代码为:

Copy to Clipboard

2.3.5. conversion-gen代码生成器

conversion-gen 代码生成器:

  1. conversion-gen 是一个自动生成 zz_generated.conversion.go 文件的代码生成器。
  2. zz_generated.conversion.go 文件主要功能就是为对象在内部和外部类型之间提供转换函数。每一个对象会生成一组转换的函数,例如:Convert_v1alpha1_PolicyList_To_audit_PolicyList()  和 Convert_audit_PolicyList_To_v1alpha1_PolicyList,PolicyList 是一种类型,conversion-gen为该类型生成了Convert函数。
  3. 生成 conversion 函数时,其 Tags 形式可以为:
    • +k8s:conversion-gen= INTERNAL_TYPES_DIR : 为整个包生成Convert相关函数,INTERNAL_TYPES_DIR 用于定义包的导入路径,例如k8s.io/kubernetes/pkg/apis/abac。
    • +k8s:conversion-gen-external-types= EXTERNAL_TYPES_DIR : 为整个包生成Convert相关函数且依赖其他包,EXTERNAL_TYPES_DIR 用于定义其他包的路径,例如k8s.io/api/autoscaling/v1。
  4. conversion-gen 生成器生成的函数介绍:
函数名Tags说明
RegisterConversions() // +k8s:conversion-gen-external-types= EXTERNAL_TYPES_DIR
// +k8s:conversion-gen=INTERNAL_TYPES_DIR
将该函数内的 Convert 方法注册到 runtime.schema 中,并且公开这个 schema
Convert_v1alpha1_EventList_To_audit_EventListNone从 v1alpha1_EventList 转换成 audit_EventList
Convert_audit_EventList_To_v1alpha1_EventListNone从 audit_EventList 转换成 v1alpha1_EventList

conversion-gen 生成器自动生成代码文件示例:

代码路径:vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1/zz_generated.conversion.go。代码为:

Copy to Clipboard

2.3.6. openapi-gen 代码生成器

openapi-gen 代码生成器:

  1. openapi-gen 是一个自动生成 zz_generated.openapi.go 文件的代码生成器。
  2. zz_generated.openapi.go 文件主要功能就是可以为其生成 OpenAPI 定义文件,该文件用于kube-apiserver服务上的OpenAPI规范的生成。函数名为:GetOpenAPIDefinitions() ,该函数会返回 map,key 表示 API ,value 表示 定义这个 API 的函数。
  3. 生成 openapi 函数时,其 Tags 形式可以为:
    • +k8s:openapi-gen=true : 为特定类型或包生成OpenAPI定义文件。
    • +k8s:openapi-gen=false : 排除为特定类型或包生成OpenAPI定义文件。
  4. openapi-gen 生成器生成的函数介绍:
函数名Tags说明
GetOpenAPIDefinitions() // +k8s:openapi-gen=true将该函数会返回一个 map ,是一个 API 的对应关系
schema_API_PATH()None返回这个 API 的详细定义,返回应该是一个 Json 对象

这个函数可以为:schema_k8sio_api_autoscaling_v1_ContainerResourceMetricSource(ref)
对应的 API 接口为:k8s.io/api/autoscaling/v1.ContainerResourceMetricSource

fan

openapi-gen 生成器自动生成代码文件示例:

代码路径:vendor/k8s.io/sample-apiserver/pkg/generated/openapi/zz_generated.openapi.go。代码为:

Copy to Clipboard

2.3.7. go-bindata 代码生成器

go-bindata 代码生成器:

  1. go-bindata 是一个构建 go-bindata 二进制文件,并执行 go-bindata 代码生成器,为 translations 静态资源目录生成 pkg/kubectl/generated/bindata.go.tmp 文件。
  2. go-bindata 的功能:把静态文件都打包到程序中。Golang 是可以把整个项目打包成一个可执行文件,但如果依赖一些静态资源,可以利用 go-bindata 将静态资源打包进可执行文件中。
  3. 使用 go-bindata 时,没有 Tags 。

3. 容器环境构建

Kubernetes提供了两种容器环境下的构建方式:

  • make release:构建所有的目标平台(Darwin、Linux、Windows),构建过程会比较久,并同时执行单元测试过程。
  • make quick-release:快速构建,只构建当前平台,并略过单元测试过程。

3.1 make quick-release 在 Makefile 的源码分析

3.2 容器环境构建过程

容器环境的构建过程,主要是在 build/release.sh 脚本中定义,下面我们看一下脚本的具体内容:

Copy to Clipboard

从上面的代码中,可以看到 Kubernetes 容器环境构建过程分为几步:

  1. 容器构建环境的配置和验证(kube::build::verify_prereqs)
  2. build image 构建镜像(kube::build::build_image)
  3. 具体构建方法(kube::build::run_build_command)
  4. 将文件从容器中拷贝到主机(kube::build::copy_output)
  5. 打包(kube::build::package_tarballs)

每个步骤的具体细节参考后面小节

3.2.1 步骤1-环境配置与验证

(kube::build::verify_prereqs) 进行构建环境的配置及验证。该过程会检查本机是否安装了Docker容器环境,而对于Darwin平台,该过程会检查本机是否安装了docker-machine环境。kube::build::verify_prereqs 方法在 build/common.sh ,其具体内容为:

Copy to Clipboard

根据 kube::build::verify_prereqs 的源码,可以看到这个函数的功能:

  • 验证系统中有没有 tar、rsync、docker 的环境
  • 设置相关的环境变量
  • 将环境变量保存在文件中

在设置环境变量时,我们发现,设置了三个容器的名称,在容器环境构建过程中,主要是这三个容器镜像参与其中,分别:

  • build 容器(kube-cross):即构建容器,在该容器中会对代码文件执行构建操作,完成后其会被删除。
  • data 容器:即存储容器,用于存放构建过程中所需的所有文件。
  • rsync 容器:即同步容器,用于在容器和主机之间传输数据,完成后其会被删除。

这里用到的基础镜像 kube-cross ,需要从 k8s.gcr.io 仓库拉取,我这里用了代理拉取。完整的基础镜像名称为: k8s.gcr.io/build-image/kube-cross:v1.16.1-1

3.2.2 步骤2-编译镜像

(kube::build::build_image) 主要是用来编译镜像,一共需要编译上面提到的三个镜像。该函数在 build/common.sh 源码如下:

Copy to Clipboard

构建容器镜像的流程如下。

  • 通过 mkdir 命令创建构建镜像的文件夹(即_output/images/…)。
  • 通过 cp 命令复制构建镜像所需的相关文件,如Dockerfile文件和rsyncd同步脚本等。
  • 通过 kube::build::docker_build 函数,构建容器镜像。
  • 通过 kube::build::ensure_data_container 函数,运行存储容器并挂载Volume。
  • 通过 kube::build::sync_to_container 函数,运行同步容器并挂载存储容器的 Volume,然后通过 rsync 命令同步 Kubernetes 源码到存储容器的 Volume。

3.2.3 步骤3-构建方法

此时,容器构建环境已经准备好,下面开始运行构建容器并在构建容器内部执行构建Kubernetes源码的操作,构建过程在 kube::build::run_build_command make cross。 该函数位于:build/common.sh,代码示例如下:

Copy to Clipboard

通过 ${docker_cmd[@]}""${cmd[@]} 命令执行构建操作(即在容器内执行make cross命令)。容器内的构建过程与本地环境下的构建过程相同,故不再赘述。

3.2.4 步骤4-将文件从容器中拷贝到主机

(kube::build::copy_output)使用同步容器,将编译后的代码文件复制到主机上。该函数位于:build/common.sh,源码为:

Copy to Clipboard

根据 kube::build::copy_output 函数的源码,该函数的步骤为:

  1. 启动 RSYNCD 容器,并且获取该容器的 ip 地址,存储在 KUBE_RSYNC_ADDR 环境变量中。
  2. 使用 rsync 命令,对构建后的目录进行过滤,将需要的目录,从容器中拷贝到本地目录。
  3. 停止 RSYNCD 容器,并且 unset KUBE_RSYNC_ADDR 。

3.2.5 步骤5-打包

(kube::release::package_tarballs)函数用于打包,将二进制文件打包到 _output 目录中。最终,代码文件以 tar.gz 压缩包的形式输出至 _output/release-tars 文件夹。该函数位于:build/lib/release.sh,源码为:

Copy to Clipboard

打包很简单,就是利用 tar 命令对不同的目录分别进行打包操作。

4. Bazel环境构建

除了使用官方构建工具,Kubernetes 还支持 Bazel 构建。Bazel 是 Google 公司开源的一个自动化软件构建和测试工具。Bazel 使用分布式缓存和增量构建方法,使构建更加快速。其支持构建任务,包括运行编译器和链接器以生成可执行程序和库。Bazel 与 Make、Gradle 及 Maven 等构建工具类似,但Bazel在构建速度、可扩展性、灵活性及跨语言和对不同平台的支持上更加出色。

Bazel 优势:

  • 高级别的构建语言:项目以BUILD语言进行描述。BUILD是一种简洁的文本格式,可描述多个小而互相关联的库、二进制程序和测试程序组成的项目。
  • 支持多平台:相同的工具和BUILD文件可以为不同架构或平台构建软件。
  • 再现性:在BUILD文件中,必须明确为每个库、测试程序、二进制文件指定其直接依赖。在修改源码文件后,Bazel使用这个依赖信息就可以知道哪些东西必须重新构建,哪些任务可以并行执行。这意味着所有的构建都是以增量的形式构建的并能够每次都生成相同的结果。
  • 可扩展性强:Bazel可以处理大型程序的构建;在Google公司内,一个二进制程序通常有超过100KB的源码文件,在代码文件没有被改动的情况下,构建过程大约需要200ms。
  • 构建速度快:支持增量编译。对依赖关系进行了优化,从而支持并发执行。

4.1 Bazel的工作原理

4.2 Bazel在kubernetes中的使用

Kubernetes 源码的根目录下有一个 WORKSPACE(工作区)文件,用于指定当前目录是 Bazel 的一个工作区域,该文件一般存放在项目根目录下。另外,项目中包含一个或多个 BUILD 文件,用于告诉 Bazel 如何进行构建。Bazel 工作原理大致分为3部分:

  1. 加载与 Target 相关的 BUILD 文件。
  2. 分析 BUILD 文件的内容,生成ActionGraph。
  3. 执行 Action Graph,最后产出 Outputs。

在 vendor/github.com/grpc-ecosystem/grpc-gateway/utilities/BUILD.bazel 文件中,定义如下:

Copy to Clipboard

BUILD文件内容如下。

  • load:需要使用哪个.bzl规则来编译当前Target。
  • go_library:设置构建规则。
    • name:当前Target构建后的名称。
    • src:当前Target下被构建的源码文件。
    • deps:当前Target构建时依赖的静态库名称。

5. 附录

hackmake-rulesbuild.sh 脚本主要功能是:就是执行编译

但其中具体的编译函数,位于 hack/lib/golang.sh ,golang.sh 脚本我截取了 platform 是本机的相关代码,下面看一下这两个脚本的内容:

Copy to Clipboard
Copy to Clipboard