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  }