github.com/KyaXTeam/consul@v1.4.5/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  `