【作者】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/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
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/v1
kind: StatefulSet
metadata:
name: my-statefulset
spec:
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/v1
kind: DaemonSet
metadata:
name: my-daemonset
spec:
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/v1
kind: Job
metadata:
name: my-job
spec:
template:
spec:
containers:
- name: my-container
image: my-job-image:1.0
restartPolicy: OnFailure
1.4.5. CronJob控制器
CronJob 控制器用于管理定时任务,按预定时间表周期性地执行任务。
主要功能如下 :
定时执行 : 根据指定的时间表(Cron表达式 ), 定时启动Job任务。
任务管理 : 管理定时任务的创建、执行和清理。
CronJob资源定义如下:
apiVersion: batch/v1
kind: CronJob
metadata:
name: my-cronjob
spec:
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 myapp
go 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/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.10.0
creationTimestamp: null
name: apps.auth.mypass.io
spec:
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, 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.
*/
// +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 API
type 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 App
type 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 App
type AppStatus struct {
// 应用状态
Replicas int32 `json:"replicas"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// AppList contains a list of App
type 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 objects
var SchemeGroupVersion = schema.GroupVersion{Group: auth.GroupName, Version: "v1"}
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func 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 package
const (
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 errexit
set -o nounset
set -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
# 生成vendor
go 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, 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.
*/
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 object
type 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/reconcile
func (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到kubernetes
make install
# 启动控制器
make run
3.5.2. 创建测试应用
kubectl apply -f config/samples/ingress_v1beta1_app.yaml
# yaml文件内容
apiVersion: auth.mypaas.app/v1
kind: App
metadata:
labels:
app :
name: app-sample
spec:
# TODO(user): Add fields here
image: nginx:latest
replicas: 3
enable_ingress: false
false :
如有任何问题,可点击文末阅读原文,到社区原文下评论交流 觉得本文有用,请转发、点赞或点击在看,让更多同行看到
资料/文章推荐:
欢迎关注社区 “容器云”技术主题 ,将会不断更新优质资料、文章。地址:https://www.talkwithtrend.com/Topic/98447