详解Kubernetes控制器

科技   2024-11-10 07:35   海南  
【摘要】为了确保系统的高可用性、弹性和自动化运维,Kubernetes 引入了控制器机制,从而大大简化了大规模分布式系统的管理和维护。本文介绍了Kubernetes控制器原理并详细分享了自定义控制器开发具体过程步骤方法,是非常实用的实践教程。

【作者】ccww552010(社区ID),任职于科技企业

目录

第一章 Kubernetes控制器

1.1. 控制器出现的背景

1.2. 什么是Kubernetes控制器

1.3. Kubernetes中控制器作用

1.4. Kubernetes常见内置控制器

1.4.1. Deployment控制器

1.4.2. StatefulSet控制器

1.4.3. Daemonset控制器

1.4.4. Job控制器

1.4.5. CronJob控制器

第二章 控制器原理

2.1. 控制器模型

2.2. 控制器List/Watch原理

第三章 自定义控制器开发

3.1. Kubebuilder工具生成控制器项目

3.2. CRD编写

3.3. code-generator生成通用模板代码

3.4. 编写控制器代码

3.5. 测试启动控制器

3.5.1. 启动控制器

3.5.2. 创建测试应用


第一章 Kubernetes控制器

1.1. 控制器出现的背景

Kubernetes 控制器(Controllers)的出现背景源于现代分布式系统的复杂性和动态性。随着云计算的普及和微服务架构的广泛应用,系统资源和应用负载变得高度动态且难以手动管理。为了确保系统的高可用性、弹性和自动化运维,Kubernetes 引入了控制器机制。这些控制器通过持续监控集群状态,并采取自动化操作来维持预期状态,解决了资源调度、自愈、扩展和更新等关键问题,从而大大简化了大规模分布式系统的管理和维护。

1.2. 什么是Kubernetes控制器

Kubernetes控制器是Kubernetes 中用于实现和维护所需状态的一类进程或功能模块。每个控制器都是一个循环系统,负责不断监控和调整 Kubernetes 集群中的资源状态,以确保这些资源达到用户期望的状态。简单来说,控制器的主要职责就是通过监控和管理 Kubernetes 资源来实现自我修复和自动化操作。

1.3. Kubernetes中控制器作用

Kubernetes提供了一组内置控制器,用于管理核心资源并确保集群的基本功能正常运行。控制器的作用如下:

  • 状态驱动

Kubernetes使用声明式管理,即用户定义所需的系统状态,而不是具体的操作步骤。控制器负责将实际状态逐步调整为用户声明的状态。例如,用户希望有三个副本的应用运行在集群中,则副本控制器(Replication Controller)会确保实际有且只有三个副本运行。

  • 持续监控

控制器通过监控Kubernetes API Server中的资源对象,来获取集群的当前状态。一旦检测到状态偏差,例如Pod宕机、节点故障等,控制器会立即采取行动进行恢复。

  • 执行恢复操作

控制器根据特定的业务逻辑对资源进行创建、更新或删除操作,以恢复集群的期望状态。

例如,部署控制器(Deployment Controller)会管理 Pod 的部署和更新,确保按计划完成滚动更新和回滚操作。

  • 扩展性和灵活性

Kubernetes 提供了内置控制器,例如 Deployment、StatefulSet和 DaemonSet 等 控制器,用户也可以根据特定需求开发自定义控制器。自定义控制器可以管理用户自定义的资源类型,满足特定业务场景的需求。

  • 负载均衡和调度

一些控制器会与调度器协同工作,确保工作负载均匀分布在集群中的各个节点上。
例如,HPA(Horizontal Pod Autoscaler)控制器根据资源使用情况自动调整 Pod 副本数量,以实现负载均衡。

  • 自动化和自愈

控制器的一个关键功能是实现集群的自动化管理和自愈能力。例如,Node Controller检测到节点不可用时,会将该节点上的Pod标记为失效,并重新调度这些Pod到其他可用节点上。

  • 保障高可用性

控制器通过不断监控和调整资源,确保应用和服务的高可用性。可以自动处理故障和恢复任务,减少人工干预的需求。

1.4. Kubernetes常见内置控制器

kube-controller-manage r 的组件,就是一系列 内置 控制器的集合。可以查看一下 Kubernetes 项目的pkg/controller目录 ,如下图所示:

图1 kubernetes内置控制器

Kubernetes提供了一系列内置控制器,用于管理集群中的各种资源和应用。这些控制器负责不同类型的工作负载管理,这些内置控制器共同构成了Kubernetes强大的资源管理和调度系统,确保系统的高可用性和稳定性。

K8S有五种控制器,分别对应处理无状态应用、有状态应用、守护型应用和批处理应用。下面详细介绍一些常见的内置控制器及其作用。

1.4.1. Deployment控制器

Deployment控制器用于管理无状态应用的部署和生命周期。它支持滚动更新、回滚、扩缩容等功能,确保应用在集群中的高可用性。在Deployment 文件里可以定义Pod数量、更新方式、使用的镜像,资源限制等。如下所示:

apiVersion: extensions/v1beta1kind: Deployment metadata:  name: nginx-deploymentspec:  replicas: 2  template:    metadata:      labels:        app: nginx    spec:      containers:      - name: nginx        image: nginx:1.8.0        ports:        - containerPort: 80

1.4.2. StatefulSet控制器

StatefulSet 控制器用于管理有状态应用,确保应用实例的持久性和顺序性,例如数据库、分布式系统等。

主要功能如下:

  • 稳定的网络标识:每个 Pod 都有一个唯一的标识,在重新调度时保持不变。

  • 有序部署和更新:Pod 按照顺序创建、删除和更新,确保在特定顺序下执行操作。

  • 持久存储:每个 Pod 都有独立的持久存储卷,确保数据持久性。

StatefulSet资源定义如下:

apiVersion: apps/v1kind: StatefulSetmetadata:  name: my-statefulsetspec:  serviceName: "my-service"  replicas: 3  selector:    matchLabels:      app: my-app  template:    metadata:      labels:        app: my-app    spec:      containers:      - name: my-container        image: my-image:1.0        volumeMounts:        - name: my-storage          mountPath: /data  volumeClaimTemplates:  - metadata:      name: my-storage    spec:      accessModes: ["ReadWriteOnce"]      resources:        requests:          storage: 1Gi

1.4.3. Daemonset控制器

DaemonSet 控制器确保每个(或指定的)节点上都运行一个Pod,常用于日志收集、监控代理、系统守护进程等场景。

主要功能如下:

  • 节点覆盖:在每个节点上都运行一个Pod,适用于需要节点级别监控或日志收集的应用。

  • 自动适应节点变化:当有新节点加入集群时,DaemonSet控制器会自动在新节点上创建 Pod ;当节点被移除时,相关的Pod 也会被自动清理。

Daemonset资源定义如下:

apiVersion: apps/v1kind: DaemonSetmetadata:  name: my-daemonsetspec:  selector:    matchLabels:      app: my-daemon  template:    metadata:      labels:        app: my-daemon    spec:      containers:      - name: my-daemon-container        image: my-daemon-image:1.0

1.4.4. Job控制器

Job控制器用于管理一次性任务,确保任务成功完成一次或多次。

主要功能如下:

  • 任务执行:确保任务被成功执行指定次数。

  • 失败重试:任务失败时,自动重试直到成功或达到重试上限。

Job资源定义如下:

apiVersion: batch/v1kind: Jobmetadata:  name: my-jobspec:  template:    spec:      containers:      - name: my-container        image: my-job-image:1.0      restartPolicy: OnFailure

1.4.5. CronJob控制器

CronJob 控制器用于管理定时任务,按预定时间表周期性地执行任务。

主要功能如下 :

  • 定时执行 : 根据指定的时间表(Cron表达式 ), 定时启动Job任务。

  • 任务管理 : 管理定时任务的创建、执行和清理。

CronJob资源定义如下:

apiVersion: batch/v1kind: CronJobmetadata:  name: my-cronjobspec:  schedule: "*/5 * * * *"  jobTemplate:    spec:      template:        spec:          containers:          - name: my-container            image: my-cronjob-image:1.0          restartPolicy: OnFailure

第二章 控制器原理

2.1. 控制器模型

控制器模型则通过控制循环(Control Loop)将Kubernetes内部的资源调整为声明式API对象期望的样子。通过不断地监控和调整集群的状态,确保实际状态(Current State)接近期望状态(Desired State)。控制器模型分为两部分:

  • 控制器定义

控制器实际上都是由上半部分的控制器定义(包括期望状态),加上下半部分的被控制对象的模板组成的。如下图所示:

图2 控制器定义

  • 控制循环

控制器是控制循环的一部分,持续观察 Kubernetes 集群的状态,并采取行动使实际状态向期望状态靠拢 。控制器通过与Kubernetes API 服务器交互,创建、更新或删除资源,间接管理集群状态。

用一段Go语言风格的伪代码,描述这个控制循环:f

for {actualState := GetResourceActualState(rsvc)// 控制器模板定义    expectState := GetResourceExpectState(rsvc)     if actualState == expectState {        // do nothing} else {// 编排逻辑,调谐的最终结果一般是对被控制对象的某种写操作,比如增/删/改 Pod        Reconcile(rsvc)     }}

2.2. 控制器List/Watch原理

图3 控制器List/Informer机制

如上图所示,控制器Informer List/Informer 工作原理如下:

  • Informer通过Reflector包,与APIServer建立连接,使用ListAndWatch方法监控特定资源对象实例的变化;

  • APIServer 的 资源对象实例发生变化(Add,Update,Delete),Reflector会收到“事件通知” ,放入Delta FIFO Queue队列中;

  • Informer(执行processLoop方法)会不断从Delta FIFO Queue里读取增量。每拿到一个增量,Informer就会判断增量里的事件类型,然后创建/更新/删除本地对象的缓存(local store) ;

  • 缓存更新之后,Delta FIFO Queue会pop事件到Controller,Controller会调用预先注册的ResourceEventHandler回调函数进行处理 ;

  • Controller从工作队列中取出API对象(/),并启动一个worker执行相应的调协逻辑 。

第三章 自定义控制器开发

自定义控制器的需求源于每个应用和系统的独特性和复杂性,这些特性往往超出内置控制器的能力范围。通过创建自定义控制器,开发者可以定义和实现特定的逻辑来管理和调度资源,满足特殊业务需求和操作流程。这使得 Kubernetes 可以灵活适应各种应用场景,实现更加精细化和自动化的运维,从而提升系统的可靠性和效率。

自定义控制器开发,分为三部分:

  • CRD定义

  • 生成自定义资源的 Clientset、Informers、Listers等

  • 编写控制器调协代码

本节自定义应用控制器,基于应用模板,生成对应Deployment,service,ingress等资源对象

3.1. Kubebuilder工具生成控制器项目

# 常见项目目录并初始化为go项目mkdir -p myappgo mod init github.com/mypaas/app
# 初始化控制器项目kubebuilder init --domain mypaas.io
# 生成应用资源对象App控制器相关模板代码kubebuilder create api --group auth --version v1 --kind App

图4 控制器项目结构

3.2. CRD编写

编辑项目config /crd/bases 目录下文件auth .mypaas.io_apps.yaml ,根据业务需求,编写crd。

---apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata:  annotations:    controller-gen.kubebuilder.io/version: v0.10.0  creationTimestamp: null  name: apps.auth.mypass.iospec:  group: auth.mypass.io  names:    kind: App    listKind: AppList    plural: apps    singular: app  scope: Namespaced  versions:  - additionalPrinterColumns:    - jsonPath: .status.replicas      name: replicas      type: integer    name: v1beta1    schema:      openAPIV3Schema:        description: App is the Schema for the apps API        properties:          apiVersion:            description: '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/sig-architecture/api-conventions.md#resources'            type: string          kind:            description: '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/sig-architecture/api-conventions.md#types-kinds'            type: string          metadata:            type: object          spec:            description: AppSpec defines the desired state of App            properties:              enable_ingress:                type: boolean              enable_service:                type: boolean              image:                type: string              replicas:                format: int32                type: integer            required:            - enable_service            - image            - replicas            type: object          status:            description: AppStatus defines the observed state of App            properties:              replicas:                description: 'INSERT ADDITIONAL STATUS FIELD - define observed state                  of cluster Important: Run "make" to regenerate code after modifying                  this file 应用状态'                format: int32                type: integer            required:            - replicas            type: object        type: object    served: true    storage: true    subresources:      status: {}

3.3. code-generator生成通用模板代码

Kubebuilder工具不会生成Informers,Clientsets,listers等代码,因此需要借助code-generator工具生成。

  • 在pkg/apis/auth/v1目录下,创建doc.go,自定义对象go type,注册文件,如下图所示:

图5 注册类型相关文件图

  • doc.go文件

/*Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");you may not use this file except in compliance with the License.You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License.*/
// +k8s:deepcopy-gen=package// +groupName=cebpaas.io
// Package v1alpha1 is the v1alpha1 version of the API.package v1
  • apps.go
package v1
import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1")
// +genclient// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// App is the Schema for the apps APItype App struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"`
Spec AppSpec `json:"spec,omitempty"` Status AppStatus `json:"status,omitempty"`}
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// AppSpec defines the desired state of Apptype AppSpec struct { EnableIngress bool `json:"enable_ingress,omitempty"` EnableService bool `json:"enable_service"` Replicas int32 `json:"replicas"` Image string `json:"image"`}
// AppStatus defines the observed state of Apptype AppStatus struct { // 应用状态 Replicas int32 `json:"replicas"`}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// AppList contains a list of Apptype AppList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` Items []App `json:"items"`}
  • register.go

package v1
import ( "github.com/mypaas/app/pkg/apis/auth" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema")
// SchemeGroupVersion is group version used to register these objectsvar SchemeGroupVersion = schema.GroupVersion{Group: auth.GroupName, Version: "v1"}
// Kind takes an unqualified kind and returns back a Group qualified GroupKindfunc Kind(kind string) schema.GroupKind { return SchemeGroupVersion.WithKind(kind).GroupKind()}
// Resource takes an unqualified resource and returns a Group qualified GroupResourcefunc Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource()}
var ( // SchemeBuilder initializes a scheme builder SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) // AddToScheme is a global function that registers this API group & version to a scheme AddToScheme = SchemeBuilder.AddToScheme)
// Adds the list of known types to Scheme.func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &App{}, &AppList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil}
  • pkg/apis/auth目录下,创建register.go

package auth
// GroupName is the group name used in this packageconst ( GroupName = "mypaas.io")
  • 脚本文件生成

如下图所示,为脚本生成文件:

图6 code-generator脚本文件

  • boilerplate.go.txt:代码文件title注释声明。

 /*  Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.  */
  • tools.go: 引入code-gernerator

//go:build tools  // +build tools
package tools
  import _ "k8s.io/code-generator"
  • update-codegen.sh: 生成代码脚本

#!/usr/bin/env bash
set -o errexitset -o nounsetset -o pipefail
SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}
echo ${SCRIPT_ROOT}echo ${CODEGEN_PKG}
bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \ github.com/mypaas/app/pkg/generated github.com/mypaas/app/pkg/apis \ auth:v1 \  --go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt
  • verify-codegen.sh

set -o errexit  set -o nounset  set -o pipefail
SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
DIFFROOT="${SCRIPT_ROOT}/pkg" TMP_DIFFROOT="${SCRIPT_ROOT}/_tmp/pkg" _tmp="${SCRIPT_ROOT}/_tmp"
cleanup() { rm -rf "${_tmp}" } trap "cleanup" EXIT SIGINT
cleanup
mkdir -p "${TMP_DIFFROOT}" cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}"
"${SCRIPT_ROOT}/hack/update-codegen.sh" echo "diffing ${DIFFROOT} against freshly generated codegen" ret=0 diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$? cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}" if [[ $ret -eq 0 ]] then echo "${DIFFROOT} up to date." else echo "${DIFFROOT} is out of date. Please run hack/update-codegen.sh" exit 1  fi
  • 生成clientset,informer,list代码

# 将项目代码移动至GOPATH路径下mkdir -p $GOPATH/src/github.com/mypaas/app# 生成vendorgo mod vendor# 生成代码sh hack/update-codegen.sh

图7 clientset,lister,informer代码

3.4. 编写控制器代码

在controllers/ app_controllers.go 编写代码

/*Copyright 2023 xxxxx@126.com.
Licensed under the Apache License, Version 2.0 (the "License");you may not use this file except in compliance with the License.You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License.*/
package controllers
import ( "context" "fmt" "github.com/cebpaas/app/controllers/utils" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" networkv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "reflect" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" "time"
authv1beta1 "github.com/cebpaas/app/api/v1beta1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client")
// AppReconciler reconciles a App objecttype AppReconciler struct { client.Client Scheme *runtime.Scheme Recorder record.EventRecorder}
//+kubebuilder:rbac:groups=auth.cebpass.io,resources=apps,verbs=get;list;watch;create;update;patch;delete//+kubebuilder:rbac:groups=auth.cebpass.io,resources=apps/status,verbs=get;update;patch//+kubebuilder:rbac:groups=auth.cebpass.io,resources=apps/finalizers,verbs=update
// Reconcile is part of the main kubernetes reconciliation loop which aims to// move the current state of the cluster closer to the desired state.// TODO(user): Modify the Reconcile function to compare the state specified by// the App object against the actual cluster state, and then// perform operations to make the cluster state reflect the state specified by// the user.//// For more details, check Reconcile and its Result here:// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcilefunc (r *AppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx) app := &authv1beta1.App{}
logger.Info("enter....................")
// 从缓存中获取app if err := r.Get(ctx, req.NamespacedName, app); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) }
// 非删除app操作 if app.ObjectMeta.DeletionTimestamp.IsZero() { // 设置finalizer if app.Finalizers == nil { fmt.Println("finalizers...................") app.Finalizers = append(app.Finalizers, "finalizer.app.cebpaas.io") if err := r.Update(ctx, app); err != nil { r.Recorder.Event(app, corev1.EventTypeWarning, "update error", fmt.Sprintf("Failed to update App: %s", app.Name)) return ctrl.Result{}, err }
r.Recorder.Event(app, corev1.EventTypeNormal, "update finalizer", fmt.Sprintf("Successfully to update App:%s Finalizer", app.Name)) return ctrl.Result{}, nil }
d := &appsv1.Deployment{} // deploy处理 deployment := utils.NewDeployment(app) if err := r.Get(ctx, req.NamespacedName, d); err != nil { // 如果k8s中不存在deploy,则创建deployment if errors.IsNotFound(err) { // 设置deployment的属主,便于垃圾回收和事件触发 if err := controllerutil.SetControllerReference(app, deployment, r.Scheme); err != nil { return ctrl.Result{}, err }
if err := r.Create(ctx, deployment); err != nil { logger.Error(err, "create deploy failed") return ctrl.Result{}, err } }
return ctrl.Result{}, err } else { // 当image或者replicas发生变化,才会更新deployment if app.Spec.Replicas != *(d.Spec.Replicas) || app.Spec.Image != d.Spec.Template.Spec.Containers[0].Image { if err := r.Update(ctx, deployment); err != nil { return ctrl.Result{}, err } } }
// service处理 service := utils.NewService(app)
s := &corev1.Service{} if err := r.Get(ctx, types.NamespacedName{Name: app.Name, Namespace: app.Namespace}, s); err != nil { // 如果没有找到service,而且启动service开关,则创建 if errors.IsNotFound(err) && app.Spec.EnableService { if err := controllerutil.SetControllerReference(app, service, r.Scheme); err != nil { return ctrl.Result{}, err }
if err := r.Create(ctx, service); err != nil { logger.Error(err, "create service failed") return ctrl.Result{}, err } }
// 如果err不是not found,而且启动service,需要重试 if !errors.IsNotFound(err) && app.Spec.EnableService { return ctrl.Result{}, err }
} else { // k8s存在service且开启service开关 if app.Spec.EnableService { logger.Info("skip update service") } else { // service存在,但关闭service开关,则删除service if err := r.Delete(ctx, s); err != nil { return ctrl.Result{}, err } } }
// ingress处理 ingress := utils.NewIngress(app) i := &networkv1.Ingress{} if err := r.Get(ctx, types.NamespacedName{Name: app.Name, Namespace: app.Namespace}, i); err != nil { // ingress不存在且开启ingress开关,则创建ingress if errors.IsNotFound(err) && app.Spec.EnableIngress { if err := controllerutil.SetControllerReference(app, ingress, r.Scheme); err != nil { return ctrl.Result{}, err }
if err := r.Create(ctx, ingress); err != nil { logger.Error(err, "create ingress failed") return ctrl.Result{}, err } }
// 非不存在错误且开启ingress,需要重试 if !errors.IsNotFound(err) && app.Spec.EnableIngress { return ctrl.Result{}, err } } else { // 开启ingress开关,则不做任何操作 if app.Spec.EnableIngress { logger.Info("skip update ingress") } else { // 关闭ingress开关,则删除ingress if err := r.Delete(ctx, i); err != nil { return ctrl.Result{}, err } } }
// 更新app的状态:需要判断真实状态和期望状态,不一致,再更新,否则会导致循环触发。 if app.Status.Replicas != d.Status.AvailableReplicas { app.Status.Replicas = d.Status.AvailableReplicas if err := r.Status().Update(ctx, app); err != nil { return ctrl.Result{}, err } }
return ctrl.Result{}, nil
// 删除app操作: 删除后的清理工作 } else { fmt.Println("starting to handler something.") time.Sleep(time.Second * 5)
app.Finalizers = nil if err := r.Update(ctx, app); err != nil { return ctrl.Result{}, err }
return ctrl.Result{}, nil }}
// SetupWithManager sets up the controller with the Manager.func (r *AppReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&authv1beta1.App{}).WithEventFilter(&EventResource{}). // 只会watch属主为app的Deployment Owns(&appsv1.Deployment{}). // 只会watch属主为app的Ingress Owns(&networkv1.Ingress{}). // 只会watch属主为app的Service Owns(&corev1.Service{}). Complete(r)}
type EventResource struct { predicate.Funcs}
func (rl *EventResource) Update(e event.UpdateEvent) bool { newObj := e.ObjectNew.(*authv1beta1.App) // delete事件-----> update事件 if newObj.DeletionTimestamp != nil { return true }
oldObj := e.ObjectOld.(*authv1beta1.App)
if reflect.DeepEqual(newObj.Spec, oldObj.Spec) { // 更新状态
return false }
return true}

3.5. 测试启动控制器

3.5.1. 启动控制器

# 安装crd到kubernetesmake install
# 启动控制器make run

3.5.2. 创建测试应用

kubectl apply -f config/samples/ingress_v1beta1_app.yaml# yaml文件内容apiVersion: auth.mypaas.app/v1kind: Appmetadata:   labels:      app.kubernetes.io/name: app   name: app-samplespec:   # TODO(user): Add fields here   image: nginx:latest   replicas: 3   enable_ingress: false   enable_service: false
如有任何问题,可点击文末阅读原文,到社区原文下评论交流
觉得本文有用,请转发、点赞或点击在看,让更多同行看到

 资料/文章推荐:


欢迎关注社区 “容器云”技术主题 ,将会不断更新优质资料、文章。地址:https://www.talkwithtrend.com/Topic/98447

下载 twt 社区客户端 APP


长按识别二维码即可下载

或到应用商店搜索“twt”


长按二维码关注公众号
*本公众号所发布内容仅代表作者观点,不代表社区立场;封面图片由版权图库授权使用,授权本文不代表授权封面图片使用

twt企业IT社区
talkwithtrend.com社区(即twt社区)官方公众号,持续发布优秀社区原创内容。内容深度服务企业内各方向的架构师、运维主管、开发和运维工程师等IT专业岗位人群,让您时刻和国内企业IT同行保持信息同步。
 最新文章