Merge pull request #3812 from hashicorp/autopilot-config-change

Refactor redundancy_zone/upgrade_version out of client meta
This commit is contained in:
Kyle Havlovitz 2018-01-30 16:14:26 -08:00 committed by GitHub
commit cb2321353c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 364 additions and 331 deletions

View File

@ -2,6 +2,7 @@ package api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strconv"
@ -19,7 +20,7 @@ type AutopilotConfiguration struct {
// LastContactThreshold is the limit on the amount of time a server can go
// without leader contact before being considered unhealthy.
LastContactThreshold *ReadableDuration
LastContactThreshold time.Duration
// MaxTrailingLogs is the amount of entries in the Raft Log that a server can
// be behind before being considered unhealthy.
@ -28,20 +29,19 @@ type AutopilotConfiguration struct {
// ServerStabilizationTime is the minimum amount of time a server must be
// in a stable, healthy state before it can be added to the cluster. Only
// applicable with Raft protocol version 3 or higher.
ServerStabilizationTime *ReadableDuration
ServerStabilizationTime time.Duration
// (Enterprise-only) RedundancyZoneTag is the node tag to use for separating
// servers into zones for redundancy. If left blank, this feature will be disabled.
RedundancyZoneTag string
// (Enterprise-only) EnableRedundancyZones specifies whether to enable redundancy zones.
EnableRedundancyZones bool
// (Enterprise-only) DisableUpgradeMigration will disable Autopilot's upgrade migration
// strategy of waiting until enough newer-versioned servers have been added to the
// cluster before promoting them to voters.
DisableUpgradeMigration bool
// (Enterprise-only) UpgradeVersionTag is the node tag to use for version info when
// performing upgrade migrations. If left blank, the Nomad version will be used.
UpgradeVersionTag string
// (Enterprise-only) EnableCustomUpgrades specifies whether to enable using custom
// upgrade versions when performing migrations.
EnableCustomUpgrades bool
// CreateIndex holds the index corresponding the creation of this configuration.
// This is a read-only field.
@ -54,6 +54,45 @@ type AutopilotConfiguration struct {
ModifyIndex uint64
}
func (u *AutopilotConfiguration) MarshalJSON() ([]byte, error) {
type Alias AutopilotConfiguration
return json.Marshal(&struct {
LastContactThreshold string
ServerStabilizationTime string
*Alias
}{
LastContactThreshold: u.LastContactThreshold.String(),
ServerStabilizationTime: u.ServerStabilizationTime.String(),
Alias: (*Alias)(u),
})
}
func (u *AutopilotConfiguration) UnmarshalJSON(data []byte) error {
type Alias AutopilotConfiguration
aux := &struct {
LastContactThreshold string
ServerStabilizationTime string
*Alias
}{
Alias: (*Alias)(u),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
var err error
if aux.LastContactThreshold != "" {
if u.LastContactThreshold, err = time.ParseDuration(aux.LastContactThreshold); err != nil {
return err
}
}
if aux.ServerStabilizationTime != "" {
if u.ServerStabilizationTime, err = time.ParseDuration(aux.ServerStabilizationTime); err != nil {
return err
}
}
return nil
}
// ServerHealth is the health (from the leader's point of view) of a server.
type ServerHealth struct {
// ID is the raft ID of the server.
@ -75,7 +114,7 @@ type ServerHealth struct {
Leader bool
// LastContact is the time since this node's last contact with the leader.
LastContact *ReadableDuration
LastContact time.Duration
// LastTerm is the highest leader term this server has a record of in its Raft log.
LastTerm uint64
@ -94,6 +133,37 @@ type ServerHealth struct {
StableSince time.Time
}
func (u *ServerHealth) MarshalJSON() ([]byte, error) {
type Alias ServerHealth
return json.Marshal(&struct {
LastContact string
*Alias
}{
LastContact: u.LastContact.String(),
Alias: (*Alias)(u),
})
}
func (u *ServerHealth) UnmarshalJSON(data []byte) error {
type Alias ServerHealth
aux := &struct {
LastContact string
*Alias
}{
Alias: (*Alias)(u),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
var err error
if aux.LastContact != "" {
if u.LastContact, err = time.ParseDuration(aux.LastContact); err != nil {
return err
}
}
return nil
}
// OperatorHealthReply is a representation of the overall health of the cluster
type OperatorHealthReply struct {
// Healthy is true if all the servers in the cluster are healthy.
@ -107,46 +177,6 @@ type OperatorHealthReply struct {
Servers []ServerHealth
}
// ReadableDuration is a duration type that is serialized to JSON in human readable format.
type ReadableDuration time.Duration
func NewReadableDuration(dur time.Duration) *ReadableDuration {
d := ReadableDuration(dur)
return &d
}
func (d *ReadableDuration) String() string {
return d.Duration().String()
}
func (d *ReadableDuration) Duration() time.Duration {
if d == nil {
return time.Duration(0)
}
return time.Duration(*d)
}
func (d *ReadableDuration) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%s"`, d.Duration().String())), nil
}
func (d *ReadableDuration) UnmarshalJSON(raw []byte) error {
if d == nil {
return fmt.Errorf("cannot unmarshal to nil pointer")
}
str := string(raw)
if len(str) < 2 || str[0] != '"' || str[len(str)-1] != '"' {
return fmt.Errorf("must be enclosed with quotes: %s", str)
}
dur, err := time.ParseDuration(str[1 : len(str)-1])
if err != nil {
return err
}
*d = ReadableDuration(dur)
return nil
}
// AutopilotGetConfiguration is used to query the current Autopilot configuration.
func (op *Operator) AutopilotGetConfiguration(q *QueryOptions) (*AutopilotConfiguration, error) {
r, err := op.c.newRequest("GET", "/v1/operator/autopilot/configuration")

View File

@ -17,13 +17,17 @@ func TestAPI_OperatorAutopilotGetSetConfiguration(t *testing.T) {
defer s.Stop()
operator := c.Operator()
config, err := operator.AutopilotGetConfiguration(nil)
assert.Nil(err)
var config *AutopilotConfiguration
retry.Run(t, func(r *retry.R) {
var err error
config, err = operator.AutopilotGetConfiguration(nil)
r.Check(err)
})
assert.True(config.CleanupDeadServers)
// Change a config setting
newConf := &AutopilotConfiguration{CleanupDeadServers: false}
err = operator.AutopilotSetConfiguration(newConf, nil)
err := operator.AutopilotSetConfiguration(newConf, nil)
assert.Nil(err)
config, err = operator.AutopilotGetConfiguration(nil)

View File

@ -163,6 +163,12 @@ func convertServerConfig(agentConfig *Config, logOutput io.Writer) (*nomad.Confi
if agentConfig.Server.NonVotingServer {
conf.NonVoter = true
}
if agentConfig.Server.RedundancyZone != "" {
conf.RedundancyZone = agentConfig.Server.RedundancyZone
}
if agentConfig.Server.UpgradeVersion != "" {
conf.UpgradeVersion = agentConfig.Server.UpgradeVersion
}
if agentConfig.Autopilot != nil {
if agentConfig.Autopilot.CleanupDeadServers != nil {
conf.AutopilotConfig.CleanupDeadServers = *agentConfig.Autopilot.CleanupDeadServers
@ -176,14 +182,14 @@ func convertServerConfig(agentConfig *Config, logOutput io.Writer) (*nomad.Confi
if agentConfig.Autopilot.MaxTrailingLogs != 0 {
conf.AutopilotConfig.MaxTrailingLogs = uint64(agentConfig.Autopilot.MaxTrailingLogs)
}
if agentConfig.Autopilot.RedundancyZoneTag != "" {
conf.AutopilotConfig.RedundancyZoneTag = agentConfig.Autopilot.RedundancyZoneTag
if agentConfig.Autopilot.EnableRedundancyZones != nil {
conf.AutopilotConfig.EnableRedundancyZones = *agentConfig.Autopilot.EnableRedundancyZones
}
if agentConfig.Autopilot.DisableUpgradeMigration != nil {
conf.AutopilotConfig.DisableUpgradeMigration = *agentConfig.Autopilot.DisableUpgradeMigration
}
if agentConfig.Autopilot.UpgradeVersionTag != "" {
conf.AutopilotConfig.UpgradeVersionTag = agentConfig.Autopilot.UpgradeVersionTag
if agentConfig.Autopilot.EnableCustomUpgrades != nil {
conf.AutopilotConfig.EnableCustomUpgrades = *agentConfig.Autopilot.EnableCustomUpgrades
}
}

View File

@ -83,7 +83,9 @@ server {
retry_interval = "15s"
rejoin_after_leave = true
non_voting_server = true
encrypt = "abc"
redundancy_zone = "foo"
upgrade_version = "0.8.0"
encrypt = "abc"
}
acl {
enabled = true
@ -166,7 +168,7 @@ autopilot {
disable_upgrade_migration = true
last_contact_threshold = "12705s"
max_trailing_logs = 17849
redundancy_zone_tag = "foo"
enable_redundancy_zones = true
server_stabilization_time = "23057s"
upgrade_version_tag = "bar"
enable_custom_upgrades = true
}

View File

@ -330,10 +330,17 @@ type ServerConfig struct {
// true, we ignore the leave, and rejoin the cluster on start.
RejoinAfterLeave bool `mapstructure:"rejoin_after_leave"`
// NonVotingServer is whether this server will act as a non-voting member
// of the cluster to help provide read scalability. (Enterprise-only)
// (Enterprise-only) NonVotingServer is whether this server will act as a
// non-voting member of the cluster to help provide read scalability.
NonVotingServer bool `mapstructure:"non_voting_server"`
// (Enterprise-only) RedundancyZone is the redundancy zone to use for this server.
RedundancyZone string `mapstructure:"redundancy_zone"`
// (Enterprise-only) UpgradeVersion is the custom upgrade version to use when
// performing upgrade migrations.
UpgradeVersion string `mapstructure:"upgrade_version"`
// Encryption key to use for the Serf communication
EncryptKey string `mapstructure:"encrypt" json:"-"`
}
@ -1034,6 +1041,12 @@ func (a *ServerConfig) Merge(b *ServerConfig) *ServerConfig {
if b.NonVotingServer {
result.NonVotingServer = true
}
if b.RedundancyZone != "" {
result.RedundancyZone = b.RedundancyZone
}
if b.UpgradeVersion != "" {
result.UpgradeVersion = b.UpgradeVersion
}
if b.EncryptKey != "" {
result.EncryptKey = b.EncryptKey
}

View File

@ -9,6 +9,7 @@ import (
"time"
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/nomad/helper"
@ -536,6 +537,8 @@ func parseServer(result **ServerConfig, list *ast.ObjectList) error {
"encrypt",
"authoritative_region",
"non_voting_server",
"redundancy_zone",
"upgrade_version",
}
if err := helper.CheckHCLKeys(listVal, valid); err != nil {
return err
@ -559,6 +562,12 @@ func parseServer(result **ServerConfig, list *ast.ObjectList) error {
return err
}
if config.UpgradeVersion != "" {
if _, err := version.NewVersion(config.UpgradeVersion); err != nil {
return fmt.Errorf("error parsing upgrade_version: %v", err)
}
}
*result = &config
return nil
}
@ -865,9 +874,9 @@ func parseAutopilot(result **config.AutopilotConfig, list *ast.ObjectList) error
"server_stabilization_time",
"last_contact_threshold",
"max_trailing_logs",
"redundancy_zone_tag",
"enable_redundancy_zones",
"disable_upgrade_migration",
"upgrade_version_tag",
"enable_custom_upgrades",
}
if err := helper.CheckHCLKeys(listVal, valid); err != nil {

View File

@ -104,6 +104,8 @@ func TestConfig_Parse(t *testing.T) {
RejoinAfterLeave: true,
RetryMaxAttempts: 3,
NonVotingServer: true,
RedundancyZone: "foo",
UpgradeVersion: "0.8.0",
EncryptKey: "abc",
},
ACL: &ACLConfig{
@ -193,9 +195,9 @@ func TestConfig_Parse(t *testing.T) {
ServerStabilizationTime: 23057 * time.Second,
LastContactThreshold: 12705 * time.Second,
MaxTrailingLogs: 17849,
RedundancyZoneTag: "foo",
EnableRedundancyZones: &trueValue,
DisableUpgradeMigration: &trueValue,
UpgradeVersionTag: "bar",
EnableCustomUpgrades: &trueValue,
},
},
false,

View File

@ -107,6 +107,8 @@ func TestConfig_Merge(t *testing.T) {
HeartbeatGrace: 30 * time.Second,
MinHeartbeatTTL: 30 * time.Second,
MaxHeartbeatsPerSecond: 30.0,
RedundancyZone: "foo",
UpgradeVersion: "foo",
},
ACL: &ACLConfig{
Enabled: true,
@ -165,9 +167,9 @@ func TestConfig_Merge(t *testing.T) {
ServerStabilizationTime: 1 * time.Second,
LastContactThreshold: 1 * time.Second,
MaxTrailingLogs: 1,
RedundancyZoneTag: "1",
EnableRedundancyZones: &falseValue,
DisableUpgradeMigration: &falseValue,
UpgradeVersionTag: "1",
EnableCustomUpgrades: &falseValue,
},
}
@ -260,6 +262,8 @@ func TestConfig_Merge(t *testing.T) {
RetryInterval: "10s",
retryInterval: time.Second * 10,
NonVotingServer: true,
RedundancyZone: "bar",
UpgradeVersion: "bar",
},
ACL: &ACLConfig{
Enabled: true,
@ -328,9 +332,9 @@ func TestConfig_Merge(t *testing.T) {
ServerStabilizationTime: 2 * time.Second,
LastContactThreshold: 2 * time.Second,
MaxTrailingLogs: 2,
RedundancyZoneTag: "2",
EnableRedundancyZones: &trueValue,
DisableUpgradeMigration: &trueValue,
UpgradeVersionTag: "2",
EnableCustomUpgrades: &trueValue,
},
}

View File

@ -18,7 +18,6 @@ import (
assetfs "github.com/elazarl/go-bindata-assetfs"
"github.com/hashicorp/nomad/helper/tlsutil"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/mitchellh/mapstructure"
"github.com/rs/cors"
"github.com/ugorji/go/codec"
)
@ -346,24 +345,6 @@ func decodeBody(req *http.Request, out interface{}) error {
return dec.Decode(&out)
}
// decodeBodyFunc is used to decode a JSON request body invoking
// a given callback function
func decodeBodyFunc(req *http.Request, out interface{}, cb func(interface{}) error) error {
var raw interface{}
dec := json.NewDecoder(req.Body)
if err := dec.Decode(&raw); err != nil {
return err
}
// Invoke the callback prior to decode
if cb != nil {
if err := cb(raw); err != nil {
return err
}
}
return mapstructure.Decode(raw, out)
}
// setIndex is used to set the index response header
func setIndex(resp http.ResponseWriter, index uint64) {
resp.Header().Set("X-Nomad-Index", strconv.FormatUint(index, 10))

View File

@ -104,19 +104,19 @@ func (s *HTTPServer) OperatorAutopilotConfiguration(resp http.ResponseWriter, re
return nil, nil
}
var reply autopilot.Config
var reply structs.AutopilotConfig
if err := s.agent.RPC("Operator.AutopilotGetConfiguration", &args, &reply); err != nil {
return nil, err
}
out := api.AutopilotConfiguration{
CleanupDeadServers: reply.CleanupDeadServers,
LastContactThreshold: api.NewReadableDuration(reply.LastContactThreshold),
LastContactThreshold: reply.LastContactThreshold,
MaxTrailingLogs: reply.MaxTrailingLogs,
ServerStabilizationTime: api.NewReadableDuration(reply.ServerStabilizationTime),
RedundancyZoneTag: reply.RedundancyZoneTag,
ServerStabilizationTime: reply.ServerStabilizationTime,
EnableRedundancyZones: reply.EnableRedundancyZones,
DisableUpgradeMigration: reply.DisableUpgradeMigration,
UpgradeVersionTag: reply.UpgradeVersionTag,
EnableCustomUpgrades: reply.EnableCustomUpgrades,
CreateIndex: reply.CreateIndex,
ModifyIndex: reply.ModifyIndex,
}
@ -129,21 +129,20 @@ func (s *HTTPServer) OperatorAutopilotConfiguration(resp http.ResponseWriter, re
s.parseToken(req, &args.AuthToken)
var conf api.AutopilotConfiguration
durations := NewDurationFixer("lastcontactthreshold", "serverstabilizationtime")
if err := decodeBodyFunc(req, &conf, durations.FixupDurations); err != nil {
if err := decodeBody(req, &conf); err != nil {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(resp, "Error parsing autopilot config: %v", err)
return nil, nil
}
args.Config = autopilot.Config{
args.Config = structs.AutopilotConfig{
CleanupDeadServers: conf.CleanupDeadServers,
LastContactThreshold: conf.LastContactThreshold.Duration(),
LastContactThreshold: conf.LastContactThreshold,
MaxTrailingLogs: conf.MaxTrailingLogs,
ServerStabilizationTime: conf.ServerStabilizationTime.Duration(),
RedundancyZoneTag: conf.RedundancyZoneTag,
ServerStabilizationTime: conf.ServerStabilizationTime,
EnableRedundancyZones: conf.EnableRedundancyZones,
DisableUpgradeMigration: conf.DisableUpgradeMigration,
UpgradeVersionTag: conf.UpgradeVersionTag,
EnableCustomUpgrades: conf.EnableCustomUpgrades,
}
// Check for cas value
@ -210,7 +209,7 @@ func (s *HTTPServer) OperatorServerHealth(resp http.ResponseWriter, req *http.Re
Version: server.Version,
Leader: server.Leader,
SerfStatus: server.SerfStatus.String(),
LastContact: api.NewReadableDuration(server.LastContact),
LastContact: server.LastContact,
LastTerm: server.LastTerm,
LastIndex: server.LastIndex,
Healthy: server.Healthy,
@ -221,56 +220,3 @@ func (s *HTTPServer) OperatorServerHealth(resp http.ResponseWriter, req *http.Re
return out, nil
}
type durationFixer map[string]bool
func NewDurationFixer(fields ...string) durationFixer {
d := make(map[string]bool)
for _, field := range fields {
d[field] = true
}
return d
}
// FixupDurations is used to handle parsing any field names in the map to time.Durations
func (d durationFixer) FixupDurations(raw interface{}) error {
rawMap, ok := raw.(map[string]interface{})
if !ok {
return nil
}
for key, val := range rawMap {
switch val.(type) {
case map[string]interface{}:
if err := d.FixupDurations(val); err != nil {
return err
}
case []interface{}:
for _, v := range val.([]interface{}) {
if err := d.FixupDurations(v); err != nil {
return err
}
}
case []map[string]interface{}:
for _, v := range val.([]map[string]interface{}) {
if err := d.FixupDurations(v); err != nil {
return err
}
}
default:
if d[strings.ToLower(key)] {
// Convert a string value into an integer
if vStr, ok := val.(string); ok {
dur, err := time.ParseDuration(vStr)
if err != nil {
return err
}
rawMap[key] = dur
}
}
}
}
return nil
}

View File

@ -9,7 +9,6 @@ import (
"testing"
"time"
"github.com/hashicorp/consul/agent/consul/autopilot"
"github.com/hashicorp/consul/testutil/retry"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/nomad/structs"
@ -112,7 +111,7 @@ func TestOperator_AutopilotSetConfiguration(t *testing.T) {
t.Fatalf("err: %v", err)
}
if resp.Code != 200 {
t.Fatalf("bad code: %d", resp.Code)
t.Fatalf("bad code: %d, %q", resp.Code, resp.Body.String())
}
args := structs.GenericRequest{
@ -121,7 +120,7 @@ func TestOperator_AutopilotSetConfiguration(t *testing.T) {
},
}
var reply autopilot.Config
var reply structs.AutopilotConfig
if err := s.RPC("Operator.AutopilotGetConfiguration", &args, &reply); err != nil {
t.Fatalf("err: %v", err)
}
@ -150,7 +149,7 @@ func TestOperator_AutopilotCASConfiguration(t *testing.T) {
},
}
var reply autopilot.Config
var reply structs.AutopilotConfig
if err := s.RPC("Operator.AutopilotGetConfiguration", &args, &reply); err != nil {
t.Fatalf("err: %v", err)
}
@ -200,7 +199,6 @@ func TestOperator_AutopilotCASConfiguration(t *testing.T) {
}
func TestOperator_ServerHealth(t *testing.T) {
t.Parallel()
httpTest(t, func(c *Config) {
c.Server.RaftProtocol = 3
}, func(s *TestAgent) {
@ -259,47 +257,3 @@ func TestOperator_ServerHealth_Unhealthy(t *testing.T) {
})
})
}
func TestDurationFixer(t *testing.T) {
assert := assert.New(t)
obj := map[string]interface{}{
"key1": []map[string]interface{}{
{
"subkey1": "10s",
},
{
"subkey2": "5d",
},
},
"key2": map[string]interface{}{
"subkey3": "30s",
"subkey4": "20m",
},
"key3": "11s",
"key4": "49h",
}
expected := map[string]interface{}{
"key1": []map[string]interface{}{
{
"subkey1": 10 * time.Second,
},
{
"subkey2": "5d",
},
},
"key2": map[string]interface{}{
"subkey3": "30s",
"subkey4": 20 * time.Minute,
},
"key3": "11s",
"key4": 49 * time.Hour,
}
fixer := NewDurationFixer("key4", "subkey1", "subkey4")
if err := fixer.FixupDurations(obj); err != nil {
t.Fatal(err)
}
// Ensure we only processed the intended fieldnames
assert.Equal(obj, expected)
}

View File

@ -45,9 +45,9 @@ func (c *OperatorAutopilotGetCommand) Run(args []string) int {
c.Ui.Output(fmt.Sprintf("LastContactThreshold = %v", config.LastContactThreshold.String()))
c.Ui.Output(fmt.Sprintf("MaxTrailingLogs = %v", config.MaxTrailingLogs))
c.Ui.Output(fmt.Sprintf("ServerStabilizationTime = %v", config.ServerStabilizationTime.String()))
c.Ui.Output(fmt.Sprintf("RedundancyZoneTag = %q", config.RedundancyZoneTag))
c.Ui.Output(fmt.Sprintf("EnableRedundancyZones = %v", config.EnableRedundancyZones))
c.Ui.Output(fmt.Sprintf("DisableUpgradeMigration = %v", config.DisableUpgradeMigration))
c.Ui.Output(fmt.Sprintf("UpgradeVersionTag = %q", config.UpgradeVersionTag))
c.Ui.Output(fmt.Sprintf("EnableCustomUpgrades = %v", config.EnableCustomUpgrades))
return 0
}

View File

@ -3,10 +3,8 @@ package command
import (
"fmt"
"strings"
"time"
"github.com/hashicorp/consul/command/flags"
"github.com/hashicorp/nomad/api"
"github.com/posener/complete"
)
@ -21,9 +19,9 @@ func (c *OperatorAutopilotSetCommand) AutocompleteFlags() complete.Flags {
"-max-trailing-logs": complete.PredictAnything,
"-last-contact-threshold": complete.PredictAnything,
"-server-stabilization-time": complete.PredictAnything,
"-redundancy-zone-tag": complete.PredictAnything,
"-disable-upgrade-migration": complete.PredictAnything,
"-upgrade-version-tag": complete.PredictAnything,
"-enable-redundancy-zones": complete.PredictNothing,
"-disable-upgrade-migration": complete.PredictNothing,
"-enable-custom-upgrades": complete.PredictNothing,
})
}
@ -36,9 +34,9 @@ func (c *OperatorAutopilotSetCommand) Run(args []string) int {
var maxTrailingLogs flags.UintValue
var lastContactThreshold flags.DurationValue
var serverStabilizationTime flags.DurationValue
var redundancyZoneTag flags.StringValue
var enableRedundancyZones flags.BoolValue
var disableUpgradeMigration flags.BoolValue
var upgradeVersionTag flags.StringValue
var enableCustomUpgrades flags.BoolValue
f := c.Meta.FlagSet("autopilot", FlagSetClient)
f.Usage = func() { c.Ui.Output(c.Help()) }
@ -47,9 +45,9 @@ func (c *OperatorAutopilotSetCommand) Run(args []string) int {
f.Var(&maxTrailingLogs, "max-trailing-logs", "")
f.Var(&lastContactThreshold, "last-contact-threshold", "")
f.Var(&serverStabilizationTime, "server-stabilization-time", "")
f.Var(&redundancyZoneTag, "redundancy-zone-tag", "")
f.Var(&enableRedundancyZones, "enable-redundancy-zones", "")
f.Var(&disableUpgradeMigration, "disable-upgrade-migration", "")
f.Var(&upgradeVersionTag, "upgrade-version-tag", "")
f.Var(&enableCustomUpgrades, "enable-custom-upgrades", "")
if err := f.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to parse args: %v", err))
@ -73,21 +71,15 @@ func (c *OperatorAutopilotSetCommand) Run(args []string) int {
// Update the config values based on the set flags.
cleanupDeadServers.Merge(&conf.CleanupDeadServers)
redundancyZoneTag.Merge(&conf.RedundancyZoneTag)
enableRedundancyZones.Merge(&conf.EnableRedundancyZones)
disableUpgradeMigration.Merge(&conf.DisableUpgradeMigration)
upgradeVersionTag.Merge(&conf.UpgradeVersionTag)
enableRedundancyZones.Merge(&conf.EnableCustomUpgrades)
trailing := uint(conf.MaxTrailingLogs)
maxTrailingLogs.Merge(&trailing)
conf.MaxTrailingLogs = uint64(trailing)
last := time.Duration(*conf.LastContactThreshold)
lastContactThreshold.Merge(&last)
conf.LastContactThreshold = api.NewReadableDuration(last)
stablization := time.Duration(*conf.ServerStabilizationTime)
serverStabilizationTime.Merge(&stablization)
conf.ServerStabilizationTime = api.NewReadableDuration(stablization)
lastContactThreshold.Merge(&conf.LastContactThreshold)
serverStabilizationTime.Merge(&conf.ServerStabilizationTime)
// Check-and-set the new configuration.
result, err := operator.AutopilotCASConfiguration(conf, nil)

View File

@ -53,10 +53,10 @@ func TestOperatorAutopilotSetConfigCommmand(t *testing.T) {
if conf.MaxTrailingLogs != 99 {
t.Fatalf("bad: %#v", conf)
}
if conf.LastContactThreshold.Duration() != 123*time.Millisecond {
if conf.LastContactThreshold != 123*time.Millisecond {
t.Fatalf("bad: %#v", conf)
}
if conf.ServerStabilizationTime.Duration() != 123*time.Millisecond {
if conf.ServerStabilizationTime != 123*time.Millisecond {
t.Fatalf("bad: %#v", conf)
}
}

View File

@ -10,13 +10,45 @@ import (
"github.com/hashicorp/serf/serf"
)
const (
// AutopilotRZTag is the Serf tag to use for the redundancy zone value
// when passing the server metadata to Autopilot.
AutopilotRZTag = "ap_zone"
// AutopilotRZTag is the Serf tag to use for the custom version value
// when passing the server metadata to Autopilot.
AutopilotVersionTag = "ap_version"
)
// AutopilotDelegate is a Nomad delegate for autopilot operations.
type AutopilotDelegate struct {
server *Server
}
func (d *AutopilotDelegate) AutopilotConfig() *autopilot.Config {
return d.server.getOrCreateAutopilotConfig()
c := d.server.getOrCreateAutopilotConfig()
if c == nil {
return nil
}
conf := &autopilot.Config{
CleanupDeadServers: c.CleanupDeadServers,
LastContactThreshold: c.LastContactThreshold,
MaxTrailingLogs: c.MaxTrailingLogs,
ServerStabilizationTime: c.ServerStabilizationTime,
DisableUpgradeMigration: c.DisableUpgradeMigration,
ModifyIndex: c.ModifyIndex,
CreateIndex: c.CreateIndex,
}
if c.EnableRedundancyZones {
conf.RedundancyZoneTag = AutopilotRZTag
}
if c.EnableCustomUpgrades {
conf.UpgradeVersionTag = AutopilotVersionTag
}
return conf
}
func (d *AutopilotDelegate) FetchStats(ctx context.Context, servers []serf.Member) map[string]*autopilot.ServerStats {

View File

@ -270,8 +270,11 @@ func TestAutopilot_CleanupStaleRaftServer(t *testing.T) {
testutil.WaitForLeader(t, s1.RPC)
// Add s4 to peers directly
addr := fmt.Sprintf("127.0.0.1:%d", s4.config.SerfConfig.MemberlistConfig.BindPort)
s1.raft.AddVoter(raft.ServerID(s4.config.NodeID), raft.ServerAddress(addr), 0, 0)
addr := fmt.Sprintf("127.0.0.1:%d", s4.config.RPCAddr.Port)
future := s1.raft.AddVoter(raft.ServerID(s4.config.NodeID), raft.ServerAddress(addr), 0, 0)
if err := future.Error(); err != nil {
t.Fatal(err)
}
// Verify we have 4 peers
peers, err := s1.numPeers()

View File

@ -8,7 +8,6 @@ import (
"runtime"
"time"
"github.com/hashicorp/consul/agent/consul/autopilot"
"github.com/hashicorp/memberlist"
"github.com/hashicorp/nomad/helper/tlsutil"
"github.com/hashicorp/nomad/helper/uuid"
@ -98,6 +97,13 @@ type Config struct {
// as a voting member of the Raft cluster.
NonVoter bool
// (Enterprise-only) RedundancyZone is the redundancy zone to use for this server.
RedundancyZone string
// (Enterprise-only) UpgradeVersion is the custom upgrade version to use when
// performing upgrade migrations.
UpgradeVersion string
// SerfConfig is the configuration for the serf cluster
SerfConfig *serf.Config
@ -269,7 +275,7 @@ type Config struct {
// AutopilotConfig is used to apply the initial autopilot config when
// bootstrapping.
AutopilotConfig *autopilot.Config
AutopilotConfig *structs.AutopilotConfig
// ServerHealthInterval is the frequency with which the health of the
// servers in the cluster will be updated.
@ -339,7 +345,7 @@ func DefaultConfig() *Config {
TLSConfig: &config.TLSConfig{},
ReplicationBackoff: 30 * time.Second,
SentinelGCInterval: 30 * time.Second,
AutopilotConfig: &autopilot.Config{
AutopilotConfig: &structs.AutopilotConfig{
CleanupDeadServers: true,
LastContactThreshold: 200 * time.Millisecond,
MaxTrailingLogs: 250,

View File

@ -10,7 +10,6 @@ import (
"time"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/consul/agent/consul/autopilot"
memdb "github.com/hashicorp/go-memdb"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/nomad/mock"
@ -2319,7 +2318,7 @@ func TestFSM_Autopilot(t *testing.T) {
// Set the autopilot config using a request.
req := structs.AutopilotSetConfigRequest{
Datacenter: "dc1",
Config: autopilot.Config{
Config: structs.AutopilotConfig{
CleanupDeadServers: true,
LastContactThreshold: 10 * time.Second,
MaxTrailingLogs: 300,

View File

@ -13,7 +13,6 @@ import (
"golang.org/x/time/rate"
"github.com/armon/go-metrics"
"github.com/hashicorp/consul/agent/consul/autopilot"
memdb "github.com/hashicorp/go-memdb"
"github.com/hashicorp/go-version"
"github.com/hashicorp/nomad/helper/uuid"
@ -1174,7 +1173,7 @@ func diffACLTokens(state *state.StateStore, minIndex uint64, remoteList []*struc
}
// getOrCreateAutopilotConfig is used to get the autopilot config, initializing it if necessary
func (s *Server) getOrCreateAutopilotConfig() *autopilot.Config {
func (s *Server) getOrCreateAutopilotConfig() *structs.AutopilotConfig {
state := s.fsm.State()
_, config, err := state.AutopilotConfig()
if err != nil {

View File

@ -192,7 +192,7 @@ REMOVE:
}
// AutopilotGetConfiguration is used to retrieve the current Autopilot configuration.
func (op *Operator) AutopilotGetConfiguration(args *structs.GenericRequest, reply *autopilot.Config) error {
func (op *Operator) AutopilotGetConfiguration(args *structs.GenericRequest, reply *structs.AutopilotConfig) error {
if done, err := op.srv.forward("Operator.AutopilotGetConfiguration", args, args, reply); done {
return err
}

View File

@ -1105,6 +1105,12 @@ func (s *Server) setupSerf(conf *serf.Config, ch chan serf.Event, path string) (
if s.config.NonVoter {
conf.Tags["nonvoter"] = "1"
}
if s.config.RedundancyZone != "" {
conf.Tags[AutopilotRZTag] = s.config.RedundancyZone
}
if s.config.UpgradeVersion != "" {
conf.Tags[AutopilotVersionTag] = s.config.UpgradeVersion
}
conf.MemberlistConfig.LogOutput = s.config.LogOutput
conf.LogOutput = s.config.LogOutput
conf.EventCh = ch

View File

@ -3,8 +3,8 @@ package state
import (
"fmt"
"github.com/hashicorp/consul/agent/consul/autopilot"
"github.com/hashicorp/go-memdb"
"github.com/hashicorp/nomad/nomad/structs"
)
// autopilotConfigTableSchema returns a new table schema used for storing
@ -26,7 +26,7 @@ func autopilotConfigTableSchema() *memdb.TableSchema {
}
// AutopilotConfig is used to get the current Autopilot configuration.
func (s *StateStore) AutopilotConfig() (uint64, *autopilot.Config, error) {
func (s *StateStore) AutopilotConfig() (uint64, *structs.AutopilotConfig, error) {
tx := s.db.Txn(false)
defer tx.Abort()
@ -36,7 +36,7 @@ func (s *StateStore) AutopilotConfig() (uint64, *autopilot.Config, error) {
return 0, nil, fmt.Errorf("failed autopilot config lookup: %s", err)
}
config, ok := c.(*autopilot.Config)
config, ok := c.(*structs.AutopilotConfig)
if !ok {
return 0, nil, nil
}
@ -45,7 +45,7 @@ func (s *StateStore) AutopilotConfig() (uint64, *autopilot.Config, error) {
}
// AutopilotSetConfig is used to set the current Autopilot configuration.
func (s *StateStore) AutopilotSetConfig(idx uint64, config *autopilot.Config) error {
func (s *StateStore) AutopilotSetConfig(idx uint64, config *structs.AutopilotConfig) error {
tx := s.db.Txn(true)
defer tx.Abort()
@ -58,7 +58,7 @@ func (s *StateStore) AutopilotSetConfig(idx uint64, config *autopilot.Config) er
// AutopilotCASConfig is used to try updating the Autopilot configuration with a
// given Raft index. If the CAS index specified is not equal to the last observed index
// for the config, then the call is a noop,
func (s *StateStore) AutopilotCASConfig(idx, cidx uint64, config *autopilot.Config) (bool, error) {
func (s *StateStore) AutopilotCASConfig(idx, cidx uint64, config *structs.AutopilotConfig) (bool, error) {
tx := s.db.Txn(true)
defer tx.Abort()
@ -71,7 +71,7 @@ func (s *StateStore) AutopilotCASConfig(idx, cidx uint64, config *autopilot.Conf
// If the existing index does not match the provided CAS
// index arg, then we shouldn't update anything and can safely
// return early here.
e, ok := existing.(*autopilot.Config)
e, ok := existing.(*structs.AutopilotConfig)
if !ok || e.ModifyIndex != cidx {
return false, nil
}
@ -82,7 +82,7 @@ func (s *StateStore) AutopilotCASConfig(idx, cidx uint64, config *autopilot.Conf
return true, nil
}
func (s *StateStore) autopilotSetConfigTxn(idx uint64, tx *memdb.Txn, config *autopilot.Config) error {
func (s *StateStore) autopilotSetConfigTxn(idx uint64, tx *memdb.Txn, config *structs.AutopilotConfig) error {
// Check for an existing config
existing, err := tx.First("autopilot-config", "id")
if err != nil {
@ -91,7 +91,7 @@ func (s *StateStore) autopilotSetConfigTxn(idx uint64, tx *memdb.Txn, config *au
// Set the indexes.
if existing != nil {
config.CreateIndex = existing.(*autopilot.Config).CreateIndex
config.CreateIndex = existing.(*structs.AutopilotConfig).CreateIndex
} else {
config.CreateIndex = idx
}

View File

@ -5,20 +5,20 @@ import (
"testing"
"time"
"github.com/hashicorp/consul/agent/consul/autopilot"
"github.com/hashicorp/nomad/nomad/structs"
)
func TestStateStore_Autopilot(t *testing.T) {
s := testStateStore(t)
expected := &autopilot.Config{
expected := &structs.AutopilotConfig{
CleanupDeadServers: true,
LastContactThreshold: 5 * time.Second,
MaxTrailingLogs: 500,
ServerStabilizationTime: 100 * time.Second,
RedundancyZoneTag: "az",
EnableRedundancyZones: true,
DisableUpgradeMigration: true,
UpgradeVersionTag: "build",
EnableCustomUpgrades: true,
}
if err := s.AutopilotSetConfig(0, expected); err != nil {
@ -40,7 +40,7 @@ func TestStateStore_Autopilot(t *testing.T) {
func TestStateStore_AutopilotCAS(t *testing.T) {
s := testStateStore(t)
expected := &autopilot.Config{
expected := &structs.AutopilotConfig{
CleanupDeadServers: true,
}
@ -52,7 +52,7 @@ func TestStateStore_AutopilotCAS(t *testing.T) {
}
// Do a CAS with an index lower than the entry
ok, err := s.AutopilotCASConfig(2, 0, &autopilot.Config{
ok, err := s.AutopilotCASConfig(2, 0, &structs.AutopilotConfig{
CleanupDeadServers: false,
})
if ok || err != nil {
@ -73,7 +73,7 @@ func TestStateStore_AutopilotCAS(t *testing.T) {
}
// Do another CAS, this time with the correct index
ok, err = s.AutopilotCASConfig(2, 1, &autopilot.Config{
ok, err = s.AutopilotCASConfig(2, 1, &structs.AutopilotConfig{
CleanupDeadServers: false,
})
if !ok || err != nil {

View File

@ -24,25 +24,23 @@ type AutopilotConfig struct {
// be behind before being considered unhealthy.
MaxTrailingLogs int `mapstructure:"max_trailing_logs"`
// (Enterprise-only) RedundancyZoneTag is the node tag to use for separating
// servers into zones for redundancy. If left blank, this feature will be disabled.
RedundancyZoneTag string `mapstructure:"redundancy_zone_tag"`
// (Enterprise-only) EnableRedundancyZones specifies whether to enable redundancy zones.
EnableRedundancyZones *bool `mapstructure:"enable_redundancy_zones"`
// (Enterprise-only) DisableUpgradeMigration will disable Autopilot's upgrade migration
// strategy of waiting until enough newer-versioned servers have been added to the
// cluster before promoting them to voters.
DisableUpgradeMigration *bool `mapstructure:"disable_upgrade_migration"`
// (Enterprise-only) UpgradeVersionTag is the node tag to use for version info when
// performing upgrade migrations. If left blank, the Nomad version will be used.
UpgradeVersionTag string `mapstructure:"upgrade_version_tag"`
// (Enterprise-only) EnableCustomUpgrades specifies whether to enable using custom
// upgrade versions when performing migrations.
EnableCustomUpgrades *bool `mapstructure:"enable_custom_upgrades"`
}
// DefaultAutopilotConfig() returns the canonical defaults for the Nomad
// `autopilot` configuration.
func DefaultAutopilotConfig() *AutopilotConfig {
return &AutopilotConfig{
CleanupDeadServers: helper.BoolToPtr(true),
LastContactThreshold: 200 * time.Millisecond,
MaxTrailingLogs: 250,
ServerStabilizationTime: 10 * time.Second,
@ -64,14 +62,14 @@ func (a *AutopilotConfig) Merge(b *AutopilotConfig) *AutopilotConfig {
if b.MaxTrailingLogs != 0 {
result.MaxTrailingLogs = b.MaxTrailingLogs
}
if b.RedundancyZoneTag != "" {
result.RedundancyZoneTag = b.RedundancyZoneTag
if b.EnableRedundancyZones != nil {
result.EnableRedundancyZones = b.EnableRedundancyZones
}
if b.DisableUpgradeMigration != nil {
result.DisableUpgradeMigration = helper.BoolToPtr(*b.DisableUpgradeMigration)
}
if b.UpgradeVersionTag != "" {
result.UpgradeVersionTag = b.UpgradeVersionTag
if b.EnableCustomUpgrades != nil {
result.EnableCustomUpgrades = b.EnableCustomUpgrades
}
return result
@ -90,9 +88,15 @@ func (a *AutopilotConfig) Copy() *AutopilotConfig {
if a.CleanupDeadServers != nil {
nc.CleanupDeadServers = helper.BoolToPtr(*a.CleanupDeadServers)
}
if a.EnableRedundancyZones != nil {
nc.EnableRedundancyZones = helper.BoolToPtr(*a.EnableRedundancyZones)
}
if a.DisableUpgradeMigration != nil {
nc.DisableUpgradeMigration = helper.BoolToPtr(*a.DisableUpgradeMigration)
}
if a.EnableCustomUpgrades != nil {
nc.EnableCustomUpgrades = helper.BoolToPtr(*a.EnableCustomUpgrades)
}
return nc
}

View File

@ -14,9 +14,9 @@ func TestAutopilotConfig_Merge(t *testing.T) {
ServerStabilizationTime: 1 * time.Second,
LastContactThreshold: 1 * time.Second,
MaxTrailingLogs: 1,
RedundancyZoneTag: "1",
EnableRedundancyZones: &trueValue,
DisableUpgradeMigration: &falseValue,
UpgradeVersionTag: "1",
EnableCustomUpgrades: &trueValue,
}
c2 := &AutopilotConfig{
@ -24,9 +24,9 @@ func TestAutopilotConfig_Merge(t *testing.T) {
ServerStabilizationTime: 2 * time.Second,
LastContactThreshold: 2 * time.Second,
MaxTrailingLogs: 2,
RedundancyZoneTag: "2",
EnableRedundancyZones: nil,
DisableUpgradeMigration: nil,
UpgradeVersionTag: "2",
EnableCustomUpgrades: nil,
}
e := &AutopilotConfig{
@ -34,9 +34,9 @@ func TestAutopilotConfig_Merge(t *testing.T) {
ServerStabilizationTime: 2 * time.Second,
LastContactThreshold: 2 * time.Second,
MaxTrailingLogs: 2,
RedundancyZoneTag: "2",
EnableRedundancyZones: &trueValue,
DisableUpgradeMigration: &falseValue,
UpgradeVersionTag: "2",
EnableCustomUpgrades: &trueValue,
}
result := c1.Merge(c2)

View File

@ -1,7 +1,8 @@
package structs
import (
"github.com/hashicorp/consul/agent/consul/autopilot"
"time"
"github.com/hashicorp/raft"
)
@ -69,7 +70,7 @@ type AutopilotSetConfigRequest struct {
Datacenter string
// Config is the new Autopilot configuration to use.
Config autopilot.Config
Config AutopilotConfig
// CAS controls whether to use check-and-set semantics for this request.
CAS bool
@ -82,3 +83,39 @@ type AutopilotSetConfigRequest struct {
func (op *AutopilotSetConfigRequest) RequestDatacenter() string {
return op.Datacenter
}
// AutopilotConfig is the internal config for the Autopilot mechanism.
type AutopilotConfig struct {
// CleanupDeadServers controls whether to remove dead servers when a new
// server is added to the Raft peers.
CleanupDeadServers bool
// ServerStabilizationTime is the minimum amount of time a server must be
// in a stable, healthy state before it can be added to the cluster. Only
// applicable with Raft protocol version 3 or higher.
ServerStabilizationTime time.Duration
// LastContactThreshold is the limit on the amount of time a server can go
// without leader contact before being considered unhealthy.
LastContactThreshold time.Duration
// MaxTrailingLogs is the amount of entries in the Raft Log that a server can
// be behind before being considered unhealthy.
MaxTrailingLogs uint64
// (Enterprise-only) EnableRedundancyZones specifies whether to enable redundancy zones.
EnableRedundancyZones bool
// (Enterprise-only) DisableUpgradeMigration will disable Autopilot's upgrade migration
// strategy of waiting until enough newer-versioned servers have been added to the
// cluster before promoting them to voters.
DisableUpgradeMigration bool
// (Enterprise-only) EnableCustomUpgrades specifies whether to enable using custom
// upgrade versions when performing migrations.
EnableCustomUpgrades bool
// CreateIndex/ModifyIndex store the create/modify indexes of this configuration.
CreateIndex uint64
ModifyIndex uint64
}

View File

@ -46,7 +46,6 @@ type serverParts struct {
MinorVersion int
Build version.Version
RaftVersion int
NonVoter bool
Addr net.Addr
RPCAddr net.Addr
Status serf.MemberStatus
@ -71,7 +70,6 @@ func isNomadServer(m serf.Member) (bool, *serverParts) {
region := m.Tags["region"]
datacenter := m.Tags["dc"]
_, bootstrap := m.Tags["bootstrap"]
_, nonVoter := m.Tags["nonvoter"]
expect := 0
expectStr, ok := m.Tags["expect"]
@ -140,7 +138,6 @@ func isNomadServer(m serf.Member) (bool, *serverParts) {
MinorVersion: minorVersion,
Build: *buildVersion,
RaftVersion: raftVsn,
NonVoter: nonVoter,
Status: m.Status,
}
return true, parts

View File

@ -24,7 +24,6 @@ func TestIsNomadServer(t *testing.T) {
"port": "10000",
"vsn": "1",
"raft_vsn": "2",
"nonvoter": "1",
"build": "0.7.0+ent",
},
}
@ -51,9 +50,6 @@ func TestIsNomadServer(t *testing.T) {
if parts.RPCAddr.String() != "1.1.1.1:10000" {
t.Fatalf("bad: %v", parts.RPCAddr.String())
}
if !parts.NonVoter {
t.Fatalf("bad: %v", parts.NonVoter)
}
if seg := parts.Build.Segments(); len(seg) != 3 {
t.Fatalf("bad: %v", parts.Build)
} else if seg[0] != 0 && seg[1] != 7 && seg[2] != 0 {

View File

@ -168,9 +168,9 @@ $ curl \
"LastContactThreshold": "200ms",
"MaxTrailingLogs": 250,
"ServerStabilizationTime": "10s",
"RedundancyZoneTag": "",
"EnableRedundancyZones": false,
"DisableUpgradeMigration": false,
"UpgradeVersionTag": "",
"EnableCustomUpgrades": false,
"CreateIndex": 4,
"ModifyIndex": 4
}
@ -221,19 +221,16 @@ The table below shows this endpoint's support for
cluster. Only takes effect if all servers are running Raft protocol version 3
or higher. Must be a duration value such as `30s`.
- `RedundancyZoneTag` `(string: "")` - Controls the node-meta key to use when
Autopilot is separating servers into zones for redundancy. Only one server in
each zone can be a voting member at one time. If left blank, this feature will
be disabled.
- `EnableRedundancyZones` `(bool: false)` - (Enterprise-only) Specifies whether
to enable redundancy zones.
- `DisableUpgradeMigration` `(bool: false)` - Disables Autopilot's upgrade
migration strategy in Nomad Enterprise of waiting until enough
- `DisableUpgradeMigration` `(bool: false)` - (Enterprise-only) Disables Autopilot's
upgrade migration strategy in Nomad Enterprise of waiting until enough
newer-versioned servers have been added to the cluster before promoting any of
them to voters.
- `UpgradeVersionTag` `(string: "")` - Controls the node-meta key to use for
version info when performing upgrade migrations. If left blank, the Nomad
version will be used.
- `EnableCustomUpgrades` `(bool: false)` - (Enterprise-only) Specifies whether to
enable using custom upgrade versions when performing migrations.
### Sample Payload
@ -243,9 +240,9 @@ The table below shows this endpoint's support for
"LastContactThreshold": "200ms",
"MaxTrailingLogs": 250,
"ServerStabilizationTime": "10s",
"RedundancyZoneTag": "",
"EnableRedundancyZones": false,
"DisableUpgradeMigration": false,
"UpgradeVersionTag": "",
"EnableCustomUpgrades": false,
"CreateIndex": 4,
"ModifyIndex": 4
}

View File

@ -18,6 +18,7 @@ description: |-
</table>
The `autopilot` stanza configures the Nomad agent to configure Autopilot behavior.
For more information about Autopilot, see the [Autopilot Guide](/guides/cluster/autopilot.html).
```hcl
autopilot {
@ -25,9 +26,9 @@ autopilot {
last_contact_threshold = "200ms"
max_trailing_logs = 250
server_stabilization_time = "10s"
redundancy_zone_tag = ""
disable_upgrade_migration = true
upgrade_version_tag = ""
enable_redundancy_zones = false
disable_upgrade_migration = false
enable_custom_upgrades = false
}
```
@ -48,17 +49,17 @@ autopilot {
cluster. Only takes effect if all servers are running Raft protocol version 3
or higher. Must be a duration value such as `30s`.
- `redundancy_zone_tag` `(string: "")` - Controls the node-meta key to use when
Autopilot is separating servers into zones for redundancy. Only one server in
each zone can be a voting member at one time. If left blank, this feature will
be disabled.
- `enable_redundancy_zones` `(bool: false)` - (Enterprise-only) Controls whether
Autopilot separates servers into zones for redundancy, in conjunction with the
[redundancy_zone](/docs/agent/configuration/server.html#redundancy_zone) parameter.
Only one server in each zone can be a voting member at one time.
- `disable_upgrade_migration` `(bool: false)` - Disables Autopilot's upgrade
migration strategy in Nomad Enterprise of waiting until enough
- `disable_upgrade_migration` `(bool: false)` - (Enterprise-only) Disables Autopilot's
upgrade migration strategy in Nomad Enterprise of waiting until enough
newer-versioned servers have been added to the cluster before promoting any of
them to voters.
- `upgrade_version_tag` `(string: "")` - Controls the node-meta key to use for
version info when performing upgrade migrations. If left blank, the Nomad
version will be used.
- `enable_custom_upgrades` `(bool: false)` - (Enterprise-only) Specifies whether to
enable using custom upgrade versions when performing migrations, in conjunction with
the [upgrade_version](/docs/agent/configuration/server.html#upgrade_version) parameter.

View File

@ -102,8 +102,9 @@ server {
second is a tradeoff as it lowers failure detection time of nodes at the
tradeoff of false positives and increased load on the leader.
- `non_voting_server` `(bool: false)` - is whether this server will act as
a non-voting member of the cluster to help provide read scalability. (Enterprise-only)
- `non_voting_server` `(bool: false)` - (Enterprise-only) Specifies whether
this server will act as a non-voting member of the cluster to help provide
read scalability.
- `num_schedulers` `(int: [num-cores])` - Specifies the number of parallel
scheduler threads to run. This can be as many as one per core, or `0` to
@ -120,6 +121,10 @@ server {
features and is typically not required as the agent internally knows the
latest version, but may be useful in some upgrade scenarios.
- `redundancy_zone` `(string: "")` - (Enterprise-only) Specifies the redundancy
zone that this server will be a part of for Autopilot management. For more
information, see the [Autopilot Guide](/guides/cluster/autopilot.html).
- `rejoin_after_leave` `(bool: false)` - Specifies if Nomad will ignore a
previous leave and attempt to rejoin the cluster when starting. By default,
Nomad treats leave as a permanent intent and does not attempt to join the
@ -149,6 +154,10 @@ server {
[server address format](#server-address-format) section for more information
on the format of the string.
- `upgrade_version` `(string: "")` - A custom version of the format X.Y.Z to use
in place of the Nomad version when custom upgrades are enabled in Autopilot.
For more information, see the [Autopilot Guide](/guides/cluster/autopilot.html).
### Server Address Format
This section describes the acceptable syntax and format for describing the

View File

@ -32,9 +32,9 @@ autopilot {
last_contact_threshold = 200ms
max_trailing_logs = 250
server_stabilization_time = "10s"
redundancy_zone_tag = "az"
enable_redundancy_zones = false
disable_upgrade_migration = false
upgrade_version_tag = ""
enable_custom_upgrades = false
}
```
@ -49,21 +49,21 @@ CleanupDeadServers = true
LastContactThreshold = 200ms
MaxTrailingLogs = 250
ServerStabilizationTime = 10s
RedundancyZoneTag = ""
EnableRedundancyZones = false
DisableUpgradeMigration = false
UpgradeVersionTag = ""
EnableCustomUpgrades = false
$ Nomad operator autopilot set-config -cleanup-dead-servers=false
$ nomad operator autopilot set-config -cleanup-dead-servers=false
Configuration updated!
$ Nomad operator autopilot get-config
$ nomad operator autopilot get-config
CleanupDeadServers = false
LastContactThreshold = 200ms
MaxTrailingLogs = 250
ServerStabilizationTime = 10s
RedundancyZoneTag = ""
EnableRedundancyZones = false
DisableUpgradeMigration = false
UpgradeVersionTag = ""
EnableCustomUpgrades = false
```
## Dead Server Cleanup
@ -164,15 +164,21 @@ isolated failure domains such as AWS Availability Zones; users would be forced t
have an overly-large quorum (2-3 nodes per AZ) or give up redundancy within an AZ by
deploying just one server in each.
If the `RedundancyZoneTag` setting is set, Nomad will use its value to look for a
zone in each server's specified [`-meta`](/docs/agent/configuration/client.html#meta)
tag. For example, if `RedundancyZoneTag` is set to `zone`, and `-meta zone=east1a`
is used when starting a server, that server's redundancy zone will be `east1a`.
If the `EnableRedundancyZones` setting is set, Nomad will use its value to look for a
zone in each server's specified [`redundancy_zone`]
(/docs/agent/configuration/server.html#redundancy_zone) field.
Here's an example showing how to configure this:
```hcl
/* config.hcl */
server {
redundancy_zone = "west-1"
}
```
$ nomad operator autopilot set-config -redundancy-zone-tag=zone
```
$ nomad operator autopilot set-config -enable-redundancy-zones=true
Configuration updated!
```
@ -193,27 +199,25 @@ to voters and demoting the old servers. After this is finished, the old servers
safely removed from the cluster.
To check the Nomad version of the servers, either the [autopilot health]
(/api/operator.html#read-health) endpoint or the `Nomad members`
(/api/operator.html#read-health) endpoint or the `nomad members`
command can be used:
```
$ Nomad members
Node Address Status Type Build Protocol DC
node1 127.0.0.1:8301 alive server 0.7.5 2 dc1
node2 127.0.0.1:8703 alive server 0.7.5 2 dc1
node3 127.0.0.1:8803 alive server 0.7.5 2 dc1
node4 127.0.0.1:8203 alive server 0.8.0 2 dc1
$ nomad server-members
Name Address Port Status Leader Protocol Build Datacenter Region
node1 127.0.0.1 4648 alive true 3 0.7.1 dc1 global
node2 127.0.0.1 4748 alive false 3 0.7.1 dc1 global
node3 127.0.0.1 4848 alive false 3 0.7.1 dc1 global
node4 127.0.0.1 4948 alive false 3 0.8.0 dc1 global
```
### Migrations Without a Nomad Version Change
The `UpgradeVersionTag` can be used to override the version information used during
The `EnableCustomUpgrades` field can be used to override the version information used during
a migration, so that the migration logic can be used for updating the cluster when
changing configuration.
If the `UpgradeVersionTag` setting is set, Nomad will use its value to look for a
version in each server's specified [`-meta`](/docs/agent/configuration/client.html#meta)
tag. For example, if `UpgradeVersionTag` is set to `build`, and `-meta build:0.0.2`
is used when starting a server, that server's version will be `0.0.2` when considered in
a migration. The upgrade logic will follow semantic versioning and the version string
If the `EnableCustomUpgrades` setting is set to `true`, Nomad will use its value to look for a
version in each server's specified [`upgrade_version`](/docs/agent/configuration/server.html#upgrade_version)
tag. The upgrade logic will follow semantic versioning and the `upgrade_version`
must be in the form of either `X`, `X.Y`, or `X.Y.Z`.