Skip to content

Commit 2bb8ec3

Browse files
Dipta Dastamalsaha
authored andcommitted
Backup and restore Elasticsearch (#702)
1 parent 1f21268 commit 2bb8ec3

File tree

3 files changed

+296
-0
lines changed

3 files changed

+296
-0
lines changed

backup_es.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package cmds
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
7+
"github.com/appscode/go/flags"
8+
"github.com/codeskyblue/go-sh"
9+
"github.com/spf13/cobra"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/util/errors"
12+
"k8s.io/client-go/kubernetes"
13+
"k8s.io/client-go/tools/clientcmd"
14+
appcatalog_cs "kmodules.xyz/custom-resources/client/clientset/versioned"
15+
"stash.appscode.dev/stash/pkg/restic"
16+
)
17+
18+
const (
19+
JobESBackup = "stash-es-backup"
20+
ESUser = "ADMIN_USERNAME"
21+
ESPassword = "ADMIN_PASSWORD"
22+
ESDataDir = "/var/data"
23+
)
24+
25+
func NewCmdBackupES() *cobra.Command {
26+
var (
27+
masterURL string
28+
kubeconfigPath string
29+
namespace string
30+
appBindingName string
31+
esArgs string
32+
outputDir string
33+
setupOpt = restic.SetupOptions{
34+
ScratchDir: restic.DefaultScratchDir,
35+
EnableCache: false,
36+
}
37+
backupOpt = restic.BackupOptions{
38+
BackupDirs: []string{ESDataDir},
39+
}
40+
metrics = restic.MetricsOptions{
41+
JobName: JobESBackup,
42+
}
43+
)
44+
45+
cmd := &cobra.Command{
46+
Use: "backup-es",
47+
Short: "Takes a backup of ElasticSearch DB",
48+
DisableAutoGenTag: true,
49+
RunE: func(cmd *cobra.Command, args []string) error {
50+
flags.EnsureRequiredFlags(cmd, "app-binding", "provider", "secret-dir")
51+
52+
// prepare client
53+
config, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfigPath)
54+
if err != nil {
55+
return err
56+
}
57+
kubeClient, err := kubernetes.NewForConfig(config)
58+
if err != nil {
59+
return err
60+
}
61+
appCatalogClient, err := appcatalog_cs.NewForConfig(config)
62+
if err != nil {
63+
return err
64+
}
65+
66+
// get app binding
67+
appBinding, err := appCatalogClient.AppcatalogV1alpha1().AppBindings(namespace).Get(appBindingName, metav1.GetOptions{})
68+
if err != nil {
69+
return err
70+
}
71+
// get secret
72+
appBindingSecret, err := kubeClient.CoreV1().Secrets(namespace).Get(appBinding.Spec.Secret.Name, metav1.GetOptions{})
73+
if err != nil {
74+
return err
75+
}
76+
77+
// run separate shell to dump indices
78+
esShell := sh.NewSession()
79+
esShell.ShowCMD = true
80+
esShell.SetEnv("DB_SCHEME", appBinding.Spec.ClientConfig.Service.Scheme)
81+
esShell.SetEnv("DB_USER", string(appBindingSecret.Data[ESUser]))
82+
esShell.SetEnv("DB_PASSWORD", string(appBindingSecret.Data[ESPassword]))
83+
esShell.SetEnv("AUTH_PLUGIN", "SearchGuard") // TODO
84+
esShell.Command("es-tools.sh",
85+
"backup",
86+
fmt.Sprintf(`--host=%s`, appBinding.Spec.ClientConfig.Service.Name),
87+
fmt.Sprintf(`--data-dir=%s`, ESDataDir),
88+
"--", esArgs,
89+
)
90+
if err := esShell.Run(); err != nil {
91+
return err
92+
}
93+
94+
// init restic wrapper
95+
resticWrapper, err := restic.NewResticWrapper(setupOpt)
96+
if err != nil {
97+
return err
98+
}
99+
// Run backup
100+
backupOutput, backupErr := resticWrapper.RunBackup(backupOpt)
101+
// If metrics are enabled then generate metrics
102+
if metrics.Enabled {
103+
err := backupOutput.HandleMetrics(&metrics, backupErr)
104+
if err != nil {
105+
return errors.NewAggregate([]error{backupErr, err})
106+
}
107+
}
108+
// If output directory specified, then write the output in "output.json" file in the specified directory
109+
if backupErr == nil && outputDir != "" {
110+
err := backupOutput.WriteOutput(filepath.Join(outputDir, restic.DefaultOutputFileName))
111+
if err != nil {
112+
return err
113+
}
114+
}
115+
return backupErr
116+
},
117+
}
118+
119+
cmd.Flags().StringVar(&esArgs, "es-args", esArgs, "Additional arguments")
120+
121+
cmd.Flags().StringVar(&masterURL, "master", masterURL, "The address of the Kubernetes API server (overrides any value in kubeconfig)")
122+
cmd.Flags().StringVar(&kubeconfigPath, "kubeconfig", kubeconfigPath, "Path to kubeconfig file with authorization information (the master location is set by the master flag).")
123+
cmd.Flags().StringVar(&namespace, "namespace", "default", "Namespace of Backup/Restore Session")
124+
cmd.Flags().StringVar(&appBindingName, "app-binding", appBindingName, "Name of the app binding")
125+
126+
cmd.Flags().StringVar(&setupOpt.Provider, "provider", setupOpt.Provider, "Backend provider (i.e. gcs, s3, azure etc)")
127+
cmd.Flags().StringVar(&setupOpt.Bucket, "bucket", setupOpt.Bucket, "Name of the cloud bucket/container (keep empty for local backend)")
128+
cmd.Flags().StringVar(&setupOpt.Endpoint, "endpoint", setupOpt.Endpoint, "Endpoint for s3/s3 compatible backend")
129+
cmd.Flags().StringVar(&setupOpt.Path, "path", setupOpt.Path, "Directory inside the bucket where backup will be stored")
130+
cmd.Flags().StringVar(&setupOpt.SecretDir, "secret-dir", setupOpt.SecretDir, "Directory where storage secret has been mounted")
131+
cmd.Flags().StringVar(&setupOpt.ScratchDir, "scratch-dir", setupOpt.ScratchDir, "Temporary directory")
132+
cmd.Flags().BoolVar(&setupOpt.EnableCache, "enable-cache", setupOpt.EnableCache, "Specify weather to enable caching for restic")
133+
134+
cmd.Flags().StringVar(&backupOpt.Host, "hostname", backupOpt.Host, "Name of the host machine")
135+
136+
cmd.Flags().IntVar(&backupOpt.RetentionPolicy.KeepLast, "retention-keep-last", backupOpt.RetentionPolicy.KeepLast, "Specify value for retention strategy")
137+
cmd.Flags().IntVar(&backupOpt.RetentionPolicy.KeepHourly, "retention-keep-hourly", backupOpt.RetentionPolicy.KeepHourly, "Specify value for retention strategy")
138+
cmd.Flags().IntVar(&backupOpt.RetentionPolicy.KeepDaily, "retention-keep-daily", backupOpt.RetentionPolicy.KeepDaily, "Specify value for retention strategy")
139+
cmd.Flags().IntVar(&backupOpt.RetentionPolicy.KeepWeekly, "retention-keep-weekly", backupOpt.RetentionPolicy.KeepWeekly, "Specify value for retention strategy")
140+
cmd.Flags().IntVar(&backupOpt.RetentionPolicy.KeepMonthly, "retention-keep-monthly", backupOpt.RetentionPolicy.KeepMonthly, "Specify value for retention strategy")
141+
cmd.Flags().IntVar(&backupOpt.RetentionPolicy.KeepYearly, "retention-keep-yearly", backupOpt.RetentionPolicy.KeepYearly, "Specify value for retention strategy")
142+
cmd.Flags().StringSliceVar(&backupOpt.RetentionPolicy.KeepTags, "retention-keep-tags", backupOpt.RetentionPolicy.KeepTags, "Specify value for retention strategy")
143+
cmd.Flags().BoolVar(&backupOpt.RetentionPolicy.Prune, "retention-prune", backupOpt.RetentionPolicy.Prune, "Specify weather to prune old snapshot data")
144+
cmd.Flags().BoolVar(&backupOpt.RetentionPolicy.DryRun, "retention-dry-run", backupOpt.RetentionPolicy.DryRun, "Specify weather to test retention policy without deleting actual data")
145+
146+
cmd.Flags().StringVar(&outputDir, "output-dir", outputDir, "Directory where output.json file will be written (keep empty if you don't need to write output in file)")
147+
148+
cmd.Flags().BoolVar(&metrics.Enabled, "metrics-enabled", metrics.Enabled, "Specify weather to export Prometheus metrics")
149+
cmd.Flags().StringVar(&metrics.PushgatewayURL, "metrics-pushgateway-url", metrics.PushgatewayURL, "Pushgateway URL where the metrics will be pushed")
150+
cmd.Flags().StringVar(&metrics.MetricFileDir, "metrics-dir", metrics.MetricFileDir, "Directory where to write metric.prom file (keep empty if you don't want to write metric in a text file)")
151+
cmd.Flags().StringSliceVar(&metrics.Labels, "metrics-labels", metrics.Labels, "Labels to apply in exported metrics")
152+
153+
return cmd
154+
}

restore_es.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package cmds
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
7+
"github.com/appscode/go/flags"
8+
"github.com/codeskyblue/go-sh"
9+
"github.com/spf13/cobra"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/util/errors"
12+
"k8s.io/client-go/kubernetes"
13+
"k8s.io/client-go/tools/clientcmd"
14+
appcatalog_cs "kmodules.xyz/custom-resources/client/clientset/versioned"
15+
"stash.appscode.dev/stash/pkg/restic"
16+
)
17+
18+
func NewCmdRestoreES() *cobra.Command {
19+
var (
20+
masterURL string
21+
kubeconfigPath string
22+
namespace string
23+
appBindingName string
24+
outputDir string
25+
esArgs string
26+
setupOpt = restic.SetupOptions{
27+
ScratchDir: restic.DefaultScratchDir,
28+
EnableCache: false,
29+
}
30+
restoreOpt = restic.RestoreOptions{
31+
RestoreDirs: []string{ESDataDir},
32+
}
33+
metrics = restic.MetricsOptions{
34+
JobName: JobESBackup,
35+
}
36+
)
37+
38+
cmd := &cobra.Command{
39+
Use: "restore-es",
40+
Short: "Restores ElasticSearch DB Backup",
41+
DisableAutoGenTag: true,
42+
RunE: func(cmd *cobra.Command, args []string) error {
43+
flags.EnsureRequiredFlags(cmd, "app-binding", "provider", "secret-dir")
44+
45+
// prepare client
46+
config, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfigPath)
47+
if err != nil {
48+
return err
49+
}
50+
kubeClient, err := kubernetes.NewForConfig(config)
51+
if err != nil {
52+
return err
53+
}
54+
appCatalogClient, err := appcatalog_cs.NewForConfig(config)
55+
if err != nil {
56+
return err
57+
}
58+
59+
// get app binding
60+
appBinding, err := appCatalogClient.AppcatalogV1alpha1().AppBindings(namespace).Get(appBindingName, metav1.GetOptions{})
61+
if err != nil {
62+
return err
63+
}
64+
// get secret
65+
appBindingSecret, err := kubeClient.CoreV1().Secrets(namespace).Get(appBinding.Spec.Secret.Name, metav1.GetOptions{})
66+
if err != nil {
67+
return err
68+
}
69+
70+
// init restic wrapper
71+
resticWrapper, err := restic.NewResticWrapper(setupOpt)
72+
if err != nil {
73+
return err
74+
}
75+
// Run restore
76+
restoreOutput, restoreErr := resticWrapper.RunRestore(restoreOpt)
77+
78+
// run separate shell to restore indices
79+
esShell := sh.NewSession()
80+
esShell.ShowCMD = true
81+
esShell.SetEnv("DB_SCHEME", appBinding.Spec.ClientConfig.Service.Scheme)
82+
esShell.SetEnv("DB_USER", string(appBindingSecret.Data[ESUser]))
83+
esShell.SetEnv("DB_PASSWORD", string(appBindingSecret.Data[ESPassword]))
84+
esShell.SetEnv("AUTH_PLUGIN", "SearchGuard") // TODO
85+
esShell.Command("es-tools.sh",
86+
"restore",
87+
fmt.Sprintf(`--host=%s`, appBinding.Spec.ClientConfig.Service.Name),
88+
fmt.Sprintf(`--data-dir=%s`, ESDataDir),
89+
"--", esArgs,
90+
)
91+
if err := esShell.Run(); err != nil {
92+
return err
93+
}
94+
95+
// If metrics are enabled then generate metrics
96+
if metrics.Enabled {
97+
err := restoreOutput.HandleMetrics(&metrics, restoreErr)
98+
if err != nil {
99+
return errors.NewAggregate([]error{restoreErr, err})
100+
}
101+
}
102+
// If output directory specified, then write the output in "output.json" file in the specified directory
103+
if restoreErr == nil && outputDir != "" {
104+
err := restoreOutput.WriteOutput(filepath.Join(outputDir, restic.DefaultOutputFileName))
105+
if err != nil {
106+
return err
107+
}
108+
}
109+
return restoreErr
110+
},
111+
}
112+
113+
cmd.Flags().StringVar(&esArgs, "es-args", esArgs, "Additional arguments")
114+
115+
cmd.Flags().StringVar(&masterURL, "master", masterURL, "The address of the Kubernetes API server (overrides any value in kubeconfig)")
116+
cmd.Flags().StringVar(&kubeconfigPath, "kubeconfig", kubeconfigPath, "Path to kubeconfig file with authorization information (the master location is set by the master flag).")
117+
cmd.Flags().StringVar(&namespace, "namespace", "default", "Namespace of Backup/Restore Session")
118+
cmd.Flags().StringVar(&appBindingName, "app-binding", appBindingName, "Name of the app binding")
119+
120+
cmd.Flags().StringVar(&setupOpt.Provider, "provider", setupOpt.Provider, "Backend provider (i.e. gcs, s3, azure etc)")
121+
cmd.Flags().StringVar(&setupOpt.Bucket, "bucket", setupOpt.Bucket, "Name of the cloud bucket/container (keep empty for local backend)")
122+
cmd.Flags().StringVar(&setupOpt.Endpoint, "endpoint", setupOpt.Endpoint, "Endpoint for s3/s3 compatible backend")
123+
cmd.Flags().StringVar(&setupOpt.Path, "path", setupOpt.Path, "Directory inside the bucket where backup will be stored")
124+
cmd.Flags().StringVar(&setupOpt.SecretDir, "secret-dir", setupOpt.SecretDir, "Directory where storage secret has been mounted")
125+
cmd.Flags().StringVar(&setupOpt.ScratchDir, "scratch-dir", setupOpt.ScratchDir, "Temporary directory")
126+
cmd.Flags().BoolVar(&setupOpt.EnableCache, "enable-cache", setupOpt.EnableCache, "Specify weather to enable caching for restic")
127+
128+
cmd.Flags().StringVar(&restoreOpt.Host, "hostname", restoreOpt.Host, "Name of the host machine")
129+
cmd.Flags().StringSliceVar(&restoreOpt.Snapshots, "snapshots", restoreOpt.Snapshots, "Snapshots to restore")
130+
131+
cmd.Flags().StringVar(&outputDir, "output-dir", outputDir, "Directory where output.json file will be written (keep empty if you don't need to write output in file)")
132+
133+
cmd.Flags().BoolVar(&metrics.Enabled, "metrics-enabled", metrics.Enabled, "Specify weather to export Prometheus metrics")
134+
cmd.Flags().StringVar(&metrics.PushgatewayURL, "metrics-pushgateway-url", metrics.PushgatewayURL, "Pushgateway URL where the metrics will be pushed")
135+
cmd.Flags().StringVar(&metrics.MetricFileDir, "metrics-dir", metrics.MetricFileDir, "Directory where to write metric.prom file (keep empty if you don't want to write metric in a text file)")
136+
cmd.Flags().StringSliceVar(&metrics.Labels, "metrics-labels", metrics.Labels, "Labels to apply in exported metrics")
137+
138+
return cmd
139+
}

root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ func NewRootCmd() *cobra.Command {
7070
rootCmd.AddCommand(NewCmdBackupMongo())
7171
rootCmd.AddCommand(NewCmdRestoreMongo())
7272

73+
rootCmd.AddCommand(NewCmdBackupES())
74+
rootCmd.AddCommand(NewCmdRestoreES())
75+
7376
rootCmd.AddCommand(NewCmdUpdateStatus())
7477

7578
rootCmd.AddCommand(stash_cli.NewCLICmd())

0 commit comments

Comments
 (0)