瀏覽代碼

Enhance network inspect to show all tasks, local & non-local, in swarm mode

Signed-off-by: Santhosh Manohar <santhosh@docker.com>
Santhosh Manohar 8 年之前
父節點
當前提交
14f76a21db

+ 39 - 5
api/server/router/network/network_routes.go

@@ -4,10 +4,12 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
+	"strconv"
 	"strings"
 	"strings"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
+	"github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/filters"
@@ -65,7 +67,7 @@ SKIP:
 		// run across all the networks. Starting API version 1.27, this detailed
 		// run across all the networks. Starting API version 1.27, this detailed
 		// info is available for network specific GET API (equivalent to inspect)
 		// info is available for network specific GET API (equivalent to inspect)
 		if versions.LessThan(httputils.VersionFromContext(ctx), "1.27") {
 		if versions.LessThan(httputils.VersionFromContext(ctx), "1.27") {
-			nr = n.buildDetailedNetworkResources(nw)
+			nr = n.buildDetailedNetworkResources(nw, false)
 		} else {
 		} else {
 			nr = n.buildNetworkResource(nw)
 			nr = n.buildNetworkResource(nw)
 		}
 		}
@@ -85,6 +87,16 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
 	}
 	}
 
 
 	term := vars["id"]
 	term := vars["id"]
+	var (
+		verbose bool
+		err     error
+	)
+	if v := r.URL.Query().Get("verbose"); v != "" {
+		if verbose, err = strconv.ParseBool(v); err != nil {
+			err = fmt.Errorf("invalid value for verbose: %s", v)
+			return errors.NewBadRequestError(err)
+		}
+	}
 
 
 	// In case multiple networks have duplicate names, return error.
 	// In case multiple networks have duplicate names, return error.
 	// TODO (yongtang): should we wrap with version here for backward compatibility?
 	// TODO (yongtang): should we wrap with version here for backward compatibility?
@@ -100,17 +112,17 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
 	nw := n.backend.GetNetworks()
 	nw := n.backend.GetNetworks()
 	for _, network := range nw {
 	for _, network := range nw {
 		if network.ID() == term {
 		if network.ID() == term {
-			return httputils.WriteJSON(w, http.StatusOK, *n.buildDetailedNetworkResources(network))
+			return httputils.WriteJSON(w, http.StatusOK, *n.buildDetailedNetworkResources(network, verbose))
 		}
 		}
 		if network.Name() == term {
 		if network.Name() == term {
 			// No need to check the ID collision here as we are still in
 			// No need to check the ID collision here as we are still in
 			// local scope and the network ID is unique in this scope.
 			// local scope and the network ID is unique in this scope.
-			listByFullName[network.ID()] = *n.buildDetailedNetworkResources(network)
+			listByFullName[network.ID()] = *n.buildDetailedNetworkResources(network, verbose)
 		}
 		}
 		if strings.HasPrefix(network.ID(), term) {
 		if strings.HasPrefix(network.ID(), term) {
 			// No need to check the ID collision here as we are still in
 			// No need to check the ID collision here as we are still in
 			// local scope and the network ID is unique in this scope.
 			// local scope and the network ID is unique in this scope.
-			listByPartialID[network.ID()] = *n.buildDetailedNetworkResources(network)
+			listByPartialID[network.ID()] = *n.buildDetailedNetworkResources(network, verbose)
 		}
 		}
 	}
 	}
 
 
@@ -294,7 +306,7 @@ func (n *networkRouter) buildNetworkResource(nw libnetwork.Network) *types.Netwo
 	return r
 	return r
 }
 }
 
 
-func (n *networkRouter) buildDetailedNetworkResources(nw libnetwork.Network) *types.NetworkResource {
+func (n *networkRouter) buildDetailedNetworkResources(nw libnetwork.Network, verbose bool) *types.NetworkResource {
 	if nw == nil {
 	if nw == nil {
 		return &types.NetworkResource{}
 		return &types.NetworkResource{}
 	}
 	}
@@ -315,6 +327,28 @@ func (n *networkRouter) buildDetailedNetworkResources(nw libnetwork.Network) *ty
 
 
 		r.Containers[key] = buildEndpointResource(tmpID, e.Name(), ei)
 		r.Containers[key] = buildEndpointResource(tmpID, e.Name(), ei)
 	}
 	}
+	if !verbose {
+		return r
+	}
+	services := nw.Info().Services()
+	r.Services = make(map[string]network.ServiceInfo)
+	for name, service := range services {
+		tasks := []network.Task{}
+		for _, t := range service.Tasks {
+			tasks = append(tasks, network.Task{
+				Name:       t.Name,
+				EndpointID: t.EndpointID,
+				EndpointIP: t.EndpointIP,
+				Info:       t.Info,
+			})
+		}
+		r.Services[name] = network.ServiceInfo{
+			VIP:          service.VIP,
+			Ports:        service.Ports,
+			Tasks:        tasks,
+			LocalLBIndex: service.LocalLBIndex,
+		}
+	}
 	return r
 	return r
 }
 }
 
 

+ 5 - 0
api/swagger.yaml

@@ -6265,6 +6265,11 @@ paths:
           description: "Network ID or name"
           description: "Network ID or name"
           required: true
           required: true
           type: "string"
           type: "string"
+        - name: "verbose"
+          in: "query"
+          description: "Detailed inspect output for troubleshooting"
+          type: "boolean"
+          default: false
       tags: ["Network"]
       tags: ["Network"]
 
 
     delete:
     delete:

+ 16 - 0
api/types/network/network.go

@@ -60,6 +60,22 @@ type EndpointSettings struct {
 	MacAddress          string
 	MacAddress          string
 }
 }
 
 
+// Task carries the information about one backend task
+type Task struct {
+	Name       string
+	EndpointID string
+	EndpointIP string
+	Info       map[string]string
+}
+
+// ServiceInfo represents service parameters with the list of service's tasks
+type ServiceInfo struct {
+	VIP          string
+	Ports        []string
+	LocalLBIndex int
+	Tasks        []Task
+}
+
 // Copy makes a deep copy of `EndpointSettings`
 // Copy makes a deep copy of `EndpointSettings`
 func (es *EndpointSettings) Copy() *EndpointSettings {
 func (es *EndpointSettings) Copy() *EndpointSettings {
 	epCopy := *es
 	epCopy := *es

+ 14 - 13
api/types/types.go

@@ -390,19 +390,20 @@ type MountPoint struct {
 
 
 // NetworkResource is the body of the "get network" http response message
 // NetworkResource is the body of the "get network" http response message
 type NetworkResource struct {
 type NetworkResource struct {
-	Name       string                      // Name is the requested name of the network
-	ID         string                      `json:"Id"` // ID uniquely identifies a network on a single machine
-	Created    time.Time                   // Created is the time the network created
-	Scope      string                      // Scope describes the level at which the network exists (e.g. `global` for cluster-wide or `local` for machine level)
-	Driver     string                      // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`)
-	EnableIPv6 bool                        // EnableIPv6 represents whether to enable IPv6
-	IPAM       network.IPAM                // IPAM is the network's IP Address Management
-	Internal   bool                        // Internal represents if the network is used internal only
-	Attachable bool                        // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode.
-	Containers map[string]EndpointResource // Containers contains endpoints belonging to the network
-	Options    map[string]string           // Options holds the network specific options to use for when creating the network
-	Labels     map[string]string           // Labels holds metadata specific to the network being created
-	Peers      []network.PeerInfo          `json:",omitempty"` // List of peer nodes for an overlay network
+	Name       string                         // Name is the requested name of the network
+	ID         string                         `json:"Id"` // ID uniquely identifies a network on a single machine
+	Created    time.Time                      // Created is the time the network created
+	Scope      string                         // Scope describes the level at which the network exists (e.g. `global` for cluster-wide or `local` for machine level)
+	Driver     string                         // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`)
+	EnableIPv6 bool                           // EnableIPv6 represents whether to enable IPv6
+	IPAM       network.IPAM                   // IPAM is the network's IP Address Management
+	Internal   bool                           // Internal represents if the network is used internal only
+	Attachable bool                           // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode.
+	Containers map[string]EndpointResource    // Containers contains endpoints belonging to the network
+	Options    map[string]string              // Options holds the network specific options to use for when creating the network
+	Labels     map[string]string              // Labels holds metadata specific to the network being created
+	Peers      []network.PeerInfo             `json:",omitempty"` // List of peer nodes for an overlay network
+	Services   map[string]network.ServiceInfo `json:",omitempty"`
 }
 }
 
 
 // EndpointResource contains network resources allocated and used for a container in a network
 // EndpointResource contains network resources allocated and used for a container in a network

+ 5 - 3
cli/command/network/inspect.go

@@ -10,8 +10,9 @@ import (
 )
 )
 
 
 type inspectOptions struct {
 type inspectOptions struct {
-	format string
-	names  []string
+	format  string
+	names   []string
+	verbose bool
 }
 }
 
 
 func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
 func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
@@ -28,6 +29,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
 	}
 	}
 
 
 	cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
 	cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
+	cmd.Flags().BoolVarP(&opts.verbose, "verbose", "v", false, "Verbose output for diagnostics")
 
 
 	return cmd
 	return cmd
 }
 }
@@ -38,7 +40,7 @@ func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
 	ctx := context.Background()
 	ctx := context.Background()
 
 
 	getNetFunc := func(name string) (interface{}, []byte, error) {
 	getNetFunc := func(name string) (interface{}, []byte, error) {
-		return client.NetworkInspectWithRaw(ctx, name)
+		return client.NetworkInspectWithRaw(ctx, name, opts.verbose)
 	}
 	}
 
 
 	return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getNetFunc)
 	return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getNetFunc)

+ 1 - 1
cli/command/stack/deploy_composefile.go

@@ -140,7 +140,7 @@ func validateExternalNetworks(
 	client := dockerCli.Client()
 	client := dockerCli.Client()
 
 
 	for _, networkName := range externalNetworks {
 	for _, networkName := range externalNetworks {
-		network, err := client.NetworkInspect(ctx, networkName)
+		network, err := client.NetworkInspect(ctx, networkName, false)
 		if err != nil {
 		if err != nil {
 			if dockerclient.IsErrNetworkNotFound(err) {
 			if dockerclient.IsErrNetworkNotFound(err) {
 				return fmt.Errorf("network %q is declared as external, but could not be found. You need to create the network before the stack is deployed (with overlay driver)", networkName)
 				return fmt.Errorf("network %q is declared as external, but could not be found. You need to create the network before the stack is deployed (with overlay driver)", networkName)

+ 1 - 1
cli/command/system/inspect.go

@@ -67,7 +67,7 @@ func inspectImages(ctx context.Context, dockerCli *command.DockerCli) inspect.Ge
 
 
 func inspectNetwork(ctx context.Context, dockerCli *command.DockerCli) inspect.GetRefFunc {
 func inspectNetwork(ctx context.Context, dockerCli *command.DockerCli) inspect.GetRefFunc {
 	return func(ref string) (interface{}, []byte, error) {
 	return func(ref string) (interface{}, []byte, error) {
-		return dockerCli.Client().NetworkInspectWithRaw(ctx, ref)
+		return dockerCli.Client().NetworkInspectWithRaw(ctx, ref, false)
 	}
 	}
 }
 }
 
 

+ 2 - 2
client/interface.go

@@ -91,8 +91,8 @@ type NetworkAPIClient interface {
 	NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error
 	NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error
 	NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
 	NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
 	NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error
 	NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error
-	NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error)
-	NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error)
+	NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error)
+	NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error)
 	NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
 	NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
 	NetworkRemove(ctx context.Context, networkID string) error
 	NetworkRemove(ctx context.Context, networkID string) error
 	NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error)
 	NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error)

+ 14 - 5
client/network_inspect.go

@@ -5,21 +5,30 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"io/ioutil"
 	"io/ioutil"
 	"net/http"
 	"net/http"
+	"net/url"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
 // NetworkInspect returns the information for a specific network configured in the docker host.
 // NetworkInspect returns the information for a specific network configured in the docker host.
-func (cli *Client) NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error) {
-	networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID)
+func (cli *Client) NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error) {
+	networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID, verbose)
 	return networkResource, err
 	return networkResource, err
 }
 }
 
 
 // NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation.
 // NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation.
-func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error) {
-	var networkResource types.NetworkResource
-	resp, err := cli.get(ctx, "/networks/"+networkID, nil, nil)
+func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error) {
+	var (
+		networkResource types.NetworkResource
+		resp            serverResponse
+		err             error
+	)
+	query := url.Values{}
+	if verbose {
+		query.Set("verbose", "true")
+	}
+	resp, err = cli.get(ctx, "/networks/"+networkID, query, nil)
 	if err != nil {
 	if err != nil {
 		if resp.statusCode == http.StatusNotFound {
 		if resp.statusCode == http.StatusNotFound {
 			return networkResource, nil, networkNotFoundError{networkID}
 			return networkResource, nil, networkNotFoundError{networkID}

+ 33 - 6
client/network_inspect_test.go

@@ -10,6 +10,7 @@ import (
 	"testing"
 	"testing"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/network"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
@@ -18,7 +19,7 @@ func TestNetworkInspectError(t *testing.T) {
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
 	}
 	}
 
 
-	_, err := client.NetworkInspect(context.Background(), "nothing")
+	_, err := client.NetworkInspect(context.Background(), "nothing", false)
 	if err == nil || err.Error() != "Error response from daemon: Server error" {
 	if err == nil || err.Error() != "Error response from daemon: Server error" {
 		t.Fatalf("expected a Server Error, got %v", err)
 		t.Fatalf("expected a Server Error, got %v", err)
 	}
 	}
@@ -29,7 +30,7 @@ func TestNetworkInspectContainerNotFound(t *testing.T) {
 		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
 		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
 	}
 	}
 
 
-	_, err := client.NetworkInspect(context.Background(), "unknown")
+	_, err := client.NetworkInspect(context.Background(), "unknown", false)
 	if err == nil || !IsErrNetworkNotFound(err) {
 	if err == nil || !IsErrNetworkNotFound(err) {
 		t.Fatalf("expected a networkNotFound error, got %v", err)
 		t.Fatalf("expected a networkNotFound error, got %v", err)
 	}
 	}
@@ -46,9 +47,23 @@ func TestNetworkInspect(t *testing.T) {
 				return nil, fmt.Errorf("expected GET method, got %s", req.Method)
 				return nil, fmt.Errorf("expected GET method, got %s", req.Method)
 			}
 			}
 
 
-			content, err := json.Marshal(types.NetworkResource{
-				Name: "mynetwork",
-			})
+			var (
+				content []byte
+				err     error
+			)
+			if strings.HasPrefix(req.URL.RawQuery, "verbose=true") {
+				s := map[string]network.ServiceInfo{
+					"web": {},
+				}
+				content, err = json.Marshal(types.NetworkResource{
+					Name:     "mynetwork",
+					Services: s,
+				})
+			} else {
+				content, err = json.Marshal(types.NetworkResource{
+					Name: "mynetwork",
+				})
+			}
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
@@ -59,11 +74,23 @@ func TestNetworkInspect(t *testing.T) {
 		}),
 		}),
 	}
 	}
 
 
-	r, err := client.NetworkInspect(context.Background(), "network_id")
+	r, err := client.NetworkInspect(context.Background(), "network_id", false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	if r.Name != "mynetwork" {
 	if r.Name != "mynetwork" {
 		t.Fatalf("expected `mynetwork`, got %s", r.Name)
 		t.Fatalf("expected `mynetwork`, got %s", r.Name)
 	}
 	}
+
+	r, err = client.NetworkInspect(context.Background(), "network_id", true)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if r.Name != "mynetwork" {
+		t.Fatalf("expected `mynetwork`, got %s", r.Name)
+	}
+	_, ok := r.Services["web"]
+	if !ok {
+		t.Fatalf("expected service `web` missing in the verbose output")
+	}
 }
 }

+ 1 - 0
docs/api/version-history.md

@@ -17,6 +17,7 @@ keywords: "API, Docker, rcli, REST, documentation"
 
 
 [Docker Engine API v1.27](https://docs.docker.com/engine/api/v1.27/) documentation
 [Docker Engine API v1.27](https://docs.docker.com/engine/api/v1.27/) documentation
 
 
+* Optional query parameter `verbose` for `GET /networks/(id or name)` will now list all services with all the tasks, including the non-local tasks on the given network.
 * `GET /containers/(id or name)/attach/ws` now returns WebSocket in binary frame format for API version >= v1.27, and returns WebSocket in text frame format for API version< v1.27, for the purpose of backward-compatibility.
 * `GET /containers/(id or name)/attach/ws` now returns WebSocket in binary frame format for API version >= v1.27, and returns WebSocket in text frame format for API version< v1.27, for the purpose of backward-compatibility.
 * `GET /networks` is optimised only to return list of all networks and network specific information. List of all containers attached to a specific network is removed from this API and is only available using the network specific `GET /networks/{network-id}.
 * `GET /networks` is optimised only to return list of all networks and network specific information. List of all containers attached to a specific network is removed from this API and is only available using the network specific `GET /networks/{network-id}.
 * `GET /containers/json` now supports `publish` and `expose` filters to filter containers that expose or publish certain ports.
 * `GET /containers/json` now supports `publish` and `expose` filters to filter containers that expose or publish certain ports.

+ 96 - 1
docs/reference/commandline/network_inspect.md

@@ -48,7 +48,7 @@ The `network inspect` command shows the containers, by id, in its
 results. For networks backed by multi-host network driver, such as Overlay,
 results. For networks backed by multi-host network driver, such as Overlay,
 this command also shows the container endpoints in other hosts in the
 this command also shows the container endpoints in other hosts in the
 cluster. These endpoints are represented as "ep-{endpoint-id}" in the output.
 cluster. These endpoints are represented as "ep-{endpoint-id}" in the output.
-However, for swarm-scoped networks, only the endpoints that are local to the
+However, for swarm mode networks, only the endpoints that are local to the
 node are shown.
 node are shown.
 
 
 You can specify an alternate format to execute a given
 You can specify an alternate format to execute a given
@@ -201,6 +201,101 @@ $ docker network inspect ingress
 ]
 ]
 ```
 ```
 
 
+### Using `verbose` option for `network inspect`
+
+`docker network inspect --verbose` for swarm mode overlay networks shows service-specific
+details such as the service's VIP and port mappings. It also shows IPs of service tasks,
+and the IPs of the nodes where the tasks are running.
+
+Following is an example output for a overlay network `ov1` that has one service `s1`
+attached to. service `s1` in this case has three replicas.
+
+```bash
+$ docker network inspect --verbose ov1
+[
+    {
+        "Name": "ov1",
+        "Id": "ybmyjvao9vtzy3oorxbssj13b",
+        "Created": "2017-03-13T17:04:39.776106792Z",
+        "Scope": "swarm",
+        "Driver": "overlay",
+        "EnableIPv6": false,
+        "IPAM": {
+            "Driver": "default",
+            "Options": null,
+            "Config": [
+                {
+                    "Subnet": "10.0.0.0/24",
+                    "Gateway": "10.0.0.1"
+                }
+            ]
+        },
+        "Internal": false,
+        "Attachable": false,
+        "Containers": {
+            "020403bd88a15f60747fd25d1ad5fa1272eb740e8a97fc547d8ad07b2f721c5e": {
+                "Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o",
+                "EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e",
+                "MacAddress": "02:42:0a:00:00:04",
+                "IPv4Address": "10.0.0.4/24",
+                "IPv6Address": ""
+            }
+        },
+        "Options": {
+            "com.docker.network.driver.overlay.vxlanid_list": "4097"
+        },
+        "Labels": {},
+        "Peers": [
+            {
+                "Name": "net-3-5d3cfd30a58c",
+                "IP": "192.168.33.13"
+            },
+            {
+                "Name": "net-1-6ecbc0040a73",
+                "IP": "192.168.33.11"
+            },
+            {
+                "Name": "net-2-fb80208efd75",
+                "IP": "192.168.33.12"
+            }
+        ],
+        "Services": {
+            "s1": {
+                "VIP": "10.0.0.2",
+                "Ports": [],
+                "LocalLBIndex": 257,
+                "Tasks": [
+                    {
+                        "Name": "s1.2.q4hcq2aiiml25ubtrtg4q1txt",
+                        "EndpointID": "040879b027e55fb658e8b60ae3b87c6cdac7d291e86a190a3b5ac6567b26511a",
+                        "EndpointIP": "10.0.0.5",
+                        "Info": {
+                            "Host IP": "192.168.33.11"
+                        }
+                    },
+                    {
+                        "Name": "s1.3.yawl4cgkp7imkfx469kn9j6lm",
+                        "EndpointID": "106edff9f120efe44068b834e1cddb5b39dd4a3af70211378b2f7a9e562bbad8",
+                        "EndpointIP": "10.0.0.3",
+                        "Info": {
+                            "Host IP": "192.168.33.12"
+                        }
+                    },
+                    {
+                        "Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o",
+                        "EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e",
+                        "EndpointIP": "10.0.0.4",
+                        "Info": {
+                            "Host IP": "192.168.33.13"
+                        }
+                    }
+                ]
+            }
+        }
+    }
+]
+```
+
 ## Related commands
 ## Related commands
 
 
 * [network disconnect ](network_disconnect.md)
 * [network disconnect ](network_disconnect.md)

+ 93 - 0
man/src/network/inspect.md

@@ -86,3 +86,96 @@ $ docker network inspect simple-network
     }
     }
 ]
 ]
 ```
 ```
+
+`docker network inspect --verbose` for swarm mode overlay networks shows service-specific
+details such as the service's VIP and port mappings. It also shows IPs of service tasks,
+and the IPs of the nodes where the tasks are running.
+
+Following is an example output for a overlay network `ov1` that has one service `s1`
+attached to. service `s1` in this case has three replicas.
+
+```bash
+$ docker network inspect --verbose ov1
+[
+    {
+        "Name": "ov1",
+        "Id": "ybmyjvao9vtzy3oorxbssj13b",
+        "Created": "2017-03-13T17:04:39.776106792Z",
+        "Scope": "swarm",
+        "Driver": "overlay",
+        "EnableIPv6": false,
+        "IPAM": {
+            "Driver": "default",
+            "Options": null,
+            "Config": [
+                {
+                    "Subnet": "10.0.0.0/24",
+                    "Gateway": "10.0.0.1"
+                }
+            ]
+        },
+        "Internal": false,
+        "Attachable": false,
+        "Containers": {
+            "020403bd88a15f60747fd25d1ad5fa1272eb740e8a97fc547d8ad07b2f721c5e": {
+                "Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o",
+                "EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e",
+                "MacAddress": "02:42:0a:00:00:04",
+                "IPv4Address": "10.0.0.4/24",
+                "IPv6Address": ""
+            }
+        },
+        "Options": {
+            "com.docker.network.driver.overlay.vxlanid_list": "4097"
+        },
+        "Labels": {},
+        "Peers": [
+            {
+                "Name": "net-3-5d3cfd30a58c",
+                "IP": "192.168.33.13"
+            },
+            {
+                "Name": "net-1-6ecbc0040a73",
+                "IP": "192.168.33.11"
+            },
+            {
+                "Name": "net-2-fb80208efd75",
+                "IP": "192.168.33.12"
+            }
+        ],
+        "Services": {
+            "s1": {
+                "VIP": "10.0.0.2",
+                "Ports": [],
+                "LocalLBIndex": 257,
+                "Tasks": [
+                    {
+                        "Name": "s1.2.q4hcq2aiiml25ubtrtg4q1txt",
+                        "EndpointID": "040879b027e55fb658e8b60ae3b87c6cdac7d291e86a190a3b5ac6567b26511a",
+                        "EndpointIP": "10.0.0.5",
+                        "Info": {
+                            "Host IP": "192.168.33.11"
+                        }
+                    },
+                    {
+                        "Name": "s1.3.yawl4cgkp7imkfx469kn9j6lm",
+                        "EndpointID": "106edff9f120efe44068b834e1cddb5b39dd4a3af70211378b2f7a9e562bbad8",
+                        "EndpointIP": "10.0.0.3",
+                        "Info": {
+                            "Host IP": "192.168.33.12"
+                        }
+                    },
+                    {
+                        "Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o",
+                        "EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e",
+                        "EndpointIP": "10.0.0.4",
+                        "Info": {
+                            "Host IP": "192.168.33.13"
+                        }
+                    }
+                ]
+            }
+        }
+    }
+]
+```