本章我们来实现编写一个用于 etcd 备份的 operator。

介绍

要实现 etcd 集群的完整运维,备份和恢复肯定也是必不可少的,本文主要和大家介绍如何编写一个用与 etcd 备份的 Operator。

首先当然需要了解 etcd 的备份逻辑。etcd 的数据默认会存放在我们的命令工作目录中,数据所在的目录会被分为两个文件夹中:

ETCD 不同的版本的 etcdctl 命令不一样,但大致差不多,备份我们可以直接使用 snapshot save ,由于 etcd 集群本身就是分布式的,所以每次备份一个节点就行。

# 备份命令
$ ETCDCTL_API=3 etcdctl --endpoints=${ENDPOINTS} snapshot save /data/etcd_backup_dir/etcd-snapshot.db

恢复时会覆盖 snapshot 的元数据(member ID 和 cluster ID),使用 snapshot restore 命令指定备份的数据目录即可。

从上面我们可以看出要备份 etcd 集群是很简单的,只需要用一条命令指定备份的节点和备份的数据目录即可,所以如果是我们要编写一个 Operator 来完成这个动作在 CR 资源里面至少要提供备份的 etcd 节点地址,以及备份的数据存放目录,对于备份数据这种一般我们用对象存储来保存,比如 S3、OSS 等,这里我们测试的时候使用兼容 S3 接口的 minio,但是为了扩展我们需要在 CR 资源里面明确告诉控制器我们希望把数据备份到什么类型的什么路径上去,比如这里我们提前设计一个 CR 资源如下所示:

apiVersion: etcd.ydzs.io/v1alpha1
kind: EtcdBackup
metadata:
  name: etcdbackup-sample
spec:
  etcdUrl: <etcd-cluster-endpoints>  # 备份的节点 etcd 地址
  storageType: s3  # 指定备份类型
  s3:
    path: "foo-bucket/snapshot.db"  # 数据存放目录
    s3Secret: "secret"   # 包含 accessKeyID 与 secretAccessKey
  oss:
    path: "foo-bucket/snapshot.db"
    ossSecret: "secret"

设计好了 CR 资源过后,接下来我们只需要去创建这个 API 资源,然后实现对应的控制器就可以了。

添加接口

同样直接在项目目录下面执行创建 API 的命令:

$ kubebuilder create api --group etcd --version v1alpha1 --kind EtcdBackup
Create Resource [y/n]
y
Create Controller [y/n]
y
Writing scaffold for you to edit...
api/v1alpha1/etcdbackup_types.go
controllers/etcdbackup_controller.go
Running make:
$ make
/Users/ych/devs/projects/go/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go build -o bin/manager main.go

创建完成后,在项目中会新增 EtcdBackup 相关的 API 和对应的控制器,我们可以用上面设计的 CR 资源覆盖 samples 目录中的 EtcdBackup 对象。

接下来同样是根据我们预设计的 CR 资源去更改 EtcdBackup 的结构体,修改里面的 EtcdBackupSpec 结构体:

// api/v1alpha1/etcdbackup_types.go

type BackupStorageType string

// EtcdBackupSpec defines the desired state of EtcdBackup
type EtcdBackupSpec struct {
	// Specific Backup Etcd Endpoints.
	EtcdUrl string `json:"etcdUrl"`
	// Storage Type:s3 OR oss
	StorageType BackupStorageType `json:"storageType"`
	// Backup Source
	BackupSource `json:",inline"`
}

// BackupSource contains the supported backup sources.
type BackupSource struct {
	// S3 defines the S3 backup source spec.
	S3 *S3BackupSource `json:"s3,omitempty"`
	// OSS defines the OSS backup source spec.
	OSS *OSSBackupSource `json:"oss,omitempty"`
}

// S3BackupSource provides the spec how to store backups on S3.
type S3BackupSource struct {
	// Path is the full s3 path where the backup is saved.
	// The format of the path must be: "<s3-bucket-name>/<path-to-backup-file>"
	// e.g: "mybucket/etcd.backup"
	Path string `json:"path"`

	// The name of the secret object that stores the credential which will be used
	// to access S3
	//
	// The secret must contain the following keys/fields:
	//     accessKeyID
	//     accessKeySecret
	S3Secret string `json:"s3Secret"`

	// Endpoint if blank points to aws. If specified, can point to s3 compatible object
	// stores.
	Endpoint string `json:"endpoint,omitempty"`
}

// OSSBackupSource provides the spec how to store backups on OSS.
type OSSBackupSource struct {
	// Path is the full abs path where the backup is saved.
	// The format of the path must be: "<oss-bucket-name>/<path-to-backup-file>"
	// e.g: "mybucket/etcd.backup"
	Path string `json:"path"`

	// The name of the secret object that stores the credential which will be used
	// to access Alibaba Cloud OSS.
	//
	// The secret must contain the following keys/fields:
	//     accessKeyID
	//     accessKeySecret
	//
	// The format of secret:
	//
	//   apiVersion: v1
	//   kind: Secret
	//   metadata:
	//     name: <my-credential-name>
	//   type: Opaque
	//   data:
	//     accessKeyID: <base64 of my-access-key-id>
	//     accessKeySecret: <base64 of my-access-key-secret>
	//
	OSSSecret string `json:"ossSecret"`

	// Endpoint is the OSS service endpoint on alibaba cloud, defaults to
	// "<http://oss-cn-hangzhou.aliyuncs.com>".
	//
	// Details about regions and endpoints, see:
	//  <https://www.alibabacloud.com/help/doc-detail/31837.htm>
	Endpoint string `json:"endpoint,omitempty"`
}