背景
我們公司最近計劃將產品遷移到 Kubernetes 環境。 為了更好地管理和自動化我們的應用程序,我們決定使用 Kubernetes Operator。 本系列博客將記錄我們學習和開發 Operator 的過程,希望能幫助更多的人入門 Operator 開發。
目標讀者
- 對 Kubernetes 有一定了解的開發人員和運維人員
- 希望使用 Operator 自動化管理應用程序的人員
- 對 Go 語言有基本了解的人員
準備工作
在開始之前,你需要準備以下環境:
-
Go 語言環境 (>= 1.23): Operator 通常使用 Go 語言開發,你需要安裝 Go 語言環境。 建議使用 Go 1.21 或更高版本。 可以從 https://go.dev/dl/ 下載安裝包。 安裝完成后,請配置好
GOPATH
和PATH
環境變量。 -
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.go
和 controllers/helloworld_controller.go
兩個文件。
編輯 api/v1alpha1/helloworld_types.go
文件,修改 HelloWorldSpec
的定義,添加 name
和 message
字段:
// 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 resourceName string `json:"name,omitempty"`// Message is the message to be printed by the podMessage string `json:"message,omitempty"`
}
3. 實現 Reconcile 邏輯
編輯 controllers/helloworld_controller.go
文件,實現 Reconcile
函數, 創建一個 Pod,該 Pod 運行一個 busybox
鏡像,并輸出 HelloWorld
資源中定義的 message
。
package controllersimport ("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.ClientScheme *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 instancehelloWorld := &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 Podpod := &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 controllerif 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 existsfound := &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 requeuereturn 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 requeuelog.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-worldmessage: "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/