Introduction
所谓的云原生应用,能够清楚自己运行在k8s上,并且使用k8s api 和资源当作其扩展。
一方面,云原生应用能够轻便地在不同的云上迁移。另一方面,受益于k8s提供的简洁可定义声明式api。
Extension Patterns
- cloud providers: in-tree controller manager:
- kubelet: network, devices, storage, container runtimes
- extend kubectl with plugins
- extensions in the API server, dynamic admission control with webhook
- Custom resources and custom controllers.
- custom apiserver
- scheduler extensions.
Controllers and operators
- controller: 实现了一个控制循环,从API server监听到的集群状态,并且把当前状态调整到预期状态。
- operator: 是指controller+ 自定义资源的方式,去做像应用生命周期管理相关的一些操作。
the controller loop
- 读取资源的状态,更倾向于事件驱动。
- 改变集群或外部集群的对象的状态。
- 通过apiserver更新存储在etcd中资源的状态。
- 重复循环。回到步骤1.
不管controller的实现复杂与否,这三步骤都是一致的。读取资源状态->改变the world-> 更新资源状态。
informers:
负责watch期望的状态,实现resync机制加强周期性的调谐,通常用来确保集群状态和存在内存中的期望状态不飘动。
work queues:
在client-go的workqueue包中实现了,为event handler提供work queues来“存储”状态变更操作的队列及其各种重试操作。当更新当前资源对象状态出错时,资源需要重新被排序。
Events
k8s的控制面通过事件驱动 进行解耦,其他分布式系统通过远程过程调用来触发各种行为。controller 通过watch k8s对象的变化对APISERVER的增、删、改请求。
example:
- 用户 创建一个dp时,dp controller通过dp informer 创建一个rs。
- rs controller通过 rs informer 创建一个新的rs,并且创建一个pod对象。
- scheduler通过pod informer观测到新的pod,且它的spec.nodeName 为空,就把这个pod放入调度队列。
- 与此同时,kubelet通过pod informer观测到新的pod,且它的spec.nodeName 为空。所以并没有match到kubelet的nodeName,会忽略这个pod 继续等待下一次事件。
- scheduler从workqueue中拿出pod,并且通过更新spec.nodeName字段,调度到有足够资源的指定node,并且写入apiserver。
- kubelet因为pod update事件,继续比较spec.NodeName 和其本身的nodeName. 当match之后,通过启动pod的container,并且把container启动的状态信息写入pod status, 返回存储到apiserver。
- rs controller注意到pod改变。
- 最后pod销毁时,kubelet watch到pod事件并且将pod状态设置为“terminated”状态,并更新到apiserver。
- rs controller观测到结束的pod,决定这个pod一定需要被替换。于是通过apiserver删除 terminated状态的pod,并且创建一个新的。
一系列独立的控制循环,通过watch apiserver中对象的变化,并且这些变更事件通过informers来触发变更。
watch events 和 event对象是两个东西:
- 前者通过在apiserver和controllers之间建立http streaming 的链接用来驱动informers.
- 后者是一种资源,像pod,dp,services这些,带有特殊的时效属性并且自动从etcd里消失。
Edge-driven triggers or Level-driven triggers
前者是基于某个状态值变更的瞬间去触发handler,后者是在某个时间段内、状态被验证与期望值相符,则去触发handler。
后者更像是一种polling轮询,能否扩容到指定数量、控制器注意到变更的延迟依赖于polling轮询的时间间隔以及apiserver的响应时间。与很多异步的控制器相关,最终系统需要花费一段时间才能达到预期状态。
前者效率更高,延迟则依赖在控制器处理事件时,运行的工作线程。因此k8s基于事件驱动,即edge-driven triggers.
reconciliation with resync:每5分钟持续调谐。
edge-triggered, level-driven
level-triggering-and-reconciliation
Changing Cluster Objects or the External world
Optimistic Concurrency
单体式:
二阶段:
共享状态:
并行调度架构:
新一代的并发调度架构依赖共享状态,使用乐观并发控制达到实现可扩展和性能可伸缩。
k8s 中乐观锁的场景是:
retry loop中,获取了foo对象最新的状态后,尝试根据foo spec更新real world 和foo status,实际修改的操作在Update事件之前执行。
通过client.Get返回的foo对象,包含资源的版本,ObjectMeta结构体。这个结构体会在client.Update调用时,通过写操作更新etcd的数据。所谓的资源版本,在etcd中存储的实际上是<string,int> 键值对。etcd维护了一个计数器counter,每次key的值被修改时,counter值加一。
在API machinery代码中,资源版本的处理像是任意的string,实际上还是遵循一些规则。实际的实现细节在etcd存储后端。
essence: 控制器中的冲突错误十分常见,处理和期待十分优雅。
Operators
operator是一种针对特定应用的控制器,扩展了k8s API的创建、配置、管理复杂的有状态应用的实例。
一个operator包括一个 CRD+ controller。
Kubernetes API Basics
apiserver作为挡在存储etcd前的唯一组件,跟etcd进行直接交互。主要职责是作为k8s api的server 和集群组件的proxy。
伺服API意味着 读取状态和 操作状态。
声明式状态管理
通过比较期望状态的spec和当前实际状态的值,
例如:当你在dp的声明yaml里指定20个副本数,dp controller读到dp spec并且创建rs,实际去接管rs- 即特定个数的pods。如果有任何副本跪了,dp controller会让你在状态中感知到。
APIServer Processes Requests
DefaultBuildHandlerChain()
WithPanicRecovery(): 处理恢复和日志panic
Basics of client-go
Repos
ClientLibrary
k8s项目提供client-go(https://github.com/kubernetes/client-go) 作为用户的开发工具集,提供了多种类型的API支持,以及包含许多通用库代码。
K8S API Types
client-go管理了客户端接口,像PODS,services,deployments这些接口,在这个repo中维护(https://github.com/kubernetes/api),pod等的类型在k8s.io/api/core/v1/types.go
中定义,而这些文件均由code gen自动生成维护。
// Pod is a collection of containers that can run on a host. This resource is created
// by clients and scheduled onto hosts.
type Pod struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Specification of the desired behavior of the pod.
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
// +optional
Spec PodSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
// Most recently observed status of the pod.
// This data may not be up to date.
// Populated by the system.
// Read-only.
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
// +optional
Status PodStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
API machinery
第三个repo(https://github.com/kubernetes/apimachinery ) 包含所有通用的构建模块,不仅限于容器管理,还包括其他构建 APIs。在pkg/api/meta/v1 下可以查看到许多API类型的定义,包括如下:
ObjectMeta: Name + Namespaces + ResourceVersion + Labels + Annotations
TypeMeta : Kind + APIVersion
GetOptions,
ListOptions
构建模块用来创建k8s客户端对象,代表在k8s集群中能访问的资源。
版本和兼容
k8s 的api是按版本区分的,一方面client的不同版本去访问API server,如果版本不匹配,请求会失败。client 与特定的版本绑定,应用开发者需要选择正确的
其版本与k8s版本对应关系如图:
| Branch | Canonical source code location | Maintenance status |
| `release-1.4` | Kubernetes main repo, 1.4 branch | = - |
| `release-1.5` | Kubernetes main repo, 1.5 branch | = - |
| `release-2.0` | Kubernetes main repo, 1.5 branch | = - |
| `release-3.0` | Kubernetes main repo, 1.6 branch | = - |
| `release-4.0` | Kubernetes main repo, 1.7 branch | = - |
| `release-5.0` | Kubernetes main repo, 1.8 branch | = - |
| `release-6.0` | Kubernetes main repo, 1.9 branch | = - |
| `release-7.0` | Kubernetes main repo, 1.10 branch | = - |
| `release-8.0` | Kubernetes main repo, 1.11 branch | =- |
| `release-9.0` | Kubernetes main repo, 1.12 branch | =- |
| `release-10.0` | Kubernetes main repo, 1.13 branch | =- |
| `release-11.0` | Kubernetes main repo, 1.14 branch | ✓ |
| `release-12.0` | Kubernetes main repo, 1.15 branch | ✓ |
| `release-13.0` | Kubernetes main repo, 1.16 branch | ✓ |
| `release-14.0` | Kubernetes main repo, 1.17 branch | ✓ |
| client-go HEAD | Kubernetes main repo, master branch | ✓ |
Key:
-
✓
Changes in main Kubernetes repo are actively published to client-go by a bot -
=
Maintenance is manual, only severe security bugs will be patched. -
-
Deprecated; please upgrade.
你看代码有哪些用的较多的公用库呀: sharedInformer、workqueue、event、leaderelection、rest、scheme、flowcontrol、codec
K8S objects in Go
k8s资源是一种kind的实例,在API server作为一种资源结构体提供服务。
k8s中的object 实现了runtime.Object接口,来自于k8s.io/apimachinery/pkg/runtime包。其中,schema.ObjectKind主要实现的另一个接口,来自于k8s.io/apimachinery/pkg/runtime/schema.
这些Go实现的object作为数据结构主要是用来返回和设置GroupVersionKind, 以及用来深度拷贝。
type Object interface {
GetObjectKind () schema.ObjectKind
DeepCopyObject () Object
}
type ObjectKind interface {
// SetGroupVersionKind sets or clears the intended serialized kind of an
// object. Passing kind nil should clear the current setting.
SetGroupVersionKind ( kind GroupVersionKind )
// GroupVersionKind returns the stored group, version, and kind of an // object, or nil if the object does not expose or provide these fields.
GroupVersionKind () GroupVersionKind
}
TypeMeta
通过嵌套metav1.TypeMeta(k8s.io/apimachinery/meta/v1)来实现k8s对象schema.ObjectMeta(k8s.io/api)的getter和setter类型。
// TypeMeta describes an individual object in an API response or request
// with strings representing the type of the object and its API schema version.
// Structures that are versioned or persisted should inline TypeMeta.
//
// +k8s:deepcopy-gen=false
type TypeMeta struct {
// 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/api-conventions.md#types-kinds
// +optional
Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"`
// APIVersion defines the versioned schema of this representation of an object.
// Servers should convert recognized schemas to the latest internal value, and
// may reject unrecognized values.
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources
// +optional
APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"`
}
// Pod is a collection of containers that can run on a host. This resource is created
// by clients and scheduled onto hosts.
type Pod struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Specification of the desired behavior of the pod.
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
// +optional
Spec PodSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
// Most recently observed status of the pod.
// This data may not be up to date.
// Populated by the system.
// Read-only.
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
// +optional
Status PodStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
基于client-go的应用这些字段在内存中值为空,在实际序列化成json或者pb时,才会有实际的值。而这些约定在版本序列中自动实现。
ObjectMeta
出了TypeMeta,大部分top-level对象还会包括metav1.ObjectMeta,来自k8s.io/apimachinery/pkg/meta/v1。
(staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go)
type ObjectMeta struct {
Name string `json:"name,omitempty"`
Namespace string `json:"namespace,omitempty"`
UID types.UID `json:"uid,omitempty"`
ResourceVersion string `json:"resourceVersion,omitempty"` CreationTimestamp Time `json:"creationTimestamp,omitempty"` DeletionTimestamp * Time `json:"deletionTimestamp,omitempty"`
Labels map [ string ] string `json:"labels,omitempty"`
Annotations map [ string ] string `json:"annotations,omitempty"`
...
}
metav1.ObjectMeta基本包含所有元数据信息,像name,namespace,resource version, 一些时间戳,以及labels 和annotation. 其中client-go并不能对resource version进行读写,但却是整个k8s代码中核心工作的重要信息.
它作为ObjectMeta信息的一部分,来源于etcd里所有数据的key字段。
Client Sets
kubernetes.NewForConfig(config)返回一个 client set, 用它可以访问定义在k8s.io/api中的大部分API groups和resources,除了APIServices(aggregated API servers)和CRD。
clientSet主接口如下:
type Interface interface {
Discovery() discovery.DiscoveryInterface
AdmissionregistrationV1alpha1() admissionregistrationv1alpha1.AdmissionregistrationV1alpha1Interface
AdmissionregistrationV1beta1() admissionregistrationv1beta1.AdmissionregistrationV1beta1Interface
// Deprecated: please explicitly pick a version if possible.
Admissionregistration() admissionregistrationv1beta1.AdmissionregistrationV1beta1Interface
AppsV1beta1() appsv1beta1.AppsV1beta1Interface
AppsV1beta2() appsv1beta2.AppsV1beta2Interface
AppsV1() appsv1.AppsV1Interface
// Deprecated: please explicitly pick a version if possible.
Apps() appsv1.AppsV1Interface
AuthenticationV1() authenticationv1.AuthenticationV1Interface
// Deprecated: please explicitly pick a version if possible.
Authentication() authenticationv1.AuthenticationV1Interface
AuthenticationV1beta1() authenticationv1beta1.AuthenticationV1beta1Interface
AuthorizationV1() authorizationv1.AuthorizationV1Interface
// Deprecated: please explicitly pick a version if possible.
Authorization() authorizationv1.AuthorizationV1Interface
AuthorizationV1beta1() authorizationv1beta1.AuthorizationV1beta1Interface
AutoscalingV1() autoscalingv1.AutoscalingV1Interface
// Deprecated: please explicitly pick a version if possible.
Autoscaling() autoscalingv1.AutoscalingV1Interface
AutoscalingV2beta1() autoscalingv2beta1.AutoscalingV2beta1Interface
BatchV1() batchv1.BatchV1Interface
// Deprecated: please explicitly pick a version if possible.
Batch() batchv1.BatchV1Interface
BatchV1beta1() batchv1beta1.BatchV1beta1Interface
BatchV2alpha1() batchv2alpha1.BatchV2alpha1Interface
CertificatesV1beta1() certificatesv1beta1.CertificatesV1beta1Interface
// Deprecated: please explicitly pick a version if possible.
Certificates() certificatesv1beta1.CertificatesV1beta1Interface
CoreV1() corev1.CoreV1Interface
// Deprecated: please explicitly pick a version if possible.
Core() corev1.CoreV1Interface
EventsV1beta1() eventsv1beta1.EventsV1beta1Interface
// Deprecated: please explicitly pick a version if possible.
Events() eventsv1beta1.EventsV1beta1Interface
ExtensionsV1beta1() extensionsv1beta1.ExtensionsV1beta1Interface
// Deprecated: please explicitly pick a version if possible.
Extensions() extensionsv1beta1.ExtensionsV1beta1Interface
NetworkingV1() networkingv1.NetworkingV1Interface
// Deprecated: please explicitly pick a version if possible.
Networking() networkingv1.NetworkingV1Interface
PolicyV1beta1() policyv1beta1.PolicyV1beta1Interface
// Deprecated: please explicitly pick a version if possible.
Policy() policyv1beta1.PolicyV1beta1Interface
RbacV1() rbacv1.RbacV1Interface
// Deprecated: please explicitly pick a version if possible.
Rbac() rbacv1.RbacV1Interface
RbacV1beta1() rbacv1beta1.RbacV1beta1Interface
RbacV1alpha1() rbacv1alpha1.RbacV1alpha1Interface
SchedulingV1alpha1() schedulingv1alpha1.SchedulingV1alpha1Interface
SchedulingV1beta1() schedulingv1beta1.SchedulingV1beta1Interface
// Deprecated: please explicitly pick a version if possible.
Scheduling() schedulingv1beta1.SchedulingV1beta1Interface
SettingsV1alpha1() settingsv1alpha1.SettingsV1alpha1Interface
// Deprecated: please explicitly pick a version if possible.
Settings() settingsv1alpha1.SettingsV1alpha1Interface
StorageV1beta1() storagev1beta1.StorageV1beta1Interface
StorageV1() storagev1.StorageV1Interface
// Deprecated: please explicitly pick a version if possible.
Storage() storagev1.StorageV1Interface
StorageV1alpha1() storagev1alpha1.StorageV1alpha1Interface
}
在这个接口中存在一些没有版本的方法:appsv1beta1.AppsV1beta1Interface。
>在过去k8s有所谓的内部客户端,为了对象在内存中存在的更加通用,并且遵循在需要的时候再定义的规范。这种规范为了从实际使用的API版本中抽象controller代码,同时方便切换不同版本。事实上,为了遵循这种规范,增加了大量的复杂性,付出了不太值得的代价。 >此外,在client和APIServer之间的交互,并没有自动协商的机制。尽管存在内部的版本和客户端,controller硬编码到指定版本导致并没有很好兼容。 >在最近的版本,k8s代码尽量避免用到这些内部版本。
所有clientset可以访问discovery client,被用作RESTMappers;
type AppsV1beta1Interface interface {
RESTClient() rest.Interface
ControllerRevisionsGetter
DeploymentsGetter
ScalesGetter
StatefulSetsGetter
}
在每个GroupVersion(AppsV1beta1)下我们发现API group的资源都存在通用的RESTClient
Status subresources: UpdateStatus
来源:oschina
链接:https://my.oschina.net/markz0928/blog/3163481