k8s控制面相关学习

informer

informer:https://zhuanlan.zhihu.com/p/391465614 https://www.zhihu.com/question/463207052/answer/2377261278

(注意Informer最后也有个controller,跟这个不一样)

对于Informer而言

  1. 大步骤1: Reflector 将资源对象的事件添加进 Delta FIFO queue

这里先提前介绍一下 Delta FIFO queue。 所谓 Delta 就是变化的意思,什么的变化呢?就是资源对象的变化。

即 资源对象的变化都会被添加到 Delta FIFO queue 中!这样是不是就很好理解了。

  1. 大步骤2: Informer 将 Delta FIFO queue 中的对象数据 添加到本地 cache 中。

补充一下这个本地 cache 缓存的就是监听资源对象的最新版。就是缓存的当前集群里面的资源信息。

  1. 大步骤3: 使用 workqueue 处理业务逻辑。

Kubernetes 控制面controller基础概念解析

1. 控制面的基本组件

控制器 (Controller)

pkg/controller/controller.go 可以看到,控制面主要由多个控制器组成:

  • 主控制器:协调所有子控制器
  • EgressGateway 控制器:处理出口网关策略
  • IPAMPool 控制器:管理 IP 地址池
  • Operator 控制器:处理网格配置

每个控制器都遵循相同的模式:

1
2
3
4
5
6
type controller struct {
*controllerruntime.GenericController
cache gridCache.Cache
cnf *config.Config
processors []controllerruntime.Processor
}

缓存系统 (Cache)

pkg/controller/cache/interfaces.go 可以看到缓存系统的作用:

  • 资源监听:监听 Kubernetes 资源变化
  • 数据同步:保持本地缓存与集群状态同步
  • 数据查询:提供高效的资源查询接口

处理器 (Processors)

每个控制器包含多个处理器,如 egressgateway 控制器中的:

  • IPPoolProcessor:处理 IP 池
  • DeploymentProcessor:处理部署
  • StatusProcessor:更新状态

2. 控制面设计原则

声明式 API

pkg/controller/apis/egressgateway_policy.go 可以看到声明式设计:

1
2
3
4
5
6
type EgressGatewayPolicyInfo struct {
*v1alpha1.EgressGatewayPolicy
Deployment *EgressGatewayDeployment
EgressPods map[EgressPodID]*EgressPodInfo
IPPool *v1alpha1.IPAMPool
}

用户声明期望状态,控制器负责将当前状态调整为期望状态。

协调循环 (Reconcile Loop)

每个控制器都有 Reconcile 方法:

1
2
3
4
5
6
func (c *controller) Reconcile(ctx context.Context, key string, keyLogger logman.Logman) error {
// 获取当前状态
// 比较与期望状态的差异
// 执行必要的操作
// 更新状态
}

领导者选举

pkg/controller/controller.go 可以看到高可用设计:

1
2
3
4
5
6
7
8
9
10
11
runner, err := runnable.NewLeaseLeaderElection(
c.clients.KubeClient(),
c.cnf.DelpoyingNamespace(),
c.cnf.DeployingApp(),
id,
leaderelection.LeaderElectionConfig{
LeaseDuration: time.Second * 15,
RenewDeadline: time.Second * 10,
RetryPeriod: time.Second * 2,
},
)

确保只有一个控制器实例处于活跃状态。

3. 事件驱动架构

资源监听

WatchResources 方法可以看到:

1
2
3
4
5
6
7
8
9
10
11
func (c *controller) WatchResources() []controllerruntime.WatchResourceDefination {
return []controllerruntime.WatchResourceDefination{
{
NewFunc: func() runtime.Object {
return &v1alpha1.EgressGatewayPolicy{}
},
// ...
},
// 其他资源...
}
}

控制器监听感兴趣的 Kubernetes 资源变化。

事件处理

AddEventHandlers 方法可以看到事件处理机制:

1
2
3
4
5
6
7
func (c *controller) AddEventHandlers(obj runtime.Object, handlers cache.ResourceEventHandlerFuncs) {
switch obj.(type) {
case *v1alpha1.EgressGatewayPolicy:
c.cache.AddEgressGatewayPolicyHandler(handlers, time.Second*3)
// ...
}
}

4. 错误处理和重试机制

优雅的错误处理

Reconcile 方法可以看到:

1
2
3
4
if errors.IsNotFound(err) {
keyLogger.Infof("EgressGatewayPolicy %s not found, skip", key)
return nil
}

速率限制

RateLimiter 方法可以看到:

1
2
3
func (c *controller) RateLimiter() workqueue.RateLimiter {
return nil // 使用默认速率限制器
}

5. 学习要点总结

  1. 控制器模式:Kubernetes 控制面的核心是控制器模式,通过协调循环不断调整当前状态到期望状态。

  2. 声明式API:用户声明期望状态,系统负责实现和维护这个状态。

  3. 事件驱动:基于资源变化事件触发协调操作。

  4. 高可用性:通过领导者选举确保只有一个活跃实例。

  5. 缓存优化:使用本地缓存减少对 API Server 的压力。

  6. 模块化设计:控制器、处理器、缓存等组件职责分离。

这个 grid 项目展示了典型的 Kubernetes 控制器实现模式,是学习控制面设计的优秀示例。

CRD controller/informer生成?

基于对代码的详细分析,这个项目基于自定义CRD生成informer、controller等的实现机制如下:

1. 核心实现机制

API定义阶段

代码生成流程

  1. CRD生成:通过 hack/gencrd.sh 使用 controller-gen 生成CRD YAML文件到 manifests/ 目录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # gencrd.sh
    #!/bin/bash

    set -o errexit

    go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.15.0

    gobin="${GOBIN:-$(go env GOPATH)/bin}"

    rm -rf ./manifests/*
    ${gobin}/controller-gen crd:generateEmbeddedObjectMeta=true paths=./pkg/apis/... output:crd:dir=./manifests

  2. 客户端代码生成:通过 hack/gencode.sh 调用Kubernetes的代码生成器:

    • client-gen:生成客户端接口和实现

    • lister-gen:生成Lister用于缓存访问

    • informer-gen:生成Informer用于监听资源变化

      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
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      # gencode.sh
      #!/bin/bash

      set -o errexit
      set -o nounset
      set -o pipefail

      # 获取脚本根目录
      SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
      pushd "${SCRIPT_ROOT}"

      # 获取 kubernetes code-generator 的特定版本
      echo "Fetching code-generator..."
      go get k8s.io/code-generator@v0.30.14

      # 定义生成代码的输入根包和输出根包
      INPUT_PKG="./pkg/apis"
      OUTPUT_PKG="gitlab.infini-ai.com/mizar/apis/pkg/client"

      # 检查并导出 GOPATH
      GOPATH=$(go env GOPATH)
      echo "GOPATH=${GOPATH}"

      # 设置 CODEGEN_PKG 连接到正确定义的路径
      CODEGEN_PKG="$GOPATH/pkg/mod/k8s.io/code-generator@v0.30.14"

      if [ ! -d "$CODEGEN_PKG" ]; then
      echo "Unable to find code-generator at $CODEGEN_PKG"
      exit 1
      fi

      source "${CODEGEN_PKG}/kube_codegen.sh"
      echo "Using code-generation package at: $CODEGEN_PKG"

      # 生成 Deepcopy、Defaulter、Conversion 代码
      echo "Generating deepcopy, defaulter, and conversion code..."
      kube::codegen::gen_helpers \
      --boilerplate "./hack/boilerplate.go.txt" \
      "${INPUT_PKG}"

      # 生成 Client、Lister、Informer 代码
      echo "Generating client, lister, and informer code..."
      kube::codegen::gen_client \
      --output-dir "${SCRIPT_ROOT}/pkg/client" \
      --output-pkg "${OUTPUT_PKG}" \
      --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \
      --with-watch \
      "${INPUT_PKG}"

      # 检查生成的代码是否存在于预期的位置
      echo "Checking generated code..."
      echo "Contents of ${SCRIPT_ROOT}/${INPUT_PKG}/pkg/client/:"

      # 整理 go 模块
      echo "Tidying up go modules..."
      go mod tidy

      echo "Code generation complete."

生成的代码结构

客户端代码 (pkg/client/)

Informer系统 (pkg/client/informers/)

Lister缓存 (pkg/client/listers/)

2. 关键特性

自动代码生成

  • 基于Go结构体定义和注解自动生成全套客户端代码
  • 支持watch功能,实时监听资源变化
  • 内置缓存机制,减少API服务器压力

类型安全

  • 强类型接口,编译时检查
  • 自动生成的深拷贝方法确保数据一致性

完整的CRUD操作

  • Create、Get、List、Update、Delete、Patch、Watch等完整操作
  • 支持status子资源更新

3. 使用流程

  1. 定义API:在pkg/apis下定义资源结构体和注解
  2. 生成代码:运行 make generate 生成客户端、informer、lister代码
  3. 部署CRD:应用manifests目录下的CRD定义
  4. 使用客户端:通过生成的Clientset访问自定义资源

这种实现方式充分利用了Kubernetes的代码生成生态系统,确保了类型安全和开发效率,同时保持了与原生Kubernetes资源相同的使用体验。

CRD controller/informer生成具体的实现?

1. CRD生成 (hack/gencrd.sh)

1
2
3
# 使用Kubebuilder的controller-gen工具
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.15.0
${gobin}/controller-gen crd:generateEmbeddedObjectMeta=true paths=./pkg/apis/... output:crd:dir=./manifests
  • 工具: Kubebuilder的controller-gen
  • 功能: 解析API定义中的Kubebuilder注解,生成完整的CRD YAML
  • 输出: manifests/ 目录下的CRD文件

2. 客户端代码生成 (hack/gencode.sh)

1
2
3
4
5
6
7
# 使用Kubernetes原生代码生成器
go get k8s.io/code-generator@v0.30.14
source "${CODEGEN_PKG}/kube_codegen.sh"

# 生成helpers和客户端代码
kube::codegen::gen_helpers --boilerplate "./hack/boilerplate.go.txt" "${INPUT_PKG}"
kube::codegen::gen_client --with-watch "${INPUT_PKG}"
  • 工具: Kubernetes的code-generator (client-gen, informer-gen, lister-gen)
  • 功能: 生成客户端、informer、lister等运行时组件
  • 输出: pkg/client/ 目录下的完整客户端代码

3. 与Kubebuilder的关系

使用Kubebuilder的部分:

  • 注解系统: +kubebuilder: 注解定义CRD元数据和验证规则
  • CRD生成: 使用controller-gen工具生成CRD YAML
  • 丰富特性: 支持验证规则、打印列、子资源等高级CRD特性

使用Kubernetes原生工具的部分:

  • 客户端生成: 使用client-gen生成类型安全的客户端接口
  • Informer系统: 使用informer-gen生成高效的watch机制
  • Lister缓存: 使用lister-gen生成缓存访问接口

4. 生成的关键组件

客户端层 (pkg/client/clientset/)

  • Clientset: 统一的客户端入口,支持所有API组版本
  • 类型化客户端: 针对每个资源的强类型操作接口

Informer层 (pkg/client/informers/)

  • SharedInformerFactory: 管理所有informer的工厂模式
  • 资源特定Informer: 实现ListWatch机制,实时监听资源变化

Lister层 (pkg/client/listers/)

  • 缓存访问: 提供从本地缓存读取资源的接口
  • 索引支持: 基于cache.Indexer的高效数据检索

5. 优势总结

这种混合架构结合了:

  1. Kubebuilder的CRD定义能力 - 丰富的注解系统和验证规则
  2. Kubernetes原生的客户端生态 - 与k8s完全兼容的客户端模式
  3. 灵活性 - 可以独立使用客户端而不依赖完整的controller框架
  4. 性能 - 高效的informer/watch机制和本地缓存

这种实现确保了自定义资源拥有与Kubernetes原生资源相同的开发体验和性能特性。