github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/cmd/reloader/app/cmd.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package app 21 22 import ( 23 "context" 24 "fmt" 25 "net" 26 "os" 27 "time" 28 29 "github.com/fsnotify/fsnotify" 30 "github.com/spf13/cobra" 31 zaplogfmt "github.com/sykesm/zap-logfmt" 32 "go.uber.org/zap" 33 "go.uber.org/zap/zapcore" 34 "google.golang.org/grpc" 35 36 cfgcore "github.com/1aal/kubeblocks/pkg/configuration/config_manager" 37 cfgutil "github.com/1aal/kubeblocks/pkg/configuration/core" 38 cfgproto "github.com/1aal/kubeblocks/pkg/configuration/proto" 39 ) 40 41 var logger *zap.SugaredLogger 42 43 // NewConfigManagerCommand is used to reload configuration 44 func NewConfigManagerCommand(ctx context.Context, name string) *cobra.Command { 45 opt := NewVolumeWatcherOpts() 46 cmd := &cobra.Command{ 47 Use: name, 48 Short: name + " provides a mechanism to implement reload config files in a sidecar for kubeblocks.", 49 RunE: func(cmd *cobra.Command, args []string) error { 50 return runConfigManagerCommand(ctx, opt) 51 }, 52 } 53 54 cmd.SetContext(ctx) 55 InstallFlags(cmd.Flags(), opt) 56 return cmd 57 } 58 59 func runConfigManagerCommand(ctx context.Context, opt *VolumeWatcherOpts) error { 60 zapLog := initLog(opt.LogLevel) 61 defer func() { 62 _ = zapLog.Sync() 63 }() 64 65 logger = zapLog.Sugar() 66 cfgcore.SetLogger(zapLog) 67 68 if err := checkOptions(opt); err != nil { 69 return err 70 } 71 if opt.BackupPath == "" { 72 tmpDir, err := os.MkdirTemp(os.TempDir(), "reload-backup-") 73 if err != nil { 74 return err 75 } 76 opt.BackupPath = tmpDir 77 defer os.RemoveAll(tmpDir) 78 } 79 return run(ctx, opt) 80 } 81 82 func run(ctx context.Context, opt *VolumeWatcherOpts) error { 83 var ( 84 err error 85 volumeWatcher *cfgcore.ConfigMapVolumeWatcher 86 configHandler cfgcore.ConfigHandler 87 ) 88 89 if configHandler, err = cfgcore.CreateCombinedHandler(opt.CombConfig, opt.BackupPath); err != nil { 90 return err 91 } 92 if len(opt.VolumeDirs) > 0 { 93 if volumeWatcher, err = startVolumeWatcher(ctx, opt, configHandler); err != nil { 94 return err 95 } 96 defer volumeWatcher.Close() 97 } 98 99 if err = checkAndCreateService(ctx, opt, configHandler); err != nil { 100 return err 101 } 102 103 logger.Info("config manager started.") 104 <-ctx.Done() 105 logger.Info("config manager shutdown.") 106 return nil 107 } 108 109 func checkAndCreateService(ctx context.Context, opt *VolumeWatcherOpts, handler cfgcore.ConfigHandler) error { 110 serviceOpt := opt.ServiceOpt 111 if !serviceOpt.ContainerRuntimeEnable && !serviceOpt.RemoteOnlineUpdateEnable { 112 return nil 113 } 114 if err := startGRPCService(opt, ctx, handler); err != nil { 115 return cfgutil.WrapError(err, "failed to start grpc service") 116 } 117 return nil 118 } 119 120 func startVolumeWatcher(ctx context.Context, opt *VolumeWatcherOpts, handler cfgcore.ConfigHandler) (*cfgcore.ConfigMapVolumeWatcher, error) { 121 eventHandler := func(ctx context.Context, event fsnotify.Event) error { 122 return handler.VolumeHandle(ctx, event) 123 } 124 logger.Info("starting fsnotify VolumeWatcher.") 125 volumeWatcher := cfgcore.NewVolumeWatcher(opt.VolumeDirs, ctx, logger) 126 err := volumeWatcher.AddHandler(eventHandler).Run() 127 if err != nil { 128 logger.Error(err, "failed to handle VolumeWatcher.") 129 return nil, err 130 } 131 logger.Info("fsnotify VolumeWatcher started.") 132 return volumeWatcher, nil 133 } 134 135 func startGRPCService(opt *VolumeWatcherOpts, ctx context.Context, handler cfgcore.ConfigHandler) error { 136 var ( 137 server *grpc.Server 138 proxy = &reconfigureProxy{opt: opt.ServiceOpt, ctx: ctx, logger: logger.Named("grpcProxy")} 139 ) 140 141 if err := proxy.Init(handler); err != nil { 142 return err 143 } 144 145 tcpSpec := fmt.Sprintf("%s:%d", proxy.opt.PodIP, proxy.opt.GrpcPort) 146 147 logger.Infof("starting reconfigure service: %s", tcpSpec) 148 listener, err := net.Listen("tcp", tcpSpec) 149 if err != nil { 150 return cfgutil.WrapError(err, "failed to create listener: [%s]", tcpSpec) 151 } 152 153 server = grpc.NewServer(grpc.UnaryInterceptor(logUnaryServerInterceptor)) 154 cfgproto.RegisterReconfigureServer(server, proxy) 155 156 go func() { 157 if err := server.Serve(listener); err != nil { 158 logger.Error(err, "failed to serve connections from cri") 159 os.Exit(1) 160 } 161 }() 162 logger.Info("reconfigure service started.") 163 return nil 164 } 165 166 func logUnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { 167 logger.Debugf("info: [%+v]", info) 168 return handler(ctx, req) 169 } 170 171 func checkOptions(opt *VolumeWatcherOpts) error { 172 if len(opt.VolumeDirs) == 0 && !opt.ServiceOpt.RemoteOnlineUpdateEnable { 173 return cfgutil.MakeError("require volume directory is null.") 174 } 175 if opt.CombConfig == "" { 176 return cfgutil.MakeError("required config is empty.") 177 } 178 return nil 179 } 180 181 func initLog(level string) *zap.Logger { 182 const ( 183 rfc3339Mills = "2006-01-02T15:04:05.000" 184 ) 185 186 levelStrings := map[string]zapcore.Level{ 187 "debug": zap.DebugLevel, 188 "info": zap.InfoLevel, 189 "error": zap.ErrorLevel, 190 } 191 192 if _, ok := levelStrings[level]; !ok { 193 fmt.Printf("not supported log level[%s], set default info", level) 194 level = "info" 195 } 196 197 logCfg := zap.NewProductionEncoderConfig() 198 logCfg.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) { 199 encoder.AppendString(ts.UTC().Format(rfc3339Mills)) 200 } 201 202 // NOTES: 203 // zap is "Blazing fast, structured, leveled logging in Go.", DON'T event try 204 // to refactor this logging lib to anything else. Check FAQ - https://github.com/uber-go/zap/blob/master/FAQ.md 205 zapLog := zap.New(zapcore.NewCore(zaplogfmt.NewEncoder(logCfg), os.Stdout, levelStrings[level])) 206 return zapLog 207 }