github.com/sl1pm4t/consul@v1.4.5-0.20190325224627-74c31c540f9c/command/agent/agent.go (about) 1 package agent 2 3 import ( 4 "flag" 5 "fmt" 6 "io" 7 "log" 8 "os" 9 "os/signal" 10 "path/filepath" 11 "strings" 12 "syscall" 13 "time" 14 15 "github.com/hashicorp/consul/agent" 16 "github.com/hashicorp/consul/agent/config" 17 "github.com/hashicorp/consul/command/flags" 18 "github.com/hashicorp/consul/lib" 19 "github.com/hashicorp/consul/logger" 20 "github.com/hashicorp/consul/service_os" 21 "github.com/hashicorp/go-checkpoint" 22 multierror "github.com/hashicorp/go-multierror" 23 "github.com/hashicorp/logutils" 24 "github.com/mitchellh/cli" 25 "google.golang.org/grpc/grpclog" 26 ) 27 28 func New(ui cli.Ui, revision, version, versionPre, versionHuman string, shutdownCh <-chan struct{}) *cmd { 29 ui = &cli.PrefixedUi{ 30 OutputPrefix: "==> ", 31 InfoPrefix: " ", 32 ErrorPrefix: "==> ", 33 Ui: ui, 34 } 35 36 c := &cmd{ 37 UI: ui, 38 revision: revision, 39 version: version, 40 versionPrerelease: versionPre, 41 versionHuman: versionHuman, 42 shutdownCh: shutdownCh, 43 } 44 c.init() 45 return c 46 } 47 48 // AgentCommand is a Command implementation that runs a Consul agent. 49 // The command will not end unless a shutdown message is sent on the 50 // ShutdownCh. If two messages are sent on the ShutdownCh it will forcibly 51 // exit. 52 type cmd struct { 53 UI cli.Ui 54 flags *flag.FlagSet 55 http *flags.HTTPFlags 56 help string 57 revision string 58 version string 59 versionPrerelease string 60 versionHuman string 61 shutdownCh <-chan struct{} 62 flagArgs config.Flags 63 logFilter *logutils.LevelFilter 64 logOutput io.Writer 65 logger *log.Logger 66 } 67 68 func (c *cmd) init() { 69 c.flags = flag.NewFlagSet("", flag.ContinueOnError) 70 config.AddFlags(c.flags, &c.flagArgs) 71 c.help = flags.Usage(help, c.flags) 72 } 73 74 func (c *cmd) Run(args []string) int { 75 code := c.run(args) 76 if c.logger != nil { 77 c.logger.Println("[INFO] agent: Exit code:", code) 78 } 79 return code 80 } 81 82 // readConfig is responsible for setup of our configuration using 83 // the command line and any file configs 84 func (c *cmd) readConfig() *config.RuntimeConfig { 85 b, err := config.NewBuilder(c.flagArgs) 86 if err != nil { 87 c.UI.Error(err.Error()) 88 return nil 89 } 90 cfg, err := b.BuildAndValidate() 91 if err != nil { 92 c.UI.Error(err.Error()) 93 return nil 94 } 95 for _, w := range b.Warnings { 96 c.UI.Warn(w) 97 } 98 return &cfg 99 } 100 101 // checkpointResults is used to handler periodic results from our update checker 102 func (c *cmd) checkpointResults(results *checkpoint.CheckResponse, err error) { 103 if err != nil { 104 c.UI.Error(fmt.Sprintf("Failed to check for updates: %v", err)) 105 return 106 } 107 if results.Outdated { 108 c.UI.Error(fmt.Sprintf("Newer Consul version available: %s (currently running: %s)", results.CurrentVersion, c.version)) 109 } 110 for _, alert := range results.Alerts { 111 switch alert.Level { 112 case "info": 113 c.UI.Info(fmt.Sprintf("Bulletin [%s]: %s (%s)", alert.Level, alert.Message, alert.URL)) 114 default: 115 c.UI.Error(fmt.Sprintf("Bulletin [%s]: %s (%s)", alert.Level, alert.Message, alert.URL)) 116 } 117 } 118 } 119 120 func (c *cmd) startupUpdateCheck(config *config.RuntimeConfig) { 121 version := config.Version 122 if config.VersionPrerelease != "" { 123 version += fmt.Sprintf("-%s", config.VersionPrerelease) 124 } 125 updateParams := &checkpoint.CheckParams{ 126 Product: "consul", 127 Version: version, 128 } 129 if !config.DisableAnonymousSignature { 130 updateParams.SignatureFile = filepath.Join(config.DataDir, "checkpoint-signature") 131 } 132 133 // Schedule a periodic check with expected interval of 24 hours 134 checkpoint.CheckInterval(updateParams, 24*time.Hour, c.checkpointResults) 135 136 // Do an immediate check within the next 30 seconds 137 go func() { 138 time.Sleep(lib.RandomStagger(30 * time.Second)) 139 c.checkpointResults(checkpoint.Check(updateParams)) 140 }() 141 } 142 143 // startupJoin is invoked to handle any joins specified to take place at start time 144 func (c *cmd) startupJoin(agent *agent.Agent, cfg *config.RuntimeConfig) error { 145 if len(cfg.StartJoinAddrsLAN) == 0 { 146 return nil 147 } 148 149 c.UI.Output("Joining cluster...") 150 n, err := agent.JoinLAN(cfg.StartJoinAddrsLAN) 151 if err != nil { 152 return err 153 } 154 155 c.UI.Info(fmt.Sprintf("Join completed. Synced with %d initial agents", n)) 156 return nil 157 } 158 159 // startupJoinWan is invoked to handle any joins -wan specified to take place at start time 160 func (c *cmd) startupJoinWan(agent *agent.Agent, cfg *config.RuntimeConfig) error { 161 if len(cfg.StartJoinAddrsWAN) == 0 { 162 return nil 163 } 164 165 c.UI.Output("Joining -wan cluster...") 166 n, err := agent.JoinWAN(cfg.StartJoinAddrsWAN) 167 if err != nil { 168 return err 169 } 170 171 c.UI.Info(fmt.Sprintf("Join -wan completed. Synced with %d initial agents", n)) 172 return nil 173 } 174 175 func (c *cmd) run(args []string) int { 176 // Parse our configs 177 if err := c.flags.Parse(args); err != nil { 178 if !strings.Contains(err.Error(), "help requested") { 179 c.UI.Error(fmt.Sprintf("error parsing flags: %v", err)) 180 } 181 return 1 182 } 183 c.flagArgs.Args = c.flags.Args() 184 config := c.readConfig() 185 if config == nil { 186 return 1 187 } 188 189 // Setup the log outputs 190 logConfig := &logger.Config{ 191 LogLevel: config.LogLevel, 192 EnableSyslog: config.EnableSyslog, 193 SyslogFacility: config.SyslogFacility, 194 LogFilePath: config.LogFile, 195 LogRotateDuration: config.LogRotateDuration, 196 LogRotateBytes: config.LogRotateBytes, 197 } 198 logFilter, logGate, logWriter, logOutput, ok := logger.Setup(logConfig, c.UI) 199 if !ok { 200 return 1 201 } 202 c.logFilter = logFilter 203 c.logOutput = logOutput 204 c.logger = log.New(logOutput, "", log.LstdFlags) 205 206 // Setup gRPC logger to use the same output/filtering 207 grpclog.SetLoggerV2(logger.NewGRPCLogger(logConfig, c.logger)) 208 209 memSink, err := lib.InitTelemetry(config.Telemetry) 210 if err != nil { 211 c.UI.Error(err.Error()) 212 return 1 213 } 214 215 // Create the agent 216 c.UI.Output("Starting Consul agent...") 217 agent, err := agent.New(config) 218 if err != nil { 219 c.UI.Error(fmt.Sprintf("Error creating agent: %s", err)) 220 return 1 221 } 222 agent.LogOutput = logOutput 223 agent.LogWriter = logWriter 224 agent.MemSink = memSink 225 226 if err := agent.Start(); err != nil { 227 c.UI.Error(fmt.Sprintf("Error starting agent: %s", err)) 228 return 1 229 } 230 231 // shutdown agent before endpoints 232 defer agent.ShutdownEndpoints() 233 defer agent.ShutdownAgent() 234 235 if !config.DisableUpdateCheck { 236 c.startupUpdateCheck(config) 237 } 238 239 if err := c.startupJoin(agent, config); err != nil { 240 c.UI.Error(err.Error()) 241 return 1 242 } 243 244 if err := c.startupJoinWan(agent, config); err != nil { 245 c.UI.Error(err.Error()) 246 return 1 247 } 248 249 // Let the agent know we've finished registration 250 agent.StartSync() 251 252 segment := config.SegmentName 253 if config.ServerMode { 254 segment = "<all>" 255 } 256 257 c.UI.Output("Consul agent running!") 258 c.UI.Info(fmt.Sprintf(" Version: '%s'", c.versionHuman)) 259 c.UI.Info(fmt.Sprintf(" Node ID: '%s'", config.NodeID)) 260 c.UI.Info(fmt.Sprintf(" Node name: '%s'", config.NodeName)) 261 c.UI.Info(fmt.Sprintf(" Datacenter: '%s' (Segment: '%s')", config.Datacenter, segment)) 262 c.UI.Info(fmt.Sprintf(" Server: %v (Bootstrap: %v)", config.ServerMode, config.Bootstrap)) 263 c.UI.Info(fmt.Sprintf(" Client Addr: %v (HTTP: %d, HTTPS: %d, gRPC: %d, DNS: %d)", config.ClientAddrs, 264 config.HTTPPort, config.HTTPSPort, config.GRPCPort, config.DNSPort)) 265 c.UI.Info(fmt.Sprintf(" Cluster Addr: %v (LAN: %d, WAN: %d)", config.AdvertiseAddrLAN, 266 config.SerfPortLAN, config.SerfPortWAN)) 267 c.UI.Info(fmt.Sprintf(" Encrypt: Gossip: %v, TLS-Outgoing: %v, TLS-Incoming: %v", 268 agent.GossipEncrypted(), config.VerifyOutgoing, config.VerifyIncoming)) 269 270 // Enable log streaming 271 c.UI.Info("") 272 c.UI.Output("Log data will now stream in as it occurs:\n") 273 logGate.Flush() 274 275 // wait for signal 276 signalCh := make(chan os.Signal, 10) 277 signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGPIPE) 278 279 for { 280 var sig os.Signal 281 var reloadErrCh chan error 282 select { 283 case s := <-signalCh: 284 sig = s 285 case ch := <-agent.ReloadCh(): 286 sig = syscall.SIGHUP 287 reloadErrCh = ch 288 case <-service_os.Shutdown_Channel(): 289 sig = os.Interrupt 290 case <-c.shutdownCh: 291 sig = os.Interrupt 292 case err := <-agent.RetryJoinCh(): 293 c.logger.Println("[ERR] agent: Retry join failed: ", err) 294 return 1 295 case <-agent.ShutdownCh(): 296 // agent is already down! 297 return 0 298 } 299 300 switch sig { 301 case syscall.SIGPIPE: 302 continue 303 304 case syscall.SIGHUP: 305 c.logger.Println("[INFO] agent: Caught signal: ", sig) 306 307 conf, err := c.handleReload(agent, config) 308 if conf != nil { 309 config = conf 310 } 311 if err != nil { 312 c.logger.Println("[ERR] agent: Reload config failed: ", err) 313 } 314 // Send result back if reload was called via HTTP 315 if reloadErrCh != nil { 316 reloadErrCh <- err 317 } 318 319 default: 320 c.logger.Println("[INFO] agent: Caught signal: ", sig) 321 322 graceful := (sig == os.Interrupt && !(config.SkipLeaveOnInt)) || (sig == syscall.SIGTERM && (config.LeaveOnTerm)) 323 if !graceful { 324 c.logger.Println("[INFO] agent: Graceful shutdown disabled. Exiting") 325 return 1 326 } 327 328 c.logger.Println("[INFO] agent: Gracefully shutting down agent...") 329 gracefulCh := make(chan struct{}) 330 go func() { 331 if err := agent.Leave(); err != nil { 332 c.logger.Println("[ERR] agent: Error on leave:", err) 333 return 334 } 335 close(gracefulCh) 336 }() 337 338 gracefulTimeout := 15 * time.Second 339 select { 340 case <-signalCh: 341 c.logger.Printf("[INFO] agent: Caught second signal %v. Exiting\n", sig) 342 return 1 343 case <-time.After(gracefulTimeout): 344 c.logger.Println("[INFO] agent: Timeout on graceful leave. Exiting") 345 return 1 346 case <-gracefulCh: 347 c.logger.Println("[INFO] agent: Graceful exit completed") 348 return 0 349 } 350 } 351 } 352 } 353 354 // handleReload is invoked when we should reload our configs, e.g. SIGHUP 355 func (c *cmd) handleReload(agent *agent.Agent, cfg *config.RuntimeConfig) (*config.RuntimeConfig, error) { 356 c.logger.Println("[INFO] agent: Reloading configuration...") 357 var errs error 358 newCfg := c.readConfig() 359 if newCfg == nil { 360 errs = multierror.Append(errs, fmt.Errorf("Failed to reload configs")) 361 return cfg, errs 362 } 363 364 // Change the log level 365 minLevel := logutils.LogLevel(strings.ToUpper(newCfg.LogLevel)) 366 if logger.ValidateLevelFilter(minLevel, c.logFilter) { 367 c.logFilter.SetMinLevel(minLevel) 368 } else { 369 errs = multierror.Append(fmt.Errorf( 370 "Invalid log level: %s. Valid log levels are: %v", 371 minLevel, c.logFilter.Levels)) 372 373 // Keep the current log level 374 newCfg.LogLevel = cfg.LogLevel 375 } 376 377 if err := agent.ReloadConfig(newCfg); err != nil { 378 errs = multierror.Append(fmt.Errorf( 379 "Failed to reload configs: %v", err)) 380 } 381 382 return newCfg, errs 383 } 384 385 func (c *cmd) Synopsis() string { 386 return synopsis 387 } 388 389 func (c *cmd) Help() string { 390 return c.help 391 } 392 393 const synopsis = "Runs a Consul agent" 394 const help = ` 395 Usage: consul agent [options] 396 397 Starts the Consul agent and runs until an interrupt is received. The 398 agent represents a single node in a cluster. 399 `