watch机制是一种非常重要的功能,它允许客户端实时获取Kubernetes集群中资源对象的状态变更通知。
一、基本概念
- watch机制:在Kubernetes中,watch机制是一种基于HTTP长轮询的实时资源状态变更通知机制。当客户端发起一个watch请求到API Server时,API Server并不会立即返回响应,而是保持连接打开。当API Server接收到任何与该请求所关注资源相关的操作时(如创建、更新或删除资源),它会将相应的操作类型和新状态作为事件消息通过同一条连接发送给客户端。
二、工作机制
- 建立连接:
- 客户端使用Kubernetes的client库(如Go语言中的client-go)创建并启动一个watch对象,该对象会向API Server发起一个带有watch参数的HTTP请求。
- API Server接收到请求后,不会立即返回响应,而是保持连接处于打开状态,等待资源状态的变化。
- 资源变更处理:
- 当API Server监听到资源对象的状态发生变化时(如Pod的创建、更新或删除),它会将这些变化封装成事件消息。
- 这些事件消息会通过之前建立的HTTP连接发送给客户端。客户端可以在一个循环中持续监听这些事件消息,并根据事件类型进行相应的处理。
- 事件类型:
- ADDED:表示一个新的资源对象被创建。
- UPDATED:表示已存在的资源对象被修改了属性。
- DELETED:表示资源对象已被删除。
- BOOKMARK:在某些版本中引入,用于标记某个资源版本,以辅助客户端恢复丢失的事件流。
- ERROR:在连接中断或其他异常情况发生时,API Server可能会发送错误事件。
- 连接管理:
- watch连接可能由于多种原因(如网络问题、超时或API Server重启)而断开。客户端需要能够重新建立连接,并从上次断开的地方继续接收事件。
- 这通常涉及到事件序列号(ResourceVersion)的管理和重连逻辑。客户端可以使用ResourceVersion来确保不会错过任何事件。
三、应用场景
- 控制器(Controller):控制器是watch机制的主要使用者之一。它们通过监听资源状态的变化来维护集群的实际状态与期望状态的一致性。
- kubelet和kube-proxy:这些组件也利用watch机制来实时响应集群中的资源变动。
- 第三方工具和服务:开发者可以构建基于watch机制的第三方工具和服务,以实时监控和处理Kubernetes集群中的资源变更。
四、Watch CRD
在Kubernetes(K8S)中,watch CRD(CustomResourceDefinition,自定义资源定义)是指客户端实时监听Kubernetes API Server上自定义资源(CRD)的变化情况。这种机制使得开发者可以构建出对自定义资源变化敏感的应用程序,从而自动化地响应这些变化。
- 基本概念
- CRD:CustomResourceDefinition是Kubernetes API的扩展机制,允许用户定义新的资源类型。通过CRD,开发者可以定义资源的schema(结构)、版本等信息,并在Kubernetes集群中创建和管理这些自定义资源。
- watch机制:在Kubernetes中,watch机制允许客户端实时获取资源对象的状态变更通知。当客户端发起一个watch请求到API Server时,API Server会保持连接打开,并在资源状态发生变化时,将变更信息发送给客户端。
- 工作流程
- 定义CRD:首先,开发者需要定义CRD,包括资源的名称、schema、版本等信息。这通常通过创建CRD资源对象(CustomResourceDefinition对象)在Kubernetes集群中完成。
- 创建自定义资源实例:在CRD定义好之后,开发者可以在Kubernetes集群中创建自定义资源的实例。这些实例将按照CRD中定义的schema进行验证和管理。
- 发起watch请求:客户端(如控制器、应用程序等)通过Kubernetes的client库(如client-go)发起watch请求,指定要监听的CRD和相应的命名空间(如果需要的话)。
- 监听资源变化:API Server接收到watch请求后,会保持连接打开,并监听指定CRD下资源的变化情况。当资源被创建、更新或删除时,API Server会将变更信息封装成事件,并通过之前建立的连接发送给客户端。
- 处理变更事件:客户端在接收到变更事件后,会根据事件类型(ADDED、UPDATED、DELETED等)和资源状态进行相应的处理。例如,更新本地缓存、触发业务逻辑等。
- 实现细节
- HTTP长轮询:Kubernetes的watch机制通常基于HTTP长轮询实现。客户端发起watch请求后,API Server会保持连接打开,直到有资源变化或连接超时。如果连接超时,客户端需要重新发起watch请求。
- 资源版本:为了确保客户端不会错过任何事件,Kubernetes引入了资源版本(ResourceVersion)的概念。客户端在发起watch请求时,可以指定一个起始的资源版本。API Server只会发送该版本之后发生的事件给客户端。
- 错误处理和重连:由于网络问题、API Server重启等原因,watch连接可能会中断。客户端需要能够处理这些错误,并在必要时重新发起watch请求。
- 应用场景
- 控制器开发:开发者可以编写自定义控制器来监听CRD资源的变化,并根据这些变化来执行相应的业务逻辑。
- 状态同步:在分布式系统中,各个组件可能需要保持对某些资源的状态同步。通过watch CRD,组件可以实时获取资源状态的变化,并进行相应的状态更新。
- 自动化运维:运维人员可以编写脚本来监听特定CRD资源的变化,并根据这些变化来触发自动化的运维操作(如扩缩容、故障恢复等)。
5、简单CRD
创建一个Custom Resource Definition(CRD)的demo涉及几个步骤,包括定义CRD本身、创建CRD资源,以及(可选地)编写一个控制器来监听这些资源的变化并作出响应。
- 定义CRD
首先,需要定义一个CRD的YAML文件。这个文件描述了自定义资源的结构、它所属的组、版本、以及它是集群范围的还是命名空间范围的。
yaml
# myapps.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: myapps.my.domain
spec:
group: my.domain
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
appName:
type: string
version:
type: string
names:
plural: myapps
singular: myapp
kind: MyApp
shortNames:
- ma
scope: Namespaced
- 创建CRD
使用kubectl命令行工具来创建CRD。
bash
kubectl apply -f myapps.yaml
- 创建CRD实例
一旦CRD被创建,就可以开始创建该CRD的实例了。下面是一个MyApp实例的YAML文件。
yaml
# myapp-instance.yaml
apiVersion: my.domain/v1
kind: MyApp
metadata:
name: example-myapp
namespace: default
spec:
appName: MyAwesomeApp
version: 1.0.0
使用kubectl来创建这个实例。
bash
kubectl apply -f myapp-instance.yaml
- 验证CRD实例
可以使用kubectl来列出所有的MyApp实例,并查看它们的详细信息。
bash
kubectl get myapps -n default
kubectl describe myapp example-myapp -n default
或者,如果定义了短名称,也可以使用短名称来列出实例。
bash
kubectl get ma -n default
- (可选)编写控制器
虽然这个demo没有包括编写控制器的部分,但通常会想要为CRD编写一个控制器。控制器是一个运行在Kubernetes集群中的程序,它监听CRD资源的变化,并根据这些变化执行自定义的业务逻辑。
控制器可以使用多种语言编写,但最常见的是使用Go语言结合Kubernetes的client-go库。编写控制器时,需要使用Kubernetes的API来监听CRD资源的变化,并根据这些变化来更新集群的状态。
样例:
go
// main.go
package main
import (
"flag"
"os"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
// +kubebuilder:scaffold:imports
mycrdsv1 "path/to/your/api/v1" // 替换为你的API包路径
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
// 将你的CRD添加到scheme中
utilruntime.Must(mycrdsv1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}
func main() {
var metricsAddr string
var enableLeaderElection bool
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
flag.Parse()
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Port: 9443,
LeaderElection: enableLeaderElection,
LeaderElectionID: "your-controller-leader-election", // 替换为你的控制器ID
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
if err = (&MyCrdReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("MyCrd"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "MyCrd")
os.Exit(1)
}
setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}
// MyCrdReconciler 是控制器逻辑的实现
// 这里需要自己实现Reconcile方法
type MyCrdReconc