pod内的网络共享
pod是一组共享了资源的容器 ,所有容器共享同一个network namespace,并可以声明共享同一个Volume pod内多个容器是对等关系,所以pod内引入了一个中间容器,叫infra容器(汇编语言写的,占用极少的资源,永远处于暂停状态的容器,解压大小为100-200kb),在pod中,infra容器是第一个被创建的,其他用户定义的容器,则通过 Join Network Namespace 的方式,与 Infra 容器关联在一起。
对于pod内的不同容器来说
不同容器可以通过localhost通信 不同容器看到的网络设备一致,和infra内看到的也一样 一个pod只有一个IP地址,网络资源也是一个pod一份 pod的生命周期只与infra容器一致,与pod内其他容器无关 一个pod流量的进出,都是通过infra容器完成的 。如果要为 k8s 开发一个网络插件时,应该重点考虑的是如何配置这个 Pod 的 Network Namespace ,而不是每一个用户容器如何使用你的网络配置
如果网络插件需要在容器里安装某些包或者配置才能完成的话,是不可取的:Infra 容器镜像的 rootfs 里几乎什么都没有,没有随意发挥的空间。当然,这同时也意味着你的网络插件完全不必关心用户容器的启动与否,而只需要关注如何配置 Pod,也就是 Infra 容器的 Network Namespace 即可。
pod内的存储共享
k8s 项目只要把所有 Volume 的定义都设计在 Pod 层级 即可。这样,一个 Volume 对应的宿主机目录对于 Pod 来说就只有一个 ,Pod 里的容器只要声明挂载这个 Volume,就一定可以共享这个 Volume 对应的宿主机目录
容器设计模式(用Pod的设计思想来指导容器编排)
Volume
projected Volume:在 k8s 中,有几种特殊的 Volume,它们存在的意义不是为了存放容器里的数据,也不是用来进行容器和宿主机之间的数据交换。这些特殊 Volume 的作用是为容器提供预先定义好的数据 。到目前为止,k8s 支持的 Projected Volume 一共有四种: Secret:存放加密的键值对(默认是base64加密存放,在真正的生产环境中需要在 k8s 中开启 Secret 的加密插件,增强数据的安全性。)
configmap:与Secret类似,唯一区别是不加密 downward api:让 Pod 里的容器能够直接获取到这个 Pod API 对象本身的信息,不过Downward API 能够获取到的信息,一定是 Pod 里的容器进程启动之前就能够确定下来的信息 。而如果想要获取 Pod 容器运行后才会出现的信息,比如,容器进程的 PID,那就肯定不能使用 Downward API 了,而应该考虑在 Pod 里定义一个 sidecar 容器。 serviceAccountToken:Service Account 的授权信息和文件 保存在它所绑定的一个特殊的 Secret 对象里的。这个特殊的 Secret 对象,就叫作 ServiceAccountToken
serviceAccount:一种“服务账户”,它是 k8s 进行权限分配的对象。比如,Service Account A,可以只被允许对 Kubernetes API 进行 GET 操作,而 Service Account B,则可以有 Kubernetes API 的所有操作权限。 k8s 提供了一个默认“服务账户”(default Service Account)。并且,任何一个运行的 Pod,都可以直接使用这个默认的 Service Account,而无需显示地声明挂载(依靠projected Volume) 。一旦 Pod 创建完成,容器里的应用就可以直接从这个默认 ServiceAccountToken 的挂载目录里访问到授权信息和文件,这种把 Kubernetes 客户端以容器的方式运行在集群里,然后使用 default Service Account 自动授权的方式,被称作“InClusterConfig”。
健康检查和恢复机制
定义一个健康检查“探针”(LivenessProbe)。这样,kubelet 就会根据这个 Probe 的返回值决定这个容器的状态 ,而不是直接以容器镜像是否运行(来自 Docker 返回的信息)作为依据。 当pod为unhealthy状态时,会根据restartPolicy确认pod的恢复机制,需要注意,Pod 的恢复过程,永远都是发生在当前节点上,而不会跑到别的节点上 。事实上,一旦一个 Pod 与一个节点(Node)绑定,除非这个绑定发生了变化(pod.spec.node 字段被修改),否则它永远都不会离开这个节点。这也就意味着,如果这个宿主机宕机了,这个 Pod 也不会主动迁移到其他节点上去(如果要调度到其他节点上,需要修改deployment)
restartPolicy
默认为Always,只要容器不在运行状态,就自动重启容器 OnFailure: 只在容器异常时才自动重启容器 Never: 从来不重启容器
restart policy和pod状态对应关系
只要 Pod 的 restartPolicy 指定的策略允许重启异常的容器(比如:Always),那么这个 Pod 就会保持 Running 状态,并进行容器重启。否则,Pod 就会进入 Failed 状态 对于包含多个容器的 Pod,只有它里面所有的容器都进入异常状态后,Pod 才会进入 Failed 状态。在此之前,Pod 都是 Running 状态 readinessProbe,探针检查结果成功与否,决定了这个 Pod 是不是能被通过 Service 的方式访问到,而并不影响 Pod 的生命周期
控制器模型
Pod 这个看似复杂的 API 对象,实际上就是对容器的进一步抽象和封装 而已。k8s中所有控制器都遵循一个通用的编排模式:即控制循环 ,循环对比实际状态(status)和期望状态(spec),然后执行动作到达期望状态
实际状态来源:kubelet通过心跳汇报 、监控系统中保存的应用数据、控制器自己收集 期望状态来源:创建deployment创建的yaml
deployment控制器模型实现
从etcd中获取到携带了selecctor中matchLabels标签的pod,并获取状态(实际状态) deployment的yaml中定义了期望状态 deployment对两个状态作对比,根据结果对pod进行写操作。这个阶段也成reconcile,调谐循环或同步循环,最终结果是某种对象的写操作
被控制对象的定义来源于模板template字段
副本与水平扩展
pod的水平扩容/伸缩,滚动更新都依赖的是replicaset
一个 ReplicaSet 对象,是由副本数目的定义 和一个 Pod 模板 组成的,定义就是deployment的一个子集 Deployment 控制器实际操纵的,正是这样的 ReplicaSet 对象,而不是 Pod 对象 滚动更新的好处:
在滚动更新过程中,如果新版本pod有问题启动不起来,那么“滚动更新”就会停止,从而允许开发和运维人员介入。而在这个过程中,由于应用本身还有旧版本的 Pod 在线,所以服务并不会受到太大的影响。
为了保证服务连续性,在任何时间窗口内,只有指定比例的 Pod 处于离线状态。同时,它也会确保只有指定比例的新 Pod 被创建出来
通过多个 ReplicaSet 对象,Kubernetes 项目就实现了对多个“应用版本”的描述。
之所以使用ReplicaSet对象来管理pod对象,而不是deployment亲自管理pod对象;原因就在于通过ReplicaSet对象管理,可以实现通过一个Deployment对象来管理应用程序多个版本的更新和回退 。 即抽象出ReplicaSet对象这个概念,可以将Deployment对象与pod对象进行解耦,使得Deployment对象可以更加关注于对应用程序版本更新/回退的管控 ,而弹性伸缩 的细节就可以让ReplicaSet 对象来协助管理。 如果Deployment对象只需要提供pod弹性伸缩的功能,则本质上是不需要ReplicaSet对象的。
statefulset
deployment这种控制器模型不能覆盖所有的应用编排场景。因为对deployment来说,一个应用的所有 Pod,是完全一样的 。所以,它们互相之间没有顺序,也无所谓运行在哪台宿主机上。需要的时候,Deployment 就可以通过 Pod 模板创建新的 Pod;不需要的时候,Deployment 就可以“杀掉”任意一个 Pod 在实际场景中(尤其是分布式应用 ),多个实例间往往存在依赖关系,比如:主从关系、主备关系,在这种实例之间不对等的关系情况下,以及实例对外部数据有依赖关系的应用,就被称为“有状态应用”(Stateful Application) statefulset设计
拓扑状态:应用的多个实例之间不是完全对等的关系。这些应用实例必须按照某些顺序启动,比如应用的主节点 A 要先于从节点 B 启动。而如果你把 A 和 B 两个 Pod 删除掉,它们再次被创建出来时也必须严格按照这个顺序才行。并且,新创建出来的 Pod,必须和原来 Pod 的网络标识一样 ,这样原先的访问者才能使用同样的方法,访问到这个新 Pod。 存储状态:多个实例分别绑定了不同的存储数据 。对于这些应用实例来说,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间 Pod A 被重新创建过。这种情况最典型的例子,就是一个数据库应用的多个存储实例。
statefulset拓扑状态保持
headless service
集群内可以通过访问svc访问到pod,访问svc有两种方式
通过svc的虚拟IP 通过DNS(svc.namespace.svc.cluster.local),该方式还可以分为两个方法
Normal Service。这种情况下,访问解析到的是 svc 这个 Service 的 虚拟IP,后面的流程就跟方式一一致 Headless Service。这种情况下访问解析到的直接就是 svc 代理的某一个 Pod 的 IP 地址。这里的区别在于,Headless Service 不需要分配一个虚拟IP,而是可以直接以 DNS 记录的方式解析出被代理 Pod 的 IP 地址,适用于分布式集群场景
直接访问每个 Pod:在分布式存储系统中,通常需要直接与每个存储节点进行通信,而不是通过负载均衡器或代理,Headless Service 可以通过 DNS 解析直接获取每个Pod的 IP 地址列表,使你可以直接与每个节点建立连接 简化服务发现:分布式存储系统通常需要对存储节点进行动态的服务发现,以便将请求路由到可用的节点。使用 Headless Service,你可以通过 DNS 查询获得存储节点的 IP 地址,无需复杂的服务发现机制,从而简化了服务发现的过程 无需额外负载均衡层:在一些分布式存储系统中,每个存储节点本身就具备负载均衡和故障转移的能力。通过直接访问每个存储节点,你可以利用存储系统自身的负载均衡机制,无需引入额外的负载均衡层,从而减少了系统的复杂性和性能开销
灵活的部署和扩展:使用 Headless Service,每个存储节点都可以以独立的 Pod 形式部署和扩展。这使得分布式存储系统可以根据需求动态地添加或删除存储节点,而无需影响整体服务的可用性和性能。
Headless service 与 service vip最本质的区别就是
headless service -->> coredns -->> podip ,dns直接代理所有的pod。pod之间可以互相访问,这样对于一些集群类型的应用就可以解决互相之间身份识别的问题了 service -->> coredns -->> vip(clusterip) -->> 通过iptables规则负载pod -->> real server (podip)
headless service代理的所有pod的ip地址,都会被绑定到DNS记录中,只要有pod、svc名称就能找到对应pod的ip地址
sts的yaml,相比于deployment,多了serviceName字段 ,这个字段的作用,就是告诉 sts 控制器,在执行控制循环的时候,使用 serivceName字段值的 这个 Headless Service 来保证 Pod 的“可解析身份”。 sts会给它管理的所有 Pod 进行编号,从 0 开始累加,与 sts的每个 Pod 实例一一对应,绝不重复。同时创建也是严格按照编号顺序进行。比如,在 web-0 进入到 Running 状态、并且Conditions成为 Ready 之前,web-1 会一直处于 Pending 状态 通过headless service可以做到直接通过域名访问的方式直接访问指定的pod,而不是访问通过VIP访问到svc,再由svc负载均衡到某一个pod 。k8s为每个pod通过名称+编号的形式固定了pod的拓扑关系,也为每个pod提供了唯一的访问入口
statefulset存储状态保持
如果每个应用自己申请声明volume,有几个问题
Volume的每个字段难以理解 安全问题,类似如IP、秘钥文件路径这些直接暴露给开发人员不安全
因此引入了pvc和pv,降低了使用持久化Volume的门槛,有了pvc后声明使用pv只需要两步
定义一个pvc,声明想要Volume的属性,如**存储大小、读写模式(读写模式包括:
readyWriterOnce(RWO):只能在一个节点上被读写,不能跨节点声明 readOnlyMany(ROX):只读,可以被多个节点挂载声明 readWriteMany(RWX):可读写,可以被多个节点挂载声明 readyWriterOncePod(RWOP):可读写,只能被一个pod声明**
在应用中,声明使用pvc
k8s 中 PVC 和 PV 的设计,实际上类似于“接口”和“实现”的思想。开发者只要知道并会使用“接口”,即:PVC;而运维人员则负责给“接口”绑定具体的实现,即:PV。 PVC 与 PV 的绑定得以实现的前提是系统里存在创建好了符合条件的 PV;或者,k8s 集群运行在公有云上,这样 k8s 就会通过 Dynamic Provisioning 的方式 ,自动为你创建与 PVC 匹配的 PV
statefulset原理
StatefulSet 的控制器直接管理的是 Pod(pod的ownerReference就是sts) 。这是因为,StatefulSet 里的不同 Pod 实例,不再像 ReplicaSet 中那样都是完全一样的,而是有了细微区别的。比如,每个 Pod 的 hostname、名字(序号不仅表示了启动顺序,也标志不同的网络标识)等都是不同的 ,而 StatefulSet 区分这些实例的方式,就是通过在 Pod 的名字里加上事先约定好的编号。 k8s 通过 Headless Service,为这些有编号的 Pod,在 DNS 服务器(coreDNS)中生成带有同样编号的 DNS 记录 。只要 StatefulSet 能够保证这些 Pod 名字里的编号不变,那么 Service 里类似于 web-0.nginx.default.svc.cluster.local 这样的 DNS 记录也就不会变,而这条记录解析出来的 Pod 的 IP 地址,则会随着后端 Pod 的删除和再创建而自动更新 。 StatefulSet 为每一个 Pod 分配并创建一个同样编号的 PVC(通过volumeClaimTemplates) 。这样,Kubernetes 就可以通过 Persistent Volume 机制为这个 PVC 绑定上对应的 PV,从而保证了每一个 Pod 都拥有一个独立的 Volume。 sts的滚动更新是从后往前进行 的,原因为
有状态应用的依赖关系:StatefulSet 中的有状态应用通常存在依赖关系 ,例如主从复制、分布式存储等。以数据库为例,如果先更新了前面的 Pod,那么后面的 Pod 在更新时,外部请求可能会连接到一个已经更新的 Pod,导致数据不一致或中断服务。因此,从后往前的更新顺序可以确保更新的 Pod 在进行连接和同步时始终连接到稳定的旧版本 Pod 。 稳定的网络标识符:有状态应用通常会使用稳定的网络标识符 (如主机名或 DNS 记录)来标识彼此。当一个有状态应用的 Pod 更新时,会保留其稳定的网络标识符,以便其他应用或服务能够继续与其进行通信。从后往前的更新顺序可以确保更新的 Pod 在更新过程中能够保持其稳定的网络标识符,避免应用之间的连接中断。 数据迁移和持久化存储:有状态应用通常需要迁移数据或使用持久化存储。从后往前的更新顺序可以使得更新的 Pod 在更新过程中能够正确地迁移数据或重新连接到持久化存储,确保数据的完整性和可用性。
sts的滚动更新还能做到更精细的控制,如金丝雀分布、灰度发布
金丝雀发布是一种将新版本或功能引入到现有生产环境中的策略,但只在一小部分用户或系统上进行测试和评估 。它的目标是在真实环境中评估新版本或功能的性能、稳定性和用户体验,以便在全面部署之前及时发现和解决任何问题灰度发布是**一种逐步引入新版本或功能的策略,通过逐渐增加受影响的用户或系统来降低风险。**它的目标是在控制风险的同时,逐步将新版本或功能推广给更多的用户或系统,以确保其稳定性和可用性。
sts中rollingUpdate中partition字段值控制可以实现灰度发布 (只有序号大于等于partition的pod才会被更新,序号小于partition参数的pod不会被更新)
daemonset
Job与CronJob
Deployment、StatefulSet,以及 DaemonSet主要编排的是Long running work job controller工作原理
Job Controller 控制的对象直接就是 Pod 其次,Job Controller 在控制循环中Reconcile操作,是根据实际在 Running 状态 Pod 的数目 、成功退出的 Pod 的数目 ,以及 parallelism(最大并发) 、completions (需要成功运行完成的数量)参数的值共同计算出在这个周期里,应该创建或者删除的 Pod 数目,然后调用 Kubernetes API 来执行这个操作
三种常用的用法
外部管理器 +Job 模板 拥有固定任务数目的并行 Job:只关心最后是否有指定数目(completions)个任务成功退出,不关心并行度 指定并行度(parallelism),但不设置固定的 completions 的值
CronJob:CronJob是job对象的控制器,yaml中最关键就是jobTemplate(CronJob与Job的关系类似于Deployment与replicaset的关系) 由于定时任务的特殊性,很可能某个 Job 还没有执行完,另外一个新 Job 就产生了,需要通过concurrencyPolicy 字段来定义具体的处理策略
Allow(默认情况):Job 可以同时存在 Forbid:不会创建新的 Pod,该创建周期被跳过 Replace:新产生的 Job 会替换旧的、没有执行完的 Job
startingDeadlineSeconds:如果job创建失败就会被标记为“miss”。当DeadlineSeconds范围内,miss 的数目达到 100 时,那么 CronJob 会停止再创建这个 Job
k8s声明式与命令式
对现有api对象的修改(Patch)称为声明式API,比如apply、set、edit,api-server在响应声明式请求的时候,一次能处理多个写操作,并具备merge能力
替换现有对象是一种命令式操作,比如replace,api-server在响应命令式请求时,只能处理一个写请求,否则可能产生冲突 每一个API对象在etcd里的的资源路径(定义一个资源对象):资源对象组->资源版本->资源名称,资源对象创建过程:
提交资源对象的yaml信息到api-server,apiserver过滤请求,前置工作(如授权、超时处理、审计) 请求进入MUX和Routes过程,完成url和handler绑定,找到对应Cronjob类型定义 apiserver根据CronJob定义,使用yaml定义创建CronJob对象,在这个过程中,会进行convert操作,即把用户提交的yaml转成superVerson对象(API 资源类型所有版本的字段全集)
Admission和Validation操作,验证通过的api对象会存入apiserver里的registry apiserver 把验证过的 API 对象转换成用户最初提交的版本,进行序列化操作,并调用 Etcd 的 API 把它保存起来
添加一个自定义对象
添加一个自定义对象流程
在GOAPTH路径下,创建结构项目(也可以自定义一个yaml,然后通过operator自动生成)
$GOPATH/src/github.com/<your- name> /k8s- controller- custom- resource
.
├── controller.go
├── crd
│ └── network.yaml
├── example
│ └── example- network.yaml
├── main.go
└── pkg
└── apis
└── samplecrd //groupname
├── register.go
└── v1 // version
├── doc.go
├── register.go ///放置后面要用到的全局变量
└── types.go // crd struct
通过代码生成工具code-generator为自定义资源生成client、informer、lister
├── controller.go
├── crd
│ └── network.yaml
├── example
│ └── example- network.yaml
├── main.go
└── pkg
├── apis
│ └── samplecrd
│ ├── constants.go
│ └── v1
│ ├── doc.go
│ ├── register.go
│ ├── types.go
│ └── zz_generated.deepcopy.go //自动生成的 DeepCopy 代码文件
└── client
├── clientset
├── informers
└── listers
添加一个自定义对象控制器
声明式 API不像命令式 API有着明显的执行逻辑。使得基于声明式 API 的业务功能实现,往往需要通过控制器模式来“监视”API 对象的变化,然后以此来决定实际要执行的工作。
import (
"fmt"
)
func main ( ) {
stopCh := signals. SetupSignalHandler ( )
cfg, err := clientcmd. BuildConfigFromFlags ( masterUrl, kubeconfig)
kubeClient, err := kubernetes. NewForConfig ( cfg)
networkCleint, err := clientset. NewForConfig ( cfg)
networkInformerFactory := informers. NewSharedInformerFactory ( networkClient, ... )
controller := NewController ( kubeClient, networkClient, networkInformerFactory. Samplecrd ( ) . V1 ( ) . Networks ( ) )
go networkInformerFactory. Start ( stopCh)
if err = controller. Run ( 2 , stopCh) ; err != nil {
glog. Fatalf ( "Error running controller: %s" , err. Error ( ) )
}
}
自定义对象控制器流程
根据Master 配置(APIServer 的地址端口和 kubeconfig 的路径),创建一个 Kubernetes 的 client(kubeClient)和 Network 对象的 client(networkClient) 创建一个叫作 InformerFactory的工厂,并使用它生成一个 Network 对象的 Informer ,传递给控制器 启动Informer,然后执行 controller.Run,启动自定义控制器
自定义控制器原理
informer的两个职责,本质上就是一个带有本地缓存和索引机制 的、可以注册eventHandler 的client
同步本地缓存 根据事件类型,触发事先注册好的 ResourceEventHandler
控制器首先从apiserver里获取关心的对象 ,Informer 与 API 对象 是一一对应的,在创建informer的时候,传递一个client,正是这个client与apiserver建立了连接,负责维护连接的则是informer使用的reflector (使用内置的ListAndWatch方法获取和监听资源) 在ListAndWatch监听下,当监视的自定义资源对象发生变化,reflector会收到“事件通知”,这时该增量变化会被放进Delta FIFO队列中 发生变化的包含:
Create:k8s对象的创建流程为:
判断对象的 resourceVersion 是否合法 ,如果 resourceVersion != 0,则抛出错误 对待处理对象做一些预处理:把 resourceVersion 和 selfLink 置为空 对待处理对象进行编码,转换成二进制,进而转换成可被 ETCD 接受的格式 判断 key 是否已存在,如果不存在,则存入 ETCD,否则返回错误信息 记录执行耗时 返回存储好的数据,并将 ETCD 中更新后的 Revision 设置为 resourceVersion
Update:k8s对于更新操作提供了update和patch两种方法,但判断冲突的机制是相同的
获取当前更新请求中 obj 对象的 ResourceVersion 值,及服务器端最新 obj 对象 (existing) 的 ResourceVersion 值 如果当前更新请求中 bj 对象的 ResourceVersion 值等于 0,即客户端未设置该值,则判断是否要硬改写 (AllowUnconditionalUpdate),如配置为硬改写策略,将直接更新 obj 对象 如果当前更新请求中 obj 对象的 ResourceVersion 值不等于 0,则判断两个 ResourceVersion 值是否一致,不一致返回冲突错误 (OptimisticLockErrorMsg)
Patch
首先判断 patch 的类型,根据类型选择相应的 mechanism 利用 DefaultUpdatedObjectInfo 方法将 applyPatch (应用 Patch 的方法) 添加到 admission chain 的头部 最终还是调用 Update 方法执行更新操作
Delete
判断目标对象类型是否正确:是否为指针类型,是否不为 nil 删除之前,先从 ETCD 中获取对应的数据,并判断该删除操作是否满足前置条件 通过比对 ModVersion 判断这段时间内目标对象是否被其他进程 / 线程修改,如果未被修改,则执行删除操作;否则执行 Get 操作,删除失败,打印错误信息,并重新尝试删除 删除成功,返回被删除的数据
Informer 会不断地从这个 Delta FIFO Queue 里读取(Pop)增量。每拿到一个增量,Informer 就会判断这个增量里的事件类型,然后创建或者更新本地对象的缓存。这个缓存,在 Kubernetes 里一般被叫作 Store。比如,如果事件类型是添加对象,Informer 就会通过Indexer 的库把这个增量里的 API 对象保存在本地缓存中,并为它创建索引。相反,如果增量的事件类型是 Deleted(删除对象),那么 Informer 就会从本地缓存中删除这个对象。
func NewController (
kubeclientset kubernetes. Interface,
networkclientset clientset. Interface,
networkInformer informers. NetworkInformer) * Controller {
...
controller := & Controller{
kubeclientset: kubeclientset,
networkclientset: networkclientset,
networksLister: networkInformer. Lister ( ) ,
networksSynced: networkInformer. Informer ( ) . HasSynced,
workqueue: workqueue. NewNamedRateLimitingQueue ( ... , "Networks" ) ,
...
}
networkInformer. Informer ( ) . AddEventHandler ( cache. ResourceEventHandlerFuncs{
AddFunc: controller. enqueueNetwork,
UpdateFunc: func ( old, new interface { } ) {
oldNetwork := old. ( * samplecrdv1. Network)
newNetwork := new . ( * samplecrdv1. Network)
if oldNetwork. ResourceVersion == newNetwork. ResourceVersion {
return
}
controller. enqueueNetwork ( new )
} ,
DeleteFunc: controller. enqueueNetworkForDelete,
return controller
}
work Queue的作用同步informer和reconcile之间的数据 eventHandler里定义了3个func,具体操作都是将事件放入workQueue,但入队的不是API对象,而是API对象的key,在reconcile中,则会不断地从这个workQueue里拿到这些 Key,然后开始执行真正的控制逻辑 Informer本质上是自定义控制器跟 APIServer 进行数据同步的重要组件,具体来说,informer通过ListWatch把apiserver中的api对象缓存到本地,并负责维护和更新这个缓存(先更新本地缓存,然后触发eventHandler) 在reconcile过程中,每经过 resyncPeriod 指定的时间,Informer 维护的本地缓存都会使用最近一次 LIST 返回的结果强制更新一次,从而保证缓存的有效性。这个缓存强制更新的操作就叫作:resync。
operator
利用k8s的自定义资源来描述想要部署的有状态应用,然后通过在自定义控制器里,根据自定义对象的变化,来完成对应的控制逻辑(应用部署和运维)。 Operator 本质上就是一种自定义控制器(Custom Controller)。但是,Operator 与其他自定义控制器还是有一些不同之处:
控制的资源类型不同:其他自定义控制器可能只是控制一些业务逻辑或者某些资源的变化,Operator 则专注于部署和管理有状态应用程序,如数据库、消息队列等复杂的应用。 复杂性和自主性更强:Operator 需要处理应用程序的复杂部署、扩缩容、备份恢复等全生命周期管理,相比之下,其他自定义控制器通常只需要实现一些相对简单的业务逻辑。 面向应用程序的设计:Operator 是以应用程序为中心进行设计的,关注的是应用程序的整体状态和行为,其他自定义控制器则更多地关注某些特定的资源对象及其变化。
但是,crd并不适合所有场景
CRD 目前不支持 protobuf,当 API Object 数量 >1K,或者单个对象 >1KB,或者高频请求时,CRD 的响应都会有问题。 所以,CRD 千万不能也不应该被当作数据库使用。 像 k8s ,或者说 Etcd 本身,最佳的使用场景就是作为配置管理的依赖。如果业务需求不能用 CRD 进行建模的时候,比如,需要等待 API 最终返回,或者需要检查 API 的返回值,也是不能用 CRD 的