深入剖析Kubernetes--第五章:声明式API与Kubernetes编程范式
声明式API与Kubernetes编程范式
Istio项目
Dynamic Admission Control
在 Kubernetes 项目中,当一个 Pod 或者任何一个 API 对象被提交给 APIServer 之后,在K8S项目正式处理之前会通过 项目里一组被称为 Admission Controller 的代码实现 Admission 的功能 来 进行一些初始化工作(如自动为所有 Pod 加上某些标签(Labels)。)
如果想要添加一些自己的规则到 Admission Controller(需要重新编译并重启 APIServer)。
所以,k8s提供了一种**“热插拔”**式的 Admission 机制,它就是 Dynamic Admission Control,也叫作:Initializer。
用户提交的Pod中只有一个容器:myapp-pod
apiVersion: v1
kind: Pod
metadata:name: myapp-podlabels:app: myapp
spec:containers:- name: myapp-containerimage: busyboxcommand: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
Istio 项目要做的,就是在这个 Pod YAML 被提交给 Kubernetes 之后,在它对应的 API 对象里自动加上 Envoy 容器的配置,使这个对象变成如下所示的样子:
apiVersion: v1
kind: Pod
metadata:name: myapp-podlabels:app: myapp
spec:containers:- name: myapp-containerimage: busyboxcommand: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']- name: envoyimage: lyft/envoy:845747b88f102c0fd262ab234308e9e22f693a1command: ["/usr/local/bin/envoy"]...
如何做到呢
Istio 编写一个用来为 Pod“自动注入”Envoy 容器的 Initializer。
Istio 会将这个 Envoy 容器本身的定义,以 ConfigMap 的方式保存在 Kubernetes 当中。
apiVersion: v1
kind: ConfigMap
metadata:name: envoy-initializer
data:config: |containers:- name: envoyimage: lyft/envoy:845747db88f102c0fd262ab234308e9e22f693a1command: ["/usr/local/bin/envoy"]args:- "--concurrency 4"- "--config-path /etc/envoy/envoy.json"- "--mode serve"ports:- containerPort: 80protocol: TCPresources:limits:cpu: "1000m"memory: "512Mi"requests:cpu: "100m"memory: "64Mi"volumeMounts:- name: envoy-confmountPath: /etc/envoyvolumes:- name: envoy-confconfigMap:name: envoy
Initializer 需要把envoy的相关字段merge到用户提交的Pod内容中,(使用PATCH API实现,时声明式API的主要能力)
Istio 将一个编写好的 Initializer,作为一个 Pod 部署在 Kubernetes 中。
apiVersion: v1
kind: Pod
metadata:labels:app: envoy-initializername: envoy-initializer
spec:containers:- name: envoy-initializerimage: envoy-initializer:0.0.1imagePullPolicy: Always
envoy-initializer:0.0.1 镜像,就是一个事先编写好的“自定义控制器”(Custom Controller)
功能:
for {// 获取新创建的 Podpod := client.GetLatestPod()// Diff 一下,检查是否已经初始化过if !isInitialized(pod) {// 没有?那就来初始化一下doSomething(pod)}
}
//在 Initializer 控制器的工作逻辑里,它首先会从 APIServer 中拿到这个 ConfigMap
//把这个 ConfigMap 里存储的 containers 和 volumes 字段,直接添加进一个空的 Pod 对象里:
//Kubernetes 的 API 库,为我们提供了一个方法,使得我们可以直接使用新旧两个 Pod 对象,生成一个 TwoWayMergePatch
func doSomething(pod) {cm := client.Get(ConfigMap, "envoy-initializer")newPod := Pod{}newPod.Spec.Containers = cm.ContainersnewPod.Spec.Volumes = cm.Volumes// 生成 patch 数据patchBytes := strategicpatch.CreateTwoWayMergePatch(pod, newPod)// 发起 PATCH 请求,修改这个 pod 对象client.Patch(pod.Name, patchBytes)
}
Kubernetes 还允许你通过配置,来指定要对什么样的资源进行这个 Initialize 操作
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: InitializerConfiguration
metadata:name: envoy-config
initializers:// 这个名字必须至少包括两个 "."- name: envoy.initializer.kubernetes.iorules:- apiGroups:- "" // 前面说过, "" 就是 core API Group 的意思apiVersions:- v1resources:- pods
一旦这个 InitializerConfiguration 被创建,Kubernetes 就会把这个 Initializer 的名字,加在所有新创建的 Pod 的 Metadata 上
apiVersion: v1
kind: Pod
metadata:initializers:pending:- name: envoy.initializer.kubernetes.ioname: myapp-podlabels:app: myapp
...
每一个新创建的 Pod,都会自动携带了 metadata.initializers.pending 的 Metadata 信息
这个 Metadata,正是接下来 Initializer 的控制器判断这个 Pod 有没有执行过自己所负责的初始化操作的重要依据
当在 Initializer 里完成了要做的操作后,一定要metadata.initializers.pending 标志清除掉。
声明式API的工作原理
总结
K8s中,一个API对象在etcd中完整的路径是由Group(API组)、Version(API版本)、Resource(API资源类型)3个部分决定的。
核心 API 对象
对于 Kubernetes 里的核心 API 对象,比如:Pod、Node 等,是不需要 Group 的(即:它们 Group 是“”)。所以,对于这些 API 对象来说,Kubernetes 会直接在 /api 这个层级进行下一步的匹配过程
非核心 API 对象
对于 CronJob 等非核心 API 对象来说,Kubernetes 就必须在 /apis 这个层级里查找它对应的 Group,进而根据“batch”这个 Group 的名字,找到 /apis/batch,Kubernetes 会进一步匹配到 API 对象的版本号。
ps:同一种 API 对象可以有多个版本,这正是 Kubernetes 进行 API 版本化管理的重要手段
APIServer:
资源操作的唯一入口,接受用户输入的命令,提供认证、授权、API注册和发现等机制
代码生成功能(Gengo、code-generator)
CRD
Kubernetes v1.7 之后,得益于一个全新的 API 插件机制:CRD(Custom Resource Definition),允许用户在 Kubernetes 中添加一个跟 Pod、Node 类似的、新的 API 资源类型,即:自定义 API 资源。。
举例:
声明如下一个API对象
apiVersion: samplecrd.k8s.io/v1
kind: Network
metadata:name: example-network
spec:cidr: "192.168.0.0/16"gateway: "192.168.0.1"
可以看到,API 组是 samplecrd.k8s.io,版本是v1, 资源类型是Network
想要让K8s认识这个API,需要用到CR(Custom Resource)
声明一个CRD
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:name: networks.samplecrd.k8s.io
spec:group: samplecrd.k8s.ioversion: v1names:kind: Networkplural: networksscope: Namespaced
在这个CRD中,指定了这个 group: samplecrd.k8s.io version: v1
指定了这个 CR 的资源类型叫作 Network,复数(plural)是 networks。
声明了它的 scope 是 Namespaced,即:我们定义的这个 Network 是一个属于 Namespace 的对象,类似于 Pod。
使k8s认识字段
让 Kubernetes“认识”这种 YAML 文件里描述的“网络”部分,比如“cidr”(网段),“gateway”(网关)这些字段的含义。
张哥代码
代码生成简介
register.go
位置:pkg/apis/samplecrd
用来放置后面要用到的全局变量
package samplecrdconst (GroupName = "samplecrd.k8s.io"Version = "v1"
)
doc.go
// +k8s:deepcopy-gen=package// +groupName=samplecrd.k8s.io
package v1
+k8s:deepcopy-gen=package 意思是,请为整个 v1 包里的所有类型定义自动生成 DeepCopy 方法;而+groupName=samplecrd.k8s.io
,则定义了这个包对应的 API 组的名字
这些定义在 doc.go 文件的注释,起到的是全局的代码生成控制的作用,所以也被称为 Global Tags。
types.go
定义一个 Network 类型到底有哪些字段(比如,spec 字段里的内容)
package v1
...
// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object// Network describes a Network resource
type Network struct {// TypeMeta is the metadata for the resource, like kind and apiversionmetav1.TypeMeta `json:",inline"`// ObjectMeta contains the metadata for the particular object, including// things like...// - name// - namespace// - self link// - labels// - ... etc ...//metav1.TypeMeta `json:",inline"`metav1.ObjectMeta `json:"metadata,omitempty"`Spec networkspec `json:"spec"`
}
// networkspec is the spec for a Network resource
type networkspec struct {Cidr string `json:"cidr"`Gateway string `json:"gateway"`
}// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object// NetworkList is a list of Network resources
type NetworkList struct {metav1.TypeMeta `json:",inline"`metav1.ListMeta `json:"metadata"`Items []Network `json:"items"`
}
Network 类型定义方法跟标准的 Kubernetes 对象一样,包括 TypeMeta(API 元数据)和 ObjectMeta(对象元数据)字段
其中的 Spec 字段,就是需要我们自己定义的部分。所以,在 networkspec 里,定义了 Cidr 和 Gateway 两个字段。
??除了定义 Network 类型,你还需要定义一个 NetworkList 类型,用来描述一组 Network 对象应该包括哪些字段。??
(在 Kubernetes 中,获取所有 X 对象的 List() 方法,返回值都是List 类型,而不是 X 类型的数组。这是不一样的。)
+genclient 的意思是:请为下面这个 API 资源类型生成对应的 Client 代码(这个 Client,我马上会讲到)。而 +genclient:noStatus 的意思是:这个 API 资源类型定义里,没有 Status 字段。否则,生成的 Client 就会自动带上 UpdateStatus 方法。
//+genclient 只需要写在 Network 类型上,而不用写在 NetworkList 上。因为 NetworkList 只是一个返回值类型,Network 才是“主类型”。
一个固定的操作:添加+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
的注释。它的意思是,请在生成 DeepCopy 的时候,实现 Kubernetes 提供的 runtime.Object 接口。否则,在某些版本的 Kubernetes 里,你的这个类型定义会出现编译错误。
register.go
Network 资源类型在服务器端的注册的工作,APIServer 会自动帮我们完成(“registry”的作用就是注册一个类型(Type)给 APIServer)。但与之对应的,我们还需要让客户端也能“知道”Network 资源类型的定义。这就需要我们在项目里添加一个 register.go 文件。它最主要的功能,就是定义了如下所示的 addKnownTypes() 方法:
package v1
...
// addKnownTypes adds our types to the API scheme by registering
// Network and NetworkList
func addKnownTypes(scheme *runtime.Scheme) error {scheme.AddKnownTypes(SchemeGroupVersion,&Network{},&NetworkList{},)// register the type in the schememetav1.AddToGroupVersion(scheme, SchemeGroupVersion)return nil
}
有了这个方法,Kubernetes 就能够在后面生成客户端的时候,“知道”Network 以及 NetworkList 类型的定义了。
总结
Network 对象的定义工作就全部完成了。可以看到,它其实定义了两部分内容:
- 第一部分是,自定义资源类型的 API 描述,包括:组(Group)、版本(Version)、资源类型(Resource)等。这相当于告诉了计算机:兔子是哺乳动物。
- 第二部分是,自定义资源类型的对象描述,包括:Spec、Status 等。这相当于告诉了计算机:兔子有长耳朵和三瓣嘴。
接下来要使用 Kubernetes 提供的代码生成工具,为上面定义的 Network 资源类型自动生成 clientset、informer 和 lister
pkg/apis/samplecrd/v1 下面的 zz_generated.deepcopy.go 文件,就是自动生成的 DeepCopy 代码文件。
而整个 client 目录,以及下面的三个包(clientset、informers、 listers),都是 Kubernetes 为 Network 类型生成的客户端库,这些库会在后面编写自定义控制器的时候用到。
创建出这样一个自定义 API 对象,只是完成了 Kubernetes 声明式 API 的一半工作。
接下来的另一半工作是:为这个 API 对象编写一个自定义控制器(Custom Controller)。这样, Kubernetes 才能根据 Network API 对象的“增、删、改”操作,在真实环境中做出相应的响应。比如,“创建、删除、修改”真正的 Neutron 网络。
而这,正是 Network 这个 API 对象所关注的“业务逻辑”。
API编程范式的具体原理
自定义控制器代码:编写main函数、编写自定义控制器信息、以及编写控制器的业务逻辑
编写main函数
var (masterURL stringkubeconfig string
)func main() {flag.Parse()// set up signals so we handle the first shutdown signal gracefullystopCh := signals.SetupSignalHandler()cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)if err != nil {glog.Fatalf("Error building kubeconfig: %s", err.Error())}kubeClient, err := kubernetes.NewForConfig(cfg)if err != nil {glog.Fatalf("Error building kubernetes clientset: %s", err.Error())}networkClient, err := clientset.NewForConfig(cfg)if err != nil {glog.Fatalf("Error building example clientset: %s", err.Error())}networkInformerFactory := informers.NewSharedInformerFactory(networkClient, time.Second*30)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())}
}
//flag
func init() {flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")flag.StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
}
通过散步完成初始化,并启动一个自定义控制器的工作
第一步
var (masterURL stringkubeconfig string
)cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)kubeClient, err := kubernetes.NewForConfig(cfg)networkClient, err := clientset.NewForConfig(cfg)
根据提供的 Master 配置(APIServer 的地址端口和 kubeconfig 的路径),创建一个 Kubernetes 的 client(kubeClient)和 Network 对象的 client(networkClient)。
第二步
networkInformerFactory := informers.NewSharedInformerFactory(networkClient, time.Second*30)controller := NewController(kubeClient, networkClient,networkInformerFactory.Samplecrd().V1().Networks())
main 函数为 Network 对象创建一个叫作 InformerFactory(即:networkInformerFactory)的工厂,并使用它生成一个 Network 对象的 Informer,传递给控制器。
第三步
go networkInformerFactory.Start(stopCh)if err = controller.Run(2, stopCh); err != nil {glog.Fatalf("Error running controller: %s", err.Error())}
main 函数启动上述的 Informer,然后执行 controller.Run,启动自定义控制器
自定义控制器的工作原理
这个控制器要做的第一件事,是从 Kubernetes 的 APIServer 里获取它所关心的对象(Network )
(通过 Informer(可以翻译为:通知器)代码库完成)
Informer 与 API 对象是一一对应的,上述代码传递给自定义控制器的,正是一个 Network 对象的 Informer(Network Informer)
在创建这个 Informer 工厂的时候,需要给它传递一个 networkClient。事实上,Network Informer 正是使用这个 networkClient,跟 APIServer 建立了连接。不过,真正负责维护这个连接的,则是 Informer 所使用的 Reflector 包。
ListAndWatch
Reflector 使用的是一种叫作ListAndWatch的方法,来**“获取”并“监听”**这些 Network 对象实例的变化。
一旦 APIServer 端有新的 Network 实例被创建、删除或者更新,Reflector 都会收到“事件通知”。这时,该事件及它对应的 API 对象这个组合,就被称为增量(Delta),它会被放进一个 Delta FIFO Queue(即:增量先进先出队列)中。
Informe 会不断地从这个 Delta FIFO Queue 里读取(Pop)增量。每拿到一个增量,Informer 就会判断这个增量里的事件类型,然后创建或者更新本地对象的缓存。这个缓存,在 Kubernetes 里一般被叫作 Store。
2.1. 如果事件类型是 Added(添加对象),那么 Informer 就会通过一个叫作 Indexer 的库把这个增量里的 API 对象保存在本地缓存 中,并为它创建索引。相反地,如果增量的事件类型是 Deleted(删除对象),那么 Informer 就会从本地缓存中删除这个对象。
同步本地缓存的工作,是 Informer 的第一个职责,也是它最重要的职责。
2.2. 根据这些事件的类型,触发事先注册好的 ResourceEventHandler。这些 Handler,需要在创建控制器的时候注册给它对应的 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 {// Periodic resync will send update events for all known Networks.// Two different versions of the same Network will always have different RVs.return}controller.enqueueNetwork(new)},DeleteFunc: controller.enqueueNetworkForDelete,})return controller
}
使用创建的两个 client(kubeclientset 和 networkclientset)和Informer 初始化了自定义控制器
这个自定义控制器中还有一个工作队列(work queue) WorkQueue(k8s中有很多工作队列的实现)。负责同步 Informer 和控制循环之间的数据。
为 networkInformer 注册了三个 Handler(AddFunc、UpdateFunc 和 DeleteFunc),分别对应 API 对象的“添加”“更新”和“删除”事件。而具体的处理操作,都是将该事件对应的 API 对象加入到工作队列中。实际上入队的是他们的Key,即该API对象的<namespace>/<name>
,后面的控制循环,则会不断地从这个工作队列里拿到这些 Key,然后开始执行真正的控制逻辑
Informer
Informer,其实就是一个带有本地缓存和索引机制的、可以注册 EventHandler 的 client。它是自定义控制器跟 APIServer 进行数据同步的重要组件
Informer 通过一种叫作 ListAndWatch 的方法,把 APIServer 中的 API 对象缓存在了本地,并负责更新和维护这个缓存。
其中,ListAndWatch 方法的含义是:首先,通过 APIServer 的 LIST API“获取”所有最新版本的 API 对象;然后,再通过 WATCH API 来“监听”所有这些 API 对象的变化。
而通过监听到的事件变化,Informer 就可以实时地更新本地缓存,并且调用这些事件对应的 EventHandler 了。
此外,在这个过程中,每经过 resyncPeriod 指定的时间,Informer 维护的本地缓存,都会使用最近一次 LIST 返回的结果强制更新一次,从而保证缓存的有效性。在 Kubernetes 中,这个缓存强制更新的操作就叫作:resync。
需要注意的是,这个定时 resync 操作,也会触发 Informer 注册的“更新”事件。但此时,这个“更新”事件对应的 Network 对象实际上并没有发生变化,即:新、旧两个 Network 对象的 ResourceVersion 是一样的。在这种情况下,Informer 就不需要对这个更新事件再做进一步的处理了。
这也是为什么我在上面的 UpdateFunc 方法里,先判断了一下新、旧两个 Network 对象的版本(ResourceVersion)是否发生了变化,然后才开始进行的入队操作。
以上,就是 Kubernetes 中的 Informer 库的工作原理了。
控制循环(Control Loop)部分
也正是 main 函数最后调用 controller.Run() 启动的“控制循环
func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error {...if ok := cache.WaitForCacheSync(stopCh, c.networksSynced); !ok {return fmt.Errorf("failed to wait for caches to sync")}...for i := 0; i < threadiness; i++ {go wait.Until(c.runWorker, time.Second, stopCh)}...return nil
}
可以看到,启动控制循环的逻辑非常简单:
首先,等待 Informer 完成一次本地缓存的数据同步操作;
然后,直接通过 goroutine 启动一个(或者并发启动多个)“无限循环”的任务。
而这个“无限循环”任务的每一个循环周期,执行的正是我们真正关心的业务逻辑。
编写自定义控制器的业务逻辑
func (c *Controller) runWorker() {for c.processNextWorkItem() {}
}func (c *Controller) processNextWorkItem() bool {obj, shutdown := c.workqueue.Get()...err := func(obj interface{}) error {...if err := c.syncHandler(key); err != nil {return fmt.Errorf("error syncing '%s': %s", key, err.Error())}c.workqueue.Forget(obj)...return nil}(obj)...return true
}
在这个执行周期里(processNextWorkItem)
首先从工作队列里出队(workqueue.Get)了一个成员( Key(Network 对象的:namespace/name)).
func (c *Controller) syncHandler(key string) error {namespace, name, err := cache.SplitMetaNamespaceKey(key)...network, err := c.networksLister.Networks(namespace).Get(name)if err != nil {if errors.IsNotFound(err) {glog.Warningf("Network does not exist in local cache: %s/%s, will delete it from Neutron ...",namespace, name)glog.Warningf("Network: %s/%s does not exist in local cache, will delete it from Neutron ...",namespace, name)// FIX ME: call Neutron API to delete this network by name.//// neutron.Delete(namespace, name)return nil}...未结束
然后,在 syncHandler 方法中,我使用这个 Key,尝试从 Informer 维护的缓存中拿到了它所对应的 Network 对象。
(使用 networksLister 来尝试获取这个 Key 对应的 Network 对象。这个操作,其实就是在访问本地缓存的索引。)
(在 Kubernetes 的源码中,你会经常看到控制器从各种 Lister 里获取对象,比如:podLister、nodeLister 等等,它们使用的都是 Informer 和缓存机制。)
如果控制循环从缓存中拿不到这个对象(即:networkLister 返回了 IsNotFound 错误),那就意味着这个 Network 对象的 Key 是通过前面的“删除”事件添加进工作队列的。所以,尽管队列里有这个 Key,但是对应的 Network 对象已经被删除了。
需要调用 Neutron 的 API,把这个 Key 对应的 Neutron 网络从真实的集群里删除掉。
而如果能够获取到对应的 Network 对象,就执行控制器模式里的对比“期望状态”和“实际状态”的逻辑
return err}glog.Infof("[Neutron] Try to process network: %#v ...", network)// FIX ME: Do diff().//// actualNetwork, exists := neutron.Get(namespace, name)//// if !exists {// neutron.Create(namespace, name)// } else if !reflect.DeepEqual(actualNetwork, network) {// neutron.Update(namespace, name)// }return nil
}
自定义控制器“千辛万苦”拿到的这个 Network 对象,正是 APIServer 里保存的“期望状态”,即:用户通过 YAML 文件提交到 APIServer 里的信息。当然,在我们的例子里,它已经被 Informer 缓存在了本地。
然后通过Neutron API 来查询实际的网络情况
比如,可以先通过 Neutron 来查询这个 Network 对象对应的真实网络是否存在。
- 如果不存在,这就是一个典型的“期望状态”与“实际状态”不一致的情形。这时,我就需要使用这个 Network 对象里的信息(比如:CIDR 和 Gateway),调用 Neutron API 来创建真实的网络。
- 如果存在,那么,我就要读取这个真实网络的信息,判断它是否跟 Network 对象里的信息一致,从而决定我是否要通过 Neutron 来更新这个已经存在的真实网络。
这样,我就通过对比“期望状态”和“实际状态”的差异,完成了一次调协(Reconcile)的过程。
总结
在实际应用中,除了控制循环之外的所有代码,实际上都是 Kubernetes 为你自动生成的,即:pkg/client/{informers, listers, clientset}里的内容。
而这些自动生成的代码,就为我们提供了一个可靠而高效地获取 API 对象“期望状态”的编程库。
所以,接下来,作为开发者,你就只需要关注如何拿到“实际状态”,然后如何拿它去跟“期望状态”做对比,从而决定接下来要做的业务逻辑即可。
me)
// }
return nil
}
自定义控制器“千辛万苦”拿到的这个 Network 对象,**正是 APIServer 里保存的“期望状态”**,即:用户通过 YAML 文件提交到 APIServer 里的信息。当然,在我们的例子里,它已经被 Informer 缓存在了本地。然后通过Neutron API 来查询**实际的网络情况**比如,可以先通过 Neutron 来查询这个 Network 对象对应的真实网络是否存在。- 如果不存在,这就是一个典型的“期望状态”与“实际状态”不一致的情形。这时,我就需要使用这个 Network 对象里的信息(比如:CIDR 和 Gateway),调用 Neutron API 来创建真实的网络。
- 如果存在,那么,我就要读取这个真实网络的信息,判断它是否跟 Network 对象里的信息一致,从而决定我是否要通过 Neutron 来更新这个已经存在的真实网络。这样,我就通过对比“期望状态”和“实际状态”的差异,完成了一次调协(Reconcile)的过程。# 总结在实际应用中,除了控制循环之外的所有代码,实际上都是 Kubernetes 为你自动生成的,即:pkg/client/{informers, listers, clientset}里的内容。而这些自动生成的代码,就为我们提供了一个可靠而高效地获取 API 对象“期望状态”的编程库。所以,接下来,作为开发者,你就只需要关注如何拿到“实际状态”,然后如何拿它去跟“期望状态”做对比,从而决定接下来要做的业务逻辑即可。
深入剖析Kubernetes--第五章:声明式API与Kubernetes编程范式相关推荐
- api接口怎么写_面向声明式API编程(DAP)
DAP是Mars-java 最近提出的一个新的开发方式,全称 Declarative API Programming, 提倡后端为一个独立的整体,不应该是为前端服务的,所以当前端需要接口的时候,只需要 ...
- 《Spring系列》第15章 声明式事务(一) 基础使用
一.ACID特性 ⑴ 原子性(Atomicity)原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库 ...
- 声明式API replica controller vs replica set 对比
1.在命令式API中,你可以直接发出服务器要执行的命令,例如: "运行容器"."停止容器"等. 在声明性API中,你声明系统要执行的操作,系统将不断向该状态驱动 ...
- Kubernetes 第五章 YAML
对于kubernetes 资源可以使用加载 yaml 标记语言的方式,进行自定义: YAML(/ˈjæməl/,尾音类似camel骆驼)是一个可读性高,用来表达数据序列化的格式.YAML参考了其他多种 ...
- 【C】【笔记】《C语言深度剖析》第五章 内存管理
本文为个人读书笔记,仅供记录学习过程中遇到的日后需要留意的问题,如有相关版权问题请及时通知作者. 野指针 也称悬垂指针,指向已经不存在的对象.要杜绝野指针,.定义指针变量的同时就初始化为NULL,用完 ...
- KCL:声明式的云原生配置策略语言
楔子: 以蚂蚁集团典型的建站场景为例,在接入 Kusion 后,用户侧配置代码减少到 5.5%,用户面对的 4 个平台通过接入统一代码库而消减,在无其他异常的情况下交付时间从 2 天下降到 2 小时- ...
- 通过 React Hooks 声明式地使用 setInterval
2019独角兽企业重金招聘Python工程师标准>>> 本文由云+社区发表 作者:Dan Abramov 接触 React Hooks 一定时间的你,也许会碰到一个神奇的问题: se ...
- 04.声明式服务调用:Spring Cloud Feign(Greenwich.SR2)
1.Feign是什么 Feign是整合了Ribbon与Hystrix外,还提供了声明式的Web服务客户端定义方式.采用了声明式API接口的风格,将Java Http客户端绑定到它的内部.Feign的首 ...
- Kubernetes 学习总结(23)—— 2022 年 Kubernetes 的 5 个趋势
前言 Kubernetes 在成长,使用它的团队也在成长.早期采用者现在已经进入了自己的领域,能够基于经验和云原生生态系统的增长,以新的方式扩展 Kubernetes 的核心功能."我们将继 ...
最新文章
- 精灵盛典电脑模拟器服务器怎么修改,精灵盛典ios电脑版
- 如何将NSString转换为NSNumber
- Linux服务器数据备份
- treemap怎么保证有序_干货!208道面试题教你怎么通过面试!
- php怎么使得字体滚动,滚动文字+字体特效代码(全集)
- oracle 中sql的分类,Oracle数据库语言分类
- inner join 与 left join 、right join之间的区别
- java写顾客购买的商品总价格_成交总金额=商品价格×商品件数-总优惠额。 如果一个顾客,购买的商品一口价为5元,购买的商品件数为4,总物流运费4元,满20送3,请问成交的金额是()。...
- iOS开发之解析XML格式数据
- solr处理oracle中CLOB、BLOB
- WPF高级教程(三)XAML
- 解决虚拟机-虚拟网络配置没有桥接模式,本地没有虚拟网卡
- Python编写尼姆游戏
- 我的世界服务器增加刷怪率,我的世界:最简易刷怪空间,别再暴殄天物了!
- 爱阅书香之书源制作 POST请求方式
- nvm use出现乱码
- outlook2007 配置
- 根据概率密度函数生成随机数的代码
- 华为荣耀8电信卡显示无服务器,华为荣耀手机实现双电信卡双待双通,5步告诉你真相...
- 红黑树系列1——红黑树的建立
热门文章
- LeetCode 14.最长公共前缀(字符串)
- spark ui job和stage的dag图查看过去运行的任务,查不到,分析源码解决问题
- pythongui库推荐_八款常用的 Python GUI 开发框架推荐
- 猎豹浏览器使用评测(2)-一款很轻的极简绿色浏览器
- Vue 使用echarts 地图自定义图标和修改图标样式,点击切换图标
- 反射创建实例时出现异常 class *** cannot access a member of class *** with modifiers
- 图片转成base64格式上传至数据库
- FFMpeg-9、给视频添加实时时间水印drawtext filters+中文水印显示问题
- 用js写卡牌游戏(五)
- cx_Oracle.DatabaseError: ORA-01036: 非法的变量名/编号