搜索结果正在快递途中
Kubernetes

Kubernetes

Operator 开发入门系列(一):Hello World!

默认分类INFINI Labs 小助手 发表了文章 • 0 个评论 • 434 次浏览 • 2 天前 • 来自相关话题

背景

我们公司最近计划将产品迁移到 Kubernetes 环境。 为了更好地管理和自动化我们的应用程序,我们决定使用 Kubernetes Operator。 本系列博客将记录我们学习和开发 Operator 的过程,希望能帮助更多的人入门 Operator 开发。

目标读者

  • 对 Kubernetes 有一定了解的开发人员和运维人员
  • 希望使用 Operator 自动化管理应用程序的人员
  • 对 Go 语言有基本了解的人员

准备工作

在开始之前,你需要准备以下环境:

  • Go 语言环境 (>= 1.23): Operator 通常使用 Go 语言开发,你需要安装 Go 语言环境。 建议使用 Go 1.21 或更高版本。 可以从 https://go.dev/dl/ 下载安装包。 安装完成后,请配置好 GOPATHPATH 环境变量。

  • Kubernetes 集群: 你需要一个可用的 Kubernetes 集群来部署和测试 Operator。 可以使用 Minikube、Kind 或其他的 Kubernetes 发行版。

  • kubectl 命令行工具: kubectl 是 Kubernetes 的命令行工具,用于与 Kubernetes 集群交互。 请确保你已经安装并配置了 kubectl, 并且能够连接到你的 Kubernetes 集群。

  • Kubebuilder (>= 3.0): Kubebuilder 是一个用于快速构建 Kubernetes Operator 的框架。 使用 Kubebuilder 可以简化 Operator 的开发流程,并生成一些必要的代码框架。 可以使用以下命令安装 Kubebuilder:
cd $HOME/go/bin
curl -L -o kubebuilder "https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)"
chmod +x kubebuilder

请确保 $HOME/go/bin 目录在你的 PATH 环境变量中。 可以运行 kubebuilder version 命令来验证 Kubebuilder 是否安装成功。

  • Docker (可选): 如果你需要构建 Operator 的 Docker 镜像,你需要安装 Docker。

我的环境是 MacOS(arm64) + Orbstack

什么是 Operator?

简单来说,Operator 是 Kubernetes 的扩展,它利用自定义资源(Custom Resources, CRs)来自动化管理应用程序。Operator 允许我们像管理 Kubernetes 内置资源一样管理复杂的应用程序,例如数据库、消息队列等。

为什么选择 Operator?

Operator 提供了一种声明式的方式来管理应用程序的生命周期,包括部署、升级、备份、恢复等。它可以简化运维流程,提高自动化程度,并确保应用程序的状态符合预期。

我们的第一个 Operator:Hello World

这个 Operator 将监听一个名为 HelloWorld 的自定义资源,并在 Kubernetes 中创建一个 Pod,该 Pod 运行一个简单的 "Hello World" 应用程序。

1. 初始化 Kubebuilder 项目

首先,我们需要使用 Kubebuilder 创建一个新的项目。 在你的 GOPATH 目录下创建一个新的目录,例如 hello-world-operator,然后进入该目录,运行以下命令

kubebuilder init --domain infini.cloud --repo github.com/infinilabs/hello-world-operator

这个命令会创建一个新的 Kubebuilder 项目,并生成一些必要的文件和目录。

2. 创建自定义资源(Custom Resource Definition, CRD)

接下来,我们需要定义 HelloWorld 资源的结构。 运行以下命令

kubebuilder create api --group example --version v1alpha1 --kind HelloWorld

这个命令会创建一个新的 API 定义,包括 api/v1alpha1/helloworld_types.gocontrollers/helloworld_controller.go 两个文件。

编辑 api/v1alpha1/helloworld_types.go 文件,修改 HelloWorldSpec 的定义,添加 namemessage 字段:

// HelloWorldSpec defines the desired state of HelloWorld
type HelloWorldSpec struct {
    // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
    // Important: Run "make" to regenerate code after modifying this file

    // Name is the name of the HelloWorld resource
    Name string `json:"name,omitempty"`

    // Message is the message to be printed by the pod
    Message string `json:"message,omitempty"`
}

3. 实现 Reconcile 逻辑

编辑 controllers/helloworld_controller.go 文件,实现 Reconcile 函数, 创建一个 Pod,该 Pod 运行一个 busybox 镜像,并输出 HelloWorld 资源中定义的 message

package controllers

import (
    "context"
    "fmt"

    corev1 "k8s.io/api/core/v1"
    apierrors "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"

    examplev1alpha1 "github.com/infinilabs/hello-world-operator/api/v1alpha1"
)

// HelloWorldReconciler reconciles a HelloWorld object
type HelloWorldReconciler struct {
    client.Client
    Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=example.com,resources=helloworlds,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=example.com,resources=helloworlds/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=example.com,resources=helloworlds/finalizers,verbs=update
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile
func (r *HelloWorldReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := log.FromContext(ctx)

    // 1. Fetch the HelloWorld instance
    helloWorld := &examplev1alpha1.HelloWorld{}
    err := r.Get(ctx, req.NamespacedName, helloWorld)
    if err != nil {
        if apierrors.IsNotFound(err) {
            // Object not found, return.  Created objects are automatically garbage collected.
            // For additional cleanup logic use finalizers.
            log.Info("HelloWorld resource not found. Ignoring since object must be deleted")
            return ctrl.Result{}, nil
        }
        // Error reading the object - requeue the request.
        log.Error(err, "Failed to get HelloWorld")
        return ctrl.Result{}, err
    }

    // 2. Define the desired Pod
    pod := &corev1.Pod{
        ObjectMeta: metav1.ObjectMeta{
            Name:      helloWorld.Name + "-pod",
            Namespace: helloWorld.Namespace,
            Labels: map[string]string{
                "app": helloWorld.Name,
            },
        },
        Spec: corev1.PodSpec{
            Containers: []corev1.Container{
                {
                    Name:  "hello-world",
                    Image: "busybox",
                    Command: []string{"sh", "-c", fmt.Sprintf("echo '%s' && sleep 3600", helloWorld.Spec.Message)},
                },
            },
        },
    }

    // 3. Set HelloWorld instance as the owner and controller
    if err := ctrl.SetControllerReference(helloWorld, pod, r.Scheme); err != nil {
        log.Error(err, "Failed to set controller reference")
        return ctrl.Result{}, err
    }

    // 4. Check if the Pod already exists
    found := &corev1.Pod{}
    err = r.Get(ctx, client.ObjectKey{Name: pod.Name, Namespace: pod.Namespace}, found)
    if err != nil && apierrors.IsNotFound(err) {
        log.Info("Creating a new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
        err = r.Create(ctx, pod)
        if err != nil {
            log.Error(err, "Failed to create new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
            return ctrl.Result{}, err
        }
        // Pod created successfully - return and requeue
        return ctrl.Result{Requeue: true}, nil
    } else if err != nil {
        log.Error(err, "Failed to get Pod")
        return ctrl.Result{}, err
    }

    // 5. Pod already exists - don't requeue
    log.Info("Skip reconcile: Pod already exists", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name)
    return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *HelloWorldReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&examplev1alpha1.HelloWorld{}).
        Owns(&corev1.Pod{}).
        Complete(r)
}

4. 安装 CRD 到 Kubernetes 集群

运行以下命令安装 CRD 到 Kubernetes 集群:

make install

5. 运行 Operator

运行以下命令在本地运行 Operator:

make run

6. 创建 HelloWorld 资源

创建一个名为 my-hello-world.yaml 的文件,内容如下:

apiVersion: example.com/v1alpha1
kind: HelloWorld
metadata:
  name: my-hello-world
spec:
  name: my-hello-world
  message: "Hello World from Operator!"

使用 kubectl apply -f my-hello-world.yaml 创建资源。

7. 验证

使用 kubectl get pods 命令查看是否创建了名为 my-hello-world-pod 的 Pod。 使用 kubectl logs my-hello-world-pod 查看 Pod 的日志,确认是否输出了 "Hello World from Operator!"。

总结

恭喜你完成了第一个 Operator! 虽然这个 Operator 非常简单,但它展示了 Operator 的基本原理:监听自定义资源,并根据资源的状态来管理 Kubernetes 资源。 在接下来的系列中,我们将深入探讨 Operator 的更多高级特性。

敬请期待下一篇博客!

作者:罗厚付,极限科技(INFINI Labs)云上产品设计与研发负责人,拥有多年安全风控及大数据系统架构经验,主导过多个核心产品的设计与落地,日常负责运维超大规模 ES 集群(800+节点/1PB+数据)。
原文:https://infinilabs.cn/blog/2025/kubernetes-operator-develop-part-1/

Operator 开发入门系列(一):Hello World!

默认分类INFINI Labs 小助手 发表了文章 • 0 个评论 • 434 次浏览 • 2 天前 • 来自相关话题

背景

我们公司最近计划将产品迁移到 Kubernetes 环境。 为了更好地管理和自动化我们的应用程序,我们决定使用 Kubernetes Operator。 本系列博客将记录我们学习和开发 Operator 的过程,希望能帮助更多的人入门 Operator 开发。

目标读者

  • 对 Kubernetes 有一定了解的开发人员和运维人员
  • 希望使用 Operator 自动化管理应用程序的人员
  • 对 Go 语言有基本了解的人员

准备工作

在开始之前,你需要准备以下环境:

  • Go 语言环境 (>= 1.23): Operator 通常使用 Go 语言开发,你需要安装 Go 语言环境。 建议使用 Go 1.21 或更高版本。 可以从 https://go.dev/dl/ 下载安装包。 安装完成后,请配置好 GOPATHPATH 环境变量。

  • Kubernetes 集群: 你需要一个可用的 Kubernetes 集群来部署和测试 Operator。 可以使用 Minikube、Kind 或其他的 Kubernetes 发行版。

  • kubectl 命令行工具: kubectl 是 Kubernetes 的命令行工具,用于与 Kubernetes 集群交互。 请确保你已经安装并配置了 kubectl, 并且能够连接到你的 Kubernetes 集群。

  • Kubebuilder (>= 3.0): Kubebuilder 是一个用于快速构建 Kubernetes Operator 的框架。 使用 Kubebuilder 可以简化 Operator 的开发流程,并生成一些必要的代码框架。 可以使用以下命令安装 Kubebuilder:
cd $HOME/go/bin
curl -L -o kubebuilder "https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)"
chmod +x kubebuilder

请确保 $HOME/go/bin 目录在你的 PATH 环境变量中。 可以运行 kubebuilder version 命令来验证 Kubebuilder 是否安装成功。

  • Docker (可选): 如果你需要构建 Operator 的 Docker 镜像,你需要安装 Docker。

我的环境是 MacOS(arm64) + Orbstack

什么是 Operator?

简单来说,Operator 是 Kubernetes 的扩展,它利用自定义资源(Custom Resources, CRs)来自动化管理应用程序。Operator 允许我们像管理 Kubernetes 内置资源一样管理复杂的应用程序,例如数据库、消息队列等。

为什么选择 Operator?

Operator 提供了一种声明式的方式来管理应用程序的生命周期,包括部署、升级、备份、恢复等。它可以简化运维流程,提高自动化程度,并确保应用程序的状态符合预期。

我们的第一个 Operator:Hello World

这个 Operator 将监听一个名为 HelloWorld 的自定义资源,并在 Kubernetes 中创建一个 Pod,该 Pod 运行一个简单的 "Hello World" 应用程序。

1. 初始化 Kubebuilder 项目

首先,我们需要使用 Kubebuilder 创建一个新的项目。 在你的 GOPATH 目录下创建一个新的目录,例如 hello-world-operator,然后进入该目录,运行以下命令

kubebuilder init --domain infini.cloud --repo github.com/infinilabs/hello-world-operator

这个命令会创建一个新的 Kubebuilder 项目,并生成一些必要的文件和目录。

2. 创建自定义资源(Custom Resource Definition, CRD)

接下来,我们需要定义 HelloWorld 资源的结构。 运行以下命令

kubebuilder create api --group example --version v1alpha1 --kind HelloWorld

这个命令会创建一个新的 API 定义,包括 api/v1alpha1/helloworld_types.gocontrollers/helloworld_controller.go 两个文件。

编辑 api/v1alpha1/helloworld_types.go 文件,修改 HelloWorldSpec 的定义,添加 namemessage 字段:

// HelloWorldSpec defines the desired state of HelloWorld
type HelloWorldSpec struct {
    // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
    // Important: Run "make" to regenerate code after modifying this file

    // Name is the name of the HelloWorld resource
    Name string `json:"name,omitempty"`

    // Message is the message to be printed by the pod
    Message string `json:"message,omitempty"`
}

3. 实现 Reconcile 逻辑

编辑 controllers/helloworld_controller.go 文件,实现 Reconcile 函数, 创建一个 Pod,该 Pod 运行一个 busybox 镜像,并输出 HelloWorld 资源中定义的 message

package controllers

import (
    "context"
    "fmt"

    corev1 "k8s.io/api/core/v1"
    apierrors "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"

    examplev1alpha1 "github.com/infinilabs/hello-world-operator/api/v1alpha1"
)

// HelloWorldReconciler reconciles a HelloWorld object
type HelloWorldReconciler struct {
    client.Client
    Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=example.com,resources=helloworlds,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=example.com,resources=helloworlds/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=example.com,resources=helloworlds/finalizers,verbs=update
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile
func (r *HelloWorldReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := log.FromContext(ctx)

    // 1. Fetch the HelloWorld instance
    helloWorld := &examplev1alpha1.HelloWorld{}
    err := r.Get(ctx, req.NamespacedName, helloWorld)
    if err != nil {
        if apierrors.IsNotFound(err) {
            // Object not found, return.  Created objects are automatically garbage collected.
            // For additional cleanup logic use finalizers.
            log.Info("HelloWorld resource not found. Ignoring since object must be deleted")
            return ctrl.Result{}, nil
        }
        // Error reading the object - requeue the request.
        log.Error(err, "Failed to get HelloWorld")
        return ctrl.Result{}, err
    }

    // 2. Define the desired Pod
    pod := &corev1.Pod{
        ObjectMeta: metav1.ObjectMeta{
            Name:      helloWorld.Name + "-pod",
            Namespace: helloWorld.Namespace,
            Labels: map[string]string{
                "app": helloWorld.Name,
            },
        },
        Spec: corev1.PodSpec{
            Containers: []corev1.Container{
                {
                    Name:  "hello-world",
                    Image: "busybox",
                    Command: []string{"sh", "-c", fmt.Sprintf("echo '%s' && sleep 3600", helloWorld.Spec.Message)},
                },
            },
        },
    }

    // 3. Set HelloWorld instance as the owner and controller
    if err := ctrl.SetControllerReference(helloWorld, pod, r.Scheme); err != nil {
        log.Error(err, "Failed to set controller reference")
        return ctrl.Result{}, err
    }

    // 4. Check if the Pod already exists
    found := &corev1.Pod{}
    err = r.Get(ctx, client.ObjectKey{Name: pod.Name, Namespace: pod.Namespace}, found)
    if err != nil && apierrors.IsNotFound(err) {
        log.Info("Creating a new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
        err = r.Create(ctx, pod)
        if err != nil {
            log.Error(err, "Failed to create new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
            return ctrl.Result{}, err
        }
        // Pod created successfully - return and requeue
        return ctrl.Result{Requeue: true}, nil
    } else if err != nil {
        log.Error(err, "Failed to get Pod")
        return ctrl.Result{}, err
    }

    // 5. Pod already exists - don't requeue
    log.Info("Skip reconcile: Pod already exists", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name)
    return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *HelloWorldReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&examplev1alpha1.HelloWorld{}).
        Owns(&corev1.Pod{}).
        Complete(r)
}

4. 安装 CRD 到 Kubernetes 集群

运行以下命令安装 CRD 到 Kubernetes 集群:

make install

5. 运行 Operator

运行以下命令在本地运行 Operator:

make run

6. 创建 HelloWorld 资源

创建一个名为 my-hello-world.yaml 的文件,内容如下:

apiVersion: example.com/v1alpha1
kind: HelloWorld
metadata:
  name: my-hello-world
spec:
  name: my-hello-world
  message: "Hello World from Operator!"

使用 kubectl apply -f my-hello-world.yaml 创建资源。

7. 验证

使用 kubectl get pods 命令查看是否创建了名为 my-hello-world-pod 的 Pod。 使用 kubectl logs my-hello-world-pod 查看 Pod 的日志,确认是否输出了 "Hello World from Operator!"。

总结

恭喜你完成了第一个 Operator! 虽然这个 Operator 非常简单,但它展示了 Operator 的基本原理:监听自定义资源,并根据资源的状态来管理 Kubernetes 资源。 在接下来的系列中,我们将深入探讨 Operator 的更多高级特性。

敬请期待下一篇博客!

作者:罗厚付,极限科技(INFINI Labs)云上产品设计与研发负责人,拥有多年安全风控及大数据系统架构经验,主导过多个核心产品的设计与落地,日常负责运维超大规模 ES 集群(800+节点/1PB+数据)。
原文:https://infinilabs.cn/blog/2025/kubernetes-operator-develop-part-1/