mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
4420 字
22 分钟
Kubernetes Operator开发超详细指南:从零基础到实战部署

Kubernetes Operator开发超详细指南:从零基础到实战部署#

一、前言:什么是Operator?#

1.1 Operator的本质#

想象一下,如果你有一个复杂的应用,比如数据库集群,你需要手动执行很多操作:部署、配置、升级、备份、故障恢复等等。这些操作需要专业的运维知识,而且容易出错。

Operator就是为了解决这个问题而生的。它是一种Kubernetes扩展,能够将特定应用的运维知识编码到软件中,实现应用的自动化管理。

简单来说,Operator = 自定义资源(CRD) + 控制器(Controller) + 应用运维知识

1.2 为什么需要Operator?#

  • 自动化管理:自动执行应用的部署、配置、升级等操作
  • 一致性:确保应用在不同环境中的配置和状态一致
  • 减少人为错误:避免手动操作带来的失误
  • 标准化:将最佳实践编码到软件中,确保应用按照标准方式运行
  • 可扩展性:可以根据应用的特性定制管理逻辑

1.3 Operator的工作原理#

Operator的工作基于Kubernetes的控制器模式,核心是一个调谐循环(Reconcile Loop):

  1. 观察期望状态:读取自定义资源(CR)中定义的期望状态(Spec)
  2. 观察实际状态:检查集群中应用的实际状态
  3. 调谐:如果实际状态与期望状态不一致,执行操作使它们一致

这个过程会持续运行,确保应用始终保持在期望的状态。

二、准备工作:环境搭建#

2.1 安装必要工具#

参考开发环境搭建进行依赖工具的安装:

  • go: Operator开发使用Go语言,需要安装Go 1.20或更高版本。
  • kubectl: Kubernetes的命令行工具,用于与集群交互。
  • kind: 用于在本地运行Kubernetes集群的工具,非常适合开发和测试。
  • kubebuilder: 用于构建Kubernetes API扩展和控制器的框架,是开发Operator的必备工具。

2.2 创建本地Kubernetes集群#

参考基于kind搭建测试集群创建一个本地高可用Kubernetes集群,用于开发和测试。

集群配置:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
apiServerAddress: "192.168.1.13"
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 6443
hostPort: 6443
listenAddress: "192.168.1.13"
protocol: tcp
- role: control-plane
- role: control-plane
- role: worker
extraPortMappings:
- containerPort: 80
hostPort: 7080
listenAddress: "0.0.0.0"
protocol: tcp
- containerPort: 443
hostPort: 7443
listenAddress: "0.0.0.0"
protocol: tcp
- role: worker
extraPortMappings:
- containerPort: 80
hostPort: 8080
listenAddress: "0.0.0.0"
protocol: tcp
- containerPort: 443
hostPort: 8443
listenAddress: "0.0.0.0"
protocol: tcp
- role: worker
extraPortMappings:
- containerPort: 80
hostPort: 9080
listenAddress: "0.0.0.0"
protocol: tcp
- containerPort: 443
hostPort: 9443
listenAddress: "0.0.0.0"
protocol: tcp

创建高可用集群命令:

sudo kind create cluster --config=huari.yaml --name huari-test --image kindest/node:v1.34.0 --retain; sudo kind export logs --name huari-test

切换kubectl上下文:

sudo kubectl cluster-info --context kind-huari-test

查看信息:

# 查看集群节点
sudo kubectl get nodes
# 查看集群全部的pod
sudo kubectl get pods -A -owide

删除集群:

sudo kind delete cluster --name huari-test

2.3 配置Go环境变量#

为了确保依赖下载顺畅,设置Go的代理:

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

三、项目初始化:创建Operator项目#

3.1 初始化项目结构#

首先,创建一个目录来存放我们的Operator项目,并使用kubebuilder初始化项目:

# 创建项目目录
mkdir -p ~/workspace/operator/myapp-operator
cd ~/workspace/operator/myapp-operator
# 初始化项目
kubebuilder init --domain example.com --repo myapp-operator

执行这个命令后,kubebuilder会创建一个基础的项目结构,并下载必要的依赖。你会看到类似以下输出:

INFO Writing kustomize manifests for you to edit...
INFO Writing scaffold for you to edit...
INFO Get controller runtime
INFO Update dependencies
Next: define a resource with:
$ kubebuilder create api

3.2 查看项目结构#

初始化完成后,让我们查看一下项目的结构:

ls -la

你会看到类似以下的目录结构:

.
├── cmd/ # 命令行入口
├── config/ # 配置文件
├── hack/ # 脚本文件
├── test/ # 测试文件
├── Dockerfile # Docker构建文件
├── Makefile # 构建脚本
├── PROJECT # 项目元数据
├── README.md # 项目说明
├── go.mod # Go模块文件
└── go.sum # Go依赖校验文件

3.3 创建API和控制器#

现在,让我们使用kubebuilder创建一个API资源和对应的控制器:

kubebuilder create api --group apps --version v1 --kind MyApp

执行这个命令时,kubebuilder会提示你是否创建资源和控制器,都选择y

INFO Create Resource [y/n]
y
INFO Create Controller [y/n]
y
INFO Writing kustomize manifests for you to edit...
INFO Writing scaffold for you to edit...
INFO api/v1/myapp_types.go
INFO api/v1/groupversion_info.go
INFO internal/controller/suite_test.go
INFO internal/controller/myapp_controller.go
INFO internal/controller/myapp_controller_test.go
INFO Update dependencies
INFO Running make
mkdir -p /Users/king/workspace/operator/myapp-operator/bin
Downloading sigs.k8s.io/controller-tools/cmd/controller-gen@v0.19.0
/Users/king/workspace/operator/myapp-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:
$ make manifests

3.4 查看更新后的项目结构#

创建API和控制器后,项目结构会更新,让我们查看一下:

ls -la
ls -la api/v1/
ls -la internal/controller/

你会看到新增了以下文件:

  • api/v1/myapp_types.go:定义了MyApp资源的类型
  • api/v1/groupversion_info.go:定义了API组和版本信息
  • internal/controller/myapp_controller.go:实现了MyApp资源的控制器逻辑
  • internal/controller/myapp_controller_test.go:控制器的测试文件

四、CRD定义:定义自定义资源#

4.1 理解CRD结构#

CRD(Custom Resource Definition)是自定义资源的定义,它扩展了Kubernetes API,允许我们创建和管理自定义资源。

api/v1/myapp_types.go文件中,我们可以看到MyApp资源的定义:

package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// 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.
// MyAppSpec defines the desired state of MyApp
type MyAppSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// The following markers will use OpenAPI v3 schema to validate the value
// More info: https://book.kubebuilder.io/reference/markers/crd-validation.html
// foo is an example field of MyApp. Edit myapp_types.go to remove/update
// +optional
Foo *string `json:"foo,omitempty"`
}
// MyAppStatus defines the observed state of MyApp.
type MyAppStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
// For Kubernetes API conventions, see:
// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
// conditions represent the current state of the MyApp resource.
// Each condition has a unique type and reflects the status of a specific aspect of the resource.
//
// Standard condition types include:
// - "Available": the resource is fully functional
// - "Progressing": the resource is being created or updated
// - "Degraded": the resource failed to reach or maintain its desired state
//
// The status of each condition is one of True, False, or Unknown.
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// MyApp is the Schema for the myapps API
type MyApp struct {
metav1.TypeMeta `json:",inline"`
// metadata is a standard object metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty,omitzero"`
// spec defines the desired state of MyApp
// +required
Spec MyAppSpec `json:"spec"`
// status defines the observed state of MyApp
// +optional
Status MyAppStatus `json:"status,omitempty,omitzero"`
}
// +kubebuilder:object:root=true
// MyAppList contains a list of MyApp
type MyAppList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []MyApp `json:"items"`
}
func init() {
SchemeBuilder.Register(&MyApp{}, &MyAppList{})
}

4.2 修改CRD定义#

现在,让我们修改MyApp资源的定义,添加一些实际的字段。我们将创建一个简单的应用管理资源,用于部署和管理Nginx应用。

编辑api/v1/myapp_types.go文件:

// MyAppSpec defines the desired state of MyApp
type MyAppSpec struct {
// 应用名称
AppName string `json:"appName,omitempty"`
// 副本数
Replicas int32 `json:"replicas,omitempty"`
// 镜像信息
Image string `json:"image,omitempty"`
// 端口信息
Port int32 `json:"port,omitempty"`
}
// MyAppStatus defines the observed state of MyApp
type MyAppStatus struct {
// 部署状态
Status string `json:"status,omitempty"`
// 可用副本数
AvailableReplicas int32 `json:"availableReplicas,omitempty"`
// 服务URL
ServiceURL string `json:"serviceURL,omitempty"`
}

4.3 生成代码#

修改完CRD定义后,我们需要运行make generate命令来生成相关的代码:

make generate

这个命令会生成DeepCopy方法的实现,确保资源对象可以正确地进行深拷贝操作。

4.4 生成CRD清单#

现在,让我们运行make manifests命令来生成CRD的清单文件:

make manifests

这个命令会生成以下文件:

  • CRD定义文件(在config/crd/bases/目录下)
  • RBAC规则文件(在config/rbac/目录下)
  • Webhook配置文件(如果启用了Webhook)

五、控制器开发:编写调谐逻辑#

5.1 理解控制器结构#

控制器是Operator的核心,它负责监控自定义资源的变化,并执行调谐逻辑,确保实际状态与期望状态一致。

internal/controller/myapp_controller.go文件中,我们可以看到MyApp控制器的结构:

package controller
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
appsv1 "myapp-operator/api/v1"
)
// MyAppReconciler reconciles a MyApp object
type MyAppReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=apps.example.com,resources=myapps,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=apps.example.com,resources=myapps/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps.example.com,resources=myapps/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 MyApp 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.22.1/pkg/reconcile
func (r *MyAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = logf.FromContext(ctx)
// TODO(user): your logic here
return ctrl.Result{}, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *MyAppReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&appsv1.MyApp{}).
Named("myapp").
Complete(r)
}

5.2 编写调谐逻辑#

现在,让我们实现Reconcile函数的逻辑。我们的目标是:

  1. 当创建MyApp资源时,自动创建对应的Deployment和Service
  2. 当更新MyApp资源时,自动更新对应的Deployment和Service
  3. 当删除MyApp资源时,自动删除对应的Deployment和Service
  4. 更新MyApp资源的状态,反映实际的部署情况

编辑internal/controller/myapp_controller.go文件:

import (
"context"
"fmt"
"time"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
apps "myapp-operator/api/v1"
)
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
func (r *MyAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
logger.Info("开始处理MyApp资源", "name", req.Name, "namespace", req.Namespace)
// 1. 获取MyApp资源
var myapp apps.MyApp
if err := r.Get(ctx, req.NamespacedName, &myapp); err != nil {
if errors.IsNotFound(err) {
logger.Info("MyApp资源不存在,可能已被删除", "name", req.Name, "namespace", req.Namespace)
return ctrl.Result{}, nil
}
logger.Error(err, "获取MyApp资源失败", "name", req.Name, "namespace", req.Namespace)
return ctrl.Result{}, err
}
// 2. 处理Deployment
deploymentName := fmt.Sprintf("%s-deployment", myapp.Name)
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: deploymentName,
Namespace: myapp.Namespace,
Labels: map[string]string{
"app": myapp.Name,
},
},
Spec: appsv1.DeploymentSpec{
Replicas: &myapp.Spec.Replicas,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": myapp.Name,
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": myapp.Name,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: myapp.Name,
Image: myapp.Spec.Image,
Ports: []corev1.ContainerPort{
{
ContainerPort: myapp.Spec.Port,
},
},
},
},
},
},
},
}
// 设置OwnerReference,确保Deployment随MyApp资源一起删除
if err := ctrl.SetControllerReference(&myapp, deployment, r.Scheme); err != nil {
logger.Error(err, "设置Deployment的OwnerReference失败")
return ctrl.Result{}, err
}
// 检查Deployment是否存在
var existingDeployment appsv1.Deployment
err := r.Get(ctx, types.NamespacedName{Name: deploymentName, Namespace: myapp.Namespace}, &existingDeployment)
if err != nil {
if errors.IsNotFound(err) {
// Deployment不存在,创建它
logger.Info("创建Deployment", "name", deploymentName, "namespace", myapp.Namespace)
if err := r.Create(ctx, deployment); err != nil {
logger.Error(err, "创建Deployment失败")
return ctrl.Result{}, err
}
} else {
logger.Error(err, "获取Deployment失败")
return ctrl.Result{}, err
}
} else {
// Deployment存在,更新它
logger.Info("更新Deployment", "name", deploymentName, "namespace", myapp.Namespace)
existingDeployment.Spec = deployment.Spec
if err := r.Update(ctx, &existingDeployment); err != nil {
logger.Error(err, "更新Deployment失败")
return ctrl.Result{}, err
}
}
// 3. 处理Service
serviceName := fmt.Sprintf("%s-service", myapp.Name)
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: serviceName,
Namespace: myapp.Namespace,
Labels: map[string]string{
"app": myapp.Name,
},
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"app": myapp.Name,
},
Ports: []corev1.ServicePort{
{
Port: myapp.Spec.Port,
Protocol: corev1.ProtocolTCP,
},
},
Type: corev1.ServiceTypeClusterIP,
},
}
// 设置OwnerReference,确保Service随MyApp资源一起删除
if err := ctrl.SetControllerReference(&myapp, service, r.Scheme); err != nil {
logger.Error(err, "设置Service的OwnerReference失败")
return ctrl.Result{}, err
}
// 检查Service是否存在
var existingService corev1.Service
err = r.Get(ctx, types.NamespacedName{Name: serviceName, Namespace: myapp.Namespace}, &existingService)
if err != nil {
if errors.IsNotFound(err) {
// Service不存在,创建它
logger.Info("创建Service", "name", serviceName, "namespace", myapp.Namespace)
if err := r.Create(ctx, service); err != nil {
logger.Error(err, "创建Service失败")
return ctrl.Result{}, err
}
} else {
logger.Error(err, "获取Service失败")
return ctrl.Result{}, err
}
} else {
// Service存在,更新它
logger.Info("更新Service", "name", serviceName, "namespace", myapp.Namespace)
existingService.Spec = service.Spec
if err := r.Update(ctx, &existingService); err != nil {
logger.Error(err, "更新Service失败")
return ctrl.Result{}, err
}
}
// 4. 更新MyApp资源的状态
logger.Info("更新MyApp资源状态", "name", myapp.Name, "namespace", myapp.Namespace)
// 获取最新的Deployment状态
if err := r.Get(ctx, types.NamespacedName{Name: deploymentName, Namespace: myapp.Namespace}, &existingDeployment); err != nil {
logger.Error(err, "获取Deployment状态失败")
return ctrl.Result{}, err
}
// 获取最新的Service状态
if err := r.Get(ctx, types.NamespacedName{Name: serviceName, Namespace: myapp.Namespace}, &existingService); err != nil {
logger.Error(err, "获取Service状态失败")
return ctrl.Result{}, err
}
// 构建Service URL
serviceURL := fmt.Sprintf("%s.%s.svc.cluster.local:%d", existingService.Name, existingService.Namespace, myapp.Spec.Port)
// 更新状态
myapp.Status.Status = "Running"
myapp.Status.AvailableReplicas = existingDeployment.Status.AvailableReplicas
myapp.Status.ServiceURL = serviceURL
if err := r.Status().Update(ctx, &myapp); err != nil {
logger.Error(err, "更新MyApp状态失败")
return ctrl.Result{}, err
}
logger.Info("MyApp资源处理完成", "name", myapp.Name, "namespace", myapp.Namespace)
return ctrl.Result{}, nil
}

5.3 注册控制器#

确保控制器在Manager中正确注册,编辑cmd/main.go文件:

func main() {
// ... 省略前面的代码 ...
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Metrics: metricsServerOptions,
WebhookServer: webhookServer,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "80807133.example.com",
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
// 注册MyApp控制器
if err = (&controller.MyAppReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "MyApp")
os.Exit(1)
}
// ... 省略后面的代码 ...
}

六、部署和测试:验证Operator功能#

6.1 安装CRD到集群#

首先,让我们将CRD安装到集群中:

make install

这个命令会将生成的CRD定义应用到集群中,使Kubernetes能够识别和处理MyApp资源。

6.2 本地运行控制器#

现在,让我们在本地运行控制器,以便于开发和测试:

export ENABLE_WEBHOOKS=false
make run

这个命令会在本地启动控制器,它会监听集群中MyApp资源的变化,并执行调谐逻辑。

6.3 创建测试资源#

现在,让我们创建一个MyApp资源的实例,来测试我们的Operator:

创建一个myapp-test.yaml文件:

apiVersion: apps.example.com/v1
kind: MyApp
metadata:
name: myapp-test
namespace: default
spec:
appName: nginx-test
replicas: 2
image: m.daocloud.io/docker.io/nginx:latest
port: 80

然后,应用这个资源:

kubectl apply -f myapp-test.yaml

6.4 验证部署结果#

现在,让我们验证Operator是否正确地创建了Deployment和Service:

# 查看MyApp资源
kubectl get myapp
# 查看MyApp资源的详细信息
kubectl describe myapp myapp-test
# 查看Deployment
kubectl get deployment
# 查看Pod
kubectl get pods
# 查看Service
kubectl get service

你应该会看到:

  • MyApp资源状态为”Running”
  • Deployment被创建,副本数为2
  • Pod被创建,状态为”Running”
  • Service被创建,可以通过Service URL访问应用

6.5 更新测试资源#

现在,让我们更新MyApp资源,测试Operator的更新功能:

修改myapp-test.yaml文件,将副本数改为3:

apiVersion: apps.example.com/v1
kind: MyApp
metadata:
name: myapp-test
namespace: default
spec:
appName: nginx-test
replicas: 3 # 修改为3
image: m.daocloud.io/docker.io/nginx:latest
port: 80

然后,应用这个更新:

kubectl apply -f myapp-test.yaml

等待几秒钟后,验证更新结果:

# 查看MyApp资源的详细信息
kubectl describe myapp myapp-test
# 查看Deployment
kubectl get deployment
# 查看Pod
kubectl get pods

你应该会看到副本数已经更新为3,并且有3个Pod在运行。

6.6 删除测试资源#

最后,让我们删除MyApp资源,测试Operator的删除功能:

kubectl delete -f myapp-test.yaml

然后,验证资源是否被正确删除:

# 查看MyApp资源
kubectl get myapp
# 查看Deployment
kubectl get deployment
# 查看Pod
kubectl get pods
# 查看Service
kubectl get service

你应该会看到所有相关的资源都被删除了。

七、构建和部署:将Operator部署到集群#

7.1 构建Docker镜像#

现在,让我们将Operator构建为Docker镜像:

# 构建镜像
make docker-build IMG=myapp-operator:v1.0.0

这个命令会使用项目中的Dockerfile构建一个Docker镜像,标签为myapp-operator:v1.0.0

7.2 加载镜像到kind集群#

由于我们使用的是kind本地集群,我们需要将构建好的镜像加载到集群中:

kind load docker-image myapp-operator:v1.0.0 --name huari-test

7.3 部署Operator到集群#

现在,让我们将Operator部署到集群中:

make deploy IMG=myapp-operator:v1.0.0

这个命令会:

  1. 更新config/manager/manager.yaml文件中的镜像名称
  2. 使用kustomize构建部署清单
  3. 使用kubectl将部署清单应用到集群中

7.4 验证Operator部署#

现在,让我们验证Operator是否正确部署:

# 查看Deployment
kubectl get deployment -n myapp-operator-system
# 查看Pod
kubectl get pods -n myapp-operator-system
# 查看Service
kubectl get service -n myapp-operator-system

你应该会看到Operator的Deployment、Pod和Service都被创建,并且状态正常。

7.5 测试集群中的Operator#

现在,让我们在集群中测试Operator的功能,创建一个MyApp资源:

创建一个myapp-cluster-test.yaml文件:

apiVersion: apps.example.com/v1
kind: MyApp
metadata:
name: myapp-cluster-test
namespace: default
spec:
appName: nginx-cluster-test
replicas: 2
image: m.daocloud.io/docker.io/nginx:latest
port: 80

然后,应用这个资源:

kubectl apply -f myapp-cluster-test.yaml

验证部署结果:

# 查看MyApp资源
kubectl get myapp
# 查看MyApp资源的详细信息
kubectl describe myapp myapp-cluster-test
# 查看Deployment
kubectl get deployment
# 查看Pod
kubectl get pods
# 查看Service
kubectl get service

你应该会看到Operator正确地创建了Deployment、Pod和Service。

八、常见问题和解决方案#

8.1 连接集群失败#

错误信息dial tcp 127.0.0.1:8080: connect: connection refused

解决方案

  1. 确保kubeconfig文件正确配置
  2. 对于kind集群,使用以下命令导出kubeconfig:
    kind export kubeconfig --name=my-operator-test --kubeconfig=$HOME/.kube/config
  3. 验证kubectl是否可以连接到集群:
    kubectl cluster-info

8.2 CRD注解过长#

错误信息metadata.annotations: Too long: must have at most 262144 bytes

解决方案: 修改Makefile,在manifests命令中添加crd:maxDescLen=0

.PHONY: manifests
manifests: controller-gen
$(CONTROLLER_GEN) rbac:roleName=manager-role crd:maxDescLen=0 webhook paths="./..." output:crd:artifacts:config=config/crd/bases

8.3 Webhook证书问题#

错误信息failed to get webhook server certificate

解决方案

  1. 开发环境中,临时禁用Webhook:
    export ENABLE_WEBHOOKS=false
  2. 生产环境中,安装cert-manager并正确配置Webhook证书:
    kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml

8.4 控制器启动失败#

错误信息unable to create controller

解决方案

  1. 检查控制器代码是否有语法错误
  2. 检查依赖是否正确安装
  3. 检查RBAC权限是否正确配置
  4. 查看详细的日志信息,找出具体的错误原因:
    kubectl logs -n myapp-operator-system deployment/myapp-operator-controller-manager

十、结语#

10.1 总结#

通过本指南,我们学习了如何从零开始开发一个Kubernetes Operator,包括:

  • 环境搭建和准备
  • 项目初始化和结构创建
  • CRD定义和控制器开发
  • 部署和测试
  • 常见问题和解决方案

10.2 下一步#

现在,你已经掌握了Operator开发的基本技能,可以尝试开发更复杂的Operator:

  • 数据库Operator:管理数据库的部署、备份、恢复等操作
  • 监控系统Operator:管理监控系统的部署和配置
  • 消息队列Operator:管理消息队列的部署和扩缩容
  • 自定义应用Operator:为你的特定应用开发专用的Operator

10.3 资源推荐#

Operator开发是一个不断学习和实践的过程,希望本指南能够为你提供一个良好的起点。祝你在Operator开发的道路上越走越远!

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Kubernetes Operator开发超详细指南:从零基础到实战部署
https://hua-ri.cn/posts/kubernetes-operator开发超详细指南从零基础到实战部署/
作者
花日
发布于
2026-02-03
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时