add docker network prune

`docker network prune` prunes unused networks, including overlay ones.
`docker system prune` also prunes unused networks.

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
This commit is contained in:
Akihiro Suda 2016-10-18 04:36:52 +00:00
parent f901c5ed1c
commit 7e24c16086
29 changed files with 399 additions and 3 deletions

View file

@ -17,4 +17,5 @@ type Backend interface {
ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error
DeleteNetwork(name string) error
NetworksPrune(config *types.NetworksPruneConfig) (*types.NetworksPruneReport, error)
}

View file

@ -37,6 +37,7 @@ func (r *networkRouter) initRoutes() {
router.NewPostRoute("/networks/create", r.postNetworkCreate),
router.NewPostRoute("/networks/{id:.*}/connect", r.postNetworkConnect),
router.NewPostRoute("/networks/{id:.*}/disconnect", r.postNetworkDisconnect),
router.NewPostRoute("/networks/prune", r.postNetworksPrune),
// DELETE
router.NewDeleteRoute("/networks/{id:.*}", r.deleteNetwork),
}

View file

@ -274,3 +274,24 @@ func buildEndpointResource(id string, name string, info libnetwork.EndpointInfo)
}
return er
}
func (n *networkRouter) postNetworksPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := httputils.ParseForm(r); err != nil {
return err
}
if err := httputils.CheckForJSON(r); err != nil {
return err
}
var cfg types.NetworksPruneConfig
if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
return err
}
pruneReport, err := n.backend.NetworksPrune(&cfg)
if err != nil {
return err
}
return httputils.WriteJSON(w, http.StatusOK, pruneReport)
}

View file

@ -522,6 +522,11 @@ type ContainersPruneConfig struct {
type VolumesPruneConfig struct {
}
// NetworksPruneConfig contains the configuration for Remote API:
// POST "/networks/prune"
type NetworksPruneConfig struct {
}
// ContainersPruneReport contains the response for Remote API:
// POST "/containers/prune"
type ContainersPruneReport struct {
@ -542,3 +547,9 @@ type ImagesPruneReport struct {
ImagesDeleted []ImageDelete
SpaceReclaimed uint64
}
// NetworksPruneReport contains the response for Remote API:
// POST "/networks/prune"
type NetworksPruneReport struct {
NetworksDeleted []string
}

View file

@ -26,6 +26,7 @@ func NewNetworkCommand(dockerCli *command.DockerCli) *cobra.Command {
newInspectCommand(dockerCli),
newListCommand(dockerCli),
newRemoveCommand(dockerCli),
NewPruneCommand(dockerCli),
)
return cmd
}

View file

@ -0,0 +1,72 @@
package network
import (
"fmt"
"golang.org/x/net/context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/spf13/cobra"
)
type pruneOptions struct {
force bool
}
// NewPruneCommand returns a new cobra prune command for networks
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts pruneOptions
cmd := &cobra.Command{
Use: "prune [OPTIONS]",
Short: "Remove all unused networks",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
output, err := runPrune(dockerCli, opts)
if err != nil {
return err
}
if output != "" {
fmt.Fprintln(dockerCli.Out(), output)
}
return nil
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
return cmd
}
const warning = `WARNING! This will remove all networks not used by at least one container.
Are you sure you want to continue?`
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (output string, err error) {
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
return
}
report, err := dockerCli.Client().NetworksPrune(context.Background(), types.NetworksPruneConfig{})
if err != nil {
return
}
if len(report.NetworksDeleted) > 0 {
output = "Deleted Networks:\n"
for _, id := range report.NetworksDeleted {
output += id + "\n"
}
}
return
}
// RunPrune calls the Network Prune API
// This returns the amount of space reclaimed and a detailed output string
func RunPrune(dockerCli *command.DockerCli) (uint64, string, error) {
output, err := runPrune(dockerCli, pruneOptions{force: true})
return 0, output, err
}

View file

@ -4,6 +4,7 @@ import (
"github.com/docker/docker/cli/command"
"github.com/docker/docker/cli/command/container"
"github.com/docker/docker/cli/command/image"
"github.com/docker/docker/cli/command/network"
"github.com/docker/docker/cli/command/volume"
"github.com/spf13/cobra"
)
@ -23,6 +24,11 @@ func NewImagePruneCommand(dockerCli *command.DockerCli) *cobra.Command {
return image.NewPruneCommand(dockerCli)
}
// NewNetworkPruneCommand returns a cobra prune command for Networks
func NewNetworkPruneCommand(dockerCli *command.DockerCli) *cobra.Command {
return network.NewPruneCommand(dockerCli)
}
// RunContainerPrune executes a prune command for containers
func RunContainerPrune(dockerCli *command.DockerCli) (uint64, string, error) {
return container.RunPrune(dockerCli)
@ -37,3 +43,8 @@ func RunVolumePrune(dockerCli *command.DockerCli) (uint64, string, error) {
func RunImagePrune(dockerCli *command.DockerCli, all bool) (uint64, string, error) {
return image.RunPrune(dockerCli, all)
}
// RunNetworkPrune executes a prune command for networks
func RunNetworkPrune(dockerCli *command.DockerCli) (uint64, string, error) {
return network.RunPrune(dockerCli)
}

View file

@ -39,6 +39,7 @@ const (
warning = `WARNING! This will remove:
- all stopped containers
- all volumes not used by at least one container
- all networks not used by at least one container
%s
Are you sure you want to continue?`
@ -64,13 +65,14 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) error {
for _, pruneFn := range []func(dockerCli *command.DockerCli) (uint64, string, error){
prune.RunContainerPrune,
prune.RunVolumePrune,
prune.RunNetworkPrune,
} {
spc, output, err := pruneFn(dockerCli)
if err != nil {
return err
}
if spc > 0 {
spaceReclaimed += spc
spaceReclaimed += spc
if output != "" {
fmt.Fprintln(dockerCli.Out(), output)
}
}

View file

@ -91,6 +91,7 @@ type NetworkAPIClient interface {
NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error)
NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
NetworkRemove(ctx context.Context, networkID string) error
NetworksPrune(ctx context.Context, cfg types.NetworksPruneConfig) (types.NetworksPruneReport, error)
}
// NodeAPIClient defines API client methods for the nodes

26
client/network_prune.go Normal file
View file

@ -0,0 +1,26 @@
package client
import (
"encoding/json"
"fmt"
"github.com/docker/docker/api/types"
"golang.org/x/net/context"
)
// NetworksPrune requests the daemon to delete unused networks
func (cli *Client) NetworksPrune(ctx context.Context, cfg types.NetworksPruneConfig) (types.NetworksPruneReport, error) {
var report types.NetworksPruneReport
serverResp, err := cli.post(ctx, "/networks/prune", nil, cfg, nil)
if err != nil {
return report, err
}
defer ensureReaderClosed(serverResp)
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
return report, fmt.Errorf("Error retrieving network prune report: %v", err)
}
return report, nil
}

View file

@ -279,6 +279,7 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) {
// initMiddlewares needs cli.d to be populated. Dont change this init order.
cli.initMiddlewares(api, serverConfig)
d.SetCluster(c)
initRouter(api, d, c)
cli.setupConfigReloadTrap()

12
daemon/cluster.go Normal file
View file

@ -0,0 +1,12 @@
package daemon
import (
apitypes "github.com/docker/docker/api/types"
)
// Cluster is the interface for github.com/docker/docker/daemon/cluster.(*Cluster).
type Cluster interface {
GetNetwork(input string) (apitypes.NetworkResource, error)
GetNetworks() ([]apitypes.NetworkResource, error)
RemoveNetwork(input string) error
}

View file

@ -102,6 +102,7 @@ type Daemon struct {
containerdRemote libcontainerd.Remote
defaultIsolation containertypes.Isolation // Default isolation mode on Windows
clusterProvider cluster.Provider
cluster Cluster
}
// HasExperimental returns whether the experimental features of the daemon are enabled or not
@ -1234,3 +1235,13 @@ func copyBlkioEntry(entries []*containerd.BlkioStatsEntry) []types.BlkioStatEntr
}
return out
}
// GetCluster returns the cluster
func (daemon *Daemon) GetCluster() Cluster {
return daemon.cluster
}
// SetCluster sets the cluster
func (daemon *Daemon) SetCluster(cluster Cluster) {
daemon.cluster = cluster
}

View file

@ -1,6 +1,8 @@
package daemon
import (
"regexp"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/docker/docker/api/types"
@ -8,7 +10,9 @@ import (
"github.com/docker/docker/layer"
"github.com/docker/docker/pkg/directory"
"github.com/docker/docker/reference"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/volume"
"github.com/docker/libnetwork"
)
// ContainersPrune removes unused containers
@ -150,3 +154,72 @@ func (daemon *Daemon) ImagesPrune(config *types.ImagesPruneConfig) (*types.Image
return rep, nil
}
// localNetworksPrune removes unused local networks
func (daemon *Daemon) localNetworksPrune(config *types.NetworksPruneConfig) (*types.NetworksPruneReport, error) {
rep := &types.NetworksPruneReport{}
var err error
// When the function returns true, the walk will stop.
l := func(nw libnetwork.Network) bool {
nwName := nw.Name()
predefined := runconfig.IsPreDefinedNetwork(nwName)
if !predefined && len(nw.Endpoints()) == 0 {
if err = daemon.DeleteNetwork(nw.ID()); err != nil {
logrus.Warnf("could not remove network %s: %v", nwName, err)
return false
}
rep.NetworksDeleted = append(rep.NetworksDeleted, nwName)
}
return false
}
daemon.netController.WalkNetworks(l)
return rep, err
}
// clusterNetworksPrune removes unused cluster networks
func (daemon *Daemon) clusterNetworksPrune(config *types.NetworksPruneConfig) (*types.NetworksPruneReport, error) {
rep := &types.NetworksPruneReport{}
cluster := daemon.GetCluster()
networks, err := cluster.GetNetworks()
if err != nil {
return rep, err
}
networkIsInUse := regexp.MustCompile(`network ([[:alnum:]]+) is in use`)
for _, nw := range networks {
if nw.Name == "ingress" {
continue
}
// https://github.com/docker/docker/issues/24186
// `docker network inspect` unfortunately displays ONLY those containers that are local to that node.
// So we try to remove it anyway and check the error
err = cluster.RemoveNetwork(nw.ID)
if err != nil {
// we can safely ignore the "network .. is in use" error
match := networkIsInUse.FindStringSubmatch(err.Error())
if len(match) != 2 || match[1] != nw.ID {
logrus.Warnf("could not remove network %s: %v", nw.Name, err)
}
continue
}
rep.NetworksDeleted = append(rep.NetworksDeleted, nw.Name)
}
return rep, nil
}
// NetworksPrune removes unused networks
func (daemon *Daemon) NetworksPrune(config *types.NetworksPruneConfig) (*types.NetworksPruneReport, error) {
rep := &types.NetworksPruneReport{}
clusterRep, err := daemon.clusterNetworksPrune(config)
if err != nil {
logrus.Warnf("could not remove cluster networks: %v", err)
} else {
rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...)
}
localRep, err := daemon.localNetworksPrune(config)
if err != nil {
logrus.Warnf("could not remove local networks: %v", err)
} else {
rep.NetworksDeleted = append(rep.NetworksDeleted, localRep.NetworksDeleted...)
}
return rep, err
}

View file

@ -157,8 +157,10 @@ This section lists each version from latest to oldest. Each listing includes a
* `POST /containers/prune` prunes stopped containers.
* `POST /images/prune` prunes unused images.
* `POST /volumes/prune` prunes unused volumes.
* `POST /networks/prune` prunes unused networks.
* Every API response now includes a `Docker-Experimental` header specifying if experimental features are enabled (value can be `true` or `false`).
### v1.24 API changes
[Docker Remote API v1.24](docker_remote_api_v1.24.md) documentation

View file

@ -3881,6 +3881,36 @@ Instruct the driver to remove the network (`id`).
- **404** - no such network
- **500** - server error
### Prune unused networks
`POST /networks/prune`
Delete unused networks
**Example request**:
POST /networks/prune HTTP/1.1
Content-Type: application/json
{
}
**Example response**:
HTTP/1.1 200 OK
Content-Type: application/json
{
"NetworksDeleted": [
"n1"
],
}
**Status codes**:
- **200** no error
- **500** server error
## 3.6 Plugins
### List plugins

View file

@ -43,4 +43,5 @@ Total reclaimed space: 212 B
* [system df](system_df.md)
* [volume prune](volume_prune.md)
* [image prune](image_prune.md)
* [network prune](network_prune.md)
* [system prune](system_prune.md)

View file

@ -67,4 +67,5 @@ Total reclaimed space: 16.43 MB
* [system df](system_df.md)
* [container prune](container_prune.md)
* [volume prune](volume_prune.md)
* [network prune](network_prune.md)
* [system prune](system_prune.md)

View file

@ -98,5 +98,6 @@ You can connect a container to one or more networks. The networks need not be th
* [network disconnect](network_disconnect.md)
* [network ls](network_ls.md)
* [network rm](network_rm.md)
* [network prune](network_prune.md)
* [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)
* [Work with networks](https://docs.docker.com/engine/userguide/networking/work-with-networks/)

View file

@ -197,4 +197,5 @@ to create an externally isolated `overlay` network, you can specify the
* [network disconnect](network_disconnect.md)
* [network ls](network_ls.md)
* [network rm](network_rm.md)
* [network prune](network_prune.md)
* [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)

View file

@ -39,4 +39,5 @@ Disconnects a container from a network. The container must be running to disconn
* [network create](network_create.md)
* [network ls](network_ls.md)
* [network rm](network_rm.md)
* [network prune](network_prune.md)
* [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)

View file

@ -128,4 +128,5 @@ $ docker network inspect simple-network
* [network create](network_create.md)
* [network ls](network_ls.md)
* [network rm](network_rm.md)
* [network prune](network_prune.md)
* [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)

View file

@ -214,4 +214,5 @@ d1584f8dc718: host
* [network create](network_create.md)
* [network inspect](network_inspect.md)
* [network rm](network_rm.md)
* [network prune](network_prune.md)
* [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)

View file

@ -0,0 +1,45 @@
---
title: "network prune"
description: "Remove unused networks"
keywords: [network, prune, delete]
---
# network prune
```markdown
Usage: docker network prune [OPTIONS]
Remove all unused networks
Options:
-f, --force Do not prompt for confirmation
--help Print usage
```
Remove all unused networks. Unused networks are those which are not referenced by any containers.
Example output:
```bash
$ docker network prune
WARNING! This will remove all networks not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Networks:
n1
n2
```
## Related information
* [network disconnect ](network_disconnect.md)
* [network connect](network_connect.md)
* [network create](network_create.md)
* [network ls](network_ls.md)
* [network inspect](network_inspect.md)
* [network rm](network_rm.md)
* [Understand Docker container networks](../../userguide/networking/index.md)
* [system df](system_df.md)
* [container prune](container_prune.md)
* [image prune](image_prune.md)
* [volume prune](volume_prune.md)
* [system prune](system_prune.md)

View file

@ -55,4 +55,5 @@ deletion.
* [network create](network_create.md)
* [network ls](network_ls.md)
* [network inspect](network_inspect.md)
* [network prune](network_prune.md)
* [Understand Docker container networks](https://docs.docker.com/engine/userguide/networking/)

View file

@ -66,8 +66,11 @@ my-named-vol 0
* `UNIQUE SIZE` is the amount of space that is only used by a given image
* `SIZE` is the virtual size of the image, it is the sum of `SHARED SIZE` and `UNIQUE SIZE`
Note that network information is not shown because it doesn't consume the disk space.
## Related Information
* [system prune](system_prune.md)
* [container prune](container_prune.md)
* [volume prune](volume_prune.md)
* [image prune](image_prune.md)
* [network prune](network_prune.md)

View file

@ -26,7 +26,7 @@ Options:
--help Print usage
```
Remove all unused containers, volumes and images (both dangling and unreferenced).
Remove all unused containers, volumes, networks and images (both dangling and unreferenced).
Example output:
@ -35,6 +35,7 @@ $ docker system prune -a
WARNING! This will remove:
- all stopped containers
- all volumes not used by at least one container
- all networks not used by at least one container
- all images without at least one container associated to them
Are you sure you want to continue? [y/N] y
Deleted Containers:
@ -74,4 +75,5 @@ Total reclaimed space: 13.5 MB
* [system df](system_df.md)
* [container prune](container_prune.md)
* [image prune](image_prune.md)
* [network prune](network_prune.md)
* [system prune](system_prune.md)

View file

@ -50,4 +50,5 @@ Total reclaimed space: 36 B
* [system df](system_df.md)
* [container prune](container_prune.md)
* [image prune](image_prune.md)
* [network prune](network_prune.md)
* [system prune](system_prune.md)

View file

@ -0,0 +1,61 @@
// +build !windows
package main
import (
"strconv"
"strings"
"github.com/docker/docker/pkg/integration/checker"
"github.com/go-check/check"
)
func pruneNetworkAndVerify(c *check.C, d *SwarmDaemon, kept, pruned []string) {
_, err := d.Cmd("network", "prune", "--force")
c.Assert(err, checker.IsNil)
out, err := d.Cmd("network", "ls", "--format", "{{.Name}}")
c.Assert(err, checker.IsNil)
for _, s := range kept {
c.Assert(out, checker.Contains, s)
}
for _, s := range pruned {
c.Assert(out, checker.Not(checker.Contains), s)
}
}
func (s *DockerSwarmSuite) TestPruneNetwork(c *check.C) {
d := s.AddDaemon(c, true, true)
_, err := d.Cmd("network", "create", "n1") // used by container (testprune)
c.Assert(err, checker.IsNil)
_, err = d.Cmd("network", "create", "n2")
c.Assert(err, checker.IsNil)
_, err = d.Cmd("network", "create", "n3", "--driver", "overlay") // used by service (testprunesvc)
c.Assert(err, checker.IsNil)
_, err = d.Cmd("network", "create", "n4", "--driver", "overlay")
c.Assert(err, checker.IsNil)
cName := "testprune"
_, err = d.Cmd("run", "-d", "--name", cName, "--net", "n1", "busybox", "top")
c.Assert(err, checker.IsNil)
serviceName := "testprunesvc"
replicas := 1
out, err := d.Cmd("service", "create", "--name", serviceName,
"--replicas", strconv.Itoa(replicas),
"--network", "n3",
"busybox", "top")
c.Assert(err, checker.IsNil)
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, replicas+1)
// prune and verify
pruneNetworkAndVerify(c, d, []string{"n1", "n3"}, []string{"n2", "n4"})
// remove containers, then prune and verify again
_, err = d.Cmd("rm", "-f", cName)
c.Assert(err, checker.IsNil)
_, err = d.Cmd("service", "rm", serviceName)
c.Assert(err, checker.IsNil)
waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 0)
pruneNetworkAndVerify(c, d, []string{}, []string{"n1", "n3"})
}