Kubernetes Operator将运维知识编码为控制器程序,通过CRD(Custom Resource Definition)扩展Kubernetes API,用自定义资源管理复杂有状态应用。Operator模式的核心是Reconcile循环:持续观察资源实际状态与期望状态的差异,执行操作使其趋于一致。本文使用Go语言和controller-runtime框架构建一个数据库备份Operator。
CRD自定义资源定义与API声明
使用kubebuilder脚手架初始化项目,创建BackupSchedule资源:
kubebuilder init --domain ops.io --repo github.com/example/backup-operator
kubebuilder create api --group ops --version v1alpha1 --kind BackupSchedule
编辑API类型定义,声明BackupSchedule的期望状态:
// api/v1alpha1/backupschedule_types.go
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type BackupSpec struct {
// 目标数据库连接信息
DatabaseHost string `json:"databaseHost"`
DatabasePort int `json:"databasePort"`
DatabaseName string `json:"databaseName"`
DatabaseUser string `json:"databaseUser"`
// S3存储配置
S3Bucket string `json:"s3Bucket"`
S3Prefix string `json:"s3Prefix"`
// Cron表达式
Schedule string `json:"schedule"`
// 保留备份数量
Retention int `json:"retention"`
}
type BackupStatus struct {
LastBackupTime *metav1.Time `json:"lastBackupTime,omitempty"`
LastBackupStatus string `json:"lastBackupStatus,omitempty"`
NextBackupTime *metav1.Time `json:"nextBackupTime,omitempty"`
}
type BackupSchedule struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec BackupSpec `json:"spec,omitempty"`
Status BackupStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
type BackupScheduleList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []BackupSchedule `json:"items"`
}
生成CRD清单并安装到集群:
make manifests
make install
Reconcile控制器调谐循环实现
Reconcile函数是Operator的核心逻辑,每次相关资源变更时触发。备份Operator的Reconcile逻辑:检查是否到达备份时间,创建Job执行备份,更新Status字段。
// controllers/backupschedule_controller.go
func (r *BackupScheduleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
// 1. 获取BackupSchedule资源
var bs opsv1alpha1.BackupSchedule
if err := r.Get(ctx, req.NamespacedName, &bs); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 2. 解析Cron表达式判断是否需要执行
sched, err := cron.ParseStandard(bs.Spec.Schedule)
if err != nil {
log.Error(err, "invalid cron schedule")
return ctrl.Result{}, err
}
now := time.Now()
var lastTime time.Time
if bs.Status.LastBackupTime != nil {
lastTime = bs.Status.LastBackupTime.Time
} else {
lastTime = bs.CreationTimestamp.Time
}
nextTime := sched.Next(lastTime)
if now.Before(nextTime) {
// 未到执行时间,设置Requeue
return ctrl.Result{RequeueAfter: nextTime.Sub(now)}, nil
}
// 3. 创建备份Job
jobName := fmt.Sprintf("backup-%s-%d", bs.Name, now.Unix())
job := r.buildBackupJob(&bs, jobName)
if err := r.Create(ctx, job); err != nil && !errors.IsAlreadyExists(err) {
log.Error(err, "failed to create backup job")
return ctrl.Result{RequeueAfter: 30 * time.Second}, err
}
// 4. 更新Status
bs.Status.LastBackupTime = &metav1.Time{Time: now}
bs.Status.LastBackupStatus = "JobCreated"
bs.Status.NextBackupTime = &metav1.Time{Time: sched.Next(now)}
if err := r.Status().Update(ctx, &bs); err != nil {
return ctrl.Result{Requeue: true}, err
}
return ctrl.Result{RequeueAfter: sched.Next(now).Sub(now)}, nil
}
Reconcile函数返回ctrl.Result的RequeueAfter字段控制下次触发时间。该值应基于Cron计算的下次执行时间,而非固定间隔,避免时间漂移。
构建备份Job与资源管理逻辑
备份Job构建函数封装容器模板,使用mysqldump和aws cli完成数据库导出和S3上传:
func (r *BackupScheduleReconciler) buildBackupJob(bs *opsv1alpha1.BackupSchedule, name string) *batchv1.Job {
return &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: bs.Namespace,
},
Spec: batchv1.JobSpec{
TTLSecondsAfterFinished: ptr.To(int32(3600)),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyOnFailure,
Containers: []corev1.Container{{
Name: "backup",
Image: "mysql-client:8.0",
Command: []string{"/bin/sh", "-c"},
Args: []string{fmt.Sprintf(
"mysqldump -h%s -P%d -u%s -p$MYSQL_PASS %s | gzip | aws s3 cp - s3://%s/%s/%s.sql.gz",
bs.Spec.DatabaseHost, bs.Spec.DatabasePort,
bs.Spec.DatabaseUser, bs.Spec.DatabaseName,
bs.Spec.S3Bucket, bs.Spec.S3Prefix, name,
)},
Env: []corev1.EnvVar{
{Name: "MYSQL_PASS", ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: "db-secret"},
Key: "password",
},
}},
},
}},
},
},
},
}
}
TTLSecondsAfterFinished设为3600秒,Job完成后1小时自动清理。通过SetControllerReference建立OwnerReference,BackupSchedule删除时级联清理关联Job。
测试部署与线上运维注意事项
本地测试使用envtest启动临时控制平面:
make test
部署到集群前,构建镜像并推送:
make docker-build docker-push IMG=registry.example.com/backup-operator:v0.1.0
make deploy IMG=registry.example.com/backup-operator:v0.1.0
创建BackupSchedule实例验证:
apiVersion: ops.io/v1alpha1
kind: BackupSchedule
metadata:
name: mysql-daily-backup
namespace: default
spec:
databaseHost: mysql.default.svc.cluster.local
databasePort: 3306
databaseName: myapp
databaseUser: root
s3Bucket: my-backups
s3Prefix: mysql
schedule: "0 2 * * *"
retention: 7
线上运维需关注三点:Reconcile幂等性保证——同一资源多次调谐不应产生副作用,通过Job名称包含时间戳避免重复创建;Finalizer处理——资源删除时需等待关联资源清理完成;Status子资源更新冲突——并发更新Status可能产生OptimisticLockError,重新Get后重试即可。监控方面,controller-runtime暴露的metrics(controller_runtime_reconcile_total、controller_runtime_reconcile_errors_total)应接入Prometheus,对错误率突增做告警。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/kubernetesoperator-kai-fa-shi-zhan-crd-zi-ding-yi-zi-yuan/