github.com/hspak/nomad@v0.7.2-0.20180309000617-bc4ae22a39a5/command/agent/agent.go (about) 1 package agent 2 3 import ( 4 "fmt" 5 "io" 6 "log" 7 "net" 8 "os" 9 "path/filepath" 10 "runtime" 11 "strings" 12 "sync" 13 "sync/atomic" 14 "time" 15 16 metrics "github.com/armon/go-metrics" 17 "github.com/hashicorp/consul/api" 18 version "github.com/hashicorp/go-version" 19 "github.com/hashicorp/nomad/client" 20 clientconfig "github.com/hashicorp/nomad/client/config" 21 "github.com/hashicorp/nomad/command/agent/consul" 22 "github.com/hashicorp/nomad/nomad" 23 "github.com/hashicorp/nomad/nomad/structs" 24 "github.com/hashicorp/nomad/nomad/structs/config" 25 "github.com/hashicorp/raft" 26 ) 27 28 const ( 29 agentHttpCheckInterval = 10 * time.Second 30 agentHttpCheckTimeout = 5 * time.Second 31 serverRpcCheckInterval = 10 * time.Second 32 serverRpcCheckTimeout = 3 * time.Second 33 serverSerfCheckInterval = 10 * time.Second 34 serverSerfCheckTimeout = 3 * time.Second 35 36 // roles used in identifying Consul entries for Nomad agents 37 consulRoleServer = "server" 38 consulRoleClient = "client" 39 ) 40 41 // Agent is a long running daemon that is used to run both 42 // clients and servers. Servers are responsible for managing 43 // state and making scheduling decisions. Clients can be 44 // scheduled to, and are responsible for interfacing with 45 // servers to run allocations. 46 type Agent struct { 47 config *Config 48 configLock sync.Mutex 49 50 logger *log.Logger 51 logOutput io.Writer 52 53 // consulService is Nomad's custom Consul client for managing services 54 // and checks. 55 consulService *consul.ServiceClient 56 57 // consulCatalog is the subset of Consul's Catalog API Nomad uses. 58 consulCatalog consul.CatalogAPI 59 60 // consulSupportsTLSSkipVerify flags whether or not Nomad can register 61 // checks with TLSSkipVerify 62 consulSupportsTLSSkipVerify bool 63 64 client *client.Client 65 66 server *nomad.Server 67 68 shutdown bool 69 shutdownCh chan struct{} 70 shutdownLock sync.Mutex 71 72 InmemSink *metrics.InmemSink 73 } 74 75 // NewAgent is used to create a new agent with the given configuration 76 func NewAgent(config *Config, logOutput io.Writer, inmem *metrics.InmemSink) (*Agent, error) { 77 a := &Agent{ 78 config: config, 79 logger: log.New(logOutput, "", log.LstdFlags|log.Lmicroseconds), 80 logOutput: logOutput, 81 shutdownCh: make(chan struct{}), 82 InmemSink: inmem, 83 } 84 85 if err := a.setupConsul(config.Consul); err != nil { 86 return nil, fmt.Errorf("Failed to initialize Consul client: %v", err) 87 } 88 if err := a.setupServer(); err != nil { 89 return nil, err 90 } 91 if err := a.setupClient(); err != nil { 92 return nil, err 93 } 94 if a.client == nil && a.server == nil { 95 return nil, fmt.Errorf("must have at least client or server mode enabled") 96 } 97 98 return a, nil 99 } 100 101 // convertServerConfig takes an agent config and log output and returns a Nomad 102 // Config. 103 func convertServerConfig(agentConfig *Config, logOutput io.Writer) (*nomad.Config, error) { 104 conf := agentConfig.NomadConfig 105 if conf == nil { 106 conf = nomad.DefaultConfig() 107 } 108 conf.LogOutput = logOutput 109 conf.DevMode = agentConfig.DevMode 110 conf.Build = agentConfig.Version.VersionNumber() 111 if agentConfig.Region != "" { 112 conf.Region = agentConfig.Region 113 } 114 115 // Set the Authoritative Region if set, otherwise default to 116 // the same as the local region. 117 if agentConfig.Server.AuthoritativeRegion != "" { 118 conf.AuthoritativeRegion = agentConfig.Server.AuthoritativeRegion 119 } else if agentConfig.Region != "" { 120 conf.AuthoritativeRegion = agentConfig.Region 121 } 122 123 if agentConfig.Datacenter != "" { 124 conf.Datacenter = agentConfig.Datacenter 125 } 126 if agentConfig.NodeName != "" { 127 conf.NodeName = agentConfig.NodeName 128 } 129 if agentConfig.Server.BootstrapExpect > 0 { 130 if agentConfig.Server.BootstrapExpect == 1 { 131 conf.Bootstrap = true 132 } else { 133 atomic.StoreInt32(&conf.BootstrapExpect, int32(agentConfig.Server.BootstrapExpect)) 134 } 135 } 136 if agentConfig.DataDir != "" { 137 conf.DataDir = filepath.Join(agentConfig.DataDir, "server") 138 } 139 if agentConfig.Server.DataDir != "" { 140 conf.DataDir = agentConfig.Server.DataDir 141 } 142 if agentConfig.Server.ProtocolVersion != 0 { 143 conf.ProtocolVersion = uint8(agentConfig.Server.ProtocolVersion) 144 } 145 if agentConfig.Server.RaftProtocol != 0 { 146 conf.RaftConfig.ProtocolVersion = raft.ProtocolVersion(agentConfig.Server.RaftProtocol) 147 } 148 if agentConfig.Server.NumSchedulers != 0 { 149 conf.NumSchedulers = agentConfig.Server.NumSchedulers 150 } 151 if len(agentConfig.Server.EnabledSchedulers) != 0 { 152 conf.EnabledSchedulers = agentConfig.Server.EnabledSchedulers 153 } 154 if agentConfig.ACL.Enabled { 155 conf.ACLEnabled = true 156 } 157 if agentConfig.ACL.ReplicationToken != "" { 158 conf.ReplicationToken = agentConfig.ACL.ReplicationToken 159 } 160 if agentConfig.Sentinel != nil { 161 conf.SentinelConfig = agentConfig.Sentinel 162 } 163 if agentConfig.Server.NonVotingServer { 164 conf.NonVoter = true 165 } 166 if agentConfig.Server.RedundancyZone != "" { 167 conf.RedundancyZone = agentConfig.Server.RedundancyZone 168 } 169 if agentConfig.Server.UpgradeVersion != "" { 170 conf.UpgradeVersion = agentConfig.Server.UpgradeVersion 171 } 172 if agentConfig.Autopilot != nil { 173 if agentConfig.Autopilot.CleanupDeadServers != nil { 174 conf.AutopilotConfig.CleanupDeadServers = *agentConfig.Autopilot.CleanupDeadServers 175 } 176 if agentConfig.Autopilot.ServerStabilizationTime != 0 { 177 conf.AutopilotConfig.ServerStabilizationTime = agentConfig.Autopilot.ServerStabilizationTime 178 } 179 if agentConfig.Autopilot.LastContactThreshold != 0 { 180 conf.AutopilotConfig.LastContactThreshold = agentConfig.Autopilot.LastContactThreshold 181 } 182 if agentConfig.Autopilot.MaxTrailingLogs != 0 { 183 conf.AutopilotConfig.MaxTrailingLogs = uint64(agentConfig.Autopilot.MaxTrailingLogs) 184 } 185 if agentConfig.Autopilot.EnableRedundancyZones != nil { 186 conf.AutopilotConfig.EnableRedundancyZones = *agentConfig.Autopilot.EnableRedundancyZones 187 } 188 if agentConfig.Autopilot.DisableUpgradeMigration != nil { 189 conf.AutopilotConfig.DisableUpgradeMigration = *agentConfig.Autopilot.DisableUpgradeMigration 190 } 191 if agentConfig.Autopilot.EnableCustomUpgrades != nil { 192 conf.AutopilotConfig.EnableCustomUpgrades = *agentConfig.Autopilot.EnableCustomUpgrades 193 } 194 } 195 196 // Set up the bind addresses 197 rpcAddr, err := net.ResolveTCPAddr("tcp", agentConfig.normalizedAddrs.RPC) 198 if err != nil { 199 return nil, fmt.Errorf("Failed to parse RPC address %q: %v", agentConfig.normalizedAddrs.RPC, err) 200 } 201 serfAddr, err := net.ResolveTCPAddr("tcp", agentConfig.normalizedAddrs.Serf) 202 if err != nil { 203 return nil, fmt.Errorf("Failed to parse Serf address %q: %v", agentConfig.normalizedAddrs.Serf, err) 204 } 205 conf.RPCAddr.Port = rpcAddr.Port 206 conf.RPCAddr.IP = rpcAddr.IP 207 conf.SerfConfig.MemberlistConfig.BindPort = serfAddr.Port 208 conf.SerfConfig.MemberlistConfig.BindAddr = serfAddr.IP.String() 209 210 // Set up the advertise addresses 211 rpcAddr, err = net.ResolveTCPAddr("tcp", agentConfig.AdvertiseAddrs.RPC) 212 if err != nil { 213 return nil, fmt.Errorf("Failed to parse RPC advertise address %q: %v", agentConfig.AdvertiseAddrs.RPC, err) 214 } 215 serfAddr, err = net.ResolveTCPAddr("tcp", agentConfig.AdvertiseAddrs.Serf) 216 if err != nil { 217 return nil, fmt.Errorf("Failed to parse Serf advertise address %q: %v", agentConfig.AdvertiseAddrs.Serf, err) 218 } 219 conf.RPCAdvertise = rpcAddr 220 conf.SerfConfig.MemberlistConfig.AdvertiseAddr = serfAddr.IP.String() 221 conf.SerfConfig.MemberlistConfig.AdvertisePort = serfAddr.Port 222 223 // Set up gc threshold and heartbeat grace period 224 if gcThreshold := agentConfig.Server.NodeGCThreshold; gcThreshold != "" { 225 dur, err := time.ParseDuration(gcThreshold) 226 if err != nil { 227 return nil, err 228 } 229 conf.NodeGCThreshold = dur 230 } 231 if gcThreshold := agentConfig.Server.JobGCThreshold; gcThreshold != "" { 232 dur, err := time.ParseDuration(gcThreshold) 233 if err != nil { 234 return nil, err 235 } 236 conf.JobGCThreshold = dur 237 } 238 if gcThreshold := agentConfig.Server.EvalGCThreshold; gcThreshold != "" { 239 dur, err := time.ParseDuration(gcThreshold) 240 if err != nil { 241 return nil, err 242 } 243 conf.EvalGCThreshold = dur 244 } 245 if gcThreshold := agentConfig.Server.DeploymentGCThreshold; gcThreshold != "" { 246 dur, err := time.ParseDuration(gcThreshold) 247 if err != nil { 248 return nil, err 249 } 250 conf.DeploymentGCThreshold = dur 251 } 252 253 if heartbeatGrace := agentConfig.Server.HeartbeatGrace; heartbeatGrace != 0 { 254 conf.HeartbeatGrace = heartbeatGrace 255 } 256 if min := agentConfig.Server.MinHeartbeatTTL; min != 0 { 257 conf.MinHeartbeatTTL = min 258 } 259 if maxHPS := agentConfig.Server.MaxHeartbeatsPerSecond; maxHPS != 0 { 260 conf.MaxHeartbeatsPerSecond = maxHPS 261 } 262 263 if *agentConfig.Consul.AutoAdvertise && agentConfig.Consul.ServerServiceName == "" { 264 return nil, fmt.Errorf("server_service_name must be set when auto_advertise is enabled") 265 } 266 267 // Add the Consul and Vault configs 268 conf.ConsulConfig = agentConfig.Consul 269 conf.VaultConfig = agentConfig.Vault 270 271 // Set the TLS config 272 conf.TLSConfig = agentConfig.TLSConfig 273 274 // Setup telemetry related config 275 conf.StatsCollectionInterval = agentConfig.Telemetry.collectionInterval 276 conf.DisableTaggedMetrics = agentConfig.Telemetry.DisableTaggedMetrics 277 conf.BackwardsCompatibleMetrics = agentConfig.Telemetry.BackwardsCompatibleMetrics 278 279 return conf, nil 280 } 281 282 // serverConfig is used to generate a new server configuration struct 283 // for initializing a nomad server. 284 func (a *Agent) serverConfig() (*nomad.Config, error) { 285 return convertServerConfig(a.config, a.logOutput) 286 } 287 288 // clientConfig is used to generate a new client configuration struct 289 // for initializing a Nomad client. 290 func (a *Agent) clientConfig() (*clientconfig.Config, error) { 291 // Setup the configuration 292 conf := a.config.ClientConfig 293 if conf == nil { 294 conf = clientconfig.DefaultConfig() 295 } 296 if a.server != nil { 297 conf.RPCHandler = a.server 298 } 299 conf.LogOutput = a.logOutput 300 conf.LogLevel = a.config.LogLevel 301 conf.DevMode = a.config.DevMode 302 if a.config.Region != "" { 303 conf.Region = a.config.Region 304 } 305 if a.config.DataDir != "" { 306 conf.StateDir = filepath.Join(a.config.DataDir, "client") 307 conf.AllocDir = filepath.Join(a.config.DataDir, "alloc") 308 } 309 if a.config.Client.StateDir != "" { 310 conf.StateDir = a.config.Client.StateDir 311 } 312 if a.config.Client.AllocDir != "" { 313 conf.AllocDir = a.config.Client.AllocDir 314 } 315 conf.Servers = a.config.Client.Servers 316 if a.config.Client.NetworkInterface != "" { 317 conf.NetworkInterface = a.config.Client.NetworkInterface 318 } 319 conf.ChrootEnv = a.config.Client.ChrootEnv 320 conf.Options = a.config.Client.Options 321 // Logging deprecation messages about consul related configuration in client 322 // options 323 var invalidConsulKeys []string 324 for key := range conf.Options { 325 if strings.HasPrefix(key, "consul") { 326 invalidConsulKeys = append(invalidConsulKeys, fmt.Sprintf("options.%s", key)) 327 } 328 } 329 if len(invalidConsulKeys) > 0 { 330 a.logger.Printf("[WARN] agent: Invalid keys: %v", strings.Join(invalidConsulKeys, ",")) 331 a.logger.Printf(`Nomad client ignores consul related configuration in client options. 332 Please refer to the guide https://www.nomadproject.io/docs/agent/configuration/consul.html 333 to configure Nomad to work with Consul.`) 334 } 335 336 if a.config.Client.NetworkSpeed != 0 { 337 conf.NetworkSpeed = a.config.Client.NetworkSpeed 338 } 339 if a.config.Client.CpuCompute != 0 { 340 conf.CpuCompute = a.config.Client.CpuCompute 341 } 342 if a.config.Client.MaxKillTimeout != "" { 343 dur, err := time.ParseDuration(a.config.Client.MaxKillTimeout) 344 if err != nil { 345 return nil, fmt.Errorf("Error parsing max kill timeout: %s", err) 346 } 347 conf.MaxKillTimeout = dur 348 } 349 conf.ClientMaxPort = uint(a.config.Client.ClientMaxPort) 350 conf.ClientMinPort = uint(a.config.Client.ClientMinPort) 351 352 // Setup the node 353 conf.Node = new(structs.Node) 354 conf.Node.Datacenter = a.config.Datacenter 355 conf.Node.Name = a.config.NodeName 356 conf.Node.Meta = a.config.Client.Meta 357 conf.Node.NodeClass = a.config.Client.NodeClass 358 359 // Set up the HTTP advertise address 360 conf.Node.HTTPAddr = a.config.AdvertiseAddrs.HTTP 361 362 // Reserve resources on the node. 363 r := conf.Node.Reserved 364 if r == nil { 365 r = new(structs.Resources) 366 conf.Node.Reserved = r 367 } 368 r.CPU = a.config.Client.Reserved.CPU 369 r.MemoryMB = a.config.Client.Reserved.MemoryMB 370 r.DiskMB = a.config.Client.Reserved.DiskMB 371 r.IOPS = a.config.Client.Reserved.IOPS 372 conf.GloballyReservedPorts = a.config.Client.Reserved.ParsedReservedPorts 373 374 conf.Version = a.config.Version 375 376 if *a.config.Consul.AutoAdvertise && a.config.Consul.ClientServiceName == "" { 377 return nil, fmt.Errorf("client_service_name must be set when auto_advertise is enabled") 378 } 379 380 conf.ConsulConfig = a.config.Consul 381 conf.VaultConfig = a.config.Vault 382 383 // Set up Telemetry configuration 384 conf.StatsCollectionInterval = a.config.Telemetry.collectionInterval 385 conf.PublishNodeMetrics = a.config.Telemetry.PublishNodeMetrics 386 conf.PublishAllocationMetrics = a.config.Telemetry.PublishAllocationMetrics 387 conf.DisableTaggedMetrics = a.config.Telemetry.DisableTaggedMetrics 388 conf.BackwardsCompatibleMetrics = a.config.Telemetry.BackwardsCompatibleMetrics 389 390 // Set the TLS related configs 391 conf.TLSConfig = a.config.TLSConfig 392 conf.Node.TLSEnabled = conf.TLSConfig.EnableHTTP 393 394 // Set the GC related configs 395 conf.GCInterval = a.config.Client.GCInterval 396 conf.GCParallelDestroys = a.config.Client.GCParallelDestroys 397 conf.GCDiskUsageThreshold = a.config.Client.GCDiskUsageThreshold 398 conf.GCInodeUsageThreshold = a.config.Client.GCInodeUsageThreshold 399 conf.GCMaxAllocs = a.config.Client.GCMaxAllocs 400 if a.config.Client.NoHostUUID != nil { 401 conf.NoHostUUID = *a.config.Client.NoHostUUID 402 } else { 403 // Default no_host_uuid to true 404 conf.NoHostUUID = true 405 } 406 407 // Setup the ACLs 408 conf.ACLEnabled = a.config.ACL.Enabled 409 conf.ACLTokenTTL = a.config.ACL.TokenTTL 410 conf.ACLPolicyTTL = a.config.ACL.PolicyTTL 411 412 return conf, nil 413 } 414 415 // setupServer is used to setup the server if enabled 416 func (a *Agent) setupServer() error { 417 if !a.config.Server.Enabled { 418 return nil 419 } 420 421 // Setup the configuration 422 conf, err := a.serverConfig() 423 if err != nil { 424 return fmt.Errorf("server config setup failed: %s", err) 425 } 426 427 // Sets up the keyring for gossip encryption 428 if err := a.setupKeyrings(conf); err != nil { 429 return fmt.Errorf("failed to configure keyring: %v", err) 430 } 431 432 // Create the server 433 server, err := nomad.NewServer(conf, a.consulCatalog, a.logger) 434 if err != nil { 435 return fmt.Errorf("server setup failed: %v", err) 436 } 437 a.server = server 438 439 // Consul check addresses default to bind but can be toggled to use advertise 440 rpcCheckAddr := a.config.normalizedAddrs.RPC 441 serfCheckAddr := a.config.normalizedAddrs.Serf 442 if *a.config.Consul.ChecksUseAdvertise { 443 rpcCheckAddr = a.config.AdvertiseAddrs.RPC 444 serfCheckAddr = a.config.AdvertiseAddrs.Serf 445 } 446 447 // Create the Nomad Server services for Consul 448 if *a.config.Consul.AutoAdvertise { 449 httpServ := &structs.Service{ 450 Name: a.config.Consul.ServerServiceName, 451 PortLabel: a.config.AdvertiseAddrs.HTTP, 452 Tags: []string{consul.ServiceTagHTTP}, 453 } 454 const isServer = true 455 if check := a.agentHTTPCheck(isServer); check != nil { 456 httpServ.Checks = []*structs.ServiceCheck{check} 457 } 458 rpcServ := &structs.Service{ 459 Name: a.config.Consul.ServerServiceName, 460 PortLabel: a.config.AdvertiseAddrs.RPC, 461 Tags: []string{consul.ServiceTagRPC}, 462 Checks: []*structs.ServiceCheck{ 463 { 464 Name: "Nomad Server RPC Check", 465 Type: "tcp", 466 Interval: serverRpcCheckInterval, 467 Timeout: serverRpcCheckTimeout, 468 PortLabel: rpcCheckAddr, 469 }, 470 }, 471 } 472 serfServ := &structs.Service{ 473 Name: a.config.Consul.ServerServiceName, 474 PortLabel: a.config.AdvertiseAddrs.Serf, 475 Tags: []string{consul.ServiceTagSerf}, 476 Checks: []*structs.ServiceCheck{ 477 { 478 Name: "Nomad Server Serf Check", 479 Type: "tcp", 480 Interval: serverSerfCheckInterval, 481 Timeout: serverSerfCheckTimeout, 482 PortLabel: serfCheckAddr, 483 }, 484 }, 485 } 486 487 // Add the http port check if TLS isn't enabled 488 consulServices := []*structs.Service{ 489 rpcServ, 490 serfServ, 491 httpServ, 492 } 493 if err := a.consulService.RegisterAgent(consulRoleServer, consulServices); err != nil { 494 return err 495 } 496 } 497 498 return nil 499 } 500 501 // setupKeyrings is used to initialize and load keyrings during agent startup 502 func (a *Agent) setupKeyrings(config *nomad.Config) error { 503 file := filepath.Join(a.config.DataDir, serfKeyring) 504 505 if a.config.Server.EncryptKey == "" { 506 goto LOAD 507 } 508 if _, err := os.Stat(file); err != nil { 509 if err := initKeyring(file, a.config.Server.EncryptKey); err != nil { 510 return err 511 } 512 } 513 514 LOAD: 515 if _, err := os.Stat(file); err == nil { 516 config.SerfConfig.KeyringFile = file 517 } 518 if err := loadKeyringFile(config.SerfConfig); err != nil { 519 return err 520 } 521 // Success! 522 return nil 523 } 524 525 // setupClient is used to setup the client if enabled 526 func (a *Agent) setupClient() error { 527 if !a.config.Client.Enabled { 528 return nil 529 } 530 531 // Setup the configuration 532 conf, err := a.clientConfig() 533 if err != nil { 534 return fmt.Errorf("client setup failed: %v", err) 535 } 536 537 // Reserve some ports for the plugins if we are on Windows 538 if runtime.GOOS == "windows" { 539 if err := a.reservePortsForClient(conf); err != nil { 540 return err 541 } 542 } 543 544 client, err := client.NewClient(conf, a.consulCatalog, a.consulService, a.logger) 545 if err != nil { 546 return fmt.Errorf("client setup failed: %v", err) 547 } 548 a.client = client 549 550 // Create the Nomad Client services for Consul 551 if *a.config.Consul.AutoAdvertise { 552 httpServ := &structs.Service{ 553 Name: a.config.Consul.ClientServiceName, 554 PortLabel: a.config.AdvertiseAddrs.HTTP, 555 Tags: []string{consul.ServiceTagHTTP}, 556 } 557 const isServer = false 558 if check := a.agentHTTPCheck(isServer); check != nil { 559 httpServ.Checks = []*structs.ServiceCheck{check} 560 } 561 if err := a.consulService.RegisterAgent(consulRoleClient, []*structs.Service{httpServ}); err != nil { 562 return err 563 } 564 } 565 566 return nil 567 } 568 569 // agentHTTPCheck returns a health check for the agent's HTTP API if possible. 570 // If no HTTP health check can be supported nil is returned. 571 func (a *Agent) agentHTTPCheck(server bool) *structs.ServiceCheck { 572 // Resolve the http check address 573 httpCheckAddr := a.config.normalizedAddrs.HTTP 574 if *a.config.Consul.ChecksUseAdvertise { 575 httpCheckAddr = a.config.AdvertiseAddrs.HTTP 576 } 577 check := structs.ServiceCheck{ 578 Name: "Nomad Client HTTP Check", 579 Type: "http", 580 Path: "/v1/agent/health?type=client", 581 Protocol: "http", 582 Interval: agentHttpCheckInterval, 583 Timeout: agentHttpCheckTimeout, 584 PortLabel: httpCheckAddr, 585 } 586 // Switch to endpoint that doesn't require a leader for servers 587 if server { 588 check.Name = "Nomad Server HTTP Check" 589 check.Path = "/v1/agent/health?type=server" 590 } 591 if !a.config.TLSConfig.EnableHTTP { 592 // No HTTPS, return a plain http check 593 return &check 594 } 595 if !a.consulSupportsTLSSkipVerify { 596 a.logger.Printf("[WARN] agent: not registering Nomad HTTPS Health Check because it requires Consul>=0.7.2") 597 return nil 598 } 599 if a.config.TLSConfig.VerifyHTTPSClient { 600 a.logger.Printf("[WARN] agent: not registering Nomad HTTPS Health Check because verify_https_client enabled") 601 return nil 602 } 603 604 // HTTPS enabled; skip verification 605 check.Protocol = "https" 606 check.TLSSkipVerify = true 607 return &check 608 } 609 610 // reservePortsForClient reserves a range of ports for the client to use when 611 // it creates various plugins for log collection, executors, drivers, etc 612 func (a *Agent) reservePortsForClient(conf *clientconfig.Config) error { 613 // finding the device name for loopback 614 deviceName, addr, mask, err := a.findLoopbackDevice() 615 if err != nil { 616 return fmt.Errorf("error finding the device name for loopback: %v", err) 617 } 618 619 // seeing if the user has already reserved some resources on this device 620 var nr *structs.NetworkResource 621 if conf.Node.Reserved == nil { 622 conf.Node.Reserved = &structs.Resources{} 623 } 624 for _, n := range conf.Node.Reserved.Networks { 625 if n.Device == deviceName { 626 nr = n 627 } 628 } 629 // If the user hasn't already created the device, we create it 630 if nr == nil { 631 nr = &structs.NetworkResource{ 632 Device: deviceName, 633 IP: addr, 634 CIDR: mask, 635 ReservedPorts: make([]structs.Port, 0), 636 } 637 } 638 // appending the port ranges we want to use for the client to the list of 639 // reserved ports for this device 640 for i := conf.ClientMinPort; i <= conf.ClientMaxPort; i++ { 641 nr.ReservedPorts = append(nr.ReservedPorts, structs.Port{Label: fmt.Sprintf("plugin-%d", i), Value: int(i)}) 642 } 643 conf.Node.Reserved.Networks = append(conf.Node.Reserved.Networks, nr) 644 return nil 645 } 646 647 // findLoopbackDevice iterates through all the interfaces on a machine and 648 // returns the ip addr, mask of the loopback device 649 func (a *Agent) findLoopbackDevice() (string, string, string, error) { 650 var ifcs []net.Interface 651 var err error 652 ifcs, err = net.Interfaces() 653 if err != nil { 654 return "", "", "", err 655 } 656 for _, ifc := range ifcs { 657 addrs, err := ifc.Addrs() 658 if err != nil { 659 return "", "", "", err 660 } 661 for _, addr := range addrs { 662 var ip net.IP 663 switch v := addr.(type) { 664 case *net.IPNet: 665 ip = v.IP 666 case *net.IPAddr: 667 ip = v.IP 668 } 669 if ip.IsLoopback() { 670 if ip.To4() == nil { 671 continue 672 } 673 return ifc.Name, ip.String(), addr.String(), nil 674 } 675 } 676 } 677 678 return "", "", "", fmt.Errorf("no loopback devices with IPV4 addr found") 679 } 680 681 // Leave is used gracefully exit. Clients will inform servers 682 // of their departure so that allocations can be rescheduled. 683 func (a *Agent) Leave() error { 684 if a.client != nil { 685 if err := a.client.Leave(); err != nil { 686 a.logger.Printf("[ERR] agent: client leave failed: %v", err) 687 } 688 } 689 if a.server != nil { 690 if err := a.server.Leave(); err != nil { 691 a.logger.Printf("[ERR] agent: server leave failed: %v", err) 692 } 693 } 694 return nil 695 } 696 697 // Shutdown is used to terminate the agent. 698 func (a *Agent) Shutdown() error { 699 a.shutdownLock.Lock() 700 defer a.shutdownLock.Unlock() 701 702 if a.shutdown { 703 return nil 704 } 705 706 a.logger.Println("[INFO] agent: requesting shutdown") 707 if a.client != nil { 708 if err := a.client.Shutdown(); err != nil { 709 a.logger.Printf("[ERR] agent: client shutdown failed: %v", err) 710 } 711 } 712 if a.server != nil { 713 if err := a.server.Shutdown(); err != nil { 714 a.logger.Printf("[ERR] agent: server shutdown failed: %v", err) 715 } 716 } 717 718 if err := a.consulService.Shutdown(); err != nil { 719 a.logger.Printf("[ERR] agent: shutting down Consul client failed: %v", err) 720 } 721 722 a.logger.Println("[INFO] agent: shutdown complete") 723 a.shutdown = true 724 close(a.shutdownCh) 725 return nil 726 } 727 728 // RPC is used to make an RPC call to the Nomad servers 729 func (a *Agent) RPC(method string, args interface{}, reply interface{}) error { 730 if a.server != nil { 731 return a.server.RPC(method, args, reply) 732 } 733 return a.client.RPC(method, args, reply) 734 } 735 736 // Client returns the configured client or nil 737 func (a *Agent) Client() *client.Client { 738 return a.client 739 } 740 741 // Server returns the configured server or nil 742 func (a *Agent) Server() *nomad.Server { 743 return a.server 744 } 745 746 // Stats is used to return statistics for debugging and insight 747 // for various sub-systems 748 func (a *Agent) Stats() map[string]map[string]string { 749 stats := make(map[string]map[string]string) 750 if a.server != nil { 751 subStat := a.server.Stats() 752 for k, v := range subStat { 753 stats[k] = v 754 } 755 } 756 if a.client != nil { 757 subStat := a.client.Stats() 758 for k, v := range subStat { 759 stats[k] = v 760 } 761 } 762 return stats 763 } 764 765 // ShouldReload determines if we should reload the configuration and agent 766 // connections. If the TLS Configuration has not changed, we shouldn't reload. 767 func (a *Agent) ShouldReload(newConfig *Config) (bool, bool) { 768 a.configLock.Lock() 769 defer a.configLock.Unlock() 770 if a.config.TLSConfig.Equals(newConfig.TLSConfig) { 771 return false, false 772 } 773 774 return true, true // requires a reload of both agent and http server 775 } 776 777 // Reload handles configuration changes for the agent. Provides a method that 778 // is easier to unit test, as this action is invoked via SIGHUP. 779 func (a *Agent) Reload(newConfig *Config) error { 780 a.configLock.Lock() 781 defer a.configLock.Unlock() 782 783 if newConfig == nil || newConfig.TLSConfig == nil { 784 return fmt.Errorf("cannot reload agent with nil configuration") 785 } 786 787 // This is just a TLS configuration reload, we don't need to refresh 788 // existing network connections 789 if !a.config.TLSConfig.IsEmpty() && !newConfig.TLSConfig.IsEmpty() { 790 791 // Reload the certificates on the keyloader and on success store the 792 // updated TLS config. It is important to reuse the same keyloader 793 // as this allows us to dynamically reload configurations not only 794 // on the Agent but on the Server and Client too (they are 795 // referencing the same keyloader). 796 keyloader := a.config.TLSConfig.GetKeyLoader() 797 _, err := keyloader.LoadKeyPair(newConfig.TLSConfig.CertFile, newConfig.TLSConfig.KeyFile) 798 if err != nil { 799 return err 800 } 801 a.config.TLSConfig = newConfig.TLSConfig 802 a.config.TLSConfig.KeyLoader = keyloader 803 return nil 804 } 805 806 // Completely reload the agent's TLS configuration (moving from non-TLS to 807 // TLS, or vice versa) 808 // This does not handle errors in loading the new TLS configuration 809 a.config.TLSConfig = newConfig.TLSConfig.Copy() 810 811 if newConfig.TLSConfig.IsEmpty() { 812 a.logger.Println("[WARN] agent: Downgrading agent's existing TLS configuration to plaintext") 813 } else { 814 a.logger.Println("[INFO] agent: Upgrading from plaintext configuration to TLS") 815 } 816 817 return nil 818 } 819 820 // GetConfig creates a locked reference to the agent's config 821 func (a *Agent) GetConfig() *Config { 822 a.configLock.Lock() 823 defer a.configLock.Unlock() 824 825 return a.config 826 } 827 828 // setupConsul creates the Consul client and starts its main Run loop. 829 func (a *Agent) setupConsul(consulConfig *config.ConsulConfig) error { 830 apiConf, err := consulConfig.ApiConfig() 831 if err != nil { 832 return err 833 } 834 client, err := api.NewClient(apiConf) 835 if err != nil { 836 return err 837 } 838 839 // Determine version for TLSSkipVerify 840 if self, err := client.Agent().Self(); err == nil { 841 a.consulSupportsTLSSkipVerify = consulSupportsTLSSkipVerify(self) 842 } 843 844 // Create Consul Catalog client for service discovery. 845 a.consulCatalog = client.Catalog() 846 847 // Create Consul Service client for service advertisement and checks. 848 a.consulService = consul.NewServiceClient(client.Agent(), a.consulSupportsTLSSkipVerify, a.logger) 849 850 // Run the Consul service client's sync'ing main loop 851 go a.consulService.Run() 852 return nil 853 } 854 855 var consulTLSSkipVerifyMinVersion = version.Must(version.NewVersion("0.7.2")) 856 857 // consulSupportsTLSSkipVerify returns true if Consul supports TLSSkipVerify. 858 func consulSupportsTLSSkipVerify(self map[string]map[string]interface{}) bool { 859 member, ok := self["Member"] 860 if !ok { 861 return false 862 } 863 tagsI, ok := member["Tags"] 864 if !ok { 865 return false 866 } 867 tags, ok := tagsI.(map[string]interface{}) 868 if !ok { 869 return false 870 } 871 buildI, ok := tags["build"] 872 if !ok { 873 return false 874 } 875 build, ok := buildI.(string) 876 if !ok { 877 return false 878 } 879 parts := strings.SplitN(build, ":", 2) 880 if len(parts) != 2 { 881 return false 882 } 883 v, err := version.NewVersion(parts[0]) 884 if err != nil { 885 return false 886 } 887 if v.LessThan(consulTLSSkipVerifyMinVersion) { 888 return false 889 } 890 return true 891 }