浏览代码

Allow swarm join with `--availability=drain`
This fix tries to address the issue raised in 24596 where it was not
possible to join as manager only (`--availability=drain`).

This fix adds a new flag `--availability` to `swarm join`.

Related documentation has been updated.

An integration test has been added.

NOTE: Additional pull request for swarmkit and engine-api will
be created separately.

This fix fixes 24596.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

Yong Tang 8 年之前
父节点
当前提交
a8e7e37aa8

+ 1 - 0
api/types/swarm/swarm.go

@@ -143,6 +143,7 @@ type JoinRequest struct {
 	AdvertiseAddr string
 	RemoteAddrs   []string
 	JoinToken     string // accept by secret
+	Availability  NodeAvailability
 }
 
 // UnlockRequest is the request used to unlock a swarm.

+ 18 - 3
cli/command/swarm/join.go

@@ -2,12 +2,15 @@ package swarm
 
 import (
 	"fmt"
+	"strings"
+
+	"golang.org/x/net/context"
 
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/spf13/cobra"
-	"golang.org/x/net/context"
+	"github.com/spf13/pflag"
 )
 
 type joinOptions struct {
@@ -16,6 +19,7 @@ type joinOptions struct {
 	// Not a NodeAddrOption because it has no default port.
 	advertiseAddr string
 	token         string
+	availability  string
 }
 
 func newJoinCommand(dockerCli command.Cli) *cobra.Command {
@@ -29,7 +33,7 @@ func newJoinCommand(dockerCli command.Cli) *cobra.Command {
 		Args:  cli.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			opts.remote = args[0]
-			return runJoin(dockerCli, opts)
+			return runJoin(dockerCli, cmd.Flags(), opts)
 		},
 	}
 
@@ -37,10 +41,11 @@ func newJoinCommand(dockerCli command.Cli) *cobra.Command {
 	flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: <ip|interface>[:port])")
 	flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: <ip|interface>[:port])")
 	flags.StringVar(&opts.token, flagToken, "", "Token for entry into the swarm")
+	flags.StringVar(&opts.availability, flagAvailability, "active", "Availability of the node (active/pause/drain)")
 	return cmd
 }
 
-func runJoin(dockerCli command.Cli, opts joinOptions) error {
+func runJoin(dockerCli command.Cli, flags *pflag.FlagSet, opts joinOptions) error {
 	client := dockerCli.Client()
 	ctx := context.Background()
 
@@ -50,6 +55,16 @@ func runJoin(dockerCli command.Cli, opts joinOptions) error {
 		AdvertiseAddr: opts.advertiseAddr,
 		RemoteAddrs:   []string{opts.remote},
 	}
+	if flags.Changed(flagAvailability) {
+		availability := swarm.NodeAvailability(strings.ToLower(opts.availability))
+		switch availability {
+		case swarm.NodeAvailabilityActive, swarm.NodeAvailabilityPause, swarm.NodeAvailabilityDrain:
+			req.Availability = availability
+		default:
+			return fmt.Errorf("invalid availability %q, only active, pause and drain are supported", opts.availability)
+		}
+	}
+
 	err := client.SwarmJoin(ctx, req)
 	if err != nil {
 		return err

+ 1 - 0
cli/command/swarm/opts.go

@@ -28,6 +28,7 @@ const (
 	flagSnapshotInterval    = "snapshot-interval"
 	flagLockKey             = "lock-key"
 	flagAutolock            = "autolock"
+	flagAvailability        = "availability"
 )
 
 type swarmOptions struct {

+ 1 - 0
daemon/cluster/cluster.go

@@ -389,6 +389,7 @@ func (c *Cluster) Join(req types.JoinRequest) error {
 		AdvertiseAddr: advertiseAddr,
 		joinAddr:      req.RemoteAddrs[0],
 		joinToken:     req.JoinToken,
+		availability:  req.Availability,
 	})
 	if err != nil {
 		return err

+ 12 - 2
daemon/cluster/noderunner.go

@@ -1,6 +1,7 @@
 package cluster
 
 import (
+	"fmt"
 	"path/filepath"
 	"runtime"
 	"strings"
@@ -51,6 +52,7 @@ type nodeStartConfig struct {
 	joinToken       string
 	lockKey         []byte
 	autolock        bool
+	availability    types.NodeAvailability
 }
 
 func (n *nodeRunner) Ready() chan error {
@@ -92,7 +94,7 @@ func (n *nodeRunner) start(conf nodeStartConfig) error {
 		control = filepath.Join(n.cluster.runtimeRoot, controlSocket)
 	}
 
-	node, err := swarmnode.New(&swarmnode.Config{
+	swarmnodeConfig := swarmnode.Config{
 		Hostname:           n.cluster.config.Name,
 		ForceNewCluster:    conf.forceNewCluster,
 		ListenControlAPI:   control,
@@ -106,7 +108,15 @@ func (n *nodeRunner) start(conf nodeStartConfig) error {
 		ElectionTick:       3,
 		UnlockKey:          conf.lockKey,
 		AutoLockManagers:   conf.autolock,
-	})
+	}
+	if conf.availability != "" {
+		avail, ok := swarmapi.NodeSpec_Availability_value[strings.ToUpper(string(conf.availability))]
+		if !ok {
+			return fmt.Errorf("invalid Availability: %q", conf.availability)
+		}
+		swarmnodeConfig.Availability = swarmapi.NodeSpec_Availability(avail)
+	}
+	node, err := swarmnode.New(&swarmnodeConfig)
 	if err != nil {
 		return err
 	}

+ 10 - 0
docs/reference/commandline/swarm_join.md

@@ -22,6 +22,7 @@ Join a swarm as a node and/or manager
 
 Options:
       --advertise-addr string   Advertised address (format: <ip|interface>[:port])
+      --availability string     Availability of the node (active/pause/drain) (default "active")
       --help                    Print usage
       --listen-addr node-addr   Listen address (format: <ip|interface>[:port]) (default 0.0.0.0:2377)
       --token string            Token for entry into the swarm
@@ -94,6 +95,15 @@ This flag is generally not necessary when joining an existing swarm.
 
 Secret value required for nodes to join the swarm
 
+### `--availability`
+
+This flag specifies the availability of the node at the time the node joins a master.
+Possible availability values are `active`, `pause`, or `drain`.
+
+This flag is useful in certain situations. For example, a cluster may want to have
+dedicated manager nodes that are not served as worker nodes. This could be achieved
+by passing `--availability=drain` to `docker swarm join`.
+
 
 ## Related information
 

+ 28 - 0
integration-cli/docker_cli_swarm_test.go

@@ -1591,3 +1591,31 @@ func (s *DockerSwarmSuite) TestSwarmPublishDuplicatePorts(c *check.C) {
 	c.Assert(out, checker.Contains, "{ tcp 80 5000 ingress}")
 	c.Assert(out, checker.Contains, "{ tcp 80 5001 ingress}")
 }
+
+func (s *DockerSwarmSuite) TestSwarmJoinWithDrain(c *check.C) {
+	d := s.AddDaemon(c, true, true)
+
+	out, err := d.Cmd("node", "ls")
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Not(checker.Contains), "Drain")
+
+	out, err = d.Cmd("swarm", "join-token", "-q", "manager")
+	c.Assert(err, checker.IsNil)
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
+
+	token := strings.TrimSpace(out)
+
+	d1 := s.AddDaemon(c, false, false)
+
+	out, err = d1.Cmd("swarm", "join", "--availability=drain", "--token", token, d.ListenAddr)
+	c.Assert(err, checker.IsNil)
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
+
+	out, err = d.Cmd("node", "ls")
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Contains, "Drain")
+
+	out, err = d1.Cmd("node", "ls")
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Contains, "Drain")
+}