Kubernetes编程——什么是 Kubernetes 编程?

左扬(你们的胃叫胃,孤的叫胃PLUS) / 2023-05-22 / 原文

什么是 Kubernetes 编程?

 

  这里的 Kubernetes编程 指开发原生 Kubernetes 应用,这类应用通过与 API 服务器进行开发,直接查询、更新资源的状态。

  这里不会在 `Controller` 和 `Operator` 中,这里也不会过多关注操作层面的东西,而是会关注开发和测试的阶段。

  因此,我们会聊下关于如何开发纯正的云原生应用。

一、举个例子

  我们把这个例子叫做 `cnat`,其工作逻辑是:假如你想让 cnat 在 2023年5月21日凌晨2点运行命令echo "Hello, Shanghai!",你可以这么做:

apiVersion: cnat.programming-kubernetes.info/v1alpha1
kind: At
metadata:
  name: cnrex
spec:
  schedule: "2023-05-21T02:00:00Z"
  containers:
  - name: shell
    image: centos:7
    command:
    - "bin/bash"
    - "-c"
    - echo "Hello, Shanghai!"

二、扩展模式

  Kubernetes 是一个强大且天然可扩展的系统。有很多种方法来定制、扩展 Kubernetes,比如:修改控制平面组件的配置文件和参数,或者通过各种预定义的扩展点。

三、控制器和Operator

  在 kubernetes 的术语中,控制器实现了一个控制循环,它通过 API 服务器观测集群中的共享状态,进行必要的变更,尝试把当前状态转换到期望的目标状态。

    • 控制器可以对 Kubernetes 的核心资源(比如 Deployment 或 Service)进行操作,通常是控制平面中 Kubernetes 控制器管理的一部分,它们也可以观察并操作用户自定义的资源。
    • Operator 也是一种控制器,但它会包含一些运维逻辑,比如应用程序的生命周期管理。

  控制器进程通常会在集群中的一个 Pod 里运行。

3.1、控制循环

控制循环逻辑如下:

1、读取资源的状态,通常采用事件驱动模式(使用 watch 来实现)。

2、改变集群中的对象状态或集群外部系统的状态。

3、通过 API服务更新到第1步的资源状态,这个状态是放在 etcd 中。

4、循环执行这些逻辑,返回第1步骤。

 

一个控制器通常包含以下的数据结构:

Informer:负责观察资源的目标状态,这个过程需要是可伸缩和可持续的。

工作队列:事件处理器把状态变化情况放入工作队列,用于保证在必要时可以进行重试。

3.2、事件 

Kubernetes 的控制器会通过 API 服务器来观察 Kubernetes 对象的变化情况:添加、更新或删除。如果发生了这样的事件,控制器就开始执行对应的业务逻辑。

比如,想要通过一个 Deployment 来启动一个 Pod,一系列的控制器和控制平面中的组件需要协同工作:

1、Deployment 的控制器(在 kube-controller-manager 中)会发现(通过 Deployment Informer)用户创建了一个 Deployment,并执行其相关的业务逻辑;创建一个 ReplicaSet 对象。

2、ReplicaSet 的控制器(同样在 kube-controller-manager 中)会发现(通过 ReplicaSet Informer)这个新创建的 ReplicaSet,并执行其相关的业务逻辑:创建一个 Pod 对象。

3、调度器(在 kube-scheduler 中)本身是一个控制器,它发现了新创建的 Pod(通过 Pod Informer),并且这个 Pod 的 spec.nodeName 字段是空值。调度器执行的业务逻辑就是把这个 Pod 放入它的调度队列。

4、与此同时,kubelet(它是一个控制器)发现了这个新创建的 Pod(通过它的 Pod Informer)。

5、由于这个新的 Pod 的 spec.nodeName 字段是空值,与本 kubelet 所在节点的名字不同,所以它忽略了这个 Pod,并继续等待下一个时间到来。

6、调度器把 Pod 从工作队列中取出来,把它调度到一个具备足够资源的节点的名字不同,并把节点的名字更新到 Pod 的 spec.nodeName 字段中,然后把它写入 API 服务器中。

7、由于上一步操作产生了新的 Pod 更新事件,kubelet 再次被唤醒。它再次比较 Pod 的 spec.nodeName 字段值与自身所在节点的名字。如果两个名字一致,这个 kubelet 就会启动 Pod 所需要的容器,并把容器启动成功的信息写入 Pod 的 Status 中,回报到 API 服务器上。

8、ReplicaSet 控制器收到 Pod 状态变化的事件中,不过它没有社么工作要做。

9、最终,Pod 运行结束。kubelet 发现了这个事件,从 API 服务器获取 Pod 的对象,并把 Pod 的状态设置为 “已终止”,写回 API 服务器。

10、ReplicaSet 控制器发现 Pod 运行结束了,它认为这个 Pod 需要被替换掉。它把已终止的 Pod 在 API 服务器上删除,然后重新创建一个新的 Pod。

  多个相互独立的控制器循环完全通过 API 服务器上的对象状态变化相互通信,对象状态的变化由 Informer 以事件的方式通知到控制器。这些事件都是通过 watch 机制在控制器内部由从 API 服务器发向 Informer,也就是流式发送的 watch 事件。这一切对用户来说都是不可见的。甚至在 API 服务器的审计机制中也看不到这些事件,只能看到对象更新的事件。控制器通常在处理事件时,以输出日志的方式来记录它做的动作。

  • watch 事件在 API 服务器与控制器之间通过 HTTP 流的方式发送,用于驱动 Informer。
  • 顶层 event 对象是与 Pod、Deployment 或 Service 等相似的资源,它具有一个特殊属性,即长度为一小时的生命周期。如果这个对象的存在事件超过了这个阈值,就会从 etcd 中自动清除。

四、API 服务器

  kubernetes 集群是由一系列节点组成(kubernetes master nodes 和 kubernetes worker nodes),这些节点属于不同的角色。kubernetes master nodes 包含 API 服务器、控制器管理器和调度器组成。

  API 服务器包含以下的职责:

1、提供 kubernetes API。

2、为其他集群组件提供代理。

  API 服务器提供的 API 包含以下功能:

1、读取状态:获取单个对象、列出对象、获取变更信息。

2、操作状态:创建、更新或删除对象。

  所有的状态都会被持久化在 etcd 中。

  4.1、 API 服务器的 HTTP 接口

  从客户端的角度来看,API 服务器提供了一个 RESTful HTTP API,提供 JSON 或 Protocol Buffers(缩写为 Protobuf)格式的数据内容。出于性能考虑,集群内的通信主要使用 Protobuf。

  API 服务器的 HTTP 接口负责处理 HTTP 请求,这些请求通过指定不同的 HTTP 动词(HTTP Verb,也就 HTTP 方法)来对 kubernetes 的资源进行不同的操作:

  • HTTP GET 方法用于获取特定资源(比如一个 Pod)的相关数据,或者列出一组资源(比如,列出一个命名空间中的所有 Pod)。
  • HTTP POST 方法用于创建资源,比如创建一个 Service 或者一个 Deployment。
  • HTTP PUT 方法用于创建已有的资源,比如:修改一个 Pod 的容器所使用的镜像。
  • HTTP PATCH 方法用于部分更新已有的资源。
  • HTTP DELETE 方法用于销毁一个资源,这个操作是不可逆的。

kubernetes API 参考文档:https://kubernetes.io/docs/reference/#api-overview

4.2、API 术语

Kind

 

Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

 

Kind 一共有 3 种类型的类别:

Object 用于表示系统中的持久化的实体对象,比如,Pod 或 Endpoint。这类对象需要名字,并且它们中的大部分都从术语某个命名空间。

List 是一种或多种实体的列表集。这些列表对象包含少量的公共信息,比如 PodList 或 NodeList。当你执行 kubectl get pods 时就会得到这样一个对象。

其他特殊用途的 kind 会用于某些对象上的特定动作或用于一些非持久化的实体,比如 /binding 或者 /scale。 kubernetes 使用 apigroup 和 apiresource 来提供服务发现结果,使用 Status 来返回出错误结果。

在 Kubernetes 中,每一种 kind 都对应一种 Golang 的类型。所以,与 Golang 其他的类型一样,kind 名字都是以单数形式出现,并以大写字母开头。

API 组

一组逻辑上相关的 Kind。比如所有的批对象,Job 或 ScheduledJob 都属于 batch 这个 API 组。

版本

每个 API 组都允许多个版本共存,并且它们大部分也确实如此。比如有个组在 v1alpha1 版本中出现,持续到 v1beta1 版本并最终在 v1 中正式发布。

在某个特定版本下(比如 v1beat1)创建的对象,可以在其他支持的版本中获取并使用。API 服务器会负责把返回的对象无损地转换成需要的版本。

从集群用户的角度来看,不同的版本只是同一个对象的不同表示方式。

资源

通常来说,小写复数形式的单词(比如 pods)会出现在 HTTP 端点(路径)中,用于暴露对于系统中某种对象的 CURD(创建、读取、更新、删除)操作。