Unverified Commit ae4a1a14 authored by Chris Kim's avatar Chris Kim Committed by GitHub

etcd snapshot functionality enhancements (#4453)

Signed-off-by: 's avatarChris Kim <oats87g@gmail.com>
parent 0c1f816f
...@@ -26,6 +26,11 @@ var EtcdSnapshotFlags = []cli.Flag{ ...@@ -26,6 +26,11 @@ var EtcdSnapshotFlags = []cli.Flag{
Destination: &ServerConfig.DataDir, Destination: &ServerConfig.DataDir,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "dir,etcd-snapshot-dir",
Usage: "(db) Directory to save etcd on-demand snapshot. (default: ${data-dir}/db/snapshots)",
Destination: &ServerConfig.EtcdSnapshotDir,
},
&cli.StringFlag{
Name: "name", Name: "name",
Usage: "(db) Set the base name of the etcd on-demand snapshot (appended with UNIX timestamp).", Usage: "(db) Set the base name of the etcd on-demand snapshot (appended with UNIX timestamp).",
Destination: &ServerConfig.EtcdSnapshotName, Destination: &ServerConfig.EtcdSnapshotName,
...@@ -101,11 +106,7 @@ func NewEtcdSnapshotCommand(action func(*cli.Context) error, subcommands []cli.C ...@@ -101,11 +106,7 @@ func NewEtcdSnapshotCommand(action func(*cli.Context) error, subcommands []cli.C
SkipArgReorder: true, SkipArgReorder: true,
Action: action, Action: action,
Subcommands: subcommands, Subcommands: subcommands,
Flags: append(EtcdSnapshotFlags, &cli.StringFlag{ Flags: EtcdSnapshotFlags,
Name: "dir,etcd-snapshot-dir",
Usage: "(db) Directory to save etcd on-demand snapshot. (default: ${data-dir}/db/snapshots)",
Destination: &ServerConfig.EtcdSnapshotDir,
}),
} }
} }
...@@ -130,7 +131,7 @@ func NewEtcdSnapshotSubcommands(delete, list, prune, save func(ctx *cli.Context) ...@@ -130,7 +131,7 @@ func NewEtcdSnapshotSubcommands(delete, list, prune, save func(ctx *cli.Context)
}, },
{ {
Name: "prune", Name: "prune",
Usage: "Remove snapshots that exceed the configured retention count", Usage: "Remove snapshots that match the name prefix that exceed the configured retention count",
SkipFlagParsing: false, SkipFlagParsing: false,
SkipArgReorder: true, SkipArgReorder: true,
Action: prune, Action: prune,
...@@ -147,11 +148,7 @@ func NewEtcdSnapshotSubcommands(delete, list, prune, save func(ctx *cli.Context) ...@@ -147,11 +148,7 @@ func NewEtcdSnapshotSubcommands(delete, list, prune, save func(ctx *cli.Context)
SkipFlagParsing: false, SkipFlagParsing: false,
SkipArgReorder: true, SkipArgReorder: true,
Action: save, Action: save,
Flags: append(EtcdSnapshotFlags, &cli.StringFlag{ Flags: EtcdSnapshotFlags,
Name: "dir",
Usage: "(db) Directory to save etcd on-demand snapshot. (default: ${data-dir}/db/snapshots)",
Destination: &ServerConfig.EtcdSnapshotDir,
}),
}, },
} }
} }
...@@ -173,13 +173,21 @@ func list(app *cli.Context, cfg *cmds.Server) error { ...@@ -173,13 +173,21 @@ func list(app *cli.Context, cfg *cmds.Server) error {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
defer w.Flush() defer w.Flush()
for _, s := range sf {
if cfg.EtcdS3 { if cfg.EtcdS3 {
fmt.Fprint(w, "Name\tSize\tCreated\n")
for _, s := range sf {
if s.NodeName == "s3" {
fmt.Fprintf(w, "%s\t%d\t%s\n", s.Name, s.Size, s.CreatedAt.Format(time.RFC3339)) fmt.Fprintf(w, "%s\t%d\t%s\n", s.Name, s.Size, s.CreatedAt.Format(time.RFC3339))
}
}
} else { } else {
fmt.Fprint(w, "Name\tLocation\tSize\tCreated\n")
for _, s := range sf {
if s.NodeName != "s3" {
fmt.Fprintf(w, "%s\t%s\t%d\t%s\n", s.Name, s.Location, s.Size, s.CreatedAt.Format(time.RFC3339)) fmt.Fprintf(w, "%s\t%s\t%d\t%s\n", s.Name, s.Location, s.Size, s.CreatedAt.Format(time.RFC3339))
} }
} }
}
return nil return nil
} }
...@@ -201,10 +209,17 @@ func prune(app *cli.Context, cfg *cmds.Server) error { ...@@ -201,10 +209,17 @@ func prune(app *cli.Context, cfg *cmds.Server) error {
serverConfig.ControlConfig.DataDir = dataDir serverConfig.ControlConfig.DataDir = dataDir
serverConfig.ControlConfig.EtcdSnapshotRetention = cfg.EtcdSnapshotRetention serverConfig.ControlConfig.EtcdSnapshotRetention = cfg.EtcdSnapshotRetention
serverConfig.ControlConfig.Runtime.KubeConfigAdmin = filepath.Join(dataDir, "cred", "admin.kubeconfig")
ctx := signals.SetupSignalContext() ctx := signals.SetupSignalContext()
e := etcd.NewETCD() e := etcd.NewETCD()
e.SetControlConfig(&serverConfig.ControlConfig) e.SetControlConfig(&serverConfig.ControlConfig)
sc, err := server.NewContext(ctx, serverConfig.ControlConfig.Runtime.KubeConfigAdmin)
if err != nil {
return err
}
serverConfig.ControlConfig.Runtime.Core = sc.Core
return e.PruneSnapshots(ctx) return e.PruneSnapshots(ctx)
} }
...@@ -104,7 +104,7 @@ func (c *Cluster) Start(ctx context.Context) (<-chan struct{}, error) { ...@@ -104,7 +104,7 @@ func (c *Cluster) Start(ctx context.Context) (<-chan struct{}, error) {
} }
if !c.config.EtcdDisableSnapshots { if !c.config.EtcdDisableSnapshots {
if err := c.managedDB.StoreSnapshotData(ctx); err != nil { if err := c.managedDB.ReconcileSnapshotData(ctx); err != nil {
logrus.Errorf("Failed to record snapshots for cluster: %v", err) logrus.Errorf("Failed to record snapshots for cluster: %v", err)
} }
} }
......
...@@ -22,7 +22,7 @@ type Driver interface { ...@@ -22,7 +22,7 @@ type Driver interface {
Restore(ctx context.Context) error Restore(ctx context.Context) error
EndpointName() string EndpointName() string
Snapshot(ctx context.Context, config *config.Control) error Snapshot(ctx context.Context, config *config.Control) error
StoreSnapshotData(ctx context.Context) error ReconcileSnapshotData(ctx context.Context) error
GetMembersClientURLs(ctx context.Context) ([]string, error) GetMembersClientURLs(ctx context.Context) ([]string, error)
RemoveSelf(ctx context.Context) error RemoveSelf(ctx context.Context) error
} }
......
...@@ -36,7 +36,7 @@ var _ = Describe("etcd snapshots", func() { ...@@ -36,7 +36,7 @@ var _ = Describe("etcd snapshots", func() {
}) })
It("saves an etcd snapshot", func() { It("saves an etcd snapshot", func() {
Expect(testutil.K3sCmd("etcd-snapshot", "save")). Expect(testutil.K3sCmd("etcd-snapshot", "save")).
To(ContainSubstring("Saving current etcd snapshot set to k3s-etcd-snapshots")) To(ContainSubstring("saved"))
}) })
It("list snapshots", func() { It("list snapshots", func() {
Expect(testutil.K3sCmd("etcd-snapshot", "ls")). Expect(testutil.K3sCmd("etcd-snapshot", "ls")).
...@@ -70,13 +70,13 @@ var _ = Describe("etcd snapshots", func() { ...@@ -70,13 +70,13 @@ var _ = Describe("etcd snapshots", func() {
When("using etcd snapshot prune", func() { When("using etcd snapshot prune", func() {
It("saves 3 different snapshots", func() { It("saves 3 different snapshots", func() {
Expect(testutil.K3sCmd("etcd-snapshot", "save", "-name", "PRUNE_TEST")). Expect(testutil.K3sCmd("etcd-snapshot", "save", "-name", "PRUNE_TEST")).
To(ContainSubstring("Saving current etcd snapshot set to k3s-etcd-snapshots")) To(ContainSubstring("saved"))
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
Expect(testutil.K3sCmd("etcd-snapshot", "save", "-name", "PRUNE_TEST")). Expect(testutil.K3sCmd("etcd-snapshot", "save", "-name", "PRUNE_TEST")).
To(ContainSubstring("Saving current etcd snapshot set to k3s-etcd-snapshots")) To(ContainSubstring("saved"))
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
Expect(testutil.K3sCmd("etcd-snapshot", "save", "-name", "PRUNE_TEST")). Expect(testutil.K3sCmd("etcd-snapshot", "save", "-name", "PRUNE_TEST")).
To(ContainSubstring("Saving current etcd snapshot set to k3s-etcd-snapshots")) To(ContainSubstring("saved"))
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
}) })
It("lists all 3 snapshots", func() { It("lists all 3 snapshots", func() {
...@@ -89,7 +89,7 @@ var _ = Describe("etcd snapshots", func() { ...@@ -89,7 +89,7 @@ var _ = Describe("etcd snapshots", func() {
}) })
It("prunes snapshots down to 2", func() { It("prunes snapshots down to 2", func() {
Expect(testutil.K3sCmd("etcd-snapshot", "prune", "--snapshot-retention", "2", "--name", "PRUNE_TEST")). Expect(testutil.K3sCmd("etcd-snapshot", "prune", "--snapshot-retention", "2", "--name", "PRUNE_TEST")).
To(BeEmpty()) To(ContainSubstring("Removing local snapshot"))
lsResult, err := testutil.K3sCmd("etcd-snapshot", "ls") lsResult, err := testutil.K3sCmd("etcd-snapshot", "ls")
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
reg, err := regexp.Compile(`:///var/lib/rancher/k3s/server/db/snapshots/PRUNE_TEST`) reg, err := regexp.Compile(`:///var/lib/rancher/k3s/server/db/snapshots/PRUNE_TEST`)
......
...@@ -14,12 +14,14 @@ import ( ...@@ -14,12 +14,14 @@ import (
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
"time"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/credentials"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// S3 maintains state for S3 functionality. // S3 maintains state for S3 functionality.
...@@ -32,6 +34,9 @@ type S3 struct { ...@@ -32,6 +34,9 @@ type S3 struct {
// copy of the config.Control pointer and initializes // copy of the config.Control pointer and initializes
// a new Minio client. // a new Minio client.
func NewS3(ctx context.Context, config *config.Control) (*S3, error) { func NewS3(ctx context.Context, config *config.Control) (*S3, error) {
if config.EtcdS3BucketName == "" {
return nil, errors.New("s3 bucket name was not set")
}
tr := http.DefaultTransport tr := http.DefaultTransport
switch { switch {
...@@ -88,9 +93,11 @@ func NewS3(ctx context.Context, config *config.Control) (*S3, error) { ...@@ -88,9 +93,11 @@ func NewS3(ctx context.Context, config *config.Control) (*S3, error) {
// upload uploads the given snapshot to the configured S3 // upload uploads the given snapshot to the configured S3
// compatible backend. // compatible backend.
func (s *S3) upload(ctx context.Context, snapshot string) error { func (s *S3) upload(ctx context.Context, snapshot, extraMetadata string, now time.Time) (*SnapshotFile, error) {
logrus.Infof("Uploading snapshot %s to S3", snapshot)
basename := filepath.Base(snapshot) basename := filepath.Base(snapshot)
var snapshotFileName string var snapshotFileName string
var snapshotFile SnapshotFile
if s.config.EtcdS3Folder != "" { if s.config.EtcdS3Folder != "" {
snapshotFileName = filepath.Join(s.config.EtcdS3Folder, basename) snapshotFileName = filepath.Join(s.config.EtcdS3Folder, basename)
} else { } else {
...@@ -103,11 +110,56 @@ func (s *S3) upload(ctx context.Context, snapshot string) error { ...@@ -103,11 +110,56 @@ func (s *S3) upload(ctx context.Context, snapshot string) error {
ContentType: "application/zip", ContentType: "application/zip",
NumThreads: 2, NumThreads: 2,
} }
if _, err := s.client.FPutObject(toCtx, s.config.EtcdS3BucketName, snapshotFileName, snapshot, opts); err != nil { uploadInfo, err := s.client.FPutObject(toCtx, s.config.EtcdS3BucketName, snapshotFileName, snapshot, opts)
logrus.Errorf("Error received in attempt to upload snapshot to S3: %s", err) if err != nil {
snapshotFile = SnapshotFile{
Name: filepath.Base(uploadInfo.Key),
Metadata: extraMetadata,
NodeName: "s3",
CreatedAt: &metav1.Time{
Time: now,
},
Message: base64.StdEncoding.EncodeToString([]byte(err.Error())),
Size: 0,
Status: FailedSnapshotStatus,
S3: &s3Config{
Endpoint: s.config.EtcdS3Endpoint,
EndpointCA: s.config.EtcdS3EndpointCA,
SkipSSLVerify: s.config.EtcdS3SkipSSLVerify,
Bucket: s.config.EtcdS3BucketName,
Region: s.config.EtcdS3Region,
Folder: s.config.EtcdS3Folder,
Insecure: s.config.EtcdS3Insecure,
},
}
logrus.Errorf("Error received during snapshot upload to S3: %s", err)
} else {
ca, err := time.Parse(time.RFC3339, uploadInfo.LastModified.Format(time.RFC3339))
if err != nil {
return nil, err
} }
return nil snapshotFile = SnapshotFile{
Name: filepath.Base(uploadInfo.Key),
Metadata: extraMetadata,
NodeName: "s3",
CreatedAt: &metav1.Time{
Time: ca,
},
Size: uploadInfo.Size,
Status: SuccessfulSnapshotStatus,
S3: &s3Config{
Endpoint: s.config.EtcdS3Endpoint,
EndpointCA: s.config.EtcdS3EndpointCA,
SkipSSLVerify: s.config.EtcdS3SkipSSLVerify,
Bucket: s.config.EtcdS3BucketName,
Region: s.config.EtcdS3Region,
Folder: s.config.EtcdS3Folder,
Insecure: s.config.EtcdS3Insecure,
},
}
}
return &snapshotFile, nil
} }
// download downloads the given snapshot from the configured S3 // download downloads the given snapshot from the configured S3
...@@ -170,9 +222,13 @@ func (s *S3) snapshotPrefix() string { ...@@ -170,9 +222,13 @@ func (s *S3) snapshotPrefix() string {
return prefix return prefix
} }
// snapshotRetention deletes the given snapshot from the configured S3 // snapshotRetention prunes snapshots in the configured S3 compatible backend for this specific node.
// compatible backend.
func (s *S3) snapshotRetention(ctx context.Context) error { func (s *S3) snapshotRetention(ctx context.Context) error {
if s.config.EtcdSnapshotRetention < 1 {
return nil
}
logrus.Infof("Applying snapshot retention policy to snapshots stored in S3: retention: %d, snapshotPrefix: %s", s.config.EtcdSnapshotRetention, s.snapshotPrefix())
var snapshotFiles []minio.ObjectInfo var snapshotFiles []minio.ObjectInfo
toCtx, cancel := context.WithTimeout(ctx, s.config.EtcdS3Timeout) toCtx, cancel := context.WithTimeout(ctx, s.config.EtcdS3Timeout)
...@@ -199,7 +255,7 @@ func (s *S3) snapshotRetention(ctx context.Context) error { ...@@ -199,7 +255,7 @@ func (s *S3) snapshotRetention(ctx context.Context) error {
delCount := len(snapshotFiles) - s.config.EtcdSnapshotRetention delCount := len(snapshotFiles) - s.config.EtcdSnapshotRetention
for _, df := range snapshotFiles[:delCount] { for _, df := range snapshotFiles[:delCount] {
logrus.Debugf("Removing snapshot: %s", df.Key) logrus.Infof("Removing S3 snapshot: %s", df.Key)
if err := s.client.RemoveObject(ctx, s.config.EtcdS3BucketName, df.Key, minio.RemoveObjectOptions{}); err != nil { if err := s.client.RemoveObject(ctx, s.config.EtcdS3BucketName, df.Key, minio.RemoveObjectOptions{}); err != nil {
return err return err
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment