mirror of https://github.com/docker/docs.git
Add --force to node removal
Signed-off-by: Diogo Monica <diogo.monica@gmail.com> (cherry picked from commit a327c231b5c68c13b7dcde2fdc83b8e4cec59c43) Signed-off-by: Tibor Vass <tibor@docker.com>
This commit is contained in:
parent
b32462e2a4
commit
caaf53ad3e
|
@ -7,26 +7,36 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/api/client"
|
"github.com/docker/docker/api/client"
|
||||||
"github.com/docker/docker/cli"
|
"github.com/docker/docker/cli"
|
||||||
|
"github.com/docker/engine-api/types"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type removeOptions struct {
|
||||||
|
force bool
|
||||||
|
}
|
||||||
|
|
||||||
func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||||
return &cobra.Command{
|
opts := removeOptions{}
|
||||||
Use: "rm NODE [NODE...]",
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "rm [OPTIONS] NODE [NODE...]",
|
||||||
Aliases: []string{"remove"},
|
Aliases: []string{"remove"},
|
||||||
Short: "Remove one or more nodes from the swarm",
|
Short: "Remove one or more nodes from the swarm",
|
||||||
Args: cli.RequiresMinArgs(1),
|
Args: cli.RequiresMinArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runRemove(dockerCli, args)
|
return runRemove(dockerCli, args, opts)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.BoolVar(&opts.force, "force", false, "Force remove an active node")
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRemove(dockerCli *client.DockerCli, args []string) error {
|
func runRemove(dockerCli *client.DockerCli, args []string, opts removeOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
for _, nodeID := range args {
|
for _, nodeID := range args {
|
||||||
err := client.NodeRemove(ctx, nodeID)
|
err := client.NodeRemove(ctx, nodeID, types.NodeRemoveOptions{Force: opts.force})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ type Backend interface {
|
||||||
GetNodes(basictypes.NodeListOptions) ([]types.Node, error)
|
GetNodes(basictypes.NodeListOptions) ([]types.Node, error)
|
||||||
GetNode(string) (types.Node, error)
|
GetNode(string) (types.Node, error)
|
||||||
UpdateNode(string, uint64, types.NodeSpec) error
|
UpdateNode(string, uint64, types.NodeSpec) error
|
||||||
RemoveNode(string) error
|
RemoveNode(string, bool) error
|
||||||
GetTasks(basictypes.TaskListOptions) ([]types.Task, error)
|
GetTasks(basictypes.TaskListOptions) ([]types.Task, error)
|
||||||
GetTask(string) (types.Task, error)
|
GetTask(string) (types.Task, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,7 +219,13 @@ func (sr *swarmRouter) updateNode(ctx context.Context, w http.ResponseWriter, r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sr *swarmRouter) removeNode(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (sr *swarmRouter) removeNode(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := sr.backend.RemoveNode(vars["id"]); err != nil {
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
force := httputils.BoolValue(r, "force")
|
||||||
|
|
||||||
|
if err := sr.backend.RemoveNode(vars["id"], force); err != nil {
|
||||||
logrus.Errorf("Error removing node %s: %v", vars["id"], err)
|
logrus.Errorf("Error removing node %s: %v", vars["id"], err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1023,7 +1023,7 @@ func (c *Cluster) UpdateNode(nodeID string, version uint64, spec types.NodeSpec)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveNode removes a node from a cluster
|
// RemoveNode removes a node from a cluster
|
||||||
func (c *Cluster) RemoveNode(input string) error {
|
func (c *Cluster) RemoveNode(input string, force bool) error {
|
||||||
c.RLock()
|
c.RLock()
|
||||||
defer c.RUnlock()
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
@ -1039,7 +1039,7 @@ func (c *Cluster) RemoveNode(input string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := c.client.RemoveNode(ctx, &swarmapi.RemoveNodeRequest{NodeID: node.ID}); err != nil {
|
if _, err := c.client.RemoveNode(ctx, &swarmapi.RemoveNodeRequest{NodeID: node.ID, Force: force}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -13,7 +13,7 @@ parent = "smn_cli"
|
||||||
# node rm
|
# node rm
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Usage: docker node rm NODE [NODE...]
|
Usage: docker node rm [OPTIONS] NODE [NODE...]
|
||||||
|
|
||||||
Remove one or more nodes from the swarm
|
Remove one or more nodes from the swarm
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ Aliases:
|
||||||
rm, remove
|
rm, remove
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
--force Force remove an active node
|
||||||
--help Print usage
|
--help Print usage
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -32,6 +33,24 @@ Example output:
|
||||||
$ docker node rm swarm-node-02
|
$ docker node rm swarm-node-02
|
||||||
Node swarm-node-02 removed from swarm
|
Node swarm-node-02 removed from swarm
|
||||||
|
|
||||||
|
Removes nodes from the swarm that are in the down state. Attempting to remove
|
||||||
|
an active node will result in an error:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ docker node rm swarm-node-03
|
||||||
|
Error response from daemon: rpc error: code = 9 desc = node swarm-node-03 is not down and can't be removed
|
||||||
|
```
|
||||||
|
|
||||||
|
If a worker node becomes compromised, exhibits unexpected or unwanted behavior, or if you lose access to it so
|
||||||
|
that a clean shutdown is impossible, you can use the force option.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ docker node rm --force swarm-node-03
|
||||||
|
Node swarm-node-03 removed from swarm
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that manager nodes have to be demoted to worker nodes before they can be removed
|
||||||
|
from the cluster.
|
||||||
|
|
||||||
## Related information
|
## Related information
|
||||||
|
|
||||||
|
|
|
@ -208,6 +208,17 @@ func (d *SwarmDaemon) getNode(c *check.C, id string) *swarm.Node {
|
||||||
return &node
|
return &node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *SwarmDaemon) removeNode(c *check.C, id string, force bool) {
|
||||||
|
url := "/nodes/" + id
|
||||||
|
if force {
|
||||||
|
url += "?force=1"
|
||||||
|
}
|
||||||
|
|
||||||
|
status, out, err := d.SockRequest("DELETE", url, nil)
|
||||||
|
c.Assert(status, checker.Equals, http.StatusOK, check.Commentf("output: %q", string(out)))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *SwarmDaemon) updateNode(c *check.C, id string, f ...nodeConstructor) {
|
func (d *SwarmDaemon) updateNode(c *check.C, id string, f ...nodeConstructor) {
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
node := d.getNode(c, id)
|
node := d.getNode(c, id)
|
||||||
|
|
|
@ -510,6 +510,37 @@ func (s *DockerSwarmSuite) TestApiSwarmNodeUpdate(c *check.C) {
|
||||||
c.Assert(n.Spec.Availability, checker.Equals, swarm.NodeAvailabilityPause)
|
c.Assert(n.Spec.Availability, checker.Equals, swarm.NodeAvailabilityPause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DockerSwarmSuite) TestApiSwarmNodeRemove(c *check.C) {
|
||||||
|
testRequires(c, Network)
|
||||||
|
d1 := s.AddDaemon(c, true, true)
|
||||||
|
d2 := s.AddDaemon(c, true, false)
|
||||||
|
_ = s.AddDaemon(c, true, false)
|
||||||
|
|
||||||
|
nodes := d1.listNodes(c)
|
||||||
|
c.Assert(len(nodes), checker.Equals, 3, check.Commentf("nodes: %#v", nodes))
|
||||||
|
|
||||||
|
// Getting the info so we can take the NodeID
|
||||||
|
d2Info, err := d2.info()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// forceful removal of d2 should work
|
||||||
|
d1.removeNode(c, d2Info.NodeID, true)
|
||||||
|
|
||||||
|
nodes = d1.listNodes(c)
|
||||||
|
c.Assert(len(nodes), checker.Equals, 2, check.Commentf("nodes: %#v", nodes))
|
||||||
|
|
||||||
|
// Restart the node that was removed
|
||||||
|
err = d2.Restart()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Give some time for the node to rejoin
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
// Make sure the node didn't rejoin
|
||||||
|
nodes = d1.listNodes(c)
|
||||||
|
c.Assert(len(nodes), checker.Equals, 2, check.Commentf("nodes: %#v", nodes))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DockerSwarmSuite) TestApiSwarmNodeDrainPause(c *check.C) {
|
func (s *DockerSwarmSuite) TestApiSwarmNodeDrainPause(c *check.C) {
|
||||||
testRequires(c, Network)
|
testRequires(c, Network)
|
||||||
d1 := s.AddDaemon(c, true, true)
|
d1 := s.AddDaemon(c, true, true)
|
||||||
|
|
Loading…
Reference in New Issue