Kubernetes编程——什么是 Kubernetes 编程?
什么是 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(创建、读取、更新、删除)操作。
-
- 根路径,比如 .../pods,用于列出对应类型的所有势力。
- 提供资源的某个特定实例的路径,比如 .../pods/nginx。
通常来说,这些端点会返回或接受一种类型(比如第一种情况下就是一个 PodList,第二种情况下是一个 Pod)。但在其他情况下(比如出错),也可能返回一个 Status 类型的对象。
资源和型别经常会被混淆,它们的区别在于:
-
- 资源都会对应一个 HTTP 路径。
- 型别是通过这些 HTTP 端点返回或接收的对象类型,也是持久化到 etcd 中的实体。
资源总是从属于某个 API 组的某个版本(经常表示为 GroupVersionResource 或 GVR),一个 GVR 唯一确定了一个 HTTP 路径。
例如:在 default 命名空间中,有一个 /apis/batch/v1/namespaces/default/jobs 的 GVR。与 jobs GVR 的例子不同,还有一些集群范围的资源,比如节点或命名空间自身,它们的路径中就不包含这一段。比如一个 nodes GVR 的路径可能是 /api/v1/nodes。注意,$NAMESPACE 在其他 HTTP 路径中看到的命名空间,它们自身也是一种资源,可以通过 /api/v1/namespaces 来访问。
与 GVR 相似,各种型别也是属于某个 API 组的某个版本的,它们被称为 GroupVersionKind(GVK)。
GVK 和 GVR 是相关的,比如 某个 GVR 定义的 HTTP 路径下会提供对相关 GVK 的服务。对 GVK 和 GVR 建立映射关系的过程叫作 REST 映射。
/ ├── / ├── │ ├── //batchbindings ├── │ ├── //bindings ├── │ ├── //componentstatuses ├── │ ├── //configmaps ├── │ ├── //endpoints ├── │ ├── //events ├── │ ├── //limitranges ├── │ ├── //namespaces ├── │ ├── //namespaces/finalize ├── │ ├── //namespaces/status ├── │ ├── //nodes ├── │ ├── //nodes/proxy ├── │ ├── //nodes/status ├── │ ├── //persistentvolumeclaims ├── │ ├── //persistentvolumeclaims/status ├── │ ├── //persistentvolumes ├── │ ├── //persistentvolumes/status ├── │ ├── //pods ├── │ ├── //pods/attach ├── │ ├── //pods/binding ├── │ ├── //pods/eviction ├── │ ├── //pods/exec ├── │ ├── //pods/log ├── │ ├── //pods/portforward ├── │ ├── //pods/proxy ├── │ ├── //pods/status ├── │ ├── //podtemplates ├── │ ├── //replicationcontrollers ├── │ ├── //replicationcontrollers/scale ├── │ ├── //replicationcontrollers/status ├── │ ├── //resourcequotas ├── │ ├── //resourcequotas/status ├── │ ├── //secrets ├── │ ├── //serviceaccounts ├── │ ├── //serviceaccounts/token ├── │ ├── //services ├── │ ├── //services/proxy ├──└── │ ├── //services/status ├── /apiregistration.k8s.io ├── │ ├── /apiregistration.k8s.io/apiservices ├──└── │ ├── /apiregistration.k8s.io/apiservices/status ├── /extensions ├── │ ├── /extensions/ingresses ├──└── │ ├── /extensions/ingresses/status ├── /apps ├── │ ├── /apps/controllerrevisions ├── │ ├── /apps/daemonsets ├── │ ├── /apps/daemonsets/status ├── │ ├── /apps/deployments ├── │ ├── /apps/deployments/scale ├── │ ├── /apps/deployments/status ├── │ ├── /apps/replicasets ├── │ ├── /apps/replicasets/scale ├── │ ├── /apps/replicasets/status ├── │ ├── /apps/statefulsets ├── │ ├── /apps/statefulsets/scale ├──└── │ ├── /apps/statefulsets/status ├── /events.k8s.io ├──└── │ ├── /events.k8s.io/events ├── /authentication.k8s.io ├──└── │ ├── /authentication.k8s.io/tokenreviews ├── /authorization.k8s.io ├── │ ├── /authorization.k8s.io/localsubjectaccessreviews ├── │ ├── /authorization.k8s.io/selfsubjectaccessreviews ├── │ ├── /authorization.k8s.io/selfsubjectrulesreviews ├──└── │ ├── /authorization.k8s.io/subjectaccessreviews ├── /autoscaling ├── │ ├── /autoscaling/horizontalpodautoscalers ├──└── │ ├── /autoscaling/horizontalpodautoscalers/status ├── /batch ├── │ ├── /batch/jobs ├──└── │ ├── /batch/jobs/status ├── /certificates.k8s.io ├── │ ├── /certificates.k8s.io/certificatesigningrequests ├── │ ├── /certificates.k8s.io/certificatesigningrequests/approval ├──└── │ ├── /certificates.k8s.io/certificatesigningrequests/status ├── /networking.k8s.io ├── │ ├── /networking.k8s.io/ingressclasses ├── │ ├── /networking.k8s.io/ingresses ├── │ ├── /networking.k8s.io/ingresses/status ├──└── │ ├── /networking.k8s.io/networkpolicies ├── /policy ├── │ ├── /policy/poddisruptionbudgets ├── │ ├── /policy/poddisruptionbudgets/status ├──└── │ ├── /policy/podsecuritypolicies ├── /rbac.authorization.k8s.io ├── │ ├── /rbac.authorization.k8s.io/clusterrolebindings ├── │ ├── /rbac.authorization.k8s.io/clusterroles ├── │ ├── /rbac.authorization.k8s.io/rolebindings ├──└── │ ├── /rbac.authorization.k8s.io/roles ├── /storage.k8s.io ├── │ ├── /storage.k8s.io/csidrivers ├── │ ├── /storage.k8s.io/csinodes ├── │ ├── /storage.k8s.io/storageclasses ├── │ ├── /storage.k8s.io/volumeattachments ├──└── │ ├── /storage.k8s.io/volumeattachments/status ├── /admissionregistration.k8s.io ├── │ ├── /admissionregistration.k8s.io/mutatingwebhookconfigurations ├──└── │ ├── /admissionregistration.k8s.io/validatingwebhookconfigurations ├── /apiextensions.k8s.io ├── │ ├── /apiextensions.k8s.io/customresourcedefinitions ├──└── │ ├── /apiextensions.k8s.io/customresourcedefinitions/status ├── /scheduling.k8s.io ├──└── │ ├── /scheduling.k8s.io/priorityclasses ├── /coordination.k8s.io ├──└── │ ├── /coordination.k8s.io/leases ├── /node.k8s.io ├──└── │ ├── /node.k8s.io/runtimeclasses ├── /discovery.k8s.io ├──└── │ ├── /discovery.k8s.io/endpointslices ├── /crd.yangtse.cni ├── │ ├── /crd.yangtse.cni/podnetworkinterfacepools ├── │ ├── /crd.yangtse.cni/podnetworkinterfacepools/status ├── │ ├── /crd.yangtse.cni/podnetworkinterfaces ├── │ ├── /crd.yangtse.cni/podnetworkinterfaces/status ├── │ ├── /crd.yangtse.cni/podnetworkinterfaceclaims ├── │ ├── /crd.yangtse.cni/podnetworkinterfaceclaims/status ├──└── │ ├── /crd.yangtse.cni/podnetworkinterfaceqosconfigs ├── /k8s.cni.cncf.io ├──└── │ ├── /k8s.cni.cncf.io/network-attachment-definitions ├── /localvolume.everest.io ├──└── │ ├── /localvolume.everest.io/nodelocalvolumes ├── /config.caicloud.io ├── │ ├── /config.caicloud.io/configclaims ├──└── │ ├── /config.caicloud.io/configreferences ├── /geip.cce.io ├── │ ├── /geip.cce.io/geips ├──└── │ ├── /geip.cce.io/geips/status ├── /microservice.caicloud.io ├──└── │ ├── /microservice.caicloud.io/springclouds ├── /networking.caicloud.io ├──└── │ ├── /networking.caicloud.io/networkpolicies ├── /orchestration.caicloud.io ├── │ ├── /orchestration.caicloud.io/applicationdrafts ├──└── │ ├── /orchestration.caicloud.io/applications ├── /release.caicloud.io ├── │ ├── /release.caicloud.io/releases ├──└── │ ├── /release.caicloud.io/releasehistories ├── /tenant.caicloud.io ├── │ ├── /tenant.caicloud.io/partitions ├── │ ├── /tenant.caicloud.io/tenants ├──└── │ ├── /tenant.caicloud.io/clusterquotas ├── /loadbalance.caicloud.io ├──└── │ ├── /loadbalance.caicloud.io/loadbalancers ├── /config.k8s.io ├──└── │ ├── /config.k8s.io/nodeconfigs ├── /rbac.cce.io ├──└── │ ├── /rbac.cce.io/permissions ├── /resource.caicloud.io ├── │ ├── /resource.caicloud.io/configs ├── │ ├── /resource.caicloud.io/nodelocalstorages ├── │ ├── /resource.caicloud.io/machineautoscalinggroups ├── │ ├── /resource.caicloud.io/requirementgaps ├── │ ├── /resource.caicloud.io/snapshots ├── │ ├── /resource.caicloud.io/clusters ├── │ ├── /resource.caicloud.io/extendedresources ├── │ ├── /resource.caicloud.io/nodeclaims ├── │ ├── /resource.caicloud.io/networks ├── │ ├── /resource.caicloud.io/networks/status ├── │ ├── /resource.caicloud.io/machines ├── │ ├── /resource.caicloud.io/tags ├── │ ├── /resource.caicloud.io/infranetworks ├──└── │ ├── /resource.caicloud.io/resourceclasses ├── /snapshot.storage.k8s.io ├── │ ├── /snapshot.storage.k8s.io/volumesnapshots ├── │ ├── /snapshot.storage.k8s.io/volumesnapshots/status ├── │ ├── /snapshot.storage.k8s.io/volumesnapshotclasses ├── │ ├── /snapshot.storage.k8s.io/volumesnapshotclasses/status ├── │ ├── /snapshot.storage.k8s.io/volumesnapshotcontents ├──└── │ ├── /snapshot.storage.k8s.io/volumesnapshotcontents/status ├── /version.cce.io ├── │ ├── /version.cce.io/packageversions ├──└── │ ├── /version.cce.io/packageversions/status ├── /workload.caicloud.io ├──└── │ ├── /workload.caicloud.io/workloads ├── /custom.metrics.k8s.io ├── /metrics.k8s.io ├── │ ├── /metrics.k8s.io/nodes ├──└── │ ├── /metrics.k8s.io/pods └── ├── /proxy.exporter.k8s.io └── ├──└── │ ├── /proxy.exporter.k8s.io/exporter