github.com/alibaba/sealer@v0.8.6-0.20220430115802-37a2bdaa8173/pkg/plugin/etcd_backup_plugin.go (about) 1 // Copyright © 2021 Alibaba Group Holding Ltd. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package plugin 16 17 import ( 18 "context" 19 "crypto/tls" 20 "crypto/x509" 21 "errors" 22 "fmt" 23 "io/ioutil" 24 "time" 25 26 clientv3 "go.etcd.io/etcd/client/v3" 27 "go.etcd.io/etcd/client/v3/snapshot" 28 "go.uber.org/zap" 29 30 "github.com/alibaba/sealer/logger" 31 "github.com/alibaba/sealer/utils/ssh" 32 ) 33 34 type EtcdBackupPlugin struct { 35 } 36 37 func NewEtcdBackupPlugin() Interface { 38 return &EtcdBackupPlugin{} 39 } 40 41 func init() { 42 Register(EtcdPlugin, NewEtcdBackupPlugin()) 43 } 44 45 func (e EtcdBackupPlugin) Run(context Context, phase Phase) error { 46 masterIP, err := getMasterIP(context) 47 if err != nil { 48 return err 49 } 50 51 if err := fetchRemoteCert(context, masterIP); err != nil { 52 return err 53 } 54 55 cfg, err := connEtcd(masterIP) 56 if err != nil { 57 return err 58 } 59 60 return snapshotEtcd(context.Plugin.Spec.On, cfg) 61 } 62 63 func getMasterIP(context Context) (string, error) { 64 masterIPList := context.Cluster.GetMasterIPList() 65 if len(masterIPList) == 0 { 66 return "", errors.New("cluster master does not exist") 67 } 68 return masterIPList[0], nil 69 } 70 71 func fetchRemoteCert(context Context, masterIP string) error { 72 certs := []string{"healthcheck-client.crt", "healthcheck-client.key", "ca.crt"} 73 for _, cert := range certs { 74 sshClient, err := ssh.GetHostSSHClient(masterIP, context.Cluster) 75 if err != nil { 76 return err 77 } 78 if err := sshClient.Fetch(masterIP, "/tmp/"+cert, "/etc/kubernetes/pki/etcd/"+cert); err != nil { 79 return fmt.Errorf("host %s %s file does not exist, err: %v", masterIP, cert, err) 80 } 81 } 82 return nil 83 } 84 85 func connEtcd(masterIP string) (clientv3.Config, error) { 86 const ( 87 dialTimeout = 5 * time.Second 88 etcdCert = "/tmp/healthcheck-client.crt" 89 etcdCertKey = "/tmp/healthcheck-client.key" 90 etcdCa = "/tmp/ca.crt" 91 ) 92 93 cert, err := tls.LoadX509KeyPair(etcdCert, etcdCertKey) 94 if err != nil { 95 return clientv3.Config{}, fmt.Errorf("cacert or key file is not exist, err:%v", err) 96 } 97 98 caData, err := ioutil.ReadFile(etcdCa) 99 if err != nil { 100 return clientv3.Config{}, fmt.Errorf("ca certificate reading failed, err:%v", err) 101 } 102 103 pool := x509.NewCertPool() 104 pool.AppendCertsFromPEM(caData) 105 // #nosec 106 _tlsConfig := &tls.Config{ 107 Certificates: []tls.Certificate{cert}, 108 RootCAs: pool, 109 } 110 111 endpoints := []string{fmt.Sprintf("https://%s:2379", masterIP)} 112 cfg := clientv3.Config{ 113 Endpoints: endpoints, 114 DialTimeout: dialTimeout, 115 TLS: _tlsConfig, 116 } 117 118 cli, err := clientv3.New(cfg) 119 if err != nil { 120 return clientv3.Config{}, fmt.Errorf("connect to etcd failed, err:%v", err) 121 } 122 123 logger.Info("connect to etcd success") 124 125 defer cli.Close() 126 127 return cfg, nil 128 } 129 130 func snapshotEtcd(snapshotPath string, cfg clientv3.Config) error { 131 lg, err := zap.NewProduction() 132 if err != nil { 133 return fmt.Errorf("get zap logger error, err:%v", err) 134 } 135 136 ctx, cancel := context.WithCancel(context.Background()) 137 defer cancel() 138 139 if err := snapshot.Save(ctx, lg, cfg, snapshotPath); err != nil { 140 return fmt.Errorf("snapshot save err: %v", err) 141 } 142 logger.Info("Snapshot saved at %s\n", snapshotPath) 143 144 return nil 145 }