Kubernetes Operator 开发教程 – 从0到1实现简单的应用部署控制器

共计 5835 个字符,预计需要花费 15 分钟才能阅读完成。

1. 项目介绍

1.1 背景和目标

本教程将指导您使用 Go 语言和 kubebuilder 框架开发一个简单的 Kubernetes Operator。我们将实现一个应用部署控制器,通过自定义资源(CRD)来管理应用的部署。

主要目标:

  • 学习使用 kubebuilder 开发 operator 的基本流程
  • 实现一个简单但完整的 CRD 和 controller
  • 掌握 operator 开发的最佳实践

1.2 功能概述

我们将实现一个 SimpleApp CRD,用于部署简单的应用。它具有以下特点:

  • 支持指定容器镜像和副本数
  • 自动创建对应的 Deployment 资源
  • 支持查看应用部署状态

2. 环境准备

2.1 前置要求

  • Go 1.19+
  • Docker
  • Kubernetes 集群(1.20+)
  • kubectl 命令行工具
  • kubebuilder 3.0+

2.2 安装 kubebuilder

# 下载 kubebuilder 并添加到 PATH
curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/

3. 项目初始化

3.1 创建项目

# 创建项目目录
mkdir simple-app-operator
cd simple-app-operator

# 初始化项目
kubebuilder init --domain my.domain --repo my.domain/simple-app-operator

# 创建 API
kubebuilder create api --group apps --version v1alpha1 --kind SimpleApp

3.2 项目结构

.
├── Dockerfile
├── Makefile
├── PROJECT
├── api/
│   └── v1alpha1/
│       ├── simpleapp_types.go
│       ├── groupversion_info.go
│       └── zz_generated.deepcopy.go
├── config/
│   ├── default
│   ├── manager
│   ├── rbac
│   └── samples
├── controllers/
│   └── simpleapp_controller.go
└── main.go

4. CRD 开发

4.1 定义 API

编辑 api/v1alpha1/simpleapp_types.go:

package v1alpha1

import (
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// SimpleAppSpec 定义了 SimpleApp 的期望状态
type SimpleAppSpec struct {
    // 容器镜像
    Image string `json:"image"`

    // 副本数
    Replicas *int32 `json:"replicas"`
}

// SimpleAppStatus 定义了 SimpleApp 的实际状态
type SimpleAppStatus struct {
    // 可用副本数
    AvailableReplicas int32 `json:"availableReplicas"`

    // 应用状态
    Status string `json:"status"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:printcolumn:name="Replicas",type="integer",JSONPath=".spec.replicas"
//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.status"

// SimpleApp 是 apps.my.domain/v1alpha1 的顶层 API 对象
type SimpleApp struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   SimpleAppSpec   `json:"spec,omitempty"`
    Status SimpleAppStatus `json:"status,omitempty"`
}

//+kubebuilder:object:root=true

// SimpleAppList 包含 SimpleApp 列表
type SimpleAppList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []SimpleApp `json:"items"`
}

func init() {
    SchemeBuilder.Register(&SimpleApp{}, &SimpleAppList{})
}

4.2 生成代码

make generate
make manifests

5. Controller 实现

5.1 编写控制器逻辑

编辑 controllers/simpleapp_controller.go:

package controllers

import (
    "context"
    "fmt"

    appsv1 "k8s.io/api/apps/v1"
    corev1 "k8s.io/api/core/v1"
    "k8s.io/apimachinery/pkg/api/errors"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
    "sigs.k8s.io/controller-runtime/pkg/log"

    appsv1alpha1 "my.domain/simple-app-operator/api/v1alpha1"
)

// SimpleAppReconciler reconciles a SimpleApp object
type SimpleAppReconciler struct {
    client.Client
    Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=apps.my.domain,resources=simpleapps,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps.my.domain,resources=simpleapps/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=apps.my.domain,resources=simpleapps/finalizers,verbs=update
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete

func (r *SimpleAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := log.FromContext(ctx)

    // 获取 SimpleApp 实例
    simpleApp := &appsv1alpha1.SimpleApp{}
    err := r.Get(ctx, req.NamespacedName, simpleApp)
    if err != nil {
        if errors.IsNotFound(err) {
            return ctrl.Result{}, nil
        }
        return ctrl.Result{}, err
    }

    // 创建或更新 Deployment
    deployment := &appsv1.Deployment{}
    err = r.Get(ctx, req.NamespacedName, deployment)

    if err != nil && errors.IsNotFound(err) {
        // 创建新的 Deployment
        deployment = r.deploymentForSimpleApp(simpleApp)
        err = r.Create(ctx, deployment)
        if err != nil {
            log.Error(err, "Failed to create Deployment")
            return ctrl.Result{}, err
        }
    } else if err != nil {
        log.Error(err, "Failed to get Deployment")
        return ctrl.Result{}, err
    } else {
        // 更新现有 Deployment
        deployment.Spec.Replicas = simpleApp.Spec.Replicas
        deployment.Spec.Template.Spec.Containers[0].Image = simpleApp.Spec.Image

        err = r.Update(ctx, deployment)
        if err != nil {
            log.Error(err, "Failed to update Deployment")
            return ctrl.Result{}, err
        }
    }

    // 更新状态
    simpleApp.Status.AvailableReplicas = deployment.Status.AvailableReplicas
    if deployment.Status.AvailableReplicas == *simpleApp.Spec.Replicas {
        simpleApp.Status.Status = "Ready"
    } else {
        simpleApp.Status.Status = "NotReady"
    }

    err = r.Status().Update(ctx, simpleApp)
    if err != nil {
        log.Error(err, "Failed to update SimpleApp status")
        return ctrl.Result{}, err
    }

    return ctrl.Result{}, nil
}

// deploymentForSimpleApp 返回 SimpleApp 对应的 Deployment 对象
func (r *SimpleAppReconciler) deploymentForSimpleApp(app *appsv1alpha1.SimpleApp) *appsv1.Deployment {
    labels := map[string]string{
        "app": app.Name,
    }

    deployment := &appsv1.Deployment{
        ObjectMeta: metav1.ObjectMeta{
            Name:      app.Name,
            Namespace: app.Namespace,
        },
        Spec: appsv1.DeploymentSpec{
            Replicas: app.Spec.Replicas,
            Selector: &metav1.LabelSelector{
                MatchLabels: labels,
            },
            Template: corev1.PodTemplateSpec{
                ObjectMeta: metav1.ObjectMeta{
                    Labels: labels,
                },
                Spec: corev1.PodSpec{
                    Containers: []corev1.Container{{
                        Name:  app.Name,
                        Image: app.Spec.Image,
                    }},
                },
            },
        },
    }

    ctrl.SetControllerReference(app, deployment, r.Scheme)
    return deployment
}

func (r *SimpleAppReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&appsv1alpha1.SimpleApp{}).
        Owns(&appsv1.Deployment{}).
        Complete(r)
}

6. 测试和部署

6.1 本地测试

# 安装 CRD
make install

# 运行 controller
make run

6.2 创建 SimpleApp 示例

# config/samples/apps_v1alpha1_simpleapp.yaml
apiVersion: apps.my.domain/v1alpha1
kind: SimpleApp
metadata:
  name: simpleapp-sample
spec:
  image: nginx:1.19
  replicas: 3
# 创建实例
kubectl apply -f config/samples/apps_v1alpha1_simpleapp.yaml

# 查看状态
kubectl get simpleapp

6.3 构建和部署到集群

# 构建镜像
make docker-build docker-push IMG=<your-registry>/simple-app-operator:v1

# 部署到集群
make deploy IMG=<your-registry>/simple-app-operator:v1

7. 清理

# 删除 operator
make undeploy

# 删除 CRD
make uninstall

8. 最佳实践建议

  1. 错误处理

    • 合理处理各种错误情况
    • 使用 controller-runtime 的日志记录
  2. 状态管理

    • 及时更新 CR 状态
    • 提供有意义的状态信息
  3. 资源管理

    • 使用 owner references 管理资源生命周期
    • 正确设置 RBAC 权限
  4. 测试

    • 编写单元测试和集成测试
    • 在本地环境充分测试后再部署到生产环境

9. 进阶优化方向

  1. 添加更多配置选项

    • 支持配置资源限制
    • 支持配置环境变量
    • 支持配置存储卷
  2. 增强状态监控

    • 添加更详细的状态信息
    • 支持事件记录
  3. 实现高级特性

    • 滚动更新策略
    • 健康检查
    • 自动扩缩容
  4. 提升可靠性

    • 添加重试机制
    • 实现优雅终止
    • 添加监控指标
正文完
 0
评论(没有评论)