Skip to content

Commit c64f4df

Browse files
authored
Merge pull request radondb#386 from acekingke/CronJOBONE
*: support the cronjob to backup radondb#215
2 parents 306a51f + 7bea130 commit c64f4df

File tree

12 files changed

+440
-0
lines changed

12 files changed

+440
-0
lines changed

PROJECT

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ resources:
3030
kind: Backup
3131
path: github.com/radondb/radondb-mysql-kubernetes/api/v1alpha1
3232
version: v1alpha1
33+
- api:
34+
crdVersion: v1
35+
namespaced: true
36+
controller: true
37+
domain: radondb.com
38+
group: mysql
39+
kind: BackupCron
40+
version: v1alpha1
3341
- api:
3442
crdVersion: v1
3543
namespaced: true

api/v1alpha1/mysqlcluster_types.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ type MysqlClusterSpec struct {
8585
// Represents NFS ip address where cluster restore from.
8686
// +optional
8787
NFSServerAddress string `json:"nfsServerAddress,omitempty"`
88+
89+
// Specify under crontab format interval to take backups
90+
// leave it empty to deactivate the backup process
91+
// Defaults to ""
92+
// +optional
93+
BackupSchedule string `json:"backupSchedule,omitempty"`
94+
95+
// If set keeps last BackupScheduleJobsHistoryLimit Backups
96+
// +optional
97+
// +kubebuilder:default:=6
98+
BackupScheduleJobsHistoryLimit *int `json:"backupScheduleJobsHistoryLimit,omitempty"`
8899
}
89100

90101
// MysqlOpts defines the options of MySQL container.

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backup/cronbackup.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package backup
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"sort"
7+
"time"
8+
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"sigs.k8s.io/controller-runtime/pkg/client"
11+
12+
"github.com/go-logr/logr"
13+
apiv1alpha1 "github.com/radondb/radondb-mysql-kubernetes/api/v1alpha1"
14+
)
15+
16+
// The job structure contains the context to schedule a backup
17+
type CronJob struct {
18+
ClusterName string
19+
Namespace string
20+
21+
// kubernetes client
22+
Client client.Client
23+
24+
BackupScheduleJobsHistoryLimit *int
25+
Image string
26+
Log logr.Logger
27+
}
28+
29+
func (j *CronJob) Run() {
30+
// nolint: govet
31+
log := j.Log
32+
log.Info("scheduled backup job started")
33+
34+
// run garbage collector if needed
35+
if j.BackupScheduleJobsHistoryLimit != nil {
36+
defer j.backupGC()
37+
}
38+
39+
// check if a backup is running
40+
if j.scheduledBackupsRunningCount() > 0 {
41+
log.Info("at least a backup is running", "running_backups_count", j.scheduledBackupsRunningCount())
42+
return
43+
}
44+
45+
// create the backup
46+
if _, err := j.createBackup(); err != nil {
47+
log.Error(err, "failed to create backup")
48+
}
49+
}
50+
51+
func (j *CronJob) scheduledBackupsRunningCount() int {
52+
log := j.Log
53+
backupsList := &apiv1alpha1.BackupList{}
54+
// select all backups with labels recurrent=true and and not completed of the cluster
55+
selector := j.backupSelector()
56+
client.MatchingFields{"status.completed": "false"}.ApplyToList(selector)
57+
58+
if err := j.Client.List(context.TODO(), backupsList, selector); err != nil {
59+
log.Error(err, "failed getting backups", "selector", selector)
60+
return 0
61+
}
62+
63+
return len(backupsList.Items)
64+
}
65+
66+
func (j *CronJob) backupSelector() *client.ListOptions {
67+
selector := &client.ListOptions{}
68+
69+
client.InNamespace(j.Namespace).ApplyToList(selector)
70+
client.MatchingLabels(j.recurrentBackupLabels()).ApplyToList(selector)
71+
72+
return selector
73+
}
74+
75+
func (j *CronJob) recurrentBackupLabels() map[string]string {
76+
return map[string]string{
77+
"recurrent": "true",
78+
"cluster": j.ClusterName,
79+
}
80+
}
81+
82+
func (j *CronJob) backupGC() {
83+
var err error
84+
log := j.Log
85+
backupsList := &apiv1alpha1.BackupList{}
86+
if err = j.Client.List(context.TODO(), backupsList, j.backupSelector()); err != nil {
87+
log.Error(err, "failed getting backups", "selector", j.backupSelector())
88+
return
89+
}
90+
91+
// sort backups by creation time before removing extra backups
92+
sort.Sort(byTimestamp(backupsList.Items))
93+
94+
for i, backup := range backupsList.Items {
95+
if i >= *j.BackupScheduleJobsHistoryLimit {
96+
// delete the backup
97+
if err = j.Client.Delete(context.TODO(), &backup); err != nil {
98+
log.Error(err, "failed to delete a backup", "backup", backup)
99+
}
100+
}
101+
}
102+
}
103+
104+
func (j *CronJob) createBackup() (*apiv1alpha1.Backup, error) {
105+
backupName := fmt.Sprintf("%s-auto-%s", j.ClusterName, time.Now().Format("2006-01-02t15-04-05"))
106+
107+
backup := &apiv1alpha1.Backup{
108+
ObjectMeta: metav1.ObjectMeta{
109+
Name: backupName,
110+
Namespace: j.Namespace,
111+
Labels: j.recurrentBackupLabels(),
112+
},
113+
Spec: apiv1alpha1.BackupSpec{
114+
ClusterName: j.ClusterName,
115+
//TODO modify to cluster sidecar image
116+
Image: j.Image,
117+
//RemoteDeletePolicy: j.BackupRemoteDeletePolicy,
118+
HostName: fmt.Sprintf("%s-mysql-0", j.ClusterName),
119+
},
120+
}
121+
return backup, j.Client.Create(context.TODO(), backup)
122+
}
123+
124+
type byTimestamp []apiv1alpha1.Backup
125+
126+
func (a byTimestamp) Len() int { return len(a) }
127+
func (a byTimestamp) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
128+
func (a byTimestamp) Less(i, j int) bool {
129+
return a[j].ObjectMeta.CreationTimestamp.Before(&a[i].ObjectMeta.CreationTimestamp)
130+
}

charts/mysql-operator/crds/mysql.radondb.com_mysqlclusters.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ spec:
5858
spec:
5959
description: MysqlClusterSpec defines the desired state of MysqlCluster
6060
properties:
61+
backupSchedule:
62+
description: Specify under crontab format interval to take backups
63+
leave it empty to deactivate the backup process Defaults to ""
64+
type: string
65+
backupScheduleJobsHistoryLimit:
66+
default: 6
67+
description: If set keeps last BackupScheduleJobsHistoryLimit Backups
68+
type: integer
6169
backupSecretName:
6270
description: Represents the name of the secret that contains credentials
6371
to connect to the storage provider to store backups.

cmd/manager/main.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package main
1919
import (
2020
"flag"
2121
"os"
22+
"sync"
2223

2324
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
2425
// to ensure that exec-entrypoint and run can make use of them.
@@ -34,6 +35,7 @@ import (
3435
mysqlv1alpha1 "github.com/radondb/radondb-mysql-kubernetes/api/v1alpha1"
3536
"github.com/radondb/radondb-mysql-kubernetes/controllers"
3637
"github.com/radondb/radondb-mysql-kubernetes/internal"
38+
"github.com/wgliang/cron"
3739
//+kubebuilder:scaffold:imports
3840
)
3941

@@ -116,6 +118,16 @@ func main() {
116118
setupLog.Error(err, "unable to create controller", "controller", "MysqlUser")
117119
os.Exit(1)
118120
}
121+
if err = (&controllers.BackupCronReconciler{
122+
Client: mgr.GetClient(),
123+
Scheme: mgr.GetScheme(),
124+
Recorder: mgr.GetEventRecorderFor("controller.BackupCron"),
125+
Cron: cron.New(),
126+
LockJobRegister: new(sync.Mutex),
127+
}).SetupWithManager(mgr); err != nil {
128+
setupLog.Error(err, "unable to create controller", "controller", "BackupCron")
129+
os.Exit(1)
130+
}
119131
//+kubebuilder:scaffold:builder
120132
if os.Getenv("ENABLE_WEBHOOKS") != "false" {
121133
if err = (&mysqlv1alpha1.MysqlCluster{}).SetupWebhookWithManager(mgr); err != nil {

config/crd/bases/mysql.radondb.com_mysqlclusters.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ spec:
5858
spec:
5959
description: MysqlClusterSpec defines the desired state of MysqlCluster
6060
properties:
61+
backupSchedule:
62+
description: Specify under crontab format interval to take backups
63+
leave it empty to deactivate the backup process Defaults to ""
64+
type: string
65+
backupScheduleJobsHistoryLimit:
66+
default: 6
67+
description: If set keeps last BackupScheduleJobsHistoryLimit Backups
68+
type: integer
6169
backupSecretName:
6270
description: Represents the name of the secret that contains credentials
6371
to connect to the storage provider to store backups.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
apiVersion: mysql.radondb.com/v1alpha1
2+
kind: MysqlCluster
3+
metadata:
4+
name: sample
5+
spec:
6+
replicas: 3
7+
mysqlVersion: "5.7"
8+
9+
# the backupSecretName specify the secret file name which store S3 information,
10+
# if you want S3 backup or restore, please create backup_secret.yaml, uncomment below and fill secret name:
11+
backupSecretName: sample-backup-secret
12+
13+
# if you want create mysqlcluster from S3, uncomment and fill the directory in S3 bucket below:
14+
# restoreFrom:
15+
BackupSchedule: "0 50 * * * *"
16+
mysqlOpts:
17+
rootPassword: "RadonDB@123"
18+
rootHost: localhost
19+
user: radondb_usr
20+
password: RadonDB@123
21+
database: radondb
22+
initTokuDB: true
23+
24+
# A simple map between string and string.
25+
# Such as:
26+
# mysqlConf:
27+
# expire_logs_days: "7"
28+
mysqlConf: {}
29+
30+
resources:
31+
requests:
32+
cpu: 100m
33+
memory: 256Mi
34+
limits:
35+
cpu: 500m
36+
memory: 1Gi
37+
38+
xenonOpts:
39+
image: radondb/xenon:1.1.5-alpha
40+
admitDefeatHearbeatCount: 5
41+
electionTimeout: 10000
42+
43+
resources:
44+
requests:
45+
cpu: 50m
46+
memory: 128Mi
47+
limits:
48+
cpu: 100m
49+
memory: 256Mi
50+
51+
metricsOpts:
52+
enabled: false
53+
image: prom/mysqld-exporter:v0.12.1
54+
55+
resources:
56+
requests:
57+
cpu: 10m
58+
memory: 32Mi
59+
limits:
60+
cpu: 100m
61+
memory: 128Mi
62+
63+
podPolicy:
64+
imagePullPolicy: IfNotPresent
65+
sidecarImage: radondb/mysql-sidecar:latest
66+
busyboxImage: busybox:1.32
67+
68+
slowLogTail: false
69+
auditLogTail: false
70+
71+
labels: {}
72+
annotations: {}
73+
affinity: {}
74+
priorityClassName: ""
75+
tolerations: []
76+
schedulerName: ""
77+
# extraResources defines quotas for containers other than mysql or xenon.
78+
extraResources:
79+
requests:
80+
cpu: 10m
81+
memory: 32Mi
82+
83+
persistence:
84+
enabled: true
85+
accessModes:
86+
- ReadWriteOnce
87+
#storageClass: ""
88+
size: 20Gi

0 commit comments

Comments
 (0)