当前位置: 首页 > news >正文

三门峡市建设局网站上海心橙科技网站建设

三门峡市建设局网站,上海心橙科技网站建设,典型的四大综合门户网站,网站开发与管理学什么0. 前言 近日我们在开发符合我们业务自身需求的微服务平台时#xff0c;使用了 Kubernetes 的 Operator Pattern 来实现其中的运维系统#xff0c;在本文#xff0c;我们将实现过程中积累的主要知识点和技术细节做了一个整理。 读者在阅读完本文之后#xff0c;会对 Oper…0. 前言 近日我们在开发符合我们业务自身需求的微服务平台时使用了 Kubernetes 的 Operator Pattern 来实现其中的运维系统在本文我们将实现过程中积累的主要知识点和技术细节做了一个整理。 读者在阅读完本文之后会对 Operator Pattern 有一个基本的了解并能将该模式应用到自己的业务中去。除此之外我们也会分享要实现这一运维系统需要具备的一些相关知识。 注阅读本文内容需要对 Kubernetes 和 Go 语言有基本了解。 1. 什么是 Operator Pattern 在解释什么是 Operator Pattern 之前我们得先了解在我们使用一个 Kubernetes 客户端——这里以 kubectl 举例——向 Kubernetes 集群发出指令直到这项指令被 Kubernetes 集群执行结束这段时间之内到底都发生了什么。 这里以我们输入_ kubectl create -f ns-my-workspace.yaml_ 这条命令举例这条命令的整条执行链路大致如下图所示 ### ns-my-workspace.yaml apiVersion: v1 kind: Namespace metadata:name: my-workspace 如上图所示所有在 Kubernetes 集群中的组件交互都是通过 RESTful API 的形式完成的包括第一步的控制器监听操作以及第二步中 kubectl 发送的指令。虽说我们执行的是 _kubectl create -f ns-my-workspace.yaml _指令但其实 kubectl 会向「API服务器」发送一个 POST 请求 curl --request POST \--url http://${k8s.host}:${k8s.port}/api/v1/namespaces \--header content-type: application/json \--data {apiVersion:v1,kind:Namespace,metadata:{name:my-workspace} } 如上面的 cURL 指令Kubernetes API 服务器接受的其实是 JSON 数据类型而并非是 YAML。 然后所有这些创建的 resources 都会持久化到 etcd 组件中「API服务器」也是 Kubernetes 集群中与「etcd」交互的唯一一个组件。 之后被创建的 my-workspace resource 就会被发送给监听了 namespaces resource 变更的 「Namespace控制器」中最后就由「Namespace控制器」执行创建 my-workspace 命名空间的具体操作。那么同理当创建 ReplicaSet resource 时就会由「ReplicaSet控制器」做具体执行当创建 Pod 时则会由「Pod控制器」具体执行其他类型的 resource 与之类似这些控制器共同组成了上图中的「Kubernetes API 控制器集合」。 说到这里我们不难发现实现 Kubernetes 中某一种领域类型——如上面提到的 Namespace、ReplicaSet、Pod也即 Kubernetes 中的 Kind——的操作逻辑需要具备两个因素 对该领域类型的模型抽象如上面的 ns-my-workspace.yaml 文件描述的 YAML 数据结构这个抽象决定了 Kubernetes client 发送到 Kubernetes API server 的 RESTful API 请求也描述了这个领域类型本身。实际去处理这个领域类型抽象的控制器如上面的「Namespace控制器」、「ReplicaSet控制器」、「Pod控制器」这些控制器实现了这个抽象描述的具体业务逻辑并通过 RESTful API 提供这些服务。 而当 Kubernetes 开发者需要扩展 Kubernetes 能力时也可以遵循这种模式即提供一份对想要扩展的能力的抽象和实现了这个抽象具体逻辑的控制器。前者称作 CRD(Custom Resource Definition)后者称作 Controller。Operator pattern 就是通过这种方式实现 Kubernetes 扩展性的一种模式Operator 模式认为可以将一个领域问题的解决方案想像成是一个「操作者」这个操作者在用户和集群之间通过一份份「订单」去操作集群的API来达到完成这个领域各种需求的目的。这里的订单就是 CR(Custom Resource即 CRD 的一个实例)而操作者就是控制器是具体逻辑的实现者。之所以强调是 operator而不是计算机领域里传统的 server 角色则是因为 operator 本质上不创造和提供新的服务他只是已有 Kubernetes API service 的组合。 而本文实践的「运维系统」就是一个为了解决运维领域问题而实现出来的 operator。 2. Operator Pattern 实战 在本节我们会通过使用 kubebuilder 工具构建一个 Kubernetes Operator在本节之后我们会在自己的 Kubernetes 集群中获得一个 CRD 和其对应的 Kubernetes API 控制器用于简单的部署一个微服务。即当我们 create 如下 YAML 时 apiVersion: devops.my.domain/v1 kind: DemoMicroService metadata:name: demomicroservice-sample spec:image: stefanprodan/podinfo:0.0.1 可得到一个 Kubernetes 部署实例 本节所有示例代码均提供在https://github.com/l4wei/kubebuilder-example 2.1 Kubebuilder 实现 Kubebuilder(https://github.com/kubernetes-sigs/kubebuilder)是一个用 Go 语言构建 Kubernetes APIs 控制器和 CRD 的脚手架工具通过使用 kubebuilder用户可以遵循一套简单的编程框架使用 Go 语言方便的实现一个 operator。 2.1.1 安装 在安装 kubebuilder 之前需要先安装 Go 语言和 kustomize并确保可以正常使用。 kustomize 是一个可定制化生成 Kubernetes YAML Configuration 文件的工具你可以通过遵循一套 kustomize 的配置批量的生成你需要的 Kubernetes YAML 配置。kubebuilder 使用了 kustomize 去生成控制器所需的一些 YAML 配置。mac 用户可使用 brew 方便地安装 kustomize。 然后使用使用下面的脚本安装 kubebuilder os$(go env GOOS) arch$(go env GOARCH)# download kubebuilder and extract it to tmp curl -L https://go.kubebuilder.io/dl/2.2.0/${os}/${arch} | tar -xz -C /tmp/# move to a long-term location and put it on your path # (youll need to set the KUBEBUILDER_ASSETS env var if you put it somewhere else) sudo mv /tmp/kubebuilder_2.2.0_${os}_${arch} /usr/local/kubebuilder export PATH$PATH:/usr/local/kubebuilder/bin 使用 kubebuilder -h 若能看到帮助文档则表示 kubebuilder 安装成功。 2.1.2 创建工程 使用下面的脚本创建一个 kubebuilder 工程 mkdir example cd example go mod init my.domain/example kubebuilder init --domain my.domain 上述命令的 my.domain 一般是你所在机构的域名_example_ 一般是你这个 Go 语言项目的项目名。根据这样的设定如果这个 Go 项目作为一个模块要被其他 Go 项目依赖那么一般命名为 my.domain/example_。 如果你的 example 目录建立在 ${GOPATH} 目录之下那么就不需要 _go mod init my.domain/example 这条命令Go 语言也能找到该 example 目录下的 go pkg。 然后确保以下两条命令在你的开发机器上被执行过 export GO111MODULEon sudo chmod -R 777 ${GOPATH}/go/pkg 以上两条命令的执行可以解决在开发时可能出现的cannot find package ... (from $GOROOT)这种问题。 在创建完工程之后你的 example 目录结构会大致如下 . ├── Dockerfile ├── Makefile ├── PROJECT ├── bin │   └── manager ├── config │   ├── certmanager │   │   ├── certificate.yaml │   │   ├── kustomization.yaml │   │   └── kustomizeconfig.yaml │   ├── default │   │   ├── kustomization.yaml │   │   ├── manager_auth_proxy_patch.yaml │   │   ├── manager_webhook_patch.yaml │   │   └── webhookcainjection_patch.yaml │   ├── manager │   │   ├── kustomization.yaml │   │   └── manager.yaml │   ├── prometheus │   │   ├── kustomization.yaml │   │   └── monitor.yaml │   ├── rbac │   │   ├── auth_proxy_role.yaml │   │   ├── auth_proxy_role_binding.yaml │   │   ├── auth_proxy_service.yaml │   │   ├── kustomization.yaml │   │   ├── leader_election_role.yaml │   │   ├── leader_election_role_binding.yaml │   │   └── role_binding.yaml │   └── webhook │   ├── kustomization.yaml │   ├── kustomizeconfig.yaml │   └── service.yaml ├── go.mod ├── go.sum ├── hack │   └── boilerplate.go.txt └── main.go 上面目录中的 bin 目录下的 manager 即工程编译出的二进制可执行文件也就是这个控制器的可执行文件。 config 目录下都是 kustomize 的配置例如 config/manager 目录下面的文件即生成控制器部署 YAML 配置文件的 kustomize 配置如果你执行下面的指令 kustomize build config/manager 就能看到 kustomize 生成的 YAML 配置 apiVersion: v1 kind: Namespace metadata:labels:control-plane: controller-managername: system --- apiVersion: apps/v1 kind: Deployment metadata:labels:control-plane: controller-managername: controller-managernamespace: system spec:replicas: 1selector:matchLabels:control-plane: controller-managertemplate:metadata:labels:control-plane: controller-managerspec:containers:- args:- --enable-leader-electioncommand:- /managerimage: controller:latestname: managerresources:limits:cpu: 100mmemory: 30Mirequests:cpu: 100mmemory: 20MiterminationGracePeriodSeconds: 10 上面就是将 bin/manager 部署到 Kubernetes 集群的 YAML configurations。 2.1.3 创建API 上面创建的工程仅仅只是一个空壳还没有提供任何的 Kubernetes API也不能处理任何的 CR。使用下面的脚本创建一个 Kubernetes API kubebuilder create api --group devops --version v1 --kind DemoMicroService 上述命令的 group 将与之前创建工程时输入的 domain 共同组成你创建的 Kubernetes API YAML resource 里 apiVersion 字段的前半部分上面的 version 即后半部分所以你自定义的 resource YAML 里的 apiVersion 就应该写作devops.my.domain/v1。上面的 kind 就是你自定义 resource 里的 kind 字段。通过该条指令创建的 resource 看起来正如 kubebuilder 创建的 config/samples/devops_v1_demomicroservice.yaml 文件一样 apiVersion: devops.my.domain/v1 kind: DemoMicroService metadata:name: demomicroservice-sample spec:# Add fields here foo: bar 输入该命令会提示你是否创建 Resource即 CRD是否创建 Controller即控制器全部输入「y」同意即可。 在执行完该命令之后你的工程结构将变成这样 . ├── Dockerfile ├── Makefile ├── PROJECT ├── api │   └── v1 │   ├── demomicroservice_types.go │   ├── groupversion_info.go │   └── zz_generated.deepcopy.go ├── bin │   └── manager ├── config │   ├── certmanager │   │   ├── certificate.yaml │   │   ├── kustomization.yaml │   │   └── kustomizeconfig.yaml │   ├── crd │   │   ├── kustomization.yaml │   │   ├── kustomizeconfig.yaml │   │   └── patches │   │   ├── cainjection_in_demomicroservices.yaml │   │   └── webhook_in_demomicroservices.yaml │   ├── default │   │   ├── kustomization.yaml │   │   ├── manager_auth_proxy_patch.yaml │   │   ├── manager_webhook_patch.yaml │   │   └── webhookcainjection_patch.yaml │   ├── manager │   │   ├── kustomization.yaml │   │   └── manager.yaml │   ├── prometheus │   │   ├── kustomization.yaml │   │   └── monitor.yaml │   ├── rbac │   │   ├── auth_proxy_role.yaml │   │   ├── auth_proxy_role_binding.yaml │   │   ├── auth_proxy_service.yaml │   │   ├── demomicroservice_editor_role.yaml │   │   ├── demomicroservice_viewer_role.yaml │   │   ├── kustomization.yaml │   │   ├── leader_election_role.yaml │   │   ├── leader_election_role_binding.yaml │   │   └── role_binding.yaml │   ├── samples │   │   └── devops_v1_demomicroservice.yaml │   └── webhook │   ├── kustomization.yaml │   ├── kustomizeconfig.yaml │   └── service.yaml ├── controllers │   ├── demomicroservice_controller.go │   └── suite_test.go ├── go.mod ├── go.sum ├── hack │   └── boilerplate.go.txt └── main.go 比起创建 API 之前增加了 api 目录——即定义了你创建的 Kubernetes API 的数据结构代码。controllers 目录——即控制器的实现代码。config/crd 目录——该目录里的 kustomize 配置可生成你要定义的 CRD 的 YAML 配置。 输入以下命令 make manifests 即可在 config/crd/bases/devops.my.domain_demomicroservices.yaml 文件里看到你创建该 Kubernetes API 时创建的 CRD --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata:annotations:controller-gen.kubebuilder.io/version: v0.2.4creationTimestamp: nullname: demomicroservices.devops.my.domain spec:group: devops.my.domainnames:kind: DemoMicroServicelistKind: DemoMicroServiceListplural: demomicroservicessingular: demomicroservicescope: Namespacedvalidation:openAPIV3Schema:description: DemoMicroService is the Schema for the demomicroservices APIproperties:apiVersion:description: APIVersion defines the versioned schema of this representationof an object. Servers should convert recognized schemas to the latestinternal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resourcestype: stringkind:description: Kind is a string value representing the REST resource thisobject represents. Servers may infer this from the endpoint the clientsubmits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kindstype: stringmetadata:type: objectspec:description: DemoMicroServiceSpec defines the desired state of DemoMicroServiceproperties:foo:description: Foo is an example field of DemoMicroService. Edit DemoMicroService_types.goto remove/updatetype: stringtype: objectstatus:description: DemoMicroServiceStatus defines the observed state of DemoMicroServicetype: objecttype: objectversion: v1versions:- name: v1served: truestorage: true status:acceptedNames:kind: plural: conditions: []storedVersions: [] 2.1.4 API属性定义 Kubernetes API 创建好了现在我们需要定义该 API 的属性这些属性才真正描述了创建出的 CRD 的抽象特征。 在我们这个 DemoMicroService Kind 例子中我们只简单的抽象出一个微服务的部署 CRD所以我们这个 CRD 只有一个属性即该服务的容器镜像地址。 为此我们只需要修改 api/v1/demomicroservice_types.go 文件 上面的 git diff 显示我们将原来示例的 Foo 属性改成了我们需要的 Image 属性。对 API 属性的定义和对 CRD 的定义基本上只需要修改该文件即可。 再次执行 make manifests 即可看到生成的 CRD resource 发生了变更这里不再赘述。 现在我们也修改一下 config/samples/devops_v1_demomicroservice.yaml 文件后面需要使用该文件测试我们实现的控制器 apiVersion: devops.my.domain/v1 kind: DemoMicroService metadata:name: demomicroservice-sample spec:image: stefanprodan/podinfo:0.0.1 2.1.5 控制器逻辑实现 CRD 定义好了现在开始实现控制器。 我们在这次示例中要实现的控制器逻辑非常简单基本可以描述成 当我们执行 kubectl create -f config/samples/devops_v1_demomicroservice.yaml 时控制器会在集群中创建一个 Kubernetes Deployment resource用于实现该 DemoMicroService 的部署。当我们执行 kubectl delete -f config/samples/devops_v1_demomicroservice.yaml 时控制器会将在集群中创建的 Deployment resource 删掉表示该 DemoMicroService 的下线。 2.1.5.1 部署的实现 写代码之前我们需要先了解 kubebuilder 程序的开发方式。 因为我们要实现的是 DemoMicroService 的控制器所以我们需要先将注意力集中在 _controllers/demomicroservice_controller.go_ 文件如果不是复杂的功能通常我们只需改该文件即可。而在文件中我们最需要关心的就是 Reconcile 方法 func (r *DemoMicroServiceReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {_ context.Background()_ r.Log.WithValues(demomicroservice, req.NamespacedName)// your logic herereturn ctrl.Result{}, nil } 简单来说每当 kubernetes 集群监控到 DemoMicroService CR 的变化时都会调用到这个 Reconcile 方法并将变更的 DemoMicroService resource name 及其所在的 namespace 作为 Reconcile 方法的参数用于定位到变更的 resource。即上面的 req 参数该参数的结构为 type Request struct {// NamespacedName is the name and namespace of the object to reconcile.types.NamespacedName } type NamespacedName struct {Namespace stringName string } 熟悉前端开发的朋友可能会联想到 React 的开发方式两者确实很像都是监听对象的变化再根据监听对象的变化来执行一些逻辑。不过 kubebuilder 做的更加极端他没有抽象出生命周期的概念只提供一个 Reconcile 方法开发者需要自己在这个方法中判断出 CRD 的生命周期并在不同的生命周期中执行不同的逻辑。 以下的代码实现了部署的功能 func (r *DemoMicroServiceReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {ctx : context.Background()log : r.Log.WithValues(demomicroservice, req.NamespacedName)dms : devopsv1.DemoMicroService{}if err : r.Get(ctx, req.NamespacedName, dms); err ! nil {if err : client.IgnoreNotFound(err); err nil {log.Info(此时没有找到对应的 DemoMicroService resource, 即此处进入了 resource 被删除成功后的生命周期)return ctrl.Result{}, nil} else {log.Error(err, 不是未找到的错误那么就是意料之外的错误所以这里直接返回错误)return ctrl.Result{}, err}}log.Info(走到这里意味着 DemoMicroService resource 被找到即该 resource 被成功创建进入到了可根据该 resource 来执行逻辑的主流程)podLabels : map[string]string{app: req.Name,}deployment : appv1.Deployment{TypeMeta: metav1.TypeMeta{Kind: Deployment,APIVersion: apps/v1,},ObjectMeta: metav1.ObjectMeta{Name: req.Name,Namespace: req.Namespace,},Spec: appv1.DeploymentSpec{Selector: metav1.LabelSelector{MatchLabels: podLabels,},Template: corev1.PodTemplateSpec{ObjectMeta: metav1.ObjectMeta{Labels: podLabels,},Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: req.Name,Image: dms.Spec.Image,ImagePullPolicy: Always,Ports: []corev1.ContainerPort{{ContainerPort: 9898,},},},},},},},}if err : r.Create(ctx, deployment); err ! nil {log.Error(err, 创建 Deployment resource 出错)return ctrl.Result{}, err}return ctrl.Result{}, nil } 上述代码展现了生命周期中的两个阶段即第8行中代表 DemoMicroService resource 被成功删除之后的阶段此时我们什么都没有做。以及在第16行进入到创建/更新完 DemoMicroService resource 之后的阶段此时我们构建了一个 Deployment resource并将其创建到了 Kubernetes 集群中。 这段代码有个问题即无法实现 DemoMicroService resource 的更新如果同一个 DemoMicroService resource 的 spec.image 被改变了那么在上述代码中会再次 create 相同的 Deployment resource这会导致一个 already exists 的报错。这里为了方便说明开发逻辑没有处理这个问题请读者注意。 2.1.5.1 下线的实现 其实在上一节我们说明部署逻辑的时候就能实现下线的逻辑我们只需在「删除成功后」的生命周期阶段将创建的 Deployment 删掉即可。但是这样做有一个问题我们是在 DemoMicroService resource 删除成功之后再删的 Deployment如果删除 Deployment 的逻辑出错了没有将 Deployment 删除成功那么就会出现 Deployment 还在DemoMicroService 却不再的情况如果我们需要用 DemoMicroService 管理 Deployment那么这就不是我们想要的结果。 所以我们最好在 DemoMicroService 真正消失之前即「删除 DemoMicroService」到「DemoMicroService 完全消失」这段时间去删除 Deployment那么要怎么做呢请看下面的代码示例 const (demoMicroServiceFinalizer string demomicroservice.finalizers.devops.my.domain )func (r *DemoMicroServiceReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {ctx : context.Background()log : r.Log.WithValues(demomicroservice, req.NamespacedName)dms : devopsv1.DemoMicroService{}if err : r.Get(ctx, req.NamespacedName, dms); err ! nil {if err : client.IgnoreNotFound(err); err nil {log.Info(此时没有找到对应的 DemoMicroService resource, 即此处进入了 resource 被删除成功后的生命周期)return ctrl.Result{}, nil} else {log.Error(err, 不是未找到的错误那么就是意料之外的错误所以这里直接返回错误)return ctrl.Result{}, err}}if dms.ObjectMeta.DeletionTimestamp.IsZero() {log.Info(进入到 apply 这个 DemoMicroService CR 的逻辑)log.Info(此时必须确保 resource 的 finalizers 里有控制器指定的 finalizer)if !util.ContainsString(dms.ObjectMeta.Finalizers, demoMicroServiceFinalizer) {dms.ObjectMeta.Finalizers append(dms.ObjectMeta.Finalizers, demoMicroServiceFinalizer)if err : r.Update(ctx, dms); err ! nil {return ctrl.Result{}, err}}if _, err : r.applyDeployment(ctx, req, dms); err ! nil {return ctrl.Result{}, nil}} else {log.Info(进入到删除这个 DemoMicroService CR 的逻辑)if util.ContainsString(dms.ObjectMeta.Finalizers, demoMicroServiceFinalizer) {log.Info(如果 finalizers 被清空则该 DemoMicroService CR 就已经不存在了所以必须在次之前删除 Deployment)if err : r.cleanDeployment(ctx, req); err ! nil {return ctrl.Result{}, nil}}log.Info(清空 finalizers在此之后该 DemoMicroService CR 才会真正消失)dms.ObjectMeta.Finalizers util.RemoveString(dms.ObjectMeta.Finalizers, demoMicroServiceFinalizer)if err : r.Update(ctx, dms); err ! nil {return ctrl.Result{}, err}}return ctrl.Result{}, nil } 为了方便展示主逻辑我将创建 Deployment 和删除 Deployment 的代码封装到 applyDeployment 和 cleanDeployment 两个方法中了。 如上面代码所示想要判断「删除 DemoMicroService」到「DemoMicroService 完全消失」这段生命周期的阶段关键点在于判断 DemoMicroService.ObjectMeta.DeletionTimestam 和 DemoMicroService.ObjectMeta.Finalizers 这两个元信息。前者表示「删除 DemoMicroService」这一行为的具体发生时间如果不为0则表示「删除 DemoMicroService」指令已经下达而后者表示在真正删除 DemoMicroService 之前即「DemoMicroService 完全消失」之前还有哪些逻辑没有被执行如果 Finalizers 不为空那么该 DemoMicroService 则不会真正消失。 任何 resource 的 ObjectMeta.Finalizers 都是一个字符串的列表每一个字符串都表示一段 pre-delete 逻辑尚未被执行。如上面的「demomicroservice.finalizers.devops.my.domain」所示表示着 DemoMicroService 控制器对 DemoMicroService resource 的 pre-delete 逻辑该 Finalizer 会在 DemoMicroService 被创建之后迅速被 DemoMicroService 控制器给种上并在下达「删除 DemoMicroService」指令后且 pre-delete 逻辑在这里即删除 Deployment被正确执行完后再由 DemoMicroService 控制器将该 Finalizer 抹除。至此 DemoMicroService 上的 Finalizers 为空此时 Kubernetes 才会让该 DemoMicroService 完全消失。Kubernetes 正是通过这种机制创造出了 resource 的「对该 resource 下达删除指令」到「该 resource 完全消失」这段生命周期的阶段。 如果因为各种原因导致 Finalizers 不可能为空那么会发生什么答案是会导致这个 resource 永远无法被删掉如果你使用 kubectl delete 去删那么这个指令将永远不会返回。这也是使用 Finalizers 机制时会经常碰到的问题如果你发现有一个 resource 始终无法被删掉那么请检查一下你是否种上了某个不会被删掉的 Finalizer。 2.1.6 调试与发布 2.1.5.1 调试 在我们开始运行调试我们编写的控制器之前我们最好为我们的 DemoMicroService CRD 设置一个缩写名称这是为了我们后面的操作不用输入demomicroservice这么长的名称。 如上图在 _api/v1/demomicroservice_types.go_ 文件里加上一行注释我们将demomicroservice的缩写设置成dms。顺带一提kubebuilder 中所有带kubebuilder的注释都是有用的不要轻易删掉他们是对该 kubebuilder 工程的一些配置。 改完之后再执行make manifests指令会发现 kubebuiler 生成的 CRD 文件被修改添加了缩写的配置 再设置缩写之后我们执行以下命令将 CRD 安装到你开发机器当前连接的 Kubernetes 集群 make install 之后就能看到你的集群里被安装了自己定义的 CRD 现在我们可以启动我们的控制器程序了由于笔者使用的是 GoLand IDE所以我直接点击启动了 main.go 里的 main 函数 读者可根据自己的开发工具选择使用自己的启动方式总之就是运行该 Go 程序即可。也可以直接使用下面的命令启动 make run 在本地启动控制器之后你的开发机器就是该 DemoMicroService 的控制器了你连接的 Kubernetes 集群会将有关 DemoMicroService resource 的变更发送到你的开发机器上执行所以打断点也会断住。 接下来我们验收一下我们之前写的代码是否正常工作执行命令 kubectl apply -f ./config/samples/devops_v1_demomicroservice.yaml 我们会看到 Kubernetes 集群中出现了该 resource 以上的dms即我们刚才设置的demomicroservice的缩写。 以及伴随着 DemoMicroService 创建的 Deployment 及其创建的 Pod 当我们执行删除 dms 的命令时这些 deployment 和 pod 也会被删掉。 2.1.5.1 发布 使用如下命令将控制器发布到你开发机器当前连接的 Kubernetes 集群 make deploy 之后 kubebuilder 会在集群中创建一个专门用于放该控制器的 namespace在我们这个例子里该 namespace 为 example-system之后可以通过如下命令看到自己的控制器已经被发布到你当前连接的集群 kubectl get po -n example-system 如果该 pod 发布失败那么多半是国内连接不上 gcr.io 的镜像仓库导致的在工程内搜索gcr.io的镜像仓库将其替换成你方便访问的镜像仓库即可。 2.2 总结 我们在本节大致了解了 kubebuilder 脚手架的使用方法和 kubebuilder 程序的开发方法。并实践了一个实现了微服务的部署和下线的控制器。 或许读者会问为什么不直接创建一个 Deployment而要用这么麻烦的方式来实现。那是因为自定义的 CRD 能更好的抽象开发者的业务场景比如在我们的这个例子中我们的微服务只关心镜像地址其他的 Deployment 属性全部可以默认那么我们的 DemoMicroService 看上去就比 Deployment 清爽很多。 除此之外这样做还有如下优点 Kubernetes 的 resource 保证了执行结果的一致性Kubernetes 对于执行 resource 天然的符合幂等性并且其内提供的 resourceVersion 机制也解决了并发执行时带来的结果不一致的问题这些问题如果开发者自己去解往往会费时费力而且吃力不讨好。 kubebuilder 的开发模型帮助开发者节省了大量工作量这些工作量包括监听 resource 变化出错 resource 的重试以及必要的 YAML configurations 生成。 这里值得一提的是 kubebuilder 的重试机制如果你自定的 resource 执行失败那么 kubebuilder 会帮助你重试直到该 resource 被成功执行这省去了你自己实现重试逻辑的工作量。调用链路安全可控通过将你的业务逻辑沉淀成 CRD 和控制器可以完全享受 Kubernetes 的 rbac 权限管控系统能更安全方便和精细的管控你开发的控制器接口。 由于我们的示例过于简单所以这些优势听起来可能比较苍白在下一节我们到更复杂的运维场景里之后我们能对以上描述的这些优势有更深的体会。 3. 运维系统实现 如果你只对 operator pattern 及其实践感兴趣并不关心运维系统如何实现那么可以不读本节。 下图展示了在我们开发的微服务平台中微服务运维控制器所做的事情读者可以看到实现的这样一个 operator 在整个微服务平台中的位置 上图中的 dmsp 表示我们的微服务平台而 dmsp-ops-operator 即该运维系统的控制器。可以看到因为 dmsp-ops-operator 的存在用户操作管控台要下达的指令就很简单实际的运维操作都由 dmsp-ops-operator 执行即可。并且 dmsp-ops-operator 也作为 Kubernetes 集群里的能力沉淀到了技术栈的最下层与上层的业务逻辑完全清晰的分离了开来。 3.1 微服务的完整抽象 在第2节我们实现了一个 demo 微服务事实上那个 demo 微服务只关心镜像地址这明显是不够的所以我们实现了 MicroServiceDeploy CRD 及其控制器能抽象和实现更多的运维功能一个 MicroServiceDeploy CR 看起来如下所示 apiVersion: custom.ops/v1 kind: MicroServiceDeploy metadata:name: ms-sample-v1s0 spec:msName: ms-sample # 微服务名称fullName: ms-sample-v1s0 # 微服务实例名称version: 1.0 # 微服务实例版本path: v1 # 微服务实例的大版本该字符串将出现在微服务实例的域名中image: just a image url # 微服务实例的镜像地址replicas: 3 # 微服务实例的 replica 数量autoscaling: true # 该微服务是否开启自动扩缩容功能needAuth: true # 访问该微服务实例时是否需要租户 base 认证config: password88888888 # 该微服务实例的运行时配置项creationTimestamp: 1535546718115 # 该微服务实例的创建时间戳resourceRequirements: # 该微服务实例要求的机器资源limits: # 该微服务实例会使用到的最大资源配置cpu: 2memory: 4Girequests: # 该微服务实例至少要用到的资源配置cpu: 2memory: 4Giidle: false # 是否进入空载状态 而以上一个 resource 实际上创建了很多其他的 Kubernetes resource这些 Kubernetes resource 才真正构成了该微服务实际的能力。创建这些 Kubernetes resource 的方式基本上就是第2节讲解的方式。 下面我将分开介绍这些 Kubernetes resource并分别说明这些 Kubernetes resource 的意义和作用。 3.2 ServiceServiceAccountDeployment 首先是对于一个微服务而言必备的 Service, ServiceAccount 和 Deployment。这三种 resource 大家应该已经很熟悉了这里就不过多说明直接贴出由 MicroServiceDeploy 控制器创建出的 YAML 配置。 3.2.1 ServiceServiceAccount apiVersion: v1 kind: Service metadata:labels:app: ms-samplemy-domain-ops-controller-make: truename: ms-sample spec:ports:- name: httpport: 9898protocol: TCPtargetPort: 9898selector:app: ms-sample status:loadBalancer: {} --- apiVersion: v1 kind: ServiceAccount metadata:labels:my-domain-ops-controller-make: truename: ms-sample 上面的 my-domain-ops-controller-make 是自定义控制器自己打上的 label用于区分该 resource 是我们的自定义控制器创建的。 3.2.2 Deployment apiVersion: apps/v1 kind: Deployment metadata:annotations:app.ops.my.domain/last-rollout-at: 1234labels:app: ms-samplemy-domain-ops-controller-make: truename: ms-sample-v1s0 spec:replicas: 1selector:matchLabels:app: ms-sampletype: RollingUpdatetemplate:metadata:annotations:app.ops.my.domain/create-at: 1234prometheus.io/scrape: truelabels:app: ms-samplespec:containers:- env:- name: POD_NAMEvalueFrom:fieldRef:apiVersion: v1fieldPath: metadata.name- name: SERVICE_NAMEvalueFrom:fieldRef:apiVersion: v1fieldPath: metadata.labels[app]image: just a image urlimagePullPolicy: Alwaysname: ms-sampleports:- containerPort: 9898protocol: TCPresources:limits:cpu: 100mmemory: 400Mirequests:cpu: 100mmemory: 400MivolumeMounts:- mountPath: /home/admin/logsname: log- mountPath: /home/admin/confname: configinitContainers:- command:- sh- -c- chmod -R 777 /home/admin/logs || exit 0image: busyboximagePullPolicy: Alwaysname: log-volume-mount-hackvolumeMounts:- mountPath: /home/admin/logsname: logvolumes:- hostPath:path: /data0/logs/ms-sample/1.0type: DirectoryOrCreatename: log- configMap:defaultMode: 420name: ms-sample-v1s0name: config 上面的 Deployment 有三点需要说明 Pod 里 app.ops.my.domain/create-at 的 annotation 是控制器给 Pod 打上的注释用于强制让该 Deployment 下的 Pods重启这样即使 Deployment apply 时没有其他变化这些 Pods 也会被重启这在需要 Pods 被强制重启时很有用。上面 name 为 log 的 volume 表示日志的 volume 挂载代表容器内的 /home/admin/logs 目录里收集的日志会同步到宿主机的 /data0/logs/ms-sample/1.0 目录下。要让这个机制成立需要你容器里的服务确保将日志打到 /home/admin/logs 目录下。上面 name 为 config 的 volume 挂载表示将 name 为 ms-sample-v1s0 的 ConfigMap 配置挂载到容器里的 /home/admin/conf 目录下这样你在你的容器里通过读取 /home/admin/conf 目录下的配置文件就能在容器中读取到运行时配置。详情将在 3.3 节里说明。 3.3 代表运行时配置项的ConfigMap 在 3.2.2 节提到的 ms-sample-v1s0 ConfigMap 如下所示 apiVersion: v1 data:ms.properties: namefoo kind: ConfigMap metadata:labels:app: ms-samplemy-domain-ops-controller-make: truename: ms-sample-v1s0上述 ConfigMap 当 mount 到容器内的 /home/admin/conf 下时就会在 /home/admin/conf 下创建一个 ms.properties 文件该文件的内容就是namefoo。此时容器内部便可以通过读取该文件来获取运行时配置。而且该配置是动态实时更新的即 ConfigMap 变化了容器里的文件内容也会变化这样就可以做到即使容器不重启最新的配置也会生效。 3.3 资源管理 在 Kubernetes 中资源这一名词一般指代系统默认的机器资源即cpu 与 memory。 这里的资源管理是指对微服务部署的 namespace 进行资源总量的管控以及对每个微服务部署的容器做资源限制。用于实现这一目的的 YAML 为 apiVersion: v1 kind: ResourceQuota metadata:labels:my-domain-ops-controller-make: truename: default-resource-quotanamespace: default spec:hard:requests.cpu: 88limits.cpu: 352requests.memory: 112Gilimits.memory: 448Gi --- apiVersion: v1 kind: LimitRange metadata:labels:my-domain-ops-controller-make: truename: default-limit-rangenamespace: default spec:limits:- default:cpu: 400mmemory: 2GidefaultRequest:cpu: 100mmemory: 500Mimax:cpu: 2memory: 8Gitype: Container 与其他的 Kubernetes resource 不同的是上面两个 resource 并不是在部署 MicroServiceDeploy CR 时创建的而是在控制器部署时创建的作为针对集群内某一 namespace 的配置。所以你需要在控制器的 init 方法中去 create 上面两个 resource。 上面的 ResourceQuota 限制了 default namespace 所能占用的资源额度总量。 而 LimitRange 限制了 default namespace 中所有没有限制资源量的容器所能占用的资源额度。之所以要为每个让容器有缺省的资源额度原因在于 Kubernetes 会对根据资源配置的情况对 Pod 做分级如果一个 Pod 没有被配置资源量则该 Pod 重要性最低其次是分配了资源配置但是 limits ! requests 的 Pod最后是分配了资源配置而且 limits requests 的 Pod其重要性最高。Kubernetes 会在资源总量不足时将重要性更低的 Pod 释放掉用于调度更重要的 Pod。 以上描述的三个等级分别称作BestEffort优先级最低BurstableGuaranteed优先级最高。该等级称作 QoS(Quality of Service) 等级。你可以在 Kubernetes Pod resource 的 status.qosClass 字段里查看该 Pod 的 QoS 等级。 3.4 HPA自动扩缩容 用于实现自动扩缩容的 HPA(HorizontalPodAutoscaler) resource 也是在部署 MicroServiceDeploy CR 时创建的他针对的是这个 MicroServiceDeploy CR 代表的微服务 apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata:labels:app: ms-samplemy-domain-ops-controller-make: truename: ms-sample-v1s0 spec:maxReplicas: 10minReplicas: 1scaleTargetRef:apiVersion: apps/v1kind: Deploymentname: ms-sample-v1s0targetCPUUtilizationPercentage: 81 上面的 HPA resource 表示将根据 cpu 使用率来对 ms-sample-v1s0 Deployment 下的 Pod 进行扩缩容并且 Pod 数的区间在[1, 10]。 关于自动扩缩容可以讲的比较多我会单写一篇文章详细的来说明这一块内容。 4. 参考资料 《Kubernetes in Action》——电子工业出版社 Kubernetes 对 Operator 的官方解释https://kubernetes.io/docs/concepts/extend-kubernetes/operator/ kubebuilder 使用手册https://book.kubebuilder.io/quick-start.html 本文所有示例代码开源于https://github.com/l4wei/kubebuilder-example 数加平台DataWorks团队2020届校招实习生报名已经开始 加入我们将有机会参与大数据人工智能算法云计算深度学习机器学习WebIDE 的产品化以相关的国内领先的创新工程, 将在专业师兄指导下快速成长。 简历精准投递快速进入面试通道; 请将简历发送至: dataplus-develop-recruitlist.alibaba-inc.com 邮件标题“【实习报名】姓名学校专业职位期望base地北京或杭州” 有其他疑问欢迎邮件咨询。 作者信息 吴谋花名四唯阿里云智能-计算平台事业部技术专家负责数加平台 DataWorks 的微服务生态建设目前主要关注 Kubernetes、微服务等相关技术方向。 原文链接 本文为云栖社区原创内容未经允许不得转载。
http://www.zqtcl.cn/news/26757/

相关文章:

  • 网推资源网站长春网站建设方案服务
  • 网站建设建设公司网站开发竞聘报告
  • 网站进行诊断山西手机版建站系统开发
  • 电商网站优缺点营销网站制作
  • 医院可以做网站吗广告推广哪个平台好
  • 公司网站开发模板pc端网站建设哪里有
  • 做招聘网站价格怀化电视台网站
  • 知名网站制作企业昆明网站搭建多少钱
  • 房产网站建设方案项目书网站建设新际
  • 什么网站可以做国外生意贵州网站建设seo优化
  • 网站的安全建设或者解决方案ae模板免费下载网站有哪些
  • 洛阳有没有做家教的网站西安网站代维护
  • 大型网站技术架构 pdfapp怎么制作流程
  • 湖北城市建设职业技术学院教务网站wordpress支持的语言包
  • 张家港做网站的最新网站域名
  • 江苏建设人才无纸化考核网站新网站快速收录
  • 怎么做期货网站孝感网站开发选优搏
  • 易班网站的建设内容asp 网站 内容静态化
  • 邯郸网站制作厂家wordpress搜索用户
  • 新网站建设的流程软件开发模型思维导图
  • 有什么好看的网站资源网站建设支出
  • 服务器512m内存做网站昆山市有没有做网站设计的
  • 绿化面积 建设网站建筑类培训网校排名
  • 青岛网站设计公司哪家好免费空间建网站
  • 东莞清溪网站建设用thinkphp做的网站
  • 买了一台配置强悍的电脑怎么做网站服务器滨州wordpress建站
  • 书店网站建设的设计报告网站桥页怎么找
  • 网站建设技术中心网站开发如何处理兼容性问题
  • 做网站简单还是做app简单简历制作网址
  • 网站底部备案东营网手机版