Browse code

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

Signed-off-by: Santhosh Manohar <santhosh@docker.com>

Santhosh Manohar authored on 2017/03/10 04:42:10
Showing 13 changed files
... ...
@@ -4,10 +4,12 @@ import (
4 4
 	"encoding/json"
5 5
 	"fmt"
6 6
 	"net/http"
7
+	"strconv"
7 8
 	"strings"
8 9
 
9 10
 	"golang.org/x/net/context"
10 11
 
12
+	"github.com/docker/docker/api/errors"
11 13
 	"github.com/docker/docker/api/server/httputils"
12 14
 	"github.com/docker/docker/api/types"
13 15
 	"github.com/docker/docker/api/types/filters"
... ...
@@ -65,7 +67,7 @@ SKIP:
65 65
 		// run across all the networks. Starting API version 1.27, this detailed
66 66
 		// info is available for network specific GET API (equivalent to inspect)
67 67
 		if versions.LessThan(httputils.VersionFromContext(ctx), "1.27") {
68
-			nr = n.buildDetailedNetworkResources(nw)
68
+			nr = n.buildDetailedNetworkResources(nw, false)
69 69
 		} else {
70 70
 			nr = n.buildNetworkResource(nw)
71 71
 		}
... ...
@@ -85,6 +87,16 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
85 85
 	}
86 86
 
87 87
 	term := vars["id"]
88
+	var (
89
+		verbose bool
90
+		err     error
91
+	)
92
+	if v := r.URL.Query().Get("verbose"); v != "" {
93
+		if verbose, err = strconv.ParseBool(v); err != nil {
94
+			err = fmt.Errorf("invalid value for verbose: %s", v)
95
+			return errors.NewBadRequestError(err)
96
+		}
97
+	}
88 98
 
89 99
 	// In case multiple networks have duplicate names, return error.
90 100
 	// 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
100 100
 	nw := n.backend.GetNetworks()
101 101
 	for _, network := range nw {
102 102
 		if network.ID() == term {
103
-			return httputils.WriteJSON(w, http.StatusOK, *n.buildDetailedNetworkResources(network))
103
+			return httputils.WriteJSON(w, http.StatusOK, *n.buildDetailedNetworkResources(network, verbose))
104 104
 		}
105 105
 		if network.Name() == term {
106 106
 			// No need to check the ID collision here as we are still in
107 107
 			// local scope and the network ID is unique in this scope.
108
-			listByFullName[network.ID()] = *n.buildDetailedNetworkResources(network)
108
+			listByFullName[network.ID()] = *n.buildDetailedNetworkResources(network, verbose)
109 109
 		}
110 110
 		if strings.HasPrefix(network.ID(), term) {
111 111
 			// No need to check the ID collision here as we are still in
112 112
 			// local scope and the network ID is unique in this scope.
113
-			listByPartialID[network.ID()] = *n.buildDetailedNetworkResources(network)
113
+			listByPartialID[network.ID()] = *n.buildDetailedNetworkResources(network, verbose)
114 114
 		}
115 115
 	}
116 116
 
... ...
@@ -294,7 +306,7 @@ func (n *networkRouter) buildNetworkResource(nw libnetwork.Network) *types.Netwo
294 294
 	return r
295 295
 }
296 296
 
297
-func (n *networkRouter) buildDetailedNetworkResources(nw libnetwork.Network) *types.NetworkResource {
297
+func (n *networkRouter) buildDetailedNetworkResources(nw libnetwork.Network, verbose bool) *types.NetworkResource {
298 298
 	if nw == nil {
299 299
 		return &types.NetworkResource{}
300 300
 	}
... ...
@@ -315,6 +327,28 @@ func (n *networkRouter) buildDetailedNetworkResources(nw libnetwork.Network) *ty
315 315
 
316 316
 		r.Containers[key] = buildEndpointResource(tmpID, e.Name(), ei)
317 317
 	}
318
+	if !verbose {
319
+		return r
320
+	}
321
+	services := nw.Info().Services()
322
+	r.Services = make(map[string]network.ServiceInfo)
323
+	for name, service := range services {
324
+		tasks := []network.Task{}
325
+		for _, t := range service.Tasks {
326
+			tasks = append(tasks, network.Task{
327
+				Name:       t.Name,
328
+				EndpointID: t.EndpointID,
329
+				EndpointIP: t.EndpointIP,
330
+				Info:       t.Info,
331
+			})
332
+		}
333
+		r.Services[name] = network.ServiceInfo{
334
+			VIP:          service.VIP,
335
+			Ports:        service.Ports,
336
+			Tasks:        tasks,
337
+			LocalLBIndex: service.LocalLBIndex,
338
+		}
339
+	}
318 340
 	return r
319 341
 }
320 342
 
... ...
@@ -6265,6 +6265,11 @@ paths:
6265 6265
           description: "Network ID or name"
6266 6266
           required: true
6267 6267
           type: "string"
6268
+        - name: "verbose"
6269
+          in: "query"
6270
+          description: "Detailed inspect output for troubleshooting"
6271
+          type: "boolean"
6272
+          default: false
6268 6273
       tags: ["Network"]
6269 6274
 
6270 6275
     delete:
... ...
@@ -60,6 +60,22 @@ type EndpointSettings struct {
60 60
 	MacAddress          string
61 61
 }
62 62
 
63
+// Task carries the information about one backend task
64
+type Task struct {
65
+	Name       string
66
+	EndpointID string
67
+	EndpointIP string
68
+	Info       map[string]string
69
+}
70
+
71
+// ServiceInfo represents service parameters with the list of service's tasks
72
+type ServiceInfo struct {
73
+	VIP          string
74
+	Ports        []string
75
+	LocalLBIndex int
76
+	Tasks        []Task
77
+}
78
+
63 79
 // Copy makes a deep copy of `EndpointSettings`
64 80
 func (es *EndpointSettings) Copy() *EndpointSettings {
65 81
 	epCopy := *es
... ...
@@ -390,19 +390,20 @@ type MountPoint struct {
390 390
 
391 391
 // NetworkResource is the body of the "get network" http response message
392 392
 type NetworkResource struct {
393
-	Name       string                      // Name is the requested name of the network
394
-	ID         string                      `json:"Id"` // ID uniquely identifies a network on a single machine
395
-	Created    time.Time                   // Created is the time the network created
396
-	Scope      string                      // Scope describes the level at which the network exists (e.g. `global` for cluster-wide or `local` for machine level)
397
-	Driver     string                      // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`)
398
-	EnableIPv6 bool                        // EnableIPv6 represents whether to enable IPv6
399
-	IPAM       network.IPAM                // IPAM is the network's IP Address Management
400
-	Internal   bool                        // Internal represents if the network is used internal only
401
-	Attachable bool                        // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode.
402
-	Containers map[string]EndpointResource // Containers contains endpoints belonging to the network
403
-	Options    map[string]string           // Options holds the network specific options to use for when creating the network
404
-	Labels     map[string]string           // Labels holds metadata specific to the network being created
405
-	Peers      []network.PeerInfo          `json:",omitempty"` // List of peer nodes for an overlay network
393
+	Name       string                         // Name is the requested name of the network
394
+	ID         string                         `json:"Id"` // ID uniquely identifies a network on a single machine
395
+	Created    time.Time                      // Created is the time the network created
396
+	Scope      string                         // Scope describes the level at which the network exists (e.g. `global` for cluster-wide or `local` for machine level)
397
+	Driver     string                         // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`)
398
+	EnableIPv6 bool                           // EnableIPv6 represents whether to enable IPv6
399
+	IPAM       network.IPAM                   // IPAM is the network's IP Address Management
400
+	Internal   bool                           // Internal represents if the network is used internal only
401
+	Attachable bool                           // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode.
402
+	Containers map[string]EndpointResource    // Containers contains endpoints belonging to the network
403
+	Options    map[string]string              // Options holds the network specific options to use for when creating the network
404
+	Labels     map[string]string              // Labels holds metadata specific to the network being created
405
+	Peers      []network.PeerInfo             `json:",omitempty"` // List of peer nodes for an overlay network
406
+	Services   map[string]network.ServiceInfo `json:",omitempty"`
406 407
 }
407 408
 
408 409
 // EndpointResource contains network resources allocated and used for a container in a network
... ...
@@ -10,8 +10,9 @@ import (
10 10
 )
11 11
 
12 12
 type inspectOptions struct {
13
-	format string
14
-	names  []string
13
+	format  string
14
+	names   []string
15
+	verbose bool
15 16
 }
16 17
 
17 18
 func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
... ...
@@ -28,6 +29,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
28 28
 	}
29 29
 
30 30
 	cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
31
+	cmd.Flags().BoolVarP(&opts.verbose, "verbose", "v", false, "Verbose output for diagnostics")
31 32
 
32 33
 	return cmd
33 34
 }
... ...
@@ -38,7 +40,7 @@ func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
38 38
 	ctx := context.Background()
39 39
 
40 40
 	getNetFunc := func(name string) (interface{}, []byte, error) {
41
-		return client.NetworkInspectWithRaw(ctx, name)
41
+		return client.NetworkInspectWithRaw(ctx, name, opts.verbose)
42 42
 	}
43 43
 
44 44
 	return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getNetFunc)
... ...
@@ -140,7 +140,7 @@ func validateExternalNetworks(
140 140
 	client := dockerCli.Client()
141 141
 
142 142
 	for _, networkName := range externalNetworks {
143
-		network, err := client.NetworkInspect(ctx, networkName)
143
+		network, err := client.NetworkInspect(ctx, networkName, false)
144 144
 		if err != nil {
145 145
 			if dockerclient.IsErrNetworkNotFound(err) {
146 146
 				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)
... ...
@@ -67,7 +67,7 @@ func inspectImages(ctx context.Context, dockerCli *command.DockerCli) inspect.Ge
67 67
 
68 68
 func inspectNetwork(ctx context.Context, dockerCli *command.DockerCli) inspect.GetRefFunc {
69 69
 	return func(ref string) (interface{}, []byte, error) {
70
-		return dockerCli.Client().NetworkInspectWithRaw(ctx, ref)
70
+		return dockerCli.Client().NetworkInspectWithRaw(ctx, ref, false)
71 71
 	}
72 72
 }
73 73
 
... ...
@@ -91,8 +91,8 @@ type NetworkAPIClient interface {
91 91
 	NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error
92 92
 	NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
93 93
 	NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error
94
-	NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error)
95
-	NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error)
94
+	NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error)
95
+	NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error)
96 96
 	NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
97 97
 	NetworkRemove(ctx context.Context, networkID string) error
98 98
 	NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error)
... ...
@@ -5,21 +5,30 @@ import (
5 5
 	"encoding/json"
6 6
 	"io/ioutil"
7 7
 	"net/http"
8
+	"net/url"
8 9
 
9 10
 	"github.com/docker/docker/api/types"
10 11
 	"golang.org/x/net/context"
11 12
 )
12 13
 
13 14
 // NetworkInspect returns the information for a specific network configured in the docker host.
14
-func (cli *Client) NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error) {
15
-	networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID)
15
+func (cli *Client) NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error) {
16
+	networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID, verbose)
16 17
 	return networkResource, err
17 18
 }
18 19
 
19 20
 // NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation.
20
-func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error) {
21
-	var networkResource types.NetworkResource
22
-	resp, err := cli.get(ctx, "/networks/"+networkID, nil, nil)
21
+func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error) {
22
+	var (
23
+		networkResource types.NetworkResource
24
+		resp            serverResponse
25
+		err             error
26
+	)
27
+	query := url.Values{}
28
+	if verbose {
29
+		query.Set("verbose", "true")
30
+	}
31
+	resp, err = cli.get(ctx, "/networks/"+networkID, query, nil)
23 32
 	if err != nil {
24 33
 		if resp.statusCode == http.StatusNotFound {
25 34
 			return networkResource, nil, networkNotFoundError{networkID}
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	"github.com/docker/docker/api/types"
13
+	"github.com/docker/docker/api/types/network"
13 14
 	"golang.org/x/net/context"
14 15
 )
15 16
 
... ...
@@ -18,7 +19,7 @@ func TestNetworkInspectError(t *testing.T) {
18 18
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
19 19
 	}
20 20
 
21
-	_, err := client.NetworkInspect(context.Background(), "nothing")
21
+	_, err := client.NetworkInspect(context.Background(), "nothing", false)
22 22
 	if err == nil || err.Error() != "Error response from daemon: Server error" {
23 23
 		t.Fatalf("expected a Server Error, got %v", err)
24 24
 	}
... ...
@@ -29,7 +30,7 @@ func TestNetworkInspectContainerNotFound(t *testing.T) {
29 29
 		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
30 30
 	}
31 31
 
32
-	_, err := client.NetworkInspect(context.Background(), "unknown")
32
+	_, err := client.NetworkInspect(context.Background(), "unknown", false)
33 33
 	if err == nil || !IsErrNetworkNotFound(err) {
34 34
 		t.Fatalf("expected a networkNotFound error, got %v", err)
35 35
 	}
... ...
@@ -46,9 +47,23 @@ func TestNetworkInspect(t *testing.T) {
46 46
 				return nil, fmt.Errorf("expected GET method, got %s", req.Method)
47 47
 			}
48 48
 
49
-			content, err := json.Marshal(types.NetworkResource{
50
-				Name: "mynetwork",
51
-			})
49
+			var (
50
+				content []byte
51
+				err     error
52
+			)
53
+			if strings.HasPrefix(req.URL.RawQuery, "verbose=true") {
54
+				s := map[string]network.ServiceInfo{
55
+					"web": {},
56
+				}
57
+				content, err = json.Marshal(types.NetworkResource{
58
+					Name:     "mynetwork",
59
+					Services: s,
60
+				})
61
+			} else {
62
+				content, err = json.Marshal(types.NetworkResource{
63
+					Name: "mynetwork",
64
+				})
65
+			}
52 66
 			if err != nil {
53 67
 				return nil, err
54 68
 			}
... ...
@@ -59,11 +74,23 @@ func TestNetworkInspect(t *testing.T) {
59 59
 		}),
60 60
 	}
61 61
 
62
-	r, err := client.NetworkInspect(context.Background(), "network_id")
62
+	r, err := client.NetworkInspect(context.Background(), "network_id", false)
63 63
 	if err != nil {
64 64
 		t.Fatal(err)
65 65
 	}
66 66
 	if r.Name != "mynetwork" {
67 67
 		t.Fatalf("expected `mynetwork`, got %s", r.Name)
68 68
 	}
69
+
70
+	r, err = client.NetworkInspect(context.Background(), "network_id", true)
71
+	if err != nil {
72
+		t.Fatal(err)
73
+	}
74
+	if r.Name != "mynetwork" {
75
+		t.Fatalf("expected `mynetwork`, got %s", r.Name)
76
+	}
77
+	_, ok := r.Services["web"]
78
+	if !ok {
79
+		t.Fatalf("expected service `web` missing in the verbose output")
80
+	}
69 81
 }
... ...
@@ -17,6 +17,7 @@ keywords: "API, Docker, rcli, REST, documentation"
17 17
 
18 18
 [Docker Engine API v1.27](https://docs.docker.com/engine/api/v1.27/) documentation
19 19
 
20
+* 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.
20 21
 * `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.
21 22
 * `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}.
22 23
 * `GET /containers/json` now supports `publish` and `expose` filters to filter containers that expose or publish certain ports.
... ...
@@ -48,7 +48,7 @@ The `network inspect` command shows the containers, by id, in its
48 48
 results. For networks backed by multi-host network driver, such as Overlay,
49 49
 this command also shows the container endpoints in other hosts in the
50 50
 cluster. These endpoints are represented as "ep-{endpoint-id}" in the output.
51
-However, for swarm-scoped networks, only the endpoints that are local to the
51
+However, for swarm mode networks, only the endpoints that are local to the
52 52
 node are shown.
53 53
 
54 54
 You can specify an alternate format to execute a given
... ...
@@ -201,6 +201,101 @@ $ docker network inspect ingress
201 201
 ]
202 202
 ```
203 203
 
204
+### Using `verbose` option for `network inspect`
205
+
206
+`docker network inspect --verbose` for swarm mode overlay networks shows service-specific
207
+details such as the service's VIP and port mappings. It also shows IPs of service tasks,
208
+and the IPs of the nodes where the tasks are running.
209
+
210
+Following is an example output for a overlay network `ov1` that has one service `s1`
211
+attached to. service `s1` in this case has three replicas.
212
+
213
+```bash
214
+$ docker network inspect --verbose ov1
215
+[
216
+    {
217
+        "Name": "ov1",
218
+        "Id": "ybmyjvao9vtzy3oorxbssj13b",
219
+        "Created": "2017-03-13T17:04:39.776106792Z",
220
+        "Scope": "swarm",
221
+        "Driver": "overlay",
222
+        "EnableIPv6": false,
223
+        "IPAM": {
224
+            "Driver": "default",
225
+            "Options": null,
226
+            "Config": [
227
+                {
228
+                    "Subnet": "10.0.0.0/24",
229
+                    "Gateway": "10.0.0.1"
230
+                }
231
+            ]
232
+        },
233
+        "Internal": false,
234
+        "Attachable": false,
235
+        "Containers": {
236
+            "020403bd88a15f60747fd25d1ad5fa1272eb740e8a97fc547d8ad07b2f721c5e": {
237
+                "Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o",
238
+                "EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e",
239
+                "MacAddress": "02:42:0a:00:00:04",
240
+                "IPv4Address": "10.0.0.4/24",
241
+                "IPv6Address": ""
242
+            }
243
+        },
244
+        "Options": {
245
+            "com.docker.network.driver.overlay.vxlanid_list": "4097"
246
+        },
247
+        "Labels": {},
248
+        "Peers": [
249
+            {
250
+                "Name": "net-3-5d3cfd30a58c",
251
+                "IP": "192.168.33.13"
252
+            },
253
+            {
254
+                "Name": "net-1-6ecbc0040a73",
255
+                "IP": "192.168.33.11"
256
+            },
257
+            {
258
+                "Name": "net-2-fb80208efd75",
259
+                "IP": "192.168.33.12"
260
+            }
261
+        ],
262
+        "Services": {
263
+            "s1": {
264
+                "VIP": "10.0.0.2",
265
+                "Ports": [],
266
+                "LocalLBIndex": 257,
267
+                "Tasks": [
268
+                    {
269
+                        "Name": "s1.2.q4hcq2aiiml25ubtrtg4q1txt",
270
+                        "EndpointID": "040879b027e55fb658e8b60ae3b87c6cdac7d291e86a190a3b5ac6567b26511a",
271
+                        "EndpointIP": "10.0.0.5",
272
+                        "Info": {
273
+                            "Host IP": "192.168.33.11"
274
+                        }
275
+                    },
276
+                    {
277
+                        "Name": "s1.3.yawl4cgkp7imkfx469kn9j6lm",
278
+                        "EndpointID": "106edff9f120efe44068b834e1cddb5b39dd4a3af70211378b2f7a9e562bbad8",
279
+                        "EndpointIP": "10.0.0.3",
280
+                        "Info": {
281
+                            "Host IP": "192.168.33.12"
282
+                        }
283
+                    },
284
+                    {
285
+                        "Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o",
286
+                        "EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e",
287
+                        "EndpointIP": "10.0.0.4",
288
+                        "Info": {
289
+                            "Host IP": "192.168.33.13"
290
+                        }
291
+                    }
292
+                ]
293
+            }
294
+        }
295
+    }
296
+]
297
+```
298
+
204 299
 ## Related commands
205 300
 
206 301
 * [network disconnect ](network_disconnect.md)
... ...
@@ -86,3 +86,96 @@ $ docker network inspect simple-network
86 86
     }
87 87
 ]
88 88
 ```
89
+
90
+`docker network inspect --verbose` for swarm mode overlay networks shows service-specific
91
+details such as the service's VIP and port mappings. It also shows IPs of service tasks,
92
+and the IPs of the nodes where the tasks are running.
93
+
94
+Following is an example output for a overlay network `ov1` that has one service `s1`
95
+attached to. service `s1` in this case has three replicas.
96
+
97
+```bash
98
+$ docker network inspect --verbose ov1
99
+[
100
+    {
101
+        "Name": "ov1",
102
+        "Id": "ybmyjvao9vtzy3oorxbssj13b",
103
+        "Created": "2017-03-13T17:04:39.776106792Z",
104
+        "Scope": "swarm",
105
+        "Driver": "overlay",
106
+        "EnableIPv6": false,
107
+        "IPAM": {
108
+            "Driver": "default",
109
+            "Options": null,
110
+            "Config": [
111
+                {
112
+                    "Subnet": "10.0.0.0/24",
113
+                    "Gateway": "10.0.0.1"
114
+                }
115
+            ]
116
+        },
117
+        "Internal": false,
118
+        "Attachable": false,
119
+        "Containers": {
120
+            "020403bd88a15f60747fd25d1ad5fa1272eb740e8a97fc547d8ad07b2f721c5e": {
121
+                "Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o",
122
+                "EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e",
123
+                "MacAddress": "02:42:0a:00:00:04",
124
+                "IPv4Address": "10.0.0.4/24",
125
+                "IPv6Address": ""
126
+            }
127
+        },
128
+        "Options": {
129
+            "com.docker.network.driver.overlay.vxlanid_list": "4097"
130
+        },
131
+        "Labels": {},
132
+        "Peers": [
133
+            {
134
+                "Name": "net-3-5d3cfd30a58c",
135
+                "IP": "192.168.33.13"
136
+            },
137
+            {
138
+                "Name": "net-1-6ecbc0040a73",
139
+                "IP": "192.168.33.11"
140
+            },
141
+            {
142
+                "Name": "net-2-fb80208efd75",
143
+                "IP": "192.168.33.12"
144
+            }
145
+        ],
146
+        "Services": {
147
+            "s1": {
148
+                "VIP": "10.0.0.2",
149
+                "Ports": [],
150
+                "LocalLBIndex": 257,
151
+                "Tasks": [
152
+                    {
153
+                        "Name": "s1.2.q4hcq2aiiml25ubtrtg4q1txt",
154
+                        "EndpointID": "040879b027e55fb658e8b60ae3b87c6cdac7d291e86a190a3b5ac6567b26511a",
155
+                        "EndpointIP": "10.0.0.5",
156
+                        "Info": {
157
+                            "Host IP": "192.168.33.11"
158
+                        }
159
+                    },
160
+                    {
161
+                        "Name": "s1.3.yawl4cgkp7imkfx469kn9j6lm",
162
+                        "EndpointID": "106edff9f120efe44068b834e1cddb5b39dd4a3af70211378b2f7a9e562bbad8",
163
+                        "EndpointIP": "10.0.0.3",
164
+                        "Info": {
165
+                            "Host IP": "192.168.33.12"
166
+                        }
167
+                    },
168
+                    {
169
+                        "Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o",
170
+                        "EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e",
171
+                        "EndpointIP": "10.0.0.4",
172
+                        "Info": {
173
+                            "Host IP": "192.168.33.13"
174
+                        }
175
+                    }
176
+                ]
177
+            }
178
+        }
179
+    }
180
+]
181
+```