# 扩展

  • k8s 的软件生态很开放,可以在不修改 k8s 源码的情况下,通过多种方式扩展 k8s 的功能:

    • CRI、CNI、CSI、admission 等插件
    • APIService
    • CRD
    • Custom Controller
  • 从通信角度来看,k8s 的扩展方式分为两大类:

    • Binary Plugin :让 k8s 执行某个二进制文件。例如 kubelet 会执行 CRI 插件 ,kubectl 命令集成了 kustomize 。
    • Webhook :独立运行一个服务器,让 k8s 发送网络请求来调用它。例如 APIService、Custom Controller 。

# API 规范

  • k8s 内部的一些组件通过 API 相互调用,并且定义了 API 规范。

    • 采用 gRPC 协议进行通信。
    • API 规范使得 k8s 组件之间比较解耦,有利于模块化设计、更换组件。
    • 例如 k8s 容器运行时的 API 规范是 CRI ,如果一个插件符合该 API 规范,则可以替换 k8s 默认的容器运行时组件。
  • 容器运行时接口(Container Runtime Interface ,CRI)

    • :kubelet 通过该接口,调用担任容器运行时的组件,从而管理容器、镜像。
    • 早期的 k8s 默认采用 Docker 作为容器引擎,因为 Docker 是当时最流行的容器引擎。
      • 后来 k8s 社区制定了 CRI 规范,但 Docker 不符合该规范。因此 k8s 社区开发了一个符合 CRI 规范的 dockershim 模块,通过它间接调用 dockerd ,从而管理容器。
      • 2020 年, k8s 社区认为维护 dockershim 模块比较麻烦,因此弃用 dockershim 模块,建议用户改用 containerd 或 CRI-O 作为 CRI 组件。
        • 不使用 dockershim 模块,就不能通过 docker 命令查看、管理 kubelet 部署的容器。但用 docker build 命令构建的镜像符合 OCI 标准,依然可以被 containerd 或 CRI-O 运行。
      • 2021 年,Mirantis 公司和 Docker 公司联合开发 cri-dockerd 项目,用于继续实现 dockershim 的功能,从而不必用户更换 CRI 组件。
  • 容器网络接口(Container Network Interface ,CNI)

    • :供 kubelet 调用,管理容器的网络。
  • 容器存储接口(Container Storage Interface ,CSI)

    • :供 kubelet 调用,管理容器的存储层。
    • k8s 本身提供了 hostPath、ConfigMap 等类型的 volume 。而一些第三方的存储程序可通过 CSI 接口接入 k8s 集群,提供一些其它类型的 volume 。
  • 设备插件(Device Plugins)

    • :允许将主机上的 GPU、FPGA 等设备接入 k8s 进行管理。

# API Aggregation

  • apiserver 进程中设计了一个聚合层(Aggregation Layer),允许用户注册一些自定义的 API 服务器,被 apiserver 反向代理,从而在逻辑上聚合成一个 API 服务器。

    • 优点:方便扩展 apiserver ,添加自定义的 API 。
  • 例如 metrics-server 是一个给 k8s 增加 CPU、内存使用率等监控指标的工具,采用 API Aggregation 的方式工作。原理如下:

    1. 按 API 规范开发 metrics-server 程序,然后在 k8s 中以 Deployment 方式部署,并绑定 Service 。
    2. 创建一个 APIService 类型的资源,将 metrics-server 注册为一个 API 服务器。
      apiVersion: apiregistration.k8s.io/v1
      kind: APIService
      metadata:
        name: v1beta1.metrics.k8s.io  # 该 API 服务器的名称,格式为 ${version}.${group}
      spec:
        group: metrics.k8s.io         # API 服务器的组名
        groupPriorityMinimum: 100     # 该 group 的优先级
        insecureSkipTLSVerify: true   # apiserver 向该 API 服务器发送 HTTPS 请求时,是否跳过 TLS 认证
        service:                      # 让 apiserver 将请求发送到该 k8s service ,从而调用该 API 服务器
          name: metrics-server
          namespace: kube-system
          port: 443
        version: v1beta1              # API 服务器的版本
        versionPriority: 100          # 该 version 在当前 group 全部版本中的优先级
      
    3. 客户端发送 HTTPS 请求给 apiserver 。如果请求指向的 URI 为 /apis/metrics.k8s.io/v1beta1/... ,则 apiserver 会将请求转发给 metrics-server 这个 APIService 。
  • 用户可创建多个 APIService 资源。

    • 如果多个 APIService 划分到同一个 group ,只是 version 不同,则表示同一种 API 服务器的不同版本的实例。
    • 整个 group 的优先级,取决于该 group 中 groupPriorityMinimum 最高的那个 APIService 。

# CRD

  • 自定义资源的声明(CustomResourceDefinition,CRD)

    • :k8s 原生提供的一种资源,供用户注册一些自定义的 k8s 资源类型。然后用户就可以创建这些类型的 k8s 资源实例。
    • 优点:k8s 外部的资源通常用 shell 脚本管理,注册为 CRD 资源之后能像 k8s 原生资源一样管理,更方便。
    • 缺点:自定义资源可能需要进行一些改造,才适合使用 k8s 的声明式配置文件。
  • 例:

    1. 创建 CRD,注册一个资源类型 Zone ,表示服务器地区。
      apiVersion: apiextensions.k8s.io/v1
      kind: CustomResourceDefinition
      metadata:
        name: zones.test.com              # 该 CRD 资源的名称,格式为 ${plural}.${group}
      spec:
        group: test.com
        names:
          kind: Zone                      # 该 CRD 资源的类型名
          listKind: ZoneList
          plural: zones                   # 复数的英文名
          singular: zone                  # 单数的英文名
        scope: Cluster                    # 该 CRD 资源的作用域。取值为 Namespaced 表示单个命名空间,取值为 Cluster 表示整个集群
        versions:                         # 可以给该 CRD 资源注册多个版本
        - name: v1                        # 该 CRD 资源的其中一个版本号
          schema:                         # 声明该 CRD 资源的数据结构。主要是配置文件中,各个字段的名称、数据类型
            openAPIV3Schema:
              type: object
              properties:
                spec:                     # 声明 spec 字段,取值为 object 类型,并通过 properties 声明子字段
                  properties:
                    city:                 # 声明 spec.country 字段,取值为 string 类型
                      nullable: true      # 是否允许取值为空
                      type: string
                  type: object
          served: true                    # 是否允许通过 RESTful API 访问该 CRD 资源
          storage: true                   # 必须有且仅有一个版本,标记为存储版本
      
    2. 创建一个 Zone 类型的资源实例:
      apiVersion: test.com/v1
      kind: Zone
      metadata:
        name: zone1
      spec:
        city: beijing
      
      • 可用 kubectl 命令查看:
        kubectl get zones
        
      • 删除一个 CRD 时,会删除这种类型的所有资源实例。
  • 比较 CRD 与 APIService 资源:

    • 注册 CRD 资源时,不需要编程。而注册 APIService 资源时,需要开发、部署自己的 API 服务器。
    • 一个 CRD 资源可注册多个 versions 。而一个 APIService 资源只注册一个 version 。

# Operator

  • 创建 CRD 资源之后,它们只是作为静态数据存储在 k8s 中。可以在 k8s 中添加自定义控制器(Custom Controller),负责监听 k8s 事件、自动管理 CRD 资源。

  • Operator

    • :一种部署 k8s 应用的方式,由 CoreOS 团队提出。结合使用 CRD 和 Custom Controller ,能自动完成大部分运维工作。
  • 例如 Oracle MySQL Operator (opens new window) 用于在 k8s 中部署 MySQL 集群,步骤如下:

    1. 下载该 Operator 的发行版,执行命令 kubectl apply -f xxhelm install xx 安装到 k8s 中。这会对 k8s 做出以下改动:
      • 注册一些 CRD 资源,比如 InnoDBCluster 表示 MySQL 集群,MySQLBackup 表示 MySQL 备份。
      • 部署一个名为 mysql-operator 的 Deployment ,作为 Custom Controller 工作。
      • 创建一些 RBAC 资源。
    2. 新建一个 InnoDBCluster 资源,此时 Custom Controller 会自动配置 StatefulSet、Service、Volume 等资源,部署 MySQL 集群。
      apiVersion: mysql.oracle.com/v2
      kind: InnoDBCluster
      metadata:
        name: mycluster
      spec:
        secretName: mypwds    # 指定 MySQL 挂载的 Secret
        instances: 3          # 部署的 MySQL 实例数
        version: 8.0.31       # 部署的 MySQL 版本
        mycnf: |              # 传入 my.conf 配置文件
          [mysqld]
          max_connections = 151
        ...
      
      • 部署 MySQL 集群之后,创建 MySQLBackup 资源,则 Custom Controller 会自动创建 Job 去备份数据。
  • 开发 Operator 的框架:

    • kubebuilder
    • Operator Framework
  • operatorhub.io 网站上分享了一些 Operator 。