diff --git a/command/agent/agent.go b/command/agent/agent.go index 80bd701ca1..dddefe0bb0 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -67,8 +67,8 @@ func Create(config *Config, logOutput io.Writer) (*Agent, error) { return nil, fmt.Errorf("Must configure a DataDir") } - // Ensure the RPC Addr is sane - if _, err := net.ResolveTCPAddr("tcp", config.ServerAddr); err != nil { + // Ensure the Bind Addr is sane + if _, err := net.ResolveTCPAddr("tcp", config.BindAddr); err != nil { return nil, fmt.Errorf("Bad server address: %v", err) } @@ -138,20 +138,20 @@ func (a *Agent) consulConfig() *consul.Config { if a.config.NodeName != "" { base.NodeName = a.config.NodeName } - if a.config.SerfBindAddr != "" { - base.SerfLANConfig.MemberlistConfig.BindAddr = a.config.SerfBindAddr - base.SerfWANConfig.MemberlistConfig.BindAddr = a.config.SerfBindAddr + if a.config.BindAddr != "" { + base.SerfLANConfig.MemberlistConfig.BindAddr = a.config.BindAddr + base.SerfWANConfig.MemberlistConfig.BindAddr = a.config.BindAddr } - if a.config.SerfLanPort != 0 { - base.SerfLANConfig.MemberlistConfig.BindPort = a.config.SerfLanPort - base.SerfLANConfig.MemberlistConfig.AdvertisePort = a.config.SerfLanPort + if a.config.Ports.SerfLan != 0 { + base.SerfLANConfig.MemberlistConfig.BindPort = a.config.Ports.SerfLan + base.SerfLANConfig.MemberlistConfig.AdvertisePort = a.config.Ports.SerfLan } - if a.config.SerfWanPort != 0 { - base.SerfWANConfig.MemberlistConfig.BindPort = a.config.SerfWanPort - base.SerfWANConfig.MemberlistConfig.AdvertisePort = a.config.SerfWanPort + if a.config.Ports.SerfWan != 0 { + base.SerfWANConfig.MemberlistConfig.BindPort = a.config.Ports.SerfWan + base.SerfWANConfig.MemberlistConfig.AdvertisePort = a.config.Ports.SerfWan } - if a.config.ServerAddr != "" { - addr, _ := net.ResolveTCPAddr("tcp", a.config.ServerAddr) + if a.config.BindAddr != "" { + addr, _ := net.ResolveTCPAddr("tcp", a.config.BindAddr) base.RPCAddr = addr } if a.config.AdvertiseAddr != "" { diff --git a/command/agent/command.go b/command/agent/command.go index 435bfe36aa..87a4be2d89 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -41,28 +41,23 @@ func (c *Command) readConfig() *Config { var configFiles []string cmdFlags := flag.NewFlagSet("agent", flag.ContinueOnError) cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } - cmdFlags.StringVar(&cmdConfig.SerfBindAddr, "serf-bind", "", "address to bind serf listeners to") - cmdFlags.StringVar(&cmdConfig.ServerAddr, "server-addr", "", "address to bind server listeners to") - cmdFlags.Var((*AppendSliceValue)(&configFiles), "config-file", - "json file to read config from") - cmdFlags.Var((*AppendSliceValue)(&configFiles), "config-dir", - "directory of json files to read") - cmdFlags.StringVar(&cmdConfig.EncryptKey, "encrypt", "", "encryption key") + + cmdFlags.Var((*AppendSliceValue)(&configFiles), "config-file", "json file to read config from") + cmdFlags.Var((*AppendSliceValue)(&configFiles), "config-dir", "directory of json files to read") + cmdFlags.StringVar(&cmdConfig.LogLevel, "log-level", "", "log level") cmdFlags.StringVar(&cmdConfig.NodeName, "node", "", "node name") - cmdFlags.StringVar(&cmdConfig.RPCAddr, "rpc-addr", "", - "address to bind RPC listener to") - cmdFlags.StringVar(&cmdConfig.DataDir, "data-dir", "", "path to the data directory") cmdFlags.StringVar(&cmdConfig.Datacenter, "dc", "", "node datacenter") - cmdFlags.StringVar(&cmdConfig.DNSRecursor, "recursor", "", "address of dns recursor") - cmdFlags.StringVar(&cmdConfig.AdvertiseAddr, "advertise", "", "advertise address to use") - cmdFlags.StringVar(&cmdConfig.HTTPAddr, "http-addr", "", "address to bind http server to") - cmdFlags.StringVar(&cmdConfig.DNSAddr, "dns-addr", "", "address to bind dns server to") + cmdFlags.StringVar(&cmdConfig.DataDir, "data-dir", "", "path to the data directory") + cmdFlags.BoolVar(&cmdConfig.Server, "server", false, "run agent as server") cmdFlags.BoolVar(&cmdConfig.Bootstrap, "bootstrap", false, "enable server bootstrap mode") - cmdFlags.StringVar(&cmdConfig.StatsiteAddr, "statsite", "", "address of statsite instance") + + cmdFlags.StringVar(&cmdConfig.ClientAddr, "client", "", "address to bind client listeners to (DNS, HTTP, RPC)") + cmdFlags.StringVar(&cmdConfig.BindAddr, "bind", "", "address to bind server listeners to") + cmdFlags.IntVar(&cmdConfig.Protocol, "protocol", -1, "protocol version") - cmdFlags.BoolVar(&cmdConfig.EnableDebug, "debug", false, "enable debug features") + if err := cmdFlags.Parse(c.args); err != nil { return nil } @@ -152,7 +147,13 @@ func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *log c.agent = agent // Setup the RPC listener - rpcListener, err := net.Listen("tcp", config.RPCAddr) + rpcAddr, err := config.ClientListener(config.Ports.RPC) + if err != nil { + c.Ui.Error(fmt.Sprintf("Invalid RPC bind address: %s", err)) + return err + } + + rpcListener, err := net.Listen("tcp", rpcAddr.String()) if err != nil { agent.Shutdown() c.Ui.Error(fmt.Sprintf("Error starting RPC listener: %s", err)) @@ -163,8 +164,14 @@ func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *log c.Ui.Output("Starting Consul agent RPC...") c.rpcServer = NewAgentRPC(agent, rpcListener, logOutput, logWriter) - if config.HTTPAddr != "" { - server, err := NewHTTPServer(agent, config.EnableDebug, logOutput, config.HTTPAddr) + if config.Ports.HTTP > 0 { + httpAddr, err := config.ClientListener(config.Ports.HTTP) + if err != nil { + c.Ui.Error(fmt.Sprintf("Invalid HTTP bind address: %s", err)) + return err + } + + server, err := NewHTTPServer(agent, config.EnableDebug, logOutput, httpAddr.String()) if err != nil { agent.Shutdown() c.Ui.Error(fmt.Sprintf("Error starting http server: %s", err)) @@ -173,9 +180,15 @@ func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *log c.httpServer = server } - if config.DNSAddr != "" { + if config.Ports.DNS > 0 { + dnsAddr, err := config.ClientListener(config.Ports.DNS) + if err != nil { + c.Ui.Error(fmt.Sprintf("Invalid DNS bind address: %s", err)) + return err + } + server, err := NewDNSServer(agent, logOutput, config.Domain, - config.DNSAddr, config.DNSRecursor) + dnsAddr.String(), config.DNSRecursor) if err != nil { agent.Shutdown() c.Ui.Error(fmt.Sprintf("Error starting dns server: %s", err)) @@ -273,14 +286,13 @@ func (c *Command) Run(args []string) int { c.agent.StartSync() c.Ui.Output("Consul agent running!") - c.Ui.Info(fmt.Sprintf(" Node name: '%s'", config.NodeName)) - c.Ui.Info(fmt.Sprintf(" Datacenter: '%s'", config.Datacenter)) - c.Ui.Info(fmt.Sprintf("Advertise addr: '%s'", config.AdvertiseAddr)) - c.Ui.Info(fmt.Sprintf(" RPC addr: '%s'", config.RPCAddr)) - c.Ui.Info(fmt.Sprintf(" HTTP addr: '%s'", config.HTTPAddr)) - c.Ui.Info(fmt.Sprintf(" DNS addr: '%s'", config.DNSAddr)) - c.Ui.Info(fmt.Sprintf(" Encrypted: %#v", config.EncryptKey != "")) - c.Ui.Info(fmt.Sprintf(" Server: %v (bootstrap: %v)", config.Server, config.Bootstrap)) + c.Ui.Info(fmt.Sprintf(" Node name: '%s'", config.NodeName)) + c.Ui.Info(fmt.Sprintf("Datacenter: '%s'", config.Datacenter)) + if config.Server { + c.Ui.Info(fmt.Sprintf(" Server: %v (bootstrap: %v)", config.Server, config.Bootstrap)) + } else { + c.Ui.Info(fmt.Sprintf(" Server: %v", config.Server)) + } // Enable log streaming c.Ui.Info("") @@ -419,104 +431,27 @@ func (c *Command) Help() string { Usage: consul agent [options] Starts the Consul agent and runs until an interrupt is received. The - agent represents a single node in a cluster. An agent can also serve - as a server by configuraiton. + agent represents a single node in a cluster. Options: - -rpc-addr=127.0.0.1:8400 Address to bind the RPC listener. + -bootstrap Sets server to bootstrap mode + -bind=0.0.0.0 Sets the bind address for cluster communication + -client=127.0.0.1 Sets the address to bind for client access. + This includes RPC, DNS and HTTP + -config-file=foo Path to a JSON file to read configuration from. + This can be specified multiple times. + -config-dir=foo Path to a directory to read configuration files + from. This will read every file ending in ".json" + as configuration in this directory in alphabetical + order. + -data-dir=path Path to a data directory to store agent state + -dc=east-aws Datacenter of the agent + -log-level=info Log level of the agent. + -node=hostname Name of this node. Must be unique in the cluster + -protocol=N Sets the protocol version. Defaults to latest. + -server Switches agent to server mode. - -serf-bind - The address that the underlying Serf library will bind to. - This is an IP address that should be reachable by all other nodes in the cluster. - By default this is "0.0.0.0", meaning Consul will use the first available private - IP address. Consul uses both TCP and UDP and use the same port for both, so if you - have any firewalls be sure to allow both protocols. - - -server-addr - The address that the agent will bind to for handling RPC calls - if running in server mode. This does not affect clients running in client mode. - By default this is "0.0.0.0:8300". This port is used for TCP communications so any - firewalls must be configured to allow this. - - -advertise - The advertise flag is used to change the address that we - advertise to other nodes in the cluster. By default, the "-serf-bind" address is - advertised. However, in some cases (specifically NAT traversal), there may - be a routable address that cannot be bound to. This flag enables gossiping - a different address to support this. If this address is not routable, the node - will be in a constant flapping state, as other nodes will treat the non-routability - as a failure. - - -config-file - A configuration file to load. For more information on - the format of this file, read the "Configuration Files" section below. - This option can be specified multiple times to load multiple configuration - files. If it is specified multiple times, configuration files loaded later - will merge with configuration files loaded earlier, with the later values - overriding the earlier values. - - - config-dir - A directory of configuration files to load. Consul will - load all files in this directory ending in ".json" as configuration files - in alphabetical order. For more information on the format of the configuration - files, see the "Configuration Files" section below. - - -encrypt - Specifies the secret key to use for encryption of Consul - network traffic. This key must be 16-bytes that are base64 encoded. The - easiest way to create an encryption key is to use "consul keygen". All - nodes within a cluster must share the same encryption key to communicate. - - -log-level - The level of logging to show after the Consul agent has - started. This defaults to "info". The available log levels are "trace", - "debug", "info", "warn", "err". This is the log level that will be shown - for the agent output, but note you can always connect via "consul monitor" - to an agent at any log level. The log level can be changed during a - config reload. - - -node - The name of this node in the cluster. This must be unique within - the cluster. By default this is the hostname of the machine. - - -rpc-addr - The address that Consul will bind to for the agent's RPC server. - By default this is "127.0.0.1:8400", allowing only loopback connections. - The RPC address is used by other Consul commands, such as "consul members", - in order to query a running Consul agent. It is also used by other applications - to control Consul using it's [RPC protocol](/docs/agent/rpc.html). - - -data - This flag provides a data directory for the agent to store state. - This is required for all agents. The directory should be durable across reboots. - This is especially critical for agents that are running in server mode, as they - must be able to persist the cluster state. - - -dc - This flag controls the datacenter the agent is running in. If not provided - it defaults to "dc1". Consul has first class support for multiple data centers but - it relies on proper configuration. Nodes in the same datacenter should be on a single - LAN. - - -recursor - This flag provides an address of an upstream DNS server that is used to - recursively resolve queries if they are not inside the service domain for consul. For example, - a node can use Consul directly as a DNS server, and if the record is outside of the "consul." domain, - the query will be resolved upstream using this server. - - -http-addr - This flag controls the address the agent listens on for HTTP requests. - By default it is bound to "127.0.0.1:8500". This port must allow for TCP traffic. - - -dns-addr - This flag controls the address the agent listens on for DNS requests. - By default it is bound to "127.0.0.1:8600". This port must allow for UDP and TCP traffic. - - -server - This flag is used to control if an agent is in server or client mode. When provided, - an agent will act as a Consul server. Each Consul cluster must have at least one server, and ideally - no more than 5 *per* datacenter. All servers participate in the Raft consensus algorithm, to ensure that - transactions occur in a consistent, linearlizable manner. Transactions modify cluster state, which - is maintained on all server nodes to ensure availability in the case of node failure. Server nodes also - participate in a WAN gossip pool with server nodes in other datacenters. Servers act as gateways - to other datacenters and forward traffic as appropriate. - - -bootstrap - This flag is used to control if a server is in "bootstrap" mode. It is important that - no more than one server *per* datacenter be running in this mode. The initial server **must** be in bootstrap - mode. Technically, a server in boostrap mode is allowed to self-elect as the Raft leader. It is important - that only a single node is in this mode, because otherwise consistency cannot be guarenteed if multiple - nodes are able to self-elect. Once there are multiple servers in a datacenter, it is generally a good idea - to disable bootstrap mode on all of them. - - -statsite - This flag provides the address of a statsite instance. If provided Consul will stream - various telemetry information to that instance for aggregation. This can be used to capture various - runtime information. -` + ` return strings.TrimSpace(helpText) } diff --git a/command/agent/config.go b/command/agent/config.go index 18edee0d53..004d25fd43 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/consul/consul" "github.com/mitchellh/mapstructure" "io" + "net" "os" "path/filepath" "sort" @@ -14,28 +15,37 @@ import ( "time" ) +// Ports is used to simplify the configuration by +// providing default ports, and allowing the addresses +// to only be specified once +type PortConfig struct { + DNS int // DNS Query interface + HTTP int // HTTP API + RPC int // CLI RPC + SerfLan int `mapstructure:"serf_lan"` // LAN gossip (Client + Server) + SerfWan int `mapstructure:"serv_wan"` // WAN gossip (Server onlyg) + Server int // Server internal RPC +} + // Config is the configuration that can be set for an Agent. // Some of this is configurable as CLI flags, but most must // be set using a configuration file. type Config struct { - // AEInterval controls the anti-entropy interval. This is how often - // the agent attempts to reconcile it's local state with the server' - // representation of our state. Defaults to every 60s. - AEInterval time.Duration `mapstructure:"-"` - // Bootstrap is used to bring up the first Consul server, and // permits that node to elect itself leader Bootstrap bool `mapstructure:"bootstrap"` + // Server controls if this agent acts like a Consul server, + // or merely as a client. Servers have more state, take part + // in leader election, etc. + Server bool `mapstructure:"server"` + // Datacenter is the datacenter this node is in. Defaults to dc1 Datacenter string `mapstructure:"datacenter"` // DataDir is the directory to store our state in DataDir string `mapstructure:"data_dir"` - // DNSAddr is the address of the DNS server for the agent - DNSAddr string `mapstructure:"dns_addr"` - // DNSRecursor can be set to allow the DNS server to recursively // resolve non-consul domains DNSRecursor string `mapstructure:"recursor"` @@ -46,44 +56,28 @@ type Config struct { // Encryption key to use for the Serf communication EncryptKey string `mapstructure:"encrypt"` - // HTTP interface address - HTTPAddr string `mapstructure:"http_addr"` - // LogLevel is the level of the logs to putout LogLevel string `mapstructure:"log_level"` // Node name is the name we use to advertise. Defaults to hostname. NodeName string `mapstructure:"node_name"` - // RPCAddr is the address and port to listen on for the - // agent's RPC interface. - RPCAddr string `mapstructure:"rpc_addr"` + // ClientAddr is used to control the address we bind to for + // client services (DNS, HTTP, RPC) + ClientAddr string `mapstructure:"client_addr"` - // BindAddr is the address that Consul's RPC and Serf's will - // bind to. This address should be routable by all other hosts. - SerfBindAddr string `mapstructure:"serf_bind_addr"` - - // SerfLanPort is the port we use for the lan-local serf cluster - // This is used for all nodes. - SerfLanPort int `mapstructure:"serf_lan_port"` - - // SerfWanPort is the port we use for the wan serf cluster. - // This is only for the Consul servers - SerfWanPort int `mapstructure:"serf_wan_port"` - - // ServerAddr is the address we use for Consul server communication. - // Defaults to 0.0.0.0:8300 - ServerAddr string `mapstructure:"server_addr"` + // BindAddr is used to control the address we bind to. + // If not specified, the first private IP we find is used. + // This controls the address we use for cluster facing + // services (Gossip, Server RPC) + BindAddr string `mapstructure:"bind_addr"` // AdvertiseAddr is the address we use for advertising our Serf, - // and Consul RPC IP. If not specified, the first private IP we - // find is used. + // and Consul RPC IP. If not specified, bind address is used. AdvertiseAddr string `mapstructure:"advertise_addr"` - // Server controls if this agent acts like a Consul server, - // or merely as a client. Servers have more state, take part - // in leader election, etc. - Server bool `mapstructure:"server"` + // Port configurations + Ports PortConfig // LeaveOnTerm controls if Serf does a graceful leave when receiving // the TERM signal. Defaults false. This can be changed on reload. @@ -125,6 +119,11 @@ type Config struct { // Must be provided to serve TLS connections. KeyFile string `mapstructure:"key_file"` + // AEInterval controls the anti-entropy interval. This is how often + // the agent attempts to reconcile it's local state with the server' + // representation of our state. Defaults to every 60s. + AEInterval time.Duration `mapstructure:"-"` + // Checks holds the provided check definitions Checks []*CheckDefinition `mapstructure:"-"` @@ -140,17 +139,22 @@ type dirEnts []os.FileInfo // DefaultConfig is used to return a sane default configuration func DefaultConfig() *Config { return &Config{ - AEInterval: time.Minute, - Datacenter: consul.DefaultDC, - DNSAddr: "127.0.0.1:8600", - Domain: "consul.", - HTTPAddr: "127.0.0.1:8500", - LogLevel: "INFO", - RPCAddr: "127.0.0.1:8400", - SerfLanPort: consul.DefaultLANSerfPort, - SerfWanPort: consul.DefaultWANSerfPort, - Server: false, - Protocol: consul.ProtocolVersionMax, + Bootstrap: false, + Server: false, + Datacenter: consul.DefaultDC, + Domain: "consul.", + LogLevel: "INFO", + ClientAddr: "127.0.0.1", + Ports: PortConfig{ + DNS: 8600, + HTTP: 8500, + RPC: 8400, + SerfLan: consul.DefaultLANSerfPort, + SerfWan: consul.DefaultWANSerfPort, + Server: 8300, + }, + Protocol: consul.ProtocolVersionMax, + AEInterval: time.Minute, } } @@ -159,6 +163,16 @@ func (c *Config) EncryptBytes() ([]byte, error) { return base64.StdEncoding.DecodeString(c.EncryptKey) } +// ClientListener is used to format a listener for a +// port on a ClientAddr +func (c *Config) ClientListener(port int) (*net.TCPAddr, error) { + ip := net.ParseIP(c.ClientAddr) + if ip == nil { + return nil, fmt.Errorf("Failed to parse IP: %v", c.ClientAddr) + } + return &net.TCPAddr{IP: ip, Port: port}, nil +} + // DecodeConfig reads the configuration from the given reader in JSON // format and decodes it into a proper Config structure. func DecodeConfig(r io.Reader) (*Config, error) { @@ -303,9 +317,6 @@ func MergeConfig(a, b *Config) *Config { if b.DataDir != "" { result.DataDir = b.DataDir } - if b.DNSAddr != "" { - result.DNSAddr = b.DNSAddr - } if b.DNSRecursor != "" { result.DNSRecursor = b.DNSRecursor } @@ -315,9 +326,6 @@ func MergeConfig(a, b *Config) *Config { if b.EncryptKey != "" { result.EncryptKey = b.EncryptKey } - if b.HTTPAddr != "" { - result.HTTPAddr = b.HTTPAddr - } if b.LogLevel != "" { result.LogLevel = b.LogLevel } @@ -327,20 +335,11 @@ func MergeConfig(a, b *Config) *Config { if b.NodeName != "" { result.NodeName = b.NodeName } - if b.RPCAddr != "" { - result.RPCAddr = b.RPCAddr + if b.ClientAddr != "" { + result.ClientAddr = b.ClientAddr } - if b.SerfBindAddr != "" { - result.SerfBindAddr = b.SerfBindAddr - } - if b.SerfLanPort > 0 { - result.SerfLanPort = b.SerfLanPort - } - if b.SerfWanPort > 0 { - result.SerfWanPort = b.SerfWanPort - } - if b.ServerAddr != "" { - result.ServerAddr = b.ServerAddr + if b.BindAddr != "" { + result.BindAddr = b.BindAddr } if b.AdvertiseAddr != "" { result.AdvertiseAddr = b.AdvertiseAddr @@ -378,6 +377,24 @@ func MergeConfig(a, b *Config) *Config { if b.Services != nil { result.Services = append(result.Services, b.Services...) } + if b.Ports.DNS != 0 { + result.Ports.DNS = b.Ports.DNS + } + if b.Ports.HTTP != 0 { + result.Ports.HTTP = b.Ports.HTTP + } + if b.Ports.RPC != 0 { + result.Ports.RPC = b.Ports.RPC + } + if b.Ports.SerfLan != 0 { + result.Ports.SerfLan = b.Ports.SerfLan + } + if b.Ports.SerfWan != 0 { + result.Ports.SerfWan = b.Ports.SerfWan + } + if b.Ports.Server != 0 { + result.Ports.Server = b.Ports.Server + } return &result }