diff --git a/CHANGELOG.md b/CHANGELOG.md index 51de61470..e9de81dcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,113 @@ # Changelog +Items starting with `DEPRECATE` are important deprecation notices. For more +information on the list of deprecated flags and APIs please have a look at +https://docs.docker.com/misc/deprecated/ where target removal dates can also +be found. + +## 1.9.0 (2015-11-03) + +## Runtime + ++ `docker stats` now returns block IO metrics (#15005) ++ `docker stats` now details network stats per interface (#15786) ++ Add `ancestor=` filter to `docker ps --filter` flag to filter +containers based on their ancestor images (#14570) ++ Add `label=` filter to `docker ps --filter` to filter containers +based on label (#16530) ++ Add `--kernel-memory` flag to `docker run` (#14006) ++ Add `--message` flag to `docker import` allowing to specify an optional +message (#15711) ++ Add `--privileged` flag to `docker exec` (#14113) ++ Add `--stop-signal` flag to `docker run` allowing to replace the container +process stopping signal (#15307) ++ Add a new `unless-stopped` restart policy (#15348) ++ Inspecting an image now returns tags (#13185) ++ Add container size information to `docker inspect` (#15796) ++ Add `RepoTags` and `RepoDigests` field to `/images/{name:.*}/json` (#17275) +- Remove the deprecated `/container/ps` endpoint from the API (#15972) +- Send and document correct HTTP codes for `/exec//start` (#16250) +- Share shm and mqueue between containers sharing IPC namespace (#15862) +- Event stream now shows OOM status when `--oom-kill-disable` is set (#16235) +- Ensure special network files (/etc/hosts etc.) are read-only if bind-mounted +with `ro` option (#14965) +- Improve `rmi` performance (#16890) +- Do not update /etc/hosts for the default bridge network, except for links (#17325) +- Fix conflict with duplicate container names (#17389) +- Fix an issue with incorrect template execution in `docker inspect` (#17284) +- DEPRECATE `-c` short flag variant for `--cpu-shares` in docker run (#16271) + +## Client + ++ Allow `docker import` to import from local files (#11907) + +## Builder + ++ Add a `STOPSIGNAL` Dockerfile instruction allowing to set a different +stop-signal for the container process (#15307) ++ Add an `ARG` Dockerfile instruction and a `--build-arg` flag to `docker build` +that allows to add build-time environment variables (#15182) +- Improve cache miss performance (#16890) + +## Storage + +- devicemapper: Implement deferred deletion capability (#16381) + +## Networking + ++ `docker network` exits experimental and is part of standard release (#16645) ++ New network top-level concept, with associated subcommands and API (#16645) + WARNING: the API is different from the experimental API ++ Support for multiple isolated/micro-segmented networks (#16645) ++ Built-in multihost networking using VXLAN based overlay driver (#14071) ++ Support for third-party network plugins (#13424) ++ Ability to dynamically connect containers to multiple networks (#16645) ++ Support for user-defined IP address management via pluggable IPAM drivers (#16910) ++ Add daemon flags `--cluster-store` and `--cluster-advertise` for built-in nodes discovery (#16229) ++ Add `--cluster-store-opt` for setting up TLS settings (#16644) ++ Add `--dns-opt` to the daemon (#16031) +- DEPRECATE following container `NetworkSettings` fields in API v1.21: `EndpointID`, `Gateway`, + `GlobalIPv6Address`, `GlobalIPv6PrefixLen`, `IPAddress`, `IPPrefixLen`, `IPv6Gateway` and `MacAddress`. + Those are now specific to the `bridge` network. Use `NetworkSettings.Networks` to inspect + the networking settings of a container per network. + +## Volumes + ++ New top-level `volume` subcommand and API (#14242) +- Move API volume driver settings to host-specific config (#15798) +- Print an error message if volume name is not unique (#16009) +- Ensure volumes created from Dockerfiles always use the local volume driver +(#15507) +- DEPRECATE auto-creating missing host paths for bind mounts (#16349) + +## Logging + ++ Add `awslogs` logging driver for Amazon CloudWatch (#15495) ++ Add generic `tag` log option to allow customizing container/image +information passed to driver (e.g. show container names) (#15384) +- Implement the `docker logs` endpoint for the journald driver (#13707) +- DEPRECATE driver-specific log tags (e.g. `syslog-tag`, etc.) (#15384) + +## Distribution + ++ `docker search` now works with partial names (#16509) +- Push optimization: avoid buffering to file (#15493) +- The daemon will display progress for images that were already being pulled +by another client (#15489) +- Only permissions required for the current action being performed are requested (#) ++ Renaming trust keys (and respective environment variables) from `offline` to +`root` and `tagging` to `repository` (#16894) +- DEPRECATE trust key environment variables +`DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE` and +`DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE` (#16894) + +## Security + ++ Add SELinux profiles to the rpm package (#15832) +- Fix various issues with AppArmor profiles provided in the deb package +(#14609) +- Add AppArmor policy that prevents writing to /proc (#15571) + ## 1.8.3 (2015-10-12) ### Distribution diff --git a/Dockerfile b/Dockerfile index 5a33fe459..57e10de71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -150,10 +150,11 @@ RUN set -x \ && rm -rf "$GOPATH" # Get the "docker-py" source so we can run their integration tests -ENV DOCKER_PY_COMMIT 139850f3f3b17357bab5ba3edfb745fb14043764 +ENV DOCKER_PY_COMMIT 47ab89ec2bd3bddf1221b856ffbaff333edeabb4 RUN git clone https://github.com/docker/docker-py.git /docker-py \ && cd /docker-py \ - && git checkout -q $DOCKER_PY_COMMIT + && git checkout -q $DOCKER_PY_COMMIT \ + && pip install -r test-requirements.txt # Setup s3cmd config RUN { \ diff --git a/VERSION b/VERSION index b57588e59..f8e233b27 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.9.0-dev +1.9.0 diff --git a/api/client/build.go b/api/client/build.go index e8b2d779d..225786841 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -58,7 +58,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')") flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit") flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap") - flCPUShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") + flCPUShares := cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)") flCPUPeriod := cmd.Int64([]string{"-cpu-period"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) period") flCPUQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota") flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") diff --git a/api/client/cli.go b/api/client/cli.go index aa7e64b29..834c47a4d 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -112,8 +112,13 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientF return errors.New("Please specify only one -H") } + defaultHost := opts.DefaultTCPHost + if clientFlags.Common.TLSOptions != nil { + defaultHost = opts.DefaultTLSHost + } + var e error - if hosts[0], e = opts.ParseHost(hosts[0]); e != nil { + if hosts[0], e = opts.ParseHost(defaultHost, hosts[0]); e != nil { return e } diff --git a/api/client/info.go b/api/client/info.go index 22b7ebb80..ebed2452e 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -35,7 +35,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers) fmt.Fprintf(cli.out, "Images: %d\n", info.Images) - fmt.Fprintf(cli.out, "Engine Version: %s\n", info.ServerVersion) + ioutils.FprintfIfNotEmpty(cli.out, "Server Version: %s\n", info.ServerVersion) ioutils.FprintfIfNotEmpty(cli.out, "Storage Driver: %s\n", info.Driver) if info.DriverStatus != nil { for _, pair := range info.DriverStatus { @@ -107,5 +107,8 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Cluster store: %s\n", info.ClusterStore) } + if info.ClusterAdvertise != "" { + fmt.Fprintf(cli.out, "Cluster advertise: %s\n", info.ClusterAdvertise) + } return nil } diff --git a/api/client/inspect.go b/api/client/inspect.go index 766651d94..916eec82d 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -59,7 +59,6 @@ func (cli *DockerCli) CmdInspect(args ...string) error { } for _, name := range cmd.Args() { - if *inspectType == "" || *inspectType == "container" { obj, _, err = readBody(cli.call("GET", "/containers/"+name+"/json?"+v.Encode(), nil, nil)) if err != nil && *inspectType == "container" { @@ -101,42 +100,45 @@ func (cli *DockerCli) CmdInspect(args ...string) error { } else { rdr := bytes.NewReader(obj) dec := json.NewDecoder(rdr) + buf := bytes.NewBufferString("") if isImage { inspPtr := types.ImageInspect{} if err := dec.Decode(&inspPtr); err != nil { - fmt.Fprintf(cli.err, "%s\n", err) + fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err) status = 1 - continue + break } - if err := tmpl.Execute(cli.out, inspPtr); err != nil { + if err := tmpl.Execute(buf, inspPtr); err != nil { rdr.Seek(0, 0) - var raw interface{} - if err := dec.Decode(&raw); err != nil { - return err - } - if err = tmpl.Execute(cli.out, raw); err != nil { - return err + var ok bool + + if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok { + fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) + status = 1 + break } } } else { inspPtr := types.ContainerJSON{} if err := dec.Decode(&inspPtr); err != nil { - fmt.Fprintf(cli.err, "%s\n", err) + fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err) status = 1 - continue + break } - if err := tmpl.Execute(cli.out, inspPtr); err != nil { + if err := tmpl.Execute(buf, inspPtr); err != nil { rdr.Seek(0, 0) - var raw interface{} - if err := dec.Decode(&raw); err != nil { - return err - } - if err = tmpl.Execute(cli.out, raw); err != nil { - return err + var ok bool + + if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok { + fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) + status = 1 + break } } } + + cli.out.Write(buf.Bytes()) cli.out.Write([]byte{'\n'}) } indented.WriteString(",") @@ -162,3 +164,33 @@ func (cli *DockerCli) CmdInspect(args ...string) error { } return nil } + +// decodeRawInspect executes the inspect template with a raw interface. +// This allows docker cli to parse inspect structs injected with Swarm fields. +// Unfortunately, go 1.4 doesn't fail executing invalid templates when the input is an interface. +// It doesn't allow to modify this behavior either, sending messages to the output. +// We assume that the template is invalid when there is a , if the template was valid +// we'd get or "" values. In that case we fail with the original error raised executing the +// template with the typed input. +// +// TODO: Go 1.5 allows to customize the error behavior, we can probably get rid of this as soon as +// we build Docker with that version: +// https://golang.org/pkg/text/template/#Template.Option +func (cli *DockerCli) decodeRawInspect(tmpl *template.Template, dec *json.Decoder) (*bytes.Buffer, bool) { + var raw interface{} + buf := bytes.NewBufferString("") + + if rawErr := dec.Decode(&raw); rawErr != nil { + fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", rawErr) + return buf, false + } + + if rawErr := tmpl.Execute(buf, raw); rawErr != nil { + return buf, false + } + + if strings.Contains(buf.String(), "") { + return buf, false + } + return buf, true +} diff --git a/api/client/network.go b/api/client/network.go index d9ff387fc..8cd72a884 100644 --- a/api/client/network.go +++ b/api/client/network.go @@ -34,6 +34,7 @@ func (cli *DockerCli) CmdNetwork(args ...string) error { func (cli *DockerCli) CmdNetworkCreate(args ...string) error { cmd := Cli.Subcmd("network create", []string{"NETWORK-NAME"}, "Creates a new network with a name specified by the user", false) flDriver := cmd.String([]string{"d", "-driver"}, "bridge", "Driver to manage the Network") + flOpts := opts.NewMapOpts(nil, nil) flIpamDriver := cmd.String([]string{"-ipam-driver"}, "default", "IP Address Management Driver") flIpamSubnet := opts.NewListOpts(nil) @@ -41,10 +42,11 @@ func (cli *DockerCli) CmdNetworkCreate(args ...string) error { flIpamGateway := opts.NewListOpts(nil) flIpamAux := opts.NewMapOpts(nil, nil) - cmd.Var(&flIpamSubnet, []string{"-subnet"}, "Subnet in CIDR format that represents a network segment") + cmd.Var(&flIpamSubnet, []string{"-subnet"}, "subnet in CIDR format that represents a network segment") cmd.Var(&flIpamIPRange, []string{"-ip-range"}, "allocate container ip from a sub-range") cmd.Var(&flIpamGateway, []string{"-gateway"}, "ipv4 or ipv6 Gateway for the master subnet") - cmd.Var(flIpamAux, []string{"-aux-address"}, "Auxiliary ipv4 or ipv6 addresses used by network driver") + cmd.Var(flIpamAux, []string{"-aux-address"}, "auxiliary ipv4 or ipv6 addresses used by Network driver") + cmd.Var(flOpts, []string{"o", "-opt"}, "set driver specific options") cmd.Require(flag.Exact, 1) err := cmd.ParseFlags(args, true) @@ -52,6 +54,13 @@ func (cli *DockerCli) CmdNetworkCreate(args ...string) error { return err } + // Set the default driver to "" if the user didn't set the value. + // That way we can know whether it was user input or not. + driver := *flDriver + if !cmd.IsSet("-driver") && !cmd.IsSet("d") { + driver = "" + } + ipamCfg, err := consolidateIpam(flIpamSubnet.GetAll(), flIpamIPRange.GetAll(), flIpamGateway.GetAll(), flIpamAux.GetAll()) if err != nil { return err @@ -60,8 +69,9 @@ func (cli *DockerCli) CmdNetworkCreate(args ...string) error { // Construct network create request body nc := types.NetworkCreate{ Name: cmd.Arg(0), - Driver: *flDriver, + Driver: driver, IPAM: network.IPAM{Driver: *flIpamDriver, Config: ipamCfg}, + Options: flOpts.GetAll(), CheckDuplicate: true, } obj, _, err := readBody(cli.call("POST", "/networks/create", nc, nil)) @@ -181,31 +191,48 @@ func (cli *DockerCli) CmdNetworkLs(args ...string) error { // CmdNetworkInspect inspects the network object for more details // -// Usage: docker network inspect -// CmdNetworkInspect handles Network inspect UI +// Usage: docker network inspect [OPTIONS] [NETWORK...] func (cli *DockerCli) CmdNetworkInspect(args ...string) error { - cmd := Cli.Subcmd("network inspect", []string{"NETWORK"}, "Displays detailed information on a network", false) - cmd.Require(flag.Exact, 1) + cmd := Cli.Subcmd("network inspect", []string{"NETWORK [NETWORK...]"}, "Displays detailed information on a network", false) + cmd.Require(flag.Min, 1) err := cmd.ParseFlags(args, true) if err != nil { return err } - obj, _, err := readBody(cli.call("GET", "/networks/"+cmd.Arg(0), nil, nil)) + status := 0 + var networks []*types.NetworkResource + for _, name := range cmd.Args() { + obj, _, err := readBody(cli.call("GET", "/networks/"+name, nil, nil)) + if err != nil { + if strings.Contains(err.Error(), "not found") { + fmt.Fprintf(cli.err, "Error: No such network: %s\n", name) + } else { + fmt.Fprintf(cli.err, "%s", err) + } + status = 1 + continue + } + networkResource := types.NetworkResource{} + if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&networkResource); err != nil { + return err + } + + networks = append(networks, &networkResource) + } + + b, err := json.MarshalIndent(networks, "", " ") if err != nil { return err } - networkResource := &types.NetworkResource{} - if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil { - return err - } - indented := new(bytes.Buffer) - if err := json.Indent(indented, obj, "", " "); err != nil { + if _, err := io.Copy(cli.out, bytes.NewReader(b)); err != nil { return err } - if _, err := io.Copy(cli.out, indented); err != nil { - return err + io.WriteString(cli.out, "\n") + + if status != 0 { + return Cli.StatusError{StatusCode: status} } return nil } diff --git a/api/client/trust.go b/api/client/trust.go index 8b4407565..5936ac936 100644 --- a/api/client/trust.go +++ b/api/client/trust.go @@ -198,15 +198,17 @@ func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever { // Backwards compatibility with old env names. We should remove this in 1.10 if env["root"] == "" { - env["root"] = os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE") - fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE\n") - + if passphrase := os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"); passphrase != "" { + env["root"] = passphrase + fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE\n") + } } if env["snapshot"] == "" || env["targets"] == "" { - env["snapshot"] = os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE") - env["targets"] = os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE") - fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE\n") - + if passphrase := os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"); passphrase != "" { + env["snapshot"] = passphrase + env["targets"] = passphrase + fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE\n") + } } return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) { diff --git a/api/client/volume.go b/api/client/volume.go index 60535d7d0..1dc0ea2d0 100644 --- a/api/client/volume.go +++ b/api/client/volume.go @@ -195,7 +195,7 @@ func (cli *DockerCli) CmdVolumeCreate(args ...string) error { volReq.Name = *flName } - resp, err := cli.call("POST", "/volumes", volReq, nil) + resp, err := cli.call("POST", "/volumes/create", volReq, nil) if err != nil { return err } diff --git a/api/server/router/local/local.go b/api/server/router/local/local.go index 53240b2a3..c73e852a2 100644 --- a/api/server/router/local/local.go +++ b/api/server/router/local/local.go @@ -141,7 +141,7 @@ func (r *router) initRoutes() { NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart), NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize), NewPostRoute("/containers/{name:.*}/rename", r.postContainerRename), - NewPostRoute("/volumes", r.postVolumesCreate), + NewPostRoute("/volumes/create", r.postVolumesCreate), // PUT NewPutRoute("/containers/{name:.*}/archive", r.putContainersArchive), // DELETE diff --git a/api/server/router/network/network.go b/api/server/router/network/network.go index 7645249b4..b301820f7 100644 --- a/api/server/router/network/network.go +++ b/api/server/router/network/network.go @@ -1,9 +1,14 @@ package network import ( + "net/http" + + "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/server/router" "github.com/docker/docker/api/server/router/local" "github.com/docker/docker/daemon" + "github.com/docker/docker/errors" + "golang.org/x/net/context" ) // networkRouter is a router to talk with the network controller @@ -29,13 +34,24 @@ func (r *networkRouter) Routes() []router.Route { func (r *networkRouter) initRoutes() { r.routes = []router.Route{ // GET - local.NewGetRoute("/networks", r.getNetworksList), - local.NewGetRoute("/networks/{id:.*}", r.getNetwork), + local.NewGetRoute("/networks", r.controllerEnabledMiddleware(r.getNetworksList)), + local.NewGetRoute("/networks/{id:.*}", r.controllerEnabledMiddleware(r.getNetwork)), // POST - local.NewPostRoute("/networks/create", r.postNetworkCreate), - local.NewPostRoute("/networks/{id:.*}/connect", r.postNetworkConnect), - local.NewPostRoute("/networks/{id:.*}/disconnect", r.postNetworkDisconnect), + local.NewPostRoute("/networks/create", r.controllerEnabledMiddleware(r.postNetworkCreate)), + local.NewPostRoute("/networks/{id:.*}/connect", r.controllerEnabledMiddleware(r.postNetworkConnect)), + local.NewPostRoute("/networks/{id:.*}/disconnect", r.controllerEnabledMiddleware(r.postNetworkDisconnect)), // DELETE - local.NewDeleteRoute("/networks/{id:.*}", r.deleteNetwork), + local.NewDeleteRoute("/networks/{id:.*}", r.controllerEnabledMiddleware(r.deleteNetwork)), } } + +func (r *networkRouter) controllerEnabledMiddleware(handler httputils.APIFunc) httputils.APIFunc { + if r.daemon.NetworkControllerEnabled() { + return handler + } + return networkControllerDisabled +} + +func networkControllerDisabled(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + return errors.ErrorNetworkControllerNotEnabled.WithArgs() +} diff --git a/api/server/router/network/network_routes.go b/api/server/router/network/network_routes.go index 9d7705416..22f0a6958 100644 --- a/api/server/router/network/network_routes.go +++ b/api/server/router/network/network_routes.go @@ -13,6 +13,7 @@ import ( "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/network" "github.com/docker/docker/pkg/parsers/filters" + "github.com/docker/docker/runconfig" "github.com/docker/libnetwork" ) @@ -85,6 +86,11 @@ func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWr return err } + if runconfig.IsPreDefinedNetwork(create.Name) { + return httputils.WriteJSON(w, http.StatusForbidden, + fmt.Sprintf("%s is a pre-defined network and cannot be created", create.Name)) + } + nw, err := n.daemon.GetNetwork(create.Name, daemon.NetworkByName) if _, ok := err.(libnetwork.ErrNoSuchNetwork); err != nil && !ok { return err @@ -96,7 +102,7 @@ func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWr warning = fmt.Sprintf("Network with name %s (id : %s) already exists", nw.Name(), nw.ID()) } - nw, err = n.daemon.CreateNetwork(create.Name, create.Driver, create.IPAM) + nw, err = n.daemon.CreateNetwork(create.Name, create.Driver, create.IPAM, create.Options) if err != nil { return err } @@ -169,6 +175,11 @@ func (n *networkRouter) deleteNetwork(ctx context.Context, w http.ResponseWriter return err } + if runconfig.IsPreDefinedNetwork(nw.Name()) { + return httputils.WriteJSON(w, http.StatusForbidden, + fmt.Sprintf("%s is a pre-defined network and cannot be removed", nw.Name())) + } + return nw.Delete() } @@ -182,6 +193,7 @@ func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource { r.ID = nw.ID() r.Scope = nw.Info().Scope() r.Driver = nw.Type() + r.Options = nw.Info().DriverOptions() r.Containers = make(map[string]types.EndpointResource) buildIpamResources(r, nw) diff --git a/api/server/server.go b/api/server/server.go index a4b9fb4c4..28fbd6eea 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -165,6 +165,7 @@ func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc { func (s *Server) InitRouters(d *daemon.Daemon) { s.addRouter(local.NewRouter(d)) s.addRouter(network.NewRouter(d)) + for _, srv := range s.servers { srv.srv.Handler = s.CreateMux() } diff --git a/api/types/types.go b/api/types/types.go index ed9a0831a..ae2057df0 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -5,6 +5,7 @@ import ( "time" "github.com/docker/docker/daemon/network" + "github.com/docker/docker/pkg/nat" "github.com/docker/docker/pkg/version" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" @@ -96,7 +97,8 @@ type GraphDriverData struct { // GET "/images/{name:.*}/json" type ImageInspect struct { ID string `json:"Id"` - Tags []string + RepoTags []string + RepoDigests []string Parent string Comment string Created string @@ -218,6 +220,7 @@ type Info struct { ExperimentalBuild bool ServerVersion string ClusterStore string + ClusterAdvertise string } // ExecStartCheck is a temp struct used by execStart @@ -254,7 +257,6 @@ type ContainerJSONBase struct { Args []string State *ContainerState Image string - NetworkSettings *network.Settings ResolvConfPath string HostnamePath string HostsPath string @@ -276,8 +278,43 @@ type ContainerJSONBase struct { // ContainerJSON is newly used struct along with MountPoint type ContainerJSON struct { *ContainerJSONBase - Mounts []MountPoint - Config *runconfig.Config + Mounts []MountPoint + Config *runconfig.Config + NetworkSettings *NetworkSettings +} + +// NetworkSettings exposes the network settings in the api +type NetworkSettings struct { + NetworkSettingsBase + DefaultNetworkSettings + Networks map[string]*network.EndpointSettings +} + +// NetworkSettingsBase holds basic information about networks +type NetworkSettingsBase struct { + Bridge string + SandboxID string + HairpinMode bool + LinkLocalIPv6Address string + LinkLocalIPv6PrefixLen int + Ports nat.PortMap + SandboxKey string + SecondaryIPAddresses []network.Address + SecondaryIPv6Addresses []network.Address +} + +// DefaultNetworkSettings holds network information +// during the 2 release deprecation period. +// It will be removed in Docker 1.11. +type DefaultNetworkSettings struct { + EndpointID string + Gateway string + GlobalIPv6Address string + GlobalIPv6PrefixLen int + IPAddress string + IPPrefixLen int + IPv6Gateway string + MacAddress string } // MountPoint represents a mount point configuration inside the container. @@ -304,7 +341,7 @@ type VolumesListResponse struct { } // VolumeCreateRequest contains the response for the remote API: -// POST "/volumes" +// POST "/volumes/create" type VolumeCreateRequest struct { Name string // Name is the requested name of the volume Driver string // Driver is the name of the driver that should be used to create the volume @@ -313,42 +350,44 @@ type VolumeCreateRequest struct { // NetworkResource is the body of the "get network" http response message type NetworkResource struct { - Name string `json:"name"` - ID string `json:"id"` - Scope string `json:"scope"` - Driver string `json:"driver"` - IPAM network.IPAM `json:"ipam"` - Containers map[string]EndpointResource `json:"containers"` + Name string + ID string `json:"Id"` + Scope string + Driver string + IPAM network.IPAM + Containers map[string]EndpointResource + Options map[string]string } //EndpointResource contains network resources allocated and usd for a container in a network type EndpointResource struct { - EndpointID string `json:"endpoint"` - MacAddress string `json:"mac_address"` - IPv4Address string `json:"ipv4_address"` - IPv6Address string `json:"ipv6_address"` + EndpointID string + MacAddress string + IPv4Address string + IPv6Address string } // NetworkCreate is the expected body of the "create network" http request message type NetworkCreate struct { - Name string `json:"name"` - CheckDuplicate bool `json:"check_duplicate"` - Driver string `json:"driver"` - IPAM network.IPAM `json:"ipam"` + Name string + CheckDuplicate bool + Driver string + IPAM network.IPAM + Options map[string]string } // NetworkCreateResponse is the response message sent by the server for network create call type NetworkCreateResponse struct { - ID string `json:"id"` - Warning string `json:"warning"` + ID string `json:"Id"` + Warning string } // NetworkConnect represents the data to be used to connect a container to the network type NetworkConnect struct { - Container string `json:"container"` + Container string } // NetworkDisconnect represents the data to be used to disconnect a container from the network type NetworkDisconnect struct { - Container string `json:"container"` + Container string } diff --git a/api/types/versions/v1p19/types.go b/api/types/versions/v1p19/types.go index a2d533711..a66aa9d5e 100644 --- a/api/types/versions/v1p19/types.go +++ b/api/types/versions/v1p19/types.go @@ -3,6 +3,8 @@ package v1p19 import ( "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/versions/v1p20" + "github.com/docker/docker/pkg/nat" "github.com/docker/docker/runconfig" ) @@ -10,15 +12,20 @@ import ( // Note this is not used by the Windows daemon. type ContainerJSON struct { *types.ContainerJSONBase - Volumes map[string]string - VolumesRW map[string]bool - Config *ContainerConfig + Volumes map[string]string + VolumesRW map[string]bool + Config *ContainerConfig + NetworkSettings *v1p20.NetworkSettings } // ContainerConfig is a backcompatibility struct for APIs prior to 1.20. type ContainerConfig struct { *runconfig.Config + MacAddress string + NetworkDisabled bool + ExposedPorts map[nat.Port]struct{} + // backward compatibility, they now live in HostConfig VolumeDriver string Memory int64 diff --git a/api/types/versions/v1p20/types.go b/api/types/versions/v1p20/types.go index 6673f76de..0facbb66c 100644 --- a/api/types/versions/v1p20/types.go +++ b/api/types/versions/v1p20/types.go @@ -3,20 +3,26 @@ package v1p20 import ( "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/nat" "github.com/docker/docker/runconfig" ) // ContainerJSON is a backcompatibility struct for the API 1.20 type ContainerJSON struct { *types.ContainerJSONBase - Mounts []types.MountPoint - Config *ContainerConfig + Mounts []types.MountPoint + Config *ContainerConfig + NetworkSettings *NetworkSettings } // ContainerConfig is a backcompatibility struct used in ContainerJSON for the API 1.20 type ContainerConfig struct { *runconfig.Config + MacAddress string + NetworkDisabled bool + ExposedPorts map[nat.Port]struct{} + // backward compatibility, they now live in HostConfig VolumeDriver string } @@ -26,3 +32,9 @@ type StatsJSON struct { types.Stats Network types.NetworkStats `json:"network,omitempty"` } + +// NetworkSettings is a backward compatible struct for APIs prior to 1.21 +type NetworkSettings struct { + types.NetworkSettingsBase + types.DefaultNetworkSettings +} diff --git a/cli/common.go b/cli/common.go index d3aa391be..c03d9a90e 100644 --- a/cli/common.go +++ b/cli/common.go @@ -45,6 +45,7 @@ var dockerCommands = []Command{ {"login", "Register or log in to a Docker registry"}, {"logout", "Log out from a Docker registry"}, {"logs", "Fetch the logs of a container"}, + {"network", "Manage Docker networks"}, {"pause", "Pause all processes within a container"}, {"port", "List port mappings or a specific mapping for the CONTAINER"}, {"ps", "List containers"}, diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 0375e6f95..0baad7940 100644 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -303,6 +303,7 @@ __docker_capabilities() { __docker_log_drivers() { COMPREPLY=( $( compgen -W " + awslogs fluentd gelf journald @@ -314,15 +315,21 @@ __docker_log_drivers() { __docker_log_driver_options() { # see docs/reference/logging/index.md - local fluentd_options="fluentd-address tag" - local gelf_options="gelf-address tag" - local json_file_options="max-file max-size" - local syslog_options="syslog-address syslog-facility tag" local awslogs_options="awslogs-region awslogs-group awslogs-stream" + local fluentd_options="env fluentd-address labels tag" + local gelf_options="env gelf-address labels tag" + local journald_options="env labels" + local json_file_options="env labels max-file max-size" + local syslog_options="syslog-address syslog-facility tag" + + local all_options="$fluentd_options $gelf_options $journald_options $json_file_options $syslog_options" case $(__docker_value_of_option --log-driver) in '') - COMPREPLY=( $( compgen -W "$fluentd_options $gelf_options $json_file_options $syslog_options" -S = -- "$cur" ) ) + COMPREPLY=( $( compgen -W "$all_options" -S = -- "$cur" ) ) + ;; + awslogs) + COMPREPLY=( $( compgen -W "$awslogs_options" -S = -- "$cur" ) ) ;; fluentd) COMPREPLY=( $( compgen -W "$fluentd_options" -S = -- "$cur" ) ) @@ -330,15 +337,15 @@ __docker_log_driver_options() { gelf) COMPREPLY=( $( compgen -W "$gelf_options" -S = -- "$cur" ) ) ;; + journald) + COMPREPLY=( $( compgen -W "$journald_options" -S = -- "$cur" ) ) + ;; json-file) COMPREPLY=( $( compgen -W "$json_file_options" -S = -- "$cur" ) ) ;; syslog) COMPREPLY=( $( compgen -W "$syslog_options" -S = -- "$cur" ) ) ;; - awslogs) - COMPREPLY=( $( compgen -W "$awslogs_options" -S = -- "$cur" ) ) - ;; *) return ;; @@ -461,8 +468,37 @@ _docker_attach() { } _docker_build() { + local options_with_args=" + --build-arg + --cgroup-parent + --cpuset-cpus + --cpuset-mems + --cpu-shares + --cpu-period + --cpu-quota + --file -f + --memory -m + --memory-swap + --tag -t + --ulimit + " + + local boolean_options=" + --disable-content-trust=false + --force-rm + --help + --no-cache + --pull + --quiet -q + --rm + " + + local all_options="$options_with_args $boolean_options" + case "$prev" in - --cgroup-parent|--cpuset-cpus|--cpuset-mems|--cpu-shares|-c|--cpu-period|--cpu-quota|--memory|-m|--memory-swap) + --build-arg) + COMPREPLY=( $( compgen -e -- "$cur" ) ) + __docker_nospace return ;; --file|-f) @@ -473,14 +509,17 @@ _docker_build() { __docker_image_repos_and_tags return ;; + $(__docker_to_extglob "$options_with_args") ) + return + ;; esac case "$cur" in -*) - COMPREPLY=( $( compgen -W "--cgroup-parent --cpuset-cpus --cpuset-mems --cpu-shares -c --cpu-period --cpu-quota --file -f --force-rm --help --memory -m --memory-swap --no-cache --pull --quiet -q --rm --tag -t --ulimit" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "$all_options" -- "$cur" ) ) ;; *) - local counter="$(__docker_pos_first_nonflag '--cgroup-parent|--cpuset-cpus|--cpuset-mems|--cpu-shares|-c|--cpu-period|--cpu-quota|--file|-f|--memory|-m|--memory-swap|--tag|-t')" + local counter=$( __docker_pos_first_nonflag $( __docker_to_alternatives "$options_with_args" ) ) if [ $cword -eq $counter ]; then _filedir -d fi @@ -529,9 +568,18 @@ _docker_cp() { return ;; *) + # combined container and filename completion + _filedir + local files=( ${COMPREPLY[@]} ) + __docker_containers_all COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) ) - __docker_nospace + local containers=( ${COMPREPLY[@]} ) + + COMPREPLY=( $( compgen -W "${files[*]} ${containers[*]}" -- "$cur" ) ) + if [[ "$COMPREPLY" == *: ]]; then + __docker_nospace + fi return ;; esac @@ -539,7 +587,13 @@ _docker_cp() { (( counter++ )) if [ $cword -eq $counter ]; then - _filedir -d + if [ -e "$prev" ]; then + __docker_containers_all + COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) ) + __docker_nospace + else + _filedir + fi return fi ;; @@ -635,6 +689,8 @@ _docker_daemon() { dm.mountopt dm.override_udev_sync_check dm.thinpooldev + dm.use_deferred_deletion + dm.use_deferred_removal " local zfs_options="zfs.fsname" @@ -672,7 +728,7 @@ _docker_daemon() { case "${words[$cword-2]}$prev=" in # completions for --storage-opt - *dm.blkdiscard=*) + *dm.@(blkdiscard|override_udev_sync_check|use_deferred_@(removal|deletion))=*) COMPREPLY=( $( compgen -W "false true" -- "${cur#=}" ) ) return ;; @@ -680,10 +736,6 @@ _docker_daemon() { COMPREPLY=( $( compgen -W "ext4 xfs" -- "${cur#=}" ) ) return ;; - *dm.override_udev_sync_check=*) - COMPREPLY=( $( compgen -W "false true" -- "${cur#=}" ) ) - return - ;; *dm.thinpooldev=*) _filedir return @@ -865,12 +917,18 @@ _docker_images() { } _docker_import() { + case "$prev" in + --change|-c|--message|-m) + return + ;; + esac + case "$cur" in -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--change -c --help --message -m" -- "$cur" ) ) ;; *) - local counter=$(__docker_pos_first_nonflag) + local counter=$(__docker_pos_first_nonflag '--change|-c|--message|-m') if [ $cword -eq $counter ]; then return fi @@ -906,7 +964,7 @@ _docker_inspect() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--format -f --type --help" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--format -f --help --size -s --type" -- "$cur" ) ) ;; *) case $(__docker_value_of_option --type) in @@ -1016,6 +1074,13 @@ _docker_network_connect() { _docker_network_create() { case "$prev" in + --aux-address|--gateway|--ip-range|--opt|-o|--subnet) + return + ;; + --ipam-driver) + COMPREPLY=( $( compgen -W "default" -- "$cur" ) ) + return + ;; --driver|-d) # no need to suggest drivers that allow one instance only # (host, null) @@ -1026,7 +1091,7 @@ _docker_network_create() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--driver -d --help" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--aux-address --driver -d --gateway --help --ip-range --ipam-driver --opt -o --subnet" -- "$cur" ) ) ;; esac } @@ -1043,11 +1108,7 @@ _docker_network_inspect() { COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) ;; *) - local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then - __docker_networks - fi - ;; + __docker_networks esac } @@ -1060,7 +1121,7 @@ _docker_network_ls() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--help --latest -l -n --no-trunc --quiet -q" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--help --no-trunc --quiet -q" -- "$cur" ) ) ;; esac } @@ -1272,7 +1333,7 @@ _docker_run() { --cpu-quota --cpuset-cpus --cpuset-mems - --cpu-shares -c + --cpu-shares --device --dns --dns-opt @@ -1311,7 +1372,7 @@ _docker_run() { --workdir -w " - local all_options="$options_with_args + local boolean_options=" --disable-content-trust=false --help --interactive -i @@ -1322,14 +1383,14 @@ _docker_run() { --tty -t " + local all_options="$options_with_args $boolean_options" + [ "$command" = "run" ] && all_options="$all_options --detach -d --rm --sig-proxy=false " - local options_with_args_glob=$(__docker_to_extglob "$options_with_args") - case "$prev" in --add-host) case "$cur" in @@ -1427,7 +1488,7 @@ _docker_run() { on-failure:*) ;; *) - COMPREPLY=( $( compgen -W "no on-failure on-failure: always" -- "$cur") ) + COMPREPLY=( $( compgen -W "always no on-failure on-failure: unless-stopped" -- "$cur") ) ;; esac return @@ -1454,7 +1515,7 @@ _docker_run() { __docker_containers_all return ;; - $options_with_args_glob ) + $(__docker_to_extglob "$options_with_args") ) return ;; esac diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index 028c4822f..e0fbcaa80 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -116,7 +116,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from cp' -l help -d 'Print u complete -c docker -f -n '__fish_docker_no_subcommand' -a create -d 'Create a new container' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -s a -l attach -d 'Attach to STDIN, STDOUT or STDERR.' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l add-host -d 'Add a custom host-to-IP mapping (host:ip)' -complete -c docker -A -f -n '__fish_seen_subcommand_from create' -s c -l cpu-shares -d 'CPU shares (relative weight)' +complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l cpu-shares -d 'CPU shares (relative weight)' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l cap-add -d 'Add Linux capabilities' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l cap-drop -d 'Drop Linux capabilities' complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l cidfile -d 'Write the container ID to the file' diff --git a/contrib/completion/zsh/_docker b/contrib/completion/zsh/_docker index cd1d66789..00930499d 100644 --- a/contrib/completion/zsh/_docker +++ b/contrib/completion/zsh/_docker @@ -253,18 +253,22 @@ __docker_network_subcommand() { _arguments -A '-*' \ $opts_help \ "($help -d --driver)"{-d,--driver=}"[Driver to manage the Network]:driver:(null host bridge overlay)" \ + "($help)--ipam-driver=[IP Address Management Driver]:driver:(default)" \ + "($help)*--subnet=[Subnet in CIDR format that represents a network segment]:IP/mask: " \ + "($help)*--ip-range=[Allocate container ip from a sub-range]:IP/mask: " \ + "($help)*--gateway=[ipv4 or ipv6 Gateway for the master subnet]:IP: " \ + "($help)*--aux-address[Auxiliary ipv4 or ipv6 addresses used by network driver]:key=IP: " \ + "($help)*"{-o=,--opt=}"[Set driver specific options]:key=value: " \ "($help -)1:Network Name: " && ret=0 ;; (inspect|rm) _arguments \ $opts_help \ - "($help -):network:__docker_networks" && ret=0 + "($help -)*:network:__docker_networks" && ret=0 ;; (ls) _arguments \ $opts_help \ - "($help -l --latest)"{-l,--latest}"[Show the latest network created]" \ - "($help)-n=-[Show n last created networks]:Number of networks: " \ "($help)--no-trunc[Do not truncate the output]" \ "($help -q --quiet)"{-q,--quiet}"[Only display numeric IDs]" && ret=0 ;; @@ -330,20 +334,20 @@ __docker_volume_subcommand() { (create) _arguments \ $opts_help \ - "($help -d --driver)"{-d,--driver=-}"[Specify volume driver name]:Driver name: " \ - "($help)--name=-[Specify volume name]" \ - "($help -o --opt)*"{-o,--opt=-}"[Set driver specific options]:Driver option: " && ret=0 + "($help -d --driver)"{-d,--driver=}"[Specify volume driver name]:Driver name: " \ + "($help)--name=[Specify volume name]" \ + "($help)*"{-o,--opt=}"[Set driver specific options]:Driver option: " && ret=0 ;; (inspect) _arguments \ $opts_help \ - "($help -f --format)"{-f,--format=-}"[Format the output using the given go template]:template: " \ + "($help -f --format)"{-f,--format=}"[Format the output using the given go template]:template: " \ "($help -)1:volume:__docker_volumes" && ret=0 ;; (ls) _arguments \ $opts_help \ - "($help -f --filter)*"{-f,--filter=-}"[Provide filter values (i.e. 'dangling=true')]:filter: " \ + "($help)*"{-f,--filter=}"[Provide filter values (i.e. 'dangling=true')]:filter: " \ "($help -q --quiet)"{-q,--quiet}"[Only display volume names]" && ret=0 ;; (rm) @@ -391,57 +395,57 @@ __docker_subcommand() { opts_help=("(: -)--help[Print usage]") opts_cpumemlimit=( - "($help -c --cpu-shares)"{-c,--cpu-shares=-}"[CPU shares (relative weight)]:CPU shares:(0 10 100 200 500 800 1000)" - "($help)--cgroup-parent=-[Parent cgroup for the container]:cgroup: " - "($help)--cpu-period=-[Limit the CPU CFS (Completely Fair Scheduler) period]:CPU period: " - "($help)--cpu-quota=-[Limit the CPU CFS (Completely Fair Scheduler) quota]:CPU quota: " - "($help)--cpuset-cpus=-[CPUs in which to allow execution]:CPUs: " - "($help)--cpuset-mems=-[MEMs in which to allow execution]:MEMs: " - "($help -m --memory)"{-m,--memory=-}"[Memory limit]:Memory limit: " - "($help)--memory-swap=-[Total memory limit with swap]:Memory limit: " - "($help)*--ulimit=-[ulimit options]:ulimit: " + "($help)--cpu-shares=[CPU shares (relative weight)]:CPU shares:(0 10 100 200 500 800 1000)" + "($help)--cgroup-parent=[Parent cgroup for the container]:cgroup: " + "($help)--cpu-period=[Limit the CPU CFS (Completely Fair Scheduler) period]:CPU period: " + "($help)--cpu-quota=[Limit the CPU CFS (Completely Fair Scheduler) quota]:CPU quota: " + "($help)--cpuset-cpus=[CPUs in which to allow execution]:CPUs: " + "($help)--cpuset-mems=[MEMs in which to allow execution]:MEMs: " + "($help -m --memory)"{-m,--memory=}"[Memory limit]:Memory limit: " + "($help)--memory-swap=[Total memory limit with swap]:Memory limit: " + "($help)*--ulimit=[ulimit options]:ulimit: " ) opts_create=( - "($help -a --attach)"{-a,--attach=-}"[Attach to stdin, stdout or stderr]:device:(STDIN STDOUT STDERR)" - "($help)*--add-host=-[Add a custom host-to-IP mapping]:host\:ip mapping: " - "($help)--blkio-weight=-[Block IO (relative weight), between 10 and 1000]:Block IO weight:(10 100 500 1000)" - "($help)*--cap-add=-[Add Linux capabilities]:capability: " - "($help)*--cap-drop=-[Drop Linux capabilities]:capability: " - "($help)--cidfile=-[Write the container ID to the file]:CID file:_files" - "($help)*--device=-[Add a host device to the container]:device:_files" - "($help)*--dns=-[Set custom DNS servers]:DNS server: " - "($help)*--dns-opt=-[Set custom DNS options]:DNS option: " - "($help)*--dns-search=-[Set custom DNS search domains]:DNS domains: " - "($help)*"{-e,--env=-}"[Set environment variables]:environment variable: " - "($help)--entrypoint=-[Overwrite the default entrypoint of the image]:entry point: " - "($help)*--env-file=-[Read environment variables from a file]:environment file:_files" - "($help)*--expose=-[Expose a port from the container without publishing it]: " - "($help)*--group-add=-[Add additional groups to run as]:group:_groups" - "($help -h --hostname)"{-h,--hostname=-}"[Container host name]:hostname:_hosts" + "($help -a --attach)"{-a,--attach=}"[Attach to stdin, stdout or stderr]:device:(STDIN STDOUT STDERR)" + "($help)*--add-host=[Add a custom host-to-IP mapping]:host\:ip mapping: " + "($help)--blkio-weight=[Block IO (relative weight), between 10 and 1000]:Block IO weight:(10 100 500 1000)" + "($help)*--cap-add=[Add Linux capabilities]:capability: " + "($help)*--cap-drop=[Drop Linux capabilities]:capability: " + "($help)--cidfile=[Write the container ID to the file]:CID file:_files" + "($help)*--device=[Add a host device to the container]:device:_files" + "($help)*--dns=[Set custom DNS servers]:DNS server: " + "($help)*--dns-opt=[Set custom DNS options]:DNS option: " + "($help)*--dns-search=[Set custom DNS search domains]:DNS domains: " + "($help)*"{-e,--env=}"[Set environment variables]:environment variable: " + "($help)--entrypoint=[Overwrite the default entrypoint of the image]:entry point: " + "($help)*--env-file=[Read environment variables from a file]:environment file:_files" + "($help)*--expose=[Expose a port from the container without publishing it]: " + "($help)*--group-add=[Add additional groups to run as]:group:_groups" + "($help -h --hostname)"{-h,--hostname=}"[Container host name]:hostname:_hosts" "($help -i --interactive)"{-i,--interactive}"[Keep stdin open even if not attached]" - "($help)--ipc=-[IPC namespace to use]:IPC namespace: " + "($help)--ipc=[IPC namespace to use]:IPC namespace: " "($help)--kernel-memory[Kernel memory limit in bytes.]:Memory limit: " - "($help)*--link=-[Add link to another container]:link:->link" - "($help)*"{-l,--label=-}"[Set meta data on a container]:label: " - "($help)--log-driver=-[Default driver for container logs]:Logging driver:(json-file syslog journald gelf fluentd awslogs none)" - "($help)*--log-opt=-[Log driver specific options]:log driver options: " - "($help)*--lxc-conf=-[Add custom lxc options]:lxc options: " - "($help)--mac-address=-[Container MAC address]:MAC address: " - "($help)--name=-[Container name]:name: " - "($help)--net=-[Connect a container to a network]:network mode:(bridge none container host)" + "($help)*--link=[Add link to another container]:link:->link" + "($help)*"{-l,--label=}"[Set meta data on a container]:label: " + "($help)--log-driver=[Default driver for container logs]:Logging driver:(json-file syslog journald gelf fluentd awslogs none)" + "($help)*--log-opt=[Log driver specific options]:log driver options: " + "($help)*--lxc-conf=[Add custom lxc options]:lxc options: " + "($help)--mac-address=[Container MAC address]:MAC address: " + "($help)--name=[Container name]:name: " + "($help)--net=[Connect a container to a network]:network mode:(bridge none container host)" "($help)--oom-kill-disable[Disable OOM Killer]" "($help -P --publish-all)"{-P,--publish-all}"[Publish all exposed ports]" - "($help)*"{-p,--publish=-}"[Expose a container's port to the host]:port:_ports" - "($help)--pid=-[PID namespace to use]:PID: " + "($help)*"{-p,--publish=}"[Expose a container's port to the host]:port:_ports" + "($help)--pid=[PID namespace to use]:PID: " "($help)--privileged[Give extended privileges to this container]" "($help)--read-only[Mount the container's root filesystem as read only]" - "($help)--restart=-[Restart policy]:restart policy:(no on-failure always)" - "($help)*--security-opt=-[Security options]:security option: " + "($help)--restart=[Restart policy]:restart policy:(no on-failure always unless-stopped)" + "($help)*--security-opt=[Security options]:security option: " "($help -t --tty)"{-t,--tty}"[Allocate a pseudo-tty]" - "($help -u --user)"{-u,--user=-}"[Username or UID]:user:_users" + "($help -u --user)"{-u,--user=}"[Username or UID]:user:_users" "($help)*-v[Bind mount a volume]:volume: " - "($help)*--volumes-from=-[Mount volumes from the specified container]:volume: " - "($help -w --workdir)"{-w,--workdir=-}"[Working directory inside the container]:directory:_directories" + "($help)*--volumes-from=[Mount volumes from the specified container]:volume: " + "($help -w --workdir)"{-w,--workdir=}"[Working directory inside the container]:directory:_directories" ) case "$words[1]" in @@ -456,21 +460,22 @@ __docker_subcommand() { _arguments \ $opts_help \ $opts_cpumemlimit \ - "($help -f --file)"{-f,--file=-}"[Name of the Dockerfile]:Dockerfile:_files" \ + "($help)*--build-arg[Set build-time variables]:=: " \ + "($help -f --file)"{-f,--file=}"[Name of the Dockerfile]:Dockerfile:_files" \ "($help)--force-rm[Always remove intermediate containers]" \ "($help)--no-cache[Do not use cache when building the image]" \ "($help)--pull[Attempt to pull a newer version of the image]" \ "($help -q --quiet)"{-q,--quiet}"[Suppress verbose build output]" \ "($help)--rm[Remove intermediate containers after a successful build]" \ - "($help -t --tag)"{-t,--tag=-}"[Repository, name and tag for the image]: :__docker_repositories_with_tags" \ + "($help -t --tag)"{-t,--tag=}"[Repository, name and tag for the image]: :__docker_repositories_with_tags" \ "($help -):path or URL:_directories" && ret=0 ;; (commit) _arguments \ $opts_help \ - "($help -a --author)"{-a,--author=-}"[Author]:author: " \ - "($help -c --change)*"{-c,--change=-}"[Apply Dockerfile instruction to the created image]:Dockerfile:_files" \ - "($help -m --message)"{-m,--message=-}"[Commit message]:message: " \ + "($help -a --author)"{-a,--author=}"[Author]:author: " \ + "($help)*"{-c,--change=}"[Apply Dockerfile instruction to the created image]:Dockerfile:_files" \ + "($help -m --message)"{-m,--message=}"[Commit message]:message: " \ "($help -p --pause)"{-p,--pause}"[Pause container during commit]" \ "($help -):container:__docker_containers" \ "($help -): :__docker_repositories_with_tags" && ret=0 @@ -513,49 +518,49 @@ __docker_subcommand() { (daemon) _arguments \ $opts_help \ - "($help)--api-cors-header=-[Set CORS headers in the remote API]:CORS headers: " \ - "($help -b --bridge)"{-b,--bridge=-}"[Attach containers to a network bridge]:bridge:_net_interfaces" \ - "($help)--bip=-[Specify network bridge IP]" \ + "($help)--api-cors-header=[Set CORS headers in the remote API]:CORS headers: " \ + "($help -b --bridge)"{-b,--bridge=}"[Attach containers to a network bridge]:bridge:_net_interfaces" \ + "($help)--bip=[Specify network bridge IP]" \ "($help -D --debug)"{-D,--debug}"[Enable debug mode]" \ "($help)--default-gateway[Container default gateway IPv4 address]:IPv4 address: " \ "($help)--default-gateway-v6[Container default gateway IPv6 address]:IPv6 address: " \ - "($help)--cluster-store=-[URL of the distributed storage backend]:Cluster Store:->cluster-store" \ - "($help)--cluster-advertise=-[Address of the daemon instance to advertise]:Instance to advertise (host\:port): " \ + "($help)--cluster-store=[URL of the distributed storage backend]:Cluster Store:->cluster-store" \ + "($help)--cluster-advertise=[Address of the daemon instance to advertise]:Instance to advertise (host\:port): " \ "($help)*--cluster-store-opt[Set cluster options]:Cluster options:->cluster-store-options" \ - "($help)*--dns=-[DNS server to use]:DNS: " \ - "($help)*--dns-search=-[DNS search domains to use]:DNS search: " \ - "($help)*--dns-opt=-[DNS options to use]:DNS option: " \ - "($help)*--default-ulimit=-[Set default ulimit settings for containers]:ulimit: " \ + "($help)*--dns=[DNS server to use]:DNS: " \ + "($help)*--dns-search=[DNS search domains to use]:DNS search: " \ + "($help)*--dns-opt=[DNS options to use]:DNS option: " \ + "($help)*--default-ulimit=[Set default ulimit settings for containers]:ulimit: " \ "($help)--disable-legacy-registry[Do not contact legacy registries]" \ - "($help -e --exec-driver)"{-e,--exec-driver=-}"[Exec driver to use]:driver:(native lxc windows)" \ - "($help)*--exec-opt=-[Set exec driver options]:exec driver options: " \ - "($help)--exec-root=-[Root of the Docker execdriver]:path:_directories" \ - "($help)--fixed-cidr=-[IPv4 subnet for fixed IPs]:IPv4 subnet: " \ - "($help)--fixed-cidr-v6=-[IPv6 subnet for fixed IPs]:IPv6 subnet: " \ - "($help -G --group)"{-G,--group=-}"[Group for the unix socket]:group:_groups" \ - "($help -g --graph)"{-g,--graph=-}"[Root of the Docker runtime]:path:_directories" \ - "($help -H --host)"{-H,--host=-}"[tcp://host:port to bind/connect to]:host: " \ + "($help -e --exec-driver)"{-e,--exec-driver=}"[Exec driver to use]:driver:(native lxc windows)" \ + "($help)*--exec-opt=[Set exec driver options]:exec driver options: " \ + "($help)--exec-root=[Root of the Docker execdriver]:path:_directories" \ + "($help)--fixed-cidr=[IPv4 subnet for fixed IPs]:IPv4 subnet: " \ + "($help)--fixed-cidr-v6=[IPv6 subnet for fixed IPs]:IPv6 subnet: " \ + "($help -G --group)"{-G,--group=}"[Group for the unix socket]:group:_groups" \ + "($help -g --graph)"{-g,--graph=}"[Root of the Docker runtime]:path:_directories" \ + "($help -H --host)"{-H,--host=}"[tcp://host:port to bind/connect to]:host: " \ "($help)--icc[Enable inter-container communication]" \ - "($help)*--insecure-registry=-[Enable insecure registry communication]:registry: " \ - "($help)--ip=-[Default IP when binding container ports]" \ + "($help)*--insecure-registry=[Enable insecure registry communication]:registry: " \ + "($help)--ip=[Default IP when binding container ports]" \ "($help)--ip-forward[Enable net.ipv4.ip_forward]" \ "($help)--ip-masq[Enable IP masquerading]" \ "($help)--iptables[Enable addition of iptables rules]" \ "($help)--ipv6[Enable IPv6 networking]" \ - "($help -l --log-level)"{-l,--log-level=-}"[Set the logging level]:level:(debug info warn error fatal)" \ - "($help)*--label=-[Set key=value labels to the daemon]:label: " \ - "($help)--log-driver=-[Default driver for container logs]:Logging driver:(json-file syslog journald gelf fluentd awslogs none)" \ - "($help)*--log-opt=-[Log driver specific options]:log driver options: " \ - "($help)--mtu=-[Set the containers network MTU]:mtu:(0 576 1420 1500 9000)" \ - "($help -p --pidfile)"{-p,--pidfile=-}"[Path to use for daemon PID file]:PID file:_files" \ - "($help)*--registry-mirror=-[Preferred Docker registry mirror]:registry mirror: " \ - "($help -s --storage-driver)"{-s,--storage-driver=-}"[Storage driver to use]:driver:(aufs devicemapper btrfs zfs overlay)" \ + "($help -l --log-level)"{-l,--log-level=}"[Set the logging level]:level:(debug info warn error fatal)" \ + "($help)*--label=[Set key=value labels to the daemon]:label: " \ + "($help)--log-driver=[Default driver for container logs]:Logging driver:(json-file syslog journald gelf fluentd awslogs none)" \ + "($help)*--log-opt=[Log driver specific options]:log driver options: " \ + "($help)--mtu=[Set the containers network MTU]:mtu:(0 576 1420 1500 9000)" \ + "($help -p --pidfile)"{-p,--pidfile=}"[Path to use for daemon PID file]:PID file:_files" \ + "($help)*--registry-mirror=[Preferred Docker registry mirror]:registry mirror: " \ + "($help -s --storage-driver)"{-s,--storage-driver=}"[Storage driver to use]:driver:(aufs devicemapper btrfs zfs overlay)" \ "($help)--selinux-enabled[Enable selinux support]" \ - "($help)*--storage-opt=-[Set storage driver options]:storage driver options: " \ + "($help)*--storage-opt=[Set storage driver options]:storage driver options: " \ "($help)--tls[Use TLS]" \ - "($help)--tlscacert=-[Trust certs signed only by this CA]:PEM file:_files -g "*.(pem|crt)"" \ - "($help)--tlscert=-[Path to TLS certificate file]:PEM file:_files -g "*.(pem|crt)"" \ - "($help)--tlskey=-[Path to TLS key file]:Key file:_files -g "*.(pem|key)"" \ + "($help)--tlscacert=[Trust certs signed only by this CA]:PEM file:_files -g "*.(pem|crt)"" \ + "($help)--tlscert=[Path to TLS certificate file]:PEM file:_files -g "*.(pem|crt)"" \ + "($help)--tlskey=[Path to TLS key file]:Key file:_files -g "*.(pem|key)"" \ "($help)--tlsverify[Use TLS and verify the remote]" \ "($help)--userland-proxy[Use userland proxy for loopback traffic]" && ret=0 @@ -586,9 +591,9 @@ __docker_subcommand() { (events) _arguments \ $opts_help \ - "($help)*"{-f,--filter=-}"[Filter values]:filter: " \ - "($help)--since=-[Events created since this timestamp]:timestamp: " \ - "($help)--until=-[Events created until this timestamp]:timestamp: " && ret=0 + "($help)*"{-f,--filter=}"[Filter values]:filter: " \ + "($help)--since=[Events created since this timestamp]:timestamp: " \ + "($help)--until=[Events created until this timestamp]:timestamp: " && ret=0 ;; (exec) local state @@ -598,7 +603,7 @@ __docker_subcommand() { "($help -i --interactive)"{-i,--interactive}"[Keep stdin open even if not attached]" \ "($help)--privileged[Give extended Linux capabilities to the command]" \ "($help -t --tty)"{-t,--tty}"[Allocate a pseudo-tty]" \ - "($help -u --user)"{-u,--user=-}"[Username or UID]:user:_users" \ + "($help -u --user)"{-u,--user=}"[Username or UID]:user:_users" \ "($help -):containers:__docker_runningcontainers" \ "($help -)*::command:->anycommand" && ret=0 @@ -613,7 +618,7 @@ __docker_subcommand() { (export) _arguments \ $opts_help \ - "($help -o --output)"{-o,--output=-}"[Write to a file, instead of stdout]:output file:_files" \ + "($help -o --output)"{-o,--output=}"[Write to a file, instead of stdout]:output file:_files" \ "($help -)*:containers:__docker_containers" && ret=0 ;; (history) @@ -629,7 +634,7 @@ __docker_subcommand() { $opts_help \ "($help -a --all)"{-a,--all}"[Show all images]" \ "($help)--digest[Show digests]" \ - "($help)*"{-f,--filter=-}"[Filter values]:filter: " \ + "($help)*"{-f,--filter=}"[Filter values]:filter: " \ "($help)--no-trunc[Do not truncate output]" \ "($help -q --quiet)"{-q,--quiet}"[Only show numeric IDs]" \ "($help -): :__docker_repositories" && ret=0 @@ -637,7 +642,8 @@ __docker_subcommand() { (import) _arguments \ $opts_help \ - "($help -c --change)*"{-c,--change=-}"[Apply Dockerfile instruction to the created image]:Dockerfile:_files" \ + "($help)*"{-c,--change=}"[Apply Dockerfile instruction to the created image]:Dockerfile:_files" \ + "($help -m --message)"{-m,--message=}"[Set commit message for imported image]:message: " \ "($help -):URL:(- http:// file://)" \ "($help -): :__docker_repositories_with_tags" && ret=0 ;; @@ -649,9 +655,9 @@ __docker_subcommand() { local state _arguments \ $opts_help \ - "($help -f --format=-)"{-f,--format=-}"[Format the output using the given go template]:template: " \ + "($help -f --format)"{-f,--format=}"[Format the output using the given go template]:template: " \ "($help -s --size)"{-s,--size}"[Display total file sizes if the type is container]" \ - "($help)--type=-[Return JSON for specified type]:type:(image container)" \ + "($help)--type=[Return JSON for specified type]:type:(image container)" \ "($help -)*: :->values" && ret=0 case $state in @@ -669,20 +675,20 @@ __docker_subcommand() { (kill) _arguments \ $opts_help \ - "($help -s --signal)"{-s,--signal=-}"[Signal to send]:signal:_signals" \ + "($help -s --signal)"{-s,--signal=}"[Signal to send]:signal:_signals" \ "($help -)*:containers:__docker_runningcontainers" && ret=0 ;; (load) _arguments \ $opts_help \ - "($help -i --input)"{-i,--input=-}"[Read from tar archive file]:archive file:_files -g "*.((tar|TAR)(.gz|.GZ|.Z|.bz2|.lzma|.xz|)|(tbz|tgz|txz))(-.)"" && ret=0 + "($help -i --input)"{-i,--input=}"[Read from tar archive file]:archive file:_files -g "*.((tar|TAR)(.gz|.GZ|.Z|.bz2|.lzma|.xz|)|(tbz|tgz|txz))(-.)"" && ret=0 ;; (login) _arguments \ $opts_help \ - "($help -e --email)"{-e,--email=-}"[Email]:email: " \ - "($help -p --password)"{-p,--password=-}"[Password]:password: " \ - "($help -u --user)"{-u,--user=-}"[Username]:username: " \ + "($help -e --email)"{-e,--email=}"[Email]:email: " \ + "($help -p --password)"{-p,--password=}"[Password]:password: " \ + "($help -u --user)"{-u,--user=}"[Username]:username: " \ "($help -)1:server: " && ret=0 ;; (logout) @@ -694,9 +700,9 @@ __docker_subcommand() { _arguments \ $opts_help \ "($help -f --follow)"{-f,--follow}"[Follow log output]" \ - "($help -s --since)"{-s,--since=-}"[Show logs since this timestamp]:timestamp: " \ + "($help -s --since)"{-s,--since=}"[Show logs since this timestamp]:timestamp: " \ "($help -t --timestamps)"{-t,--timestamps}"[Show timestamps]" \ - "($help)--tail=-[Output the last K lines]:lines:(1 10 20 50 all)" \ + "($help)--tail=[Output the last K lines]:lines:(1 10 20 50 all)" \ "($help -)*:containers:__docker_containers" && ret=0 ;; (network) @@ -731,15 +737,15 @@ __docker_subcommand() { _arguments \ $opts_help \ "($help -a --all)"{-a,--all}"[Show all containers]" \ - "($help)--before=-[Show only container created before...]:containers:__docker_containers" \ - "($help)*"{-f,--filter=-}"[Filter values]:filter: " \ + "($help)--before=[Show only container created before...]:containers:__docker_containers" \ + "($help)*"{-f,--filter=}"[Filter values]:filter: " \ "($help)--format[Pretty-print containers using a Go template]:format: " \ "($help -l --latest)"{-l,--latest}"[Show only the latest created container]" \ "($help)-n[Show n last created containers, include non-running one]:n:(1 5 10 25 50)" \ "($help)--no-trunc[Do not truncate output]" \ "($help -q --quiet)"{-q,--quiet}"[Only show numeric IDs]" \ "($help -s --size)"{-s,--size}"[Display total file sizes]" \ - "($help)--since=-[Show only containers created since...]:containers:__docker_containers" && ret=0 + "($help)--since=[Show only containers created since...]:containers:__docker_containers" && ret=0 ;; (pull) _arguments \ @@ -761,7 +767,7 @@ __docker_subcommand() { (restart|stop) _arguments \ $opts_help \ - "($help -t --time=-)"{-t,--time=-}"[Number of seconds to try to stop for before killing the container]:seconds to before killing:(1 5 10 30 60)" \ + "($help -t --time)"{-t,--time=}"[Number of seconds to try to stop for before killing the container]:seconds to before killing:(1 5 10 30 60)" \ "($help -)*:containers:__docker_runningcontainers" && ret=0 ;; (rm) @@ -787,7 +793,7 @@ __docker_subcommand() { "($help -d --detach)"{-d,--detach}"[Detached mode: leave the container running in the background]" \ "($help)--rm[Remove intermediate containers when it exits]" \ "($help)--sig-proxy[Proxy all received signals to the process (non-TTY mode only)]" \ - "($help)--stop-signal=-[Signal to kill a container]:signal:_signals" \ + "($help)--stop-signal=[Signal to kill a container]:signal:_signals" \ "($help -): :__docker_images" \ "($help -):command: _command_names -e" \ "($help -)*::arguments: _normal" && ret=0 @@ -806,7 +812,7 @@ __docker_subcommand() { (save) _arguments \ $opts_help \ - "($help -o --output)"{-o,--output=-}"[Write to file]:file:_files" \ + "($help -o --output)"{-o,--output=}"[Write to file]:file:_files" \ "($help -)*: :__docker_images" && ret=0 ;; (search) @@ -814,7 +820,7 @@ __docker_subcommand() { $opts_help \ "($help)--automated[Only show automated builds]" \ "($help)--no-trunc[Do not truncate output]" \ - "($help -s --stars)"{-s,--stars=-}"[Only display with at least X stars]:stars:(0 10 100 1000)" \ + "($help -s --stars)"{-s,--stars=}"[Only display with at least X stars]:stars:(0 10 100 1000)" \ "($help -):term: " && ret=0 ;; (start) @@ -895,12 +901,12 @@ _docker() { "(: -)"{-h,--help}"[Print usage]" \ "($help)--config[Location of client config files]:path:_directories" \ "($help -D --debug)"{-D,--debug}"[Enable debug mode]" \ - "($help -H --host)"{-H,--host=-}"[tcp://host:port to bind/connect to]:host: " \ - "($help -l --log-level)"{-l,--log-level=-}"[Set the logging level]:level:(debug info warn error fatal)" \ + "($help -H --host)"{-H,--host=}"[tcp://host:port to bind/connect to]:host: " \ + "($help -l --log-level)"{-l,--log-level=}"[Set the logging level]:level:(debug info warn error fatal)" \ "($help)--tls[Use TLS]" \ - "($help)--tlscacert=-[Trust certs signed only by this CA]:PEM file:_files -g "*.(pem|crt)"" \ - "($help)--tlscert=-[Path to TLS certificate file]:PEM file:_files -g "*.(pem|crt)"" \ - "($help)--tlskey=-[Path to TLS key file]:Key file:_files -g "*.(pem|key)"" \ + "($help)--tlscacert=[Trust certs signed only by this CA]:PEM file:_files -g "*.(pem|crt)"" \ + "($help)--tlscert=[Path to TLS certificate file]:PEM file:_files -g "*.(pem|crt)"" \ + "($help)--tlskey=[Path to TLS key file]:Key file:_files -g "*.(pem|key)"" \ "($help)--tlsverify[Use TLS and verify the remote]" \ "($help)--userland-proxy[Use userland proxy for loopback traffic]" \ "($help -v --version)"{-v,--version}"[Print version information and quit]" \ diff --git a/daemon/config.go b/daemon/config.go index d335b6557..436f8cdac 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -71,7 +71,7 @@ func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) cmd.Var(opts.NewListOptsRef(&config.Labels, opts.ValidateLabel), []string{"-label"}, usageFn("Set key=value labels to the daemon")) cmd.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", usageFn("Default driver for container logs")) cmd.Var(opts.NewMapOpts(config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Set log driver options")) - cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address of the daemon instance to advertise")) + cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address or interface name to advertise")) cmd.StringVar(&config.ClusterStore, []string{"-cluster-store"}, "", usageFn("Set the cluster store")) cmd.Var(opts.NewMapOpts(config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options")) } diff --git a/daemon/container.go b/daemon/container.go index 53807f90e..938bb3714 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -328,10 +328,6 @@ func (streamConfig *streamConfig) StderrPipe() io.ReadCloser { return ioutils.NewBufReader(reader) } -func (container *Container) isNetworkAllocated() bool { - return container.NetworkSettings.IPAddress != "" -} - // cleanup releases any network resources allocated to the container along with any rules // around how containers are linked together. It also unmounts the container's root filesystem. func (container *Container) cleanup() { diff --git a/daemon/container_unix.go b/daemon/container_unix.go index ef708c104..3c51de2ca 100644 --- a/daemon/container_unix.go +++ b/daemon/container_unix.go @@ -89,15 +89,25 @@ func (container *Container) setupLinkedContainers() ([]string, error) { return nil, err } + bridgeSettings := container.NetworkSettings.Networks["bridge"] + if bridgeSettings == nil { + return nil, nil + } + if len(children) > 0 { for linkAlias, child := range children { if !child.IsRunning() { return nil, derr.ErrorCodeLinkNotRunning.WithArgs(child.Name, linkAlias) } + childBridgeSettings := child.NetworkSettings.Networks["bridge"] + if childBridgeSettings == nil { + return nil, fmt.Errorf("container %d not attached to default bridge network", child.ID) + } + link := links.NewLink( - container.NetworkSettings.IPAddress, - child.NetworkSettings.IPAddress, + bridgeSettings.IPAddress, + childBridgeSettings.IPAddress, linkAlias, child.Config.Env, child.Config.ExposedPorts, @@ -218,6 +228,12 @@ func populateCommand(c *Container, env []string) error { } else { ipc.HostIpc = c.hostConfig.IpcMode.IsHost() if ipc.HostIpc { + if _, err := os.Stat("/dev/shm"); err != nil { + return fmt.Errorf("/dev/shm is not mounted, but must be for --host=ipc") + } + if _, err := os.Stat("/dev/mqueue"); err != nil { + return fmt.Errorf("/dev/mqueue is not mounted, but must be for --host=ipc") + } c.ShmPath = "/dev/shm" c.MqueuePath = "/dev/mqueue" } @@ -525,6 +541,9 @@ func (container *Container) buildSandboxOptions(n libnetwork.Network) ([]libnetw } for linkAlias, child := range children { + if !isLinkable(child) { + return nil, fmt.Errorf("Cannot link to %s, as it does not belong to the default network", child.Name) + } _, alias := path.Split(linkAlias) // allow access to the linked container via the alias, real name, and container hostname aliasList := alias + " " + child.Config.Hostname @@ -532,13 +551,14 @@ func (container *Container) buildSandboxOptions(n libnetwork.Network) ([]libnetw if alias != child.Name[1:] { aliasList = aliasList + " " + child.Name[1:] } - sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(aliasList, child.NetworkSettings.IPAddress)) + sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(aliasList, child.NetworkSettings.Networks["bridge"].IPAddress)) cEndpoint, _ := child.getEndpointInNetwork(n) if cEndpoint != nil && cEndpoint.ID() != "" { childEndpoints = append(childEndpoints, cEndpoint.ID()) } } + bridgeSettings := container.NetworkSettings.Networks["bridge"] refs := container.daemon.containerGraph().RefPaths(container.ID) for _, ref := range refs { if ref.ParentID == "0" { @@ -551,8 +571,8 @@ func (container *Container) buildSandboxOptions(n libnetwork.Network) ([]libnetw } if c != nil && !container.daemon.configStore.DisableBridge && container.hostConfig.NetworkMode.IsPrivate() { - logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", c.ID, ref.Name, container.NetworkSettings.IPAddress) - sboxOptions = append(sboxOptions, libnetwork.OptionParentUpdate(c.ID, ref.Name, container.NetworkSettings.IPAddress)) + logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", c.ID, ref.Name, bridgeSettings.IPAddress) + sboxOptions = append(sboxOptions, libnetwork.OptionParentUpdate(c.ID, ref.Name, bridgeSettings.IPAddress)) if ep.ID() != "" { parentEndpoints = append(parentEndpoints, ep.ID()) } @@ -571,6 +591,12 @@ func (container *Container) buildSandboxOptions(n libnetwork.Network) ([]libnetw return sboxOptions, nil } +func isLinkable(child *Container) bool { + // A container is linkable only if it belongs to the default network + _, ok := child.NetworkSettings.Networks["bridge"] + return ok +} + func (container *Container) getEndpointInNetwork(n libnetwork.Network) (libnetwork.Endpoint, error) { endpointName := strings.TrimPrefix(container.Name, "/") return n.EndpointByName(endpointName) @@ -595,10 +621,6 @@ func (container *Container) buildPortMapInfo(ep libnetwork.Endpoint, networkSett return networkSettings, nil } - if mac, ok := driverInfo[netlabel.MacAddress]; ok { - networkSettings.MacAddress = mac.(net.HardwareAddr).String() - } - networkSettings.Ports = nat.PortMap{} if expData, ok := driverInfo[netlabel.ExposedPorts]; ok { @@ -632,7 +654,7 @@ func (container *Container) buildPortMapInfo(ep libnetwork.Endpoint, networkSett return networkSettings, nil } -func (container *Container) buildEndpointInfo(ep libnetwork.Endpoint, networkSettings *network.Settings) (*network.Settings, error) { +func (container *Container) buildEndpointInfo(n libnetwork.Network, ep libnetwork.Endpoint, networkSettings *network.Settings) (*network.Settings, error) { if ep == nil { return nil, derr.ErrorCodeEmptyEndpoint } @@ -647,36 +669,50 @@ func (container *Container) buildEndpointInfo(ep libnetwork.Endpoint, networkSet return networkSettings, nil } + if _, ok := networkSettings.Networks[n.Name()]; !ok { + networkSettings.Networks[n.Name()] = new(network.EndpointSettings) + } + networkSettings.Networks[n.Name()].EndpointID = ep.ID() + iface := epInfo.Iface() if iface == nil { return networkSettings, nil } + if iface.MacAddress() != nil { + networkSettings.Networks[n.Name()].MacAddress = iface.MacAddress().String() + } + if iface.Address() != nil { ones, _ := iface.Address().Mask.Size() - networkSettings.IPAddress = iface.Address().IP.String() - networkSettings.IPPrefixLen = ones + networkSettings.Networks[n.Name()].IPAddress = iface.Address().IP.String() + networkSettings.Networks[n.Name()].IPPrefixLen = ones } if iface.AddressIPv6() != nil && iface.AddressIPv6().IP.To16() != nil { onesv6, _ := iface.AddressIPv6().Mask.Size() - networkSettings.GlobalIPv6Address = iface.AddressIPv6().IP.String() - networkSettings.GlobalIPv6PrefixLen = onesv6 + networkSettings.Networks[n.Name()].GlobalIPv6Address = iface.AddressIPv6().IP.String() + networkSettings.Networks[n.Name()].GlobalIPv6PrefixLen = onesv6 } return networkSettings, nil } -func (container *Container) updateJoinInfo(ep libnetwork.Endpoint) error { +func (container *Container) updateJoinInfo(n libnetwork.Network, ep libnetwork.Endpoint) error { + if _, err := container.buildPortMapInfo(ep, container.NetworkSettings); err != nil { + return err + } + epInfo := ep.Info() if epInfo == nil { // It is not an error to get an empty endpoint info return nil } - - container.NetworkSettings.Gateway = epInfo.Gateway().String() + if epInfo.Gateway() != nil { + container.NetworkSettings.Networks[n.Name()].Gateway = epInfo.Gateway().String() + } if epInfo.GatewayIPv6().To16() != nil { - container.NetworkSettings.IPv6Gateway = epInfo.GatewayIPv6().String() + container.NetworkSettings.Networks[n.Name()].IPv6Gateway = epInfo.GatewayIPv6().String() } return nil @@ -684,11 +720,10 @@ func (container *Container) updateJoinInfo(ep libnetwork.Endpoint) error { func (container *Container) updateNetworkSettings(n libnetwork.Network) error { if container.NetworkSettings == nil { - container.NetworkSettings = &network.Settings{Networks: []string{}} + container.NetworkSettings = &network.Settings{Networks: make(map[string]*network.EndpointSettings)} } - settings := container.NetworkSettings - for _, s := range settings.Networks { + for s := range container.NetworkSettings.Networks { sn, err := container.daemon.FindNetwork(s) if err != nil { continue @@ -707,18 +742,13 @@ func (container *Container) updateNetworkSettings(n libnetwork.Network) error { return runconfig.ErrConflictNoNetwork } } - settings.Networks = append(settings.Networks, n.Name()) + container.NetworkSettings.Networks[n.Name()] = new(network.EndpointSettings) return nil } func (container *Container) updateEndpointNetworkSettings(n libnetwork.Network, ep libnetwork.Endpoint) error { - networkSettings, err := container.buildPortMapInfo(ep, container.NetworkSettings) - if err != nil { - return err - } - - networkSettings, err = container.buildEndpointInfo(ep, networkSettings) + networkSettings, err := container.buildEndpointInfo(n, ep, container.NetworkSettings) if err != nil { return err } @@ -749,7 +779,7 @@ func (container *Container) updateNetwork() error { // Find if container is connected to the default bridge network var n libnetwork.Network - for _, name := range container.NetworkSettings.Networks { + for name := range container.NetworkSettings.Networks { sn, err := container.daemon.FindNetwork(name) if err != nil { continue @@ -777,7 +807,7 @@ func (container *Container) updateNetwork() error { return nil } -func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointOption, error) { +func (container *Container) buildCreateEndpointOptions(n libnetwork.Network) ([]libnetwork.EndpointOption, error) { var ( portSpecs = make(nat.PortSet) bindings = make(nat.PortMap) @@ -855,6 +885,10 @@ func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointO createOptions = append(createOptions, libnetwork.EndpointOptionGeneric(genericOption)) } + if n.Name() == "bridge" || container.NetworkSettings.IsAnonymousEndpoint { + createOptions = append(createOptions, libnetwork.CreateOptionAnonymous()) + } + return createOptions, nil } @@ -875,11 +909,16 @@ func createNetwork(controller libnetwork.NetworkController, dnet string, driver } func (container *Container) allocateNetwork() error { - settings := container.NetworkSettings.Networks + controller := container.daemon.netController + + // Cleanup any stale sandbox left over due to ungraceful daemon shutdown + if err := controller.SandboxDestroy(container.ID); err != nil { + logrus.Errorf("failed to cleanup up stale network sandbox for container %s", container.ID) + } + updateSettings := false - if settings == nil { + if len(container.NetworkSettings.Networks) == 0 { mode := container.hostConfig.NetworkMode - controller := container.daemon.netController if container.Config.NetworkDisabled || mode.IsContainer() { return nil } @@ -888,32 +927,49 @@ func (container *Container) allocateNetwork() error { if mode.IsDefault() { networkName = controller.Config().Daemon.DefaultNetwork } - settings = []string{networkName} + container.NetworkSettings.Networks = make(map[string]*network.EndpointSettings) + container.NetworkSettings.Networks[networkName] = new(network.EndpointSettings) updateSettings = true } - for _, n := range settings { + for n := range container.NetworkSettings.Networks { if err := container.connectToNetwork(n, updateSettings); err != nil { - if updateSettings { - return err - } - // dont fail a container restart case if the user removed the network - logrus.Warnf("Could not connect container %s : %v", container.ID, err) + return err } } return container.writeHostConfig() } +func (container *Container) getNetworkSandbox() libnetwork.Sandbox { + var sb libnetwork.Sandbox + container.daemon.netController.WalkSandboxes(func(s libnetwork.Sandbox) bool { + if s.ContainerID() == container.ID { + sb = s + return true + } + return false + }) + return sb +} + // ConnectToNetwork connects a container to a netork func (container *Container) ConnectToNetwork(idOrName string) error { if !container.Running { return derr.ErrorCodeNotRunning.WithArgs(container.ID) } - return container.connectToNetwork(idOrName, true) + if err := container.connectToNetwork(idOrName, true); err != nil { + return err + } + if err := container.toDiskLocking(); err != nil { + return fmt.Errorf("Error saving container to disk: %v", err) + } + return nil } func (container *Container) connectToNetwork(idOrName string, updateSettings bool) error { + var err error + if container.hostConfig.NetworkMode.IsContainer() { return runconfig.ErrConflictSharedNetwork } @@ -938,35 +994,37 @@ func (container *Container) connectToNetwork(idOrName string, updateSettings boo } ep, err := container.getEndpointInNetwork(n) - if err != nil { - if _, ok := err.(libnetwork.ErrNoSuchEndpoint); !ok { - return err - } - - createOptions, err := container.buildCreateEndpointOptions() - if err != nil { - return err - } - - endpointName := strings.TrimPrefix(container.Name, "/") - ep, err = n.CreateEndpoint(endpointName, createOptions...) - if err != nil { - return err - } + if err == nil { + return fmt.Errorf("container already connected to network %s", idOrName) } + if _, ok := err.(libnetwork.ErrNoSuchEndpoint); !ok { + return err + } + + createOptions, err := container.buildCreateEndpointOptions(n) + if err != nil { + return err + } + + endpointName := strings.TrimPrefix(container.Name, "/") + ep, err = n.CreateEndpoint(endpointName, createOptions...) + if err != nil { + return err + } + defer func() { + if err != nil { + if e := ep.Delete(); e != nil { + logrus.Warnf("Could not rollback container connection to network %s", idOrName) + } + } + }() + if err := container.updateEndpointNetworkSettings(n, ep); err != nil { return err } - var sb libnetwork.Sandbox - controller.WalkSandboxes(func(s libnetwork.Sandbox) bool { - if s.ContainerID() == container.ID { - sb = s - return true - } - return false - }) + sb := container.getNetworkSandbox() if sb == nil { options, err := container.buildSandboxOptions(n) if err != nil { @@ -976,15 +1034,15 @@ func (container *Container) connectToNetwork(idOrName string, updateSettings boo if err != nil { return err } - } - container.updateSandboxNetworkSettings(sb) + container.updateSandboxNetworkSettings(sb) + } if err := ep.Join(sb); err != nil { return err } - if err := container.updateJoinInfo(ep); err != nil { + if err := container.updateJoinInfo(n, ep); err != nil { return derr.ErrorCodeJoinInfo.WithArgs(err) } @@ -1111,6 +1169,9 @@ func (container *Container) releaseNetwork() { sid := container.NetworkSettings.SandboxID networks := container.NetworkSettings.Networks + for n := range networks { + networks[n] = &network.EndpointSettings{} + } container.NetworkSettings = &network.Settings{Networks: networks} @@ -1124,14 +1185,6 @@ func (container *Container) releaseNetwork() { return } - for _, ns := range networks { - n, err := container.daemon.FindNetwork(ns) - if err != nil { - continue - } - container.disconnectFromNetwork(n, false) - } - if err := sb.Delete(); err != nil { logrus.Errorf("Error deleting sandbox id %s for container %s: %v", sid, container.ID, err) } @@ -1143,10 +1196,17 @@ func (container *Container) DisconnectFromNetwork(n libnetwork.Network) error { return derr.ErrorCodeNotRunning.WithArgs(container.ID) } - return container.disconnectFromNetwork(n, true) + if err := container.disconnectFromNetwork(n); err != nil { + return err + } + + if err := container.toDiskLocking(); err != nil { + return fmt.Errorf("Error saving container to disk: %v", err) + } + return nil } -func (container *Container) disconnectFromNetwork(n libnetwork.Network, updateSettings bool) error { +func (container *Container) disconnectFromNetwork(n libnetwork.Network) error { var ( ep libnetwork.Endpoint sbox libnetwork.Sandbox @@ -1176,20 +1236,7 @@ func (container *Container) disconnectFromNetwork(n libnetwork.Network, updateSe return fmt.Errorf("endpoint delete failed for container %s on network %s: %v", container.ID, n.Name(), err) } - if updateSettings { - networks := container.NetworkSettings.Networks - for i, s := range networks { - sn, err := container.daemon.FindNetwork(s) - if err != nil { - continue - } - if sn.Name() == n.Name() { - networks = append(networks[:i], networks[i+1:]...) - container.NetworkSettings.Networks = networks - break - } - } - } + delete(container.NetworkSettings.Networks, n.Name()) return nil } diff --git a/daemon/daemon.go b/daemon/daemon.go index f01af8bcc..66e1a9d14 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -12,7 +12,6 @@ import ( "io/ioutil" "os" "path/filepath" - "regexp" "runtime" "strings" "sync" @@ -50,6 +49,7 @@ import ( "github.com/docker/docker/pkg/truncindex" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" + "github.com/docker/docker/utils" volumedrivers "github.com/docker/docker/volume/drivers" "github.com/docker/docker/volume/local" "github.com/docker/docker/volume/store" @@ -57,8 +57,8 @@ import ( ) var ( - validContainerNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.-]` - validContainerNamePattern = regexp.MustCompile(`^/?` + validContainerNameChars + `+$`) + validContainerNameChars = utils.RestrictedNameChars + validContainerNamePattern = utils.RestrictedNamePattern errSystemNotSupported = errors.New("The Docker daemon is not supported on this platform.") ) @@ -406,20 +406,12 @@ func (daemon *Daemon) reserveName(id, name string) (string, error) { conflictingContainer, err := daemon.GetByName(name) if err != nil { - if strings.Contains(err.Error(), "Could not find entity") { - return "", err - } - - // Remove name and continue starting the container - if err := daemon.containerGraphDB.Delete(name); err != nil { - return "", err - } - } else { - nameAsKnownByUser := strings.TrimPrefix(name, "/") - return "", fmt.Errorf( - "Conflict. The name %q is already in use by container %s. You have to remove (or rename) that container to be able to reuse that name.", nameAsKnownByUser, - stringid.TruncateID(conflictingContainer.ID)) + return "", err } + return "", fmt.Errorf( + "Conflict. The name %q is already in use by container %s. You have to remove (or rename) that container to be able to reuse that name.", strings.TrimPrefix(name, "/"), + stringid.TruncateID(conflictingContainer.ID)) + } return name, nil } @@ -476,8 +468,9 @@ func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint *stringutils.StrSlic func (daemon *Daemon) newContainer(name string, config *runconfig.Config, imgID string) (*Container, error) { var ( - id string - err error + id string + err error + noExplicitName = name == "" ) id, name, err = daemon.generateIDAndName(name) if err != nil { @@ -494,7 +487,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, imgID base.Config = config base.hostConfig = &runconfig.HostConfig{} base.ImageID = imgID - base.NetworkSettings = &network.Settings{} + base.NetworkSettings = &network.Settings{IsAnonymousEndpoint: noExplicitName} base.Name = name base.Driver = daemon.driver.String() base.ExecDriver = daemon.execDriver.Name() @@ -761,10 +754,17 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo // initialized, the daemon is registered and we can store the discovery backend as its read-only // DiscoveryWatcher version. if config.ClusterStore != "" && config.ClusterAdvertise != "" { - var err error - if d.discoveryWatcher, err = initDiscovery(config.ClusterStore, config.ClusterAdvertise, config.ClusterOpts); err != nil { + advertise, err := discovery.ParseAdvertise(config.ClusterStore, config.ClusterAdvertise) + if err != nil { + return nil, fmt.Errorf("discovery advertise parsing failed (%v)", err) + } + config.ClusterAdvertise = advertise + d.discoveryWatcher, err = initDiscovery(config.ClusterStore, config.ClusterAdvertise, config.ClusterOpts) + if err != nil { return nil, fmt.Errorf("discovery initialization failed (%v)", err) } + } else if config.ClusterAdvertise != "" { + return nil, fmt.Errorf("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration") } d.netController, err = d.initNetworkController(config) @@ -1136,6 +1136,14 @@ func (daemon *Daemon) GetRemappedUIDGID() (int, int) { // created. nil is returned if a child cannot be found. An error is // returned if the parent image cannot be found. func (daemon *Daemon) ImageGetCached(imgID string, config *runconfig.Config) (*image.Image, error) { + // for now just exit if imgID has no children. + // maybe parentRefs in graph could be used to store + // the Image obj children for faster lookup below but this can + // be quite memory hungry. + if !daemon.Graph().HasChildren(imgID) { + return nil, nil + } + // Retrieve all images images := daemon.Graph().Map() diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 3e6c7a6a4..8ea6c5c9a 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -114,9 +114,6 @@ func (daemon *Daemon) adaptContainerSettings(hostConfig *runconfig.HostConfig, a // By default, MemorySwap is set to twice the size of Memory. hostConfig.MemorySwap = hostConfig.Memory * 2 } - if hostConfig.MemoryReservation == 0 && hostConfig.Memory > 0 { - hostConfig.MemoryReservation = hostConfig.Memory - } } // verifyPlatformContainerSettings performs platform-specific validation of the @@ -341,6 +338,9 @@ func (daemon *Daemon) networkOptions(dconfig *Config) ([]nwconfig.Option, error) options = append(options, nwconfig.OptionKVProvider(kv[0])) options = append(options, nwconfig.OptionKVProviderURL(strings.Join(kv[1:], "://"))) } + if len(dconfig.ClusterOpts) > 0 { + options = append(options, nwconfig.OptionKVOpts(dconfig.ClusterOpts)) + } if daemon.discoveryWatcher != nil { options = append(options, nwconfig.OptionDiscoveryWatcher(daemon.discoveryWatcher)) @@ -441,6 +441,8 @@ func initBridgeDriver(controller libnetwork.NetworkController, config *Config) e return err } ipamV4Conf.Gateway = ip.String() + } else if bridgeName == bridge.DefaultBridgeName && ipamV4Conf.PreferredPool != "" { + logrus.Infof("Default bridge (%s) is assigned with an IP address %s. Daemon option --bip can be used to set a preferred IP address", bridgeName, ipamV4Conf.PreferredPool) } if config.Bridge.FixedCIDR != "" { diff --git a/daemon/daemonbuilder/builder.go b/daemon/daemonbuilder/builder.go index d75e9ec8a..52689750f 100644 --- a/daemon/daemonbuilder/builder.go +++ b/daemon/daemonbuilder/builder.go @@ -193,7 +193,7 @@ func (d Docker) Copy(c *daemon.Container, destPath string, src builder.FileInfo, // GetCachedImage returns a reference to a cached image whose parent equals `parent` // and runconfig equals `cfg`. A cache miss is expected to return an empty ID and a nil error. func (d Docker) GetCachedImage(imgID string, cfg *runconfig.Config) (string, error) { - cache, err := d.Daemon.ImageGetCached(string(imgID), cfg) + cache, err := d.Daemon.ImageGetCached(imgID, cfg) if cache == nil || err != nil { return "", err } diff --git a/daemon/delete.go b/daemon/delete.go index 52cc39bea..49ccc7277 100644 --- a/daemon/delete.go +++ b/daemon/delete.go @@ -76,22 +76,20 @@ func (daemon *Daemon) rm(container *Container, forceRemove bool) (err error) { } } + // Container state RemovalInProgress should be used to avoid races. + if err = container.setRemovalInProgress(); err != nil { + if err == derr.ErrorCodeAlreadyRemoving { + // do not fail when the removal is in progress started by other request. + return nil + } + return derr.ErrorCodeRmState.WithArgs(err) + } + defer container.resetRemovalInProgress() + // stop collection of stats for the container regardless // if stats are currently getting collected. daemon.statsCollector.stopCollection(container) - element := daemon.containers.Get(container.ID) - if element == nil { - return derr.ErrorCodeRmNotFound.WithArgs(container.ID) - } - - // Container state RemovalInProgress should be used to avoid races. - if err = container.setRemovalInProgress(); err != nil { - return derr.ErrorCodeRmState.WithArgs(err) - } - - defer container.resetRemovalInProgress() - if err = container.Stop(3); err != nil { return err } diff --git a/daemon/delete_test.go b/daemon/delete_test.go new file mode 100644 index 000000000..9a82fce2d --- /dev/null +++ b/daemon/delete_test.go @@ -0,0 +1,39 @@ +package daemon + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/docker/docker/runconfig" +) + +func TestContainerDoubleDelete(t *testing.T) { + tmp, err := ioutil.TempDir("", "docker-daemon-unix-test-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + daemon := &Daemon{ + repository: tmp, + root: tmp, + } + + container := &Container{ + CommonContainer: CommonContainer{ + State: NewState(), + Config: &runconfig.Config{}, + }, + } + + // Mark the container as having a delete in progress + if err := container.setRemovalInProgress(); err != nil { + t.Fatal(err) + } + + // Try to remove the container when it's start is removalInProgress. + // It should ignore the container and not return an error. + if err := daemon.rm(container, true); err != nil { + t.Fatal(err) + } +} diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 30b36e775..12793bd01 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -324,24 +324,20 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execd c.ContainerPid = pid - oomKill := false - oomKillNotification, err := notifyOnOOM(cgroupPaths) - if hooks.Start != nil { logrus.Debugf("Invoking startCallback") - hooks.Start(&c.ProcessConfig, pid, oomKillNotification) - + chOOM := make(chan struct{}) + close(chOOM) + hooks.Start(&c.ProcessConfig, pid, chOOM) } + oomKillNotification := notifyChannelOOM(cgroupPaths) + <-waitLock exitCode := getExitCode(c) - if err == nil { - _, oomKill = <-oomKillNotification - logrus.Debugf("oomKill error: %v, waitErr: %v", oomKill, waitErr) - } else { - logrus.Warnf("Your kernel does not support OOM notifications: %s", err) - } + _, oomKill := <-oomKillNotification + logrus.Debugf("oomKill error: %v, waitErr: %v", oomKill, waitErr) // check oom error if oomKill { @@ -351,6 +347,17 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execd return execdriver.ExitStatus{ExitCode: exitCode, OOMKilled: oomKill}, waitErr } +func notifyChannelOOM(paths map[string]string) <-chan struct{} { + oom, err := notifyOnOOM(paths) + if err != nil { + logrus.Warnf("Your kernel does not support OOM notifications: %s", err) + c := make(chan struct{}) + close(c) + return c + } + return oom +} + // copy from libcontainer func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) { dir := paths["memory"] @@ -386,11 +393,13 @@ func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) { buf := make([]byte, 8) for { if _, err := eventfd.Read(buf); err != nil { + logrus.Warn(err) return } // When a cgroup is destroyed, an event is sent to eventfd. // So if the control path is gone, return instead of notifying. if _, err := os.Lstat(eventControlPath); os.IsNotExist(err) { + logrus.Warn(err) return } ch <- struct{}{} @@ -424,6 +433,11 @@ func cgroupPaths(containerID string) (map[string]string, error) { //unsupported subystem continue } + // if we are running dind + dockerPathIdx := strings.LastIndex(cgroupDir, "docker") + if dockerPathIdx != -1 { + cgroupDir = cgroupDir[:dockerPathIdx-1] + } path := filepath.Join(cgroupRoot, cgroupDir, "lxc", containerID) paths[subsystem] = path } diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 975c5f19e..6b7996f04 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -91,9 +91,9 @@ lxc.mount.entry = {{$value.Source}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabS {{if .Resources}} {{if .Resources.Memory}} lxc.cgroup.memory.limit_in_bytes = {{.Resources.Memory}} -{{with $memSwap := getMemorySwap .Resources}} -lxc.cgroup.memory.memsw.limit_in_bytes = {{$memSwap}} {{end}} +{{if gt .Resources.MemorySwap 0}} +lxc.cgroup.memory.memsw.limit_in_bytes = {{.Resources.MemorySwap}} {{end}} {{if gt .Resources.MemoryReservation 0}} lxc.cgroup.memory.soft_limit_in_bytes = {{.Resources.MemoryReservation}} @@ -209,15 +209,6 @@ func isDirectory(source string) string { return "file" } -func getMemorySwap(v *execdriver.Resources) int64 { - // By default, MemorySwap is set to twice the size of RAM. - // If you want to omit MemorySwap, set it to `-1'. - if v.MemorySwap < 0 { - return 0 - } - return v.Memory * 2 -} - func getLabel(c map[string][]string, name string) string { label := c["label"] for _, l := range label { @@ -242,7 +233,6 @@ func getHostname(env []string) string { func init() { var err error funcMap := template.FuncMap{ - "getMemorySwap": getMemorySwap, "escapeFstabSpaces": escapeFstabSpaces, "formatMountLabel": label.FormatMountLabel, "isDirectory": isDirectory, diff --git a/daemon/execdriver/lxc/lxc_template_unit_test.go b/daemon/execdriver/lxc/lxc_template_unit_test.go index afb5b1eb6..01bc3eaef 100644 --- a/daemon/execdriver/lxc/lxc_template_unit_test.go +++ b/daemon/execdriver/lxc/lxc_template_unit_test.go @@ -34,6 +34,7 @@ func TestLXCConfig(t *testing.T) { memMin = 33554432 memMax = 536870912 mem = memMin + r.Intn(memMax-memMin) + swap = memMax cpuMin = 100 cpuMax = 10000 cpu = cpuMin + r.Intn(cpuMax-cpuMin) @@ -46,8 +47,9 @@ func TestLXCConfig(t *testing.T) { command := &execdriver.Command{ ID: "1", Resources: &execdriver.Resources{ - Memory: int64(mem), - CPUShares: int64(cpu), + Memory: int64(mem), + MemorySwap: int64(swap), + CPUShares: int64(cpu), }, Network: &execdriver.Network{ Mtu: 1500, @@ -63,7 +65,7 @@ func TestLXCConfig(t *testing.T) { fmt.Sprintf("lxc.cgroup.memory.limit_in_bytes = %d", mem)) grepFile(t, p, - fmt.Sprintf("lxc.cgroup.memory.memsw.limit_in_bytes = %d", mem*2)) + fmt.Sprintf("lxc.cgroup.memory.memsw.limit_in_bytes = %d", swap)) } func TestCustomLxcConfig(t *testing.T) { diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 09f84a37b..94f200a31 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -167,7 +167,6 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execd oom := notifyOnOOM(cont) if hooks.Start != nil { - pid, err := p.Pid() if err != nil { p.Signal(os.Kill) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 130f2e3a2..06933aec8 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -599,6 +599,7 @@ func (devices *DeviceSet) cleanupDeletedDevices() error { // If there are no deleted devices, there is nothing to do. if devices.nrDeletedDevices == 0 { + devices.Unlock() return nil } diff --git a/daemon/graphdriver/devmapper/devmapper_test.go b/daemon/graphdriver/devmapper/devmapper_test.go index 61577b094..5c2abcefc 100644 --- a/daemon/graphdriver/devmapper/devmapper_test.go +++ b/daemon/graphdriver/devmapper/devmapper_test.go @@ -5,6 +5,7 @@ package devmapper import ( "fmt" "testing" + "time" "github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/daemon/graphdriver/graphtest" @@ -79,3 +80,31 @@ func testChangeLoopBackSize(t *testing.T, delta, expectDataSize, expectMetaDataS t.Fatal(err) } } + +// Make sure devices.Lock() has been release upon return from cleanupDeletedDevices() function +func TestDevmapperLockReleasedDeviceDeletion(t *testing.T) { + driver := graphtest.GetDriver(t, "devicemapper").(*graphtest.Driver).Driver.(*graphdriver.NaiveDiffDriver).ProtoDriver.(*Driver) + defer graphtest.PutDriver(t) + + // Call cleanupDeletedDevices() and after the call take and release + // DeviceSet Lock. If lock has not been released, this will hang. + driver.DeviceSet.cleanupDeletedDevices() + + doneChan := make(chan bool) + + go func() { + driver.DeviceSet.Lock() + defer driver.DeviceSet.Unlock() + doneChan <- true + }() + + select { + case <-time.After(time.Second * 5): + // Timer expired. That means lock was not released upon + // function return and we are deadlocked. Release lock + // here so that cleanup could succeed and fail the test. + driver.DeviceSet.Unlock() + t.Fatalf("Could not acquire devices lock after call to cleanupDeletedDevices()") + case <-doneChan: + } +} diff --git a/daemon/image_delete.go b/daemon/image_delete.go index efe636281..3bcf023b4 100644 --- a/daemon/image_delete.go +++ b/daemon/image_delete.go @@ -84,6 +84,11 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I daemon.EventsService.Log("untag", img.ID, "") records = append(records, untaggedRecord) + // If has remaining references then untag finishes the remove + if daemon.repositories.HasReferences(img) { + return records, nil + } + removedRepositoryRef = true } else { // If an ID reference was given AND there is exactly one @@ -279,7 +284,7 @@ func (daemon *Daemon) checkImageDeleteHardConflict(img *image.Image) *imageDelet } // Check if the image has any descendent images. - if daemon.Graph().HasChildren(img) { + if daemon.Graph().HasChildren(img.ID) { return &imageDeleteConflict{ hard: true, imgID: img.ID, @@ -337,5 +342,5 @@ func (daemon *Daemon) checkImageDeleteSoftConflict(img *image.Image) *imageDelet // that there are no repository references to the given image and it has no // child images. func (daemon *Daemon) imageIsDangling(img *image.Image) bool { - return !(daemon.repositories.HasReferences(img) || daemon.Graph().HasChildren(img)) + return !(daemon.repositories.HasReferences(img) || daemon.Graph().HasChildren(img.ID)) } diff --git a/daemon/info.go b/daemon/info.go index 395c34d40..f977f6970 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -92,6 +92,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { ExperimentalBuild: utils.ExperimentalBuild(), ServerVersion: dockerversion.VERSION, ClusterStore: daemon.config().ClusterStore, + ClusterAdvertise: daemon.config().ClusterAdvertise, } // TODO Windows. Refactor this more once sysinfo is refactored into diff --git a/daemon/inspect.go b/daemon/inspect.go index ad5463bd6..86eba4da7 100644 --- a/daemon/inspect.go +++ b/daemon/inspect.go @@ -6,6 +6,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions/v1p20" + "github.com/docker/docker/daemon/network" ) // ContainerInspect returns low-level information about a @@ -26,8 +27,23 @@ func (daemon *Daemon) ContainerInspect(name string, size bool) (*types.Container } mountPoints := addMountPoints(container) + networkSettings := &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Bridge: container.NetworkSettings.Bridge, + SandboxID: container.NetworkSettings.SandboxID, + HairpinMode: container.NetworkSettings.HairpinMode, + LinkLocalIPv6Address: container.NetworkSettings.LinkLocalIPv6Address, + LinkLocalIPv6PrefixLen: container.NetworkSettings.LinkLocalIPv6PrefixLen, + Ports: container.NetworkSettings.Ports, + SandboxKey: container.NetworkSettings.SandboxKey, + SecondaryIPAddresses: container.NetworkSettings.SecondaryIPAddresses, + SecondaryIPv6Addresses: container.NetworkSettings.SecondaryIPv6Addresses, + }, + DefaultNetworkSettings: daemon.getDefaultNetworkSettings(container.NetworkSettings.Networks), + Networks: container.NetworkSettings.Networks, + } - return &types.ContainerJSON{base, mountPoints, container.Config}, nil + return &types.ContainerJSON{base, mountPoints, container.Config, networkSettings}, nil } // ContainerInspect120 serializes the master version of a container into a json type. @@ -48,10 +64,14 @@ func (daemon *Daemon) ContainerInspect120(name string) (*v1p20.ContainerJSON, er mountPoints := addMountPoints(container) config := &v1p20.ContainerConfig{ container.Config, + container.Config.MacAddress, + container.Config.NetworkDisabled, + container.Config.ExposedPorts, container.hostConfig.VolumeDriver, } + networkSettings := daemon.getBackwardsCompatibleNetworkSettings(container.NetworkSettings) - return &v1p20.ContainerJSON{base, mountPoints, config}, nil + return &v1p20.ContainerJSON{base, mountPoints, config, networkSettings}, nil } func (daemon *Daemon) getInspectData(container *Container, size bool) (*types.ContainerJSONBase, error) { @@ -88,22 +108,21 @@ func (daemon *Daemon) getInspectData(container *Container, size bool) (*types.Co } contJSONBase := &types.ContainerJSONBase{ - ID: container.ID, - Created: container.Created.Format(time.RFC3339Nano), - Path: container.Path, - Args: container.Args, - State: containerState, - Image: container.ImageID, - NetworkSettings: container.NetworkSettings, - LogPath: container.LogPath, - Name: container.Name, - RestartCount: container.RestartCount, - Driver: container.Driver, - ExecDriver: container.ExecDriver, - MountLabel: container.MountLabel, - ProcessLabel: container.ProcessLabel, - ExecIDs: container.getExecIDs(), - HostConfig: &hostConfig, + ID: container.ID, + Created: container.Created.Format(time.RFC3339Nano), + Path: container.Path, + Args: container.Args, + State: containerState, + Image: container.ImageID, + LogPath: container.LogPath, + Name: container.Name, + RestartCount: container.RestartCount, + Driver: container.Driver, + ExecDriver: container.ExecDriver, + MountLabel: container.MountLabel, + ProcessLabel: container.ProcessLabel, + ExecIDs: container.getExecIDs(), + HostConfig: &hostConfig, } var ( @@ -148,3 +167,40 @@ func (daemon *Daemon) VolumeInspect(name string) (*types.Volume, error) { } return volumeToAPIType(v), nil } + +func (daemon *Daemon) getBackwardsCompatibleNetworkSettings(settings *network.Settings) *v1p20.NetworkSettings { + result := &v1p20.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Bridge: settings.Bridge, + SandboxID: settings.SandboxID, + HairpinMode: settings.HairpinMode, + LinkLocalIPv6Address: settings.LinkLocalIPv6Address, + LinkLocalIPv6PrefixLen: settings.LinkLocalIPv6PrefixLen, + Ports: settings.Ports, + SandboxKey: settings.SandboxKey, + SecondaryIPAddresses: settings.SecondaryIPAddresses, + SecondaryIPv6Addresses: settings.SecondaryIPv6Addresses, + }, + DefaultNetworkSettings: daemon.getDefaultNetworkSettings(settings.Networks), + } + + return result +} + +// getDefaultNetworkSettings creates the deprecated structure that holds the information +// about the bridge network for a container. +func (daemon *Daemon) getDefaultNetworkSettings(networks map[string]*network.EndpointSettings) types.DefaultNetworkSettings { + var settings types.DefaultNetworkSettings + + if defaultNetwork, ok := networks["bridge"]; ok { + settings.EndpointID = defaultNetwork.EndpointID + settings.Gateway = defaultNetwork.Gateway + settings.GlobalIPv6Address = defaultNetwork.GlobalIPv6Address + settings.GlobalIPv6PrefixLen = defaultNetwork.GlobalIPv6PrefixLen + settings.IPAddress = defaultNetwork.IPAddress + settings.IPPrefixLen = defaultNetwork.IPPrefixLen + settings.IPv6Gateway = defaultNetwork.IPv6Gateway + settings.MacAddress = defaultNetwork.MacAddress + } + return settings +} diff --git a/daemon/inspect_unix.go b/daemon/inspect_unix.go index 4e70f5904..92c967a3a 100644 --- a/daemon/inspect_unix.go +++ b/daemon/inspect_unix.go @@ -41,14 +41,18 @@ func (daemon *Daemon) ContainerInspectPre120(name string) (*v1p19.ContainerJSON, config := &v1p19.ContainerConfig{ container.Config, + container.Config.MacAddress, + container.Config.NetworkDisabled, + container.Config.ExposedPorts, container.hostConfig.VolumeDriver, container.hostConfig.Memory, container.hostConfig.MemorySwap, container.hostConfig.CPUShares, container.hostConfig.CpusetCpus, } + networkSettings := daemon.getBackwardsCompatibleNetworkSettings(container.NetworkSettings) - return &v1p19.ContainerJSON{base, volumes, volumesRW, config}, nil + return &v1p19.ContainerJSON{base, volumes, volumesRW, config, networkSettings}, nil } func addMountPoints(container *Container) []types.MountPoint { diff --git a/daemon/network.go b/daemon/network.go index 2cc164710..845946705 100644 --- a/daemon/network.go +++ b/daemon/network.go @@ -17,6 +17,12 @@ const ( NetworkByName ) +// NetworkControllerEnabled checks if the networking stack is enabled. +// This feature depends on OS primitives and it's dissabled in systems like Windows. +func (daemon *Daemon) NetworkControllerEnabled() bool { + return daemon.netController != nil +} + // FindNetwork function finds a network for a given string that can represent network name or id func (daemon *Daemon) FindNetwork(idName string) (libnetwork.Network, error) { // Find by Name @@ -80,7 +86,7 @@ func (daemon *Daemon) GetNetworksByID(partialID string) []libnetwork.Network { } // CreateNetwork creates a network with the given name, driver and other optional parameters -func (daemon *Daemon) CreateNetwork(name, driver string, ipam network.IPAM) (libnetwork.Network, error) { +func (daemon *Daemon) CreateNetwork(name, driver string, ipam network.IPAM, options map[string]string) (libnetwork.Network, error) { c := daemon.netController if driver == "" { driver = c.Config().Daemon.DefaultDriver @@ -93,9 +99,8 @@ func (daemon *Daemon) CreateNetwork(name, driver string, ipam network.IPAM) (lib return nil, err } - if len(ipam.Config) > 0 { - nwOptions = append(nwOptions, libnetwork.NetworkOptionIpam(ipam.Driver, "", v4Conf, v6Conf)) - } + nwOptions = append(nwOptions, libnetwork.NetworkOptionIpam(ipam.Driver, "", v4Conf, v6Conf)) + nwOptions = append(nwOptions, libnetwork.NetworkOptionDriverOpts(options)) return c.NewNetwork(driver, name, nwOptions...) } diff --git a/daemon/network/settings.go b/daemon/network/settings.go index d59c369db..ddc87a7c7 100644 --- a/daemon/network/settings.go +++ b/daemon/network/settings.go @@ -10,37 +10,42 @@ type Address struct { // IPAM represents IP Address Management type IPAM struct { - Driver string `json:"driver"` - Config []IPAMConfig `json:"config"` + Driver string + Config []IPAMConfig } // IPAMConfig represents IPAM configurations type IPAMConfig struct { - Subnet string `json:"subnet,omitempty"` - IPRange string `json:"ip_range,omitempty"` - Gateway string `json:"gateway,omitempty"` - AuxAddress map[string]string `json:"auxiliary_address,omitempty"` + Subnet string `json:",omitempty"` + IPRange string `json:",omitempty"` + Gateway string `json:",omitempty"` + AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"` } // Settings stores configuration details about the daemon network config // TODO Windows. Many of these fields can be factored out., type Settings struct { Bridge string - EndpointID string SandboxID string - Gateway string - GlobalIPv6Address string - GlobalIPv6PrefixLen int HairpinMode bool - IPAddress string - IPPrefixLen int - IPv6Gateway string LinkLocalIPv6Address string LinkLocalIPv6PrefixLen int - MacAddress string - Networks []string + Networks map[string]*EndpointSettings Ports nat.PortMap SandboxKey string SecondaryIPAddresses []Address SecondaryIPv6Addresses []Address + IsAnonymousEndpoint bool +} + +// EndpointSettings stores the network endpoint details +type EndpointSettings struct { + EndpointID string + Gateway string + IPAddress string + IPPrefixLen int + IPv6Gateway string + GlobalIPv6Address string + GlobalIPv6PrefixLen int + MacAddress string } diff --git a/daemon/rename.go b/daemon/rename.go index de80e609f..1aab1f930 100644 --- a/daemon/rename.go +++ b/daemon/rename.go @@ -1,18 +1,28 @@ package daemon import ( + "github.com/Sirupsen/logrus" derr "github.com/docker/docker/errors" + "github.com/docker/libnetwork" + "strings" ) // ContainerRename changes the name of a container, using the oldName // to find the container. An error is returned if newName is already // reserved. func (daemon *Daemon) ContainerRename(oldName, newName string) error { + var ( + err error + sid string + sb libnetwork.Sandbox + container *Container + ) + if oldName == "" || newName == "" { return derr.ErrorCodeEmptyRename } - container, err := daemon.Get(oldName) + container, err = daemon.Get(oldName) if err != nil { return err } @@ -27,19 +37,44 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) error { container.Name = newName - undo := func() { - container.Name = oldName - daemon.reserveName(container.ID, oldName) - daemon.containerGraphDB.Delete(newName) - } + defer func() { + if err != nil { + container.Name = oldName + daemon.reserveName(container.ID, oldName) + daemon.containerGraphDB.Delete(newName) + } + }() - if err := daemon.containerGraphDB.Delete(oldName); err != nil { - undo() + if err = daemon.containerGraphDB.Delete(oldName); err != nil { return derr.ErrorCodeRenameDelete.WithArgs(oldName, err) } - if err := container.toDisk(); err != nil { - undo() + if err = container.toDisk(); err != nil { + return err + } + + if !container.Running { + container.logEvent("rename") + return nil + } + + defer func() { + if err != nil { + container.Name = oldName + if e := container.toDisk(); e != nil { + logrus.Errorf("%s: Failed in writing to Disk on rename failure: %v", container.ID, e) + } + } + }() + + sid = container.NetworkSettings.SandboxID + sb, err = daemon.netController.SandboxByID(sid) + if err != nil { + return err + } + + err = sb.Rename(strings.TrimPrefix(container.Name, "/")) + if err != nil { return err } diff --git a/daemon/stats.go b/daemon/stats.go index ae3a2d918..722c16a19 100644 --- a/daemon/stats.go +++ b/daemon/stats.go @@ -75,7 +75,8 @@ func (daemon *Daemon) ContainerStats(prefixOrName string, config *ContainerStats return nil } - statsJSON := getStatJSON(v) + var statsJSON interface{} + statsJSONPost120 := getStatJSON(v) if config.Version.LessThan("1.21") { var ( rxBytes uint64 @@ -87,7 +88,7 @@ func (daemon *Daemon) ContainerStats(prefixOrName string, config *ContainerStats txErrors uint64 txDropped uint64 ) - for _, v := range statsJSON.Networks { + for _, v := range statsJSONPost120.Networks { rxBytes += v.RxBytes rxPackets += v.RxPackets rxErrors += v.RxErrors @@ -97,8 +98,8 @@ func (daemon *Daemon) ContainerStats(prefixOrName string, config *ContainerStats txErrors += v.TxErrors txDropped += v.TxDropped } - statsJSONPre121 := &v1p20.StatsJSON{ - Stats: statsJSON.Stats, + statsJSON = &v1p20.StatsJSON{ + Stats: statsJSONPost120.Stats, Network: types.NetworkStats{ RxBytes: rxBytes, RxPackets: rxPackets, @@ -110,20 +111,8 @@ func (daemon *Daemon) ContainerStats(prefixOrName string, config *ContainerStats TxDropped: txDropped, }, } - - if !config.Stream && noStreamFirstFrame { - // prime the cpu stats so they aren't 0 in the final output - noStreamFirstFrame = false - continue - } - - if err := enc.Encode(statsJSONPre121); err != nil { - return err - } - - if !config.Stream { - return nil - } + } else { + statsJSON = statsJSONPost120 } if !config.Stream && noStreamFirstFrame { diff --git a/docker/daemon.go b/docker/daemon.go index c551b5c08..fb68e7054 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -210,6 +210,7 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { } serverConfig = setPlatformServerConfig(serverConfig, cli.Config) + defaultHost := opts.DefaultHost if commonFlags.TLSOptions != nil { if !commonFlags.TLSOptions.InsecureSkipVerify { // server requires and verifies client's certificate @@ -220,6 +221,7 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { logrus.Fatal(err) } serverConfig.TLSConfig = tlsConfig + defaultHost = opts.DefaultTLSHost } if len(commonFlags.Hosts) == 0 { @@ -227,7 +229,7 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { } for i := 0; i < len(commonFlags.Hosts); i++ { var err error - if commonFlags.Hosts[i], err = opts.ParseHost(commonFlags.Hosts[i]); err != nil { + if commonFlags.Hosts[i], err = opts.ParseHost(defaultHost, commonFlags.Hosts[i]); err != nil { logrus.Fatalf("error parsing -H %s : %v", commonFlags.Hosts[i], err) } } diff --git a/docs/articles/ambassador_pattern_linking.md b/docs/articles/ambassador_pattern_linking.md index 5a0454030..ab09f0193 100644 --- a/docs/articles/ambassador_pattern_linking.md +++ b/docs/articles/ambassador_pattern_linking.md @@ -81,42 +81,43 @@ On the Docker host (192.168.1.52) that Redis will run on: ^D # add redis ambassador - $ docker run -t -i --link redis:redis --name redis_ambassador -p 6379:6379 busybox sh + $ docker run -t -i --link redis:redis --name redis_ambassador -p 6379:6379 alpine:3.2 sh In the `redis_ambassador` container, you can see the linked Redis containers `env`: - $ env + / # env REDIS_PORT=tcp://172.17.0.136:6379 REDIS_PORT_6379_TCP_ADDR=172.17.0.136 REDIS_NAME=/redis_ambassador/redis HOSTNAME=19d7adf4705e + SHLVL=1 + HOME=/root REDIS_PORT_6379_TCP_PORT=6379 - HOME=/ REDIS_PORT_6379_TCP_PROTO=tcp - container=lxc REDIS_PORT_6379_TCP=tcp://172.17.0.136:6379 TERM=xterm PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PWD=/ + / # exit This environment is used by the ambassador `socat` script to expose Redis to the world (via the `-p 6379:6379` port mapping): $ docker rm redis_ambassador - $ sudo ./contrib/mkimage-unittest.sh - $ docker run -t -i --link redis:redis --name redis_ambassador -p 6379:6379 docker-ut sh - - $ socat TCP4-LISTEN:6379,fork,reuseaddr TCP4:172.17.0.136:6379 + $ CMD="apk update && apk add socat && sh" + $ docker run -t -i --link redis:redis --name redis_ambassador -p 6379:6379 alpine:3.2 sh -c "$CMD" + [...] + / # socat -t 100000000 TCP4-LISTEN:6379,fork,reuseaddr TCP4:172.17.0.136:6379 Now ping the Redis server via the ambassador: Now go to a different server: - $ sudo ./contrib/mkimage-unittest.sh - $ docker run -t -i --expose 6379 --name redis_ambassador docker-ut sh - - $ socat TCP4-LISTEN:6379,fork,reuseaddr TCP4:192.168.1.52:6379 + $ CMD="apk update && apk add socat && sh" + $ docker run -t -i --expose 6379 --name redis_ambassador alpine:3.2 sh -c "$CMD" + [...] + / # socat -t 100000000 TCP4-LISTEN:6379,fork,reuseaddr TCP4:192.168.1.52:6379 And get the `redis-cli` image so we can talk over the ambassador bridge. @@ -127,8 +128,8 @@ And get the `redis-cli` image so we can talk over the ambassador bridge. ## The svendowideit/ambassador Dockerfile -The `svendowideit/ambassador` image is a small `busybox` image with -`socat` built in. When you start the container, it uses a small `sed` +The `svendowideit/ambassador` image is based on the `alpine:3.2` image with +`socat` installed. When you start the container, it uses a small `sed` script to parse out the (possibly multiple) link environment variables to set up the port forwarding. On the remote host, you need to set the variable using the `-e` command line option. @@ -139,19 +140,21 @@ Will forward the local `1234` port to the remote IP and port, in this case `192.168.1.52:6379`. # - # - # first you need to build the docker-ut image - # using ./contrib/mkimage-unittest.sh - # then - # docker build -t SvenDowideit/ambassador . - # docker tag SvenDowideit/ambassador ambassador + # do + # docker build -t svendowideit/ambassador . # then to run it (on the host that has the real backend on it) - # docker run -t -i --link redis:redis --name redis_ambassador -p 6379:6379 ambassador + # docker run -t -i -link redis:redis -name redis_ambassador -p 6379:6379 svendowideit/ambassador # on the remote host, you can set up another ambassador - # docker run -t -i --name redis_ambassador --expose 6379 sh + # docker run -t -i -name redis_ambassador -expose 6379 -e REDIS_PORT_6379_TCP=tcp://192.168.1.52:6379 svendowideit/ambassador sh + # you can read more about this process at https://docs.docker.com/articles/ambassador_pattern_linking/ - FROM docker-ut - MAINTAINER SvenDowideit@home.org.au + # use alpine because its a minimal image with a package manager. + # prettymuch all that is needed is a container that has a functioning env and socat (or equivalent) + FROM alpine:3.2 + MAINTAINER SvenDowideit@home.org.au + RUN apk update && \ + apk add socat && \ + rm -r /var/cache/ - CMD env | grep _TCP= | sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' | sh && top + CMD env | grep _TCP= | sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \& wait/' | sh diff --git a/docs/articles/dockerfile_best-practices.md b/docs/articles/dockerfile_best-practices.md index 72328d0d5..ea15a1463 100644 --- a/docs/articles/dockerfile_best-practices.md +++ b/docs/articles/dockerfile_best-practices.md @@ -59,7 +59,7 @@ in a database image. In almost all cases, you should only run a single process in a single container. Decoupling applications into multiple containers makes it much easier to scale horizontally and reuse containers. If that service depends on -another service, make use of [container linking](../userguide/dockerlinks.md). +another service, make use of [container linking](../userguide/networking/default_network/dockerlinks.md). ### Minimize the number of layers diff --git a/docs/articles/host_integration.md b/docs/articles/host_integration.md index b1c145af2..c1712a070 100644 --- a/docs/articles/host_integration.md +++ b/docs/articles/host_integration.md @@ -4,8 +4,7 @@ title = "Automatically start containers" description = "How to generate scripts for upstart, systemd, etc." keywords = ["systemd, upstart, supervisor, docker, documentation, host integration"] [menu.main] -parent = "smn_containers" -weight = 99 +parent = "smn_administrate" +++ diff --git a/docs/articles/networking.md b/docs/articles/networking.md deleted file mode 100644 index e1f7047cb..000000000 --- a/docs/articles/networking.md +++ /dev/null @@ -1,1137 +0,0 @@ - - -# Network configuration - -> **Note:** -> This document is outdated and needs a major overhaul. - -## Summary - -When Docker starts, it creates a virtual interface named `docker0` on -the host machine. It randomly chooses an address and subnet from the -private range defined by [RFC 1918](http://tools.ietf.org/html/rfc1918) -that are not in use on the host machine, and assigns it to `docker0`. -Docker made the choice `172.17.42.1/16` when I started it a few minutes -ago, for example — a 16-bit netmask providing 65,534 addresses for the -host machine and its containers. The MAC address is generated using the -IP address allocated to the container to avoid ARP collisions, using a -range from `02:42:ac:11:00:00` to `02:42:ac:11:ff:ff`. - -> **Note:** -> This document discusses advanced networking configuration -> and options for Docker. In most cases you won't need this information. -> If you're looking to get started with a simpler explanation of Docker -> networking and an introduction to the concept of container linking see -> the [Docker User Guide](../userguide/dockerlinks.md). - -But `docker0` is no ordinary interface. It is a virtual *Ethernet -bridge* that automatically forwards packets between any other network -interfaces that are attached to it. This lets containers communicate -both with the host machine and with each other. Every time Docker -creates a container, it creates a pair of “peer” interfaces that are -like opposite ends of a pipe — a packet sent on one will be received on -the other. It gives one of the peers to the container to become its -`eth0` interface and keeps the other peer, with a unique name like -`vethAQI2QT`, out in the namespace of the host machine. By binding -every `veth*` interface to the `docker0` bridge, Docker creates a -virtual subnet shared between the host machine and every Docker -container. - -The remaining sections of this document explain all of the ways that you -can use Docker options and — in advanced cases — raw Linux networking -commands to tweak, supplement, or entirely replace Docker's default -networking configuration. - -## Quick guide to the options - -Here is a quick list of the networking-related Docker command-line -options, in case it helps you find the section below that you are -looking for. - -Some networking command-line options can only be supplied to the Docker -server when it starts up, and cannot be changed once it is running: - - * `-b BRIDGE` or `--bridge=BRIDGE` — see - [Building your own bridge](#bridge-building) - - * `--bip=CIDR` — see - [Customizing docker0](#docker0) - - * `--default-gateway=IP_ADDRESS` — see - [How Docker networks a container](#container-networking) - - * `--default-gateway-v6=IP_ADDRESS` — see - [IPv6](#ipv6) - - * `--fixed-cidr` — see - [Customizing docker0](#docker0) - - * `--fixed-cidr-v6` — see - [IPv6](#ipv6) - - * `-H SOCKET...` or `--host=SOCKET...` — - This might sound like it would affect container networking, - but it actually faces in the other direction: - it tells the Docker server over what channels - it should be willing to receive commands - like “run container” and “stop container.” - - * `--icc=true|false` — see - [Communication between containers](#between-containers) - - * `--ip=IP_ADDRESS` — see - [Binding container ports](#binding-ports) - - * `--ipv6=true|false` — see - [IPv6](#ipv6) - - * `--ip-forward=true|false` — see - [Communication between containers and the wider world](#the-world) - - * `--iptables=true|false` — see - [Communication between containers](#between-containers) - - * `--mtu=BYTES` — see - [Customizing docker0](#docker0) - - * `--userland-proxy=true|false` — see - [Binding container ports](#binding-ports) - -There are three networking options that can be supplied either at startup -or when `docker run` is invoked. When provided at startup, set the -default value that `docker run` will later use if the options are not -specified: - - * `--dns=IP_ADDRESS...` — see - [Configuring DNS](#dns) - - * `--dns-search=DOMAIN...` — see - [Configuring DNS](#dns) - - * `--dns-opt=OPTION...` — see - [Configuring DNS](#dns) - -Finally, several networking options can only be provided when calling -`docker run` because they specify something specific to one container: - - * `-h HOSTNAME` or `--hostname=HOSTNAME` — see - [Configuring DNS](#dns) and - [How Docker networks a container](#container-networking) - - * `--link=CONTAINER_NAME_or_ID:ALIAS` — see - [Configuring DNS](#dns) and - [Communication between containers](#between-containers) - - * `--net=bridge|none|container:NAME_or_ID|host` — see - [How Docker networks a container](#container-networking) - - * `--mac-address=MACADDRESS...` — see - [How Docker networks a container](#container-networking) - - * `-p SPEC` or `--publish=SPEC` — see - [Binding container ports](#binding-ports) - - * `-P` or `--publish-all=true|false` — see - [Binding container ports](#binding-ports) - -To supply networking options to the Docker server at startup, use the -`DOCKER_OPTS` variable in the Docker upstart configuration file. For Ubuntu, edit the -variable in `/etc/default/docker` or `/etc/sysconfig/docker` for CentOS. - -The following example illustrates how to configure Docker on Ubuntu to recognize a -newly built bridge. - -Edit the `/etc/default/docker` file: - - $ echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker - -Then restart the Docker server. - - $ sudo service docker start - -For additional information on bridges, see [building your own -bridge](#building-your-own-bridge) later on this page. - -The following sections tackle all of the above topics in an order that we can move roughly from simplest to most complex. - -## Configuring DNS - - - -How can Docker supply each container with a hostname and DNS -configuration, without having to build a custom image with the hostname -written inside? Its trick is to overlay three crucial `/etc` files -inside the container with virtual files where it can write fresh -information. You can see this by running `mount` inside a container: - - $$ mount - ... - /dev/disk/by-uuid/1fec...ebdf on /etc/hostname type ext4 ... - /dev/disk/by-uuid/1fec...ebdf on /etc/hosts type ext4 ... - /dev/disk/by-uuid/1fec...ebdf on /etc/resolv.conf type ext4 ... - ... - -This arrangement allows Docker to do clever things like keep -`resolv.conf` up to date across all containers when the host machine -receives new configuration over DHCP later. The exact details of how -Docker maintains these files inside the container can change from one -Docker version to the next, so you should leave the files themselves -alone and use the following Docker options instead. - -Four different options affect container domain name services. - - * `-h HOSTNAME` or `--hostname=HOSTNAME` — sets the hostname by which - the container knows itself. This is written into `/etc/hostname`, - into `/etc/hosts` as the name of the container's host-facing IP - address, and is the name that `/bin/bash` inside the container will - display inside its prompt. But the hostname is not easy to see from - outside the container. It will not appear in `docker ps` nor in the - `/etc/hosts` file of any other container. - - * `--link=CONTAINER_NAME_or_ID:ALIAS` — using this option as you `run` a - container gives the new container's `/etc/hosts` an extra entry - named `ALIAS` that points to the IP address of the container identified by - `CONTAINER_NAME_or_ID`. This lets processes inside the new container - connect to the hostname `ALIAS` without having to know its IP. The - `--link=` option is discussed in more detail below, in the section - [Communication between containers](#between-containers). Because - Docker may assign a different IP address to the linked containers - on restart, Docker updates the `ALIAS` entry in the `/etc/hosts` file - of the recipient containers. - - * `--dns=IP_ADDRESS...` — sets the IP addresses added as `server` - lines to the container's `/etc/resolv.conf` file. Processes in the - container, when confronted with a hostname not in `/etc/hosts`, will - connect to these IP addresses on port 53 looking for name resolution - services. - - * `--dns-search=DOMAIN...` — sets the domain names that are searched - when a bare unqualified hostname is used inside of the container, by - writing `search` lines into the container's `/etc/resolv.conf`. - When a container process attempts to access `host` and the search - domain `example.com` is set, for instance, the DNS logic will not - only look up `host` but also `host.example.com`. - Use `--dns-search=.` if you don't wish to set the search domain. - - * `--dns-opt=OPTION...` — sets the options used by DNS resolvers - by writing an `options` line into the container's `/etc/resolv.conf`. - See documentation for `resolv.conf` for a list of valid options. - -Regarding DNS settings, in the absence of the `--dns=IP_ADDRESS...`, -`--dns-search=DOMAIN...`, or `--dns-opt=OPTION...` options, Docker makes -each container's `/etc/resolv.conf` look like the `/etc/resolv.conf` of the -host machine (where the `docker` daemon runs). When creating the container's -`/etc/resolv.conf`, the daemon filters out all localhost IP address -`nameserver` entries from the host's original file. - -Filtering is necessary because all localhost addresses on the host are -unreachable from the container's network. After this filtering, if there -are no more `nameserver` entries left in the container's `/etc/resolv.conf` -file, the daemon adds public Google DNS nameservers -(8.8.8.8 and 8.8.4.4) to the container's DNS configuration. If IPv6 is -enabled on the daemon, the public IPv6 Google DNS nameservers will also -be added (2001:4860:4860::8888 and 2001:4860:4860::8844). - -> **Note**: -> If you need access to a host's localhost resolver, you must modify your -> DNS service on the host to listen on a non-localhost address that is -> reachable from within the container. - -You might wonder what happens when the host machine's -`/etc/resolv.conf` file changes. The `docker` daemon has a file change -notifier active which will watch for changes to the host DNS configuration. - -> **Note**: -> The file change notifier relies on the Linux kernel's inotify feature. -> Because this feature is currently incompatible with the overlay filesystem -> driver, a Docker daemon using "overlay" will not be able to take advantage -> of the `/etc/resolv.conf` auto-update feature. - -When the host file changes, all stopped containers which have a matching -`resolv.conf` to the host will be updated immediately to this newest host -configuration. Containers which are running when the host configuration -changes will need to stop and start to pick up the host changes due to lack -of a facility to ensure atomic writes of the `resolv.conf` file while the -container is running. If the container's `resolv.conf` has been edited since -it was started with the default configuration, no replacement will be -attempted as it would overwrite the changes performed by the container. -If the options (`--dns`, `--dns-search`, or `--dns-opt`) have been used to -modify the default host configuration, then the replacement with an updated -host's `/etc/resolv.conf` will not happen as well. - -> **Note**: -> For containers which were created prior to the implementation of -> the `/etc/resolv.conf` update feature in Docker 1.5.0: those -> containers will **not** receive updates when the host `resolv.conf` -> file changes. Only containers created with Docker 1.5.0 and above -> will utilize this auto-update feature. - -## Communication between containers and the wider world - - - -Whether a container can talk to the world is governed by two factors. - -1. Is the host machine willing to forward IP packets? This is governed - by the `ip_forward` system parameter. Packets can only pass between - containers if this parameter is `1`. Usually you will simply leave - the Docker server at its default setting `--ip-forward=true` and - Docker will go set `ip_forward` to `1` for you when the server - starts up. If you set `--ip-forward=false` and your system's kernel - has it enabled, the `--ip-forward=false` option has no effect. - To check the setting on your kernel or to turn it on manually: - - $ sysctl net.ipv4.conf.all.forwarding - net.ipv4.conf.all.forwarding = 0 - $ sysctl net.ipv4.conf.all.forwarding=1 - $ sysctl net.ipv4.conf.all.forwarding - net.ipv4.conf.all.forwarding = 1 - - Many using Docker will want `ip_forward` to be on, to at - least make communication *possible* between containers and - the wider world. - - May also be needed for inter-container communication if you are - in a multiple bridge setup. - -2. Do your `iptables` allow this particular connection? Docker will - never make changes to your system `iptables` rules if you set - `--iptables=false` when the daemon starts. Otherwise the Docker - server will append forwarding rules to the `DOCKER` filter chain. - -Docker will not delete or modify any pre-existing rules from the `DOCKER` -filter chain. This allows the user to create in advance any rules required -to further restrict access to the containers. - -Docker's forward rules permit all external source IPs by default. To allow -only a specific IP or network to access the containers, insert a negated -rule at the top of the `DOCKER` filter chain. For example, to restrict -external access such that *only* source IP 8.8.8.8 can access the -containers, the following rule could be added: - - $ iptables -I DOCKER -i ext_if ! -s 8.8.8.8 -j DROP - -## Communication between containers - - - -Whether two containers can communicate is governed, at the operating -system level, by two factors. - -1. Does the network topology even connect the containers' network - interfaces? By default Docker will attach all containers to a - single `docker0` bridge, providing a path for packets to travel - between them. See the later sections of this document for other - possible topologies. - -2. Do your `iptables` allow this particular connection? Docker will never - make changes to your system `iptables` rules if you set - `--iptables=false` when the daemon starts. Otherwise the Docker server - will add a default rule to the `FORWARD` chain with a blanket `ACCEPT` - policy if you retain the default `--icc=true`, or else will set the - policy to `DROP` if `--icc=false`. - -It is a strategic question whether to leave `--icc=true` or change it to -`--icc=false` so that -`iptables` will protect other containers — and the main host — from -having arbitrary ports probed or accessed by a container that gets -compromised. - -If you choose the most secure setting of `--icc=false`, then how can -containers communicate in those cases where you *want* them to provide -each other services? - -The answer is the `--link=CONTAINER_NAME_or_ID:ALIAS` option, which was -mentioned in the previous section because of its effect upon name -services. If the Docker daemon is running with both `--icc=false` and -`--iptables=true` then, when it sees `docker run` invoked with the -`--link=` option, the Docker server will insert a pair of `iptables` -`ACCEPT` rules so that the new container can connect to the ports -exposed by the other container — the ports that it mentioned in the -`EXPOSE` lines of its `Dockerfile`. Docker has more documentation on -this subject — see the [linking Docker containers](../userguide/dockerlinks.md) -page for further details. - -> **Note**: -> The value `CONTAINER_NAME` in `--link=` must either be an -> auto-assigned Docker name like `stupefied_pare` or else the name you -> assigned with `--name=` when you ran `docker run`. It cannot be a -> hostname, which Docker will not recognize in the context of the -> `--link=` option. - -You can run the `iptables` command on your Docker host to see whether -the `FORWARD` chain has a default policy of `ACCEPT` or `DROP`: - - # When --icc=false, you should see a DROP rule: - - $ sudo iptables -L -n - ... - Chain FORWARD (policy ACCEPT) - target prot opt source destination - DOCKER all -- 0.0.0.0/0 0.0.0.0/0 - DROP all -- 0.0.0.0/0 0.0.0.0/0 - ... - - # When a --link= has been created under --icc=false, - # you should see port-specific ACCEPT rules overriding - # the subsequent DROP policy for all other packets: - - $ sudo iptables -L -n - ... - Chain FORWARD (policy ACCEPT) - target prot opt source destination - DOCKER all -- 0.0.0.0/0 0.0.0.0/0 - DROP all -- 0.0.0.0/0 0.0.0.0/0 - - Chain DOCKER (1 references) - target prot opt source destination - ACCEPT tcp -- 172.17.0.2 172.17.0.3 tcp spt:80 - ACCEPT tcp -- 172.17.0.3 172.17.0.2 tcp dpt:80 - -> **Note**: -> Docker is careful that its host-wide `iptables` rules fully expose -> containers to each other's raw IP addresses, so connections from one -> container to another should always appear to be originating from the -> first container's own IP address. - -## Binding container ports to the host - - - -By default Docker containers can make connections to the outside world, -but the outside world cannot connect to containers. Each outgoing -connection will appear to originate from one of the host machine's own -IP addresses thanks to an `iptables` masquerading rule on the host -machine that the Docker server creates when it starts: - - # You can see that the Docker server creates a - # masquerade rule that let containers connect - # to IP addresses in the outside world: - - $ sudo iptables -t nat -L -n - ... - Chain POSTROUTING (policy ACCEPT) - target prot opt source destination - MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0 - ... - -But if you want containers to accept incoming connections, you will need -to provide special options when invoking `docker run`. These options -are covered in more detail in the [Docker User Guide](../userguide/dockerlinks.md) -page. There are two approaches. - -First, you can supply `-P` or `--publish-all=true|false` to `docker run` which -is a blanket operation that identifies every port with an `EXPOSE` line in the -image's `Dockerfile` or `--expose ` commandline flag and maps it to a -host port somewhere within an *ephemeral port range*. The `docker port` command -then needs to be used to inspect created mapping. The *ephemeral port range* is -configured by `/proc/sys/net/ipv4/ip_local_port_range` kernel parameter, -typically ranging from 32768 to 61000. - -Mapping can be specified explicitly using `-p SPEC` or `--publish=SPEC` option. -It allows you to particularize which port on docker server - which can be any -port at all, not just one within the *ephemeral port range* — you want mapped -to which port in the container. - -Either way, you should be able to peek at what Docker has accomplished -in your network stack by examining your NAT tables. - - # What your NAT rules might look like when Docker - # is finished setting up a -P forward: - - $ iptables -t nat -L -n - ... - Chain DOCKER (2 references) - target prot opt source destination - DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80 - - # What your NAT rules might look like when Docker - # is finished setting up a -p 80:80 forward: - - Chain DOCKER (2 references) - target prot opt source destination - DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80 - -You can see that Docker has exposed these container ports on `0.0.0.0`, -the wildcard IP address that will match any possible incoming port on -the host machine. If you want to be more restrictive and only allow -container services to be contacted through a specific external interface -on the host machine, you have two choices. When you invoke `docker run` -you can use either `-p IP:host_port:container_port` or `-p IP::port` to -specify the external interface for one particular binding. - -Or if you always want Docker port forwards to bind to one specific IP -address, you can edit your system-wide Docker server settings and add the -option `--ip=IP_ADDRESS`. Remember to restart your Docker server after -editing this setting. - -> **Note**: -> With hairpin NAT enabled (`--userland-proxy=false`), containers port exposure -> is achieved purely through iptables rules, and no attempt to bind the exposed -> port is ever made. This means that nothing prevents shadowing a previously -> listening service outside of Docker through exposing the same port for a -> container. In such conflicting situation, Docker created iptables rules will -> take precedence and route to the container. - -The `--userland-proxy` parameter, true by default, provides a userland -implementation for inter-container and outside-to-container communication. When -disabled, Docker uses both an additional `MASQUERADE` iptable rule and the -`net.ipv4.route_localnet` kernel parameter which allow the host machine to -connect to a local container exposed port through the commonly used loopback -address: this alternative is preferred for performance reasons. - -Again, this topic is covered without all of these low-level networking -details in the [Docker User Guide](../userguide/dockerlinks.md) document if you -would like to use that as your port redirection reference instead. - -## IPv6 - - - -As we are [running out of IPv4 addresses](http://en.wikipedia.org/wiki/IPv4_address_exhaustion) -the IETF has standardized an IPv4 successor, [Internet Protocol Version 6](http://en.wikipedia.org/wiki/IPv6) -, in [RFC 2460](https://www.ietf.org/rfc/rfc2460.txt). Both protocols, IPv4 and -IPv6, reside on layer 3 of the [OSI model](http://en.wikipedia.org/wiki/OSI_model). - - -### IPv6 with Docker -By default, the Docker server configures the container network for IPv4 only. -You can enable IPv4/IPv6 dualstack support by running the Docker daemon with the -`--ipv6` flag. Docker will set up the bridge `docker0` with the IPv6 -[link-local address](http://en.wikipedia.org/wiki/Link-local_address) `fe80::1`. - -By default, containers that are created will only get a link-local IPv6 address. -To assign globally routable IPv6 addresses to your containers you have to -specify an IPv6 subnet to pick the addresses from. Set the IPv6 subnet via the -`--fixed-cidr-v6` parameter when starting Docker daemon: - - docker daemon --ipv6 --fixed-cidr-v6="2001:db8:1::/64" - -The subnet for Docker containers should at least have a size of `/80`. This way -an IPv6 address can end with the container's MAC address and you prevent NDP -neighbor cache invalidation issues in the Docker layer. - -With the `--fixed-cidr-v6` parameter set Docker will add a new route to the -routing table. Further IPv6 routing will be enabled (you may prevent this by -starting Docker daemon with `--ip-forward=false`): - - $ ip -6 route add 2001:db8:1::/64 dev docker0 - $ sysctl net.ipv6.conf.default.forwarding=1 - $ sysctl net.ipv6.conf.all.forwarding=1 - -All traffic to the subnet `2001:db8:1::/64` will now be routed -via the `docker0` interface. - -Be aware that IPv6 forwarding may interfere with your existing IPv6 -configuration: If you are using Router Advertisements to get IPv6 settings for -your host's interfaces you should set `accept_ra` to `2`. Otherwise IPv6 -enabled forwarding will result in rejecting Router Advertisements. E.g., if you -want to configure `eth0` via Router Advertisements you should set: - - $ sysctl net.ipv6.conf.eth0.accept_ra=2 - -![](../article-img/ipv6_basic_host_config.svg) - -Every new container will get an IPv6 address from the defined subnet. Further -a default route will be added on `eth0` in the container via the address -specified by the daemon option `--default-gateway-v6` if present, otherwise -via `fe80::1`: - - docker run -it ubuntu bash -c "ip -6 addr show dev eth0; ip -6 route show" - - 15: eth0: mtu 1500 - inet6 2001:db8:1:0:0:242:ac11:3/64 scope global - valid_lft forever preferred_lft forever - inet6 fe80::42:acff:fe11:3/64 scope link - valid_lft forever preferred_lft forever - - 2001:db8:1::/64 dev eth0 proto kernel metric 256 - fe80::/64 dev eth0 proto kernel metric 256 - default via fe80::1 dev eth0 metric 1024 - -In this example the Docker container is assigned a link-local address with the -network suffix `/64` (here: `fe80::42:acff:fe11:3/64`) and a globally routable -IPv6 address (here: `2001:db8:1:0:0:242:ac11:3/64`). The container will create -connections to addresses outside of the `2001:db8:1::/64` network via the -link-local gateway at `fe80::1` on `eth0`. - -Often servers or virtual machines get a `/64` IPv6 subnet assigned (e.g. -`2001:db8:23:42::/64`). In this case you can split it up further and provide -Docker a `/80` subnet while using a separate `/80` subnet for other -applications on the host: - -![](../article-img/ipv6_slash64_subnet_config.svg) - -In this setup the subnet `2001:db8:23:42::/80` with a range from `2001:db8:23:42:0:0:0:0` -to `2001:db8:23:42:0:ffff:ffff:ffff` is attached to `eth0`, with the host listening -at `2001:db8:23:42::1`. The subnet `2001:db8:23:42:1::/80` with an address range from -`2001:db8:23:42:1:0:0:0` to `2001:db8:23:42:1:ffff:ffff:ffff` is attached to -`docker0` and will be used by containers. - -#### Using NDP proxying - -If your Docker host is only part of an IPv6 subnet but has not got an IPv6 -subnet assigned you can use NDP proxying to connect your containers via IPv6 to -the internet. -For example your host has the IPv6 address `2001:db8::c001`, is part of the -subnet `2001:db8::/64` and your IaaS provider allows you to configure the IPv6 -addresses `2001:db8::c000` to `2001:db8::c00f`: - - $ ip -6 addr show - 1: lo: mtu 65536 - inet6 ::1/128 scope host - valid_lft forever preferred_lft forever - 2: eth0: mtu 1500 qlen 1000 - inet6 2001:db8::c001/64 scope global - valid_lft forever preferred_lft forever - inet6 fe80::601:3fff:fea1:9c01/64 scope link - valid_lft forever preferred_lft forever - -Let's split up the configurable address range into two subnets -`2001:db8::c000/125` and `2001:db8::c008/125`. The first one can be used by the -host itself, the latter by Docker: - - docker daemon --ipv6 --fixed-cidr-v6 2001:db8::c008/125 - -You notice the Docker subnet is within the subnet managed by your router that -is connected to `eth0`. This means all devices (containers) with the addresses -from the Docker subnet are expected to be found within the router subnet. -Therefore the router thinks it can talk to these containers directly. - -![](../article-img/ipv6_ndp_proxying.svg) - -As soon as the router wants to send an IPv6 packet to the first container it -will transmit a neighbor solicitation request, asking, who has -`2001:db8::c009`? But it will get no answer because no one on this subnet has -this address. The container with this address is hidden behind the Docker host. -The Docker host has to listen to neighbor solicitation requests for the container -address and send a response that itself is the device that is responsible for -the address. This is done by a Kernel feature called `NDP Proxy`. You can -enable it by executing - - $ sysctl net.ipv6.conf.eth0.proxy_ndp=1 - -Now you can add the container's IPv6 address to the NDP proxy table: - - $ ip -6 neigh add proxy 2001:db8::c009 dev eth0 - -This command tells the Kernel to answer to incoming neighbor solicitation requests -regarding the IPv6 address `2001:db8::c009` on the device `eth0`. As a -consequence of this all traffic to this IPv6 address will go into the Docker -host and it will forward it according to its routing table via the `docker0` -device to the container network: - - $ ip -6 route show - 2001:db8::c008/125 dev docker0 metric 1 - 2001:db8::/64 dev eth0 proto kernel metric 256 - -You have to execute the `ip -6 neigh add proxy ...` command for every IPv6 -address in your Docker subnet. Unfortunately there is no functionality for -adding a whole subnet by executing one command. An alternative approach would be to -use an NDP proxy daemon such as [ndppd](https://github.com/DanielAdolfsson/ndppd). - -### Docker IPv6 cluster - -#### Switched network environment -Using routable IPv6 addresses allows you to realize communication between -containers on different hosts. Let's have a look at a simple Docker IPv6 cluster -example: - -![](../article-img/ipv6_switched_network_example.svg) - -The Docker hosts are in the `2001:db8:0::/64` subnet. Host1 is configured -to provide addresses from the `2001:db8:1::/64` subnet to its containers. It -has three routes configured: - -- Route all traffic to `2001:db8:0::/64` via `eth0` -- Route all traffic to `2001:db8:1::/64` via `docker0` -- Route all traffic to `2001:db8:2::/64` via Host2 with IP `2001:db8::2` - -Host1 also acts as a router on OSI layer 3. When one of the network clients -tries to contact a target that is specified in Host1's routing table Host1 will -forward the traffic accordingly. It acts as a router for all networks it knows: -`2001:db8::/64`, `2001:db8:1::/64` and `2001:db8:2::/64`. - -On Host2 we have nearly the same configuration. Host2's containers will get -IPv6 addresses from `2001:db8:2::/64`. Host2 has three routes configured: - -- Route all traffic to `2001:db8:0::/64` via `eth0` -- Route all traffic to `2001:db8:2::/64` via `docker0` -- Route all traffic to `2001:db8:1::/64` via Host1 with IP `2001:db8:0::1` - -The difference to Host1 is that the network `2001:db8:2::/64` is directly -attached to the host via its `docker0` interface whereas it reaches -`2001:db8:1::/64` via Host1's IPv6 address `2001:db8::1`. - -This way every container is able to contact every other container. The -containers `Container1-*` share the same subnet and contact each other directly. -The traffic between `Container1-*` and `Container2-*` will be routed via Host1 -and Host2 because those containers do not share the same subnet. - -In a switched environment every host has to know all routes to every subnet. You -always have to update the hosts' routing tables once you add or remove a host -to the cluster. - -Every configuration in the diagram that is shown below the dashed line is -handled by Docker: The `docker0` bridge IP address configuration, the route to -the Docker subnet on the host, the container IP addresses and the routes on the -containers. The configuration above the line is up to the user and can be -adapted to the individual environment. - -#### Routed network environment - -In a routed network environment you replace the layer 2 switch with a layer 3 -router. Now the hosts just have to know their default gateway (the router) and -the route to their own containers (managed by Docker). The router holds all -routing information about the Docker subnets. When you add or remove a host to -this environment you just have to update the routing table in the router - not -on every host. - -![](../article-img/ipv6_routed_network_example.svg) - -In this scenario containers of the same host can communicate directly with each -other. The traffic between containers on different hosts will be routed via -their hosts and the router. For example packet from `Container1-1` to -`Container2-1` will be routed through `Host1`, `Router` and `Host2` until it -arrives at `Container2-1`. - -To keep the IPv6 addresses short in this example a `/48` network is assigned to -every host. The hosts use a `/64` subnet of this for its own services and one -for Docker. When adding a third host you would add a route for the subnet -`2001:db8:3::/48` in the router and configure Docker on Host3 with -`--fixed-cidr-v6=2001:db8:3:1::/64`. - -Remember the subnet for Docker containers should at least have a size of `/80`. -This way an IPv6 address can end with the container's MAC address and you -prevent NDP neighbor cache invalidation issues in the Docker layer. So if you -have a `/64` for your whole environment use `/78` subnets for the hosts and -`/80` for the containers. This way you can use 4096 hosts with 16 `/80` subnets -each. - -Every configuration in the diagram that is visualized below the dashed line is -handled by Docker: The `docker0` bridge IP address configuration, the route to -the Docker subnet on the host, the container IP addresses and the routes on the -containers. The configuration above the line is up to the user and can be -adapted to the individual environment. - -## Customizing docker0 - - - -By default, the Docker server creates and configures the host system's -`docker0` interface as an *Ethernet bridge* inside the Linux kernel that -can pass packets back and forth between other physical or virtual -network interfaces so that they behave as a single Ethernet network. - -Docker configures `docker0` with an IP address, netmask and IP -allocation range. The host machine can both receive and send packets to -containers connected to the bridge, and gives it an MTU — the *maximum -transmission unit* or largest packet length that the interface will -allow — of either 1,500 bytes or else a more specific value copied from -the Docker host's interface that supports its default route. These -options are configurable at server startup: - - * `--bip=CIDR` — supply a specific IP address and netmask for the - `docker0` bridge, using standard CIDR notation like - `192.168.1.5/24`. - - * `--fixed-cidr=CIDR` — restrict the IP range from the `docker0` subnet, - using the standard CIDR notation like `172.167.1.0/28`. This range must - be an IPv4 range for fixed IPs (ex: 10.20.0.0/16) and must be a subset - of the bridge IP range (`docker0` or set using `--bridge`). For example - with `--fixed-cidr=192.168.1.0/25`, IPs for your containers will be chosen - from the first half of `192.168.1.0/24` subnet. - - * `--mtu=BYTES` — override the maximum packet length on `docker0`. - - -Once you have one or more containers up and running, you can confirm -that Docker has properly connected them to the `docker0` bridge by -running the `brctl` command on the host machine and looking at the -`interfaces` column of the output. Here is a host with two different -containers connected: - - # Display bridge info - - $ sudo brctl show - bridge name bridge id STP enabled interfaces - docker0 8000.3a1d7362b4ee no veth65f9 - vethdda6 - -If the `brctl` command is not installed on your Docker host, then on -Ubuntu you should be able to run `sudo apt-get install bridge-utils` to -install it. - -Finally, the `docker0` Ethernet bridge settings are used every time you -create a new container. Docker selects a free IP address from the range -available on the bridge each time you `docker run` a new container, and -configures the container's `eth0` interface with that IP address and the -bridge's netmask. The Docker host's own IP address on the bridge is -used as the default gateway by which each container reaches the rest of -the Internet. - - # The network, as seen from a container - - $ docker run -i -t --rm base /bin/bash - - $$ ip addr show eth0 - 24: eth0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 - link/ether 32:6f:e0:35:57:91 brd ff:ff:ff:ff:ff:ff - inet 172.17.0.3/16 scope global eth0 - valid_lft forever preferred_lft forever - inet6 fe80::306f:e0ff:fe35:5791/64 scope link - valid_lft forever preferred_lft forever - - $$ ip route - default via 172.17.42.1 dev eth0 - 172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.3 - - $$ exit - -Remember that the Docker host will not be willing to forward container -packets out on to the Internet unless its `ip_forward` system setting is -`1` — see the section above on [Communication between -containers](#between-containers) for details. - -## Building your own bridge - - - -If you want to take Docker out of the business of creating its own -Ethernet bridge entirely, you can set up your own bridge before starting -Docker and use `-b BRIDGE` or `--bridge=BRIDGE` to tell Docker to use -your bridge instead. If you already have Docker up and running with its -old `docker0` still configured, you will probably want to begin by -stopping the service and removing the interface: - - # Stopping Docker and removing docker0 - - $ sudo service docker stop - $ sudo ip link set dev docker0 down - $ sudo brctl delbr docker0 - $ sudo iptables -t nat -F POSTROUTING - -Then, before starting the Docker service, create your own bridge and -give it whatever configuration you want. Here we will create a simple -enough bridge that we really could just have used the options in the -previous section to customize `docker0`, but it will be enough to -illustrate the technique. - - # Create our own bridge - - $ sudo brctl addbr bridge0 - $ sudo ip addr add 192.168.5.1/24 dev bridge0 - $ sudo ip link set dev bridge0 up - - # Confirming that our bridge is up and running - - $ ip addr show bridge0 - 4: bridge0: mtu 1500 qdisc noop state UP group default - link/ether 66:38:d0:0d:76:18 brd ff:ff:ff:ff:ff:ff - inet 192.168.5.1/24 scope global bridge0 - valid_lft forever preferred_lft forever - - # Tell Docker about it and restart (on Ubuntu) - - $ echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker - $ sudo service docker start - - # Confirming new outgoing NAT masquerade is set up - - $ sudo iptables -t nat -L -n - ... - Chain POSTROUTING (policy ACCEPT) - target prot opt source destination - MASQUERADE all -- 192.168.5.0/24 0.0.0.0/0 - - -The result should be that the Docker server starts successfully and is -now prepared to bind containers to the new bridge. After pausing to -verify the bridge's configuration, try creating a container — you will -see that its IP address is in your new IP address range, which Docker -will have auto-detected. - -Just as we learned in the previous section, you can use the `brctl show` -command to see Docker add and remove interfaces from the bridge as you -start and stop containers, and can run `ip addr` and `ip route` inside a -container to see that it has been given an address in the bridge's IP -address range and has been told to use the Docker host's IP address on -the bridge as its default gateway to the rest of the Internet. - -## How Docker networks a container - - - -While Docker is under active development and continues to tweak and -improve its network configuration logic, the shell commands in this -section are rough equivalents to the steps that Docker takes when -configuring networking for each new container. - -Let's review a few basics. - -To communicate using the Internet Protocol (IP), a machine needs access -to at least one network interface at which packets can be sent and -received, and a routing table that defines the range of IP addresses -reachable through that interface. Network interfaces do not have to be -physical devices. In fact, the `lo` loopback interface available on -every Linux machine (and inside each Docker container) is entirely -virtual — the Linux kernel simply copies loopback packets directly from -the sender's memory into the receiver's memory. - -Docker uses special virtual interfaces to let containers communicate -with the host machine — pairs of virtual interfaces called “peers” that -are linked inside of the host machine's kernel so that packets can -travel between them. They are simple to create, as we will see in a -moment. - -The steps with which Docker configures a container are: - -1. Create a pair of peer virtual interfaces. - -2. Give one of them a unique name like `veth65f9`, keep it inside of - the main Docker host, and bind it to `docker0` or whatever bridge - Docker is supposed to be using. - -3. Toss the other interface over the wall into the new container (which - will already have been provided with an `lo` interface) and rename - it to the much prettier name `eth0` since, inside of the container's - separate and unique network interface namespace, there are no - physical interfaces with which this name could collide. - -4. Set the interface's MAC address according to the `--mac-address` - parameter or generate a random one. - -5. Give the container's `eth0` a new IP address from within the - bridge's range of network addresses. The default route is set to the - IP address passed to the Docker daemon using the `--default-gateway` - option if specified, otherwise to the IP address that the Docker host - owns on the bridge. The MAC address is generated from the IP address - unless otherwise specified. This prevents ARP cache invalidation - problems, when a new container comes up with an IP used in the past by - another container with another MAC. - -With these steps complete, the container now possesses an `eth0` -(virtual) network card and will find itself able to communicate with -other containers and the rest of the Internet. - -You can opt out of the above process for a particular container by -giving the `--net=` option to `docker run`, which takes four possible -values. - - * `--net=bridge` — The default action, that connects the container to - the Docker bridge as described above. - - * `--net=host` — Tells Docker to skip placing the container inside of - a separate network stack. In essence, this choice tells Docker to - **not containerize the container's networking**! While container - processes will still be confined to their own filesystem and process - list and resource limits, a quick `ip addr` command will show you - that, network-wise, they live “outside” in the main Docker host and - have full access to its network interfaces. Note that this does - **not** let the container reconfigure the host network stack — that - would require `--privileged=true` — but it does let container - processes open low-numbered ports like any other root process. - It also allows the container to access local network services - like D-bus. This can lead to processes in the container being - able to do unexpected things like - [restart your computer](https://github.com/docker/docker/issues/6401). - You should use this option with caution. - - * `--net=container:NAME_or_ID` — Tells Docker to put this container's - processes inside of the network stack that has already been created - inside of another container. The new container's processes will be - confined to their own filesystem and process list and resource - limits, but will share the same IP address and port numbers as the - first container, and processes on the two containers will be able to - connect to each other over the loopback interface. - - * `--net=none` — Tells Docker to put the container inside of its own - network stack but not to take any steps to configure its network, - leaving you free to build any of the custom configurations explored - in the last few sections of this document. - -To get an idea of the steps that are necessary if you use `--net=none` -as described in that last bullet point, here are the commands that you -would run to reach roughly the same configuration as if you had let -Docker do all of the configuration: - - # At one shell, start a container and - # leave its shell idle and running - - $ docker run -i -t --rm --net=none base /bin/bash - root@63f36fc01b5f:/# - - # At another shell, learn the container process ID - # and create its namespace entry in /var/run/netns/ - # for the "ip netns" command we will be using below - - $ docker inspect -f '{{.State.Pid}}' 63f36fc01b5f - 2778 - $ pid=2778 - $ sudo mkdir -p /var/run/netns - $ sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid - - # Check the bridge's IP address and netmask - - $ ip addr show docker0 - 21: docker0: ... - inet 172.17.42.1/16 scope global docker0 - ... - - # Create a pair of "peer" interfaces A and B, - # bind the A end to the bridge, and bring it up - - $ sudo ip link add A type veth peer name B - $ sudo brctl addif docker0 A - $ sudo ip link set A up - - # Place B inside the container's network namespace, - # rename to eth0, and activate it with a free IP - - $ sudo ip link set B netns $pid - $ sudo ip netns exec $pid ip link set dev B name eth0 - $ sudo ip netns exec $pid ip link set eth0 address 12:34:56:78:9a:bc - $ sudo ip netns exec $pid ip link set eth0 up - $ sudo ip netns exec $pid ip addr add 172.17.42.99/16 dev eth0 - $ sudo ip netns exec $pid ip route add default via 172.17.42.1 - -At this point your container should be able to perform networking -operations as usual. - -When you finally exit the shell and Docker cleans up the container, the -network namespace is destroyed along with our virtual `eth0` — whose -destruction in turn destroys interface `A` out in the Docker host and -automatically un-registers it from the `docker0` bridge. So everything -gets cleaned up without our having to run any extra commands! Well, -almost everything: - - # Clean up dangling symlinks in /var/run/netns - - find -L /var/run/netns -type l -delete - -Also note that while the script above used modern `ip` command instead -of old deprecated wrappers like `ipconfig` and `route`, these older -commands would also have worked inside of our container. The `ip addr` -command can be typed as `ip a` if you are in a hurry. - -Finally, note the importance of the `ip netns exec` command, which let -us reach inside and configure a network namespace as root. The same -commands would not have worked if run inside of the container, because -part of safe containerization is that Docker strips container processes -of the right to configure their own networks. Using `ip netns exec` is -what let us finish up the configuration without having to take the -dangerous step of running the container itself with `--privileged=true`. - -## Tools and examples - -Before diving into the following sections on custom network topologies, -you might be interested in glancing at a few external tools or examples -of the same kinds of configuration. Here are two: - - * Jérôme Petazzoni has created a `pipework` shell script to help you - connect together containers in arbitrarily complex scenarios: - - - * Brandon Rhodes has created a whole network topology of Docker - containers for the next edition of Foundations of Python Network - Programming that includes routing, NAT'd firewalls, and servers that - offer HTTP, SMTP, POP, IMAP, Telnet, SSH, and FTP: - - -Both tools use networking commands very much like the ones you saw in -the previous section, and will see in the following sections. - -## Building a point-to-point connection - - - -By default, Docker attaches all containers to the virtual subnet -implemented by `docker0`. You can create containers that are each -connected to some different virtual subnet by creating your own bridge -as shown in [Building your own bridge](#bridge-building), starting each -container with `docker run --net=none`, and then attaching the -containers to your bridge with the shell commands shown in [How Docker -networks a container](#container-networking). - -But sometimes you want two particular containers to be able to -communicate directly without the added complexity of both being bound to -a host-wide Ethernet bridge. - -The solution is simple: when you create your pair of peer interfaces, -simply throw *both* of them into containers, and configure them as -classic point-to-point links. The two containers will then be able to -communicate directly (provided you manage to tell each container the -other's IP address, of course). You might adjust the instructions of -the previous section to go something like this: - - # Start up two containers in two terminal windows - - $ docker run -i -t --rm --net=none base /bin/bash - root@1f1f4c1f931a:/# - - $ docker run -i -t --rm --net=none base /bin/bash - root@12e343489d2f:/# - - # Learn the container process IDs - # and create their namespace entries - - $ docker inspect -f '{{.State.Pid}}' 1f1f4c1f931a - 2989 - $ docker inspect -f '{{.State.Pid}}' 12e343489d2f - 3004 - $ sudo mkdir -p /var/run/netns - $ sudo ln -s /proc/2989/ns/net /var/run/netns/2989 - $ sudo ln -s /proc/3004/ns/net /var/run/netns/3004 - - # Create the "peer" interfaces and hand them out - - $ sudo ip link add A type veth peer name B - - $ sudo ip link set A netns 2989 - $ sudo ip netns exec 2989 ip addr add 10.1.1.1/32 dev A - $ sudo ip netns exec 2989 ip link set A up - $ sudo ip netns exec 2989 ip route add 10.1.1.2/32 dev A - - $ sudo ip link set B netns 3004 - $ sudo ip netns exec 3004 ip addr add 10.1.1.2/32 dev B - $ sudo ip netns exec 3004 ip link set B up - $ sudo ip netns exec 3004 ip route add 10.1.1.1/32 dev B - -The two containers should now be able to ping each other and make -connections successfully. Point-to-point links like this do not depend -on a subnet nor a netmask, but on the bare assertion made by `ip route` -that some other single IP address is connected to a particular network -interface. - -Note that point-to-point links can be safely combined with other kinds -of network connectivity — there is no need to start the containers with -`--net=none` if you want point-to-point links to be an addition to the -container's normal networking instead of a replacement. - -A final permutation of this pattern is to create the point-to-point link -between the Docker host and one container, which would allow the host to -communicate with that one container on some single IP address and thus -communicate “out-of-band” of the bridge that connects the other, more -usual containers. But unless you have very specific networking needs -that drive you to such a solution, it is probably far preferable to use -`--icc=false` to lock down inter-container communication, as we explored -earlier. - -## Editing networking config files - -Starting with Docker v.1.2.0, you can now edit `/etc/hosts`, `/etc/hostname` -and `/etc/resolve.conf` in a running container. This is useful if you need -to install bind or other services that might override one of those files. - -Note, however, that changes to these files will not be saved by -`docker commit`, nor will they be saved during `docker run`. -That means they won't be saved in the image, nor will they persist when a -container is restarted; they will only "stick" in a running container. diff --git a/docs/articles/security.md b/docs/articles/security.md index 536770a78..2eddbd255 100644 --- a/docs/articles/security.md +++ b/docs/articles/security.md @@ -39,7 +39,7 @@ of another container. Of course, if the host system is setup accordingly, containers can interact with each other through their respective network interfaces — just like they can interact with external hosts. When you specify public ports for your containers or use -[*links*](../userguide/dockerlinks.md) +[*links*](../userguide/networking/default_network/dockerlinks.md) then IP traffic is allowed between containers. They can ping each other, send/receive UDP packets, and establish TCP connections, but that can be restricted if necessary. From a network architecture point of view, all @@ -129,7 +129,7 @@ privilege separation. Eventually, it is expected that the Docker daemon will run restricted privileges, delegating operations well-audited sub-processes, -each with its own (very limited) scope of Linux capabilities, +each with its own (very limited) scope of Linux capabilities, virtual network setup, filesystem management, etc. That is, most likely, pieces of the Docker engine itself will run inside of containers. diff --git a/docs/examples/mongodb.md b/docs/examples/mongodb.md index c1ba682c7..222178b99 100644 --- a/docs/examples/mongodb.md +++ b/docs/examples/mongodb.md @@ -172,6 +172,6 @@ the exposed port to two different ports on the host $ mongo --port 28001 $ mongo --port 28002 - - [Linking containers](../userguide/dockerlinks.md) + - [Linking containers](../userguide/networking/default_network/dockerlinks.md) - [Cross-host linking containers](../articles/ambassador_pattern_linking.md) - [Creating an Automated Build](https://docs.docker.com/docker-hub/builds/) diff --git a/docs/examples/postgresql_service.md b/docs/examples/postgresql_service.md index d5092e30a..9c0938d10 100644 --- a/docs/examples/postgresql_service.md +++ b/docs/examples/postgresql_service.md @@ -10,7 +10,7 @@ parent = "smn_applied" # Dockerizing PostgreSQL -> **Note**: +> **Note**: > - **If you don't like sudo** then see [*Giving non-root > access*](../installation/binaries.md#giving-non-root-access) @@ -85,7 +85,7 @@ And run the PostgreSQL server container (in the foreground): $ docker run --rm -P --name pg_test eg_postgresql There are 2 ways to connect to the PostgreSQL server. We can use [*Link -Containers*](../userguide/dockerlinks.md), or we can access it from our host +Containers*](../userguide/networking/default_network/dockerlinks.md), or we can access it from our host (or the network). > **Note**: diff --git a/docs/extend/plugins.md b/docs/extend/plugins.md index e04de760d..fa5cc10e1 100644 --- a/docs/extend/plugins.md +++ b/docs/extend/plugins.md @@ -18,9 +18,8 @@ plugins. Plugins extend Docker's functionality. They come in specific types. For example, a [volume plugin](plugins_volume.md) might enable Docker -volumes to persist across multiple Docker hosts and a -[network plugin](plugins_network.md) might provide network plumbing -using a favorite networking technology, such as vxlan overlay, ipvlan, EVPN, etc. +volumes to persist across multiple Docker hosts and a +[network plugin](plugins_network.md) might provide network plumbing. Currently Docker supports volume and network driver plugins. In the future it will support additional plugin types. diff --git a/docs/extend/plugins_network.md b/docs/extend/plugins_network.md index 335ee9d04..b1c05558d 100644 --- a/docs/extend/plugins_network.md +++ b/docs/extend/plugins_network.md @@ -1,7 +1,7 @@ - -# Advanced contributing - -In this section, you learn about the more advanced contributions you can make. -They are advanced because they have a more involved workflow or require greater -programming experience. Don't be scared off though, if you like to stretch and -challenge yourself, this is the place for you. - -This section gives generalized instructions for advanced contributions. You'll -read about the workflow but there are not specific descriptions of commands. -Your goal should be to understand the processes described. - -At this point, you should have read and worked through the earlier parts of -the project contributor guide. You should also have - made at least one project contribution. - -## Refactor or cleanup proposal - -A refactor or cleanup proposal changes Docker's internal structure without -altering the external behavior. To make this type of proposal: - -1. Fork `docker/docker`. - -2. Make your changes in a feature branch. - -3. Sync and rebase with `master` as you work. - -3. Run the full test suite. - -4. Submit your code through a pull request (PR). - - The PR's title should have the format: - - **Cleanup:** _short title_ - - If your changes required logic changes, note that in your request. - -5. Work through Docker's review process until merge. - - -## Design proposal - -A design proposal solves a problem or adds a feature to the Docker software. -The process for submitting design proposals requires two pull requests, one -for the design and one for the implementation. - -![Simple process](images/proposal.png) - -The important thing to notice is that both the design pull request and the -implementation pull request go through a review. In other words, there is -considerable time commitment in a design proposal; so, you might want to pair -with someone on design work. - -The following provides greater detail on the process: - -1. Come up with an idea. - - Ideas usually come from limitations users feel working with a product. So, - take some time to really use Docker. Try it on different platforms; explore - how it works with different web applications. Go to some community events - and find out what other users want. - -2. Review existing issues and proposals to make sure no other user is proposing a similar idea. - - The design proposals are all online in our GitHub pull requests. - -3. Talk to the community about your idea. - - We have lots of community forums - where you can get feedback on your idea. Float your idea in a forum or two - to get some commentary going on it. - -4. Fork `docker/docker` and clone the repo to your local host. - -5. Create a new Markdown file in the area you wish to change. - - For example, if you want to redesign our daemon create a new file under the - `daemon/` folder. - -6. Name the file descriptively, for example `redesign-daemon-proposal.md`. - -7. Write a proposal for your change into the file. - - This is a Markdown file that describes your idea. Your proposal - should include information like: - - * Why is this change needed or what are the use cases? - * What are the requirements this change should meet? - * What are some ways to design/implement this feature? - * Which design/implementation do you think is best and why? - * What are the risks or limitations of your proposal? - - This is your chance to convince people your idea is sound. - -8. Submit your proposal in a pull request to `docker/docker`. - - The title should have the format: - - **Proposal:** _short title_ - - The body of the pull request should include a brief summary of your change - and then say something like "_See the file for a complete description_". - -9. Refine your proposal through review. - - The maintainers and the community review your proposal. You'll need to - answer questions and sometimes explain or defend your approach. This is - chance for everyone to both teach and learn. - -10. Pull request accepted. - - Your request may also be rejected. Not every idea is a good fit for Docker. - Let's assume though your proposal succeeded. - -11. Implement your idea. - - Implementation uses all the standard practices of any contribution. - - * fork `docker/docker` - * create a feature branch - * sync frequently back to master - * test as you go and full test before a PR - - If you run into issues, the community is there to help. - -12. When you have a complete implementation, submit a pull request back to `docker/docker`. - -13. Review and iterate on your code. - - If you are making a large code change, you can expect greater scrutiny - during this phase. - -14. Acceptance and merge! - -## About the advanced process - -Docker is a large project. Our core team gets a great many design proposals. -Design proposal discussions can span days, weeks, and longer. The number of comments can reach the 100s. -In that situation, following the discussion flow and the decisions reached is crucial. - -Making a pull request with a design proposal simplifies this process: -* you can leave comments on specific design proposal line -* replies around line are easy to track -* as a proposal changes and is updated, pages reset as line items resolve -* GitHub maintains the entire history - -While proposals in pull requests do not end up merged into a master repository, they provide a convenient tool for managing the design process. diff --git a/docs/project/coding-style.md b/docs/project/coding-style.md deleted file mode 100644 index 082ac755e..000000000 --- a/docs/project/coding-style.md +++ /dev/null @@ -1,103 +0,0 @@ - - -# Coding style checklist - -This checklist summarizes the material you experienced working through [make a -code contribution](make-a-contribution.md) and [advanced -contributing](advanced-contributing.md). The checklist applies to both -program code and documentation code. - -## Change and commit code - -* Fork the `docker/docker` repository. - -* Make changes on your fork in a feature branch. Name your branch `XXXX-something` - where `XXXX` is the issue number you are working on. - -* Run `gofmt -s -w file.go` on each changed file before - committing your changes. Most editors have plug-ins that do this automatically. - -* Run `golint` on each changed file before - committing your changes. - -* Update the documentation when creating or modifying features. - -* Commits that fix or close an issue should reference them in the commit message - `Closes #XXXX` or `Fixes #XXXX`. Mentions help by automatically closing the - issue on a merge. - -* After every commit, run the test suite and ensure it is passing. - -* Sync and rebase frequently as you code to keep up with `docker` master. - -* Set your `git` signature and make sure you sign each commit. - -* Do not add yourself to the `AUTHORS` file. This file is autogenerated from the - Git history. - -## Tests and testing - -* Submit unit tests for your changes. - -* Make use of the builtin Go test framework built. - -* Use existing Docker test files (`name_test.go`) for inspiration. - -* Run the full test suite on your - branch before submitting a pull request. - -* Run `make docs` to build the documentation and then check it locally. - -* Use an online grammar - checker or similar to test you documentation changes for clarity, - concision, and correctness. - -## Pull requests - -* Sync and cleanly rebase on top of Docker's `master` without multiple branches - mixed into the PR. - -* Before the pull request, squash your commits into logical units of work using - `git rebase -i` and `git push -f`. - -* Include documentation changes in the same commit so that a revert would - remove all traces of the feature or fix. - -* Reference each issue in your pull request description (`#XXXX`) - -## Respond to pull requests reviews - -* Docker maintainers use LGTM (**l**ooks-**g**ood-**t**o-**m**e) in PR comments - to indicate acceptance. - -* Code review comments may be added to your pull request. Discuss, then make - the suggested modifications and push additional commits to your feature - branch. - -* Incorporate changes on your feature branch and push to your fork. This - automatically updates your open pull request. - -* Post a comment after pushing to alert reviewers to PR changes; pushing a - change does not send notifications. - -* A change requires LGTMs from an absolute majority maintainers of an - affected component. For example, if you change `docs/` and `registry/` code, - an absolute majority of the `docs/` and the `registry/` maintainers must - approve your PR. - -## Merges after pull requests - -* After a merge, [a master build](https://master.dockerproject.org/) is - available almost immediately. - -* If you made a documentation change, you can see it at - [docs.master.dockerproject.org](http://docs.master.dockerproject.org/). diff --git a/docs/project/create-pr.md b/docs/project/create-pr.md deleted file mode 100644 index fc5f626e9..000000000 --- a/docs/project/create-pr.md +++ /dev/null @@ -1,138 +0,0 @@ - - -# Create a pull request (PR) - -A pull request (PR) sends your changes to the Docker maintainers for review. You -create a pull request on GitHub. A pull request "pulls" changes from your forked -repository into the `docker/docker` repository. - -You can see the -list of active pull requests to Docker on GitHub. - -## Check your work - -Before you create a pull request, check your work. - -1. In a terminal window, go to the root of your `docker-fork` repository. - - $ cd ~/repos/docker-fork - -2. Checkout your feature branch. - - $ git checkout 11038-fix-rhel-link - Switched to branch '11038-fix-rhel-link' - -3. Run the full test suite on your branch. - - $ make test - - All the tests should pass. If they don't, find out why and correct the - situation. - -4. Optionally, if modified the documentation, build the documentation: - - $ make docs - -5. Commit and push any changes that result from your checks. - -## Rebase your branch - -Always rebase and squash your commits before making a pull request. - -1. Checkout your feature branch in your local `docker-fork` repository. - - This is the branch associated with your request. - -2. Fetch any last minute changes from `docker/docker`. - - $ git fetch upstream master - From github.com:docker/docker - * branch master -> FETCH_HEAD - -3. Start an interactive rebase. - - $ git rebase -i upstream/master - -4. Rebase opens an editor with a list of commits. - - pick 1a79f55 Tweak some of the other text for grammar - pick 53e4983 Fix a link - pick 3ce07bb Add a new line about RHEL - -5. Replace the `pick` keyword with `squash` on all but the first commit. - - pick 1a79f55 Tweak some of the other text for grammar - squash 53e4983 Fix a link - squash 3ce07bb Add a new line about RHEL - - After you save the changes and quit from the editor, git starts - the rebase, reporting the progress along the way. Sometimes - your changes can conflict with the work of others. If git - encounters a conflict, it stops the rebase, and prints guidance - for how to correct the conflict. - -6. Edit and save your commit message. - - $ git commit -s - - Make sure your message includes your signature. - -7. Force push any changes to your fork on GitHub. - - $ git push -f origin 11038-fix-rhel-link - -## Create a PR on GitHub - -You create and manage PRs on GitHub: - -1. Open your browser to your fork on GitHub. - - You should see the latest activity from your branch. - - ![Latest commits](images/latest_commits.png) - - -2. Click "Compare & pull request." - - The system displays the pull request dialog. - - ![PR dialog](images/to_from_pr.png) - - The pull request compares your changes to the `master` branch on the - `docker/docker` repository. - -3. Edit the dialog's description and add a reference to the issue you are fixing. - - GitHub helps you out by searching for the issue as you type. - - ![Fixes issue](images/fixes_num.png) - -4. Scroll down and verify the PR contains the commits and changes you expect. - - For example, is the file count correct? Are the changes in the files what - you expect? - - ![Commits](images/commits_expected.png) - -5. Press "Create pull request". - - The system creates the request and opens it for you in the `docker/docker` - repository. - - ![Pull request made](images/pull_request_made.png) - - -## Where to go next - -Congratulations, you've created your first pull request to Docker. The next -step is for you learn how to [participate in your PR's -review](review-pr.md). diff --git a/docs/project/doc-style.md b/docs/project/doc-style.md deleted file mode 100644 index e6efe9c7f..000000000 --- a/docs/project/doc-style.md +++ /dev/null @@ -1,283 +0,0 @@ - - -# Docker documentation: style & grammar conventions - -## Style standards - -Over time, different publishing communities have written standards for the style -and grammar they prefer in their publications. These standards are called -[style guides](http://en.wikipedia.org/wiki/Style_guide). Generally, Docker’s -documentation uses the standards described in the -[Associated Press's (AP) style guide](http://en.wikipedia.org/wiki/AP_Stylebook). -If a question about syntactical, grammatical, or lexical practice comes up, -refer to the AP guide first. If you don’t have a copy of (or online subscription -to) the AP guide, you can almost always find an answer to a specific question by -searching the web. If you can’t find an answer, please ask a -[maintainer](https://github.com/docker/docker/blob/master/MAINTAINERS) and -we will find the answer. - -That said, please don't get too hung up on using correct style. We'd rather have -you submit good information that doesn't conform to the guide than no -information at all. Docker's tech writers are always happy to help you with the -prose, and we promise not to judge or use a red pen! - -> **Note:** -> The documentation is written with paragraphs wrapped at 80 column lines to -> make it easier for terminal use. You can probably set up your favorite text -> editor to do this automatically for you. - -### Prose style - -In general, try to write simple, declarative prose. We prefer short, -single-clause sentences and brief three-to-five sentence paragraphs. Try to -choose vocabulary that is straightforward and precise. Avoid creating new terms, -using obscure terms or, in particular, using a lot of jargon. For example, use -"use" instead of leveraging "leverage". - -That said, don’t feel like you have to write for localization or for -English-as-a-second-language (ESL) speakers specifically. Assume you are writing -for an ordinary speaker of English with a basic university education. If your -prose is simple, clear, and straightforward it will translate readily. - -One way to think about this is to assume Docker’s users are generally university -educated and read at at least a "16th" grade level (meaning they have a -university degree). You can use a [readability -tester](https://readability-score.com/) to help guide your judgement. For -example, the readability score for the phrase "Containers should be ephemeral" -is around the 13th grade level (first year at university), and so is acceptable. - -In all cases, we prefer clear, concise communication over stilted, formal -language. Don't feel like you have to write documentation that "sounds like -technical writing." - -### Metaphor and figurative language - -One exception to the "don’t write directly for ESL" rule is to avoid the use of -metaphor or other -[figurative language](http://en.wikipedia.org/wiki/Literal_and_figurative_language) to -describe things. There are too many cultural and social issues that can prevent -a reader from correctly interpreting a metaphor. - -## Specific conventions - -Below are some specific recommendations (and a few deviations) from AP style -that we use in our docs. - -### Contractions - -As long as your prose does not become too slangy or informal, it's perfectly -acceptable to use contractions in our documentation. Make sure to use -apostrophes correctly. - -### Use of dashes in a sentence. - -Dashes refers to the en dash (–) and the em dash (—). Dashes can be used to -separate parenthetical material. - -Usage Example: This is an example of a Docker client – which uses the Big Widget -to run – and does x, y, and z. - -Use dashes cautiously and consider whether commas or parentheses would work just -as well. We always emphasize short, succinct sentences. - -More info from the always handy [Grammar Girl site](http://www.quickanddirtytips.com/education/grammar/dashes-parentheses-and-commas). - -### Pronouns - -It's okay to use first and second person pronouns, especially if it lets you avoid a passive construction. Specifically, always use "we" to -refer to Docker and "you" to refer to the user. For example, "We built the -`exec` command so you can resize a TTY session." That said, in general, try to write simple, imperative sentences that avoid the use of pronouns altogether. Say "Now, enter your SSH key" rather than "You can now enter your SSH key." - -As much as possible, avoid using gendered pronouns ("he" and "she", etc.). -Either recast the sentence so the pronoun is not needed or, less preferably, -use "they" instead. If you absolutely can't get around using a gendered pronoun, -pick one and stick to it. Which one you choose is up to you. One common -convention is to use the pronoun of the author's gender, but if you prefer to -default to "he" or "she", that's fine too. - -### Capitalization - -#### In general - -Only proper nouns should be capitalized in body text. In general, strive to be -as strict as possible in applying this rule. Avoid using capitals for emphasis -or to denote "specialness". - -The word "Docker" should always be capitalized when referring to either the -company or the technology. The only exception is when the term appears in a code -sample. - -#### Starting sentences - -Because code samples should always be written exactly as they would appear -on-screen, you should avoid starting sentences with a code sample. - -#### In headings - -Headings take sentence capitalization, meaning that only the first letter is -capitalized (and words that would normally be capitalized in a sentence, e.g., -"Docker"). Do not use Title Case (i.e., capitalizing every word) for headings. Generally, we adhere to [AP style -for titles](http://www.quickanddirtytips.com/education/grammar/capitalizing-titles). - -### Periods - -We prefer one space after a period at the end of a sentence, not two. - -See [lists](#lists) below for how to punctuate list items. - -### Abbreviations and acronyms - -* Exempli gratia (e.g.) and id est ( i.e.): these should always have periods and -are always followed by a comma. - -* Acronyms are pluralized by simply adding "s", e.g., PCs, OSs. - -* On first use on a given page, the complete term should be used, with the -abbreviation or acronym in parentheses. E.g., Red Hat Enterprise Linux (RHEL). -The exception is common, non-technical acronyms like AKA or ASAP. Note that -acronyms other than i.e. and e.g. are capitalized. - -* Other than "e.g." and "i.e." (as discussed above), acronyms do not take -periods, PC not P.C. - - -### Lists - -When writing lists, keep the following in mind: - -Use bullets when the items being listed are independent of each other and the -order of presentation is not important. - -Use numbers for steps that have to happen in order or if you have mentioned the -list in introductory text. For example, if you wrote "There are three config -settings available for SSL, as follows:", you would number each config setting -in the subsequent list. - -In all lists, if an item is a complete sentence, it should end with a -period. Otherwise, we prefer no terminal punctuation for list items. -Each item in a list should start with a capital. - -### Numbers - -Write out numbers in body text and titles from one to ten. From 11 on, use numerals. - -### Notes - -Use notes sparingly and only to bring things to the reader's attention that are -critical or otherwise deserving of being called out from the body text. Please -format all notes as follows: - - > **Note:** - > One line of note text - > another line of note text - -### Avoid excess use of "i.e." - -Minimize your use of "i.e.". It can add an unnecessary interpretive burden on -the reader. Avoid writing "This is a thing, i.e., it is like this". Just -say what it is: "This thing is …" - -### Preferred usages - -#### Login vs. log in. - -A "login" is a noun (one word), as in "Enter your login". "Log in" is a compound -verb (two words), as in "Log in to the terminal". - -### Oxford comma - -One way in which we differ from AP style is that Docker’s docs use the [Oxford -comma](http://en.wikipedia.org/wiki/Serial_comma) in all cases. That’s our -position on this controversial topic, we won't change our mind, and that’s that! - -### Code and UI text styling - -We require `code font` styling (monospace, sans-serif) for all text that refers -to a command or other input or output from the CLI. This includes file paths -(e.g., `/etc/hosts/docker.conf`). If you enclose text in backticks (`) markdown -will style the text as code. - -Text from a CLI should be quoted verbatim, even if it contains errors or its -style contradicts this guide. You can add "(sic)" after the quote to indicate -the errors are in the quote and are not errors in our docs. - -Text taken from a GUI (e.g., menu text or button text) should appear in "double -quotes". The text should take the exact same capitalisation, etc. as appears in -the GUI. E.g., Click "Continue" to save the settings. - -Text that refers to a keyboard command or hotkey is capitalized (e.g., Ctrl-D). - -When writing CLI examples, give the user hints by making the examples resemble -exactly what they see in their shell: - -* Indent shell examples by 4 spaces so they get rendered as code blocks. -* Start typed commands with `$ ` (dollar space), so that they are easily - differentiated from program output. -* Program output has no prefix. -* Comments begin with # (hash space). -* In-container shell commands, begin with `$$ ` (dollar dollar space). - -Please test all code samples to ensure that they are correct and functional so -that users can successfully cut-and-paste samples directly into the CLI. - -## Pull requests - -The pull request (PR) process is in place so that we can ensure changes made to -the docs are the best changes possible. A good PR will do some or all of the -following: - -* Explain why the change is needed -* Point out potential issues or questions -* Ask for help from experts in the company or the community -* Encourage feedback from core developers and others involved in creating the - software being documented. - -Writing a PR that is singular in focus and has clear objectives will encourage -all of the above. Done correctly, the process allows reviewers (maintainers and -community members) to validate the claims of the documentation and identify -potential problems in communication or presentation. - -### Commit messages - -In order to write clear, useful commit messages, please follow these -[recommendations](http://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message). - -## Links - -For accessibility and usability reasons, avoid using phrases such as "click -here" for link text. Recast your sentence so that the link text describes the -content of the link, as we did in the -["Commit messages" section](#commit-messages) above. - -You can use relative links (../linkeditem) to link to other pages in Docker's -documentation. - -## Graphics - -When you need to add a graphic, try to make the file-size as small as possible. -If you need help reducing file-size of a high-resolution image, feel free to -contact us for help. -Usually, graphics should go in the same directory as the .md file that -references them, or in a subdirectory for images if one already exists. - -The preferred file format for graphics is PNG, but GIF and JPG are also -acceptable. - -If you are referring to a specific part of the UI in an image, use -call-outs (circles and arrows or lines) to highlight what you’re referring to. -Line width for call-outs should not exceed five pixels. The preferred color for -call-outs is red. - -Be sure to include descriptive alt-text for the graphic. This greatly helps -users with accessibility issues. - -Lastly, be sure you have permission to use any included graphics. \ No newline at end of file diff --git a/docs/project/find-an-issue.md b/docs/project/find-an-issue.md deleted file mode 100644 index 140f0f88d..000000000 --- a/docs/project/find-an-issue.md +++ /dev/null @@ -1,237 +0,0 @@ - - - - - -# Find and claim an issue - -On this page, you choose the issue you want to work on. As a contributor, you can work -on whatever you want. If you are new to contributing, you should start by -working with our known issues. - -## Understand the issue types - -An existing issue is something reported by a Docker user. As issues come in, -our maintainers triage them. Triage is its own topic. For now, it is important -for you to know that triage includes ranking issues according to difficulty. - -Triaged issues have one of these labels: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
LabelExperience level guideline
exp/beginnerYou have made less than ten contributions in your life time to any open source project.
exp/noviceYou have made more than ten contributions to an open source project or at least 5 contributions to Docker.
exp/proficientYou have made more than five contributions to Docker which amount to at least 200 code lines or 1000 documentation lines.
exp/expertYou have made less than 20 commits to Docker which amount to 500-1000 code lines or 1000-3000 documentation lines.
exp/masterYou have made more than 20 commits to Docker and greater than 1000 code lines or 3000 documentation lines.
- -These labels are guidelines. You might have written a whole plugin for Docker in a personal -project and never contributed to Docker. With that kind of experience, you could take on an exp/expert or exp/master level issue. - -## Claim a beginner or novice issue - -To claim an issue: - -1. Go to the `docker/docker` repository. - -2. Click the "Issues" link. - - A list of the open issues appears. - - ![Open issues](images/issue_list.png) - -3. From the "Labels" drop-down, select exp/beginner. - - The system filters to show only open exp/beginner issues. - -4. Open an issue that interests you. - - The comments on the issues describe the problem and can provide information for a potential - solution. - -5. When you find an open issue that both interests you and is unclaimed, add a -`#dibs` comment. Make sure that no other user has chosen to work on the issue. - - The project does not permit external contributors to assign issues to themselves. Read - the comments to find if a user claimed the issue by leaving a - `#dibs` comment on the issue. - -7. Your issue # will be different depending on what you claimed. After a moment, Gordon the Docker -bot, changes the issue status to claimed. The following example shows issue #11038. - - ![Easy issue](images/easy_issue.png) - -8. Make a note of the issue number; you will need it for later. - -## Sync your fork and create a new branch - -If you have followed along in this guide, you forked the `docker/docker` -repository. Maybe that was an hour ago or a few days ago. In any case, before -you start working on your issue, sync your repository with the upstream -`docker/docker` master. Syncing ensures your repository has the latest -changes. - -To sync your repository: - -1. Open a terminal on your local host. - -2. Change directory to the `docker-fork` root. - - $ cd ~/repos/docker-fork - -3. Checkout the master branch. - - $ git checkout master - Switched to branch 'master' - Your branch is up-to-date with 'origin/master'. - - Recall that `origin/master` is a branch on your remote GitHub repository. - -4. Make sure you have the upstream remote `docker/docker` by listing them. - - $ git remote -v - origin https://github.com/moxiegirl/docker.git (fetch) - origin https://github.com/moxiegirl/docker.git (push) - upstream https://github.com/docker/docker.git (fetch) - upstream https://github.com/docker/docker.git (push) - - If the `upstream` is missing, add it. - - $ git remote add upstream https://github.com/docker/docker.git - -5. Fetch all the changes from the `upstream master` branch. - - $ git fetch upstream master - remote: Counting objects: 141, done. - remote: Compressing objects: 100% (29/29), done. - remote: Total 141 (delta 52), reused 46 (delta 46), pack-reused 66 - Receiving objects: 100% (141/141), 112.43 KiB | 0 bytes/s, done. - Resolving deltas: 100% (79/79), done. - From github.com:docker/docker - * branch master -> FETCH_HEAD - - This command says get all the changes from the `master` branch belonging to - the `upstream` remote. - -7. Rebase your local master with the `upstream/master`. - - $ git rebase upstream/master - First, rewinding head to replay your work on top of it... - Fast-forwarded master to upstream/master. - - This command applies all the commits from the upstream master to your local - master. - -8. Check the status of your local branch. - - $ git status - On branch master - Your branch is ahead of 'origin/master' by 38 commits. - (use "git push" to publish your local commits) - nothing to commit, working directory clean - - Your local repository now has all the changes from the `upstream` remote. You - need to push the changes to your own remote fork which is `origin master`. - -9. Push the rebased master to `origin master`. - - $ git push origin master - Username for 'https://github.com': moxiegirl - Password for 'https://moxiegirl@github.com': - Counting objects: 223, done. - Compressing objects: 100% (38/38), done. - Writing objects: 100% (69/69), 8.76 KiB | 0 bytes/s, done. - Total 69 (delta 53), reused 47 (delta 31) - To https://github.com/moxiegirl/docker.git - 8e107a9..5035fa1 master -> master - -9. Create a new feature branch to work on your issue. - - Your branch name should have the format `XXXX-descriptive` where `XXXX` is - the issue number you are working on. For example: - - $ git checkout -b 11038-fix-rhel-link - Switched to a new branch '11038-fix-rhel-link' - - Your branch should be up-to-date with the `upstream/master`. Why? Because you - branched off a freshly synced master. Let's check this anyway in the next - step. - -9. Rebase your branch from upstream/master. - - $ git rebase upstream/master - Current branch 11038-fix-rhel-link is up to date. - - At this point, your local branch, your remote repository, and the Docker - repository all have identical code. You are ready to make changes for your - issue. - - -## Where to go next - -At this point, you know what you want to work on and you have a branch to do -your work in. Go onto the next section to learn [how to work on your -changes](work-issue.md). diff --git a/docs/project/get-help.md b/docs/project/get-help.md deleted file mode 100644 index dabf46262..000000000 --- a/docs/project/get-help.md +++ /dev/null @@ -1,224 +0,0 @@ - - - - -# Where to chat or get help - -There are several communications channels you can use to chat with Docker -community members and developers. - - - - - - - - - - - - - - - - - - - -
Internet Relay Chat (IRC) - -

- IRC a direct line to our most knowledgeable Docker users. - The #docker and #docker-dev group on - chat.freenode.net. IRC was first created in 1988. - So, it is a rich chat protocol but it can overwhelm new users. You can search - our chat archives. -

- Use our IRC quickstart guide below for easy ways to get started with IRC. -
Google Groups - There are two groups. - Docker-user - is for people using Docker containers. - The docker-dev - group is for contributors and other people contributing to the Docker - project. -
Twitter - You can follow Docker's twitter - to get updates on our products. You can also tweet us questions or just - share blogs or stories. -
Stack Overflow - Stack Overflow has over 7000K Docker questions listed. We regularly - monitor Docker questions - and so do many other knowledgeable Docker users. -
- - -# IRC Quickstart - -The following instructions show you how to register with two web based IRC -tools. Use one illustrated here or find another. While these instructions are -only for two IRC web clients there are many IRC Clients available on most -platforms. - -## Webchat - -Using Webchat from Freenode.net is a quick and easy way to get chatting. To -register: - -1. In your browser open https://webchat.freenode.net - - ![Login to webchat screen](images/irc_connect.png) - -2. Fill out the form. - - - - - - - - - - - - - - -
NicknameThe short name you want to be known as on IRC chat channels.
Channels#docker
reCAPTCHAUse the value provided.
- -3. Click on the "Connect" button. - - The browser connects you to Webchat. You'll see a lot of text. At the bottom of - the Webchat web page is a command line bar. Just above the command line bar - a message is shown asking you to register. - - ![Registration needed screen](images/irc_after_login.png) - -4. Register your nickname by entering the following command in the -command line bar: - - /msg NickServ REGISTER yourpassword youremail@example.com - - ![Registering screen](images/register_nic.png) - - This command line bar is also the entry field that you will use for entering - chat messages into IRC chat channels after you have registered and joined a - chat channel. - - After entering the REGISTER command, an email is sent to the email address - that you provided. This email will contain instructions for completing - your registration. - -5. Open your email client and look for the email. - - ![Login screen](images/register_email.png) - -6. Back in the browser, complete the registration according to the email -by entering the following command into the webchat command line bar: - - /msg NickServ VERIFY REGISTER yournickname somecode - - Your nickname is now registered to chat on freenode.net. - -[Jump ahead to tips to join a docker channel and start chatting](#tips) - -## IRCCloud - -IRCCloud is a web-based IRC client service that is hosted in the cloud. This is -a Freemium product, meaning the free version is limited and you can pay for more -features. To use IRCCloud: - -1. Select the following link: - Join the #docker channel on chat.freenode.net - - The following web page is displayed in your browser: - - ![IRCCloud Register screen](images/irccloud-join.png) - -2. If this is your first time using IRCCloud enter a valid email address in the -form. People who have already registered with IRCCloud can select the "sign in -here" link. Additionally, people who are already registered with IRCCloud may -have a cookie stored on their web browser that enables a quick start "let's go" -link to be shown instead of the above form. In this case just select the -"let's go" link and [jump ahead to start chatting](#start-chatting) - -3. After entering your email address in the form, check your email for an invite -from IRCCloud and follow the instructions provided in the email. - -4. After following the instructions in your email you should have an IRCCloud -Client web page in your browser: - - ![IRCCloud](images/irccloud-register-nick.png) - - The message shown above may appear indicating that you need to register your - nickname. - -5. To register your nickname enter the following message into the command line bar -at the bottom of the IRCCloud Client: - - /msg NickServ REGISTER yourpassword youremail@example.com - - This command line bar is for chatting and entering in IRC commands. - -6. Check your email for an invite to freenode.net: - - ![Login screen](images/register_email.png) - -7. Back in the browser, complete the registration according to the email. - - /msg NickServ VERIFY REGISTER yournickname somecode - -## Tips - -The procedures in this section apply to both IRC clients. - -### Set a nickname - -Next time you return to log into chat, you may need to re-enter your password -on the command line using this command: - - /msg NickServ identify - -With Webchat if you forget or lose your password see the FAQ on -freenode.net to learn how to recover it. - -### Join a Docker Channel - -Join the `#docker` group using the following command in the command line bar of -your IRC Client: - - /j #docker - -You can also join the `#docker-dev` group: - - /j #docker-dev - -### Start chatting - -To ask questions to the group just type messages in the command line bar: - - ![Web Chat Screen](images/irc_chat.png) - -## Learning more about IRC - -This quickstart was meant to get you up and into IRC very quickly. If you find -IRC useful there is more to learn. Drupal, another open source project, -has -written some documentation about using IRC for their project -(thanks Drupal!). - diff --git a/docs/project/images/box.png b/docs/project/images/box.png deleted file mode 100644 index 642385ae6..000000000 Binary files a/docs/project/images/box.png and /dev/null differ diff --git a/docs/project/images/branch-sig.png b/docs/project/images/branch-sig.png deleted file mode 100644 index b30f007ba..000000000 Binary files a/docs/project/images/branch-sig.png and /dev/null differ diff --git a/docs/project/images/checked.png b/docs/project/images/checked.png deleted file mode 100644 index 93ab2be9b..000000000 Binary files a/docs/project/images/checked.png and /dev/null differ diff --git a/docs/project/images/commits_expected.png b/docs/project/images/commits_expected.png deleted file mode 100644 index d3d8b1e3c..000000000 Binary files a/docs/project/images/commits_expected.png and /dev/null differ diff --git a/docs/project/images/contributor-edit.png b/docs/project/images/contributor-edit.png deleted file mode 100644 index 52737d7b4..000000000 Binary files a/docs/project/images/contributor-edit.png and /dev/null differ diff --git a/docs/project/images/copy_url.png b/docs/project/images/copy_url.png deleted file mode 100644 index a715019ed..000000000 Binary files a/docs/project/images/copy_url.png and /dev/null differ diff --git a/docs/project/images/easy_issue.png b/docs/project/images/easy_issue.png deleted file mode 100644 index 6d346bcd8..000000000 Binary files a/docs/project/images/easy_issue.png and /dev/null differ diff --git a/docs/project/images/existing_issue.png b/docs/project/images/existing_issue.png deleted file mode 100644 index 6757e60bb..000000000 Binary files a/docs/project/images/existing_issue.png and /dev/null differ diff --git a/docs/project/images/existing_issue.snagproj b/docs/project/images/existing_issue.snagproj deleted file mode 100644 index 05ae2b0cc..000000000 Binary files a/docs/project/images/existing_issue.snagproj and /dev/null differ diff --git a/docs/project/images/fixes_num.png b/docs/project/images/fixes_num.png deleted file mode 100644 index df52f27fd..000000000 Binary files a/docs/project/images/fixes_num.png and /dev/null differ diff --git a/docs/project/images/fork_docker.png b/docs/project/images/fork_docker.png deleted file mode 100644 index f7c557cd4..000000000 Binary files a/docs/project/images/fork_docker.png and /dev/null differ diff --git a/docs/project/images/fresh_container.png b/docs/project/images/fresh_container.png deleted file mode 100644 index 7f69f2d3a..000000000 Binary files a/docs/project/images/fresh_container.png and /dev/null differ diff --git a/docs/project/images/git_bash.png b/docs/project/images/git_bash.png deleted file mode 100644 index 153fd2fbb..000000000 Binary files a/docs/project/images/git_bash.png and /dev/null differ diff --git a/docs/project/images/give_try.png b/docs/project/images/give_try.png deleted file mode 100644 index c04952761..000000000 Binary files a/docs/project/images/give_try.png and /dev/null differ diff --git a/docs/project/images/gordon.jpeg b/docs/project/images/gordon.jpeg deleted file mode 100644 index 8a0df7d46..000000000 Binary files a/docs/project/images/gordon.jpeg and /dev/null differ diff --git a/docs/project/images/in_room.png b/docs/project/images/in_room.png deleted file mode 100644 index 4fdec81b9..000000000 Binary files a/docs/project/images/in_room.png and /dev/null differ diff --git a/docs/project/images/include_gcc.png b/docs/project/images/include_gcc.png deleted file mode 100644 index e48f50cdf..000000000 Binary files a/docs/project/images/include_gcc.png and /dev/null differ diff --git a/docs/project/images/irc_after_login.png b/docs/project/images/irc_after_login.png deleted file mode 100644 index 79496c806..000000000 Binary files a/docs/project/images/irc_after_login.png and /dev/null differ diff --git a/docs/project/images/irc_chat.png b/docs/project/images/irc_chat.png deleted file mode 100644 index 6266020f6..000000000 Binary files a/docs/project/images/irc_chat.png and /dev/null differ diff --git a/docs/project/images/irc_connect.png b/docs/project/images/irc_connect.png deleted file mode 100644 index f411aabca..000000000 Binary files a/docs/project/images/irc_connect.png and /dev/null differ diff --git a/docs/project/images/irc_login.png b/docs/project/images/irc_login.png deleted file mode 100644 index a7a1dc7eb..000000000 Binary files a/docs/project/images/irc_login.png and /dev/null differ diff --git a/docs/project/images/irccloud-join.png b/docs/project/images/irccloud-join.png deleted file mode 100644 index 068b2c4b2..000000000 Binary files a/docs/project/images/irccloud-join.png and /dev/null differ diff --git a/docs/project/images/irccloud-register-nick.png b/docs/project/images/irccloud-register-nick.png deleted file mode 100644 index 60c856056..000000000 Binary files a/docs/project/images/irccloud-register-nick.png and /dev/null differ diff --git a/docs/project/images/issue_list.png b/docs/project/images/issue_list.png deleted file mode 100644 index 0d954986b..000000000 Binary files a/docs/project/images/issue_list.png and /dev/null differ diff --git a/docs/project/images/latest_commits.png b/docs/project/images/latest_commits.png deleted file mode 100644 index 791683a5c..000000000 Binary files a/docs/project/images/latest_commits.png and /dev/null differ diff --git a/docs/project/images/list_example.png b/docs/project/images/list_example.png deleted file mode 100644 index a306e6e7d..000000000 Binary files a/docs/project/images/list_example.png and /dev/null differ diff --git a/docs/project/images/locate_branch.png b/docs/project/images/locate_branch.png deleted file mode 100644 index b865cfcbf..000000000 Binary files a/docs/project/images/locate_branch.png and /dev/null differ diff --git a/docs/project/images/path_variable.png b/docs/project/images/path_variable.png deleted file mode 100644 index 52f197a5e..000000000 Binary files a/docs/project/images/path_variable.png and /dev/null differ diff --git a/docs/project/images/proposal.png b/docs/project/images/proposal.png deleted file mode 100644 index 250781a70..000000000 Binary files a/docs/project/images/proposal.png and /dev/null differ diff --git a/docs/project/images/proposal.snagproj b/docs/project/images/proposal.snagproj deleted file mode 100644 index c9ad49d0e..000000000 Binary files a/docs/project/images/proposal.snagproj and /dev/null differ diff --git a/docs/project/images/pull_request_made.png b/docs/project/images/pull_request_made.png deleted file mode 100644 index d51a1a75f..000000000 Binary files a/docs/project/images/pull_request_made.png and /dev/null differ diff --git a/docs/project/images/red_notice.png b/docs/project/images/red_notice.png deleted file mode 100644 index 8839723a3..000000000 Binary files a/docs/project/images/red_notice.png and /dev/null differ diff --git a/docs/project/images/register_email.png b/docs/project/images/register_email.png deleted file mode 100644 index 02ef4cd27..000000000 Binary files a/docs/project/images/register_email.png and /dev/null differ diff --git a/docs/project/images/register_nic.png b/docs/project/images/register_nic.png deleted file mode 100644 index 16cf05a39..000000000 Binary files a/docs/project/images/register_nic.png and /dev/null differ diff --git a/docs/project/images/three_running.png b/docs/project/images/three_running.png deleted file mode 100644 index cf6d25f2d..000000000 Binary files a/docs/project/images/three_running.png and /dev/null differ diff --git a/docs/project/images/three_terms.png b/docs/project/images/three_terms.png deleted file mode 100644 index 7caa6ac6e..000000000 Binary files a/docs/project/images/three_terms.png and /dev/null differ diff --git a/docs/project/images/to_from_pr.png b/docs/project/images/to_from_pr.png deleted file mode 100644 index 8dd6638e1..000000000 Binary files a/docs/project/images/to_from_pr.png and /dev/null differ diff --git a/docs/project/images/windows-env-vars.png b/docs/project/images/windows-env-vars.png deleted file mode 100644 index 68d00e908..000000000 Binary files a/docs/project/images/windows-env-vars.png and /dev/null differ diff --git a/docs/project/images/windows-mingw.png b/docs/project/images/windows-mingw.png deleted file mode 100644 index b1d15e6b1..000000000 Binary files a/docs/project/images/windows-mingw.png and /dev/null differ diff --git a/docs/project/make-a-contribution.md b/docs/project/make-a-contribution.md deleted file mode 100644 index 188a73795..000000000 --- a/docs/project/make-a-contribution.md +++ /dev/null @@ -1,41 +0,0 @@ - - -# Understand how to contribute - -Contributing is a process where you work with Docker maintainers and the -community to improve Docker. The maintainers are experienced contributors -who specialize in one or more Docker components. Maintainers play a big role -in reviewing contributions. - -There is a formal process for contributing. We try to keep our contribution -process simple so you'll want to contribute frequently. - - -## The basic contribution workflow - -In this guide, you work through Docker's basic contribution workflow by fixing a -single *beginner* issue in the `docker/docker` repository. The workflow -for fixing simple issues looks like this: - -![Simple process](images/existing_issue.png) - -All Docker repositories have code and documentation. You use this same workflow -for either content type. For example, you can find and fix doc or code issues. -Also, you can propose a new Docker feature or propose a new Docker tutorial. - -Some workflow stages do have slight differences for code or documentation -contributions. When you reach that point in the flow, we make sure to tell you. - - -## Where to go next - -Now that you know a little about the contribution process, go to the next section -to [find an issue you want to work on](find-an-issue.md). diff --git a/docs/project/review-pr.md b/docs/project/review-pr.md deleted file mode 100644 index 680a62ddd..000000000 --- a/docs/project/review-pr.md +++ /dev/null @@ -1,141 +0,0 @@ - - - -# Participate in the PR review - -Creating a pull request is nearly the end of the contribution process. At this -point, your code is reviewed both by our continuous integration (CI) systems and -by our maintainers. - -The CI system is an automated system. The maintainers are human beings that also -work on Docker. You need to understand and work with both the "bots" and the -"beings" to review your contribution. - - -## How we process your review - -First to review your pull request is Gordon. Gordon is fast. He checks your -pull request (PR) for common problems like a missing signature. If Gordon finds a -problem, he'll send an email through your GitHub user account: - -![Gordon](images/gordon.jpeg) - -Our build bot system starts building your changes while Gordon sends any emails. - -The build system double-checks your work by compiling your code with Docker's master -code. Building includes running the same tests you ran locally. If you forgot -to run tests or missed something in fixing problems, the automated build is our -safety check. - -After Gordon and the bots, the "beings" review your work. Docker maintainers look -at your pull request and comment on it. The shortest comment you might see is -`LGTM` which means **l**ooks-**g**ood-**t**o-**m**e. If you get an `LGTM`, that -is a good thing, you passed that review. - -For complex changes, maintainers may ask you questions or ask you to change -something about your submission. All maintainer comments on a PR go to the -email address associated with your GitHub account. Any GitHub user who -"participates" in a PR receives an email to. Participating means creating or -commenting on a PR. - -Our maintainers are very experienced Docker users and open source contributors. -So, they value your time and will try to work efficiently with you by keeping -their comments specific and brief. If they ask you to make a change, you'll -need to update your pull request with additional changes. - -## Update an existing pull request - -To update your existing pull request: - -1. Checkout the PR branch in your local `docker-fork` repository. - - This is the branch associated with your request. - -2. Change one or more files and then stage your changes. - - The command syntax is: - - git add - -3. Commit the change. - - $ git commit --amend - - Git opens an editor containing your last commit message. - -4. Adjust your last comment to reflect this new change. - - Added a new sentence per Anaud's suggestion - - Signed-off-by: Mary Anthony - - # Please enter the commit message for your changes. Lines starting - # with '#' will be ignored, and an empty message aborts the commit. - # On branch 11038-fix-rhel-link - # Your branch is up-to-date with 'origin/11038-fix-rhel-link'. - # - # Changes to be committed: - # modified: docs/installation/mac.md - # modified: docs/installation/rhel.md - -5. Force push the change to your origin. - - The command syntax is: - - git push -f origin - -6. Open your browser to your pull request on GitHub. - - You should see your pull request now contains your newly pushed code. - -7. Add a comment to your pull request. - - GitHub only notifies PR participants when you comment. For example, you can - mention that you updated your PR. Your comment alerts the maintainers that - you made an update. - -A change requires LGTMs from an absolute majority of an affected component's -maintainers. For example, if you change `docs/` and `registry/` code, an -absolute majority of the `docs/` and the `registry/` maintainers must approve -your PR. Once you get approval, we merge your pull request into Docker's -`master` code branch. - -## After the merge - -It can take time to see a merged pull request in Docker's official release. -A master build is available almost immediately though. Docker builds and -updates its development binaries after each merge to `master`. - -1. Browse to https://master.dockerproject.org/. - -2. Look for the binary appropriate to your system. - -3. Download and run the binary. - - You might want to run the binary in a container though. This - will keep your local host environment clean. - -4. View any documentation changes at docs.master.dockerproject.org. - -Once you've verified everything merged, feel free to delete your feature branch -from your fork. For information on how to do this, - -see the GitHub help on deleting branches. - -## Where to go next - -At this point, you have completed all the basic tasks in our contributors guide. -If you enjoyed contributing, let us know by completing another beginner -issue or two. We really appreciate the help. - -If you are very experienced and want to make a major change, go on to -[learn about advanced contributing](advanced-contributing.md). diff --git a/docs/project/set-up-dev-env.md b/docs/project/set-up-dev-env.md deleted file mode 100644 index c1fac8cf6..000000000 --- a/docs/project/set-up-dev-env.md +++ /dev/null @@ -1,426 +0,0 @@ - - -# Work with a development container - -In this section, you learn to develop like a member of Docker's core team. -The `docker` repository includes a `Dockerfile` at its root. This file defines -Docker's development environment. The `Dockerfile` lists the environment's -dependencies: system libraries and binaries, Go environment, Go dependencies, -etc. - -Docker's development environment is itself, ultimately a Docker container. -You use the `docker` repository and its `Dockerfile` to create a Docker image, -run a Docker container, and develop code in the container. Docker itself builds, -tests, and releases new Docker versions using this container. - -If you followed the procedures that -set up Git for contributing, you should have a fork of the `docker/docker` -repository. You also created a branch called `dry-run-test`. In this section, -you continue working with your fork on this branch. - -## Clean your host of Docker artifacts - -Docker developers run the latest stable release of the Docker software (with Docker Machine if their machine is Mac OS X). They clean their local -hosts of unnecessary Docker artifacts such as stopped containers or unused -images. Cleaning unnecessary artifacts isn't strictly necessary, but it is -good practice, so it is included here. - -To remove unnecessary artifacts: - -1. Verify that you have no unnecessary containers running on your host. - - $ docker ps - - You should see something similar to the following: - - - - - - - - - - - -
CONTAINER IDIMAGECOMMANDCREATEDSTATUSPORTSNAMES
- - There are no running containers on this host. If you have running but unused - containers, stop and then remove them with the `docker stop` and `docker rm` - commands. - -2. Verify that your host has no dangling images. - - $ docker images - - You should see something similar to the following: - - - - - - - - - -
REPOSITORYTAGIMAGE IDCREATEDVIRTUAL SIZE
- - This host has no images. You may have one or more _dangling_ images. A - dangling image is not used by a running container and is not an ancestor of - another image on your system. A fast way to remove dangling containers is - the following: - - $ docker rmi -f $(docker images -q -a -f dangling=true) - - This command uses `docker images` to list all images (`-a` flag) by numeric - IDs (`-q` flag) and filter them to find dangling images (`-f dangling=true`). - Then, the `docker rmi` command forcibly (`-f` flag) removes - the resulting list. To remove just one image, use the `docker rmi ID` - command. - - -## Build an image - -If you followed the last procedure, your host is clean of unnecessary images -and containers. In this section, you build an image from the Docker development -environment. - -1. Open a terminal. - - Mac users, use `docker-machine status your_vm_name` to make sure your VM is running. You - may need to run `eval "$(docker-machine env your_vm_name)"` to initialize your - shell environment. - -3. Change into the root of your forked repository. - - $ cd ~/repos/docker-fork - - If you are following along with this guide, you created a `dry-run-test` - branch when you set up Git for - contributing. - -4. Ensure you are on your `dry-run-test` branch. - - $ git checkout dry-run-test - - If you get a message that the branch doesn't exist, add the `-b` flag (git checkout -b dry-run-test) so the - command both creates the branch and checks it out. - -5. Compile your development environment container into an image. - - $ docker build -t dry-run-test . - - The `docker build` command returns informational message as it runs. The - first build may take a few minutes to create an image. Using the - instructions in the `Dockerfile`, the build may need to download source and - other images. A successful build returns a final status message similar to - the following: - - Successfully built 676815d59283 - -6. List your Docker images again. - - $ docker images - - You should see something similar to this: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
REPOSTITORYTAGIMAGE IDCREATEDVIRTUAL SIZE
dry-run-testlatest663fbee70028About a minute ago
ubuntutrusty2d24f826cb162 days ago188.3 MB
ubuntutrusty-20150218.12d24f826cb162 days ago188.3 MB
ubuntu14.042d24f826cb162 days ago188.3 MB
ubuntu14.04.22d24f826cb162 days ago188.3 MB
ubuntulatest2d24f826cb162 days ago188.3 MB
- - Locate your new `dry-run-test` image in the list. You should also see a - number of `ubuntu` images. The build process creates these. They are the - ancestors of your new Docker development image. When you next rebuild your - image, the build process reuses these ancestors images if they exist. - - Keeping the ancestor images improves the build performance. When you rebuild - the child image, the build process uses the local ancestors rather than - retrieving them from the Hub. The build process gets new ancestors only if - Docker Hub has updated versions. - -## Start a container and run a test - -At this point, you have created a new Docker development environment image. Now, -you'll use this image to create a Docker container to develop in. Then, you'll -build and run a `docker` binary in your container. - -1. Open two additional terminals on your host. - - At this point, you'll have about three terminals open. - - ![Multiple terminals](images/three_terms.png) - - Mac OS X users, make sure you run `eval "$(docker-machine env your_vm_name)"` in - any new terminals. - -2. In a terminal, create a new container from your `dry-run-test` image. - - $ docker run --privileged --rm -ti dry-run-test /bin/bash - root@5f8630b873fe:/go/src/github.com/docker/docker# - - The command creates a container from your `dry-run-test` image. It opens an - interactive terminal (`-ti`) running a `/bin/bash` shell. The - `--privileged` flag gives the container access to kernel features and device - access. This flag allows you to run a container in a container. - Finally, the `-rm` flag instructs Docker to remove the container when you - exit the `/bin/bash` shell. - - The container includes the source of your image repository in the - `/go/src/github.com/docker/docker` directory. Try listing the contents to - verify they are the same as that of your `docker-fork` repo. - - ![List example](images/list_example.png) - - -3. Investigate your container bit. - - If you do a `go version` you'll find the `go` language is part of the - container. - - root@31ed86e9ddcf:/go/src/github.com/docker/docker# go version - go version go1.4.2 linux/amd64 - - Similarly, if you do a `docker version` you find the container - has no `docker` binary. - - root@31ed86e9ddcf:/go/src/github.com/docker/docker# docker version - bash: docker: command not found - - You will create one in the next steps. - -4. From the `/go/src/github.com/docker/docker` directory make a `docker` binary -with the `make.sh` script. - - root@5f8630b873fe:/go/src/github.com/docker/docker# hack/make.sh binary - - You only call `hack/make.sh` to build a binary _inside_ a Docker - development container as you are now. On your host, you'll use `make` - commands (more about this later). - - As it makes the binary, the `make.sh` script reports the build's progress. - When the command completes successfully, you should see the following - output: - - ---> Making bundle: binary (in bundles/1.5.0-dev/binary) - Created binary: /go/src/github.com/docker/docker/bundles/1.5.0-dev/binary/docker-1.5.0-dev - -5. List all the contents of the `binary` directory. - - root@5f8630b873fe:/go/src/github.com/docker/docker# ls bundles/1.5.0-dev/binary/ - docker docker-1.5.0-dev docker-1.5.0-dev.md5 docker-1.5.0-dev.sha256 - - You should see that `binary` directory, just as it sounds, contains the - made binaries. - - -6. Copy the `docker` binary to the `/usr/bin` of your container. - - root@5f8630b873fe:/go/src/github.com/docker/docker# cp bundles/1.5.0-dev/binary/docker /usr/bin - -7. Inside your container, check your Docker version. - - root@5f8630b873fe:/go/src/github.com/docker/docker# docker --version - Docker version 1.5.0-dev, build 6e728fb - - Inside the container you are running a development version. This is the version - on the current branch. It reflects the value of the `VERSION` file at the - root of your `docker-fork` repository. - -8. Start a `docker` daemon running inside your container. - - root@5f8630b873fe:/go/src/github.com/docker/docker# docker daemon -D - - The `-D` flag starts the daemon in debug mode. You'll find this useful - when debugging your code. - -9. Bring up one of the terminals on your local host. - - -10. List your containers and look for the container running the `dry-run-test` image. - - $ docker ps - - - - - - - - - - - - - - - - - - - - -
CONTAINER IDIMAGECOMMANDCREATEDSTATUSPORTSNAMES
474f07652525dry-run-test:latest"hack/dind /bin/bash14 minutes agoUp 14 minutestender_shockley
- - In this example, the container's name is `tender_shockley`; yours will be - different. - -11. From the terminal, start another shell on your Docker development container. - - $ docker exec -it tender_shockley bash - - At this point, you have two terminals both with a shell open into your - development container. One terminal is running a debug session. The other - terminal is displaying a `bash` prompt. - -12. At the prompt, test the Docker client by running the `hello-world` container. - - root@9337c96e017a:/go/src/github.com/docker/docker# docker run hello-world - - You should see the image load and return. Meanwhile, you - can see the calls made via the debug session in your other terminal. - - ![List example](images/three_running.png) - - -## Restart a container with your source - -At this point, you have experienced the "Docker inception" technique. That is, -you have: - -* built a Docker image from the Docker repository -* created and started a Docker development container from that image -* built a Docker binary inside of your Docker development container -* launched a `docker` daemon using your newly compiled binary -* called the `docker` client to run a `hello-world` container inside - your development container - -When you really get to developing code though, you'll want to iterate code -changes and builds inside the container. For that you need to mount your local -Docker repository source into your Docker container. Try that now. - -1. If you haven't already, exit out of BASH shells in your running Docker -container. - - If you have followed this guide exactly, exiting out your BASH shells stops - the running container. You can use the `docker ps` command to verify the - development container is stopped. All of your terminals should be at the - local host prompt. - -2. Choose a terminal and make sure you are in your `docker-fork` repository. - - $ pwd - /Users/mary/go/src/github.com/moxiegirl/docker-fork - - Your location will be different because it reflects your environment. - -3. Create a container using `dry-run-test`, but this time, mount your repository -onto the `/go` directory inside the container. - - $ docker run --privileged --rm -ti -v `pwd`:/go/src/github.com/docker/docker dry-run-test /bin/bash - - When you pass `pwd`, `docker` resolves it to your current directory. - -4. From inside the container, list your `binary` directory. - - root@074626fc4b43:/go/src/github.com/docker/docker# ls bundles/1.5.0-dev/binary - ls: cannot access binary: No such file or directory - - Your `dry-run-test` image does not retain any of the changes you made inside - the container. This is the expected behavior for a container. - -5. In a fresh terminal on your local host, change to the `docker-fork` root. - - $ cd ~/repos/docker-fork/ - -6. Create a fresh binary, but this time, use the `make` command. - - $ make BINDDIR=. binary - - The `BINDDIR` flag is only necessary on Mac OS X but it won't hurt to pass - it on Linux command line. The `make` command, like the `make.sh` script - inside the container, reports its progress. When the make succeeds, it - returns the location of the new binary. - - -7. Back in the terminal running the container, list your `binary` directory. - - root@074626fc4b43:/go/src/github.com/docker/docker# ls bundles/1.5.0-dev/binary - docker docker-1.5.0-dev docker-1.5.0-dev.md5 docker-1.5.0-dev.sha256 - - The compiled binaries created from your repository on your local host are - now available inside your running Docker development container. - -8. Repeat the steps you ran in the previous procedure. - - * copy the binary inside the development container using - `cp bundles/1.5.0-dev/binary/docker /usr/bin` - * start `docker daemon -D` to launch the Docker daemon inside the container - * run `docker ps` on local host to get the development container's name - * connect to your running container `docker exec -it container_name bash` - * use the `docker run hello-world` command to create and run a container - inside your development container - -## Where to go next - -Congratulations, you have successfully achieved Docker inception. At this point, -you've set up your development environment and verified almost all the essential -processes you need to contribute. Of course, before you start contributing, -[you'll need to learn one more piece of the development environment, the test -framework](test-and-docs.md). diff --git a/docs/project/set-up-git.md b/docs/project/set-up-git.md deleted file mode 100644 index 020275112..000000000 --- a/docs/project/set-up-git.md +++ /dev/null @@ -1,248 +0,0 @@ - - -# Configure Git for contributing - -Work through this page to configure Git and a repository you'll use throughout -the Contributor Guide. The work you do further in the guide, depends on the work -you do here. - -## Fork and clone the Docker code - -Before contributing, you first fork the Docker code repository. A fork copies -a repository at a particular point in time. GitHub tracks for you where a fork -originates. - -As you make contributions, you change your fork's code. When you are ready, -you make a pull request back to the original Docker repository. If you aren't -familiar with this workflow, don't worry, this guide walks you through all the -steps. - -To fork and clone Docker: - -1. Open a browser and log into GitHub with your account. - -2. Go to the docker/docker repository. - -3. Click the "Fork" button in the upper right corner of the GitHub interface. - - ![Branch Signature](images/fork_docker.png) - - GitHub forks the repository to your GitHub account. The original - `docker/docker` repository becomes a new fork `YOUR_ACCOUNT/docker` under - your account. - -4. Copy your fork's clone URL from GitHub. - - GitHub allows you to use HTTPS or SSH protocols for clones. You can use the - `git` command line or clients like Subversion to clone a repository. - - ![Copy clone URL](images/copy_url.png) - - This guide assume you are using the HTTPS protocol and the `git` command - line. If you are comfortable with SSH and some other tool, feel free to use - that instead. You'll need to convert what you see in the guide to what is - appropriate to your tool. - -5. Open a terminal window on your local host and change to your home directory. - - $ cd ~ - - In Windows, you'll work in your Docker Quickstart Terminal window instead of - Powershell or a `cmd` window. - -6. Create a `repos` directory. - - $ mkdir repos - -7. Change into your `repos` directory. - - $ cd repos - -5. Clone the fork to your local host into a repository called `docker-fork`. - - $ git clone https://github.com/moxiegirl/docker.git docker-fork - - Naming your local repo `docker-fork` should help make these instructions - easier to follow; experienced coders don't typically change the name. - -6. Change directory into your new `docker-fork` directory. - - $ cd docker-fork - - Take a moment to familiarize yourself with the repository's contents. List - the contents. - -## Set your signature and an upstream remote - -When you contribute to Docker, you must certify you agree with the -Developer Certificate of Origin. -You indicate your agreement by signing your `git` commits like this: - - Signed-off-by: Pat Smith - -To create a signature, you configure your username and email address in Git. -You can set these globally or locally on just your `docker-fork` repository. -You must sign with your real name. We don't accept anonymous contributions or -contributions through pseudonyms. - -As you change code in your fork, you'll want to keep it in sync with the changes -others make in the `docker/docker` repository. To make syncing easier, you'll -also add a _remote_ called `upstream` that points to `docker/docker`. A remote -is just another project version hosted on the internet or network. - -To configure your username, email, and add a remote: - -1. Change to the root of your `docker-fork` repository. - - $ cd docker-fork - -2. Set your `user.name` for the repository. - - $ git config --local user.name "FirstName LastName" - -3. Set your `user.email` for the repository. - - $ git config --local user.email "emailname@mycompany.com" - -4. Set your local repo to track changes upstream, on the `docker` repository. - - $ git remote add upstream https://github.com/docker/docker.git - -7. Check the result in your `git` configuration. - - $ git config --local -l - core.repositoryformatversion=0 - core.filemode=true - core.bare=false - core.logallrefupdates=true - remote.origin.url=https://github.com/moxiegirl/docker.git - remote.origin.fetch=+refs/heads/*:refs/remotes/origin/* - branch.master.remote=origin - branch.master.merge=refs/heads/master - user.name=Mary Anthony - user.email=mary@docker.com - remote.upstream.url=https://github.com/docker/docker.git - remote.upstream.fetch=+refs/heads/*:refs/remotes/upstream/* - - To list just the remotes use: - - $ git remote -v - origin https://github.com/moxiegirl/docker.git (fetch) - origin https://github.com/moxiegirl/docker.git (push) - upstream https://github.com/docker/docker.git (fetch) - upstream https://github.com/docker/docker.git (push) - -## Create and push a branch - -As you change code in your fork, make your changes on a repository branch. -The branch name should reflect what you are working on. In this section, you -create a branch, make a change, and push it up to your fork. - -This branch is just for testing your config for this guide. The changes are part -of a dry run, so the branch name will be dry-run-test. To create and push -the branch to your fork on GitHub: - -1. Open a terminal and go to the root of your `docker-fork`. - - $ cd docker-fork - -2. Create a `dry-run-test` branch. - - $ git checkout -b dry-run-test - - This command creates the branch and switches the repository to it. - -3. Verify you are in your new branch. - - $ git branch - * dry-run-test - master - - The current branch has an * (asterisk) marker. So, these results shows you - are on the right branch. - -4. Create a `TEST.md` file in the repository's root. - - $ touch TEST.md - -5. Edit the file and add your email and location. - - ![Add your information](images/contributor-edit.png) - - You can use any text editor you are comfortable with. - -6. Save and close the file. - -7. Check the status of your branch. - - $ git status - On branch dry-run-test - Untracked files: - (use "git add ..." to include in what will be committed) - - TEST.md - - nothing added to commit but untracked files present (use "git add" to track) - - You've only changed the one file. It is untracked so far by git. - -8. Add your file. - - $ git add TEST.md - - That is the only _staged_ file. Stage is fancy word for work that Git is - tracking. - -9. Sign and commit your change. - - $ git commit -s -m "Making a dry run test." - [dry-run-test 6e728fb] Making a dry run test - 1 file changed, 1 insertion(+) - create mode 100644 TEST.md - - Commit messages should have a short summary sentence of no more than 50 - characters. Optionally, you can also include a more detailed explanation - after the summary. Separate the summary from any explanation with an empty - line. - -8. Push your changes to GitHub. - - $ git push --set-upstream origin dry-run-test - Username for 'https://github.com': moxiegirl - Password for 'https://moxiegirl@github.com': - - Git prompts you for your GitHub username and password. Then, the command - returns a result. - - Counting objects: 13, done. - Compressing objects: 100% (2/2), done. - Writing objects: 100% (3/3), 320 bytes | 0 bytes/s, done. - Total 3 (delta 1), reused 0 (delta 0) - To https://github.com/moxiegirl/docker.git - * [new branch] dry-run-test -> dry-run-test - Branch dry-run-test set up to track remote branch dry-run-test from origin. - -9. Open your browser to GitHub. - -10. Navigate to your Docker fork. - -11. Make sure the `dry-run-test` branch exists, that it has your commit, and the -commit is signed. - - ![Branch Signature](images/branch-sig.png) - -## Where to go next - -Congratulations, you have finished configuring both your local host environment -and Git for contributing. In the next section you'll [learn how to set up and -work in a Docker development container](set-up-dev-env.md). diff --git a/docs/project/software-req-win.md b/docs/project/software-req-win.md deleted file mode 100644 index 1eae96c9a..000000000 --- a/docs/project/software-req-win.md +++ /dev/null @@ -1,265 +0,0 @@ - - - -# Get the required software for Windows - -This page explains how to get the software you need to use a a Windows Server -2012 or Windows 8 machine for Docker development. Before you begin contributing -you must have: - -- a GitHub account -- Git for Windows (msysGit) -- TDM-GCC, a compiler suite for Windows -- MinGW (tar and xz) -- Go language - -> **Note**: This installation procedure refers to the `C:\` drive. If you system's main drive -is `D:\` you'll need to substitute that in where appropriate in these -instructions. - -### Get a GitHub account - -To contribute to the Docker project, you will need a GitHub account. A free account is -fine. All the Docker project repositories are public and visible to everyone. - -You should also have some experience using both the GitHub application and `git` -on the command line. - -## Install Git for Windows - -Git for Windows includes several tools including msysGit, which is a build -environment. The environment contains the tools you need for development such as -Git and a Git Bash shell. - -1. Browse to the [Git for Windows](https://msysgit.github.io/) download page. - -2. Click **Download**. - - Windows prompts you to save the file to your machine. - -3. Run the saved file. - - The system displays the **Git Setup** wizard. - -4. Click the **Next** button to move through the wizard and accept all the defaults. - -5. Click **Finish** when you are done. - -## Installing TDM-GCC - -TDM-GCC is a compiler suite for Windows. You'll use this suite to compile the -Docker Go code as you develop. - -1. Browse to - [tdm-gcc download page](http://tdm-gcc.tdragon.net/download). - -2. Click on the latest 64-bit version of the package. - - Windows prompts you to save the file to your machine - -3. Set up the suite by running the downloaded file. - - The system opens the **TDM-GCC Setup** wizard. - -4. Click **Create**. - -5. Click the **Next** button to move through the wizard and accept all the defaults. - -6. Click **Finish** when you are done. - - -## Installing MinGW (tar and xz) - -MinGW is a minimalist port of the GNU Compiler Collection (GCC). In this -procedure, you first download and install the MinGW installation manager. Then, -you use the manager to install the `tar` and `xz` tools from the collection. - -1. Browse to MinGW - [SourceForge](http://sourceforge.net/projects/mingw/). - -2. Click **Download**. - - Windows prompts you to save the file to your machine - -3. Run the downloaded file. - - The system opens the **MinGW Installation Manager Setup Tool** - -4. Choose **Install** install the MinGW Installation Manager. - -5. Press **Continue**. - - The system installs and then opens the MinGW Installation Manager. - -6. Press **Continue** after the install completes to open the manager. - -7. Select **All Packages > MSYS Base System** from the left hand menu. - - The system displays the available packages. - -8. Click on the the **msys-tar bin** package and choose **Mark for Installation**. - -9. Click on the **msys-xz bin** package and choose **Mark for Installation**. - -10. Select **Installation > Apply Changes**, to install the selected packages. - - The system displays the **Schedule of Pending Actions Dialog**. - - ![windows-mingw](images/windows-mingw.png) - -11. Press **Apply** - - MingGW installs the packages for you. - -12. Close the dialog and the MinGW Installation Manager. - - -## Set up your environment variables - -You'll need to add the compiler to your `Path` environment variable. - -1. Open the **Control Panel**. - -2. Choose **System and Security > System**. - -3. Click the **Advanced system settings** link in the sidebar. - - The system opens the **System Properties** dialog. - -3. Select the **Advanced** tab. - -4. Click **Environment Variables**. - - The system opens the **Environment Variables dialog** dialog. - -5. Locate the **System variables** area and scroll to the **Path** - variable. - - ![windows-mingw](images/path_variable.png) - -6. Click **Edit** to edit the variable (you can also double-click it). - - The system opens the **Edit System Variable** dialog. - -7. Make sure the `Path` includes `C:\TDM-GCC64\bin` - - ![include gcc](images/include_gcc.png) - - If you don't see `C:\TDM-GCC64\bin`, add it. - -8. Press **OK** to close this dialog. - -9. Press **OK** twice to close out of the remaining dialogs. - -## Install Go and cross-compile it - -In this section, you install the Go language. Then, you build the source so that it can cross-compile for `linux/amd64` architectures. - -1. Open [Go Language download](http://golang.org/dl/) page in your browser. - -2. Locate and click the latest `.msi` installer. - - The system prompts you to save the file. - -3. Run the installer. - - The system opens the **Go Programming Language Setup** dialog. - -4. Select all the defaults to install. - -5. Press **Finish** to close the installation dialog. - -6. Start a command prompt. - -7. Change to the Go `src` directory. - - cd c:\Go\src - -8. Set the following Go variables - - c:\Go\src> set GOOS=linux - c:\Go\src> set GOARCH=amd64 - -9. Compile the source. - - c:\Go\src> make.bat - - Compiling the source also adds a number of variables to your Windows environment. - -## Get the Docker repository - -In this step, you start a Git `bash` terminal and get the Docker source code -from GitHub. - -1. Locate the **Git Bash** program and start it. - - Recall that **Git Bash** came with the Git for Windows installation. **Git - Bash** just as it sounds allows you to run a Bash terminal on Windows. - - ![Git Bash](images/git_bash.png) - -2. Change to the root directory. - - $ cd /c/ - -3. Make a `gopath` directory. - - $ mkdir gopath - -4. Go get the `docker/docker` repository. - - $ go.exe get github.com/docker/docker package github.com/docker/docker - imports github.com/docker/docker - imports github.com/docker/docker: no buildable Go source files in C:\gopath\src\github.com\docker\docker - - In the next steps, you create environment variables for you Go paths. - -5. Open the **Control Panel** on your system. - -6. Choose **System and Security > System**. - -7. Click the **Advanced system settings** link in the sidebar. - - The system opens the **System Properties** dialog. - -8. Select the **Advanced** tab. - -9. Click **Environment Variables**. - - The system opens the **Environment Variables dialog** dialog. - -10. Locate the **System variables** area and scroll to the **Path** - variable. - -11. Click **New**. - - Now you are going to create some new variables. These paths you'll create in the next procedure; but you can set them now. - -12. Enter `GOPATH` for the **Variable Name**. - -13. For the **Variable Value** enter the following: - - C:\gopath;C:\gopath\src\github.com\docker\docker\vendor - - -14. Press **OK** to close this dialog. - - The system adds `GOPATH` to the list of **System Variables**. - -15. Press **OK** twice to close out of the remaining dialogs. - - -## Where to go next - -In the next section, you'll [learn how to set up and configure Git for -contributing to Docker](set-up-git.md). \ No newline at end of file diff --git a/docs/project/software-required.md b/docs/project/software-required.md deleted file mode 100644 index c82ae13de..000000000 --- a/docs/project/software-required.md +++ /dev/null @@ -1,98 +0,0 @@ - - -# Get the required software for Linux or OS X - -This page explains how to get the software you need to use a Linux or OS X -machine for Docker development. Before you begin contributing you must have: - -* a GitHub account -* `git` -* `make` -* `docker` - -You'll notice that `go`, the language that Docker is written in, is not listed. -That's because you don't need it installed; Docker's development environment -provides it for you. You'll learn more about the development environment later. - -### Get a GitHub account - -To contribute to the Docker project, you will need a GitHub account. A free account is -fine. All the Docker project repositories are public and visible to everyone. - -You should also have some experience using both the GitHub application and `git` -on the command line. - -### Install git - -Install `git` on your local system. You can check if `git` is on already on your -system and properly installed with the following command: - - $ git --version - - -This documentation is written using `git` version 2.2.2. Your version may be -different depending on your OS. - -### Install make - -Install `make`. You can check if `make` is on your system with the following -command: - - $ make -v - -This documentation is written using GNU Make 3.81. Your version may be different -depending on your OS. - -### Install or upgrade Docker - -If you haven't already, install the Docker software using the -instructions for your operating system. -If you have an existing installation, check your version and make sure you have -the latest Docker. - -To check if `docker` is already installed on Linux: - - $ docker --version - Docker version 1.5.0, build a8a31ef - -On Mac OS X or Windows, you should have installed Docker Toolbox which includes -Docker. You'll need to verify both Docker Machine and Docker. This -documentation was written on OS X using the following versions. - - $ docker-machine --version - docker-machine version 0.3.0 (0a251fe) - - $ docker --version - Docker version 1.7.0, build a8a31ef - -## Linux users and sudo - -This guide assumes you have added your user to the `docker` group on your system. -To check, list the group's contents: - - $ getent group docker - docker:x:999:ubuntu - -If the command returns no matches, you have two choices. You can preface this -guide's `docker` commands with `sudo` as you work. Alternatively, you can add -your user to the `docker` group as follows: - - $ sudo usermod -aG docker ubuntu - -You must log out and log back in for this modification to take effect. - - -## Where to go next - -In the next section, you'll [learn how to set up and configure Git for -contributing to Docker](set-up-git.md). diff --git a/docs/project/test-and-docs.md b/docs/project/test-and-docs.md deleted file mode 100644 index 3bfccea2e..000000000 --- a/docs/project/test-and-docs.md +++ /dev/null @@ -1,331 +0,0 @@ - - -# Run tests and test documentation - -Contributing includes testing your changes. If you change the Docker code, you -may need to add a new test or modify an existing one. Your contribution could -even be adding tests to Docker. For this reason, you need to know a little -about Docker's test infrastructure. - -Many contributors contribute documentation only. Or, a contributor makes a code -contribution that changes how Docker behaves and that change needs -documentation. For these reasons, you also need to know how to build, view, and -test the Docker documentation. - -In this section, you run tests in the `dry-run-test` branch of your Docker -fork. If you have followed along in this guide, you already have this branch. -If you don't have this branch, you can create it or simply use another of your -branches. - -## Understand testing at Docker - -Docker tests use the Go language's test framework. In this framework, files -whose names end in `_test.go` contain test code; you'll find test files like -this throughout the Docker repo. Use these files for inspiration when writing -your own tests. For information on Go's test framework, see Go's testing package -documentation and the go test help. - -You are responsible for _unit testing_ your contribution when you add new or -change existing Docker code. A unit test is a piece of code that invokes a -single, small piece of code ( _unit of work_ ) to verify the unit works as -expected. - -Depending on your contribution, you may need to add _integration tests_. These -are tests that combine two or more work units into one component. These work -units each have unit tests and then, together, integration tests that test the -interface between the components. The `integration` and `integration-cli` -directories in the Docker repository contain integration test code. - -Testing is its own specialty. If you aren't familiar with testing techniques, -there is a lot of information available to you on the Web. For now, you should -understand that, the Docker maintainers may ask you to write a new test or -change an existing one. - -### Run tests on your local host - -Before submitting any code change, you should run the entire Docker test suite. -The `Makefile` contains a target for the entire test suite. The target's name -is simply `test`. The `Makefile` contains several targets for testing: - - - - - - - - - - - - - - - - - - - - - - - -
TargetWhat this target does
testRun all the tests.
test-unitRun just the unit tests.
test-integration-cliRun the test for the integration command line interface.
test-docker-pyRun the tests for Docker API client.
- -Run the entire test suite on your current repository: - -1. Open a terminal on your local host. - -2. Change to the root your Docker repository. - - $ cd docker-fork - -3. Make sure you are in your development branch. - - $ git checkout dry-run-test - -4. Run the `make test` command. - - $ make test - - This command does several things, it creates a container temporarily for - testing. Inside that container, the `make`: - - * creates a new binary - * cross-compiles all the binaries for the various operating systems - * runs all the tests in the system - - It can take approximate one hour to run all the tests. The time depends - on your host performance. The default timeout is 60 minutes, which is - defined in hack/make.sh(${TIMEOUT:=60m}). You can modify the timeout - value on the basis of your host performance. When they complete - successfully, you see the output concludes with something like this: - - - PASS: docker_cli_pull_test.go:133: DockerHubPullSuite.TestPullClientDisconnect 1.127s - PASS: docker_cli_pull_test.go:16: DockerHubPullSuite.TestPullFromCentralRegistry 1.049s - PASS: docker_cli_pull_test.go:65: DockerHubPullSuite.TestPullFromCentralRegistryImplicitRefParts 9.795s - PASS: docker_cli_pull_test.go:42: DockerHubPullSuite.TestPullNonExistingImage 2.158s - PASS: docker_cli_pull_test.go:92: DockerHubPullSuite.TestPullScratchNotAllowed 0.044s - OK: 918 passed, 13 skipped - PASS - coverage: 72.9% of statements - ok github.com/docker/docker/integration-cli 1638.553s - ---> Making bundle: .integration-daemon-stop (in bundles/1.9.0-dev/test-integration-cli) - ++++ cat bundles/1.9.0-dev/test-integration-cli/docker.pid - +++ kill 9453 - +++ /etc/init.d/apparmor stop - * Clearing AppArmor profiles cache - ...done. - All profile caches have been cleared, but no profiles have been unloaded. - Unloading profiles will leave already running processes permanently - unconfined, which can lead to unexpected situations. - - To set a process to complain mode, use the command line tool - 'aa-complain'. To really tear down all profiles, run the init script - with the 'teardown' option." - - ---> Making bundle: test-docker-py (in bundles/1.9.0-dev/test-docker-py) - ---> Making bundle: .integration-daemon-start (in bundles/1.9.0-dev/test-docker-py) - +++ /etc/init.d/apparmor start - * Starting AppArmor profiles - Skipping profile in /etc/apparmor.d/disable: usr.sbin.rsyslogd - ...done. - +++ exec docker daemon --debug --host unix:///go/src/github.com/docker/docker/bundles/1.9.0-dev/test-docker-py/docker.sock --storage-driver overlay --exec-driver native --pidfile bundles/1.9.0-dev/test-docker-py/docker.pid --userland-proxy=true - ..............s..............s...................................... - ---------------------------------------------------------------------- - Ran 68 tests in 79.135s - - -### Run test targets inside the development container - -If you are working inside a Docker development container, you use the -`hack/make.sh` script to run tests. The `hack/make.sh` script doesn't -have a single target that runs all the tests. Instead, you provide a single -command line with multiple targets that does the same thing. - -Try this now. - -1. Open a terminal and change to the `docker-fork` root. - -2. Start a Docker development image. - - If you are following along with this guide, you should have a - `dry-run-test` image. - - $ docker run --privileged --rm -ti -v `pwd`:/go/src/github.com/docker/docker dry-run-test /bin/bash - -3. Run the tests using the `hack/make.sh` script. - - root@5f8630b873fe:/go/src/github.com/docker/docker# hack/make.sh dynbinary binary cross test-unit test-integration-cli test-docker-py - - The tests run just as they did within your local host. - - -Of course, you can also run a subset of these targets too. For example, to run -just the unit tests: - - root@5f8630b873fe:/go/src/github.com/docker/docker# hack/make.sh dynbinary binary cross test-unit - -Most test targets require that you build these precursor targets first: -`dynbinary binary cross` - - -## Running individual or multiple named tests - -### Unit tests - -We use golang standard [testing](https://golang.org/pkg/testing/) -package or [gocheck](https://labix.org/gocheck) for our unit tests. - -You can use the `TESTDIRS` environment variable to run unit tests for -a single package. - - $ TESTDIRS='opts' make test-unit - -You can also use the `TESTFLAGS` environment variable to run a single test. The -flag's value is passed as arguments to the `go test` command. For example, from -your local host you can run the `TestBuild` test with this command: - - $ TESTFLAGS='-test.run ^TestValidateIPAddress$' make test-unit - -On unit tests, it's better to use `TESTFLAGS` in combination with -`TESTDIRS` to make it quicker to run a specific test. - - $ TESTDIRS='opts' TESTFLAGS='-test.run ^TestValidateIPAddress$' make test-unit - -### Integration tests - -We use [gocheck](https://labix.org/gocheck) for our integration-cli tests. -You can use the `TESTFLAGS` environment variable to run a single test. The -flag's value is passed as arguments to the `go test` command. For example, from -your local host you can run the `TestBuild` test with this command: - - $ TESTFLAGS='-check.f DockerSuite.TestBuild*' make test-integration-cli - -To run the same test inside your Docker development container, you do this: - - root@5f8630b873fe:/go/src/github.com/docker/docker# TESTFLAGS='-check.f TestBuild*' hack/make.sh binary test-integration-cli - -## Testing the Windows binary against a Linux daemon - -This explains how to test the Windows binary on a Windows machine set up as a -development environment. The tests will be run against a docker daemon -running on a remote Linux machine. You'll use **Git Bash** that came with the -Git for Windows installation. **Git Bash**, just as it sounds, allows you to -run a Bash terminal on Windows. - -1. If you don't have one open already, start a Git Bash terminal. - - ![Git Bash](images/git_bash.png) - -2. Change to the `docker` source directory. - - $ cd /c/gopath/src/github.com/docker/docker - -3. Set `DOCKER_REMOTE_DAEMON` as follows: - - $ export DOCKER_REMOTE_DAEMON=1 - -4. Set `DOCKER_TEST_HOST` to the `tcp://IP_ADDRESS:2376` value; substitute your -Linux machines actual IP address. For example: - - $ export DOCKER_TEST_HOST=tcp://213.124.23.200:2376 - -5. Make the binary and run the tests: - - $ hack/make.sh binary test-integration-cli - - Some tests are skipped on Windows for various reasons. You can see which - tests were skipped by re-running the make and passing in the - `TESTFLAGS='-test.v'` value. For example - - $ TESTFLAGS='-test.v' hack/make.sh binary test-integration-cli - - Should you wish to run a single test such as one with the name - 'TestExample', you can pass in `TESTFLAGS='-check.f TestExample'`. For - example - - $TESTFLAGS='-check.f TestExample' hack/make.sh binary test-integration-cli - -You can now choose to make changes to the Docker source or the tests. If you -make any changes just run these commands again. - - -## Build and test the documentation - -The Docker documentation source files are under `docs`. The content is -written using extended Markdown. We use the static generator MkDocs to build Docker's -documentation. Of course, you don't need to install this generator -to build the documentation, it is included with container. - -You should always check your documentation for grammar and spelling. The best -way to do this is with an online grammar checker. - -When you change a documentation source file, you should test your change -locally to make sure your content is there and any links work correctly. You -can build the documentation from the local host. The build starts a container -and loads the documentation into a server. As long as this container runs, you -can browse the docs. - -1. In a terminal, change to the root of your `docker-fork` repository. - - $ cd ~/repos/docker-fork - -2. Make sure you are in your feature branch. - - $ git status - On branch dry-run-test - Your branch is up-to-date with 'origin/dry-run-test'. - nothing to commit, working directory clean - -3. Build the documentation. - - $ make docs - - When the build completes, you'll see a final output message similar to the - following: - - Successfully built ee7fe7553123 - docker run --rm -it -e AWS_S3_BUCKET -e NOCACHE -p 8000:8000 "docker-docs:dry-run-test" mkdocs serve - Running at: http://0.0.0.0:8000/ - Live reload enabled. - Hold ctrl+c to quit. - -4. Enter the URL in your browser. - - If you are using Docker Machine, replace the default localhost address - (0.0.0.0) with your DOCKERHOST value. You can get this value at any time by - entering `docker-machine ip ` at the command line. - -5. Once in the documentation, look for the red notice to verify you are seeing the correct build. - - ![Beta documentation](images/red_notice.png) - -6. Navigate to your new or changed document. - -7. Review both the content and the links. - -8. Return to your terminal and exit out of the running documentation container. - - -## Where to go next - -Congratulations, you have successfully completed the basics you need to -understand the Docker test framework. In the next steps, you use what you have -learned so far to [contribute to Docker by working on an -issue](make-a-contribution.md). diff --git a/docs/project/who-written-for.md b/docs/project/who-written-for.md deleted file mode 100644 index 5ab409d26..000000000 --- a/docs/project/who-written-for.md +++ /dev/null @@ -1,63 +0,0 @@ - - -# README first - -This section of the documentation contains a guide for Docker users who want to -contribute code or documentation to the Docker project. As a community, we -share rules of behavior and interaction. Make sure you are familiar with the community guidelines before continuing. - -## Where and what you can contribute - -The Docker project consists of not just one but several repositories on GitHub. -So, in addition to the `docker/docker` repository, there is the -`docker/compose` repo, the `docker/machine` repo, and several more. -Contribute to any of these and you contribute to the Docker project. - -Not all Docker repositories use the Go language. Also, each repository has its -own focus area. So, if you are an experienced contributor, think about -contributing to a Docker repository that has a language or a focus area you are -familiar with. - -If you are new to the open source community, to Docker, or to formal -programming, you should start out contributing to the `docker/docker` -repository. Why? Because this guide is written for that repository specifically. - -Finally, code or documentation isn't the only way to contribute. You can report -an issue, add to discussions in our community channel, write a blog post, or -take a usability test. You can even propose your own type of contribution. -Right now we don't have a lot written about this yet, so just email - if this type of contributing interests you. - -## A turtle is involved - -![Gordon](images/gordon.jpeg) - -Enough said. - -## How to use this guide - -This is written for the distracted, the overworked, the sloppy reader with fair -`git` skills and a failing memory for the GitHub GUI. The guide attempts to -explain how to use the Docker environment as precisely, predictably, and -procedurally as possible. - -Users who are new to the Docker development environment should start by setting -up their environment. Then, they should try a simple code change. After that, -you should find something to work on or propose at totally new change. - -If you are a programming prodigy, you still may find this documentation useful. -Please feel free to skim past information you find obvious or boring. - -## How to get started - -Start by [getting the software you need to contribute](software-required.md). diff --git a/docs/project/work-issue.md b/docs/project/work-issue.md deleted file mode 100644 index 3f737fef1..000000000 --- a/docs/project/work-issue.md +++ /dev/null @@ -1,200 +0,0 @@ - - - -# Work on your issue - -The work you do for your issue depends on the specific issue you picked. -This section gives you a step-by-step workflow. Where appropriate, it provides -command examples. - -However, this is a generalized workflow, depending on your issue you may repeat -steps or even skip some. How much time the work takes depends on you --- you -could spend days or 30 minutes of your time. - -## How to work on your local branch - -Follow this workflow as you work: - -1. Review the appropriate style guide. - - If you are changing code, review the coding style guide. Changing documentation? Review the - documentation style guide. - -2. Make changes in your feature branch. - - Your feature branch you created in the last section. Here you use the - development container. If you are making a code change, you can mount your - source into a development container and iterate that way. For documentation - alone, you can work on your local host. - - Make sure you don't change files in the `vendor` directory and its - subdirectories; they contain third-party dependency code. Review if you forgot the details of - working with a container. - - -3. Test your changes as you work. - - If you have followed along with the guide, you know the `make test` target - runs the entire test suite and `make docs` builds the documentation. If you - forgot the other test targets, see the documentation for testing both code and - documentation. - -4. For code changes, add unit tests if appropriate. - - If you add new functionality or change existing functionality, you should - add a unit test also. Use the existing test files for inspiration. Aren't - sure if you need tests? Skip this step; you can add them later in the - process if necessary. - -5. Format your source files correctly. - - - - - - - - - - - - - - - - - - -
File typeHow to format
.go -

- Format .go files using the gofmt command. - For example, if you edited the `docker.go` file you would format the file - like this: -

-

$ gofmt -s -w docker.go

-

- Most file editors have a plugin to format for you. Check your editor's - documentation. -

-
.md and non-.go filesWrap lines to 80 characters.
- -6. List your changes. - - $ git status - On branch 11038-fix-rhel-link - Changes not staged for commit: - (use "git add ..." to update what will be committed) - (use "git checkout -- ..." to discard changes in working directory) - - modified: docs/installation/mac.md - modified: docs/installation/rhel.md - - The `status` command lists what changed in the repository. Make sure you see - the changes you expect. - -7. Add your change to Git. - - $ git add docs/installation/mac.md - $ git add docs/installation/rhel.md - - -8. Commit your changes making sure you use the `-s` flag to sign your work. - - $ git commit -s -m "Fixing RHEL link" - -9. Push your change to your repository. - - $ git push origin 11038-fix-rhel-link - Username for 'https://github.com': moxiegirl - Password for 'https://moxiegirl@github.com': - Counting objects: 60, done. - Compressing objects: 100% (7/7), done. - Writing objects: 100% (7/7), 582 bytes | 0 bytes/s, done. - Total 7 (delta 6), reused 0 (delta 0) - To https://github.com/moxiegirl/docker.git - * [new branch] 11038-fix-rhel-link -> 11038-fix-rhel-link - Branch 11038-fix-rhel-link set up to track remote branch 11038-fix-rhel-link from origin. - -## Review your branch on GitHub - -After you push a new branch, you should verify it on GitHub: - -1. Open your browser to GitHub. - -2. Go to your Docker fork. - -3. Select your branch from the dropdown. - - ![Find branch](images/locate_branch.png) - -4. Use the "Compare" button to compare the differences between your branch and master. - - Depending how long you've been working on your branch, your branch maybe - behind Docker's upstream repository. - -5. Review the commits. - - Make sure your branch only shows the work you've done. - -## Pull and rebase frequently - -You should pull and rebase frequently as you work. - -1. Return to the terminal on your local machine and checkout your - feature branch in your local `docker-fork` repository. - -2. Fetch any last minute changes from `docker/docker`. - - $ git fetch upstream master - From github.com:docker/docker - * branch master -> FETCH_HEAD - -3. Start an interactive rebase. - - $ git rebase -i upstream/master - -4. Rebase opens an editor with a list of commits. - - pick 1a79f55 Tweak some of the other text for grammar - pick 53e4983 Fix a link - pick 3ce07bb Add a new line about RHEL - -5. Replace the `pick` keyword with `squash` on all but the first commit. - - pick 1a79f55 Tweak some of the other text for grammar - squash 53e4983 Fix a link - squash 3ce07bb Add a new line about RHEL - - After you save the changes and quit from the editor, git starts - the rebase, reporting the progress along the way. Sometimes - your changes can conflict with the work of others. If git - encounters a conflict, it stops the rebase, and prints guidance - for how to correct the conflict. - -6. Edit and save your commit message. - - $ git commit -s - - Make sure your message includes your signature. - -7. Force push any changes to your fork on GitHub. - - $ git push -f origin 11038-fix-rhel-link - - -## Where to go next - -At this point, you should understand how to work on an issue. In the next -section, you [learn how to make a pull request](create-pr.md). diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index dbf32e4e4..dfeb51633 100644 --- a/docs/reference/api/docker_remote_api.md +++ b/docs/reference/api/docker_remote_api.md @@ -5,19 +5,20 @@ description = "API Documentation for Docker" keywords = ["API, Docker, rcli, REST, documentation"] [menu.main] parent = "smn_remoteapi" +weight=-3 +++ # Docker Remote API -Docker's Remote API uses an open schema model. In this model, unknown +Docker's Remote API uses an open schema model. In this model, unknown properties in incoming messages are ignored. Client applications need to take this behavior into account to ensure they do not break when talking to newer Docker daemons. The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport STDOUT, STDIN, and STDERR. - + By default the Docker daemon listens on `unix:///var/run/docker.sock` and the client must have `root` access to interact with the daemon. If a group named `docker` exists on your system, `docker` applies ownership of the socket to the @@ -31,14 +32,14 @@ Use the table below to find the API version for a Docker version: Docker version | API version | Changes ----------------|-------------------------------------------------|----------------------------- -1.9.x | [1.21](/reference/api/docker_remote_api_v1.21/) | [API changes](/reference/api/docker_remote_api/#v1-21-api-changes) -1.8.x | [1.20](/reference/api/docker_remote_api_v1.20/) | [API changes](/reference/api/docker_remote_api/#v1-20-api-changes) -1.7.x | [1.19](/reference/api/docker_remote_api_v1.19/) | [API changes](/reference/api/docker_remote_api/#v1-19-api-changes) -1.6.x | [1.18](/reference/api/docker_remote_api_v1.18/) | [API changes](/reference/api/docker_remote_api/#v1-18-api-changes) -1.5.x | [1.17](/reference/api/docker_remote_api_v1.17/) | [API changes](/reference/api/docker_remote_api/#v1-17-api-changes) -1.4.x | [1.16](/reference/api/docker_remote_api_v1.16/) | [API changes](/reference/api/docker_remote_api/#v1-16-api-changes) -1.3.x | [1.15](/reference/api/docker_remote_api_v1.15/) | [API changes](/reference/api/docker_remote_api/#v1-15-api-changes) -1.2.x | [1.14](/reference/api/docker_remote_api_v1.14/) | [API changes](/reference/api/docker_remote_api/#v1-14-api-changes) +1.9.x | [1.21](docker_remote_api_v1.21.md) | [API changes](docker_remote_api.md#v1-21-api-changes) +1.8.x | [1.20](docker_remote_api_v1.20.md) | [API changes](docker_remote_api.md#v1-20-api-changes) +1.7.x | [1.19](docker_remote_api_v1.19.md) | [API changes](docker_remote_api.md#v1-19-api-changes) +1.6.x | [1.18](docker_remote_api_v1.18.md) | [API changes](docker_remote_api.md#v1-18-api-changes) +1.5.x | [1.17](docker_remote_api_v1.17.md) | [API changes](docker_remote_api.md#v1-17-api-changes) +1.4.x | [1.16](docker_remote_api_v1.16.md) | [API changes](docker_remote_api.md#v1-16-api-changes) +1.3.x | [1.15](docker_remote_api_v1.15.md) | [API changes](docker_remote_api.md#v1-15-api-changes) +1.2.x | [1.14](docker_remote_api_v1.14.md) | [API changes](docker_remote_api.md#v1-14-api-changes) Refer to the [GitHub repository]( https://github.com/docker/docker/tree/master/docs/reference/api) for @@ -55,7 +56,7 @@ client has to send the `authConfig` as a `POST` in `/images/(name)/push`. The {"username": "string", "password": "string", "email": "string", "serveraddress" : "string", "auth": ""} ``` - + Callers should leave the `auth` empty. The `serveraddress` is a domain/ip without protocol. Throughout this structure, double quotes are required. @@ -94,11 +95,11 @@ This section lists each version from latest to oldest. Each listing includes a [Docker Remote API v1.21](docker_remote_api_v1.21.md) documentation * `GET /volumes` lists volumes from all volume drivers. -* `POST /volumes` to create a volume. +* `POST /volumes/create` to create a volume. * `GET /volumes/(name)` get low-level information about a volume. * `DELETE /volumes/(name)`remove a volume with the specified name. * `VolumeDriver` has been moved from config to hostConfig to make the configuration portable. -* `GET /images/(name)/json` now returns information about tags of the image. +* `GET /images/(name)/json` now returns information about tags and digests of the image. * The `config` option now accepts the field `StopSignal`, which specifies the signal to use to kill a container. * `GET /containers/(id)/stats` will return networking information respectively for each interface. * The `hostConfig` option now accepts the field `DnsOptions`, which specifies a @@ -110,6 +111,16 @@ list of DNS options to be used in the container. * `GET /containers/json` will return `ImageID` of the image used by container. * `POST /exec/(name)/start` will now return an HTTP 409 when the container is either stopped or paused. * `GET /containers/(name)/json` now accepts a `size` parameter. Setting this parameter to '1' returns container size information in the `SizeRw` and `SizeRootFs` fields. +* `GET /containers/(name)/json` now returns a `NetworkSettings.Networks` field, + detailing network settings per network. This field deprecates the + `NetworkSettings.Gateway`, `NetworkSettings.IPAddress`, + `NetworkSettings.IPPrefixLen`, and `NetworkSettings.MacAddress` fields, which + are still returned for backward-compatibility, but will be removed in a future version. +* `GET /exec/(id)/json` now returns a `NetworkSettings.Networks` field, + detailing networksettings per network. This field deprecates the + `NetworkSettings.Gateway`, `NetworkSettings.IPAddress`, + `NetworkSettings.IPPrefixLen`, and `NetworkSettings.MacAddress` fields, which + are still returned for backward-compatibility, but will be removed in a future version. ### v1.20 API changes @@ -166,7 +177,7 @@ example you could add data describing the content of an image. `LABEL * `GET /containers/(id)/json` returns the list current execs associated with the container (`ExecIDs`). This endpoint now returns the container labels (`Config.Labels`). -* `POST /containers/(id)/rename` renames a container `id` to a new name.* +* `POST /containers/(id)/rename` renames a container `id` to a new name.* * `POST /containers/create` and `POST /containers/(id)/start` callers can pass `ReadonlyRootfs` in the host config to mount the container's root filesystem as read only. diff --git a/docs/reference/api/docker_remote_api_v1.20.md b/docs/reference/api/docker_remote_api_v1.20.md index d5dabc761..80103965d 100644 --- a/docs/reference/api/docker_remote_api_v1.20.md +++ b/docs/reference/api/docker_remote_api_v1.20.md @@ -217,7 +217,7 @@ Json Parameters: for the container. - **User** - A string value specifying the user inside the container. - **Memory** - Memory limit in bytes. -- **MemorySwap**- Total memory limit (memory + swap); set `-1` to disable swap +- **MemorySwap** - Total memory limit (memory + swap); set `-1` to disable swap You must use this with `memory` and make the swap value larger than `memory`. - **CpuShares** - An integer value containing the container's CPU Shares (ie. the relative weight vs other containers). @@ -2141,12 +2141,12 @@ Status Codes: `POST /exec/(id)/resize` -Resizes the `tty` session used by the `exec` command `id`. +Resizes the `tty` session used by the `exec` command `id`. The unit is number of characters. This API is valid only if `tty` was specified as part of creating and starting the `exec` command. **Example request**: - POST /exec/e90e34656806/resize HTTP/1.1 + POST /exec/e90e34656806/resize?h=40&w=80 HTTP/1.1 Content-Type: text/plain **Example response**: @@ -2257,7 +2257,7 @@ Return low-level information about the `exec` command `id`. "ProcessLabel" : "", "AppArmorProfile" : "", "RestartCount" : 0, - "Mounts" : [], + "Mounts" : [] } } diff --git a/docs/reference/api/docker_remote_api_v1.21.md b/docs/reference/api/docker_remote_api_v1.21.md index e2afd3b6a..ace071b23 100644 --- a/docs/reference/api/docker_remote_api_v1.21.md +++ b/docs/reference/api/docker_remote_api_v1.21.md @@ -5,7 +5,7 @@ description = "API Documentation for Docker" keywords = ["API, Docker, rcli, REST, documentation"] [menu.main] parent="smn_remoteapi" -weight = 0 +weight=-2 +++ @@ -46,7 +46,7 @@ List containers "Id": "8dfafdbc3a40", "Names":["/boring_feynman"], "Image": "ubuntu:latest", - "ImageID": "d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82", + "ImageID": "d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82", "Command": "echo 1", "Created": 1367854155, "Status": "Exit 0", @@ -63,7 +63,7 @@ List containers "Id": "9cd87474be90", "Names":["/coolName"], "Image": "ubuntu:latest", - "ImageID": "d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82", + "ImageID": "d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82", "Command": "echo 222222", "Created": 1367854155, "Status": "Exit 0", @@ -76,7 +76,7 @@ List containers "Id": "3176a2479c92", "Names":["/sleepy_dog"], "Image": "ubuntu:latest", - "ImageID": "d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82", + "ImageID": "d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82", "Command": "echo 3333333333333333", "Created": 1367854154, "Status": "Exit 0", @@ -89,7 +89,7 @@ List containers "Id": "4cb07b47f9fb", "Names":["/running_cat"], "Image": "ubuntu:latest", - "ImageID": "d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82", + "ImageID": "d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82", "Command": "echo 444444444444444444444444444444444", "Created": 1367854152, "Status": "Exit 0", @@ -204,7 +204,7 @@ Create a container "LogConfig": { "Type": "json-file", "Config": {} }, "SecurityOpt": [""], "CgroupParent": "", - "VolumeDriver": "" + "VolumeDriver": "" } } @@ -435,11 +435,34 @@ Return low-level information on the container `id` "Name": "/boring_euclid", "NetworkSettings": { "Bridge": "", + "SandboxID": "", + "HairpinMode": false, + "LinkLocalIPv6Address": "", + "LinkLocalIPv6PrefixLen": 0, + "Ports": null, + "SandboxKey": "", + "SecondaryIPAddresses": null, + "SecondaryIPv6Addresses": null, + "EndpointID": "", "Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, "IPAddress": "", "IPPrefixLen": 0, + "IPv6Gateway": "", "MacAddress": "", - "Ports": null + "Networks": { + "bridge": { + "EndpointID": "", + "Gateway": "", + "IPAddress": "", + "IPPrefixLen": 0, + "IPv6Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "MacAddress": "" + } + } }, "Path": "/bin/sh", "ProcessLabel": "", @@ -545,7 +568,7 @@ Status Codes: Get `stdout` and `stderr` logs from the container ``id`` > **Note**: -> This endpoint works only for containers with `json-file` logging driver. +> This endpoint works only for containers with the `json-file` or `journald` logging drivers. **Example request**: @@ -662,7 +685,7 @@ This endpoint returns a live stream of a container's resource usage statistics. { "read" : "2015-01-08T22:57:31.547920715Z", - "network": { + "networks": { "eth0": { "rx_bytes": 5338, "rx_dropped": 0, @@ -1227,14 +1250,14 @@ Query Parameters: **Example request**: - PUT /containers/8cce319429b2/archive?path=/vol1 HTTP/1.1 - Content-Type: application/x-tar + PUT /containers/8cce319429b2/archive?path=/vol1 HTTP/1.1 + Content-Type: application/x-tar - {{ TAR STREAM }} + {{ TAR STREAM }} **Example response**: - HTTP/1.1 200 OK + HTTP/1.1 200 OK Status Codes: @@ -1388,8 +1411,8 @@ Query Parameters: ignored if `remote` is specified and points to an individual filename. - **t** – A repository name (and optionally a tag) to apply to the resulting image in case of success. -- **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the - URI specifies a filename, the file's contents are placed into a file +- **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the + URI specifies a filename, the file's contents are placed into a file called `Dockerfile`. - **q** – Suppress verbose build output. - **nocache** – Do not use the cache when building the image. @@ -1465,12 +1488,15 @@ a base64-encoded AuthConfig object. Query Parameters: -- **fromImage** – Name of the image to pull. +- **fromImage** – Name of the image to pull. The name may include a tag or + digest. This parameter may only be used when pulling an image. - **fromSrc** – Source to import. The value may be a URL from which the image can be retrieved or `-` to read the image from the request body. -- **repo** – Repository name. -- **tag** – Tag. -- **registry** – The registry to pull from. + This parameter may only be used when importing an image. +- **repo** – Repository name given to an image when it is imported. + The repo may include a tag. This parameter may only be used when importing + an image. +- **tag** – Tag or digest. Request Headers: @@ -1547,7 +1573,10 @@ Return low-level information on the image `name` "Name" : "aufs", "Data" : null }, - "Tags" : [ + "RepoDigests" : [ + "localhost:5000/test/busybox/example@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf" + ], + "RepoTags" : [ "example:1.0", "example:latest", "example:stable" @@ -2360,13 +2389,36 @@ Return low-level information about the `exec` command `id`. "SecurityOpt" : null }, "Image" : "5506de2b643be1e6febbf3b8a240760c6843244c41e12aa2f60ccbb7153d17f5", - "NetworkSettings" : { - "IPAddress" : "172.17.0.2", - "IPPrefixLen" : 16, - "MacAddress" : "02:42:ac:11:00:02", - "Gateway" : "172.17.42.1", - "Bridge" : "docker0", - "Ports" : {} + "NetworkSettings": { + "Bridge": "", + "SandboxID": "", + "HairpinMode": false, + "LinkLocalIPv6Address": "", + "LinkLocalIPv6PrefixLen": 0, + "Ports": null, + "SandboxKey": "", + "SecondaryIPAddresses": null, + "SecondaryIPv6Addresses": null, + "EndpointID": "", + "Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAddress": "", + "IPPrefixLen": 0, + "IPv6Gateway": "", + "MacAddress": "", + "Networks": { + "bridge": { + "EndpointID": "", + "Gateway": "", + "IPAddress": "", + "IPPrefixLen": 0, + "IPv6Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "MacAddress": "" + } + } }, "ResolvConfPath" : "/var/lib/docker/containers/8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c/resolv.conf", "HostnamePath" : "/var/lib/docker/containers/8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c/hostname", @@ -2397,26 +2449,26 @@ Status Codes: **Example request**: - GET /volumes HTTP/1.1 + GET /volumes HTTP/1.1 **Example response**: - HTTP/1.1 200 OK - Content-Type: application/json + HTTP/1.1 200 OK + Content-Type: application/json - { - "Volumes": [ - { - "Name": "tardis", - "Driver": "local", - "Mountpoint": "/var/lib/docker/volumes/tardis" - } - ] - } + { + "Volumes": [ + { + "Name": "tardis", + "Driver": "local", + "Mountpoint": "/var/lib/docker/volumes/tardis" + } + ] + } Query Parameters: -- **filter** - JSON encoded value of the filters (a `map[string][]string`) to process on the volumes list. There is one available filter: `dangling=true` +- **filters** - JSON encoded value of the filters (a `map[string][]string`) to process on the volumes list. There is one available filter: `dangling=true` Status Codes: @@ -2425,29 +2477,29 @@ Status Codes: ### Create a volume -`POST /volumes` +`POST /volumes/create` Create a volume **Example request**: - POST /volumes HTTP/1.1 - Content-Type: application/json + POST /volumes/create HTTP/1.1 + Content-Type: application/json - { - "Name": "tardis" - } + { + "Name": "tardis" + } **Example response**: - HTTP/1.1 201 Created - Content-Type: application/json + HTTP/1.1 201 Created + Content-Type: application/json - { - "Name": "tardis" - "Driver": "local", - "Mountpoint": "/var/lib/docker/volumes/tardis" - } + { + "Name": "tardis", + "Driver": "local", + "Mountpoint": "/var/lib/docker/volumes/tardis" + } Status Codes: @@ -2473,14 +2525,14 @@ Return low-level information on the volume `name` **Example response**: - HTTP/1.1 200 OK - Content-Type: application/json + HTTP/1.1 200 OK + Content-Type: application/json - { - "Name": "tardis", - "Driver": "local", - "Mountpoint": "/var/lib/docker/volumes/tardis" - } + { + "Name": "tardis", + "Driver": "local", + "Mountpoint": "/var/lib/docker/volumes/tardis" + } Status Codes: @@ -2496,11 +2548,11 @@ Instruct the driver to remove the volume (`name`). **Example request**: - DELETE /volumes/local/tardis HTTP/1.1 + DELETE /volumes/local/tardis HTTP/1.1 **Example response**: - HTTP/1.1 204 No Content + HTTP/1.1 204 No Content Status Codes @@ -2517,41 +2569,77 @@ Status Codes **Example request**: - GET /networks HTTP/1.1 + GET /networks HTTP/1.1 **Example response**: - HTTP/1.1 200 OK - Content-Type: application/json - ``` - [ - { - "name": "bridge", - "id": "f995e41e471c833266786a64df584fbe4dc654ac99f63a4ee7495842aa093fc4", - "driver": "bridge", - "containers": {} +HTTP/1.1 200 OK +Content-Type: application/json + +[ + { + "Name": "bridge", + "Id": "f2de39df4171b0dc801e8002d1d999b77256983dfc63041c0f34030aa3977566", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [ + { + "Subnet": "172.17.0.0/16" + } + ] }, - { - "name": "none", - "id": "21e34df9b29c74ae45ba312f8e9f83c02433c9a877cfebebcf57be78f69b77c8", - "driver": "null", - "containers": {} + "Containers": { + "39b69226f9d79f5634485fb236a23b2fe4e96a0a94128390a7fbbcc167065867": { + "EndpointID": "ed2419a97c1d9954d05b46e462e7002ea552f216e9b136b80a7db8d98b442eda", + "MacAddress": "02:42:ac:11:00:02", + "IPv4Address": "172.17.0.2/16", + "IPv6Address": "" + } }, - { - "name": "host", - "id": "3f43a0873f00310a71cd6a71e2e60c113cf17d1812be2ec22fd519fbac68ec91", - "driver": "host", - "containers": {} + "Options": { + "com.docker.network.bridge.default_bridge": "true", + "com.docker.network.bridge.enable_icc": "true", + "com.docker.network.bridge.enable_ip_masquerade": "true", + "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", + "com.docker.network.bridge.name": "docker0", + "com.docker.network.driver.mtu": "1500" } - ] + }, + { + "Name": "none", + "Id": "e086a3893b05ab69242d3c44e49483a3bbbd3a26b46baa8f61ab797c1088d794", + "Scope": "local", + "Driver": "null", + "IPAM": { + "Driver": "default", + "Config": [] + }, + "Containers": {}, + "Options": {} + }, + { + "Name": "host", + "Id": "13e871235c677f196c4e1ecebb9dc733b9b2d2ab589e30c539efeda84a24215e", + "Scope": "local", + "Driver": "host", + "IPAM": { + "Driver": "default", + "Config": [] + }, + "Containers": {}, + "Options": {} + } +] ``` Query Parameters: -- **filter** - JSON encoded value of the filters (a `map[string][]string`) to process on the volumes list. Available filters: `name=[network-names]` , `id=[network-ids]` +- **filters** - JSON encoded value of the filters (a `map[string][]string`) to process on the networks list. Available filters: `name=[network-names]` , `id=[network-ids]` Status Codes: @@ -2564,39 +2652,44 @@ Status Codes: **Example request**: - GET /networks/f995e41e471c833266786a64df584fbe4dc654ac99f63a4ee7495842aa093fc4 HTTP/1.1 + GET /networks/f2de39df4171b0dc801e8002d1d999b77256983dfc63041c0f34030aa3977566 HTTP/1.1 **Example response**: - HTTP/1.1 200 OK - Content-Type: application/json - ``` - { - "name": "bridge", - "id": "f995e41e471c833266786a64df584fbe4dc654ac99f63a4ee7495842aa093fc4", - "driver": "bridge", - "containers": { - "931d29e96e63022a3691f55ca18b28600239acf53878451975f77054b05ba559": { - "endpoint": "aa79321e2899e6d72fcd46e6a4ad7f81ab9a19c3b06e384ef4ce51fea35827f9", - "mac_address": "02:42:ac:11:00:04", - "ipv4_address": "172.17.0.4/16", - "ipv6_address": "" - }, - "961249b4ae6c764b11eed923e8463c102689111fffd933627b2e7e359c7d0f7c": { - "endpoint": "4f62c5aea6b9a70512210be7db976bd4ec2cdba47125e4fe514d18c81b1624b1", - "mac_address": "02:42:ac:11:00:02", - "ipv4_address": "172.17.0.2/16", - "ipv6_address": "" - }, - "9f6e0fec4449f42a173ed85be96dc2253b6719edd850d8169bc31bdc45db675c": { - "endpoint": "352b512a5bccdfc77d16c2c04d04408e718f879a16f9ce3913a4733139e4f98d", - "mac_address": "02:42:ac:11:00:03", - "ipv4_address": "172.17.0.3/16", - "ipv6_address": "" +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "Name": "bridge", + "Id": "f2de39df4171b0dc801e8002d1d999b77256983dfc63041c0f34030aa3977566", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [ + { + "Subnet": "172.17.0.0/16" } + ] + }, + "Containers": { + "39b69226f9d79f5634485fb236a23b2fe4e96a0a94128390a7fbbcc167065867": { + "EndpointID": "ed2419a97c1d9954d05b46e462e7002ea552f216e9b136b80a7db8d98b442eda", + "MacAddress": "02:42:ac:11:00:02", + "IPv4Address": "172.17.0.2/16", + "IPv6Address": "" } + }, + "Options": { + "com.docker.network.bridge.default_bridge": "true", + "com.docker.network.bridge.enable_icc": "true", + "com.docker.network.bridge.enable_ip_masquerade": "true", + "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", + "com.docker.network.bridge.name": "docker0", + "com.docker.network.driver.mtu": "1500" } +} ``` Status Codes: @@ -2612,26 +2705,32 @@ Create a network **Example request**: - POST /networks/create HTTP/1.1 - Content-Type: application/json - ``` - { - "name":"isolated_nw", - "driver":"bridge" - } +POST /networks/create HTTP/1.1 +Content-Type: application/json + +{ + "Name":"isolated_nw", + "Driver":"bridge" + "IPAM":{ + "Config":[{ + "Subnet":"172.20.0.0/16", + "IPRange":"172.20.10.0/24", + "Gateway":"172.20.10.11" + }] +} ``` **Example response**: - HTTP/1.1 201 Created - Content-Type: application/json - ``` - { - "id": "22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30", - "warning": "" - } +HTTP/1.1 201 Created +Content-Type: application/json + +{ + "Id": "22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30", + "Warning": "" +} ``` Status Codes: @@ -2642,10 +2741,11 @@ Status Codes: JSON Parameters: -- **name** - The new network's name. this is a mandatory field -- **driver** - Name of the network driver to use. Defaults to `bridge` driver -- **options** - Network specific options to be used by the drivers -- **check_duplicate** - Requests daemon to check for networks with same name +- **Name** - The new network's name. this is a mandatory field +- **Driver** - Name of the network driver to use. Defaults to `bridge` driver +- **IPAM** - Optional custom IP scheme for the network +- **Options** - Network specific options to be used by the drivers +- **CheckDuplicate** - Requests daemon to check for networks with same name ### Connect a container to a network @@ -2655,18 +2755,18 @@ Connects a container to a network **Example request**: - POST /networks/22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30/connect HTTP/1.1 - Content-Type: application/json - ``` - { - "container":"3613f73ba0e4" - } +POST /networks/22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30/connect HTTP/1.1 +Content-Type: application/json + +{ + "Container":"3613f73ba0e4" +} ``` **Example response**: - HTTP/1.1 200 OK + HTTP/1.1 200 OK Status Codes: @@ -2685,18 +2785,18 @@ Disconnects a container from a network **Example request**: - POST /networks/22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30/disconnect HTTP/1.1 - Content-Type: application/json - ``` - { - "container":"3613f73ba0e4" - } +POST /networks/22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30/disconnect HTTP/1.1 +Content-Type: application/json + +{ + "Container":"3613f73ba0e4" +} ``` **Example response**: - HTTP/1.1 200 OK + HTTP/1.1 200 OK Status Codes: @@ -2705,7 +2805,7 @@ Status Codes: JSON Parameters: -- **container** - container-id/name to be disconnected from a network +- **Container** - container-id/name to be disconnected from a network ### Remove a network @@ -2715,11 +2815,11 @@ Instruct the driver to remove the network (`id`). **Example request**: - DELETE /networks/22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30 HTTP/1.1 + DELETE /networks/22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30 HTTP/1.1 **Example response**: - HTTP/1.1 204 No Content + HTTP/1.1 204 No Content Status Codes @@ -2764,7 +2864,7 @@ from **200 OK** to **101 UPGRADED** and resends the same headers. ## 3.3 CORS Requests -To set cross origin requests to the remote api please give values to +To set cross origin requests to the remote api please give values to `--api-cors-header` when running Docker in daemon mode. Set * (asterisk) allows all, default or blank means CORS disabled diff --git a/docs/reference/builder.md b/docs/reference/builder.md index 4a1050a86..27fd822b9 100644 --- a/docs/reference/builder.md +++ b/docs/reference/builder.md @@ -43,7 +43,7 @@ Dockerfile. >**Warning**: Do not use your root directory, `/`, as the `PATH` as it causes >the build to transfer the entire contents of your hard drive to the Docker ->daemon. +>daemon. To use a file in the build context, the `Dockerfile` refers to the file specified in an instruction, for example, a `COPY` instruction. To increase the build's @@ -78,18 +78,20 @@ the `Using cache` message in the console output. (For more information, see the [Build cache section](../articles/dockerfile_best-practices.md#build-cache)) in the `Dockerfile` best practices guide: - $ docker build -t SvenDowideit/ambassador . - Uploading context 10.24 kB - Uploading context - Step 1 : FROM docker-ut - ---> cbba202fe96b - Step 2 : MAINTAINER SvenDowideit@home.org.au + $ docker build -t svendowideit/ambassador . + Sending build context to Docker daemon 15.36 kB + Step 0 : FROM alpine:3.2 + ---> 31f630c65071 + Step 1 : MAINTAINER SvenDowideit@home.org.au ---> Using cache - ---> 51182097be13 - Step 3 : CMD env | grep _TCP= | sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' | sh && top + ---> 2a1c91448f5f + Step 2 : RUN apk update && apk add socat && rm -r /var/cache/ ---> Using cache - ---> 1a5ffc17324d - Successfully built 1a5ffc17324d + ---> 21ed6e7fbb73 + Step 3 : CMD env | grep _TCP= | sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \& wait/' | sh + ---> Using cache + ---> 7ea8aef582cc + Successfully built 7ea8aef582cc When you're done with your build, you're ready to look into [*Pushing a repository to its registry*](../userguide/dockerrepos.md#contributing-to-docker-hub). @@ -152,7 +154,7 @@ Example (parsed representation is displayed after the `#`): ADD . $foo # ADD . /bar COPY \$foo /quux # COPY $foo /quux -Environment variables are supported by the following list of instructions in +Environment variables are supported by the following list of instructions in the `Dockerfile`: * `ADD` @@ -170,7 +172,7 @@ as well as: * `ONBUILD` (when combined with one of the supported instructions above) > **Note**: -> prior to 1.4, `ONBUILD` instructions did **NOT** support environment +> prior to 1.4, `ONBUILD` instructions did **NOT** support environment > variable, even when combined with any of the instructions listed above. Environment variable substitution will use the same value for each variable @@ -180,7 +182,7 @@ throughout the entire command. In other words, in this example: ENV abc=bye def=$abc ENV ghi=$abc -will result in `def` having a value of `hello`, not `bye`. However, +will result in `def` having a value of `hello`, not `bye`. However, `ghi` will have a value of `bye` because it is not part of the same command that set `abc` to `bye`. @@ -347,13 +349,13 @@ RUN /bin/bash -c 'source $HOME/.bashrc ; echo $HOME' > Unlike the *shell* form, the *exec* form does not invoke a command shell. > This means that normal shell processing does not happen. For example, > `RUN [ "echo", "$HOME" ]` will not do variable substitution on `$HOME`. -> If you want shell processing then either use the *shell* form or execute +> If you want shell processing then either use the *shell* form or execute > a shell directly, for example: `RUN [ "sh", "-c", "echo", "$HOME" ]`. The cache for `RUN` instructions isn't invalidated automatically during -the next build. The cache for an instruction like -`RUN apt-get dist-upgrade -y` will be reused during the next build. The -cache for `RUN` instructions can be invalidated by using the `--no-cache` +the next build. The cache for an instruction like +`RUN apt-get dist-upgrade -y` will be reused during the next build. The +cache for `RUN` instructions can be invalidated by using the `--no-cache` flag, for example `docker build --no-cache`. See the [`Dockerfile` Best Practices @@ -392,8 +394,8 @@ the executable, in which case you must specify an `ENTRYPOINT` instruction as well. > **Note**: -> If `CMD` is used to provide default arguments for the `ENTRYPOINT` -> instruction, both the `CMD` and `ENTRYPOINT` instructions should be specified +> If `CMD` is used to provide default arguments for the `ENTRYPOINT` +> instruction, both the `CMD` and `ENTRYPOINT` instructions should be specified > with the JSON array format. > **Note**: @@ -404,7 +406,7 @@ instruction as well. > Unlike the *shell* form, the *exec* form does not invoke a command shell. > This means that normal shell processing does not happen. For example, > `CMD [ "echo", "$HOME" ]` will not do variable substitution on `$HOME`. -> If you want shell processing then either use the *shell* form or execute +> If you want shell processing then either use the *shell* form or execute > a shell directly, for example: `CMD [ "sh", "-c", "echo", "$HOME" ]`. When used in the shell or exec formats, the `CMD` instruction sets the command @@ -454,7 +456,7 @@ An image can have more than one label. To specify multiple labels, Docker recommends combining labels into a single `LABEL` instruction where possible. Each `LABEL` instruction produces a new layer which can result in an inefficient image if you use many labels. This example results in a single image -layer. +layer. LABEL multi.label1="value1" multi.label2="value2" other="value3" @@ -463,7 +465,7 @@ The above can also be written as: LABEL multi.label1="value1" \ multi.label2="value2" \ other="value3" - + Labels are additive including `LABEL`s in `FROM` images. If Docker encounters a label/key that already exists, the new value overrides any previous labels with identical keys. @@ -487,12 +489,15 @@ To view an image's labels, use the `docker inspect` command. The `EXPOSE` instruction informs Docker that the container listens on the specified network ports at runtime. `EXPOSE` does not make the ports of the container accessible to the host. To do that, you must use either the `-p` flag -to publish a range of ports or the `-P` flag to publish all of the exposed ports. -You can expose one port number and publish it externally under another number. - -Docker uses exposed and published ports to interconnect containers using links -(see [Linking containers together](../userguide/dockerlinks.md)) -and to set up port redirection on the host system when [using the -P flag](run.md#expose-incoming-ports). +to publish a range of ports or the `-P` flag to publish all of the exposed +ports. You can expose one port number and publish it externally under another +number. + +To set up port redirection on the host system, see [using the -P +flag](run.md#expose-incoming-ports). The Docker network feature supports +creating networks without the need to expose ports within the network, for +detailed information see the [overview of this +feature](../userguide/networking/index.md)). ## ENV @@ -500,17 +505,18 @@ and to set up port redirection on the host system when [using the -P flag](run.m ENV = ... The `ENV` instruction sets the environment variable `` to the value -``. This value will be in the environment of all "descendent" `Dockerfile` -commands and can be [replaced inline](#environment-replacement) in many as well. +``. This value will be in the environment of all "descendent" +`Dockerfile` commands and can be [replaced inline](#environment-replacement) in +many as well. The `ENV` instruction has two forms. The first form, `ENV `, will set a single variable to a value. The entire string after the first -space will be treated as the `` - including characters such as +space will be treated as the `` - including characters such as spaces and quotes. -The second form, `ENV = ...`, allows for multiple variables to -be set at one time. Notice that the second form uses the equals sign (=) -in the syntax, while the first form does not. Like command line parsing, +The second form, `ENV = ...`, allows for multiple variables to +be set at one time. Notice that the second form uses the equals sign (=) +in the syntax, while the first form does not. Like command line parsing, quotes and backslashes can be used to include spaces within values. For example: @@ -524,7 +530,7 @@ and ENV myDog Rex The Dog ENV myCat fluffy -will yield the same net results in the final container, but the first form +will yield the same net results in the final container, but the first form is preferred because it produces a single cache layer. The environment variables set using `ENV` will persist when a container is run @@ -548,8 +554,8 @@ whitespace) The `ADD` instruction copies new files, directories or remote file URLs from `` and adds them to the filesystem of the container at the path ``. -Multiple `` resource may be specified but if they are files or -directories then they must be relative to the source directory that is +Multiple `` resource may be specified but if they are files or +directories then they must be relative to the source directory that is being built (the context of the build). Each `` may contain wildcards and matching will be done using Go's @@ -612,8 +618,8 @@ guide](../articles/dockerfile_best-practices.md#build-cache) for more informatio appropriate filename can be discovered in this case (`http://example.com` will not work). -- If `` is a directory, the entire contents of the directory are copied, - including filesystem metadata. +- If `` is a directory, the entire contents of the directory are copied, + including filesystem metadata. > **Note**: > The directory itself is not copied, just its contents. @@ -633,7 +639,7 @@ guide](../articles/dockerfile_best-practices.md#build-cache) for more informatio at `/base()`. - If multiple `` resources are specified, either directly or due to the - use of a wildcard, then `` must be a directory, and it must end with + use of a wildcard, then `` must be a directory, and it must end with a slash `/`. - If `` does not end with a trailing slash, it will be considered a @@ -681,8 +687,8 @@ All new files and directories are created with a UID and GID of 0. `docker build` is to send the context directory (and subdirectories) to the docker daemon. -- If `` is a directory, the entire contents of the directory are copied, - including filesystem metadata. +- If `` is a directory, the entire contents of the directory are copied, + including filesystem metadata. > **Note**: > The directory itself is not copied, just its contents. @@ -693,7 +699,7 @@ All new files and directories are created with a UID and GID of 0. at `/base()`. - If multiple `` resources are specified, either directly or due to the - use of a wildcard, then `` must be a directory, and it must end with + use of a wildcard, then `` must be a directory, and it must end with a slash `/`. - If `` does not end with a trailing slash, it will be considered a @@ -722,7 +728,7 @@ Command line arguments to `docker run ` will be appended after all elements in an *exec* form `ENTRYPOINT`, and will override all elements specified using `CMD`. This allows arguments to be passed to the entry point, i.e., `docker run -d` -will pass the `-d` argument to the entry point. +will pass the `-d` argument to the entry point. You can override the `ENTRYPOINT` instruction using the `docker run --entrypoint` flag. @@ -753,10 +759,10 @@ When you run the container, you can see that `top` is the only process: %Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem: 2056668 total, 1616832 used, 439836 free, 99352 buffers KiB Swap: 1441840 total, 0 used, 1441840 free. 1324440 cached Mem - + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1 root 20 0 19744 2336 2080 R 0.0 0.1 0:00.04 top - + To examine the result further, you can use `docker exec`: $ docker exec -it test ps aux @@ -860,7 +866,7 @@ sys 0m 0.03s > Unlike the *shell* form, the *exec* form does not invoke a command shell. > This means that normal shell processing does not happen. For example, > `ENTRYPOINT [ "echo", "$HOME" ]` will not do variable substitution on `$HOME`. -> If you want shell processing then either use the *shell* form or execute +> If you want shell processing then either use the *shell* form or execute > a shell directly, for example: `ENTRYPOINT [ "sh", "-c", "echo", "$HOME" ]`. > Variables that are defined in the `Dockerfile`using `ENV`, will be substituted by > the `Dockerfile` parser. @@ -934,12 +940,12 @@ and marks it as holding externally mounted volumes from native host or other containers. The value can be a JSON array, `VOLUME ["/var/log/"]`, or a plain string with multiple arguments, such as `VOLUME /var/log` or `VOLUME /var/log /var/db`. For more information/examples and mounting instructions via the -Docker client, refer to +Docker client, refer to [*Share Directories via Volumes*](../userguide/dockervolumes.md#mount-a-host-directory-as-a-data-volume) documentation. -The `docker run` command initializes the newly created volume with any data -that exists at the specified location within the base image. For example, +The `docker run` command initializes the newly created volume with any data +that exists at the specified location within the base image. For example, consider the following Dockerfile snippet: FROM ubuntu @@ -948,7 +954,7 @@ consider the following Dockerfile snippet: VOLUME /myvol This Dockerfile results in an image that causes `docker run`, to -create a new mount point at `/myvol` and copy the `greeting` file +create a new mount point at `/myvol` and copy the `greeting` file into the newly created volume. > **Note**: diff --git a/docs/reference/commandline/attach.md b/docs/reference/commandline/attach.md index 04a44cdd6..d746190d6 100644 --- a/docs/reference/commandline/attach.md +++ b/docs/reference/commandline/attach.md @@ -22,7 +22,7 @@ The `docker attach` command allows you to attach to a running container using the container's ID or name, either to view its ongoing output or to control it interactively. You can attach to the same contained process multiple times simultaneously, screen sharing style, or quickly view the progress of your -daemonized process. +detached process. You can detach from the container and leave it running with `CTRL-p CTRL-q` (for a quiet exit) or with `CTRL-c` if `--sig-proxy` is false. diff --git a/docs/reference/commandline/build.md b/docs/reference/commandline/build.md index 94d2cc764..23a34581f 100644 --- a/docs/reference/commandline/build.md +++ b/docs/reference/commandline/build.md @@ -15,7 +15,7 @@ parent = "smn_cli" Build a new image from the source code at PATH --build-arg=[] Set build-time variables - -c, --cpu-shares CPU Shares (relative weight) + --cpu-shares CPU Shares (relative weight) --cgroup-parent="" Optional parent cgroup for the container --cpu-period=0 Limit the CPU CFS (Completely Fair Scheduler) period --cpu-quota=0 Limit the CPU CFS (Completely Fair Scheduler) quota @@ -128,6 +128,8 @@ See also: ## Examples +### Build with PATH + $ docker build . Uploading context 10240 bytes Step 1 : FROM busybox @@ -168,6 +170,31 @@ The transfer of context from the local machine to the Docker daemon is what the If you wish to keep the intermediate containers after the build is complete, you must use `--rm=false`. This does not affect the build cache. +### Build with URL + + $ docker build github.com/creack/docker-firefox + +This will clone the GitHub repository and use the cloned repository as context. +The Dockerfile at the root of the repository is used as Dockerfile. Note that +you can specify an arbitrary Git repository by using the `git://` or `git@` +schema. + +### Build with - + + $ docker build - < Dockerfile + +This will read a Dockerfile from `STDIN` without context. Due to the lack of a +context, no contents of any local directory will be sent to the Docker daemon. +Since there is no context, a Dockerfile `ADD` only works if it refers to a +remote URL. + + $ docker build - < context.tar.gz + +This will build an image for a compressed context read from `STDIN`. Supported +formats are: bzip2, gzip and xz. + +### Usage of .dockerignore + $ docker build . Uploading context 18.829 MB Uploading context @@ -193,29 +220,14 @@ directory from the context. Its effect can be seen in the changed size of the uploaded context. The builder reference contains detailed information on [creating a .dockerignore file](../builder.md#dockerignore-file) +### Tag image (-t) + $ docker build -t vieux/apache:2.0 . This will build like the previous example, but it will then tag the resulting image. The repository name will be `vieux/apache` and the tag will be `2.0` - $ docker build - < Dockerfile - -This will read a Dockerfile from `STDIN` without context. Due to the lack of a -context, no contents of any local directory will be sent to the Docker daemon. -Since there is no context, a Dockerfile `ADD` only works if it refers to a -remote URL. - - $ docker build - < context.tar.gz - -This will build an image for a compressed context read from `STDIN`. Supported -formats are: bzip2, gzip and xz. - - $ docker build github.com/creack/docker-firefox - -This will clone the GitHub repository and use the cloned repository as context. -The Dockerfile at the root of the repository is used as Dockerfile. Note that -you can specify an arbitrary Git repository by using the `git://` or `git@` -schema. +### Specify Dockerfile (-f) $ docker build -f Dockerfile.debug . @@ -248,14 +260,20 @@ the command line. > repeatable builds on remote Docker hosts. This is also the reason why > `ADD ../file` will not work. +### Optional parent cgroup (--cgroup-parent) + When `docker build` is run with the `--cgroup-parent` option the containers used in the build will be run with the [corresponding `docker run` flag](../run.md#specifying-custom-cgroups). +### Set ulimits in container (--ulimit) + Using the `--ulimit` option with `docker build` will cause each build step's container to be started using those [`--ulimit` flag values](../run.md#setting-ulimits-in-a-container). +### Set build-time variables (--build-arg) + You can use `ENV` instructions in a Dockerfile to define variable values. These values persist in the built image. However, often persistence is not what you want. Users want to specify variables differently @@ -263,7 +281,7 @@ depending on which host they build an image on. A good example is `http_proxy` or source versions for pulling intermediate files. The `ARG` instruction lets Dockerfile authors define values that users -can set at build-time using the `---build-arg` flag: +can set at build-time using the `--build-arg` flag: $ docker build --build-arg HTTP_PROXY=http://10.20.30.2:1234 . diff --git a/docs/reference/commandline/cli.md b/docs/reference/commandline/cli.md index 2eba7636e..78d1963d3 100644 --- a/docs/reference/commandline/cli.md +++ b/docs/reference/commandline/cli.md @@ -121,7 +121,7 @@ To list the help on any command just execute the command, followed by the Run a command in a new container -a, --attach=[] Attach to STDIN, STDOUT or STDERR - -c, --cpu-shares=0 CPU shares (relative weight) + --cpu-shares=0 CPU shares (relative weight) ... ## Option types diff --git a/docs/reference/commandline/create.md b/docs/reference/commandline/create.md index 3324fc281..723bc90a0 100644 --- a/docs/reference/commandline/create.md +++ b/docs/reference/commandline/create.md @@ -19,7 +19,7 @@ Creates a new container. -a, --attach=[] Attach to STDIN, STDOUT or STDERR --add-host=[] Add a custom host-to-IP mapping (host:ip) --blkio-weight=0 Block IO weight (relative weight) - -c, --cpu-shares=0 CPU shares (relative weight) + --cpu-shares=0 CPU shares (relative weight) --cap-add=[] Add Linux capabilities --cap-drop=[] Drop Linux capabilities --cgroup-parent="" Optional parent cgroup for the container diff --git a/docs/reference/commandline/daemon.md b/docs/reference/commandline/daemon.md index 7dc232303..91fd3c6cf 100644 --- a/docs/reference/commandline/daemon.md +++ b/docs/reference/commandline/daemon.md @@ -23,7 +23,7 @@ weight = -1 --default-gateway="" Container default gateway IPv4 address --default-gateway-v6="" Container default gateway IPv6 address --cluster-store="" URL of the distributed storage backend - --cluster-advertise="" Address of the daemon instance to advertise + --cluster-advertise="" Address of the daemon instance on the cluster --cluster-store-opt=map[] Set cluster options --dns=[] DNS server to use --dns-opt=[] DNS options to use @@ -205,9 +205,10 @@ options for `zfs` start with `zfs`. Example use: - docker daemon --storage-opt dm.thinpooldev=/dev/mapper/thin-pool + $ docker daemon \ + --storage-opt dm.thinpooldev=/dev/mapper/thin-pool - * `dm.basesize` +* `dm.basesize` Specifies the size to use when creating the base device, which limits the size of images and containers. The default value is 100G. Note, thin devices @@ -227,9 +228,11 @@ options for `zfs` start with `zfs`. $ docker daemon --storage-opt dm.basesize=20G - * `dm.loopdatasize` +* `dm.loopdatasize` - >**Note**: This option configures devicemapper loopback, which should not be used in production. + > **Note**: + > This option configures devicemapper loopback, which should not + > be used in production. Specifies the size to use when creating the loopback file for the "data" device which is used for the thin pool. The default size is @@ -240,9 +243,11 @@ options for `zfs` start with `zfs`. $ docker daemon --storage-opt dm.loopdatasize=200G - * `dm.loopmetadatasize` +* `dm.loopmetadatasize` - >**Note**: This option configures devicemapper loopback, which should not be used in production. + > **Note**: + > This option configures devicemapper loopback, which should not + > be used in production. Specifies the size to use when creating the loopback file for the "metadata" device which is used for the thin pool. The default size @@ -253,7 +258,7 @@ options for `zfs` start with `zfs`. $ docker daemon --storage-opt dm.loopmetadatasize=4G - * `dm.fs` +* `dm.fs` Specifies the filesystem type to use for the base device. The supported options are "ext4" and "xfs". The default is "ext4" @@ -262,7 +267,7 @@ options for `zfs` start with `zfs`. $ docker daemon --storage-opt dm.fs=xfs - * `dm.mkfsarg` +* `dm.mkfsarg` Specifies extra mkfs arguments to be used when creating the base device. @@ -270,7 +275,7 @@ options for `zfs` start with `zfs`. $ docker daemon --storage-opt "dm.mkfsarg=-O ^has_journal" - * `dm.mountopt` +* `dm.mountopt` Specifies extra mount options used when mounting the thin devices. @@ -278,7 +283,7 @@ options for `zfs` start with `zfs`. $ docker daemon --storage-opt dm.mountopt=nodiscard - * `dm.datadev` +* `dm.datadev` (Deprecated, use `dm.thinpooldev`) @@ -290,9 +295,11 @@ options for `zfs` start with `zfs`. Example use: - $ docker daemon --storage-opt dm.datadev=/dev/sdb1 --storage-opt dm.metadatadev=/dev/sdc1 + $ docker daemon \ + --storage-opt dm.datadev=/dev/sdb1 \ + --storage-opt dm.metadatadev=/dev/sdc1 - * `dm.metadatadev` +* `dm.metadatadev` (Deprecated, use `dm.thinpooldev`) @@ -304,13 +311,15 @@ options for `zfs` start with `zfs`. If setting up a new metadata pool it is required to be valid. This can be achieved by zeroing the first 4k to indicate empty metadata, like this: - $ dd if=/dev/zero of=$metadata_dev bs=4096 count=1 + $ dd if=/dev/zero of=$metadata_dev bs=4096 count=1 Example use: - $ docker daemon --storage-opt dm.datadev=/dev/sdb1 --storage-opt dm.metadatadev=/dev/sdc1 + $ docker daemon \ + --storage-opt dm.datadev=/dev/sdb1 \ + --storage-opt dm.metadatadev=/dev/sdc1 - * `dm.blocksize` +* `dm.blocksize` Specifies a custom blocksize to use for the thin pool. The default blocksize is 64K. @@ -319,7 +328,7 @@ options for `zfs` start with `zfs`. $ docker daemon --storage-opt dm.blocksize=512K - * `dm.blkdiscard` +* `dm.blkdiscard` Enables or disables the use of blkdiscard when removing devicemapper devices. This is enabled by default (only) if using loopback devices and is @@ -333,7 +342,7 @@ options for `zfs` start with `zfs`. $ docker daemon --storage-opt dm.blkdiscard=false - * `dm.override_udev_sync_check` +* `dm.override_udev_sync_check` Overrides the `udev` synchronization checks between `devicemapper` and `udev`. `udev` is the device manager for the Linux kernel. @@ -369,7 +378,7 @@ options for `zfs` start with `zfs`. > Otherwise, set this flag for migrating existing Docker daemons to > a daemon with a supported environment. - * `dm.use_deferred_removal` +* `dm.use_deferred_removal` Enables use of deferred device removal if `libdm` and the kernel driver support the mechanism. @@ -385,21 +394,25 @@ options for `zfs` start with `zfs`. system to schedule the device for deferred removal. It does not wait in a loop trying to remove a busy device. - Example use: `docker daemon --storage-opt dm.use_deferred_removal=true` + Example use: - * `dm.use_deferred_deletion` + $ docker daemon --storage-opt dm.use_deferred_removal=true + +* `dm.use_deferred_deletion` Enables use of deferred device deletion for thin pool devices. By default, thin pool device deletion is synchronous. Before a container is deleted, the Docker daemon removes any associated devices. If the storage driver can not remove a device, the container deletion fails and daemon returns. - `Error deleting container: Error response from daemon: Cannot destroy container` + Error deleting container: Error response from daemon: Cannot destroy container To avoid this failure, enable both deferred device deletion and deferred device removal on the daemon. - `docker daemon --storage-opt dm.use_deferred_deletion=true --storage-opt dm.use_deferred_removal=true` + $ docker daemon \ + --storage-opt dm.use_deferred_deletion=true \ + --storage-opt dm.use_deferred_removal=true With these two options enabled, if a device is busy when the driver is deleting a container, the driver marks the device as deleted. Later, when @@ -411,7 +424,7 @@ options for `zfs` start with `zfs`. Currently supported options of `zfs`: - * `zfs.fsname` +* `zfs.fsname` Set zfs filesystem under which docker will create its own datasets. By default docker will pick up the zfs filesystem where docker graph @@ -534,13 +547,16 @@ please check the [run](run.md) reference. ## Nodes discovery -`--cluster-advertise` specifies the 'host:port' combination that this particular -daemon instance should use when advertising itself to the cluster. The daemon -is reached by remote hosts on this 'host:port' combination. +The `--cluster-advertise` option specifies the 'host:port' or `interface:port` +combination that this particular daemon instance should use when advertising +itself to the cluster. The daemon is reached by remote hosts through this value. +If you specify an interface, make sure it includes the IP address of the actual +Docker host. For Engine installation created through `docker-machine`, the +interface is typically `eth1`. The daemon uses [libkv](https://github.com/docker/libkv/) to advertise -the node within the cluster. Some Key/Value backends support mutual -TLS, and the client TLS settings used by the daemon can be configured +the node within the cluster. Some key-value backends support mutual +TLS. To configure the client TLS settings used by the daemon can be configured using the `--cluster-store-opt` flag, specifying the paths to PEM encoded files. For example: diff --git a/docs/reference/commandline/info.md b/docs/reference/commandline/info.md index 446b68697..1794df40b 100644 --- a/docs/reference/commandline/info.md +++ b/docs/reference/commandline/info.md @@ -22,7 +22,7 @@ For example: $ docker -D info Containers: 14 Images: 52 - Engine Version: 1.9.0 + Server Version: 1.9.0 Storage Driver: aufs Root Dir: /var/lib/docker/aufs Backing Filesystem: extfs diff --git a/docs/reference/commandline/inspect.md b/docs/reference/commandline/inspect.md index e9aea381f..6620163f7 100644 --- a/docs/reference/commandline/inspect.md +++ b/docs/reference/commandline/inspect.md @@ -33,14 +33,14 @@ describes all the details of the format. For the most part, you can pick out any field from the JSON in a fairly straightforward manner. - $ docker inspect --format='{{.NetworkSettings.IPAddress}}' $INSTANCE_ID + $ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $INSTANCE_ID **Get an instance's MAC Address:** For the most part, you can pick out any field from the JSON in a fairly straightforward manner. - $ docker inspect --format='{{.NetworkSettings.MacAddress}}' $INSTANCE_ID + $ docker inspect '{{range .NetworkSettings.Networks}}{{.MacAddress}}{{end}}' $INSTANCE_ID **Get an instance's log path:** @@ -58,7 +58,7 @@ output: The `.Field` syntax doesn't work when the field name begins with a number, but the template language's `index` function does. The `.NetworkSettings.Ports` section contains a map of the internal port -mappings to a list of external address/port objects, so to grab just the +mappings to a list of external address/port objects. To grab just the numeric public port, you use `index` to find the specific port map, and then `index` 0 contains the first object inside of that. Then we ask for the `HostPort` field to get the public address. diff --git a/docs/reference/commandline/kill.md b/docs/reference/commandline/kill.md index e25c89922..dabbabbe7 100644 --- a/docs/reference/commandline/kill.md +++ b/docs/reference/commandline/kill.md @@ -19,3 +19,8 @@ parent = "smn_cli" The main process inside the container will be sent `SIGKILL`, or any signal specified with option `--signal`. + +> **Note:** +> `ENTRYPOINT` and `CMD` in the *shell* form run as a subcommand of `/bin/sh -c`, +> which does not pass signals. This means that the executable is not the container’s PID 1 +> and does not receive Unix signals. diff --git a/docs/reference/commandline/network_connect.md b/docs/reference/commandline/network_connect.md index dbde67df0..b3b577345 100644 --- a/docs/reference/commandline/network_connect.md +++ b/docs/reference/commandline/network_connect.md @@ -2,7 +2,7 @@ +++ title = "network connect" description = "The network connect command description and usage" -keywords = ["network, connect"] +keywords = ["network, connect, user-defined"] [menu.main] parent = "smn_cli" +++ @@ -16,14 +16,40 @@ parent = "smn_cli" --help=false Print usage -Connects a running container to a network. This enables instant communication with other containers belonging to the same network. +Connects a running container to a network. You can connect a container by name +or by ID. Once connected, the container can communicate with other containers in +the same network. -``` - $ docker network create -d overlay multi-host-network - $ docker run -d --name=container1 busybox top - $ docker network connect multi-host-network container1 +```bash +$ docker network connect multi-host-network container1 ``` -the container will be connected to the network that is created and managed by the driver (multi-host overlay driver in the above example) or external network plugins. +You can also use the `docker run --net=` option to start a container and immediately connect it to a network. -Multiple containers can be connected to the same network and the containers in the same network will start to communicate with each other. If the driver/plugin supports multi-host connectivity, then the containers connected to the same multi-host network will be able to communicate seamlessly. +```bash +$ docker run -itd --net=multi-host-network busybox +``` + +You can pause, restart, and stop containers that are connected to a network. +Paused containers remain connected and a revealed by a `network inspect`. When +the container is stopped, it does not appear on the network until you restart +it. The container's IP address is not guaranteed to remain the same when a +stopped container rejoins the network. + +To verify the container is connected, use the `docker network inspect` command. Use `docker network disconnect` to remove a container from the network. + +Once connected in network, containers can communicate using only another +container's IP address or name. For `overlay` networks or custom plugins that +support multi-host connectivity, containers connected to the same multi-host +network but launched from different Engines can also communicate in this way. + +You can connect a container to one or more networks. The networks need not be the same type. For example, you can connect a single container bridge and overlay networks. + +## Related information + +* [network inspect](network_inspect.md) +* [network create](network_create.md) +* [network disconnect](network_disconnect.md) +* [network ls](network_ls.md) +* [network rm](network_rm.md) +* [Understand Docker container networks](../../userguide/networking/dockernetworks.md) diff --git a/docs/reference/commandline/network_create.md b/docs/reference/commandline/network_create.md index 62302b886..4b794d894 100644 --- a/docs/reference/commandline/network_create.md +++ b/docs/reference/commandline/network_create.md @@ -14,18 +14,117 @@ parent = "smn_cli" Creates a new network with a name specified by the user - -d, --driver= Driver to manage the Network - --help=false Print usage + --aux-address=map[] Auxiliary ipv4 or ipv6 addresses used by network driver + -d --driver=DRIVER Driver to manage the Network bridge or overlay. The default is bridge. + --gateway=[] ipv4 or ipv6 Gateway for the master subnet + --help=false Print usage + --ip-range=[] Allocate container ip from a sub-range + --ipam-driver=default IP Address Management Driver + -o --opt=map[] Set custom network plugin options + --subnet=[] Subnet in CIDR format that represents a network segment -Creates a new network that containers can connect to. If the driver supports multi-host networking, the created network will be made available across all the hosts in the cluster. Daemon will do its best to identify network name conflicts. But its the users responsibility to make sure network name is unique across the cluster. You create a network and then configure the container to use it, for example: +Creates a new network. The `DRIVER` accepts `bridge` or `overlay` which are the +built-in network drivers. If you have installed a third party or your own custom +network driver you can specify that `DRIVER` here also. If you don't specify the +`--driver` option, the command automatically creates a `bridge` network for you. +When you install Docker Engine it creates a `bridge` network automatically. This +network corresponds to the `docker0` bridge that Engine has traditionally relied +on. When launch a new container with `docker run` it automatically connects to +this bridge network. You cannot remove this default bridge network but you can +create new ones using the `network create` command. -``` - $ docker network create -d overlay multi-host-network - $ docker run -itd --net=multi-host-network busybox +```bash +$ docker network create -d bridge my-bridge-network ``` -the container will be connected to the network that is created and managed by the driver (multi-host overlay driver in the above example) or external network plugins. +Bridge networks are isolated networks on a single Engine installation. If you +want to create a network that spans multiple Docker hosts each running an +Engine, you must create an `overlay` network. Unlike `bridge` networks overlay +networks require some pre-existing conditions before you can create one. These +conditions are: -Multiple containers can be connected to the same network and the containers in the same network will start to communicate with each other. If the driver/plugin supports multi-host connectivity, then the containers connected to the same multi-host network will be able to communicate seamlessly. +* Access to a key-value store. Engine supports Consul, Etcd, and ZooKeeper (Distributed store) key-value stores. +* A cluster of hosts with connectivity to the key-value store. +* A properly configured Engine `daemon` on each host in the cluster. -*Note*: UX needs enhancement to accept network options to be passed to the drivers +The `docker daemon` options that support the `overlay` network are: + +* `--cluster-store` +* `--cluster-store-opt` +* `--cluster-advertise` + +To read more about these options and how to configure them, see ["*Get started +with multi-host network*"](../../userguide/networking/get-started-overlay.md). + +It is also a good idea, though not required, that you install Docker Swarm on to +manage the cluster that makes up your network. Swarm provides sophisticated +discovery and server management that can assist your implementation. + +Once you have prepared the `overlay` network prerequisites you simply choose a +Docker host in the cluster and issue the following to create the network: + +```bash +$ docker network create -d overlay my-multihost-network +``` + +Network names must be unique. The Docker daemon attempts to identify naming +conflicts but this is not guaranteed. It is the user's responsibility to avoid +name conflicts. + +## Connect containers + +When you start a container use the `--net` flag to connect it to a network. +This adds the `busybox` container to the `mynet` network. + +```bash +$ docker run -itd --net=mynet busybox +``` + +If you want to add a container to a network after the container is already +running use the `docker network connect` subcommand. + +You can connect multiple containers to the same network. Once connected, the +containers can communicate using only another container's IP address or name. +For `overlay` networks or custom plugins that support multi-host connectivity, +containers connected to the same multi-host network but launched from different +Engines can also communicate in this way. + +You can disconnect a container from a network using the `docker network +disconnect` command. + +## Specifying advanced options + +When you create a network, Engine creates a non-overlapping subnetwork for the network by default. This subnetwork is not a subdivision of an existing network. It is purely for ip-addressing purposes. You can override this default and specify subnetwork values directly using the the `--subnet` option. On a `bridge` network you can only create a single subnet: + +```bash +docker network create -d --subnet=192.168.0.0/16 +``` +Additionally, you also specify the `--gateway` `--ip-range` and `--aux-address` options. + +```bash +network create --driver=bridge --subnet=172.28.0.0/16 --ip-range=172.28.5.0/24 --gateway=172.28.5.254 br0 +``` + +If you omit the `--gateway` flag the Engine selects one for you from inside a +preferred pool. For `overlay` networks and for network driver plugins that +support it you can create multiple subnetworks. + +```bash +docker network create -d overlay + --subnet=192.168.0.0/16 --subnet=192.170.0.0/16 + --gateway=192.168.0.100 --gateway=192.170.0.100 + --ip-range=192.168.1.0/24 + --aux-address a=192.168.1.5 --aux-address b=192.168.1.6 + --aux-address a=192.170.1.5 --aux-address b=192.170.1.6 + my-multihost-newtork +``` +Be sure that your subnetworks do not overlap. If they do, the network create fails and Engine returns an error. + +## Related information + +* [network inspect](network_inspect.md) +* [network connect](network_connect.md) +* [network disconnect](network_disconnect.md) +* [network ls](network_ls.md) +* [network rm](network_rm.md) +* [Understand Docker container networks](../../userguide/networking/dockernetworks.md) diff --git a/docs/reference/commandline/network_disconnect.md b/docs/reference/commandline/network_disconnect.md index bbc237203..bbb351d24 100644 --- a/docs/reference/commandline/network_disconnect.md +++ b/docs/reference/commandline/network_disconnect.md @@ -2,7 +2,7 @@ +++ title = "network disconnect" description = "The network disconnect command description and usage" -keywords = ["network, disconnect"] +keywords = ["network, disconnect, user-defined"] [menu.main] parent = "smn_cli" +++ @@ -16,12 +16,18 @@ parent = "smn_cli" --help=false Print usage -Disconnects a running container from a network. +Disconnects a container from a network. The container must be running to disconnect it from the network. -``` - $ docker network create -d overlay multi-host-network - $ docker run -d --net=multi-host-network --name=container1 busybox top +```bash $ docker network disconnect multi-host-network container1 ``` -the container will be disconnected from the network. + +## Related information + +* [network inspect](network_inspect.md) +* [network connect](network_connect.md) +* [network create](network_create.md) +* [network ls](network_ls.md) +* [network rm](network_rm.md) +* [Understand Docker container networks](../../userguide/networking/dockernetworks.md) diff --git a/docs/reference/commandline/network_inspect.md b/docs/reference/commandline/network_inspect.md index b63bf181c..aedf5c41c 100644 --- a/docs/reference/commandline/network_inspect.md +++ b/docs/reference/commandline/network_inspect.md @@ -2,7 +2,7 @@ +++ title = "network inspect" description = "The network inspect command description and usage" -keywords = ["network, inspect"] +keywords = ["network, inspect, user-defined"] [menu.main] parent = "smn_cli" +++ @@ -10,42 +10,55 @@ parent = "smn_cli" # network inspect - Usage: docker network inspect [OPTIONS] NETWORK + Usage: docker network inspect [OPTIONS] NETWORK [NETWORK..] Displays detailed information on a network --help=false Print usage -Returns information about a network. By default, this command renders all results -in a JSON object. +Returns information about one or more networks. By default, this command renders all results in a JSON object. For example, if you connect two containers to a network: -Example output: - -``` +```bash $ sudo docker run -itd --name=container1 busybox f2870c98fd504370fb86e59f32cd0753b1ac9b69b7d80566ffc7192a82b3ed27 $ sudo docker run -itd --name=container2 busybox bda12f8922785d1f160be70736f26c1e331ab8aaf8ed8d56728508f2e2fd4727 - -$ sudo docker network inspect bridge -{ - "name": "bridge", - "id": "7fca4eb8c647e57e9d46c32714271e0c3f8bf8d17d346629e2820547b2d90039", - "driver": "bridge", - "containers": { - "bda12f8922785d1f160be70736f26c1e331ab8aaf8ed8d56728508f2e2fd4727": { - "endpoint": "e0ac95934f803d7e36384a2029b8d1eeb56cb88727aa2e8b7edfeebaa6dfd758", - "mac_address": "02:42:ac:11:00:03", - "ipv4_address": "172.17.0.3/16", - "ipv6_address": "" - }, - "f2870c98fd504370fb86e59f32cd0753b1ac9b69b7d80566ffc7192a82b3ed27": { - "endpoint": "31de280881d2a774345bbfb1594159ade4ae4024ebfb1320cb74a30225f6a8ae", - "mac_address": "02:42:ac:11:00:02", - "ipv4_address": "172.17.0.2/16", - "ipv6_address": "" - } - } -} ``` + +The `network inspect` command shows the containers, by id, in its results. + +```bash +$ sudo docker network inspect bridge +[ + { + "name": "bridge", + "id": "7fca4eb8c647e57e9d46c32714271e0c3f8bf8d17d346629e2820547b2d90039", + "driver": "bridge", + "containers": { + "bda12f8922785d1f160be70736f26c1e331ab8aaf8ed8d56728508f2e2fd4727": { + "endpoint": "e0ac95934f803d7e36384a2029b8d1eeb56cb88727aa2e8b7edfeebaa6dfd758", + "mac_address": "02:42:ac:11:00:03", + "ipv4_address": "172.17.0.3/16", + "ipv6_address": "" + }, + "f2870c98fd504370fb86e59f32cd0753b1ac9b69b7d80566ffc7192a82b3ed27": { + "endpoint": "31de280881d2a774345bbfb1594159ade4ae4024ebfb1320cb74a30225f6a8ae", + "mac_address": "02:42:ac:11:00:02", + "ipv4_address": "172.17.0.2/16", + "ipv6_address": "" + } + } + } +] +``` + + +## Related information + +* [network disconnect ](network_disconnect.md) +* [network connect](network_connect.md) +* [network create](network_create.md) +* [network ls](network_ls.md) +* [network rm](network_rm.md) +* [Understand Docker container networks](../../userguide/networking/dockernetworks.md) diff --git a/docs/reference/commandline/network_ls.md b/docs/reference/commandline/network_ls.md index 0d2294e6f..9b4dbddc8 100644 --- a/docs/reference/commandline/network_ls.md +++ b/docs/reference/commandline/network_ls.md @@ -2,7 +2,7 @@ +++ title = "network ls" description = "The network ls command description and usage" -keywords = ["network, list"] +keywords = ["network, list, user-defined"] [menu.main] parent = "smn_cli" +++ @@ -14,19 +14,38 @@ parent = "smn_cli" Lists all the networks created by the user --help=false Print usage - -l, --latest=false Show the latest network created - -n=-1 Show n last created networks --no-trunc=false Do not truncate the output -q, --quiet=false Only display numeric IDs -Lists all the networks Docker knows about. This include the networks that spans across multiple hosts in a cluster. +Lists all the networks the Engine `daemon` knows about. This includes the +networks that span across multiple hosts in a cluster, for example: -Example output: - -``` +```bash $ sudo docker network ls NETWORK ID NAME DRIVER 7fca4eb8c647 bridge bridge 9f904ee27bf5 none null cf03ee007fb4 host host + 78b03ee04fc4 multi-host overlay ``` + +Use the `--no-trunc` option to display the full network id: + +```bash +docker network ls --no-trunc +NETWORK ID NAME DRIVER +18a2866682b85619a026c81b98a5e375bd33e1b0936a26cc497c283d27bae9b3 none null +c288470c46f6c8949c5f7e5099b5b7947b07eabe8d9a27d79a9cbf111adcbf47 host host +7b369448dccbf865d397c8d2be0cda7cf7edc6b0945f77d2529912ae917a0185 bridge bridge +95e74588f40db048e86320c6526440c504650a1ff3e9f7d60a497c4d2163e5bd foo bridge +``` + + +## Related information + +* [network disconnect ](network_disconnect.md) +* [network connect](network_connect.md) +* [network create](network_create.md) +* [network inspect](network_inspect.md) +* [network rm](network_rm.md) +* [Understand Docker container networks](../../userguide/networking/dockernetworks.md) diff --git a/docs/reference/commandline/network_rm.md b/docs/reference/commandline/network_rm.md index 9588f0a95..ef79fcac8 100644 --- a/docs/reference/commandline/network_rm.md +++ b/docs/reference/commandline/network_rm.md @@ -2,7 +2,7 @@ +++ title = "network rm" description = "the network rm command description and usage" -keywords = ["network, rm"] +keywords = ["network, rm, user-defined"] [menu.main] parent = "smn_cli" +++ @@ -10,14 +10,23 @@ parent = "smn_cli" # network rm - Usage: docker network rm [OPTIONS] NETWORK + Usage: docker network rm [OPTIONS] NAME | ID Deletes a network --help=false Print usage -Removes a network. You cannot remove a network that is in use by 1 or more containers. +Removes a network by name or identifier. To remove a network, you must first disconnect any containers connected to it. -``` +```bash $ docker network rm my-network ``` + +## 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) +* [Understand Docker container networks](../../userguide/networking/dockernetworks.md) diff --git a/docs/reference/commandline/run.md b/docs/reference/commandline/run.md index 9db6229a4..5e7dd5dd9 100644 --- a/docs/reference/commandline/run.md +++ b/docs/reference/commandline/run.md @@ -17,7 +17,7 @@ parent = "smn_cli" -a, --attach=[] Attach to STDIN, STDOUT or STDERR --add-host=[] Add a custom host-to-IP mapping (host:ip) --blkio-weight=0 Block IO weight (relative weight) - -c, --cpu-shares=0 CPU shares (relative weight) + --cpu-shares=0 CPU shares (relative weight) --cap-add=[] Add Linux capabilities --cap-drop=[] Drop Linux capabilities --cgroup-parent="" Optional parent cgroup for the container @@ -54,7 +54,12 @@ parent = "smn_cli" --memory-swap="" Total memory (memory + swap), '-1' to disable swap --memory-swappiness="" Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100. --name="" Assign a name to the container - --net="default" Set the Network mode for the container + --net="bridge" Connects a container to a network + 'bridge': creates a new network stack for the container on the docker bridge + 'none': no networking for this container + 'container:': reuses another container network stack + 'host': use the host network stack inside the container + 'NETWORK': connects the container to user-created network using `docker network create` command --oom-kill-disable=false Whether to disable OOM Killer for the container or not -P, --publish-all=false Publish all exposed ports to random ports -p, --publish=[] Publish a container's port(s) to the host @@ -81,17 +86,15 @@ specified image, and then `starts` it using the specified command. That is, previous changes intact using `docker start`. See `docker ps -a` to view a list of all containers. -There is detailed information about `docker run` in the [Docker run reference](run.md). - The `docker run` command can be used in combination with `docker commit` to -[*change the command that a container runs*](commit.md). +[*change the command that a container runs*](commit.md). There is additional detailed information about `docker run` in the [Docker run reference](../run.md). -See the [Docker User Guide](../../userguide/dockerlinks.md) for more detailed -information about the `--expose`, `-p`, `-P` and `--link` parameters, -and linking containers. +For information on connecting a container to a network, see the ["*Docker network overview*"](../../userguide/networking/index.md). ## Examples +### Assign name and allocate psuedo-TTY (--name, -it) + $ docker run --name test -it debian root@d6c0fe130dba:/# exit 13 $ echo $? @@ -106,6 +109,8 @@ In the example, the `bash` shell is quit by entering `exit 13`. This exit code is passed on to the caller of `docker run`, and is recorded in the `test` container's metadata. +### Capture container ID (--cidfile) + $ docker run --cidfile /tmp/docker_test.cid ubuntu echo "test" This will create a container and print `test` to the console. The `cidfile` @@ -113,6 +118,8 @@ flag makes Docker attempt to create a new file and write the container ID to it. If the file exists already, Docker will return an error. Docker will close this file when `docker run` exits. +### Full container capabilities (--privileged) + $ docker run -t -i --rm ubuntu bash root@bc338942ef20:/# mount -t tmpfs none /mnt mount: permission denied @@ -132,11 +139,15 @@ lifts all the limitations enforced by the `device` cgroup controller. In other words, the container can then do almost everything that the host can do. This flag exists to allow special use-cases, like running Docker within Docker. +### Set working directory (-w) + $ docker run -w /path/to/dir/ -i -t ubuntu pwd The `-w` lets the command being executed inside directory given, here `/path/to/dir/`. If the path does not exists it is created inside the container. +### Mount volume (-v, --read-only) + $ docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd The `-v` flag mounts the current working directory into the container. The `-w` @@ -166,18 +177,21 @@ binary (such as that provided by [https://get.docker.com]( https://get.docker.com)), you give the container the full access to create and manipulate the host's Docker daemon. +### Publish or expose port (-p, --expose) + $ docker run -p 127.0.0.1:80:8080 ubuntu bash -This binds port `8080` of the container to port `80` on `127.0.0.1` of -the host machine. The [Docker User Guide](../../userguide/dockerlinks.md) +This binds port `8080` of the container to port `80` on `127.0.0.1` of the host +machine. The [Docker User +Guide](../../userguide/networking/default_network/dockerlinks.md) explains in detail how to manipulate ports in Docker. $ docker run --expose 80 ubuntu bash -This exposes port `80` of the container for use within a link without -publishing the port to the host system's interfaces. The [Docker User -Guide](../../userguide/dockerlinks.md) explains in detail how to manipulate -ports in Docker. +This exposes port `80` of the container without publishing the port to the host +system's interfaces. + +### Set environment variables (-e, --env, --env-file) $ docker run -e MYVAR1 --env MYVAR2=foo --env-file ./env.list ubuntu bash @@ -247,7 +261,9 @@ An example of a file passed with `--env-file` 123qwe=bar org.spring.config=something -A label is a a `key=value` pair that applies metadata to a container. To label a container with two labels: +### Set metadata on container (-l, --label, --label-file) + +A label is a `key=value` pair that applies metadata to a container. To label a container with two labels: $ docker run -l my-label --label com.example.foo=bar ubuntu bash @@ -281,19 +297,31 @@ For additional information on working with labels, see [*Labels - custom metadata in Docker*](../../userguide/labels-custom-metadata.md) in the Docker User Guide. - $ docker run --link /redis:redis --name console ubuntu bash +### Connect a container to a network (--net) -The `--link` flag will link the container named `/redis` into the newly -created container with the alias `redis`. The new container can access the -network and environment of the `redis` container via environment variables. -The `--link` flag will also just accept the form `` in which case -the alias will match the name. For instance, you could have written the previous -example as: +When you start a container use the `--net` flag to connect it to a network. +This adds the `busybox` container to the `mynet` network. - $ docker run --link redis --name console ubuntu bash +```bash +$ docker run -itd --net=my-multihost-network busybox +``` -The `--name` flag will assign the name `console` to the newly created -container. +If you want to add a running container to a network use the `docker network connect` subcommand. + +You can connect multiple containers to the same network. Once connected, the +containers can communicate easily need only another container's IP address +or name. For `overlay` networks or custom plugins that support multi-host +connectivity, containers connected to the same multi-host network but launched +from different Engines can also communicate in this way. + +**Note**: Service discovery is unavailable on the default bridge network. +Containers can communicate via their IP addresses by default. To communicate +by name, they must be linked. + +You can disconnect a container from a network using the `docker network +disconnect` command. + +### Mount volumes from container (--volumes-from) $ docker run --volumes-from 777f7dc92da7 --volumes-from ba8c0c54f0f2:ro -i -t ubuntu pwd @@ -317,6 +345,8 @@ content label. Shared volume labels allow all containers to read/write content. The `Z` option tells Docker to label the content with a private unshared label. Only the current container can use a private volume. +### Attach to STDIN/STDOUT/STDERR (-a) + The `-a` flag tells `docker run` to bind to the container's `STDIN`, `STDOUT` or `STDERR`. This makes it possible to manipulate the output and input as needed. @@ -340,6 +370,8 @@ logs could be retrieved using `docker logs`. This is useful if you need to pipe a file or something else into a container and retrieve the container's ID once the container has finished running. +### Add host device to container (--device) + $ docker run --device=/dev/sdc:/dev/xvdc --device=/dev/sdd --device=/dev/zero:/dev/nulo -i -t ubuntu ls -l /dev/{xvdc,sdd,nulo} brw-rw---- 1 root disk 8, 2 Feb 9 16:05 /dev/xvdc brw-rw---- 1 root disk 8, 3 Feb 9 16:05 /dev/sdd @@ -375,38 +407,7 @@ flag: > that may be removed should not be added to untrusted containers with > `--device`. -**A complete example:** - - $ docker run -d --name static static-web-files sh - $ docker run -d --expose=8098 --name riak riakserver - $ docker run -d -m 100m -e DEVELOPMENT=1 -e BRANCH=example-code -v $(pwd):/app/bin:ro --name app appserver - $ docker run -d -p 1443:443 --dns=10.0.0.1 --dns-search=dev.org -v /var/log/httpd --volumes-from static --link riak --link app -h www.sven.dev.org --name web webserver - $ docker run -t -i --rm --volumes-from web -w /var/log/httpd busybox tail -f access.log - -This example shows five containers that might be set up to test a web -application change: - -1. Start a pre-prepared volume image `static-web-files` (in the background) - that has CSS, image and static HTML in it, (with a `VOLUME` instruction in - the Dockerfile to allow the web server to use those files); -2. Start a pre-prepared `riakserver` image, give the container name `riak` and - expose port `8098` to any containers that link to it; -3. Start the `appserver` image, restricting its memory usage to 100MB, setting - two environment variables `DEVELOPMENT` and `BRANCH` and bind-mounting the - current directory (`$(pwd)`) in the container in read-only mode as `/app/bin`; -4. Start the `webserver`, mapping port `443` in the container to port `1443` on - the Docker server, setting the DNS server to `10.0.0.1` and DNS search - domain to `dev.org`, creating a volume to put the log files into (so we can - access it from another container), then importing the files from the volume - exposed by the `static` container, and linking to all exposed ports from - `riak` and `app`. Lastly, we set the hostname to `web.sven.dev.org` so its - consistent with the pre-generated SSL certificate; -5. Finally, we create a container that runs `tail -f access.log` using the logs - volume from the `web` container, setting the workdir to `/var/log/httpd`. The - `--rm` option means that when the container exits, the container's layer is - removed. - -## Restart policies +### Restart policies (--restart) Use Docker's `--restart` to specify a container's *restart policy*. A restart policy controls whether the Docker daemon restarts a container after exit. @@ -468,7 +469,7 @@ More detailed information on restart policies can be found in the [Restart Policies (--restart)](../run.md#restart-policies-restart) section of the Docker run reference page. -## Adding entries to a container hosts file +### Add entries to container hosts file (--add-host) You can add other hosts into a container's `/etc/hosts` file by using one or more `--add-host` flags. This example adds a static address for a host named @@ -499,7 +500,7 @@ For IPv6 use the `-6` flag instead of the `-4` flag. For other network devices, replace `eth0` with the correct device name (for example `docker0` for the bridge device). -### Setting ulimits in a container +### Set ulimits in container (--ulimit) Since setting `ulimit` settings in a container requires extra privileges not available in the default container, you can set these using the `--ulimit` flag. @@ -519,13 +520,12 @@ available in the default container, you can set these using the `--ulimit` flag. The values are sent to the appropriate `syscall` as they are set. Docker doesn't perform any byte conversion. Take this into account when setting the values. -#### For `nproc` usage: +#### For `nproc` usage Be careful setting `nproc` with the `ulimit` flag as `nproc` is designed by Linux to set the maximum number of processes available to a user, not to a container. For example, start four containers with `daemon` user: - docker run -d -u daemon --ulimit nproc=3 busybox top docker run -d -u daemon --ulimit nproc=3 busybox top docker run -d -u daemon --ulimit nproc=3 busybox top @@ -535,7 +535,7 @@ The 4th container fails and reports "[8] System error: resource temporarily unav This fails because the caller set `nproc=3` resulting in the first three containers using up the three processes quota set for the `daemon` user. -### Stopping a container with a specific signal +### Stop container with signal (--stop-signal) The `--stop-signal` flag sets the system call signal that will be sent to the container to exit. This signal can be a valid unsigned number that matches a position in the kernel's syscall table, for instance 9, diff --git a/docs/reference/glossary.md b/docs/reference/glossary.md index 3f7e7a032..ff4398c24 100644 --- a/docs/reference/glossary.md +++ b/docs/reference/glossary.md @@ -135,16 +135,14 @@ after the container is created. ## libnetwork libnetwork provides a native Go implementation for creating and managing container -network namespaces and other network resources. It manage the networking lifecycle +network namespaces and other network resources. It manage the networking lifecycle of the container performing additional operations after the container is created. ## link -links provide an interface to connect Docker containers running on the same host -to each other without exposing the hosts' network ports. When you set up a link, -you create a conduit between a source container and a recipient container. -The recipient can then access select data about the source. To create a link, -you can use the `--link` flag. +links provide a legacy interface to connect Docker containers running on the +same host to each other without exposing the hosts' network ports. Use the +Docker networks feature instead. ## Machine @@ -221,4 +219,3 @@ Compared to to containers, a Virtual Machine is heavier to run, provides more is gets its own set of resources and does minimal sharing. *Also known as : VM* - diff --git a/docs/reference/logging/awslogs.md b/docs/reference/logging/awslogs.md index 4100fd108..8e52288b7 100644 --- a/docs/reference/logging/awslogs.md +++ b/docs/reference/logging/awslogs.md @@ -21,7 +21,7 @@ and Command Line Tools](http://docs.aws.amazon.com/cli/latest/reference/logs/ind You can configure the default logging driver by passing the `--log-driver` option to the Docker daemon: - docker --log-driver=awslogs + docker daemon --log-driver=awslogs You can set the logging driver for a specific container by using the `--log-driver` option to `docker run`: diff --git a/docs/reference/logging/fluentd.md b/docs/reference/logging/fluentd.md index 5e9aaad4c..c23e0a17c 100644 --- a/docs/reference/logging/fluentd.md +++ b/docs/reference/logging/fluentd.md @@ -39,7 +39,7 @@ Some options are supported by specifying `--log-opt` as many times as needed: Configure the default logging driver by passing the `--log-driver` option to the Docker daemon: - docker --log-driver=fluentd + docker daemon --log-driver=fluentd To set the logging driver for a specific container, pass the `--log-driver` option to `docker run`: diff --git a/docs/reference/logging/journald.md b/docs/reference/logging/journald.md index c22ecdc34..df475ddb7 100644 --- a/docs/reference/logging/journald.md +++ b/docs/reference/logging/journald.md @@ -29,7 +29,7 @@ driver stores the following metadata in the journal with each message: You can configure the default logging driver by passing the `--log-driver` option to the Docker daemon: - docker --log-driver=journald + docker daemon --log-driver=journald You can set the logging driver for a specific container by using the `--log-driver` option to `docker run`: diff --git a/docs/reference/logging/overview.md b/docs/reference/logging/overview.md index ad5847418..86042084c 100644 --- a/docs/reference/logging/overview.md +++ b/docs/reference/logging/overview.md @@ -39,7 +39,7 @@ Then, run a container and specify values for the `labels` or `env`. For example ``` docker run --label foo=bar -e fizz=buzz -d -P training/webapp python app.py -```` +``` This adds additional fields to the log depending on the driver, e.g. for `json-file` that looks like: diff --git a/docs/reference/run.md b/docs/reference/run.md index 5d805b6e7..9ed8be72b 100644 --- a/docs/reference/run.md +++ b/docs/reference/run.md @@ -126,9 +126,8 @@ and pass along signals. All of that is configurable: -i=false : Keep STDIN open even if not attached If you do not specify `-a` then Docker will [attach all standard -streams]( https://github.com/docker/docker/blob/ -75a7f4d90cde0295bcfb7213004abce8d4779b75/commands.go#L1797). You can -specify to which of the three standard streams (`STDIN`, `STDOUT`, +streams]( https://github.com/docker/docker/blob/75a7f4d90cde0295bcfb7213004abce8d4779b75/commands.go#L1797). +You can specify to which of the three standard streams (`STDIN`, `STDOUT`, `STDERR`) you'd like to connect instead, as in: $ docker run -a stdin -a stdout -i -t ubuntu /bin/bash @@ -155,13 +154,14 @@ The operator can identify a container in three ways: - UUID short identifier ("f78375b1c487") - Name ("evil_ptolemy") -The UUID identifiers come from the Docker daemon, and if you do not -assign a name to the container with `--name` then the daemon will also -generate a random string name too. The name can become a handy way to -add meaning to a container since you can use this name when defining -[*links*](../userguide/dockerlinks.md) (or any -other place you need to identify a container). This works for both -background and foreground Docker containers. +The UUID identifiers come from the Docker daemon. If you do not assign a +container name with the `--name` option, then the daemon generates a random +string name for you. Defining a `name` can be a handy way to add meaning to a +container. If you specify a `name`, you can use it when referencing the +container within a Docker network. This works for both background and foreground +Docker containers. + +**Note**: Containers on the default bridge network must be linked to communicate by name. ### PID equivalent @@ -260,8 +260,7 @@ with `docker run --net none` which disables all incoming and outgoing networking. In cases like this, you would perform I/O through files or `STDIN` and `STDOUT` only. -Publishing ports and linking to other containers will not work -when `--net` is anything other than the default (bridge). +Publishing ports and linking to other containers only works with the the default (bridge). The linking feature is a legacy feature. You should always prefer using Docker network drivers over linking. Your container will use the same DNS servers as the host by default, but you can override this with `--dns`. @@ -332,6 +331,9 @@ container's namespaces in addition to the `loopback` interface. An IP address will be allocated for containers on the bridge's network and traffic will be routed though this bridge to the container. +Containers can communicate via their IP addresses by default. To communicate by +name, they must be linked. + #### Network: host With the network set to `host` a container will share the host's @@ -367,19 +369,23 @@ running the `redis-cli` command and connecting to the Redis server over the $ # use the redis container's network stack to access localhost $ docker run --rm -it --net container:redis example/redis-cli -h 127.0.0.1 -#### Network: User-Created NETWORK +#### User-defined network -In addition to all the above special networks, user can create a network using -their favorite network driver or external plugin. The driver used to create the -network takes care of all the network plumbing requirements for the container -connected to that network. +You can create a network using a Docker network driver or an external network +driver plugin. You can connect multiple containers to the same network. Once +connected to a user-defined network, the containers can communicate easily using +only another container's IP address or name. -Example creating a network using the inbuilt overlay network driver and running -a container in the created network +For `overlay` networks or custom plugins that support multi-host connectivity, +containers connected to the same multi-host network but launched from different +Engines can also communicate in this way. + +The following example creates a network using the built-in `bridge` network +driver and running a container in the created network ``` -$ docker network create -d overlay multi-host-network -$ docker run --net=multi-host-network -itd --name=container3 busybox +$ docker network create -d overlay my-net +$ docker run --net=my-net -itd --name=container3 busybox ``` ### Managing /etc/hosts @@ -398,6 +404,19 @@ container itself as well as `localhost` and a few other common things. The ::1 localhost ip6-localhost ip6-loopback 86.75.30.9 db-static +If a container is connected to the default bridge network and `linked` +with other containers, then the container's `/etc/hosts` file is updated +with the linked container's name. + +If the container is connected to user-defined network, the container's +`/etc/hosts` file is updated with names of all other containers in that +user-defined network. + +> **Note** Since Docker may live update the container’s `/etc/hosts` file, there +may be situations when processes inside the container can end up reading an +empty or incomplete `/etc/hosts` file. In most cases, retrying the read again +should fix the problem. + ## Restart policies (--restart) Using the `--restart` flag on Docker run you can specify a restart policy for @@ -511,8 +530,8 @@ the container exits**, you can add the `--rm` flag: --rm=false: Automatically remove the container when it exits (incompatible with -d) -> **Note**: When you set the `--rm` flag, Docker also removes the volumes -associated with the container when the container is removed. This is similar +> **Note**: When you set the `--rm` flag, Docker also removes the volumes +associated with the container when the container is removed. This is similar to running `docker rm -v my-container`. ## Security configuration @@ -665,7 +684,7 @@ same as the hard memory limit. Memory reservation is a soft-limit feature and does not guarantee the limit won't be exceeded. Instead, the feature attempts to ensure that, when memory is -heavily contended for, memory is allocated based on the reservation hints/setup. +heavily contended for, memory is allocated based on the reservation hints/setup. The following example limits the memory (`-m`) to 500M and sets the memory reservation to 200M. @@ -1186,12 +1205,12 @@ specifies `EXPOSE 80` in the Dockerfile). At runtime, the port might be bound to 42800 on the host. To find the mapping between the host ports and the exposed ports, use `docker port`. -If the operator uses `--link` when starting a new client container, -then the client container can access the exposed port via a private -networking interface. Docker will set some environment variables in the -client container to help indicate which interface and port to use. For -more information on linking, see [the guide on linking container -together](../userguide/dockerlinks.md) +If the operator uses `--link` when starting a new client container, then the +client container can access the exposed port via a private networking interface. +Linking is a legacy feature that is only supported on the default bridge +network. You should prefer the Docker networks feature instead. For more +information on this feature, see the [*Docker network +overview*""](../userguide/networking/index.md)). ### ENV (environment variables) @@ -1227,11 +1246,6 @@ variables automatically: -The container may also include environment variables defined -as a result of the container being linked with another container. See -the [*Container Links*](../userguide/dockerlinks.md#connect-with-the-linking-system) -section for more details. - Additionally, the operator can **set any environment variable** in the container by using one or more `-e` flags, even overriding those mentioned above, or already defined by the developer with a Dockerfile `ENV`: @@ -1248,69 +1262,11 @@ above, or already defined by the developer with a Dockerfile `ENV`: Similarly the operator can set the **hostname** with `-h`. -`--link :alias` also sets environment variables, using the *alias* string to -define environment variables within the container that give the IP and PORT -information for connecting to the service container. Let's imagine we have a -container running Redis: - - # Start the service container, named redis-name - $ docker run -d --name redis-name dockerfiles/redis - 4241164edf6f5aca5b0e9e4c9eccd899b0b8080c64c0cd26efe02166c73208f3 - - # The redis-name container exposed port 6379 - $ docker ps - CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - 4241164edf6f $ dockerfiles/redis:latest /redis-stable/src/re 5 seconds ago Up 4 seconds 6379/tcp redis-name - - # Note that there are no public ports exposed since we didn᾿t use -p or -P - $ docker port 4241164edf6f 6379 - 2014/01/25 00:55:38 Error: No public port '6379' published for 4241164edf6f - -Yet we can get information about the Redis container's exposed ports -with `--link`. Choose an alias that will form a -valid environment variable! - - $ docker run --rm --link redis-name:redis_alias --entrypoint /bin/bash dockerfiles/redis -c export - declare -x HOME="/" - declare -x HOSTNAME="acda7f7b1cdc" - declare -x OLDPWD - declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - declare -x PWD="/" - declare -x REDIS_ALIAS_NAME="/distracted_wright/redis" - declare -x REDIS_ALIAS_PORT="tcp://172.17.0.32:6379" - declare -x REDIS_ALIAS_PORT_6379_TCP="tcp://172.17.0.32:6379" - declare -x REDIS_ALIAS_PORT_6379_TCP_ADDR="172.17.0.32" - declare -x REDIS_ALIAS_PORT_6379_TCP_PORT="6379" - declare -x REDIS_ALIAS_PORT_6379_TCP_PROTO="tcp" - declare -x SHLVL="1" - declare -x container="lxc" - -And we can use that information to connect from another container as a client: - - $ docker run -i -t --rm --link redis-name:redis_alias --entrypoint /bin/bash dockerfiles/redis -c '/redis-stable/src/redis-cli -h $REDIS_ALIAS_PORT_6379_TCP_ADDR -p $REDIS_ALIAS_PORT_6379_TCP_PORT' - 172.17.0.32:6379> - -Docker will also map the private IP address to the alias of a linked -container by inserting an entry into `/etc/hosts`. You can use this -mechanism to communicate with a linked container by its alias: - - $ docker run -d --name servicename busybox sleep 30 - $ docker run -i -t --link servicename:servicealias busybox ping -c 1 servicealias - -If you restart the source container (`servicename` in this case), the recipient -container's `/etc/hosts` entry will be automatically updated. - -> **Note**: -> Unlike host entries in the `/etc/hosts` file, IP addresses stored in the -> environment variables are not automatically updated if the source container is -> restarted. We recommend using the host entries in `/etc/hosts` to resolve the -> IP address of linked containers. - ### VOLUME (shared filesystems) -v=[]: Create a bind mount with: [host-dir:]container-dir[:], where - options are comma delimited and selected from [rw|ro] and [z|Z]. - If 'host-dir' is missing, then docker creates a new volume. + options are comma delimited and selected from [rw|ro] and [z|Z]. + If 'host-dir' is missing, then docker creates a new volume. If neither 'rw' or 'ro' is specified then the volume is mounted in read-write mode. --volumes-from="": Mount all volumes from the given container(s) @@ -1325,17 +1281,17 @@ one or more `VOLUME`'s associated with an image, but only the operator can give access from one container to another (or from a container to a volume mounted on the host). -The `container-dir` must always be an absolute path such as `/src/docs`. -The `host-dir` can either be an absolute path or a `name` value. If you -supply an absolute path for the `host-dir`, Docker bind-mounts to the path +The `container-dir` must always be an absolute path such as `/src/docs`. +The `host-dir` can either be an absolute path or a `name` value. If you +supply an absolute path for the `host-dir`, Docker bind-mounts to the path you specify. If you supply a `name`, Docker creates a named volume by that `name`. -A `name` value must start with start with an alphanumeric character, -followed by `a-z0-9`, `_` (underscore), `.` (period) or `-` (hyphen). +A `name` value must start with start with an alphanumeric character, +followed by `a-z0-9`, `_` (underscore), `.` (period) or `-` (hyphen). An absolute path starts with a `/` (forward slash). -For example, you can specify either `/foo` or `foo` for a `host-dir` value. -If you supply the `/foo` value, Docker creates a bind-mount. If you supply +For example, you can specify either `/foo` or `foo` for a `host-dir` value. +If you supply the `/foo` value, Docker creates a bind-mount. If you supply the `foo` specification, Docker creates a named volume. ### USER diff --git a/docs/security/trust/trust_sandbox.md b/docs/security/trust/trust_sandbox.md index 2832d9f71..9d6c2f212 100644 --- a/docs/security/trust/trust_sandbox.md +++ b/docs/security/trust/trust_sandbox.md @@ -26,7 +26,7 @@ have `sudo` privileges on your local machine or in the VM. This sandbox requires you to install two Docker tools: Docker Engine and Docker Compose. To install the Docker Engine, choose from the [list of supported platforms](../../installation). To install Docker Compose, see the -[detailed instructions here](https://docs.docker.com/compose/install.md). +[detailed instructions here](https://docs.docker.com/compose/install/). Finally, you'll need to have `git` installed on your local system or VM. diff --git a/docs/articles/basics.md b/docs/userguide/basics.md similarity index 92% rename from docs/articles/basics.md rename to docs/userguide/basics.md index 0700b9978..42102b742 100644 --- a/docs/articles/basics.md +++ b/docs/userguide/basics.md @@ -1,17 +1,16 @@ -# Get started with containers +# Quickstart containers -This guide assumes you have a working installation of Docker. To verify Docker -is installed, use the following command: +This quickstart assumes you have a working installation of Docker. To verify Docker is installed, use the following command: # Check that you have a working install $ docker info @@ -54,7 +53,7 @@ image cache. To run an interactive shell in the Ubuntu image: $ docker run -i -t ubuntu /bin/bash - + The `-i` flag starts an interactive container. The `-t` flag creates a pseudo-TTY that attaches `stdin` and `stdout`. @@ -183,7 +182,7 @@ re-used. When you commit your container, Docker only stores the diff (difference) between the source image and the current state of the container's image. To list images -you already have, use the `docker images` command. +you already have, use the `docker images` command. # Commit your container to a new named image $ docker commit @@ -193,7 +192,8 @@ you already have, use the `docker images` command. You now have an image state from which you can create new instances. -Read more about [*Share Images via -Repositories*](../userguide/dockerrepos.md) or -continue to the complete [*Command -Line*](../reference/commandline/cli.md) +## Where to go next + +* Work your way through the [Docker User Guide](../userguide/index.md) +* Read more about [*Share Images via Repositories*](../userguide/dockerrepos.md) +* Review [*Command Line*](../reference/commandline/cli.md) diff --git a/docs/userguide/dockerimages.md b/docs/userguide/dockerimages.md index 7873a1b72..e76d44156 100644 --- a/docs/userguide/dockerimages.md +++ b/docs/userguide/dockerimages.md @@ -1,28 +1,26 @@ -# Get started with images +# Build your own images -In the [introduction](../introduction/understanding-docker.md) we've discovered that Docker -images are the basis of containers. In the -[previous](dockerizing.md) [sections](usingdocker.md) -we've used Docker images that already exist, for example the `ubuntu` -image and the `training/webapp` image. +Docker images are the basis of containers. Each time you've used `docker run` +you told it which image you wanted. In the previous sections of the guide you +used Docker images that already exist, for example the `ubuntu` image and the +`training/webapp` image. -We've also discovered that Docker stores downloaded images on the Docker -host. If an image isn't already present on the host then it'll be -downloaded from a registry: by default the -[Docker Hub Registry](https://registry.hub.docker.com). +You also discovered that Docker stores downloaded images on the Docker host. If +an image isn't already present on the host then it'll be downloaded from a +registry: by default the [Docker Hub Registry](https://registry.hub.docker.com). -In this section we're going to explore Docker images a bit more +In this section you're going to explore Docker images a bit more including: * Managing and working with images locally on your Docker host. @@ -31,55 +29,40 @@ including: ## Listing images on the host -Let's start with listing the images we have locally on our host. You can +Let's start with listing the images you have locally on our host. You can do this using the `docker images` command like so: $ docker images - REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE - training/webapp latest fc77f57ad303 3 weeks ago 280.5 MB - ubuntu 13.10 5e019ab7bf6d 4 weeks ago 180 MB - ubuntu saucy 5e019ab7bf6d 4 weeks ago 180 MB - ubuntu 12.04 74fe38d11401 4 weeks ago 209.6 MB - ubuntu precise 74fe38d11401 4 weeks ago 209.6 MB - ubuntu 12.10 a7cf8ae4e998 4 weeks ago 171.3 MB - ubuntu quantal a7cf8ae4e998 4 weeks ago 171.3 MB - ubuntu 14.04 99ec81b80c55 4 weeks ago 266 MB - ubuntu latest 99ec81b80c55 4 weeks ago 266 MB - ubuntu trusty 99ec81b80c55 4 weeks ago 266 MB - ubuntu 13.04 316b678ddf48 4 weeks ago 169.4 MB - ubuntu raring 316b678ddf48 4 weeks ago 169.4 MB - ubuntu 10.04 3db9c44f4520 4 weeks ago 183 MB - ubuntu lucid 3db9c44f4520 4 weeks ago 183 MB + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + ubuntu 14.04 1d073211c498 3 days ago 187.9 MB + busybox latest 2c5ac3f849df 5 days ago 1.113 MB + training/webapp latest 54bb4e8718e8 5 months ago 348.7 MB -We can see the images we've previously used in our user guide. -Each has been downloaded from [Docker Hub](https://hub.docker.com) when we -launched a container using that image. - -We can see three crucial pieces of information about our images in the listing. +You can see the images you've previously used in the user guide. +Each has been downloaded from [Docker Hub](https://hub.docker.com) when you +launched a container using that image. When you list images, you get three crucial pieces of information in the listing. * What repository they came from, for example `ubuntu`. * The tags for each image, for example `14.04`. * The image ID of each image. -> **Note:** -> Previously, the `docker images` command supported the `--tree` and `--dot` -> arguments, which displayed different visualizations of the image data. Docker -> core removed this functionality in the 1.7 version. If you liked this -> functionality, you can still find it in -> [the third-party dockviz tool](https://github.com/justone/dockviz). +> **Tip:** +> You can use [a third-party dockviz tool](https://github.com/justone/dockviz) +> or the [Image layers site](https://imagelayers.io/) to display +> visualizations of image data. A repository potentially holds multiple variants of an image. In the case of -our `ubuntu` image we can see multiple variants covering Ubuntu 10.04, 12.04, +our `ubuntu` image you can see multiple variants covering Ubuntu 10.04, 12.04, 12.10, 13.04, 13.10 and 14.04. Each variant is identified by a tag and you can refer to a tagged image like so: ubuntu:14.04 -So when we run a container we refer to a tagged image like so: +So when you run a container you refer to a tagged image like so: $ docker run -t -i ubuntu:14.04 /bin/bash -If instead we wanted to run an Ubuntu 12.04 image we'd use: +If instead you wanted to run an Ubuntu 12.04 image you'd use: $ docker run -t -i ubuntu:12.04 /bin/bash @@ -87,16 +70,16 @@ If you don't specify a variant, for example you just use `ubuntu`, then Docker will default to using the `ubuntu:latest` image. > **Tip:** -> We recommend you always use a specific tagged image, for example +> You recommend you always use a specific tagged image, for example > `ubuntu:12.04`. That way you always know exactly what variant of an image is > being used. ## Getting a new image -So how do we get new images? Well Docker will automatically download any image -we use that isn't already present on the Docker host. But this can potentially -add some time to the launch of a container. If we want to pre-load an image we -can download it using the `docker pull` command. Let's say we'd like to +So how do you get new images? Well Docker will automatically download any image +you use that isn't already present on the Docker host. But this can potentially +add some time to the launch of a container. If you want to pre-load an image you +can download it using the `docker pull` command. Suppose you'd like to download the `centos` image. $ docker pull centos @@ -109,8 +92,8 @@ download the `centos` image. Status: Downloaded newer image for centos -We can see that each layer of the image has been pulled down and now we -can run a container from this image and we won't have to wait to +You can see that each layer of the image has been pulled down and now you +can run a container from this image and you won't have to wait to download the image. $ docker run -t -i centos /bin/bash @@ -120,14 +103,14 @@ download the image. One of the features of Docker is that a lot of people have created Docker images for a variety of purposes. Many of these have been uploaded to -[Docker Hub](https://hub.docker.com). We can search these images on the +[Docker Hub](https://hub.docker.com). You can search these images on the [Docker Hub](https://hub.docker.com) website. ![indexsearch](search.png) -We can also search for images on the command line using the `docker search` -command. Let's say our team wants an image with Ruby and Sinatra installed on -which to do our web application development. We can search for a suitable image +You can also search for images on the command line using the `docker search` +command. Suppose your team wants an image with Ruby and Sinatra installed on +which to do our web application development. You can search for a suitable image by using the `docker search` command to find all the images that contain the term `sinatra`. @@ -142,29 +125,29 @@ term `sinatra`. bmorearty/sinatra 0 . . . -We can see we've returned a lot of images that use the term `sinatra`. We've -returned a list of image names, descriptions, Stars (which measure the social -popularity of images - if a user likes an image then they can "star" it), and -the Official and Automated build statuses. -[Official Repositories](https://docs.docker.com/docker-hub/official_repos) are a carefully curated set -of Docker repositories supported by Docker, Inc. Automated repositories are -[Automated Builds](dockerrepos.md#automated-builds) that allow you to -validate the source and content of an image. +You can see the command returns a lot of images that use the term `sinatra`. +You've received a list of image names, descriptions, Stars (which measure the +social popularity of images - if a user likes an image then they can "star" it), +and the Official and Automated build statuses. [Official +Repositories](https://docs.docker.com/docker-hub/official_repos) are a carefully +curated set of Docker repositories supported by Docker, Inc. Automated +repositories are [Automated Builds](dockerrepos.md#automated-builds) that allow +you to validate the source and content of an image. -We've reviewed the images available to use and we decided to use the -`training/sinatra` image. So far we've seen two types of images repositories, +You've reviewed the images available to use and you decided to use the +`training/sinatra` image. So far you've seen two types of images repositories, images like `ubuntu`, which are called base or root images. These base images are provided by Docker Inc and are built, validated and supported. These can be identified by their single word names. -We've also seen user images, for example the `training/sinatra` image we've +You've also seen user images, for example the `training/sinatra` image you've chosen. A user image belongs to a member of the Docker community and is built and maintained by them. You can identify user images as they are always prefixed with the user name, here `training`, of the user that created them. ## Pulling our image -We've identified a suitable image, `training/sinatra`, and now we can download it using the `docker pull` command. +You've identified a suitable image, `training/sinatra`, and now you can download it using the `docker pull` command. $ docker pull training/sinatra @@ -175,24 +158,24 @@ The team can now use this image by running their own containers. ## Creating our own images -The team has found the `training/sinatra` image pretty useful but it's not quite what -they need and we need to make some changes to it. There are two ways we can -update and create images. +The team has found the `training/sinatra` image pretty useful but it's not quite +what they need and you need to make some changes to it. There are two ways you +can update and create images. -1. We can update a container created from an image and commit the results to an image. -2. We can use a `Dockerfile` to specify instructions to create an image. +1. You can update a container created from an image and commit the results to an image. +2. You can use a `Dockerfile` to specify instructions to create an image. ### Updating and committing an image -To update an image we first need to create a container from the image -we'd like to update. +To update an image you first need to create a container from the image +you'd like to update. $ docker run -t -i training/sinatra /bin/bash root@0b2616b0e5a8:/# > **Note:** -> Take note of the container ID that has been created, `0b2616b0e5a8`, as we'll +> Take note of the container ID that has been created, `0b2616b0e5a8`, as you'll > need it in a moment. Inside our running container let's add the `json` gem. @@ -202,7 +185,7 @@ Inside our running container let's add the `json` gem. Once this has completed let's exit our container using the `exit` command. -Now we have a container with the change we want to make. We can then +Now you have a container with the change you want to make. You can then commit a copy of this container to an image using the `docker commit` command. @@ -210,23 +193,23 @@ command. 0b2616b0e5a8 ouruser/sinatra:v2 4f177bd27a9ff0f6dc2a830403925b5360bfe0b93d476f7fc3231110e7f71b1c -Here we've used the `docker commit` command. We've specified two flags: `-m` +Here you've used the `docker commit` command. You've specified two flags: `-m` and `-a`. The `-m` flag allows us to specify a commit message, much like you would with a commit on a version control system. The `-a` flag allows us to specify an author for our update. -We've also specified the container we want to create this new image from, -`0b2616b0e5a8` (the ID we recorded earlier) and we've specified a target for +You've also specified the container you want to create this new image from, +`0b2616b0e5a8` (the ID you recorded earlier) and you've specified a target for the image: ouruser/sinatra:v2 -Let's break this target down. It consists of a new user, `ouruser`, that we're -writing this image to. We've also specified the name of the image, here we're -keeping the original image name `sinatra`. Finally we're specifying a tag for +Break this target down. It consists of a new user, `ouruser`, that you're +writing this image to. You've also specified the name of the image, here you're +keeping the original image name `sinatra`. Finally you're specifying a tag for the image: `v2`. -We can then look at our new `ouruser/sinatra` image using the `docker images` +You can then look at our new `ouruser/sinatra` image using the `docker images` command. $ docker images @@ -235,7 +218,7 @@ command. ouruser/sinatra v2 3c59e02ddd1a 10 hours ago 446.7 MB ouruser/sinatra latest 5db5f8471261 10 hours ago 446.7 MB -To use our new image to create a container we can then: +To use our new image to create a container you can then: $ docker run -t -i ouruser/sinatra:v2 /bin/bash root@78e82f680994:/# @@ -244,13 +227,13 @@ To use our new image to create a container we can then: Using the `docker commit` command is a pretty simple way of extending an image but it's a bit cumbersome and it's not easy to share a development process for -images amongst a team. Instead we can use a new command, `docker build`, to +images amongst a team. Instead you can use a new command, `docker build`, to build new images from scratch. -To do this we create a `Dockerfile` that contains a set of instructions that +To do this you create a `Dockerfile` that contains a set of instructions that tell Docker how to build our image. -Let's create a directory and a `Dockerfile` first. +First, create a directory and a `Dockerfile`. $ mkdir sinatra $ cd sinatra @@ -259,8 +242,8 @@ Let's create a directory and a `Dockerfile` first. If you are using Docker Machine on Windows, you may access your host directory by `cd` to `/c/Users/your_user_name`. -Each instruction creates a new layer of the image. Let's look at a simple -example now for building our own Sinatra image for our development team. +Each instruction creates a new layer of the image. Try a simple example now for +building your own Sinatra image for your fictitious development team. # This is a comment FROM ubuntu:14.04 @@ -268,25 +251,22 @@ example now for building our own Sinatra image for our development team. RUN apt-get update && apt-get install -y ruby ruby-dev RUN gem install sinatra -Let's look at what our `Dockerfile` does. Each instruction prefixes a statement and is capitalized. +Examine what your `Dockerfile` does. Each instruction prefixes a statement and +is capitalized. INSTRUCTION statement -> **Note:** -> We use `#` to indicate a comment +> **Note:** You use `#` to indicate a comment The first instruction `FROM` tells Docker what the source of our image is, in -this case we're basing our new image on an Ubuntu 14.04 image. +this case you're basing our new image on an Ubuntu 14.04 image. The instruction uses the `MAINTAINER` instruction to specify who maintains the new image. -Next we use the `MAINTAINER` instruction to specify who maintains our new image. - -Lastly, we've specified two `RUN` instructions. A `RUN` instruction executes -a command inside the image, for example installing a package. Here we're +Lastly, you've specified two `RUN` instructions. A `RUN` instruction executes +a command inside the image, for example installing a package. Here you're updating our APT cache, installing Ruby and RubyGems and then installing the Sinatra gem. -> **Note:** -> There are [a lot more instructions available to us in a Dockerfile](../reference/builder.md). + Now let's take our `Dockerfile` and use the `docker build` command to build an image. @@ -454,26 +434,26 @@ Now let's take our `Dockerfile` and use the `docker build` command to build an i Removing intermediate container 6b81cb6313e5 Successfully built 97feabe5d2ed -We've specified our `docker build` command and used the `-t` flag to identify +You've specified our `docker build` command and used the `-t` flag to identify our new image as belonging to the user `ouruser`, the repository name `sinatra` and given it the tag `v2`. -We've also specified the location of our `Dockerfile` using the `.` to +You've also specified the location of our `Dockerfile` using the `.` to indicate a `Dockerfile` in the current directory. > **Note:** > You can also specify a path to a `Dockerfile`. -Now we can see the build process at work. The first thing Docker does is +Now you can see the build process at work. The first thing Docker does is upload the build context: basically the contents of the directory you're building in. This is done because the Docker daemon does the actual build of the image and it needs the local context to do it. -Next we can see each instruction in the `Dockerfile` being executed -step-by-step. We can see that each step creates a new container, runs +Next you can see each instruction in the `Dockerfile` being executed +step-by-step. You can see that each step creates a new container, runs the instruction inside that container and then commits that change - -just like the `docker commit` work flow we saw earlier. When all the -instructions have executed we're left with the `97feabe5d2ed` image +just like the `docker commit` work flow you saw earlier. When all the +instructions have executed you're left with the `97feabe5d2ed` image (also helpfully tagged as `ouruser/sinatra:v2`) and all intermediate containers will get removed to clean things up. @@ -482,7 +462,7 @@ containers will get removed to clean things up. > This limitation is set globally to encourage optimization of the overall > size of images. -We can then create a container from our new image. +You can then create a container from our new image. $ docker run -t -i ouruser/sinatra:v2 /bin/bash root@8196968dac35:/# @@ -493,14 +473,14 @@ We can then create a container from our new image. > those instructions in later sections of the Guide or you can refer to the > [`Dockerfile`](../reference/builder.md) reference for a > detailed description and examples of every instruction. -> To help you write a clear, readable, maintainable `Dockerfile`, we've also +> To help you write a clear, readable, maintainable `Dockerfile`, you've also > written a [`Dockerfile` Best Practices guide](../articles/dockerfile_best-practices.md). ## Setting tags on an image You can also add a tag to an existing image after you commit or build it. We -can do this using the `docker tag` command. Let's add a new tag to our +can do this using the `docker tag` command. Now, add a new tag to your `ouruser/sinatra` image. $ docker tag 5db5f8471261 ouruser/sinatra:devel @@ -508,7 +488,7 @@ can do this using the `docker tag` command. Let's add a new tag to our The `docker tag` command takes the ID of the image, here `5db5f8471261`, and our user name, the repository name and the new tag. -Let's see our new tag using the `docker images` command. +Now, see your new tag using the `docker images` command. $ docker images ouruser/sinatra REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE @@ -553,7 +533,7 @@ private repository](https://registry.hub.docker.com/plans/). You can also remove images on your Docker host in a way [similar to containers](usingdocker.md) using the `docker rmi` command. -Let's delete the `training/sinatra` image as we don't need it anymore. +Delete the `training/sinatra` image as you don't need it anymore. $ docker rmi training/sinatra Untagged: training/sinatra:latest @@ -561,13 +541,13 @@ Let's delete the `training/sinatra` image as we don't need it anymore. Deleted: ed0fffdcdae5eb2c3a55549857a8be7fc8bc4241fb19ad714364cbfd7a56b22f Deleted: 5c58979d73ae448df5af1d8142436d81116187a7633082650549c52c3a2418f0 -> **Note:** In order to remove an image from the host, please make sure +> **Note:** To remove an image from the host, please make sure > that there are no containers actively based on it. # Next steps -Until now we've seen how to build individual applications inside Docker +Until now you've seen how to build individual applications inside Docker containers. Now learn how to build whole application stacks with Docker -by linking together multiple Docker containers. +by networking together multiple Docker containers. -Go to [Linking Containers Together](dockerlinks.md). +Go to [Network containers](networkingcontainers.md). diff --git a/docs/userguide/dockerizing.md b/docs/userguide/dockerizing.md index ed55f0012..588872b45 100644 --- a/docs/userguide/dockerizing.md +++ b/docs/userguide/dockerizing.md @@ -1,26 +1,27 @@ -# Dockerizing applications: A "Hello world" +# Hello world in a container *So what's this Docker thing all about?* -Docker allows you to run applications inside containers. Running an -application inside a container takes a single command: `docker run`. +Docker allows you to run applications, worlds you create, inside containers. +Running an application inside a container takes a single command: `docker run`. >**Note**: Depending on your Docker system configuration, you may be required to >preface each `docker` command on this page with `sudo`. To avoid this behavior, >your system administrator can create a Unix group called `docker` and add users ->to it. +>to it. -## Hello world +## Run a Hello world Let's try it now. @@ -132,7 +133,7 @@ a really long string: This really long string is called a *container ID*. It uniquely identifies a container so we can work with it. -> **Note:** +> **Note:** > The container ID is a bit long and unwieldy. A bit later, > we'll see a shorter ID and ways to name our containers to make > working with them easier. @@ -154,14 +155,14 @@ information about it, starting with a shorter variant of its container ID: We can also see the image we used to build it, `ubuntu:14.04`, the command it is running, its status and an automatically assigned name, -`insane_babbage`. +`insane_babbage`. -> **Note:** +> **Note:** > Docker automatically generates names for any containers started. > We'll see how to specify your own names a bit later. -Okay, so we now know it's running. But is it doing what we asked it to do? To see this -we're going to look inside the container using the `docker logs` +Okay, so we now know it's running. But is it doing what we asked it to do? To +see this we're going to look inside the container using the `docker logs` command. Let's use the container name Docker assigned. $ docker logs insane_babbage @@ -177,7 +178,7 @@ Awesome! Our daemon is working and we've just created our first Dockerized application! Now we've established we can create our own containers let's tidy up -after ourselves and stop our daemonized container. To do this we use the +after ourselves and stop our detached container. To do this we use the `docker stop` command. $ docker stop insane_babbage @@ -196,8 +197,15 @@ Excellent. Our container has been stopped. # Next steps -Now we've seen how simple it is to get started with Docker. Let's learn how to -do some more advanced tasks. +So far, you launched your first containers using the `docker run` command. You +ran an *interactive container* that ran in the foreground. You also ran a +*detached container* that ran in the background. In the process you learned +about several Docker commands: -Go to [Working With Containers](usingdocker.md). +* `docker ps` - Lists containers. +* `docker logs` - Shows us the standard output of a container. +* `docker stop` - Stops running containers. +Now, you have the basis learn more about Docker and how to do some more advanced +tasks. Go to ["*Run a simple application*"](usingdocker.md) to actually build a +web application with the Docker client. diff --git a/docs/userguide/dockernetworks.md b/docs/userguide/dockernetworks.md deleted file mode 100644 index 840644117..000000000 --- a/docs/userguide/dockernetworks.md +++ /dev/null @@ -1,519 +0,0 @@ - - -# Docker container networking - -So far we've been introduced to some [basic Docker -concepts](usingdocker.md), seen how to work with [Docker -images](dockerimages.md) as well as learned about basic [networking -and links between containers](dockerlinks.md). In this section -we're going to discuss how you can take control over more advanced -container networking. - -This section makes use of `docker network` commands and outputs to explain the -advanced networking functionality supported by Docker. - -# Default Networks - -By default, docker creates 3 networks using 3 different network drivers : - -``` -$ sudo docker network ls -NETWORK ID NAME DRIVER -7fca4eb8c647 bridge bridge -9f904ee27bf5 none null -cf03ee007fb4 host host -``` - -`docker network inspect` gives more information about a network - -``` -$ sudo docker network inspect bridge -{ - "name": "bridge", - "id": "7fca4eb8c647e57e9d46c32714271e0c3f8bf8d17d346629e2820547b2d90039", - "driver": "bridge", - "containers": {} -} -``` - -By default containers are launched on Bridge network - -``` -$ sudo docker run -itd --name=container1 busybox -f2870c98fd504370fb86e59f32cd0753b1ac9b69b7d80566ffc7192a82b3ed27 - -$ sudo docker run -itd --name=container2 busybox -bda12f8922785d1f160be70736f26c1e331ab8aaf8ed8d56728508f2e2fd4727 -``` - -``` -$ sudo docker network inspect bridge -{ - "name": "bridge", - "id": "7fca4eb8c647e57e9d46c32714271e0c3f8bf8d17d346629e2820547b2d90039", - "driver": "bridge", - "containers": { - "bda12f8922785d1f160be70736f26c1e331ab8aaf8ed8d56728508f2e2fd4727": { - "endpoint": "e0ac95934f803d7e36384a2029b8d1eeb56cb88727aa2e8b7edfeebaa6dfd758", - "mac_address": "02:42:ac:11:00:03", - "ipv4_address": "172.17.0.3/16", - "ipv6_address": "" - }, - "f2870c98fd504370fb86e59f32cd0753b1ac9b69b7d80566ffc7192a82b3ed27": { - "endpoint": "31de280881d2a774345bbfb1594159ade4ae4024ebfb1320cb74a30225f6a8ae", - "mac_address": "02:42:ac:11:00:02", - "ipv4_address": "172.17.0.2/16", - "ipv6_address": "" - } - } -} -``` -`docker network inspect` command above shows all the connected containers and its network resources on a given network - -Containers in a network should be able to communicate with each other using container names - -``` -$ sudo docker attach container1 - -/ # ifconfig -eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:02 - inet addr:172.17.0.2 Bcast:0.0.0.0 Mask:255.255.0.0 - inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link - UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 - RX packets:17 errors:0 dropped:0 overruns:0 frame:0 - TX packets:3 errors:0 dropped:0 overruns:0 carrier:0 - collisions:0 txqueuelen:0 - RX bytes:1382 (1.3 KiB) TX bytes:258 (258.0 B) - -lo Link encap:Local Loopback - inet addr:127.0.0.1 Mask:255.0.0.0 - inet6 addr: ::1/128 Scope:Host - UP LOOPBACK RUNNING MTU:65536 Metric:1 - RX packets:0 errors:0 dropped:0 overruns:0 frame:0 - TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 - collisions:0 txqueuelen:0 - RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) - -/ # ping container2 -PING container2 (172.17.0.3): 56 data bytes -64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.125 ms -64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.130 ms -64 bytes from 172.17.0.3: seq=2 ttl=64 time=0.172 ms -^C ---- container2 ping statistics --- -3 packets transmitted, 3 packets received, 0% packet loss -round-trip min/avg/max = 0.125/0.142/0.172 ms - -/ # cat /etc/hosts -172.17.0.2 f2870c98fd50 -127.0.0.1 localhost -::1 localhost ip6-localhost ip6-loopback -fe00::0 ip6-localnet -ff00::0 ip6-mcastprefix -ff02::1 ip6-allnodes -ff02::2 ip6-allrouters -172.17.0.2 container1 -172.17.0.2 container1.bridge -172.17.0.3 container2 -172.17.0.3 container2.bridge -``` - - -``` -$ sudo docker attach container2 - -/ # ifconfig -eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:03 - inet addr:172.17.0.3 Bcast:0.0.0.0 Mask:255.255.0.0 - inet6 addr: fe80::42:acff:fe11:3/64 Scope:Link - UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 - RX packets:8 errors:0 dropped:0 overruns:0 frame:0 - TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 - collisions:0 txqueuelen:0 - RX bytes:648 (648.0 B) TX bytes:648 (648.0 B) - -lo Link encap:Local Loopback - inet addr:127.0.0.1 Mask:255.0.0.0 - inet6 addr: ::1/128 Scope:Host - UP LOOPBACK RUNNING MTU:65536 Metric:1 - RX packets:0 errors:0 dropped:0 overruns:0 frame:0 - TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 - collisions:0 txqueuelen:0 - RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) - -/ # ping container1 -PING container1 (172.17.0.2): 56 data bytes -64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.277 ms -64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.179 ms -64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.130 ms -64 bytes from 172.17.0.2: seq=3 ttl=64 time=0.113 ms -^C ---- container1 ping statistics --- -4 packets transmitted, 4 packets received, 0% packet loss -round-trip min/avg/max = 0.113/0.174/0.277 ms -/ # cat /etc/hosts -172.17.0.3 bda12f892278 -127.0.0.1 localhost -::1 localhost ip6-localhost ip6-loopback -fe00::0 ip6-localnet -ff00::0 ip6-mcastprefix -ff02::1 ip6-allnodes -ff02::2 ip6-allrouters -172.17.0.2 container1 -172.17.0.2 container1.bridge -172.17.0.3 container2 -172.17.0.3 container2.bridge -/ # - -``` - -# User defined Networks - -In addition to the inbuilt networks, user can create networks using inbuilt drivers -(such as bridge or overlay driver) or external plugins supplied by the community. -Networks by definition should provides complete isolation for the containers. - -``` -$ docker network create -d bridge isolated_nw -8b05faa32aeb43215f67678084a9c51afbdffe64cd91e3f5bb8267475f8bf1a7 - -$ docker network inspect isolated_nw -{ - "name": "isolated_nw", - "id": "8b05faa32aeb43215f67678084a9c51afbdffe64cd91e3f5bb8267475f8bf1a7", - "driver": "bridge", - "containers": {} -} - -$ docker network ls -NETWORK ID NAME DRIVER -9f904ee27bf5 none null -cf03ee007fb4 host host -7fca4eb8c647 bridge bridge -8b05faa32aeb isolated_nw bridge - -``` - -Container can be launched on a user-defined network using the --net= option -in `docker run` command - -``` -$ docker run --net=isolated_nw -itd --name=container3 busybox -777344ef4943d34827a3504a802bf15db69327d7abe4af28a05084ca7406f843 - -$ docker network inspect isolated_nw -{ - "name": "isolated_nw", - "id": "8b05faa32aeb43215f67678084a9c51afbdffe64cd91e3f5bb8267475f8bf1a7", - "driver": "bridge", - "containers": { - "777344ef4943d34827a3504a802bf15db69327d7abe4af28a05084ca7406f843": { - "endpoint": "c7f22f8da07fb8ecc687d08377cfcdb80b4dd8624c2a8208b1a4268985e38683", - "mac_address": "02:42:ac:14:00:01", - "ipv4_address": "172.20.0.1/16", - "ipv6_address": "" - } - } -} -``` - - -# Connecting to Multiple networks - -Docker containers can dynamically connect to 1 or more networks with each network backed -by same or different network driver / plugin. - -``` -$ docker network connect isolated_nw container2 -$ docker network inspect isolated_nw -{ - "name": "isolated_nw", - "id": "8b05faa32aeb43215f67678084a9c51afbdffe64cd91e3f5bb8267475f8bf1a7", - "driver": "bridge", - "containers": { - "777344ef4943d34827a3504a802bf15db69327d7abe4af28a05084ca7406f843": { - "endpoint": "c7f22f8da07fb8ecc687d08377cfcdb80b4dd8624c2a8208b1a4268985e38683", - "mac_address": "02:42:ac:14:00:01", - "ipv4_address": "172.20.0.1/16", - "ipv6_address": "" - }, - "bda12f8922785d1f160be70736f26c1e331ab8aaf8ed8d56728508f2e2fd4727": { - "endpoint": "2ac11345af68b0750341beeda47cc4cce93bb818d8eb25e61638df7a4997cb1b", - "mac_address": "02:42:ac:14:00:02", - "ipv4_address": "172.20.0.2/16", - "ipv6_address": "" - } - } -} -``` - -Lets check the network resources used by container2. - -``` -$ docker inspect --format='{{.NetworkSettings.Networks}}' container2 -[bridge isolated_nw] - -$ sudo docker attach container2 - -/ # ifconfig -eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:03 - inet addr:172.17.0.3 Bcast:0.0.0.0 Mask:255.255.0.0 - inet6 addr: fe80::42:acff:fe11:3/64 Scope:Link - UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 - RX packets:21 errors:0 dropped:0 overruns:0 frame:0 - TX packets:18 errors:0 dropped:0 overruns:0 carrier:0 - collisions:0 txqueuelen:0 - RX bytes:1586 (1.5 KiB) TX bytes:1460 (1.4 KiB) - -eth1 Link encap:Ethernet HWaddr 02:42:AC:14:00:02 - inet addr:172.20.0.2 Bcast:0.0.0.0 Mask:255.255.0.0 - inet6 addr: fe80::42:acff:fe14:2/64 Scope:Link - UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 - RX packets:8 errors:0 dropped:0 overruns:0 frame:0 - TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 - collisions:0 txqueuelen:0 - RX bytes:648 (648.0 B) TX bytes:648 (648.0 B) - -lo Link encap:Local Loopback - inet addr:127.0.0.1 Mask:255.0.0.0 - inet6 addr: ::1/128 Scope:Host - UP LOOPBACK RUNNING MTU:65536 Metric:1 - RX packets:0 errors:0 dropped:0 overruns:0 frame:0 - TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 - collisions:0 txqueuelen:0 - RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) -``` - - -In the example discussed in this section thus far, container3 and container2 are -connected to isolated_nw and can talk to each other. -But container3 and container1 are not in the same network and hence they cannot communicate. - -``` -$ docker attach container3 - -/ # ifconfig -eth0 Link encap:Ethernet HWaddr 02:42:AC:14:00:01 - inet addr:172.20.0.1 Bcast:0.0.0.0 Mask:255.255.0.0 - inet6 addr: fe80::42:acff:fe14:1/64 Scope:Link - UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 - RX packets:24 errors:0 dropped:0 overruns:0 frame:0 - TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 - collisions:0 txqueuelen:0 - RX bytes:1944 (1.8 KiB) TX bytes:648 (648.0 B) - -lo Link encap:Local Loopback - inet addr:127.0.0.1 Mask:255.0.0.0 - inet6 addr: ::1/128 Scope:Host - UP LOOPBACK RUNNING MTU:65536 Metric:1 - RX packets:0 errors:0 dropped:0 overruns:0 frame:0 - TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 - collisions:0 txqueuelen:0 - RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) - -/ # ping container2.isolated_nw -PING container2.isolated_nw (172.20.0.2): 56 data bytes -64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.217 ms -64 bytes from 172.20.0.2: seq=1 ttl=64 time=0.150 ms -64 bytes from 172.20.0.2: seq=2 ttl=64 time=0.188 ms -64 bytes from 172.20.0.2: seq=3 ttl=64 time=0.176 ms -^C ---- container2.isolated_nw ping statistics --- -4 packets transmitted, 4 packets received, 0% packet loss -round-trip min/avg/max = 0.150/0.182/0.217 ms -/ # ping container2 -PING container2 (172.20.0.2): 56 data bytes -64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.120 ms -64 bytes from 172.20.0.2: seq=1 ttl=64 time=0.109 ms -^C ---- container2 ping statistics --- -2 packets transmitted, 2 packets received, 0% packet loss -round-trip min/avg/max = 0.109/0.114/0.120 ms - -/ # ping container1 -ping: bad address 'container1' - -/ # ping 172.17.0.2 -PING 172.17.0.2 (172.17.0.2): 56 data bytes -^C ---- 172.17.0.2 ping statistics --- -4 packets transmitted, 0 packets received, 100% packet loss - -/ # ping 172.17.0.3 -PING 172.17.0.3 (172.17.0.3): 56 data bytes -^C ---- 172.17.0.3 ping statistics --- -4 packets transmitted, 0 packets received, 100% packet loss - -``` - -While container2 is attached to both the networks (bridge and isolated_nw) and hence it -can talk to both container1 and container3 - -``` -$ docker attach container2 - -/ # cat /etc/hosts -172.17.0.3 bda12f892278 -127.0.0.1 localhost -::1 localhost ip6-localhost ip6-loopback -fe00::0 ip6-localnet -ff00::0 ip6-mcastprefix -ff02::1 ip6-allnodes -ff02::2 ip6-allrouters -172.17.0.2 container1 -172.17.0.2 container1.bridge -172.17.0.3 container2 -172.17.0.3 container2.bridge -172.20.0.1 container3 -172.20.0.1 container3.isolated_nw -172.20.0.2 container2 -172.20.0.2 container2.isolated_nw - -/ # ping container3 -PING container3 (172.20.0.1): 56 data bytes -64 bytes from 172.20.0.1: seq=0 ttl=64 time=0.138 ms -64 bytes from 172.20.0.1: seq=1 ttl=64 time=0.133 ms -64 bytes from 172.20.0.1: seq=2 ttl=64 time=0.133 ms -^C ---- container3 ping statistics --- -3 packets transmitted, 3 packets received, 0% packet loss -round-trip min/avg/max = 0.133/0.134/0.138 ms - -/ # ping container1 -PING container1 (172.17.0.2): 56 data bytes -64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.121 ms -64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.250 ms -64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.133 ms -^C ---- container1 ping statistics --- -3 packets transmitted, 3 packets received, 0% packet loss -round-trip min/avg/max = 0.121/0.168/0.250 ms -/ # -``` - - -Just like it is easy to connect a container to multiple networks, one can -disconnect a container from a network using the `docker network disconnect` command. - -``` -root@Ubuntu-vm ~$ docker network disconnect isolated_nw container2 - -$ docker inspect --format='{{.NetworkSettings.Networks}}' container2 -[bridge] - -root@Ubuntu-vm ~$ docker network inspect isolated_nw -{ - "name": "isolated_nw", - "id": "8b05faa32aeb43215f67678084a9c51afbdffe64cd91e3f5bb8267475f8bf1a7", - "driver": "bridge", - "containers": { - "777344ef4943d34827a3504a802bf15db69327d7abe4af28a05084ca7406f843": { - "endpoint": "c7f22f8da07fb8ecc687d08377cfcdb80b4dd8624c2a8208b1a4268985e38683", - "mac_address": "02:42:ac:14:00:01", - "ipv4_address": "172.20.0.1/16", - "ipv6_address": "" - } - } -} -``` - -Once a container is disconnected from a network, it cannot communicate with other containers -connected to that network. In this example, container2 cannot talk to container3 any more -in isolated_nw - -``` -$ sudo docker attach container2 - -/ # ifconfig -eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:03 - inet addr:172.17.0.3 Bcast:0.0.0.0 Mask:255.255.0.0 - inet6 addr: fe80::42:acff:fe11:3/64 Scope:Link - UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 - RX packets:26 errors:0 dropped:0 overruns:0 frame:0 - TX packets:23 errors:0 dropped:0 overruns:0 carrier:0 - collisions:0 txqueuelen:0 - RX bytes:1964 (1.9 KiB) TX bytes:1838 (1.7 KiB) - -lo Link encap:Local Loopback - inet addr:127.0.0.1 Mask:255.0.0.0 - inet6 addr: ::1/128 Scope:Host - UP LOOPBACK RUNNING MTU:65536 Metric:1 - RX packets:0 errors:0 dropped:0 overruns:0 frame:0 - TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 - collisions:0 txqueuelen:0 - RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) - -/ # ping container3 -PING container3 (172.20.0.1): 56 data bytes -^C ---- container3 ping statistics --- -2 packets transmitted, 0 packets received, 100% packet loss - - -But container2 still has full connectivity to the bridge network - -/ # ping container1 -PING container1 (172.17.0.2): 56 data bytes -64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.119 ms -64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.174 ms -^C ---- container1 ping statistics --- -2 packets transmitted, 2 packets received, 0% packet loss -round-trip min/avg/max = 0.119/0.146/0.174 ms -/ # - -``` - -When all the containers in a network stops or disconnected the network can be removed - -``` -$ docker network inspect isolated_nw -{ - "name": "isolated_nw", - "id": "8b05faa32aeb43215f67678084a9c51afbdffe64cd91e3f5bb8267475f8bf1a7", - "driver": "bridge", - "containers": {} -} - -$ docker network rm isolated_nw - -$ docker network ls -NETWORK ID NAME DRIVER -9f904ee27bf5 none null -cf03ee007fb4 host host -7fca4eb8c647 bridge bridge -``` - -# Native Multi-host networking - -With the help of libnetwork and the inbuilt `VXLAN based overlay network driver` docker supports multi-host networking natively out of the box. Technical details are documented under https://github.com/docker/libnetwork/blob/master/docs/overlay.md. -Using the exact same above `docker network` UI, the user can exercise the power of multi-host networking. - -In order to create a network using the inbuilt overlay driver, - -``` -$ docker network create -d overlay multi-host-network -``` - -Since `network` object is globally significant, this feature requires distributed states provided by `libkv`. Using `libkv`, the user can plug any of the supported Key-Value store (such as consul, etcd or zookeeper). -User can specify the Key-Value store of choice using the `--cluster-store` daemon flag, which takes configuration value of format `PROVIDER://URL`, where -`PROVIDER` is the name of the Key-Value store (such as consul, etcd or zookeeper) and -`URL` is the url to reach the Key-Value store. -Example : `docker daemon --cluster-store=consul://localhost:8500` - -# Next step - -Now that you know how to link Docker containers together, the next step is -learning how to manage data, volumes and mounts inside your containers. - -Go to [Managing Data in Containers](dockervolumes.md). diff --git a/docs/userguide/dockerrepos.md b/docs/userguide/dockerrepos.md index 292411261..f04254d86 100644 --- a/docs/userguide/dockerrepos.md +++ b/docs/userguide/dockerrepos.md @@ -1,28 +1,28 @@ -# Get started with Docker Hub +# Store images on Docker Hub -So far you've learned how to use the command line to run Docker on your local host. -You've learned how to [pull down images](usingdocker.md) to build containers -from existing images and you've learned how to [create your own images](dockerimages.md). +So far you've learned how to use the command line to run Docker on your local +host. You've learned how to [pull down images](usingdocker.md) to build +containers from existing images and you've learned how to [create your own +images](dockerimages.md). -Next, you're going to learn how to use the [Docker Hub](https://hub.docker.com) to -simplify and enhance your Docker workflows. +Next, you're going to learn how to use the [Docker Hub](https://hub.docker.com) +to simplify and enhance your Docker workflows. -The [Docker Hub](https://hub.docker.com) is a public registry maintained by Docker, -Inc. It contains over 15,000 images you can download and use to build containers. It also -provides authentication, work group structure, workflow tools like webhooks and build -triggers, and privacy tools like private repositories for storing images you don't want -to share publicly. +The [Docker Hub](https://hub.docker.com) is a public registry maintained by +Docker, Inc. It contains images you can download and use to build +containers. It also provides authentication, work group structure, workflow +tools like webhooks and build triggers, and privacy tools like private +repositories for storing images you don't want to share publicly. ## Docker commands and Docker Hub diff --git a/docs/userguide/dockervolumes.md b/docs/userguide/dockervolumes.md index 25775a21d..f44f5511d 100644 --- a/docs/userguide/dockervolumes.md +++ b/docs/userguide/dockervolumes.md @@ -1,22 +1,20 @@ -# Managing data in containers +# Manage data in containers -So far we've been introduced to some [basic Docker -concepts](usingdocker.md), seen how to work with [Docker -images](dockerimages.md) as well as learned about [networking -and links between containers](dockerlinks.md). In this section -we're going to discuss how you can manage data inside and between your -Docker containers. +So far we've been introduced to some [basic Docker concepts](usingdocker.md), +seen how to work with [Docker images](dockerimages.md) as well as learned about +[networking and links between containers](networking/default_network/dockerlinks.md). In this section we're +going to discuss how you can manage data inside and between your Docker +containers. We're going to look at the two primary ways you can manage data in Docker. @@ -27,21 +25,19 @@ Docker. ## Data volumes A *data volume* is a specially-designated directory within one or more -containers that bypasses the [*Union File -System*](../reference/glossary.md#union-file-system). Data volumes provide several -useful features for persistent or shared data: +containers that bypasses the [*Union File System*](../reference/glossary.md#union-file-system). Data volumes provide several useful features for persistent or shared data: - Volumes are initialized when a container is created. If the container's - base image contains data at the specified mount point, that existing data is + base image contains data at the specified mount point, that existing data is copied into the new volume upon volume initialization. - Data volumes can be shared and reused among containers. - Changes to a data volume are made directly. - Changes to a data volume will not be included when you update an image. - Data volumes persist even if the container itself is deleted. -Data volumes are designed to persist data, independent of the container's life -cycle. Docker therefore *never* automatically delete volumes when you remove -a container, nor will it "garbage collect" volumes that are no longer +Data volumes are designed to persist data, independent of the container's life +cycle. Docker therefore *never* automatically delete volumes when you remove +a container, nor will it "garbage collect" volumes that are no longer referenced by a container. ### Adding a data volume @@ -55,7 +51,7 @@ application container. This will create a new volume inside a container at `/webapp`. -> **Note:** +> **Note:** > You can also use the `VOLUME` instruction in a `Dockerfile` to add one or > more new volumes to any container created from that image. @@ -86,7 +82,7 @@ volumes. The output should look something similar to the following: ] ... -You will notice in the above 'Source' is specifying the location on the host and +You will notice in the above 'Source' is specifying the location on the host and 'Destination' is specifying the volume location inside the container. `RW` shows if the volume is read/write. @@ -105,17 +101,17 @@ image, the `/src/webapp` mount overlays but does not remove the pre-existing content. Once the mount is removed, the content is accessible again. This is consistent with the expected behavior of the `mount` command. -The `container-dir` must always be an absolute path such as `/src/docs`. -The `host-dir` can either be an absolute path or a `name` value. If you -supply an absolute path for the `host-dir`, Docker bind-mounts to the path +The `container-dir` must always be an absolute path such as `/src/docs`. +The `host-dir` can either be an absolute path or a `name` value. If you +supply an absolute path for the `host-dir`, Docker bind-mounts to the path you specify. If you supply a `name`, Docker creates a named volume by that `name`. -A `name` value must start with start with an alphanumeric character, -followed by `a-z0-9`, `_` (underscore), `.` (period) or `-` (hyphen). +A `name` value must start with start with an alphanumeric character, +followed by `a-z0-9`, `_` (underscore), `.` (period) or `-` (hyphen). An absolute path starts with a `/` (forward slash). -For example, you can specify either `/foo` or `foo` for a `host-dir` value. -If you supply the `/foo` value, Docker creates a bind-mount. If you supply +For example, you can specify either `/foo` or `foo` for a `host-dir` value. +If you supply the `/foo` value, Docker creates a bind-mount. If you supply the `foo` specification, Docker creates a named volume. If you are using Docker Machine on Mac or Windows, your Docker daemon has only limited access to your OS X or Windows filesystem. Docker Machine tries @@ -129,7 +125,7 @@ docker run -v /Users/:/ ... On Windows, mount directories using: ``` -docker run -v /c/Users/:/ ...` +docker run -v /c/Users/:/ ...` ``` All other paths come from your virtual machine's filesystem. For example, if @@ -155,10 +151,10 @@ Here we've mounted the same `/src/webapp` directory but we've added the `ro` option to specify that the mount should be read-only. Because of [limitations in the `mount` -function](http://lists.linuxfoundation.org/pipermail/containers/2015-April/ -035788.html), moving subdirectories within the host's source directory can give +function](http://lists.linuxfoundation.org/pipermail/containers/2015-April/035788.html), +moving subdirectories within the host's source directory can give access from the container to the host's file system. This requires a malicious -user with access to host and its mounted directory. +user with access to host and its mounted directory. >**Note**: The host directory is, by its nature, host-dependent. For this >reason, you can't mount a host directory from `Dockerfile` because built images @@ -182,20 +178,20 @@ Only the current container can use a private volume. ### Mount a host file as a data volume -The `-v` flag can also be used to mount a single file - instead of *just* +The `-v` flag can also be used to mount a single file - instead of *just* directories - from the host machine. $ docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash -This will drop you into a bash shell in a new container, you will have your bash -history from the host and when you exit the container, the host will have the +This will drop you into a bash shell in a new container, you will have your bash +history from the host and when you exit the container, the host will have the history of the commands typed while in the container. -> **Note:** -> Many tools used to edit files including `vi` and `sed --in-place` may result +> **Note:** +> Many tools used to edit files including `vi` and `sed --in-place` may result > in an inode change. Since Docker v1.1.0, this will produce an error such as -> "*sed: cannot rename ./sedKdJ9Dy: Device or resource busy*". In the case where -> you want to edit the mounted file, it is often easiest to instead mount the +> "*sed: cannot rename ./sedKdJ9Dy: Device or resource busy*". In the case where +> you want to edit the mounted file, it is often easiest to instead mount the > parent directory. ## Creating and mounting a data volume container @@ -238,9 +234,9 @@ be deleted. To delete the volume from disk, you must explicitly call `docker rm -v` against the last container with a reference to the volume. This allows you to upgrade, or effectively migrate data volumes between containers. -> **Note:** Docker will not warn you when removing a container *without* +> **Note:** Docker will not warn you when removing a container *without* > providing the `-v` option to delete its volumes. If you remove containers -> without using the `-v` option, you may end up with "dangling" volumes; +> without using the `-v` option, you may end up with "dangling" volumes; > volumes that are no longer referenced by a container. > Dangling volumes are difficult to get rid of and can take up a large amount > of disk space. We're working on improving volume management and you can check @@ -274,6 +270,12 @@ Then un-tar the backup file in the new container's data volume. You can use the techniques above to automate backup, migration and restore testing using your preferred tools. +## Important tips on using shared volumes + +Multiple containers can also share one or more data volumes. However, multiple containers writing to a single shared volume can cause data corruption. Make sure you're applications are designed to write to shared data stores. + +Data volumes are directly accessible from the Docker host. This means you can read and write to them with normal Linux tools. In most cases you should not do this as it can cause data corruption if your containers and applications are unaware of your direct access. + # Next steps Now we've learned a bit more about how to use Docker we're going to see how to diff --git a/docs/userguide/index.md b/docs/userguide/index.md index a8e2cad40..2813de6dd 100644 --- a/docs/userguide/index.md +++ b/docs/userguide/index.md @@ -11,10 +11,8 @@ parent = "mn_fun_docker" # Welcome to the Docker user guide In the [Introduction](../misc) you got a taste of what Docker is and how it -works. In this guide we're going to take you through the fundamentals of -using Docker and integrating it into your environment. - -We’ll teach you how to use Docker to: +works. This guide takes you through the fundamentals of using Docker and +integrating it into your environment. You'll learn how to use Docker to: * Dockerize your applications. * Run your own containers. @@ -22,8 +20,8 @@ We’ll teach you how to use Docker to: * Share your Docker images with others. * And a whole lot more! -We've broken this guide into major sections that take you through -the Docker life cycle: +This guide is broken into major sections that take you through the Docker life +cycle: ## Getting started with Docker Hub @@ -44,6 +42,7 @@ applications. To learn how to Dockerize applications and run them: Go to [Dockerizing Applications](dockerizing.md). + ## Working with containers *How do I manage my containers?* @@ -63,23 +62,13 @@ learn how to build your own application images with Docker. Go to [Working with Docker Images](dockerimages.md). -## Linking containers together +## Networking containers Until now we've seen how to build individual applications inside Docker containers. Now learn how to build whole application stacks with Docker -by linking together multiple Docker containers. +networking. -Go to [Linking Containers Together](dockerlinks.md). - -## Docker container networking - -Links provides a very easy and convenient way to connect the containers. -But, it is very opinionated and doesnt provide a lot of flexibility or -choice to the end-users. Now, lets learn about a flexible way to connect -containers together within a host or across multiple hosts in a cluster -using various networking technologies, with the help of extensible plugins. - -Go to [Docker Networking](dockernetworks.md). +Go to [Networking Containers](networkingcontainers.md). ## Managing data in containers @@ -136,4 +125,3 @@ Go to [Docker Swarm user guide](https://docs.docker.com/swarm/). * Get [Docker help](https://stackoverflow.com/search?q=docker) on StackOverflow * [Docker.com](https://www.docker.com/) - diff --git a/docs/userguide/labels-custom-metadata.md b/docs/userguide/labels-custom-metadata.md index 3bab14835..e4ac7c4cd 100644 --- a/docs/userguide/labels-custom-metadata.md +++ b/docs/userguide/labels-custom-metadata.md @@ -188,7 +188,7 @@ These labels appear as part of the `docker info` output for the daemon: $ docker -D info Containers: 12 Images: 672 - Engine Version: 1.9.0 + Server Version: 1.9.0 Storage Driver: aufs Root Dir: /var/lib/docker/aufs Backing Filesystem: extfs diff --git a/docs/userguide/networking/default_network/binding.md b/docs/userguide/networking/default_network/binding.md new file mode 100644 index 000000000..d8799f4fb --- /dev/null +++ b/docs/userguide/networking/default_network/binding.md @@ -0,0 +1,103 @@ + + +# Bind container ports to the host + +The information in this section explains binding container ports within the Docker default bridge. This is a `bridge` network named `bridge` created automatically when you install Docker. + +> **Note**: The [Docker networks feature](../dockernetworks.md) allows you to +create user-defined networks in addition to the default bridge network. + +By default Docker containers can make connections to the outside world, but the +outside world cannot connect to containers. Each outgoing connection will +appear to originate from one of the host machine's own IP addresses thanks to an +`iptables` masquerading rule on the host machine that the Docker server creates +when it starts: + +``` +$ sudo iptables -t nat -L -n +... +Chain POSTROUTING (policy ACCEPT) +target prot opt source destination +MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0 +... +``` +The Docker server creates a masquerade rule that let containers connect to IP +addresses in the outside world. + +If you want containers to accept incoming connections, you will need to provide +special options when invoking `docker run`. There are two approaches. + +First, you can supply `-P` or `--publish-all=true|false` to `docker run` which +is a blanket operation that identifies every port with an `EXPOSE` line in the +image's `Dockerfile` or `--expose ` commandline flag and maps it to a host +port somewhere within an _ephemeral port range_. The `docker port` command then +needs to be used to inspect created mapping. The _ephemeral port range_ is +configured by `/proc/sys/net/ipv4/ip_local_port_range` kernel parameter, +typically ranging from 32768 to 61000. + +Mapping can be specified explicitly using `-p SPEC` or `--publish=SPEC` option. +It allows you to particularize which port on docker server - which can be any +port at all, not just one within the _ephemeral port range_ -- you want mapped +to which port in the container. + +Either way, you should be able to peek at what Docker has accomplished in your +network stack by examining your NAT tables. + +``` +# What your NAT rules might look like when Docker +# is finished setting up a -P forward: + +$ iptables -t nat -L -n +... +Chain DOCKER (2 references) +target prot opt source destination +DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80 + +# What your NAT rules might look like when Docker +# is finished setting up a -p 80:80 forward: + +Chain DOCKER (2 references) +target prot opt source destination +DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80 +``` + +You can see that Docker has exposed these container ports on `0.0.0.0`, the +wildcard IP address that will match any possible incoming port on the host +machine. If you want to be more restrictive and only allow container services to +be contacted through a specific external interface on the host machine, you have +two choices. When you invoke `docker run` you can use either `-p +IP:host_port:container_port` or `-p IP::port` to specify the external interface +for one particular binding. + +Or if you always want Docker port forwards to bind to one specific IP address, +you can edit your system-wide Docker server settings and add the option +`--ip=IP_ADDRESS`. Remember to restart your Docker server after editing this +setting. + +> **Note**: With hairpin NAT enabled (`--userland-proxy=false`), containers port +exposure is achieved purely through iptables rules, and no attempt to bind the +exposed port is ever made. This means that nothing prevents shadowing a +previously listening service outside of Docker through exposing the same port +for a container. In such conflicting situation, Docker created iptables rules +will take precedence and route to the container. + +The `--userland-proxy` parameter, true by default, provides a userland +implementation for inter-container and outside-to-container communication. When +disabled, Docker uses both an additional `MASQUERADE` iptable rule and the +`net.ipv4.route_localnet` kernel parameter which allow the host machine to +connect to a local container exposed port through the commonly used loopback +address: this alternative is preferred for performance reasons. + +## Related information + +- [Understand Docker container networks](../dockernetworks.md) +- [Work with network commands](../work-with-networks.md) +- [Legacy container links](dockerlinks.md) diff --git a/docs/userguide/networking/default_network/build-bridges.md b/docs/userguide/networking/default_network/build-bridges.md new file mode 100644 index 000000000..a17d7fa27 --- /dev/null +++ b/docs/userguide/networking/default_network/build-bridges.md @@ -0,0 +1,77 @@ + + +# Build your own bridge + +This section explains building your own bridge to replaced the Docker default +bridge. This is a `bridge` network named `bridge` created automatically when you +install Docker. + +> **Note**: The [Docker networks feature](../dockernetworks.md) allows you to +create user-defined networks in addition to the default bridge network. + +You can set up your own bridge before starting Docker and use `-b BRIDGE` or +`--bridge=BRIDGE` to tell Docker to use your bridge instead. If you already +have Docker up and running with its default `docker0` still configured, you will +probably want to begin by stopping the service and removing the interface: + +``` +# Stopping Docker and removing docker0 + +$ sudo service docker stop +$ sudo ip link set dev docker0 down +$ sudo brctl delbr docker0 +$ sudo iptables -t nat -F POSTROUTING +``` + +Then, before starting the Docker service, create your own bridge and give it +whatever configuration you want. Here we will create a simple enough bridge +that we really could just have used the options in the previous section to +customize `docker0`, but it will be enough to illustrate the technique. + +``` +# Create our own bridge + +$ sudo brctl addbr bridge0 +$ sudo ip addr add 192.168.5.1/24 dev bridge0 +$ sudo ip link set dev bridge0 up + +# Confirming that our bridge is up and running + +$ ip addr show bridge0 +4: bridge0: mtu 1500 qdisc noop state UP group default + link/ether 66:38:d0:0d:76:18 brd ff:ff:ff:ff:ff:ff + inet 192.168.5.1/24 scope global bridge0 + valid_lft forever preferred_lft forever + +# Tell Docker about it and restart (on Ubuntu) + +$ echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker +$ sudo service docker start + +# Confirming new outgoing NAT masquerade is set up + +$ sudo iptables -t nat -L -n +... +Chain POSTROUTING (policy ACCEPT) +target prot opt source destination +MASQUERADE all -- 192.168.5.0/24 0.0.0.0/0 +``` + +The result should be that the Docker server starts successfully and is now +prepared to bind containers to the new bridge. After pausing to verify the +bridge's configuration, try creating a container -- you will see that its IP +address is in your new IP address range, which Docker will have auto-detected. + +You can use the `brctl show` command to see Docker add and remove interfaces +from the bridge as you start and stop containers, and can run `ip addr` and `ip +route` inside a container to see that it has been given an address in the +bridge's IP address range and has been told to use the Docker host's IP address +on the bridge as its default gateway to the rest of the Internet. diff --git a/docs/userguide/networking/default_network/configure-dns.md b/docs/userguide/networking/default_network/configure-dns.md new file mode 100644 index 000000000..5fe0d0e11 --- /dev/null +++ b/docs/userguide/networking/default_network/configure-dns.md @@ -0,0 +1,132 @@ + + +# Configure container DNS + +The information in this section explains configuring container DNS within +the Docker default bridge. This is a `bridge` network named `bridge` created +automatically when you install Docker. + +**Note**: The [Docker networks feature](../dockernetworks.md) allows you to create user-defined networks in addition to the default bridge network. + +How can Docker supply each container with a hostname and DNS configuration, without having to build a custom image with the hostname written inside? Its trick is to overlay three crucial `/etc` files inside the container with virtual files where it can write fresh information. You can see this by running `mount` inside a container: + +``` +$$ mount +... +/dev/disk/by-uuid/1fec...ebdf on /etc/hostname type ext4 ... +/dev/disk/by-uuid/1fec...ebdf on /etc/hosts type ext4 ... +/dev/disk/by-uuid/1fec...ebdf on /etc/resolv.conf type ext4 ... +... +``` + +This arrangement allows Docker to do clever things like keep `resolv.conf` up to date across all containers when the host machine receives new configuration over DHCP later. The exact details of how Docker maintains these files inside the container can change from one Docker version to the next, so you should leave the files themselves alone and use the following Docker options instead. + +Four different options affect container domain name services. + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ -h HOSTNAME or --hostname=HOSTNAME +

+
+

+ Sets the hostname by which the container knows itself. This is written + into /etc/hostname, into /etc/hosts as the name + of the container's host-facing IP address, and is the name that + /bin/bash inside the container will display inside its + prompt. But the hostname is not easy to see from outside the container. + It will not appear in docker ps nor in the + /etc/hosts file of any other container. +

+
+

+ --link=CONTAINER_NAME or ID:ALIAS +

+
+

+ Using this option as you run a container gives the new + container's /etc/hosts an extra entry named + ALIAS that points to the IP address of the container + identified by CONTAINER_NAME_or_ID. This lets processes + inside the new container connect to the hostname ALIAS + without having to know its IP. The --link= option is + discussed in more detail below. Because Docker may assign a different IP + address to the linked containers on restart, Docker updates the + ALIAS entry in the /etc/hosts file of the + recipient containers. +

+

+ --dns=IP_ADDRESS... +

+ Sets the IP addresses added as server lines to the container's + /etc/resolv.conf file. Processes in the container, when + confronted with a hostname not in /etc/hosts, will connect to + these IP addresses on port 53 looking for name resolution services.

+ --dns-search=DOMAIN... +

+ Sets the domain names that are searched when a bare unqualified hostname is + used inside of the container, by writing search lines into the + container's /etc/resolv.conf. When a container process attempts + to access host and the search domain example.com + is set, for instance, the DNS logic will not only look up host + but also host.example.com. +

+

+ Use --dns-search=. if you don't wish to set the search domain. +

+

+ --dns-opt=OPTION... +

+ Sets the options used by DNS resolvers by writing an options + line into the container's /etc/resolv.conf. +

+

+ See documentation for resolv.conf for a list of valid options +

+ + +Regarding DNS settings, in the absence of the `--dns=IP_ADDRESS...`, `--dns-search=DOMAIN...`, or `--dns-opt=OPTION...` options, Docker makes each container's `/etc/resolv.conf` look like the `/etc/resolv.conf` of the host machine (where the `docker` daemon runs). When creating the container's `/etc/resolv.conf`, the daemon filters out all localhost IP address `nameserver` entries from the host's original file. + +Filtering is necessary because all localhost addresses on the host are unreachable from the container's network. After this filtering, if there are no more `nameserver` entries left in the container's `/etc/resolv.conf` file, the daemon adds public Google DNS nameservers (8.8.8.8 and 8.8.4.4) to the container's DNS configuration. If IPv6 is enabled on the daemon, the public IPv6 Google DNS nameservers will also be added (2001:4860:4860::8888 and 2001:4860:4860::8844). + +> **Note**: If you need access to a host's localhost resolver, you must modify your DNS service on the host to listen on a non-localhost address that is reachable from within the container. + +You might wonder what happens when the host machine's `/etc/resolv.conf` file changes. The `docker` daemon has a file change notifier active which will watch for changes to the host DNS configuration. + +> **Note**: The file change notifier relies on the Linux kernel's inotify feature. Because this feature is currently incompatible with the overlay filesystem driver, a Docker daemon using "overlay" will not be able to take advantage of the `/etc/resolv.conf` auto-update feature. + +When the host file changes, all stopped containers which have a matching `resolv.conf` to the host will be updated immediately to this newest host configuration. Containers which are running when the host configuration changes will need to stop and start to pick up the host changes due to lack of a facility to ensure atomic writes of the `resolv.conf` file while the container is running. If the container's `resolv.conf` has been edited since it was started with the default configuration, no replacement will be attempted as it would overwrite the changes performed by the container. If the options (`--dns`, `--dns-search`, or `--dns-opt`) have been used to modify the default host configuration, then the replacement with an updated host's `/etc/resolv.conf` will not happen as well. + +> **Note**: For containers which were created prior to the implementation of the `/etc/resolv.conf` update feature in Docker 1.5.0: those containers will **not** receive updates when the host `resolv.conf` file changes. Only containers created with Docker 1.5.0 and above will utilize this auto-update feature. diff --git a/docs/userguide/networking/default_network/container-basics.md b/docs/userguide/networking/default_network/container-basics.md new file mode 100644 index 000000000..3f3905f66 --- /dev/null +++ b/docs/userguide/networking/default_network/container-basics.md @@ -0,0 +1,110 @@ + + + + +# How the default network + +The information in this section explains configuring container DNS within tthe Docker default bridge. This is a `bridge` network named `bridge` created +automatically when you install Docker. + +**Note**: The [Docker networks feature](../dockernetworks.md) allows you to create user-defined networks in addition to the default bridge network. + +While Docker is under active development and continues to tweak and improve its network configuration logic, the shell commands in this section are rough equivalents to the steps that Docker takes when configuring networking for each new container. + +## Review some basics + +To communicate using the Internet Protocol (IP), a machine needs access to at least one network interface at which packets can be sent and received, and a routing table that defines the range of IP addresses reachable through that interface. Network interfaces do not have to be physical devices. In fact, the `lo` loopback interface available on every Linux machine (and inside each Docker container) is entirely virtual -- the Linux kernel simply copies loopback packets directly from the sender's memory into the receiver's memory. + +Docker uses special virtual interfaces to let containers communicate with the host machine -- pairs of virtual interfaces called "peers" that are linked inside of the host machine's kernel so that packets can travel between them. They are simple to create, as we will see in a moment. + +The steps with which Docker configures a container are: +- Create a pair of peer virtual interfaces. +- Give one of them a unique name like `veth65f9`, keep it inside of the main Docker host, and bind it to `docker0` or whatever bridge Docker is supposed to be using. + +- Toss the other interface over the wall into the new container (which will already have been provided with an `lo` interface) and rename it to the much prettier name `eth0` since, inside of the container's separate and unique network interface namespace, there are no physical interfaces with which this name could collide. + +- Set the interface's MAC address according to the `--mac-address` parameter or generate a random one. + +- Give the container's `eth0` a new IP address from within the bridge's range of network addresses. The default route is set to the IP address passed to the Docker daemon using the `--default-gateway` option if specified, otherwise to the IP address that the Docker host owns on the bridge. The MAC address is generated from the IP address unless otherwise specified. This prevents ARP cache invalidation problems, when a new container comes up with an IP used in the past by another container with another MAC. + +With these steps complete, the container now possesses an `eth0` (virtual) network card and will find itself able to communicate with other containers and the rest of the Internet. + +You can opt out of the above process for a particular container by giving the `--net=` option to `docker run`, which takes four possible values. +- `--net=bridge` -- The default action, that connects the container to the Docker bridge as described above. + +- `--net=host` -- Tells Docker to skip placing the container inside of a separate network stack. In essence, this choice tells Docker to **not containerize the container's networking**! While container processes will still be confined to their own filesystem and process list and resource limits, a quick `ip addr` command will show you that, network-wise, they live "outside" in the main Docker host and have full access to its network interfaces. Note that this does **not** let the container reconfigure the host network stack -- that would require `--privileged=true` -- but it does let container processes open low-numbered ports like any other root process. It also allows the container to access local network services like D-bus. This can lead to processes in the container being able to do unexpected things like [restart your computer](https://github.com/docker/docker/issues/6401). You should use this option with caution. + +- `--net=container:NAME_or_ID` -- Tells Docker to put this container's processes inside of the network stack that has already been created inside of another container. The new container's processes will be confined to their own filesystem and process list and resource limits, but will share the same IP address and port numbers as the first container, and processes on the two containers will be able to connect to each other over the loopback interface. + +- `--net=none` -- Tells Docker to put the container inside of its own network stack but not to take any steps to configure its network, leaving you free to build any of the custom configurations explored in the last few sections of this document. + +## Manually network + +To get an idea of the steps that are necessary if you use `--net=none` as described in that last bullet point, here are the commands that you would run to reach roughly the same configuration as if you had let Docker do all of the configuration: + +``` +# At one shell, start a container and +# leave its shell idle and running + +$ docker run -i -t --rm --net=none base /bin/bash +root@63f36fc01b5f:/# + +# At another shell, learn the container process ID +# and create its namespace entry in /var/run/netns/ +# for the "ip netns" command we will be using below + +$ docker inspect -f '{{.State.Pid}}' 63f36fc01b5f +2778 +$ pid=2778 +$ sudo mkdir -p /var/run/netns +$ sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid + +# Check the bridge's IP address and netmask + +$ ip addr show docker0 +21: docker0: ... +inet 172.17.42.1/16 scope global docker0 +... + +# Create a pair of "peer" interfaces A and B, +# bind the A end to the bridge, and bring it up + +$ sudo ip link add A type veth peer name B +$ sudo brctl addif docker0 A +$ sudo ip link set A up + +# Place B inside the container's network namespace, +# rename to eth0, and activate it with a free IP + +$ sudo ip link set B netns $pid +$ sudo ip netns exec $pid ip link set dev B name eth0 +$ sudo ip netns exec $pid ip link set eth0 address 12:34:56:78:9a:bc +$ sudo ip netns exec $pid ip link set eth0 up +$ sudo ip netns exec $pid ip addr add 172.17.42.99/16 dev eth0 +$ sudo ip netns exec $pid ip route add default via 172.17.42.1 +``` + +At this point your container should be able to perform networking operations as usual. + +When you finally exit the shell and Docker cleans up the container, the network namespace is destroyed along with our virtual `eth0` -- whose destruction in turn destroys interface `A` out in the Docker host and automatically un-registers it from the `docker0` bridge. So everything gets cleaned up without our having to run any extra commands! Well, almost everything: + +``` +# Clean up dangling symlinks in /var/run/netns + +find -L /var/run/netns -type l -delete +``` + +Also note that while the script above used modern `ip` command instead of old deprecated wrappers like `ipconfig` and `route`, these older commands would also have worked inside of our container. The `ip addr` command can be typed as `ip a` if you are in a hurry. + +Finally, note the importance of the `ip netns exec` command, which let us reach inside and configure a network namespace as root. The same commands would not have worked if run inside of the container, because part of safe containerization is that Docker strips container processes of the right to configure their own networks. Using `ip netns exec` is what let us finish up the configuration without having to take the dangerous step of running the container itself with `--privileged=true`. diff --git a/docs/userguide/networking/default_network/container-communication.md b/docs/userguide/networking/default_network/container-communication.md new file mode 100644 index 000000000..0dc7016f4 --- /dev/null +++ b/docs/userguide/networking/default_network/container-communication.md @@ -0,0 +1,123 @@ + + +# Understand container communication + +The information in this section explains container communication within the +Docker default bridge. This is a `bridge` network named `bridge` created +automatically when you install Docker. + +**Note**: The [Docker networks feature](../dockernetworks.md) allows you to create user-defined networks in addition to the default bridge network. + +## Communicating to the outside world + +Whether a container can talk to the world is governed by two factors. The first +factor is whether the host machine is forwarding its IP packets. The second is +whether the hosts `iptables` allow this particular connections + +IP packet forwarding is governed by the `ip_forward` system parameter. Packets +can only pass between containers if this parameter is `1`. Usually you will +simply leave the Docker server at its default setting `--ip-forward=true` and +Docker will go set `ip_forward` to `1` for you when the server starts up. If you +set `--ip-forward=false` and your system's kernel has it enabled, the +`--ip-forward=false` option has no effect. To check the setting on your kernel +or to turn it on manually: +``` + $ sysctl net.ipv4.conf.all.forwarding + net.ipv4.conf.all.forwarding = 0 + $ sysctl net.ipv4.conf.all.forwarding=1 + $ sysctl net.ipv4.conf.all.forwarding + net.ipv4.conf.all.forwarding = 1 +``` + +Many using Docker will want `ip_forward` to be on, to at least make +communication _possible_ between containers and the wider world. May also be +needed for inter-container communication if you are in a multiple bridge setup. + +Docker will never make changes to your system `iptables` rules if you set +`--iptables=false` when the daemon starts. Otherwise the Docker server will +append forwarding rules to the `DOCKER` filter chain. + +Docker will not delete or modify any pre-existing rules from the `DOCKER` filter +chain. This allows the user to create in advance any rules required to further +restrict access to the containers. + +Docker's forward rules permit all external source IPs by default. To allow only +a specific IP or network to access the containers, insert a negated rule at the +top of the `DOCKER` filter chain. For example, to restrict external access such +that _only_ source IP 8.8.8.8 can access the containers, the following rule +could be added: + +``` +$ iptables -I DOCKER -i ext_if ! -s 8.8.8.8 -j DROP +``` + +## Communication between containers + +Whether two containers can communicate is governed, at the operating system level, by two factors. + +- Does the network topology even connect the containers' network interfaces? By default Docker will attach all containers to a single `docker0` bridge, providing a path for packets to travel between them. See the later sections of this document for other possible topologies. + +- Do your `iptables` allow this particular connection? Docker will never make changes to your system `iptables` rules if you set `--iptables=false` when the daemon starts. Otherwise the Docker server will add a default rule to the `FORWARD` chain with a blanket `ACCEPT` policy if you retain the default `--icc=true`, or else will set the policy to `DROP` if `--icc=false`. + +It is a strategic question whether to leave `--icc=true` or change it to +`--icc=false` so that `iptables` will protect other containers -- and the main +host -- from having arbitrary ports probed or accessed by a container that gets +compromised. + +If you choose the most secure setting of `--icc=false`, then how can containers +communicate in those cases where you _want_ them to provide each other services? +The answer is the `--link=CONTAINER_NAME_or_ID:ALIAS` option, which was +mentioned in the previous section because of its effect upon name services. If +the Docker daemon is running with both `--icc=false` and `--iptables=true` +then, when it sees `docker run` invoked with the `--link=` option, the Docker +server will insert a pair of `iptables` `ACCEPT` rules so that the new +container can connect to the ports exposed by the other container -- the ports +that it mentioned in the `EXPOSE` lines of its `Dockerfile`. + +> **Note**: The value `CONTAINER_NAME` in `--link=` must either be an +auto-assigned Docker name like `stupefied_pare` or else the name you assigned +with `--name=` when you ran `docker run`. It cannot be a hostname, which Docker +will not recognize in the context of the `--link=` option. + +You can run the `iptables` command on your Docker host to see whether the `FORWARD` chain has a default policy of `ACCEPT` or `DROP`: + +``` +# When --icc=false, you should see a DROP rule: + +$ sudo iptables -L -n +... +Chain FORWARD (policy ACCEPT) +target prot opt source destination +DOCKER all -- 0.0.0.0/0 0.0.0.0/0 +DROP all -- 0.0.0.0/0 0.0.0.0/0 +... + +# When a --link= has been created under --icc=false, +# you should see port-specific ACCEPT rules overriding +# the subsequent DROP policy for all other packets: + +$ sudo iptables -L -n +... +Chain FORWARD (policy ACCEPT) +target prot opt source destination +DOCKER all -- 0.0.0.0/0 0.0.0.0/0 +DROP all -- 0.0.0.0/0 0.0.0.0/0 + +Chain DOCKER (1 references) +target prot opt source destination +ACCEPT tcp -- 172.17.0.2 172.17.0.3 tcp spt:80 +ACCEPT tcp -- 172.17.0.3 172.17.0.2 tcp dpt:80 +``` + +> **Note**: Docker is careful that its host-wide `iptables` rules fully expose +containers to each other's raw IP addresses, so connections from one container +to another should always appear to be originating from the first container's own +IP address. diff --git a/docs/userguide/networking/default_network/custom-docker0.md b/docs/userguide/networking/default_network/custom-docker0.md new file mode 100644 index 000000000..494da6dec --- /dev/null +++ b/docs/userguide/networking/default_network/custom-docker0.md @@ -0,0 +1,61 @@ + + +# Customize the docker0 bridge + +The information in this section explains how to customize the Docker default bridge. This is a `bridge` network named `bridge` created automatically when you install Docker. + +**Note**: The [Docker networks feature](../dockernetworks.md) allows you to create user-defined networks in addition to the default bridge network. + +By default, the Docker server creates and configures the host system's `docker0` interface as an _Ethernet bridge_ inside the Linux kernel that can pass packets back and forth between other physical or virtual network interfaces so that they behave as a single Ethernet network. + +Docker configures `docker0` with an IP address, netmask and IP allocation range. The host machine can both receive and send packets to containers connected to the bridge, and gives it an MTU -- the _maximum transmission unit_ or largest packet length that the interface will allow -- of either 1,500 bytes or else a more specific value copied from the Docker host's interface that supports its default route. These options are configurable at server startup: +- `--bip=CIDR` -- supply a specific IP address and netmask for the `docker0` bridge, using standard CIDR notation like `192.168.1.5/24`. + +- `--fixed-cidr=CIDR` -- restrict the IP range from the `docker0` subnet, using the standard CIDR notation like `172.167.1.0/28`. This range must be an IPv4 range for fixed IPs (ex: 10.20.0.0/16) and must be a subset of the bridge IP range (`docker0` or set using `--bridge`). For example with `--fixed-cidr=192.168.1.0/25`, IPs for your containers will be chosen from the first half of `192.168.1.0/24` subnet. + +- `--mtu=BYTES` -- override the maximum packet length on `docker0`. + +Once you have one or more containers up and running, you can confirm that Docker has properly connected them to the `docker0` bridge by running the `brctl` command on the host machine and looking at the `interfaces` column of the output. Here is a host with two different containers connected: + +``` +# Display bridge info + +$ sudo brctl show +bridge name bridge id STP enabled interfaces +docker0 8000.3a1d7362b4ee no veth65f9 + vethdda6 +``` + +If the `brctl` command is not installed on your Docker host, then on Ubuntu you should be able to run `sudo apt-get install bridge-utils` to install it. + +Finally, the `docker0` Ethernet bridge settings are used every time you create a new container. Docker selects a free IP address from the range available on the bridge each time you `docker run` a new container, and configures the container's `eth0` interface with that IP address and the bridge's netmask. The Docker host's own IP address on the bridge is used as the default gateway by which each container reaches the rest of the Internet. + +``` +# The network, as seen from a container + +$ docker run -i -t --rm base /bin/bash + +$$ ip addr show eth0 +24: eth0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 + link/ether 32:6f:e0:35:57:91 brd ff:ff:ff:ff:ff:ff + inet 172.17.0.3/16 scope global eth0 + valid_lft forever preferred_lft forever + inet6 fe80::306f:e0ff:fe35:5791/64 scope link + valid_lft forever preferred_lft forever + +$$ ip route +default via 172.17.42.1 dev eth0 +172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.3 + +$$ exit +``` + +Remember that the Docker host will not be willing to forward container packets out on to the Internet unless its `ip_forward` system setting is `1` -- see the section above on [Communication between containers](#between-containers) for details. diff --git a/docs/userguide/dockerlinks.md b/docs/userguide/networking/default_network/dockerlinks.md similarity index 87% rename from docs/userguide/dockerlinks.md rename to docs/userguide/networking/default_network/dockerlinks.md index f4c4bc9ef..427ac586d 100644 --- a/docs/userguide/dockerlinks.md +++ b/docs/userguide/networking/default_network/dockerlinks.md @@ -1,36 +1,44 @@ -# Linking containers together +# Legacy container links -In [the Using Docker section](usingdocker.md), you saw how you can -connect to a service running inside a Docker container via a network -port. But a port connection is only one way you can interact with services and -applications running inside Docker containers. In this section, we'll briefly revisit -connecting via a network port and then we'll introduce you to another method of access: -container linking. +The information in this section explains legacy container links within the Docker default bridge. This is a `bridge` network named `bridge` created automatically when you install Docker. + +Before the [Docker networks feature](../dockernetworks.md), you could use the +Docker link feature to allow containers to discover each other and securely +transfer information about one container to another container. With the +introduction of the Docker networks feature, you can still create links but they +are only supported on the default `bridge` network named `bridge` and appearing +in your network stack as `docker0`. + +This section briefly discuss connecting via a network port and then goes into +detail on container linking. While links are still supported on Docker's default +network (`bridge bridge`), you should avoid them in preference of the Docker +networks feature. Linking is expected to be deprecated and removed in a future +release. ## Connect using network port mapping -In [the Using Docker section](usingdocker.md), you created a +In [the Using Docker section](../../usingdocker.md), you created a container that ran a Python Flask application: $ docker run -d -P training/webapp python app.py -> **Note:** +> **Note:** > Containers have an internal network and an IP address > (as we saw when we used the `docker inspect` command to show the container's -> IP address in the [Using Docker](usingdocker.md) section). +> IP address in the [Using Docker](../../usingdocker.md) section). > Docker can have a variety of network configurations. You can see more -> information on Docker networking [here](../articles/networking.md). +> information on Docker networking [here](../index.md). When that container was created, the `-P` flag was used to automatically map any network port inside it to a random high port within an *ephemeral port @@ -42,7 +50,7 @@ range* on your Docker host. Next, when `docker ps` was run, you saw that port bc533791f3f5 training/webapp:latest python app.py 5 seconds ago Up 2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse You also saw how you can bind a container's ports to a specific port using -the `-p` flag. Here port 80 of the host is mapped to port 5000 of the +the `-p` flag. Here port 80 of the host is mapped to port 5000 of the container: $ docker run -d -p 80:5000 training/webapp python app.py @@ -85,15 +93,15 @@ configurations. For example, if you've bound the container port to the $ docker port nostalgic_morse 5000 127.0.0.1:49155 -> **Note:** +> **Note:** > The `-p` flag can be used multiple times to configure multiple ports. ## Connect with the linking system -Network port mappings are not the only way Docker containers can connect -to one another. Docker also has a linking system that allows you to link -multiple containers together and send connection information from one to another. -When containers are linked, information about a source container can be sent to a +Network port mappings are not the only way Docker containers can connect to one +another. Docker also has a linking system that allows you to link multiple +containers together and send connection information from one to another. When +containers are linked, information about a source container can be sent to a recipient container. This allows the recipient to see selected data describing aspects of the source container. @@ -137,11 +145,11 @@ You can also use `docker inspect` to return the container's name. ## Communication across links -Links allow containers to discover each other and securely transfer information about one -container to another container. When you set up a link, you create a conduit between a -source container and a recipient container. The recipient can then access select data -about the source. To create a link, you use the `--link` flag. First, create a new -container, this time one containing a database. +Links allow containers to discover each other and securely transfer information +about one container to another container. When you set up a link, you create a +conduit between a source container and a recipient container. The recipient can +then access select data about the source. To create a link, you use the `--link` +flag. First, create a new container, this time one containing a database. $ docker run -d --name db training/postgres @@ -200,7 +208,7 @@ recipient container in two ways: Docker creates several environment variables when you link containers. Docker automatically creates environment variables in the target container based on -the `--link` parameters. It will also expose all environment variables +the `--link` parameters. It will also expose all environment variables originating from Docker from the source container. These include variables from: * the `ENV` commands in the source container's Dockerfile @@ -253,8 +261,8 @@ that port is used for both tcp and udp, then the tcp one is specified. Finally, Docker also exposes each Docker originated environment variable from the source container as an environment variable in the target. For each -variable Docker creates an `_ENV_` variable in the target -container. The variable's value is set to the value Docker used when it +variable Docker creates an `_ENV_` variable in the target +container. The variable's value is set to the value Docker used when it started the source container. Returning back to our database example, you can run the `env` @@ -306,7 +314,7 @@ container: You can see two relevant host entries. The first is an entry for the `web` container that uses the Container ID as a host name. The second entry uses the -link alias to reference the IP address of the `db` container. In addition to +link alias to reference the IP address of the `db` container. In addition to the alias you provide, the linked container's name--if unique from the alias provided to the `--link` parameter--and the linked container's hostname will also be added in `/etc/hosts` for the linked container's IP address. You can ping @@ -319,7 +327,7 @@ that host now via any of these entries: 56 bytes from 172.17.0.5: icmp_seq=1 ttl=64 time=0.250 ms 56 bytes from 172.17.0.5: icmp_seq=2 ttl=64 time=0.256 ms -> **Note:** +> **Note:** > In the example, you'll note you had to install `ping` because it was not included > in the container initially. @@ -327,7 +335,7 @@ Here, you used the `ping` command to ping the `db` container using its host entr which resolves to `172.17.0.5`. You can use this host entry to configure an application to make use of your `db` container. -> **Note:** +> **Note:** > You can link multiple recipient containers to a single source. For > example, you could have multiple (differently named) web containers attached to your >`db` container. @@ -344,10 +352,4 @@ allowing linked communication to continue. . . . 172.17.0.9 db -# Next step - -Now that you know how to link Docker containers together, the next step is -learning how to take complete control over docker networking. - -Go to [Docker Networking](dockernetworks.md). - +# Related information diff --git a/docs/article-img/ipv6_basic_host_config.gliffy b/docs/userguide/networking/default_network/images/ipv6_basic_host_config.gliffy similarity index 100% rename from docs/article-img/ipv6_basic_host_config.gliffy rename to docs/userguide/networking/default_network/images/ipv6_basic_host_config.gliffy diff --git a/docs/article-img/ipv6_basic_host_config.svg b/docs/userguide/networking/default_network/images/ipv6_basic_host_config.svg similarity index 100% rename from docs/article-img/ipv6_basic_host_config.svg rename to docs/userguide/networking/default_network/images/ipv6_basic_host_config.svg diff --git a/docs/article-img/ipv6_ndp_proxying.gliffy b/docs/userguide/networking/default_network/images/ipv6_ndp_proxying.gliffy similarity index 100% rename from docs/article-img/ipv6_ndp_proxying.gliffy rename to docs/userguide/networking/default_network/images/ipv6_ndp_proxying.gliffy diff --git a/docs/article-img/ipv6_ndp_proxying.svg b/docs/userguide/networking/default_network/images/ipv6_ndp_proxying.svg similarity index 100% rename from docs/article-img/ipv6_ndp_proxying.svg rename to docs/userguide/networking/default_network/images/ipv6_ndp_proxying.svg diff --git a/docs/article-img/ipv6_routed_network_example.gliffy b/docs/userguide/networking/default_network/images/ipv6_routed_network_example.gliffy similarity index 100% rename from docs/article-img/ipv6_routed_network_example.gliffy rename to docs/userguide/networking/default_network/images/ipv6_routed_network_example.gliffy diff --git a/docs/article-img/ipv6_routed_network_example.svg b/docs/userguide/networking/default_network/images/ipv6_routed_network_example.svg similarity index 100% rename from docs/article-img/ipv6_routed_network_example.svg rename to docs/userguide/networking/default_network/images/ipv6_routed_network_example.svg diff --git a/docs/article-img/ipv6_slash64_subnet_config.gliffy b/docs/userguide/networking/default_network/images/ipv6_slash64_subnet_config.gliffy similarity index 100% rename from docs/article-img/ipv6_slash64_subnet_config.gliffy rename to docs/userguide/networking/default_network/images/ipv6_slash64_subnet_config.gliffy diff --git a/docs/article-img/ipv6_slash64_subnet_config.svg b/docs/userguide/networking/default_network/images/ipv6_slash64_subnet_config.svg similarity index 100% rename from docs/article-img/ipv6_slash64_subnet_config.svg rename to docs/userguide/networking/default_network/images/ipv6_slash64_subnet_config.svg diff --git a/docs/article-img/ipv6_switched_network_example.gliffy b/docs/userguide/networking/default_network/images/ipv6_switched_network_example.gliffy similarity index 100% rename from docs/article-img/ipv6_switched_network_example.gliffy rename to docs/userguide/networking/default_network/images/ipv6_switched_network_example.gliffy diff --git a/docs/article-img/ipv6_switched_network_example.svg b/docs/userguide/networking/default_network/images/ipv6_switched_network_example.svg similarity index 100% rename from docs/article-img/ipv6_switched_network_example.svg rename to docs/userguide/networking/default_network/images/ipv6_switched_network_example.svg diff --git a/docs/userguide/networking/default_network/index.md b/docs/userguide/networking/default_network/index.md new file mode 100644 index 000000000..b72d1b499 --- /dev/null +++ b/docs/userguide/networking/default_network/index.md @@ -0,0 +1,25 @@ + + +# Docker default bridge network + +With the introduction of the Docker networks feature, you can create your own +user-defined networks. The Docker default bridge is created when you install +Docker Engine. It is a `bridge` network and is also named `bridge`. The topics +in this section are related to interacting with that default bridge network. + +- [Understand container communication](container-communication.md) +- [Legacy container links](dockerlinks.md) +- [Binding container ports to the host](binding.md) +- [Build your own bridge](build-bridges.md) +- [Configure container DNS](configure-dns.md) +- [Customize the docker0 bridge](custom-docker0.md) +- [IPv6 with Docker](ipv6.md) diff --git a/docs/userguide/networking/default_network/ipv6.md b/docs/userguide/networking/default_network/ipv6.md new file mode 100644 index 000000000..ec4871909 --- /dev/null +++ b/docs/userguide/networking/default_network/ipv6.md @@ -0,0 +1,259 @@ + + +# IPv6 with Docker + +The information in this section explains IPv6 with the Docker default bridge. +This is a `bridge` network named `bridge` created automatically when you install +Docker. + +As we are [running out of IPv4 +addresses](http://en.wikipedia.org/wiki/IPv4_address_exhaustion) the IETF has +standardized an IPv4 successor, [Internet Protocol Version +6](http://en.wikipedia.org/wiki/IPv6) , in [RFC +2460](https://www.ietf.org/rfc/rfc2460.txt). Both protocols, IPv4 and IPv6, +reside on layer 3 of the [OSI model](http://en.wikipedia.org/wiki/OSI_model). + +## How IPv6 works on Docker + +By default, the Docker server configures the container network for IPv4 only. +You can enable IPv4/IPv6 dualstack support by running the Docker daemon with the +`--ipv6` flag. Docker will set up the bridge `docker0` with the IPv6 [link-local +address](http://en.wikipedia.org/wiki/Link-local_address) `fe80::1`. + +By default, containers that are created will only get a link-local IPv6 address. +To assign globally routable IPv6 addresses to your containers you have to +specify an IPv6 subnet to pick the addresses from. Set the IPv6 subnet via the +`--fixed-cidr-v6` parameter when starting Docker daemon: + +``` +docker daemon --ipv6 --fixed-cidr-v6="2001:db8:1::/64" +``` + +The subnet for Docker containers should at least have a size of `/80`. This way +an IPv6 address can end with the container's MAC address and you prevent NDP +neighbor cache invalidation issues in the Docker layer. + +With the `--fixed-cidr-v6` parameter set Docker will add a new route to the +routing table. Further IPv6 routing will be enabled (you may prevent this by +starting Docker daemon with `--ip-forward=false`): + +``` +$ ip -6 route add 2001:db8:1::/64 dev docker0 +$ sysctl net.ipv6.conf.default.forwarding=1 +$ sysctl net.ipv6.conf.all.forwarding=1 +``` + +All traffic to the subnet `2001:db8:1::/64` will now be routed via the `docker0` interface. + +Be aware that IPv6 forwarding may interfere with your existing IPv6 +configuration: If you are using Router Advertisements to get IPv6 settings for +your host's interfaces you should set `accept_ra` to `2`. Otherwise IPv6 enabled +forwarding will result in rejecting Router Advertisements. E.g., if you want to +configure `eth0` via Router Advertisements you should set: + +``` +$ sysctl net.ipv6.conf.eth0.accept_ra=2 +``` + +![](images/ipv6_basic_host_config.svg) + +Every new container will get an IPv6 address from the defined subnet. Further a +default route will be added on `eth0` in the container via the address specified +by the daemon option `--default-gateway-v6` if present, otherwise via `fe80::1`: +``` +docker run -it ubuntu bash -c "ip -6 addr show dev eth0; ip -6 route show" + +15: eth0: mtu 1500 + inet6 2001:db8:1:0:0:242:ac11:3/64 scope global + valid_lft forever preferred_lft forever + inet6 fe80::42:acff:fe11:3/64 scope link + valid_lft forever preferred_lft forever + +2001:db8:1::/64 dev eth0 proto kernel metric 256 +fe80::/64 dev eth0 proto kernel metric 256 +default via fe80::1 dev eth0 metric 1024 +``` + +In this example the Docker container is assigned a link-local address with the +network suffix `/64` (here: `fe80::42:acff:fe11:3/64`) and a globally routable +IPv6 address (here: `2001:db8:1:0:0:242:ac11:3/64`). The container will create +connections to addresses outside of the `2001:db8:1::/64` network via the +link-local gateway at `fe80::1` on `eth0`. + +Often servers or virtual machines get a `/64` IPv6 subnet assigned (e.g. +`2001:db8:23:42::/64`). In this case you can split it up further and provide +Docker a `/80` subnet while using a separate `/80` subnet for other applications +on the host: + +![](images/ipv6_slash64_subnet_config.svg) + +In this setup the subnet `2001:db8:23:42::/80` with a range from +`2001:db8:23:42:0:0:0:0` to `2001:db8:23:42:0:ffff:ffff:ffff` is attached to +`eth0`, with the host listening at `2001:db8:23:42::1`. The subnet +`2001:db8:23:42:1::/80` with an address range from `2001:db8:23:42:1:0:0:0` to +`2001:db8:23:42:1:ffff:ffff:ffff` is attached to `docker0` and will be used by +containers. + +### Using NDP proxying + +If your Docker host is only part of an IPv6 subnet but has not got an IPv6 +subnet assigned you can use NDP proxying to connect your containers via IPv6 to +the internet. For example your host has the IPv6 address `2001:db8::c001`, is +part of the subnet `2001:db8::/64` and your IaaS provider allows you to +configure the IPv6 addresses `2001:db8::c000` to `2001:db8::c00f`: + +``` +$ ip -6 addr show +1: lo: mtu 65536 + inet6 ::1/128 scope host + valid_lft forever preferred_lft forever +2: eth0: mtu 1500 qlen 1000 + inet6 2001:db8::c001/64 scope global + valid_lft forever preferred_lft forever + inet6 fe80::601:3fff:fea1:9c01/64 scope link + valid_lft forever preferred_lft forever +``` + +Let's split up the configurable address range into two subnets +`2001:db8::c000/125` and `2001:db8::c008/125`. The first one can be used by the +host itself, the latter by Docker: + +``` +docker daemon --ipv6 --fixed-cidr-v6 2001:db8::c008/125 +``` + +You notice the Docker subnet is within the subnet managed by your router that is +connected to `eth0`. This means all devices (containers) with the addresses from +the Docker subnet are expected to be found within the router subnet. Therefore +the router thinks it can talk to these containers directly. + +![](images/ipv6_ndp_proxying.svg) + +As soon as the router wants to send an IPv6 packet to the first container it +will transmit a neighbor solicitation request, asking, who has `2001:db8::c009`? +But it will get no answer because no one on this subnet has this address. The +container with this address is hidden behind the Docker host. The Docker host +has to listen to neighbor solicitation requests for the container address and +send a response that itself is the device that is responsible for the address. +This is done by a Kernel feature called `NDP Proxy`. You can enable it by +executing + +``` +$ sysctl net.ipv6.conf.eth0.proxy_ndp=1 +``` + +Now you can add the container's IPv6 address to the NDP proxy table: + +``` +$ ip -6 neigh add proxy 2001:db8::c009 dev eth0 +``` + +This command tells the Kernel to answer to incoming neighbor solicitation +requests regarding the IPv6 address `2001:db8::c009` on the device `eth0`. As a +consequence of this all traffic to this IPv6 address will go into the Docker +host and it will forward it according to its routing table via the `docker0` +device to the container network: + +``` +$ ip -6 route show +2001:db8::c008/125 dev docker0 metric 1 +2001:db8::/64 dev eth0 proto kernel metric 256 +``` + +You have to execute the `ip -6 neigh add proxy ...` command for every IPv6 +address in your Docker subnet. Unfortunately there is no functionality for +adding a whole subnet by executing one command. An alternative approach would be +to use an NDP proxy daemon such as +[ndppd](https://github.com/DanielAdolfsson/ndppd). + +## Docker IPv6 cluster + +### Switched network environment +Using routable IPv6 addresses allows you to realize communication between +containers on different hosts. Let's have a look at a simple Docker IPv6 cluster +example: + +![](images/ipv6_switched_network_example.svg) + +The Docker hosts are in the `2001:db8:0::/64` subnet. Host1 is configured to +provide addresses from the `2001:db8:1::/64` subnet to its containers. It has +three routes configured: + +- Route all traffic to `2001:db8:0::/64` via `eth0` +- Route all traffic to `2001:db8:1::/64` via `docker0` +- Route all traffic to `2001:db8:2::/64` via Host2 with IP `2001:db8::2` + +Host1 also acts as a router on OSI layer 3. When one of the network clients +tries to contact a target that is specified in Host1's routing table Host1 will +forward the traffic accordingly. It acts as a router for all networks it knows: +`2001:db8::/64`, `2001:db8:1::/64` and `2001:db8:2::/64`. + +On Host2 we have nearly the same configuration. Host2's containers will get IPv6 +addresses from `2001:db8:2::/64`. Host2 has three routes configured: + +- Route all traffic to `2001:db8:0::/64` via `eth0` +- Route all traffic to `2001:db8:2::/64` via `docker0` +- Route all traffic to `2001:db8:1::/64` via Host1 with IP `2001:db8:0::1` + +The difference to Host1 is that the network `2001:db8:2::/64` is directly +attached to the host via its `docker0` interface whereas it reaches +`2001:db8:1::/64` via Host1's IPv6 address `2001:db8::1`. + +This way every container is able to contact every other container. The +containers `Container1-*` share the same subnet and contact each other directly. +The traffic between `Container1-*` and `Container2-*` will be routed via Host1 +and Host2 because those containers do not share the same subnet. + +In a switched environment every host has to know all routes to every subnet. +You always have to update the hosts' routing tables once you add or remove a +host to the cluster. + +Every configuration in the diagram that is shown below the dashed line is +handled by Docker: The `docker0` bridge IP address configuration, the route to +the Docker subnet on the host, the container IP addresses and the routes on the +containers. The configuration above the line is up to the user and can be +adapted to the individual environment. + +### Routed network environment +In a routed network environment you replace the layer 2 switch with a layer 3 +router. Now the hosts just have to know their default gateway (the router) and +the route to their own containers (managed by Docker). The router holds all +routing information about the Docker subnets. When you add or remove a host to +this environment you just have to update the routing table in the router - not +on every host. + +![](images/ipv6_routed_network_example.svg) + +In this scenario containers of the same host can communicate directly with each +other. The traffic between containers on different hosts will be routed via +their hosts and the router. For example packet from `Container1-1` to +`Container2-1` will be routed through `Host1`, `Router` and `Host2` until it +arrives at `Container2-1`. + +To keep the IPv6 addresses short in this example a `/48` network is assigned to +every host. The hosts use a `/64` subnet of this for its own services and one +for Docker. When adding a third host you would add a route for the subnet +`2001:db8:3::/48` in the router and configure Docker on Host3 with +`--fixed-cidr-v6=2001:db8:3:1::/64`. + +Remember the subnet for Docker containers should at least have a size of `/80`. +This way an IPv6 address can end with the container's MAC address and you +prevent NDP neighbor cache invalidation issues in the Docker layer. So if you +have a `/64` for your whole environment use `/78` subnets for the hosts and +`/80` for the containers. This way you can use 4096 hosts with 16 `/80` subnets +each. + +Every configuration in the diagram that is visualized below the dashed line is +handled by Docker: The `docker0` bridge IP address configuration, the route to +the Docker subnet on the host, the container IP addresses and the routes on the +containers. The configuration above the line is up to the user and can be +adapted to the individual environment. diff --git a/docs/userguide/networking/default_network/options.md b/docs/userguide/networking/default_network/options.md new file mode 100644 index 000000000..612dffbc5 --- /dev/null +++ b/docs/userguide/networking/default_network/options.md @@ -0,0 +1,141 @@ + + + + +# Quick guide to the options +Here is a quick list of the networking-related Docker command-line options, in case it helps you find the section below that you are looking for. + +Some networking command-line options can only be supplied to the Docker server when it starts up, and cannot be changed once it is running: +- `-b BRIDGE` or `--bridge=BRIDGE` -- see + + [Building your own bridge](#bridge-building) + +- `--bip=CIDR` -- see + + [Customizing docker0](#docker0) + +- `--default-gateway=IP_ADDRESS` -- see + + [How Docker networks a container](#container-networking) + +- `--default-gateway-v6=IP_ADDRESS` -- see + + [IPv6](#ipv6) + +- `--fixed-cidr` -- see + + [Customizing docker0](#docker0) + +- `--fixed-cidr-v6` -- see + + [IPv6](#ipv6) + +- `-H SOCKET...` or `--host=SOCKET...` -- + + This might sound like it would affect container networking, + + but it actually faces in the other direction: + + it tells the Docker server over what channels + + it should be willing to receive commands + + like "run container" and "stop container." + +- `--icc=true|false` -- see + + [Communication between containers](#between-containers) + +- `--ip=IP_ADDRESS` -- see + + [Binding container ports](#binding-ports) + +- `--ipv6=true|false` -- see + + [IPv6](#ipv6) + +- `--ip-forward=true|false` -- see + + [Communication between containers and the wider world](#the-world) + +- `--iptables=true|false` -- see + + [Communication between containers](#between-containers) + +- `--mtu=BYTES` -- see + + [Customizing docker0](#docker0) + +- `--userland-proxy=true|false` -- see + + [Binding container ports](#binding-ports) + +There are three networking options that can be supplied either at startup or when `docker run` is invoked. When provided at startup, set the default value that `docker run` will later use if the options are not specified: +- `--dns=IP_ADDRESS...` -- see + + [Configuring DNS](#dns) + +- `--dns-search=DOMAIN...` -- see + + [Configuring DNS](#dns) + +- `--dns-opt=OPTION...` -- see + + [Configuring DNS](#dns) + +Finally, several networking options can only be provided when calling `docker run` because they specify something specific to one container: +- `-h HOSTNAME` or `--hostname=HOSTNAME` -- see + + [Configuring DNS](#dns) and + + [How Docker networks a container](#container-networking) + +- `--link=CONTAINER_NAME_or_ID:ALIAS` -- see + + [Configuring DNS](#dns) and + + [Communication between containers](#between-containers) + +- `--net=bridge|none|container:NAME_or_ID|host` -- see + + [How Docker networks a container](#container-networking) + +- `--mac-address=MACADDRESS...` -- see + + [How Docker networks a container](#container-networking) + +- `-p SPEC` or `--publish=SPEC` -- see + + [Binding container ports](#binding-ports) + +- `-P` or `--publish-all=true|false` -- see + + [Binding container ports](#binding-ports) + +To supply networking options to the Docker server at startup, use the `DOCKER_OPTS` variable in the Docker upstart configuration file. For Ubuntu, edit the variable in `/etc/default/docker` or `/etc/sysconfig/docker` for CentOS. + +The following example illustrates how to configure Docker on Ubuntu to recognize a newly built bridge. + +Edit the `/etc/default/docker` file: + +``` +$ echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker +``` + +Then restart the Docker server. + +``` +$ sudo service docker start +``` + +For additional information on bridges, see [building your own bridge](#building-your-own-bridge) later on this page. diff --git a/docs/userguide/networking/default_network/saveme.md b/docs/userguide/networking/default_network/saveme.md new file mode 100644 index 000000000..aa2652ebd --- /dev/null +++ b/docs/userguide/networking/default_network/saveme.md @@ -0,0 +1,28 @@ + + + + + +## A Brief introduction to networking and docker +When Docker starts, it creates a virtual interface named `docker0` on the host machine. It randomly chooses an address and subnet from the private range defined by [RFC 1918](http://tools.ietf.org/html/rfc1918) that are not in use on the host machine, and assigns it to `docker0`. Docker made the choice `172.17.42.1/16` when I started it a few minutes ago, for example -- a 16-bit netmask providing 65,534 addresses for the host machine and its containers. The MAC address is generated using the IP address allocated to the container to avoid ARP collisions, using a range from `02:42:ac:11:00:00` to `02:42:ac:11:ff:ff`. + +> **Note:** This document discusses advanced networking configuration and options for Docker. In most cases you won't need this information. If you're looking to get started with a simpler explanation of Docker networking and an introduction to the concept of container linking see the [Docker User Guide](/userguide/networking/networking/default_network/dockerlinks.md/). + +But `docker0` is no ordinary interface. It is a virtual _Ethernet bridge_ that automatically forwards packets between any other network interfaces that are attached to it. This lets containers communicate both with the host machine and with each other. Every time Docker creates a container, it creates a pair of "peer" interfaces that are like opposite ends of a pipe -- a packet sent on one will be received on the other. It gives one of the peers to the container to become its `eth0` interface and keeps the other peer, with a unique name like `vethAQI2QT`, out in the namespace of the host machine. By binding every `veth*` interface to the `docker0` bridge, Docker creates a virtual subnet shared between the host machine and every Docker container. + +The remaining sections of this document explain all of the ways that you can use Docker options and -- in advanced cases -- raw Linux networking commands to tweak, supplement, or entirely replace Docker's default networking configuration. + +## Editing networking config files +Starting with Docker v.1.2.0, you can now edit `/etc/hosts`, `/etc/hostname` and `/etc/resolve.conf` in a running container. This is useful if you need to install bind or other services that might override one of those files. + +Note, however, that changes to these files will not be saved by `docker commit`, nor will they be saved during `docker run`. That means they won't be saved in the image, nor will they persist when a container is restarted; they will only "stick" in a running container. diff --git a/docs/userguide/networking/default_network/tools.md b/docs/userguide/networking/default_network/tools.md new file mode 100644 index 000000000..545c1e04c --- /dev/null +++ b/docs/userguide/networking/default_network/tools.md @@ -0,0 +1,83 @@ + + + + +# Tools and examples +Before diving into the following sections on custom network topologies, you might be interested in glancing at a few external tools or examples of the same kinds of configuration. Here are two: +- Jérôme Petazzoni has created a `pipework` shell script to help you + + connect together containers in arbitrarily complex scenarios: + + [https://github.com/jpetazzo/pipework](https://github.com/jpetazzo/pipework) + +- Brandon Rhodes has created a whole network topology of Docker + + containers for the next edition of Foundations of Python Network + + Programming that includes routing, NAT'd firewalls, and servers that + + offer HTTP, SMTP, POP, IMAP, Telnet, SSH, and FTP: + + [https://github.com/brandon-rhodes/fopnp/tree/m/playground](https://github.com/brandon-rhodes/fopnp/tree/m/playground) + +Both tools use networking commands very much like the ones you saw in the previous section, and will see in the following sections. + +# Building a point-to-point connection + + +By default, Docker attaches all containers to the virtual subnet implemented by `docker0`. You can create containers that are each connected to some different virtual subnet by creating your own bridge as shown in [Building your own bridge](#bridge-building), starting each container with `docker run --net=none`, and then attaching the containers to your bridge with the shell commands shown in [How Docker networks a container](#container-networking). + +But sometimes you want two particular containers to be able to communicate directly without the added complexity of both being bound to a host-wide Ethernet bridge. + +The solution is simple: when you create your pair of peer interfaces, simply throw _both_ of them into containers, and configure them as classic point-to-point links. The two containers will then be able to communicate directly (provided you manage to tell each container the other's IP address, of course). You might adjust the instructions of the previous section to go something like this: + +``` +# Start up two containers in two terminal windows + +$ docker run -i -t --rm --net=none base /bin/bash +root@1f1f4c1f931a:/# + +$ docker run -i -t --rm --net=none base /bin/bash +root@12e343489d2f:/# + +# Learn the container process IDs +# and create their namespace entries + +$ docker inspect -f '{{.State.Pid}}' 1f1f4c1f931a +2989 +$ docker inspect -f '{{.State.Pid}}' 12e343489d2f +3004 +$ sudo mkdir -p /var/run/netns +$ sudo ln -s /proc/2989/ns/net /var/run/netns/2989 +$ sudo ln -s /proc/3004/ns/net /var/run/netns/3004 + +# Create the "peer" interfaces and hand them out + +$ sudo ip link add A type veth peer name B + +$ sudo ip link set A netns 2989 +$ sudo ip netns exec 2989 ip addr add 10.1.1.1/32 dev A +$ sudo ip netns exec 2989 ip link set A up +$ sudo ip netns exec 2989 ip route add 10.1.1.2/32 dev A + +$ sudo ip link set B netns 3004 +$ sudo ip netns exec 3004 ip addr add 10.1.1.2/32 dev B +$ sudo ip netns exec 3004 ip link set B up +$ sudo ip netns exec 3004 ip route add 10.1.1.1/32 dev B +``` + +The two containers should now be able to ping each other and make connections successfully. Point-to-point links like this do not depend on a subnet nor a netmask, but on the bare assertion made by `ip route` that some other single IP address is connected to a particular network interface. + +Note that point-to-point links can be safely combined with other kinds of network connectivity -- there is no need to start the containers with `--net=none` if you want point-to-point links to be an addition to the container's normal networking instead of a replacement. + +A final permutation of this pattern is to create the point-to-point link between the Docker host and one container, which would allow the host to communicate with that one container on some single IP address and thus communicate "out-of-band" of the bridge that connects the other, more usual containers. But unless you have very specific networking needs that drive you to such a solution, it is probably far preferable to use `--icc=false` to lock down inter-container communication, as we explored earlier. diff --git a/docs/userguide/networking/dockernetworks.md b/docs/userguide/networking/dockernetworks.md new file mode 100644 index 000000000..2940e9ac3 --- /dev/null +++ b/docs/userguide/networking/dockernetworks.md @@ -0,0 +1,480 @@ + + +# Understand Docker container networks + +To build web applications that act in concert but do so securely, use the Docker +networks feature. Networks, by definition, provide complete isolation for +containers. So, it is important to have control over the networks your +applications run on. Docker container networks give you that control. + +This section provides an overview of the default networking behavior that Docker +Engine delivers natively. It describes the type of networks created by default +and how to create your own, user--defined networks. It also describes the +resources required to create networks on a single host or across a cluster of +hosts. + +## Default Networks + +When you install Docker, it creates three networks automatically. You can list +these networks using the `docker network ls` command: + +``` +$ docker network ls +NETWORK ID NAME DRIVER +7fca4eb8c647 bridge bridge +9f904ee27bf5 none null +cf03ee007fb4 host host +``` + +Historically, these three networks are part of Docker's implementation. When +you run a container you can use the `--net` flag to specify which network you +want to run a container on. These three networks are still available to you. + +The `bridge` network represents the `docker0` network present in all Docker +installations. Unless you specify otherwise with the `docker run +--net=` option, the Docker daemon connects containers to this network +by default. You can see this bridge as part of a host's network stack by using +the `ifconfig` command on the host. + +``` +ubuntu@ip-172-31-36-118:~$ ifconfig +docker0 Link encap:Ethernet HWaddr 02:42:47:bc:3a:eb + inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0 + inet6 addr: fe80::42:47ff:febc:3aeb/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:9001 Metric:1 + RX packets:17 errors:0 dropped:0 overruns:0 frame:0 + TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:1100 (1.1 KB) TX bytes:648 (648.0 B) +``` + +The `none` network adds a container to a container-specific network stack. That container lacks a network interface. Attaching to such a container and looking at it's stack you see this: + +``` +ubuntu@ip-172-31-36-118:~$ docker attach nonenetcontainer + +/ # cat /etc/hosts +127.0.0.1 localhost +::1 localhost ip6-localhost ip6-loopback +fe00::0 ip6-localnet +ff00::0 ip6-mcastprefix +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters +/ # ifconfig +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:65536 Metric:1 + RX packets:0 errors:0 dropped:0 overruns:0 frame:0 + TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) + +/ # +``` +>**Note**: You can detach from the container and leave it running with `CTRL-p CTRL-q`. + +The `host` network adds a container on the hosts network stack. You'll find the +network configuration inside the container is identical to the host. + +With the exception of the the `bridge` network, you really don't need to +interact with these default networks. While you can list and inspect them, you +cannot remove them. They are required by your Docker installation. However, you +can add your own user-defined networks and these you can remove when you no +longer need them. Before you learn more about creating your own networks, it is +worth looking at the `default` network a bit. + + +### The default bridge network in detail +The default bridge network is present on all Docker hosts. The `docker network inspect` + +``` +$ docker network inspect bridge +[ + { + "Name": "bridge", + "Id": "f7ab26d71dbd6f557852c7156ae0574bbf62c42f539b50c8ebde0f728a253b6f", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [ + { + "Subnet": "172.17.0.1/16", + "Gateway": "172.17.0.1" + } + ] + }, + "Containers": {}, + "Options": { + "com.docker.network.bridge.default_bridge": "true", + "com.docker.network.bridge.enable_icc": "true", + "com.docker.network.bridge.enable_ip_masquerade": "true", + "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", + "com.docker.network.bridge.name": "docker0", + "com.docker.network.driver.mtu": "9001" + } + } +] +``` +The Engine automatically creates a `Subnet` and `Gateway` to the network. +The `docker run` command automatically adds new containers to this network. + +``` +$ docker run -itd --name=container1 busybox +3386a527aa08b37ea9232cbcace2d2458d49f44bb05a6b775fba7ddd40d8f92c + +$ docker run -itd --name=container2 busybox +94447ca479852d29aeddca75c28f7104df3c3196d7b6d83061879e339946805c +``` + +Inspecting the `bridge` network again after starting two containers shows both newly launched containers in the network. Their ids show up in the container + +``` +$ docker network inspect bridge +{[ + { + "Name": "bridge", + "Id": "f7ab26d71dbd6f557852c7156ae0574bbf62c42f539b50c8ebde0f728a253b6f", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [ + { + "Subnet": "172.17.0.1/16", + "Gateway": "172.17.0.1" + } + ] + }, + "Containers": { + "3386a527aa08b37ea9232cbcace2d2458d49f44bb05a6b775fba7ddd40d8f92c": { + "EndpointID": "647c12443e91faf0fd508b6edfe59c30b642abb60dfab890b4bdccee38750bc1", + "MacAddress": "02:42:ac:11:00:02", + "IPv4Address": "172.17.0.2/16", + "IPv6Address": "" + }, + "94447ca479852d29aeddca75c28f7104df3c3196d7b6d83061879e339946805c": { + "EndpointID": "b047d090f446ac49747d3c37d63e4307be745876db7f0ceef7b311cbba615f48", + "MacAddress": "02:42:ac:11:00:03", + "IPv4Address": "172.17.0.3/16", + "IPv6Address": "" + } + }, + "Options": { + "com.docker.network.bridge.default_bridge": "true", + "com.docker.network.bridge.enable_icc": "true", + "com.docker.network.bridge.enable_ip_masquerade": "true", + "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", + "com.docker.network.bridge.name": "docker0", + "com.docker.network.driver.mtu": "9001" + } + } +] +``` + +The `docker network inspect` command above shows all the connected containers and their network resources on a given network. Containers in this default network are able to communicate with each other using IP addresses. Docker does not support automatic service discovery on the default bridge network. If you want to communicate with container names in this default bridge network, you must connect the containers via the legacy `docker run --link` option. + +You can `attach` to a running `container` and investigate its configuration: + +``` +$ docker attach container1 + +/ # ifconfig +ifconfig +eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:02 + inet addr:172.17.0.2 Bcast:0.0.0.0 Mask:255.255.0.0 + inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:9001 Metric:1 + RX packets:16 errors:0 dropped:0 overruns:0 frame:0 + TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:1296 (1.2 KiB) TX bytes:648 (648.0 B) + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:65536 Metric:1 + RX packets:0 errors:0 dropped:0 overruns:0 frame:0 + TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) +``` + +Then use `ping` for about 3 seconds to test the connectivity of the containers on this `bridge` network. + +``` +/ # ping -w3 172.17.0.3 +PING 172.17.0.3 (172.17.0.3): 56 data bytes +64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.096 ms +64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.080 ms +64 bytes from 172.17.0.3: seq=2 ttl=64 time=0.074 ms + +--- 172.17.0.3 ping statistics --- +3 packets transmitted, 3 packets received, 0% packet loss +round-trip min/avg/max = 0.074/0.083/0.096 ms +``` + +Finally, use the `cat` command to check the `container1` network configuration: + +``` +/ # cat /etc/hosts +172.17.0.2 3386a527aa08 +127.0.0.1 localhost +::1 localhost ip6-localhost ip6-loopback +fe00::0 ip6-localnet +ff00::0 ip6-mcastprefix +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters +``` +To detach from a `container1` and leave it running use `CTRL-p CTRL-q`.Then, attach to `container2` and repeat these three commands. + +``` +$ docker attach container2 + +/ # ifconfig +eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:03 + inet addr:172.17.0.3 Bcast:0.0.0.0 Mask:255.255.0.0 + inet6 addr: fe80::42:acff:fe11:3/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:9001 Metric:1 + RX packets:15 errors:0 dropped:0 overruns:0 frame:0 + TX packets:13 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:1166 (1.1 KiB) TX bytes:1026 (1.0 KiB) + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:65536 Metric:1 + RX packets:0 errors:0 dropped:0 overruns:0 frame:0 + TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) + +/ # ping -w3 172.17.0.2 +PING 172.17.0.2 (172.17.0.2): 56 data bytes +64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.067 ms +64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.075 ms +64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.072 ms + +--- 172.17.0.2 ping statistics --- +3 packets transmitted, 3 packets received, 0% packet loss +round-trip min/avg/max = 0.067/0.071/0.075 ms +/ # cat /etc/hosts +172.17.0.3 94447ca47985 +127.0.0.1 localhost +::1 localhost ip6-localhost ip6-loopback +fe00::0 ip6-localnet +ff00::0 ip6-mcastprefix +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters +``` + +The default `docker0` bridge network supports the use of port mapping and `docker run --link` to allow communications between containers in the `docker0` network. These techniques are cumbersome to set up and prone to error. While they are still available to you as techniques, it is better to avoid them and define your own bridge networks instead. + +## User-defined networks + +You can create your own user-defined networks that better isolate containers. +Docker provides some default **network drivers** for use creating these +networks. You can create a new **bridge network** or **overlay network**. You +can also create a **network plugin** or **remote network** written to your own +specifications. + +You can create multiple networks. You can add containers to more than one +network. Containers can only communicate within networks but not across +networks. A container attached to two networks can communicate with member +containers in either network. + +The next few sections describe each of Docker's built-in network drivers in +greater detail. + +### A bridge network + +The easiest user-defined network to create is a `bridge` network. This network +is similar to the historical, default `docker0` network. There are some added +features and some old features that aren't available. + +``` +$ docker network create --driver bridge isolated_nw +c5ee82f76de30319c75554a57164c682e7372d2c694fec41e42ac3b77e570f6b + +$ docker network inspect isolated_nw +[ + { + "Name": "isolated_nw", + "Id": "c5ee82f76de30319c75554a57164c682e7372d2c694fec41e42ac3b77e570f6b", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [ + {} + ] + }, + "Containers": {}, + "Options": {} + } +] + +$ docker network ls +NETWORK ID NAME DRIVER +9f904ee27bf5 none null +cf03ee007fb4 host host +7fca4eb8c647 bridge bridge +c5ee82f76de3 isolated_nw bridge + +``` + +After you create the network, you can launch containers on it using the `docker run --net=` option. + +``` +$ docker run --net=isolated_nw -itd --name=container3 busybox +885b7b4f792bae534416c95caa35ba272f201fa181e18e59beba0c80d7d77c1d + +$ docker network inspect isolated_nw +[ + { + "Name": "isolated_nw", + "Id": "c5ee82f76de30319c75554a57164c682e7372d2c694fec41e42ac3b77e570f6b", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [ + {} + ] + }, + "Containers": { + "885b7b4f792bae534416c95caa35ba272f201fa181e18e59beba0c80d7d77c1d": { + "EndpointID": "514e1b419074397ea92bcfaa6698d17feb62db49d1320a27393b853ec65319c3", + "MacAddress": "02:42:ac:15:00:02", + "IPv4Address": "172.21.0.2/16", + "IPv6Address": "" + } + }, + "Options": {} + } +] +``` + +The containers you launch into this network must reside on the same Docker host. +Each container in the network can immediately communicate with other containers +in the network. Though, the network itself isolates the containers from external +networks. + +![An isolated network](images/bridge_network.png) + +Within a user-defined bridge network, linking is not supported. You can +expose and publish container ports on containers in this network. This is useful +if you want make a portion of the `bridge` network available to an outside +network. + +![Bridge network](images/network_access.png) + +A bridge network is useful in cases where you want to run a relatively small +network on a single host. You can, however, create significantly larger networks +by creating an `overlay` network. + + +### An overlay network + +Docker's `overlay` network driver supports multi-host networking natively +out-of-the-box. This support is accomplished with the help of `libnetwork`, a +built-in VXLAN-based overlay network driver, and Docker's `libkv` library. + +The `overlay` network requires a valid key-value store service. Currently, +Docker's supports Consul, Etcd, and ZooKeeper (Distributed store). Before +creating a network you must install and configure your chosen key-value store +service. The Docker hosts that you intend to network and the service must be +able to communicate. + +![Key-value store](images/key_value.png) + +Each host in the network must run a Docker Engine instance. The easiest way to +provision the hosts are with Docker Machine. + +![Engine on each host](images/engine_on_net.png) + +Once you have several machines provisioned, you can use Docker Swarm to quickly +form them into a swarm which includes a discovery service as well. + +To create an overlay network, you configure options on the `daemon` on each +Docker Engine for use with `overlay` network. There are two options to set: + +| Option | Description | +|----------------------------------|-----------------------------------------------------------| +| `--cluster-store=PROVIDER://URL` | Describes the location of the KV service. | +| `--cluster-advertise=HOST_IP` | Advertises containers created by the HOST on the network. | + +Create an `overlay` network on one of the machines in the Swarm. + + $ docker network create --driver overlay my-multi-host-network + +This results in a single network spanning multiple hosts. An `overlay` network +provides complete isolation for the containers. + +![An overlay network](images/overlay_network.png) + +Then, on each host, launch containers making sure to specify the network name. + + $ docker run -itd --net=mmy-multi-host-network busybox + +Once connected, each container has access to all the containers in the network +regardless of which Docker host the container was launched on. + +![Published port](images/overlay-network-final.png) + +If you would like to try this for yourself, see the [Getting started for +overlay](get-started-overlay.md). + +### Custom network plugin + +If you like, you can write your own network driver plugin. A network +driver plugin makes use of Docker's plugin infrastructure. In this +infrastructure, a plugin is a process running on the same Docker host as the +Docker `daemon`. + +Network plugins follow the same restrictions and installation rules as other +plugins. All plugins make use of the plugin API. They have a lifecycle that +encompasses installation, starting, stopping and activation. + +Once you have created and installed a custom network driver, you use it like the +built-in network drivers. For example: + + $ docker network create --driver weave mynet + +You can inspect it, add containers too and from it, and so forth. Of course, +different plugins may make use of different technologies or frameworks. Custom +networks can include features not present in Docker's default networks. For more +information on writing plugins, see [Extending Docker](../../extend) and +[Writing a network driver plugin](../../extend/plugins_network.md). + +## Legacy links + +Before the Docker network feature, you could use the Docker link feature to +allow containers to discover each other and securely transfer information about +one container to another container. With the introduction of Docker networks, +you can still create links but they are only supported on the default `bridge` +network named `bridge` and appearing in your network stack as `docker0`. + +While links are still supported in this limited capacity, you should avoid them +in preference of Docker networks. The link feature is expected to be deprecated +and removed in a future release. + +## Related information + +- [Work with network commands](work-with-networks.md) +- [Get started with multi-host networking](get-started-overlay.md) +- [Managing Data in Containers](../dockervolumes.md) +- [Docker Machine overview](https://docs.docker.com/machine) +- [Docker Swarm overview](https://docs.docker.com/swarm) +- [Investigate the LibNetwork project](https://github.com/docker/libnetwork/blob/master) diff --git a/docs/userguide/networking/get-started-overlay.md b/docs/userguide/networking/get-started-overlay.md new file mode 100644 index 000000000..d803a0844 --- /dev/null +++ b/docs/userguide/networking/get-started-overlay.md @@ -0,0 +1,346 @@ + + +# Get started with multi-host networking + +This article uses an example to explain the basics of creating a multi-host +network. Docker Engine supports multi-host-networking out-of-the-box through the +`overlay` network driver. Unlike `bridge` networks overlay networks require +some pre-existing conditions before you can create one. These conditions are: + +* A host with a 3.16 kernel version or higher. +* Access to a key-value store. Docker supports Consul, Etcd, and ZooKeeper (Distributed store) key-value stores. +* A cluster of hosts with connectivity to the key-value store. +* A properly configured Engine `daemon` on each host in the cluster. + +Though Docker Machine and Docker Swarm are not mandatory to experience Docker +multi-host-networking, this example uses them to illustrate how they are +integrated. You'll use Machine to create both the the key-value store +server and the host cluster. This example creates a Swarm cluster. + +## Prerequisites + +Before you begin, make sure you have a system on your network with the latest +version of Docker Engine and Docker Machine installed. The example also relies +on VirtualBox. If you installed on a Mac or Windows with Docker Toolbox, you +have all of these installed already. + +If you have not already done so, make sure you upgrade Docker Engine and Docker +Machine to the latest versions. + + +## Step 1: Set up a key-value store + +An overlay network requires a key-value store. The key-value stores information +about the network state which includes discovery, networks, endpoints, +ip-addresses, and more. Docker supports Consul, Etcd, and ZooKeeper (Distributed +store) key-value stores. This example uses Consul. + +1. Log into a system prepared with the prerequisite Docker Engine, Docker Machine, and VirtualBox software. + +2. Provision a VirtualBox machine called `mh-keystore`. + + $ docker-machine create -d virtualbox mh-keystore + + When you provision a new machine, the process adds Docker Engine to the + host. This means rather than installing Consul manually, you can create an + instance using the [consul image from Docker + Hub](https://hub.docker.com/r/progrium/consul/). You'll do this in the next step. + +3. Start a `progrium/consul` container running on the `mh-keystore` machine. + + $ docker $(docker-machine config mh-keystore) run -d \ + -p "8500:8500" \ + -h "consul" \ + progrium/consul -server -bootstrap + + You passed the `docker run` command the connection configuration using a bash + expansion `$(docker-machine config mh-keystore)`. The client started a + `progrium/consul` image running in the `mh-keystore` machine. The server is called `consul`and is listening port `8500`. + +4. Set your local environment to the `mh-keystore` machine. + + $ eval "$(docker-machine env mh-keystore)" + +5. Run the `docker ps` command to see the `consul` container. + + $ docker ps + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + 4d51392253b3 progrium/consul "/bin/start -server -" 25 minutes ago Up 25 minutes 53/tcp, 53/udp, 8300-8302/tcp, 0.0.0.0:8500->8500/tcp, 8400/tcp, 8301-8302/udp admiring_panini + +Keep your terminal open and move onto the next step. + + +## Step 2: Create a Swarm cluster + +In this step, you use `docker-machine` to provision the hosts for your network. +At this point, you won't actually created the network. You'll create several +machines in VirtualBox. One of the machines will act as the Swarm master; +you'll create that first. As you create each host, you'll pass the Engine on +that machine options that are needed by the `overlay` network driver. + +1. Create a Swarm master. + + $ docker-machine create \ + -d virtualbox \ + --swarm --swarm-image="swarm" --swarm-master \ + --swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \ + --engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \ + --engine-opt="cluster-advertise=eth1:2376" \ + mhs-demo0 + + At creation time, you supply the Engine `daemon` with the ` --cluster-store` option. This option tells the Engine the location of the key-value store for the `overlay` network. The bash expansion `$(docker-machine ip mh-keystore)` resolves to the IP address of the Consul server you created in "STEP 1". The `--cluster-advertise` option advertises the machine on the network. + +2. Create another host and add it to the Swarm cluster. + + $ docker-machine create -d virtualbox \ + --swarm --swarm-image="swarm:1.0.0-rc2" \ + --swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \ + --engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \ + --engine-opt="cluster-advertise=eth1:2376" \ + mhs-demo1 + +3. List your machines to confirm they are all up and running. + + $ docker-machine ls + NAME ACTIVE DRIVER STATE URL SWARM + default virtualbox Running tcp://192.168.99.100:2376 + mh-keystore virtualbox Running tcp://192.168.99.103:2376 + mhs-demo0 virtualbox Running tcp://192.168.99.104:2376 mhs-demo0 (master) + mhs-demo1 virtualbox Running tcp://192.168.99.105:2376 mhs-demo0 + +At this point you have a set of hosts running on your network. You are ready to create a multi-host network for containers using these hosts. + + +Leave your terminal open and go onto the next step. + +## Step 3: Create the overlay Network + +To create an overlay network + +1. Set your docker environment to the Swarm master. + + $ eval $(docker-machine env --swarm mhs-demo0) + + Using the `--swarm` flag with `docker-machine` restricts the `docker` commands to Swarm information alone. + +2. Use the `docker info` command to view the Swarm. + + $ docker info + Containers: 3 + Images: 2 + Role: primary + Strategy: spread + Filters: affinity, health, constraint, port, dependency + Nodes: 2 + mhs-demo0: 192.168.99.104:2376 + └ Containers: 2 + └ Reserved CPUs: 0 / 1 + └ Reserved Memory: 0 B / 1.021 GiB + └ Labels: executiondriver=native-0.2, kernelversion=4.1.10-boot2docker, operatingsystem=Boot2Docker 1.9.0-rc1 (TCL 6.4); master : 4187d2c - Wed Oct 14 14:00:28 UTC 2015, provider=virtualbox, storagedriver=aufs + mhs-demo1: 192.168.99.105:2376 + └ Containers: 1 + └ Reserved CPUs: 0 / 1 + └ Reserved Memory: 0 B / 1.021 GiB + └ Labels: executiondriver=native-0.2, kernelversion=4.1.10-boot2docker, operatingsystem=Boot2Docker 1.9.0-rc1 (TCL 6.4); master : 4187d2c - Wed Oct 14 14:00:28 UTC 2015, provider=virtualbox, storagedriver=aufs + CPUs: 2 + Total Memory: 2.043 GiB + Name: 30438ece0915 + + From this information, you can see that you are running three containers and 2 images on the Master. + +3. Create your `overlay` network. + + $ docker network create --driver overlay my-net + + You only need to create the network on a single host in the cluster. In this case, you used the Swarm master but you could easily have run it on any host in the cluster. + +4. Check that the network is running: + + $ docker network ls + NETWORK ID NAME DRIVER + 412c2496d0eb mhs-demo1/host host + dd51763e6dd2 mhs-demo0/bridge bridge + 6b07d0be843f my-net overlay + b4234109bd9b mhs-demo0/none null + 1aeead6dd890 mhs-demo0/host host + d0bb78cbe7bd mhs-demo1/bridge bridge + 1c0eb8f69ebb mhs-demo1/none null + + Because you are in the Swarm master environment, you see all the networks on all Swarm agents. Notice that each `NETWORK ID` is unique. The default networks on each engine and the single overlay network. + +5. Switch to each Swarm agent in turn and list the network. + + $ eval $(docker-machine env mhs-demo0) + $ docker network ls + NETWORK ID NAME DRIVER + 6b07d0be843f my-net overlay + dd51763e6dd2 bridge bridge + b4234109bd9b none null + 1aeead6dd890 host host + $ eval $(docker-machine env mhs-demo1) + $ docker network ls + NETWORK ID NAME DRIVER + d0bb78cbe7bd bridge bridge + 1c0eb8f69ebb none null + 412c2496d0eb host host + 6b07d0be843f my-net overlay + + Both agents reports it has the `my-net `network with the `6b07d0be843f` id. You have a multi-host container network running! + +## Step 4: Run an application on your Network + +Once your network is created, you can start a container on any of the hosts and it automatically is part of the network. + +1. Point your environment to your `mhs-demo0` instance. + + $ eval $(docker-machine env mhs-demo0) + +2. Start an Nginx server on `mhs-demo0`. + + $ docker run -itd --name=web --net=my-net --env="constraint:node==mhs-demo0" nginx + + This command starts a web server on the Swarm master. + +3. Point your Machine environment to `mhs-demo1` + + $ eval $(docker-machine env mhs-demo1) + +4. Run a Busybox instance and get the contents of the Ngnix server's home page. + + $ docker run -it --rm --net=my-net --env="constraint:node==mhs-demo1" busybox wget -O- http://web + Unable to find image 'busybox:latest' locally + latest: Pulling from library/busybox + ab2b8a86ca6c: Pull complete + 2c5ac3f849df: Pull complete + Digest: sha256:5551dbdfc48d66734d0f01cafee0952cb6e8eeecd1e2492240bf2fd9640c2279 + Status: Downloaded newer image for busybox:latest + Connecting to web (10.0.0.2:80) + + + + Welcome to nginx! + + + +

Welcome to nginx!

+

If you see this page, the nginx web server is successfully installed and + working. Further configuration is required.

+ +

For online documentation and support please refer to + nginx.org.
+ Commercial support is available at + nginx.com.

+ +

Thank you for using nginx.

+ + + - 100% |*******************************| 612 0:00:00 ETA + +## Step 5: Check external connectivity + +As you've seen, Docker's built-in overlay network driver provides out-of-the-box +connectivity between the containers on multiple hosts within the same network. +Additionally, containers connected to the multi-host network are automatically +connected to the `docker_gwbridge` network. This network allows the containers +to have external connectivity outside of their cluster. + +1. Change your environment to the Swarm agent. + + $ eval $(docker-machine env mhs-demo1) + +2. View the `docker_gwbridge` network, by listing the networks. + + $ docker network ls + NETWORK ID NAME DRIVER + 6b07d0be843f my-net overlay + dd51763e6dd2 bridge bridge + b4234109bd9b none null + 1aeead6dd890 host host + e1dbd5dff8be docker_gwbridge bridge + +3. Repeat steps 1 and 2 on the Swarm master. + + $ eval $(docker-machine env mhs-demo0) + $ docker network ls + NETWORK ID NAME DRIVER + 6b07d0be843f my-net overlay + d0bb78cbe7bd bridge bridge + 1c0eb8f69ebb none null + 412c2496d0eb host host + 97102a22e8d2 docker_gwbridge bridge + +2. Check the Ngnix container's network interfaces. + + $ docker exec web ip addr + 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + inet6 ::1/128 scope host + valid_lft forever preferred_lft forever + 22: eth0: mtu 1450 qdisc noqueue state UP group default + link/ether 02:42:0a:00:09:03 brd ff:ff:ff:ff:ff:ff + inet 10.0.9.3/24 scope global eth0 + valid_lft forever preferred_lft forever + inet6 fe80::42:aff:fe00:903/64 scope link + valid_lft forever preferred_lft forever + 24: eth1: mtu 1500 qdisc noqueue state UP group default + link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff + inet 172.18.0.2/16 scope global eth1 + valid_lft forever preferred_lft forever + inet6 fe80::42:acff:fe12:2/64 scope link + valid_lft forever preferred_lft forever + + The `eth0` interface represents the container interface that is connected to + the `my-net` overlay network. While the `eth1` interface represents the + container interface that is connected to the `docker_gwbridge` network. + +## Step 6: Extra Credit with Docker Compose + +You can try starting a second network on your existing Swarm cluster using Docker Compose. + +1. Log into the Swarm master. + +2. Install Docker Compose. + +3. Create a `docker-compose.yml` file. + +4. Add the following content to the file. + + web: + image: bfirsh/compose-mongodb-demo + environment: + - "MONGO_HOST=counter_mongo_1" + - "constraint:node==swl-demo0" + ports: + - "80:5000" + mongo: + image: mongo + +5. Save and close the file. + +6. Start the application with Compose. + + $ docker-compose up --x-networking up -d + +## Related information + +* [Understand Docker container networks](dockernetworks.md) +* [Work with network commands](work-with-networks.md) +* [Docker Swarm overview](https://docs.docker.com/swarm) +* [Docker Machine overview](https://docs.docker.com/machine) diff --git a/docs/userguide/networking/images/bridge_network.gliffy b/docs/userguide/networking/images/bridge_network.gliffy new file mode 100644 index 000000000..d113f4fb5 --- /dev/null +++ b/docs/userguide/networking/images/bridge_network.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#ffffff","width":378,"height":236,"nodeIndex":146,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":false,"drawingGuidesOn":false,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":7,"y":5.1999969482421875},"max":{"x":378,"y":235.1428540910994}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":196.0,"y":100.69999694824219,"rotation":0.0,"id":140,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":61,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"


","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":149.0,"y":154.96785409109907,"rotation":0.0,"id":114,"width":150.0,"height":54.732142857143145,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":16,"lockAspectRatio":false,"lockShape":false,"children":[{"x":44.0,"y":2.7321428571431454,"rotation":0.0,"id":95,"width":62.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":5,"lockAspectRatio":false,"lockShape":false,"children":[{"x":29.139999999999997,"y":2.94642857142857,"rotation":0.0,"id":96,"width":3.719999999999998,"height":27.107142857142843,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":13,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":99,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":99,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.8600000000000136,-1.1785714285714448],[1.8600000000000136,28.285714285714278]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":null},{"x":51.46,"y":2.94642857142857,"rotation":0.0,"id":97,"width":1.2156862745098034,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.4193795664340882,-1.178571428571729],[-1.4193795664340882,28.28571428571442]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":null},{"x":9.919999999999993,"y":1.3749999999999987,"rotation":0.0,"id":98,"width":1.239999999999999,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.0393795664339223,0.3928571428572809],[2.0393795664339223,29.85714285714272]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":null},{"x":0.0,"y":1.7678571428571417,"rotation":0.0,"id":99,"width":62.0,"height":29.46428571428572,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":4,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":40.732142857143145,"rotation":0.0,"id":112,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

container2

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":226.0,"y":155.96785409109907,"rotation":0.0,"id":115,"width":150.0,"height":54.732142857143145,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":34,"lockAspectRatio":false,"lockShape":false,"children":[{"x":44.0,"y":2.7321428571431454,"rotation":0.0,"id":116,"width":62.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":22,"lockAspectRatio":false,"lockShape":false,"children":[{"x":29.139999999999997,"y":2.94642857142857,"rotation":0.0,"id":117,"width":3.719999999999998,"height":27.107142857142843,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":31,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":120,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":120,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.8600000000000136,-1.1785714285714448],[1.8600000000000136,28.285714285714278]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":null},{"x":51.46,"y":2.94642857142857,"rotation":0.0,"id":118,"width":1.2156862745098034,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":28,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.4193795664340882,-1.178571428571729],[-1.4193795664340882,28.28571428571442]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":null},{"x":9.919999999999993,"y":1.3749999999999987,"rotation":0.0,"id":119,"width":1.239999999999999,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.0393795664339223,0.3928571428572809],[2.0393795664339223,29.85714285714272]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":null},{"x":0.0,"y":1.7678571428571417,"rotation":0.0,"id":120,"width":62.0,"height":29.46428571428572,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":20,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":40.732142857143145,"rotation":0.0,"id":121,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":33,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

container3

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":72.0,"y":154.96785409109907,"rotation":0.0,"id":122,"width":150.0,"height":54.732142857143145,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":51,"lockAspectRatio":false,"lockShape":false,"children":[{"x":44.0,"y":2.7321428571431454,"rotation":0.0,"id":123,"width":62.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":39,"lockAspectRatio":false,"lockShape":false,"children":[{"x":29.139999999999997,"y":2.94642857142857,"rotation":0.0,"id":124,"width":3.719999999999998,"height":27.107142857142843,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":48,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":127,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":127,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.8600000000000136,-1.1785714285714448],[1.8600000000000136,28.285714285714278]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":null},{"x":51.46,"y":2.94642857142857,"rotation":0.0,"id":125,"width":1.2156862745098034,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":45,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.4193795664340882,-1.178571428571729],[-1.4193795664340882,28.28571428571442]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":null},{"x":9.919999999999993,"y":1.3749999999999987,"rotation":0.0,"id":126,"width":1.239999999999999,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.0393795664339223,0.3928571428572809],[2.0393795664339223,29.85714285714272]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":null},{"x":0.0,"y":1.7678571428571417,"rotation":0.0,"id":127,"width":62.0,"height":29.46428571428572,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":37,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":40.732142857143145,"rotation":0.0,"id":128,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":50,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

container1

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":81.38636363636368,"y":79.1428540910994,"rotation":0.0,"id":129,"width":291.1363636363638,"height":156.0,"uid":"com.gliffy.shape.iphone.iphone_ios7.icons_glyphs.glyph_cloud","order":51,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.iphone.iphone_ios7.icons_glyphs.glyph_cloud","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#929292","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":157.0,"y":124.19999694824219,"rotation":0.0,"id":130,"width":150.0,"height":27.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":52,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

isolated_nw

 

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":15.0,"y":5.1999969482421875,"rotation":0.0,"id":134,"width":73.116,"height":102.32,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":56,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":53.0,"y":57.19999694824219,"rotation":0.0,"id":136,"width":119.0,"height":45.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":57,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":134,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":6.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[35.116,-0.8400000000000034],[89.0,-0.8400000000000034],[89.0,57.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":5.0,"y":116.19999694824219,"rotation":0.0,"id":142,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":63,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Docker Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"layers":[{"guid":"9wom3rMkTrb3","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":66}],"shapeStyles":{},"lineStyles":{"global":{"stroke":"#999999","strokeWidth":6}},"textStyles":{"global":{"bold":true}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":[],"lastSerialized":1445538566750},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file diff --git a/docs/userguide/networking/images/bridge_network.png b/docs/userguide/networking/images/bridge_network.png new file mode 100644 index 000000000..314c9f796 Binary files /dev/null and b/docs/userguide/networking/images/bridge_network.png differ diff --git a/docs/userguide/networking/images/bridge_network.svg b/docs/userguide/networking/images/bridge_network.svg new file mode 100644 index 000000000..2e35695ff --- /dev/null +++ b/docs/userguide/networking/images/bridge_network.svg @@ -0,0 +1 @@ +container2container3container1isolated_nwDockerHost \ No newline at end of file diff --git a/docs/userguide/networking/images/engine_on_net.gliffy b/docs/userguide/networking/images/engine_on_net.gliffy new file mode 100644 index 000000000..2fe97eca9 --- /dev/null +++ b/docs/userguide/networking/images/engine_on_net.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#ffffff","width":277,"height":209,"nodeIndex":174,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":false,"drawingGuidesOn":false,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":3,"y":3.1889969482422202},"max":{"x":277,"y":208.1999969482422}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":223.0,"y":117.3854006442422,"rotation":0.0,"id":171,"width":26.70555282692303,"height":19.0,"uid":"com.gliffy.shape.basic.basic_v1.default.svg","order":21,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Svg","Svg":{"embeddedResourceId":0,"strokeWidth":2.0,"strokeColor":"#000000","dropShadow":true,"shadowX":5.0,"shadowY":5.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":1.0,"y":93.51999694824218,"rotation":0.0,"id":152,"width":78.0,"height":77.68,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":4,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":63.68000000000001,"rotation":0.0,"id":142,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null},{"x":23.0,"y":0.0,"rotation":0.0,"id":134,"width":42.8749022673964,"height":60.0,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":1,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":96.0,"y":130.51999694824218,"rotation":0.0,"id":153,"width":78.0,"height":77.68,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":7,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":63.68000000000001,"rotation":0.0,"id":154,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":9,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null},{"x":23.0,"y":0.0,"rotation":0.0,"id":155,"width":42.8749022673964,"height":60.0,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":6,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":197.0,"y":99.35999694824216,"rotation":0.0,"id":156,"width":78.0,"height":77.68,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":12,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":63.68000000000001,"rotation":0.0,"id":157,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":14,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null},{"x":23.0,"y":0.0,"rotation":0.0,"id":158,"width":42.8749022673964,"height":60.0,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":11,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":114.0,"y":3.1889969482422202,"rotation":0.0,"id":160,"width":48.773475410240856,"height":39.0,"uid":"com.gliffy.shape.cisco.cisco_v1.storage.relational_database","order":15,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.storage.relational_database","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#02709F","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":163,"width":88.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Key-value store

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":171.0,"y":25.199996948242188,"rotation":0.0,"id":165,"width":72.0,"height":73.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":18,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":160,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":158,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-32.613262294879576,16.989000000000033],[70.4374511336982,74.15999999999997]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":141.0,"y":37.19999694824219,"rotation":0.0,"id":168,"width":4.0,"height":91.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":19,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":160,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":155,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-2.6132622948795756,4.989000000000033],[-0.5625488663017961,93.32]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":136.0,"y":42.19999694824219,"rotation":0.0,"id":169,"width":86.0,"height":50.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":20,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":160,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":134,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.3867377051204244,-0.010999999999967258],[-90.5625488663018,51.31999999999999]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":122.0,"y":150.3854006442422,"rotation":0.0,"id":172,"width":26.70555282692303,"height":19.0,"uid":"com.gliffy.shape.basic.basic_v1.default.svg","order":22,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Svg","Svg":{"embeddedResourceId":0,"strokeWidth":2.0,"strokeColor":"#000000","dropShadow":true,"shadowX":5.0,"shadowY":5.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":27.0,"y":113.3854006442422,"rotation":0.0,"id":173,"width":26.70555282692303,"height":19.0,"uid":"com.gliffy.shape.basic.basic_v1.default.svg","order":23,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Svg","Svg":{"embeddedResourceId":0,"strokeWidth":2.0,"strokeColor":"#000000","dropShadow":true,"shadowX":5.0,"shadowY":5.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"layers":[{"guid":"9wom3rMkTrb3","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":24}],"shapeStyles":{},"lineStyles":{"global":{"stroke":"#999999","strokeWidth":2,"dashStyle":"1.0,1.0"}},"textStyles":{"global":{"bold":true}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.custom.confluence.c20f4a380e3cee362007f9e62694d34d947f28ed4263c0702b3dd72d9801532a"],"lastSerialized":1445555725710},"embeddedResources":{"index":1,"resources":[{"id":0,"mimeType":"image/svg+xml","data":"\n\n \n logo copy\n Created with Sketch.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n","width":59.29392246992643,"height":42.185403696,"x":0.4429050300735753,"y":0.7077644040000006}]}} \ No newline at end of file diff --git a/docs/userguide/networking/images/engine_on_net.png b/docs/userguide/networking/images/engine_on_net.png new file mode 100644 index 000000000..a79aa44c8 Binary files /dev/null and b/docs/userguide/networking/images/engine_on_net.png differ diff --git a/docs/userguide/networking/images/engine_on_net.svg b/docs/userguide/networking/images/engine_on_net.svg new file mode 100644 index 000000000..a74637099 --- /dev/null +++ b/docs/userguide/networking/images/engine_on_net.svg @@ -0,0 +1 @@ +HostHostHostKey-valuestore \ No newline at end of file diff --git a/docs/userguide/networking/images/key_value.gliffy b/docs/userguide/networking/images/key_value.gliffy new file mode 100644 index 000000000..4e632ef3e --- /dev/null +++ b/docs/userguide/networking/images/key_value.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#ffffff","width":277,"height":209,"nodeIndex":171,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":false,"drawingGuidesOn":false,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":3,"y":3.1889969482422202},"max":{"x":277,"y":208.1999969482422}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":1.0,"y":93.51999694824218,"rotation":0.0,"id":152,"width":78.0,"height":77.68,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":4,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":63.68000000000001,"rotation":0.0,"id":142,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null},{"x":23.0,"y":0.0,"rotation":0.0,"id":134,"width":42.8749022673964,"height":60.0,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":1,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":96.0,"y":130.51999694824218,"rotation":0.0,"id":153,"width":78.0,"height":77.68,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":5,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":63.68000000000001,"rotation":0.0,"id":154,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":9,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null},{"x":23.0,"y":0.0,"rotation":0.0,"id":155,"width":42.8749022673964,"height":60.0,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":7,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":197.0,"y":99.35999694824216,"rotation":0.0,"id":156,"width":78.0,"height":77.68,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":10,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":63.68000000000001,"rotation":0.0,"id":157,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":14,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null},{"x":23.0,"y":0.0,"rotation":0.0,"id":158,"width":42.8749022673964,"height":60.0,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":12,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":114.0,"y":3.1889969482422202,"rotation":0.0,"id":160,"width":48.773475410240856,"height":39.0,"uid":"com.gliffy.shape.cisco.cisco_v1.storage.relational_database","order":16,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.storage.relational_database","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#02709F","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":163,"width":88.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Key-value store

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":171.0,"y":25.199996948242188,"rotation":0.0,"id":165,"width":72.0,"height":73.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":17,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":160,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":158,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-32.613262294879576,16.989000000000033],[70.4374511336982,74.15999999999997]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":141.0,"y":37.19999694824219,"rotation":0.0,"id":168,"width":4.0,"height":91.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":20,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":160,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":155,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-2.6132622948795756,4.989000000000033],[-0.5625488663017961,93.32]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":136.0,"y":42.19999694824219,"rotation":0.0,"id":169,"width":86.0,"height":50.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":21,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":160,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":134,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.3867377051204244,-0.010999999999967258],[-90.5625488663018,51.31999999999999]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"layers":[{"guid":"9wom3rMkTrb3","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":22}],"shapeStyles":{},"lineStyles":{"global":{"stroke":"#999999","strokeWidth":2,"dashStyle":"1.0,1.0"}},"textStyles":{"global":{"bold":true}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":[],"lastSerialized":1445552948967},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file diff --git a/docs/userguide/networking/images/key_value.png b/docs/userguide/networking/images/key_value.png new file mode 100644 index 000000000..c4056f114 Binary files /dev/null and b/docs/userguide/networking/images/key_value.png differ diff --git a/docs/userguide/networking/images/key_value.svg b/docs/userguide/networking/images/key_value.svg new file mode 100644 index 000000000..5f8507378 --- /dev/null +++ b/docs/userguide/networking/images/key_value.svg @@ -0,0 +1 @@ +HostHostHostKey-valuestore \ No newline at end of file diff --git a/docs/userguide/networking/images/network_access.gliffy b/docs/userguide/networking/images/network_access.gliffy new file mode 100644 index 000000000..b1a1910d3 --- /dev/null +++ b/docs/userguide/networking/images/network_access.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#ffffff","width":437,"height":368,"nodeIndex":178,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":false,"drawingGuidesOn":false,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":5,"y":1.1999969482421875},"max":{"x":437,"y":367.5199969482422}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":126.38636363636371,"y":74.1428540910994,"rotation":0.0,"id":129,"width":291.1363636363638,"height":149.0,"uid":"com.gliffy.shape.iphone.iphone_ios7.icons_glyphs.glyph_cloud","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.iphone.iphone_ios7.icons_glyphs.glyph_cloud","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#929292","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":199.0,"y":150.96785409109907,"rotation":0.0,"id":114,"width":150.0,"height":54.732142857143145,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":17,"lockAspectRatio":false,"lockShape":false,"children":[{"x":44.0,"y":2.7321428571431454,"rotation":0.0,"id":95,"width":62.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":5,"lockAspectRatio":false,"lockShape":false,"children":[{"x":29.139999999999997,"y":2.94642857142857,"rotation":0.0,"id":96,"width":3.719999999999998,"height":27.107142857142843,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":14,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":99,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":99,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.8600000000000136,-1.1785714285714448],[1.8600000000000136,28.285714285714278]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":51.46,"y":2.94642857142857,"rotation":0.0,"id":97,"width":1.2156862745098034,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":11,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.4193795664340882,-1.178571428571729],[-1.4193795664340882,28.28571428571442]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":9.919999999999993,"y":1.3749999999999987,"rotation":0.0,"id":98,"width":1.239999999999999,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":8,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.0393795664339223,0.3928571428572809],[2.0393795664339223,29.85714285714272]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":0.0,"y":1.7678571428571417,"rotation":0.0,"id":99,"width":62.0,"height":29.46428571428572,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":40.732142857143145,"rotation":0.0,"id":112,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":16,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

container2

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":209.0,"y":284.96785409109907,"rotation":0.0,"id":115,"width":150.0,"height":54.732142857143145,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":34,"lockAspectRatio":false,"lockShape":false,"children":[{"x":44.0,"y":2.7321428571431454,"rotation":0.0,"id":116,"width":62.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":22,"lockAspectRatio":false,"lockShape":false,"children":[{"x":29.139999999999997,"y":2.94642857142857,"rotation":0.0,"id":117,"width":3.719999999999998,"height":27.107142857142843,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":31,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":120,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":120,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.8600000000000136,-1.178571428571388],[1.8600000000000136,28.285714285714334]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":51.46,"y":2.94642857142857,"rotation":0.0,"id":118,"width":1.2156862745098034,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":28,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.4193795664340882,-1.178571428571729],[-1.4193795664340882,28.28571428571442]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":9.919999999999993,"y":1.3749999999999987,"rotation":0.0,"id":119,"width":1.239999999999999,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.0393795664339223,0.3928571428572809],[2.0393795664339223,29.85714285714272]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":0.0,"y":1.7678571428571417,"rotation":0.0,"id":120,"width":62.0,"height":29.46428571428572,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":20,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":40.732142857143145,"rotation":0.0,"id":121,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":33,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

external_container

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":122.0,"y":150.96785409109907,"rotation":0.0,"id":122,"width":150.0,"height":54.732142857143145,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":51,"lockAspectRatio":false,"lockShape":false,"children":[{"x":44.0,"y":2.7321428571431454,"rotation":0.0,"id":123,"width":62.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":39,"lockAspectRatio":false,"lockShape":false,"children":[{"x":29.139999999999997,"y":2.94642857142857,"rotation":0.0,"id":124,"width":3.719999999999998,"height":27.107142857142843,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":48,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":127,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":127,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.8600000000000136,-1.1785714285714448],[1.8600000000000136,28.285714285714278]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":51.46,"y":2.94642857142857,"rotation":0.0,"id":125,"width":1.2156862745098034,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":45,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.4193795664340882,-1.178571428571729],[-1.4193795664340882,28.28571428571442]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":9.919999999999993,"y":1.3749999999999987,"rotation":0.0,"id":126,"width":1.239999999999999,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.0393795664339223,0.3928571428572809],[2.0393795664339223,29.85714285714272]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":0.0,"y":1.7678571428571417,"rotation":0.0,"id":127,"width":62.0,"height":29.46428571428572,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":37,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":40.732142857143145,"rotation":0.0,"id":128,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":50,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

container1

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":192.0,"y":120.19999694824219,"rotation":0.0,"id":130,"width":150.0,"height":27.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":52,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

isolated_nw

 

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":65.0,"y":1.1999969482421875,"rotation":0.0,"id":134,"width":73.116,"height":102.32,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":53,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":103.0,"y":53.19999694824219,"rotation":0.0,"id":136,"width":119.0,"height":45.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":54,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":134,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":6.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[35.115999999999985,-0.8400000000000034],[87.0,-0.8400000000000034],[87.0,55.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":20.0,"y":16.699996948242188,"rotation":0.0,"id":140,"width":150.0,"height":1.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":55,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"


","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":55.0,"y":112.19999694824219,"rotation":0.0,"id":142,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":56,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Docker Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":160.0,"y":179.0,"rotation":0.0,"id":145,"width":10.0,"height":10.0,"uid":"com.gliffy.shape.basic.basic_v1.default.ellipse","order":57,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ellipse.basic_v1","strokeWidth":1.0,"strokeColor":"#00ffff","fillColor":"#00ffff","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":31.999999999999993,"y":189.1999969482422,"rotation":0.0,"id":147,"width":73.116,"height":102.32,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":58,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":346.0,"y":265.1999969482422,"rotation":0.0,"id":149,"width":73.116,"height":102.32,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":59,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":378.0,"y":276.1999969482422,"rotation":0.0,"id":150,"width":56.0,"height":26.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":60,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":149,"py":0.5,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":120,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-32.0,40.15999999999997],[-47.5,40.15999999999997],[-47.5,28.0],[-63.0,28.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":250.0,"y":282.1999969482422,"rotation":0.0,"id":152,"width":84.0,"height":96.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":61,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":120,"py":0.0,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":145,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#666666","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.0,7.267857142857167],[3.0,-42.96606990269251],[-85.0,-42.96606990269251],[-85.0,-93.19999694824219]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":103.0,"y":242.1999969482422,"rotation":0.0,"id":153,"width":54.0,"height":53.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":62,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":147,"py":0.5,"px":1.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":145,"py":0.5,"px":0.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[2.1159999999999854,-1.8400000000000034],[29.557999999999993,-1.8400000000000034],[29.557999999999993,-58.19999694824219],[57.0,-58.19999694824219]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":250.0,"y":286.0,"rotation":0.0,"id":154,"width":10.0,"height":10.0,"uid":"com.gliffy.shape.basic.basic_v1.default.ellipse","order":63,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ellipse.basic_v1","strokeWidth":1.0,"strokeColor":"#00ffff","fillColor":"#00ffff","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":278.0,"y":149.96785409109907,"rotation":0.0,"id":155,"width":150.0,"height":54.732142857143145,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":64,"lockAspectRatio":false,"lockShape":false,"children":[{"x":44.0,"y":2.7321428571431454,"rotation":0.0,"id":156,"width":62.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":69,"lockAspectRatio":false,"lockShape":false,"children":[{"x":29.139999999999997,"y":2.94642857142857,"rotation":0.0,"id":157,"width":3.719999999999998,"height":27.107142857142843,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":78,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":160,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":160,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.8600000000000136,-1.1785714285714448],[1.8600000000000136,28.285714285714278]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":51.46,"y":2.94642857142857,"rotation":0.0,"id":158,"width":1.2156862745098034,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":75,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.4193795664340882,-1.178571428571729],[-1.4193795664340882,28.28571428571442]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":9.919999999999993,"y":1.3749999999999987,"rotation":0.0,"id":159,"width":1.239999999999999,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":72,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.0393795664339223,0.3928571428572809],[2.0393795664339223,29.85714285714272]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":0.0,"y":1.7678571428571417,"rotation":0.0,"id":160,"width":62.0,"height":29.46428571428572,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":67,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":40.732142857143145,"rotation":0.0,"id":161,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":80,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

container3

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":3.0,"y":296.1999969482422,"rotation":0.0,"id":162,"width":133.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":81,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":5,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

external host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":337.0,"y":21.199996948242188,"rotation":0.0,"id":176,"width":98.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":84,"lockAspectRatio":false,"lockShape":false,"children":[{"x":13.0,"y":0.0,"rotation":0.0,"id":174,"width":85.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":83,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":5,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

published port

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":null},{"x":0.0,"y":3.8000030517578125,"rotation":0.0,"id":173,"width":10.0,"height":10.0,"uid":"com.gliffy.shape.basic.basic_v1.default.ellipse","order":82,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.ellipse.basic_v1","strokeWidth":1.0,"strokeColor":"#00ffff","fillColor":"#00ffff","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"}],"layers":[{"guid":"9wom3rMkTrb3","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":85}],"shapeStyles":{},"lineStyles":{"global":{"stroke":"#666666","strokeWidth":2}},"textStyles":{"global":{"bold":true}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":[],"lastSerialized":1445536836098},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file diff --git a/docs/userguide/networking/images/network_access.png b/docs/userguide/networking/images/network_access.png new file mode 100644 index 000000000..d8bf6319d Binary files /dev/null and b/docs/userguide/networking/images/network_access.png differ diff --git a/docs/userguide/networking/images/network_access.svg b/docs/userguide/networking/images/network_access.svg new file mode 100644 index 000000000..33c84465d --- /dev/null +++ b/docs/userguide/networking/images/network_access.svg @@ -0,0 +1 @@ +container2external_containercontainer1isolated_nwDockerHostcontainer3externalhostpublishedport \ No newline at end of file diff --git a/docs/userguide/networking/images/overlay-network-final.gliffy b/docs/userguide/networking/images/overlay-network-final.gliffy new file mode 100644 index 000000000..75b878de1 --- /dev/null +++ b/docs/userguide/networking/images/overlay-network-final.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#ffffff","width":361,"height":263,"nodeIndex":249,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":false,"drawingGuidesOn":false,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":23.000000000000057,"y":8.18899694824222},"max":{"x":360.00000000000006,"y":262.0000000000038}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":140.0,"y":162.1999969482422,"rotation":0.0,"id":247,"width":33.0,"height":11.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":107,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":238,"py":0.9999999999999998,"px":0.29289321881345254}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":134,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-3.2842712474617883,-3.971422467905427],[-3.2842712474617883,16.319999999999993],[-43.562548866301796,16.319999999999993],[-43.562548866301796,-3.680000000000007]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":187.0,"y":134.1999969482422,"rotation":0.0,"id":246,"width":18.0,"height":17.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":106,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":134,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":223,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[-90.5625488663018,-35.68000000000001],[-90.5625488663018,-60.68000000000001],[-43.0,-60.68000000000001],[-43.0,-25.428571428571402]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":166.0,"y":169.1999969482422,"rotation":0.0,"id":245,"width":22.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":105,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":172,"py":0.7071067811865475,"px":0.0}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":228,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[7.000000000000028,-0.3795674614555935],[-6.5,-0.3795674614555935],[-6.5,29.50000000000003],[-20.0,29.50000000000003]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":189.0,"y":197.1999969482422,"rotation":0.0,"id":244,"width":15.0,"height":36.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":104,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":155,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":233,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[2.437451133698204,-1.6800000000000068],[2.437451133698204,37.5],[-19.0,37.5]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":292.0,"y":163.1999969482422,"rotation":0.0,"id":242,"width":51.0,"height":8.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":102,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":158,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":218,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[0.43745113369817545,1.1599999999999682],[0.43745113369817545,21.428571428571473],[-52.0,21.428571428571473],[-52.0,1.4285714285714732]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":289.0,"y":102.19999694824219,"rotation":0.0,"id":240,"width":51.0,"height":4.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":100,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":158,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":200,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.4374511336981755,2.159999999999968],[3.4374511336981755,-7.840000000000032],[-51.0,-7.840000000000032],[-51.0,8.571428571428598]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":23.000000000000057,"y":81.00000000000378,"rotation":180.0,"id":175,"width":337.0,"height":181.0,"uid":"com.gliffy.shape.iphone.iphone_ios7.icons_glyphs.glyph_cloud","order":41,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.iphone.iphone_ios7.icons_glyphs.glyph_cloud","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#929292","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":52.0,"y":8.18899694824222,"rotation":0.0,"id":178,"width":274.0,"height":205.01099999999997,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":42,"lockAspectRatio":false,"lockShape":false,"children":[{"x":25.999999999999996,"y":110.19640369599998,"rotation":0.0,"id":173,"width":20.88802989941042,"height":19.0,"uid":"com.gliffy.shape.basic.basic_v1.default.svg","order":40,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Svg","Svg":{"embeddedResourceId":0,"strokeWidth":2.0,"strokeColor":"#000000","dropShadow":true,"shadowX":5.0,"shadowY":5.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":121.00000000000003,"y":147.19640369599998,"rotation":0.0,"id":172,"width":20.88802989941042,"height":19.0,"uid":"com.gliffy.shape.basic.basic_v1.default.svg","order":38,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Svg","Svg":{"embeddedResourceId":0,"strokeWidth":2.0,"strokeColor":"#000000","dropShadow":true,"shadowX":5.0,"shadowY":5.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":222.0,"y":114.19640369599998,"rotation":0.0,"id":171,"width":20.88802989941042,"height":19.0,"uid":"com.gliffy.shape.basic.basic_v1.default.svg","order":36,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Svg","Svg":{"embeddedResourceId":0,"strokeWidth":2.0,"strokeColor":"#000000","dropShadow":true,"shadowX":5.0,"shadowY":5.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":135.0,"y":39.01099999999997,"rotation":0.0,"id":169,"width":86.0,"height":50.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":34,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":160,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":134,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.3867377051204244,-0.010999999999967258],[-90.5625488663018,51.31999999999999]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":140.0,"y":34.01099999999997,"rotation":0.0,"id":168,"width":4.0,"height":91.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":32,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":160,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":155,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-2.6132622948795756,4.989000000000033],[-0.5625488663017961,93.32]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":170.0,"y":22.010999999999967,"rotation":0.0,"id":165,"width":72.0,"height":73.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":30,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":160,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":158,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-32.613262294879576,16.989000000000033],[70.43745113369818,74.15999999999997]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":113.0,"y":0.0,"rotation":0.0,"id":160,"width":48.773475410240856,"height":39.0,"uid":"com.gliffy.shape.cisco.cisco_v1.storage.relational_database","order":27,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.storage.relational_database","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#02709F","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":163,"width":88.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Key-value store

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":null},{"x":196.0,"y":96.17099999999994,"rotation":0.0,"id":156,"width":78.0,"height":77.68,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":20,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":63.68000000000001,"rotation":0.0,"id":157,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":23,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":23.0,"y":0.0,"rotation":0.0,"id":158,"width":42.8749022673964,"height":60.000000000000014,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":18,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":95.0,"y":127.33099999999996,"rotation":0.0,"id":153,"width":78.0,"height":77.68,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":12,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":63.68000000000001,"rotation":0.0,"id":154,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":15,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":23.0,"y":0.0,"rotation":0.0,"id":155,"width":42.8749022673964,"height":60.000000000000014,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":10,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":90.33099999999996,"rotation":0.0,"id":152,"width":78.0,"height":77.68,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":7,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":63.68000000000001,"rotation":0.0,"id":142,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":5,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":23.0,"y":0.0,"rotation":0.0,"id":134,"width":42.8749022673964,"height":60.000000000000014,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":2,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":218.0,"y":109.69999694824222,"rotation":0.0,"id":196,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":47,"lockAspectRatio":false,"lockShape":false,"children":[{"x":18.8,"y":1.7857142857142847,"rotation":0.0,"id":197,"width":2.399999999999999,"height":16.428571428571416,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":53,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":200,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":200,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.1999999999999886,-0.7142857142857082],[1.1999999999999886,17.14285714285714]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":33.2,"y":1.7857142857142847,"rotation":0.0,"id":198,"width":1.3333333333333333,"height":17.14285714285713,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":51,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-0.9157287525381217,-0.7142857142858963],[-0.9157287525381217,17.142857142857224]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":6.399999999999995,"y":0.8333333333333324,"rotation":0.0,"id":199,"width":1.3333333333333333,"height":17.14285714285713,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":49,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.3157287525380146,0.23809523809532174],[1.3157287525380146,18.09523809523801]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":0.0,"y":1.0714285714285707,"rotation":0.0,"id":200,"width":40.0,"height":17.857142857142858,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":220.0,"y":145.69999694824222,"rotation":0.0,"id":214,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":57,"lockAspectRatio":false,"lockShape":false,"children":[{"x":18.8,"y":1.7857142857142847,"rotation":0.0,"id":215,"width":2.399999999999999,"height":16.428571428571416,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":62,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":218,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":218,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.1999999999999886,-0.714285714285694],[1.1999999999999886,17.142857142857167]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":33.2,"y":1.7857142857142847,"rotation":0.0,"id":216,"width":1.3333333333333333,"height":17.14285714285713,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":60,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-0.9157287525381217,-0.7142857142858963],[-0.9157287525381217,17.142857142857224]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":6.399999999999995,"y":0.8333333333333324,"rotation":0.0,"id":217,"width":1.3333333333333333,"height":17.14285714285713,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":58,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.3157287525380146,0.23809523809532174],[1.3157287525380146,18.09523809523801]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":0.0,"y":1.0714285714285707,"rotation":0.0,"id":218,"width":40.0,"height":17.857142857142858,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":56,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":124.0,"y":107.69999694824222,"rotation":0.0,"id":219,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":66,"lockAspectRatio":false,"lockShape":false,"children":[{"x":18.8,"y":1.7857142857142847,"rotation":0.0,"id":220,"width":2.399999999999999,"height":16.428571428571416,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":71,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":223,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":223,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.1999999999999886,-0.7142857142857082],[1.1999999999999886,17.142857142857153]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":33.2,"y":1.7857142857142847,"rotation":0.0,"id":221,"width":1.3333333333333333,"height":17.14285714285713,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":69,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-0.9157287525381217,-0.7142857142858963],[-0.9157287525381217,17.142857142857224]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":6.399999999999995,"y":0.8333333333333324,"rotation":0.0,"id":222,"width":1.3333333333333333,"height":17.14285714285713,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":67,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.3157287525380146,0.23809523809532174],[1.3157287525380146,18.09523809523801]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":0.0,"y":1.0714285714285707,"rotation":0.0,"id":223,"width":40.0,"height":17.857142857142858,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":65,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":106.0,"y":188.69999694824222,"rotation":0.0,"id":224,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":75,"lockAspectRatio":false,"lockShape":false,"children":[{"x":18.8,"y":1.7857142857142847,"rotation":0.0,"id":225,"width":2.399999999999999,"height":16.428571428571416,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":80,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":228,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":228,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.2000000000000028,-0.714285714285694],[1.2000000000000028,17.142857142857167]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":33.2,"y":1.7857142857142847,"rotation":0.0,"id":226,"width":1.3333333333333333,"height":17.14285714285713,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":78,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-0.9157287525381217,-0.7142857142858963],[-0.9157287525381217,17.142857142857224]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":6.399999999999995,"y":0.8333333333333324,"rotation":0.0,"id":227,"width":1.3333333333333333,"height":17.14285714285713,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":76,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.3157287525380146,0.23809523809532174],[1.3157287525380146,18.09523809523801]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":0.0,"y":1.0714285714285707,"rotation":0.0,"id":228,"width":40.0,"height":17.857142857142858,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":74,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":130.0,"y":224.6999969482422,"rotation":0.0,"id":229,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":84,"lockAspectRatio":false,"lockShape":false,"children":[{"x":18.8,"y":1.7857142857142847,"rotation":0.0,"id":230,"width":2.399999999999999,"height":16.428571428571416,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":89,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":233,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":233,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.1999999999999886,-0.714285714285694],[1.1999999999999886,17.142857142857167]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":33.2,"y":1.7857142857142847,"rotation":0.0,"id":231,"width":1.3333333333333333,"height":17.14285714285713,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":87,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-0.9157287525381217,-0.7142857142858963],[-0.9157287525381217,17.142857142857224]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":6.399999999999995,"y":0.8333333333333324,"rotation":0.0,"id":232,"width":1.3333333333333333,"height":17.14285714285713,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":85,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.3157287525380146,0.23809523809532174],[1.3157287525380146,18.09523809523801]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":0.0,"y":1.0714285714285707,"rotation":0.0,"id":233,"width":40.0,"height":17.857142857142858,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":83,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":125.00000000000011,"y":139.30000305176532,"rotation":0.0,"id":234,"width":40.0,"height":20.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":93,"lockAspectRatio":false,"lockShape":false,"children":[{"x":18.8,"y":1.7857142857142847,"rotation":0.0,"id":235,"width":2.399999999999999,"height":16.428571428571416,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":98,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":238,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":238,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.1999999999999886,-0.714285714285694],[1.1999999999999886,17.142857142857167]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":33.2,"y":1.7857142857142847,"rotation":0.0,"id":236,"width":1.3333333333333333,"height":17.14285714285713,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":96,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-0.9157287525381217,-0.7142857142858963],[-0.9157287525381217,17.142857142857224]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":6.399999999999995,"y":0.8333333333333324,"rotation":0.0,"id":237,"width":1.3333333333333333,"height":17.14285714285713,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":94,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.3157287525380146,0.23809523809532174],[1.3157287525380146,18.09523809523801]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":0.0,"y":1.0714285714285707,"rotation":0.0,"id":238,"width":40.0,"height":17.857142857142858,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":92,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"}],"layers":[{"guid":"9wom3rMkTrb3","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":109}],"shapeStyles":{},"lineStyles":{"global":{"stroke":"#999999","strokeWidth":2}},"textStyles":{"global":{"bold":true,"face":"Courier"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.custom.confluence.c20f4a380e3cee362007f9e62694d34d947f28ed4263c0702b3dd72d9801532a"],"lastSerialized":1445556943068},"embeddedResources":{"index":1,"resources":[{"id":0,"mimeType":"image/svg+xml","data":"\n\n \n logo copy\n Created with Sketch.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n","width":59.29392246992643,"height":42.185403696,"x":0.4429050300735753,"y":0.7077644040000006}]}} \ No newline at end of file diff --git a/docs/userguide/networking/images/overlay-network-final.png b/docs/userguide/networking/images/overlay-network-final.png new file mode 100644 index 000000000..303183934 Binary files /dev/null and b/docs/userguide/networking/images/overlay-network-final.png differ diff --git a/docs/userguide/networking/images/overlay-network-final.svg b/docs/userguide/networking/images/overlay-network-final.svg new file mode 100644 index 000000000..8ff3b3e52 --- /dev/null +++ b/docs/userguide/networking/images/overlay-network-final.svg @@ -0,0 +1 @@ +HostHostHostKey-valuestore \ No newline at end of file diff --git a/docs/userguide/networking/images/overlay_network.gliffy b/docs/userguide/networking/images/overlay_network.gliffy new file mode 100644 index 000000000..e4a6ed959 --- /dev/null +++ b/docs/userguide/networking/images/overlay_network.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#ffffff","width":361,"height":291,"nodeIndex":195,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":false,"drawingGuidesOn":false,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":23.000000000000057,"y":8.18899694824222},"max":{"x":360.00000000000006,"y":290.6999969482422}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":194.0,"y":200.1999969482422,"rotation":0.0,"id":193,"width":47.0,"height":77.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":41,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[10.0,-6.0],[47.0,77.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":64.0,"y":272.6999969482422,"rotation":0.0,"id":179,"width":247.0,"height":24.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":27,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":5,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

docker network create -d overlay

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":23.000000000000057,"y":81.00000000000378,"rotation":180.0,"id":175,"width":337.0,"height":181.0,"uid":"com.gliffy.shape.iphone.iphone_ios7.icons_glyphs.glyph_cloud","order":25,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.iphone.iphone_ios7.icons_glyphs.glyph_cloud","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#929292","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":52.0,"y":8.18899694824222,"rotation":0.0,"id":178,"width":274.0,"height":205.01099999999997,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":26,"lockAspectRatio":false,"lockShape":false,"children":[{"x":25.999999999999996,"y":110.19640369599998,"rotation":0.0,"id":173,"width":20.88802989941042,"height":19.0,"uid":"com.gliffy.shape.basic.basic_v1.default.svg","order":23,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Svg","Svg":{"embeddedResourceId":0,"strokeWidth":2.0,"strokeColor":"#000000","dropShadow":true,"shadowX":5.0,"shadowY":5.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":121.00000000000003,"y":147.19640369599998,"rotation":0.0,"id":172,"width":20.88802989941042,"height":19.0,"uid":"com.gliffy.shape.basic.basic_v1.default.svg","order":22,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Svg","Svg":{"embeddedResourceId":0,"strokeWidth":2.0,"strokeColor":"#000000","dropShadow":true,"shadowX":5.0,"shadowY":5.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":222.0,"y":114.19640369599998,"rotation":0.0,"id":171,"width":20.88802989941042,"height":19.0,"uid":"com.gliffy.shape.basic.basic_v1.default.svg","order":21,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Svg","Svg":{"embeddedResourceId":0,"strokeWidth":2.0,"strokeColor":"#000000","dropShadow":true,"shadowX":5.0,"shadowY":5.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":135.0,"y":39.01099999999997,"rotation":0.0,"id":169,"width":86.0,"height":50.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":20,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":160,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":134,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.3867377051204244,-0.010999999999967258],[-90.5625488663018,51.31999999999999]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":140.0,"y":34.01099999999997,"rotation":0.0,"id":168,"width":4.0,"height":91.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":19,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":160,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":155,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-2.6132622948795756,4.989000000000033],[-0.5625488663017961,93.32]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":170.0,"y":22.010999999999967,"rotation":0.0,"id":165,"width":72.0,"height":73.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":18,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":160,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":158,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#999999","fillColor":"none","dashStyle":"1.0,1.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-32.613262294879576,16.989000000000033],[70.43745113369818,74.15999999999997]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":113.0,"y":0.0,"rotation":0.0,"id":160,"width":48.773475410240856,"height":39.0,"uid":"com.gliffy.shape.cisco.cisco_v1.storage.relational_database","order":15,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.storage.relational_database","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#02709F","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":163,"width":88.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"both","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Key-value store

","tid":null,"valign":"middle","vposition":"below","hposition":"none"}},"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":null},{"x":196.0,"y":96.17099999999994,"rotation":0.0,"id":156,"width":78.0,"height":77.68,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":12,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":63.68000000000001,"rotation":0.0,"id":157,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":14,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":23.0,"y":0.0,"rotation":0.0,"id":158,"width":42.8749022673964,"height":60.000000000000014,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":11,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":95.0,"y":127.33099999999996,"rotation":0.0,"id":153,"width":78.0,"height":77.68,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":7,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":63.68000000000001,"rotation":0.0,"id":154,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":9,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":23.0,"y":0.0,"rotation":0.0,"id":155,"width":42.8749022673964,"height":60.000000000000014,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":6,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null},{"x":0.0,"y":90.33099999999996,"rotation":0.0,"id":152,"width":78.0,"height":77.68,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":4,"lockAspectRatio":false,"lockShape":false,"children":[{"x":0.0,"y":63.68000000000001,"rotation":0.0,"id":142,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[],"hidden":false,"layerId":null},{"x":23.0,"y":0.0,"rotation":0.0,"id":134,"width":42.8749022673964,"height":60.000000000000014,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":1,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":null}],"hidden":false,"layerId":null}],"hidden":false,"layerId":"9wom3rMkTrb3"}],"layers":[{"guid":"9wom3rMkTrb3","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":43}],"shapeStyles":{},"lineStyles":{"global":{"stroke":"#999999","strokeWidth":2,"endArrow":2}},"textStyles":{"global":{"bold":true,"face":"Courier"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.custom.confluence.c20f4a380e3cee362007f9e62694d34d947f28ed4263c0702b3dd72d9801532a"],"lastSerialized":1445556181238},"embeddedResources":{"index":1,"resources":[{"id":0,"mimeType":"image/svg+xml","data":"\n\n \n logo copy\n Created with Sketch.\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n","width":59.29392246992643,"height":42.185403696,"x":0.4429050300735753,"y":0.7077644040000006}]}} \ No newline at end of file diff --git a/docs/userguide/networking/images/overlay_network.png b/docs/userguide/networking/images/overlay_network.png new file mode 100644 index 000000000..9d728b818 Binary files /dev/null and b/docs/userguide/networking/images/overlay_network.png differ diff --git a/docs/userguide/networking/images/overlay_network.svg b/docs/userguide/networking/images/overlay_network.svg new file mode 100644 index 000000000..f53de1244 --- /dev/null +++ b/docs/userguide/networking/images/overlay_network.svg @@ -0,0 +1 @@ +HostHostHostKey-valuestoredockernetworkcreate-doverlay \ No newline at end of file diff --git a/docs/userguide/networking/images/working.gliffy b/docs/userguide/networking/images/working.gliffy new file mode 100644 index 000000000..5831a460b --- /dev/null +++ b/docs/userguide/networking/images/working.gliffy @@ -0,0 +1 @@ +{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#ffffff","width":376,"height":241,"nodeIndex":152,"autoFit":true,"exportBorder":false,"gridOn":true,"snapToGrid":false,"drawingGuidesOn":false,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":1,"y":5.1999969482421875},"max":{"x":375.38636363636374,"y":240.14285409109937}},"printModel":{"pageSize":"a4","portrait":false,"fitToOnePage":false,"displayPageBreaks":false},"objects":[{"x":85.0,"y":50.0,"rotation":0.0,"id":150,"width":211.0,"height":31.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":60,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":134,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":6.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[3.1159999999999997,6.359996948242184],[180.558,6.359996948242184],[180.558,67.0],[180.0,67.0]],"lockSegments":{"1":true},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":196.0,"y":100.69999694824219,"rotation":0.0,"id":140,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":56,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"


","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":15.0,"y":5.1999969482421875,"rotation":0.0,"id":134,"width":73.116,"height":102.32,"uid":"com.gliffy.shape.cisco.cisco_v1.servers.standard_host","order":54,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.cisco.cisco_v1.servers.standard_host","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#3d85c6","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":53.0,"y":57.19999694824219,"rotation":0.0,"id":136,"width":119.0,"height":45.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":55,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":134,"py":0.5,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":6.0,"strokeColor":"#999999","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[35.116,-0.8400000000000034],[89.0,-0.8400000000000034],[89.0,57.0]],"lockSegments":{},"ortho":true}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":5.0,"y":116.19999694824219,"rotation":0.0,"id":142,"width":78.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":57,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Docker Host

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":113.38636363636374,"y":116.14285409109937,"rotation":0.0,"id":129,"width":262.0,"height":124.0,"uid":"com.gliffy.shape.iphone.iphone_ios7.icons_glyphs.glyph_cloud","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.iphone.iphone_ios7.icons_glyphs.glyph_cloud","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#929292","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":15.386363636363683,"y":113.14285409109937,"rotation":0.0,"id":146,"width":233.0,"height":127.0,"uid":"com.gliffy.shape.iphone.iphone_ios7.icons_glyphs.glyph_cloud","order":1,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.iphone.iphone_ios7.icons_glyphs.glyph_cloud","strokeWidth":1.0,"strokeColor":"#000000","fillColor":"#929292","gradient":false,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":106.0,"y":175.96785409109907,"rotation":0.0,"id":114,"width":150.0,"height":54.732142857143145,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":18,"lockAspectRatio":false,"lockShape":false,"children":[{"x":44.0,"y":2.7321428571431454,"rotation":0.0,"id":95,"width":62.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":6,"lockAspectRatio":false,"lockShape":false,"children":[{"x":29.139999999999997,"y":2.94642857142857,"rotation":0.0,"id":96,"width":3.719999999999998,"height":27.107142857142843,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":15,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":99,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":99,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.8600000000000136,-1.1785714285714448],[1.8600000000000136,28.285714285714278]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":51.46,"y":2.94642857142857,"rotation":0.0,"id":97,"width":1.2156862745098034,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.4193795664340882,-1.178571428571729],[-1.4193795664340882,28.28571428571442]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":9.919999999999993,"y":1.3749999999999987,"rotation":0.0,"id":98,"width":1.239999999999999,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":9,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.0393795664339223,0.3928571428572809],[2.0393795664339223,29.85714285714272]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":1.7678571428571417,"rotation":0.0,"id":99,"width":62.0,"height":29.46428571428572,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":4,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":40.732142857143145,"rotation":0.0,"id":112,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":17,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

container2

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":217.0,"y":177.96785409109907,"rotation":0.0,"id":115,"width":150.0,"height":54.732142857143145,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":35,"lockAspectRatio":false,"lockShape":false,"children":[{"x":44.0,"y":2.7321428571431454,"rotation":0.0,"id":116,"width":62.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":23,"lockAspectRatio":false,"lockShape":false,"children":[{"x":29.139999999999997,"y":2.94642857142857,"rotation":0.0,"id":117,"width":3.719999999999998,"height":27.107142857142843,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":32,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":120,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":120,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.8600000000000136,-1.1785714285714448],[1.8600000000000136,28.285714285714278]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":51.46,"y":2.94642857142857,"rotation":0.0,"id":118,"width":1.2156862745098034,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":29,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.4193795664340882,-1.178571428571729],[-1.4193795664340882,28.28571428571442]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":9.919999999999993,"y":1.3749999999999987,"rotation":0.0,"id":119,"width":1.239999999999999,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":26,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.0393795664339223,0.3928571428572809],[2.0393795664339223,29.85714285714272]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":1.7678571428571417,"rotation":0.0,"id":120,"width":62.0,"height":29.46428571428572,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":21,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":40.732142857143145,"rotation":0.0,"id":121,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":34,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

container3

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":-1.0,"y":175.96785409109907,"rotation":0.0,"id":122,"width":150.0,"height":54.732142857143145,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":52,"lockAspectRatio":false,"lockShape":false,"children":[{"x":44.0,"y":2.7321428571431454,"rotation":0.0,"id":123,"width":62.0,"height":33.0,"uid":"com.gliffy.shape.basic.basic_v1.default.group","order":40,"lockAspectRatio":false,"lockShape":false,"children":[{"x":29.139999999999997,"y":2.94642857142857,"rotation":0.0,"id":124,"width":3.719999999999998,"height":27.107142857142843,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":49,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":127,"py":0.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":127,"py":1.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[1.8599999999999994,-1.1785714285714448],[1.8599999999999994,28.285714285714278]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":51.46,"y":2.94642857142857,"rotation":0.0,"id":125,"width":1.2156862745098034,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-1.4193795664340882,-1.178571428571729],[-1.4193795664340882,28.28571428571442]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":9.919999999999993,"y":1.3749999999999987,"rotation":0.0,"id":126,"width":1.239999999999999,"height":28.285714285714267,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":43,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#0b5394","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[2.0393795664339223,0.3928571428572809],[2.0393795664339223,29.85714285714272]],"lockSegments":{},"ortho":false}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":1.7678571428571417,"rotation":0.0,"id":127,"width":62.0,"height":29.46428571428572,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#6fa8dc","fillColor":"#3d85c6","gradient":true,"dashStyle":null,"dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":0.0,"y":40.732142857143145,"rotation":0.0,"id":128,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":51,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

container1

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":185.0,"y":143.1999969482422,"rotation":0.0,"id":130,"width":150.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":53,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

isolated_nw

 

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"},{"x":55.0,"y":139.1999969482422,"rotation":0.0,"id":147,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":58,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

bridge 

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"hidden":false,"layerId":"9wom3rMkTrb3"}],"layers":[{"guid":"9wom3rMkTrb3","order":0,"name":"Layer 0","active":true,"locked":false,"visible":true,"nodeIndex":62}],"shapeStyles":{},"lineStyles":{"global":{"stroke":"#999999","strokeWidth":6}},"textStyles":{"global":{"bold":true}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":[],"lastSerialized":1446315118663,"analyticsProduct":"Confluence"},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file diff --git a/docs/userguide/networking/images/working.png b/docs/userguide/networking/images/working.png new file mode 100644 index 000000000..8539913af Binary files /dev/null and b/docs/userguide/networking/images/working.png differ diff --git a/docs/userguide/networking/images/working.svg b/docs/userguide/networking/images/working.svg new file mode 100644 index 000000000..f39955e1d --- /dev/null +++ b/docs/userguide/networking/images/working.svg @@ -0,0 +1 @@ +container2container3container1isolated_nwDockerHostbridge \ No newline at end of file diff --git a/docs/userguide/networking/index.md b/docs/userguide/networking/index.md new file mode 100644 index 000000000..7680b199f --- /dev/null +++ b/docs/userguide/networking/index.md @@ -0,0 +1,21 @@ + + +# Docker networks feature overview + +This sections explains how to use the Docker networks feature. This feature allows users to define their own networks and connect containers to them. Using this feature you can create a network on a single host or a network that spans across multiple hosts. + +- [Understand Docker container networks](dockernetworks.md) +- [Work with network commands](work-with-networks.md) +- [Get started with multi-host networking](get-started-overlay.md) + +If you are already familiar with Docker's default bridge network, `docker0` that network continues to be supported. It is created automatically in every installation. The default bridge network is also named `bridge`. To see a list of topics related to that network, read the articles listed in the [Docker default bridge network](default_network/index.md). diff --git a/docs/userguide/networking/work-with-networks.md b/docs/userguide/networking/work-with-networks.md new file mode 100644 index 000000000..d346fec59 --- /dev/null +++ b/docs/userguide/networking/work-with-networks.md @@ -0,0 +1,463 @@ + + +# Work with network commands + +This article provides examples of the network subcommands you can use to interact with Docker networks and the containers in them. The commands are available through the Docker Engine CLI. These commands are: + +* `docker network create` +* `docker network connect` +* `docker network ls` +* `docker network rm` +* `docker network disconnect` +* `docker network inspect` + +While not required, it is a good idea to read [Understanding Docker +network](dockernetworks.md) before trying the examples in this section. The +examples for the rely on a `bridge` network so that you can try them +immediately. If you would prefer to experiment with an `overlay` network see +the [Getting started with multi-host networks](get-started-overlay.md) instead. + +## Create networks + +Docker Engine creates a `bridge` network automatically when you install Engine. +This network corresponds to the `docker0` bridge that Engine has traditionally +relied on. In addition to this network, you can create your own `bridge` or `overlay` network. + +A `bridge` network resides on a single host running an instance of Docker Engine. An `overlay` network can span multiple hosts running their own engines. If you run `docker network create` and supply only a network name, it creates a bridge network for you. + +```bash +$ docker network create simple-network +de792b8258895cf5dc3b43835e9d61a9803500b991654dacb1f4f0546b1c88f8 +$ docker network inspect simple-network +[ + { + "Name": "simple-network", + "Id": "de792b8258895cf5dc3b43835e9d61a9803500b991654dacb1f4f0546b1c88f8", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [ + {} + ] + }, + "Containers": {}, + "Options": {} + } +] +``` + +Unlike `bridge` networks, `overlay` networks require some pre-existing conditions +before you can create one. These conditions are: + +* Access to a key-value store. Engine supports Consul Etcd, and ZooKeeper (Distributed store) key-value stores. +* A cluster of hosts with connectivity to the key-value store. +* A properly configured Engine `daemon` on each host in the swarm. + +The `docker daemon` options that support the `overlay` network are: + +* `--cluster-store` +* `--cluster-store-opt` +* `--cluster-advertise` + +It is also a good idea, though not required, that you install Docker Swarm +to manage the cluster. Swarm provides sophisticated discovery and server +management that can assist your implementation. + +When you create a network, Engine creates a non-overlapping subnetwork for the +network by default. You can override this default and specify a subnetwork +directly using the the `--subnet` option. On a `bridge` network you can only +create a single subnet. An `overlay` network supports multiple subnets. + +In addition to the `--subnetwork` option, you also specify the `--gateway` `--ip-range` and `--aux-address` options. + +```bash +$ docker network create -d overlay + --subnet=192.168.0.0/16 --subnet=192.170.0.0/16 + --gateway=192.168.0.100 --gateway=192.170.0.100 + --ip-range=192.168.1.0/24 + --aux-address a=192.168.1.5 --aux-address b=192.168.1.6 + --aux-address a=192.170.1.5 --aux-address b=192.170.1.6 + my-multihost-network +``` + +Be sure that your subnetworks do not overlap. If they do, the network create fails and Engine returns an error. + +## Connect containers + +You can connect containers dynamically to one or more networks. These networks +can be backed the same or different network drivers. Once connected, the +containers can communicate using another container's IP address or name. + +For `overlay` networks or custom plugins that support multi-host +connectivity, containers connected to the same multi-host network but launched +from different hosts can also communicate in this way. + +Create two containers for this example: + +```bash +$ docker run -itd --name=container1 busybox +18c062ef45ac0c026ee48a83afa39d25635ee5f02b58de4abc8f467bcaa28731 + +$ docker run -itd --name=container2 busybox +498eaaaf328e1018042c04b2de04036fc04719a6e39a097a4f4866043a2c2152 +``` + +Then create a isolated, `bridge` network to test with. + +```bash +$ docker network create -d bridge isolated_nw +f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a +``` + +Connect `container2` to the network and then `inspect` the network to verify the connection: + +``` +$ docker network connect isolated_nw container2 +$ docker network inspect isolated_nw +[[ + { + "Name": "isolated_nw", + "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [ + {} + ] + }, + "Containers": { + "498eaaaf328e1018042c04b2de04036fc04719a6e39a097a4f4866043a2c2152": { + "EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9", + "MacAddress": "02:42:ac:15:00:02", + "IPv4Address": "172.21.0.2/16", + "IPv6Address": "" + } + }, + "Options": {} + } +] +``` + +You can see that the Engine automatically assigns an IP address to `container2`. +If you had specified a `--subnetwork` when creating your network, the network +would have used that addressing. Now, start a third container and connect it to +the network on launch using the `docker run` command's `--net` option: + +```bash +$ docker run --net=isolated_nw -itd --name=container3 busybox +c282ca437ee7e926a7303a64fc04109740208d2c20e442366139322211a6481c +``` + +Now, inspect the network resources used by `container3`. + +```bash +$ docker inspect --format='{{json .NetworkSettings.Networks}}' container3 +{"isolated_nw":{"EndpointID":"e5d077f9712a69c6929fdd890df5e7c1c649771a50df5b422f7e68f0ae61e847","Gateway":"172.21.0.1","IPAddress":"172.21.0.3","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:15:00:03"}} +``` +Repeat this command for `container2`. If you have Python installed, you can pretty print the output. + +```bash +$ docker inspect --format='{{json .NetworkSettings.Networks}}' container2 | python -m json.tool +{ + "bridge": { + "EndpointID": "281b5ead415cf48a6a84fd1a6504342c76e9091fe09b4fdbcc4a01c30b0d3c5b", + "Gateway": "172.17.0.1", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAddress": "172.17.0.3", + "IPPrefixLen": 16, + "IPv6Gateway": "", + "MacAddress": "02:42:ac:11:00:03" + }, + "isolated_nw": { + "EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9", + "Gateway": "172.21.0.1", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAddress": "172.21.0.2", + "IPPrefixLen": 16, + "IPv6Gateway": "", + "MacAddress": "02:42:ac:15:00:02" + } +} +``` + +You should find `container2` belongs to two networks. The `bridge` network +which it joined by default when you launched it and the `isolated_nw` which you +later connected it to. + +![](images/working.png) + +In the case of `container3`, you connected it through `docker run` to the +`isolated_nw` so that container is not connected to `bridge`. + +Use the `docker attach` command to connect to the running `container2` and +examine its networking stack: + +```bash +$ docker attach container2 +``` + +If you look a the container's network stack you should see two Ethernet interfaces, one for the default bridge network and one for the `isolated_nw` network. + +```bash +/ # ifconfig +eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:03 + inet addr:172.17.0.3 Bcast:0.0.0.0 Mask:255.255.0.0 + inet6 addr: fe80::42:acff:fe11:3/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:9001 Metric:1 + RX packets:8 errors:0 dropped:0 overruns:0 frame:0 + TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:648 (648.0 B) TX bytes:648 (648.0 B) + +eth1 Link encap:Ethernet HWaddr 02:42:AC:15:00:02 + inet addr:172.21.0.2 Bcast:0.0.0.0 Mask:255.255.0.0 + inet6 addr: fe80::42:acff:fe15:2/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:8 errors:0 dropped:0 overruns:0 frame:0 + TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:648 (648.0 B) TX bytes:648 (648.0 B) + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:65536 Metric:1 + RX packets:0 errors:0 dropped:0 overruns:0 frame:0 + TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) +``` + +Display the container's `etc/hosts` file: + +```bash +/ # cat /etc/hosts +172.17.0.3 498eaaaf328e +127.0.0.1 localhost +::1 localhost ip6-localhost ip6-loopback +fe00::0 ip6-localnet +ff00::0 ip6-mcastprefix +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters +172.21.0.3 container3 +172.21.0.3 container3.isolated_nw +``` + +On the `isolated_nw` which was user defined, the Docker network feature updated the `/etc/hosts` with the proper name resolution. Inside of `container2` it is possible to ping `container3` by name. + +```bash +/ # ping -w 4 container3 +PING container3 (172.21.0.3): 56 data bytes +64 bytes from 172.21.0.3: seq=0 ttl=64 time=0.070 ms +64 bytes from 172.21.0.3: seq=1 ttl=64 time=0.080 ms +64 bytes from 172.21.0.3: seq=2 ttl=64 time=0.080 ms +64 bytes from 172.21.0.3: seq=3 ttl=64 time=0.097 ms + +--- container3 ping statistics --- +4 packets transmitted, 4 packets received, 0% packet loss +round-trip min/avg/max = 0.070/0.081/0.097 ms +``` + +This isn't the case for the default bridge network. Both `container2` and `container1` are connected to the default bridge network. Docker does not support automatic service discovery on this network. For this reason, pinging `container1` by name fails as you would expect based on the `/etc/hosts` file: + +```bash +/ # ping -w 4 container1 +ping: bad address 'container1' +``` + +A ping using the `container1` IP address does succeed though: + +```bash +/ # ping -w 4 172.17.0.2 +PING 172.17.0.2 (172.17.0.2): 56 data bytes +64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.095 ms +64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.075 ms +64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.072 ms +64 bytes from 172.17.0.2: seq=3 ttl=64 time=0.101 ms + +--- 172.17.0.2 ping statistics --- +4 packets transmitted, 4 packets received, 0% packet loss +round-trip min/avg/max = 0.072/0.085/0.101 ms +``` + +If you wanted you could connect `container1` to `container2` with the `docker +run --link` command and that would enable the two containers to interact by name +as well as IP. + +Detach from a `container2` and leave it running using `CTRL-p CTRL-q`. + +In this example, `container2` is attached to both networks and so can talk to +`container1` and `container3`. But `container3` and `container1` are not in the +same network and cannot communicate. Test, this now by attaching to +`container3` and attempting to ping `container1` by IP address. + +```bash +$ docker attach container3 +/ # ping 172.17.0.2 +PING 172.17.0.2 (172.17.0.2): 56 data bytes +^C +--- 172.17.0.2 ping statistics --- +10 packets transmitted, 0 packets received, 100% packet loss + +``` + +To connect a container to a network, the container must be running. If you stop +a container and inspect a network it belongs to, you won't see that container. +The `docker network inspect` command only shows running containers. + +## Disconnecting containers + +You can disconnect a container from a network using the `docker network +disconnect` command. + +``` +$ docker network disconnect isolated_nw container2 + +docker inspect --format='{{json .NetworkSettings.Networks}}' container2 | python -m json.tool +{ + "bridge": { + "EndpointID": "9e4575f7f61c0f9d69317b7a4b92eefc133347836dd83ef65deffa16b9985dc0", + "Gateway": "172.17.0.1", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAddress": "172.17.0.3", + "IPPrefixLen": 16, + "IPv6Gateway": "", + "MacAddress": "02:42:ac:11:00:03" + } +} + + +$ docker network inspect isolated_nw +[[ + { + "Name": "isolated_nw", + "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [ + {} + ] + }, + "Containers": { + "c282ca437ee7e926a7303a64fc04109740208d2c20e442366139322211a6481c": { + "EndpointID": "e5d077f9712a69c6929fdd890df5e7c1c649771a50df5b422f7e68f0ae61e847", + "MacAddress": "02:42:ac:15:00:03", + "IPv4Address": "172.21.0.3/16", + "IPv6Address": "" + } + }, + "Options": {} + } +] +``` + +Once a container is disconnected from a network, it cannot communicate with +other containers connected to that network. In this example, `container2` can no longer talk to `container3` on the `isolated_nw` network. + +``` +$ docker attach container2 + +/ # ifconfig +eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:03 + inet addr:172.17.0.3 Bcast:0.0.0.0 Mask:255.255.0.0 + inet6 addr: fe80::42:acff:fe11:3/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:9001 Metric:1 + RX packets:8 errors:0 dropped:0 overruns:0 frame:0 + TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:648 (648.0 B) TX bytes:648 (648.0 B) + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:65536 Metric:1 + RX packets:0 errors:0 dropped:0 overruns:0 frame:0 + TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) + +/ # ping container3 +PING container3 (172.20.0.1): 56 data bytes +^C +--- container3 ping statistics --- +2 packets transmitted, 0 packets received, 100% packet loss +``` + +The `container2` still has full connectivity to the bridge network + +```bash +/ # ping container1 +PING container1 (172.17.0.2): 56 data bytes +64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.119 ms +64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.174 ms +^C +--- container1 ping statistics --- +2 packets transmitted, 2 packets received, 0% packet loss +round-trip min/avg/max = 0.119/0.146/0.174 ms +/ # +``` + +## Remove a network + +When all the containers in a network are stopped or disconnected, you can remove a network. + +```bash +$ docker network disconnect isolated_nw container3 +``` + +```bash +docker network inspect isolated_nw +[ + { + "Name": "isolated_nw", + "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [ + {} + ] + }, + "Containers": {}, + "Options": {} + } +] + +$ docker network rm isolated_nw +``` + +List all your networks to verify the `isolated_nw` was removed: + +``` +$ docker network ls +NETWORK ID NAME DRIVER +72314fa53006 host host +f7ab26d71dbd bridge bridge +0f32e83e61ac none null +``` + +## Related information + +* [network create](../../reference/commandline/network_create.md) +* [network inspect](../../reference/commandline/network_inspect.md) +* [network connect](../../reference/commandline/network_connect.md) +* [network disconnect](../../reference/commandline/network_disconnect.md) +* [network ls](../../reference/commandline/network_ls.md) +* [network rm](../../reference/commandline/network_rm.md) diff --git a/docs/userguide/networkingcontainers.md b/docs/userguide/networkingcontainers.md new file mode 100644 index 000000000..3a44e3b1d --- /dev/null +++ b/docs/userguide/networkingcontainers.md @@ -0,0 +1,240 @@ + + + +# Networking containers + +If you are working your way through the user guide, you just built and ran a +simple application. You've also built in your own images. This section teaches +you how to network your containers. + +## Name a container + +You've already seen that each container you create has an automatically +created name; indeed you've become familiar with our old friend +`nostalgic_morse` during this guide. You can also name containers +yourself. This naming provides two useful functions: + +* You can name containers that do specific functions in a way + that makes it easier for you to remember them, for example naming a + container containing a web application `web`. + +* Names provide Docker with a reference point that allows it to refer to other + containers. There are several commands that support this and you'll use one in a exercise later. + +You name your container by using the `--name` flag, for example launch a new container called web: + + $ docker run -d -P --name web training/webapp python app.py + +Use the `docker ps` command to see check the name: + + $ docker ps -l + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + aed84ee21bde training/webapp:latest python app.py 12 hours ago Up 2 seconds 0.0.0.0:49154->5000/tcp web + +You can also use `docker inspect` with the container's name. + + $ docker inspect web + [ + { + "Id": "3ce51710b34f5d6da95e0a340d32aa2e6cf64857fb8cdb2a6c38f7c56f448143", + "Created": "2015-10-25T22:44:17.854367116Z", + "Path": "python", + "Args": [ + "app.py" + ], + "State": { + "Status": "running", + "Running": true, + "Paused": false, + "Restarting": false, + "OOMKilled": false, + ... + +Container names must be unique. That means you can only call one container +`web`. If you want to re-use a container name you must delete the old container +(with `docker rm`) before you can reuse the name with a new container. Go ahead and stop and them remove your `web` container. + + $ docker stop web + web + $ docker rm web + web + + +## Launch a container on the default network + +Docker includes support for networking containers through the use of **network +drivers**. By default, Docker provides two network drivers for you, the +`bridge` and the `overlay` driver. You can also write a network driver plugin so +that you can create your own drivers but that is an advanced task. + +Every installation of the Docker Engine automatically includes three default networks. You can list them: + + $ docker network ls + NETWORK ID NAME DRIVER + 18a2866682b8 none null + c288470c46f6 host host + 7b369448dccb bridge bridge + +The network named `bridge` is a special network. Unless you tell it otherwise, Docker always launches your containers in this network. Try this now: + + $ docker run -itd --name=networktest ubuntu + 74695c9cea6d9810718fddadc01a727a5dd3ce6a69d09752239736c030599741 + +Inspecting the network is an easy way to find out the container's IP address. + +```bash +[ + { + "Name": "bridge", + "Id": "f7ab26d71dbd6f557852c7156ae0574bbf62c42f539b50c8ebde0f728a253b6f", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [ + { + "Subnet": "172.17.0.1/16", + "Gateway": "172.17.0.1" + } + ] + }, + "Containers": { + "3386a527aa08b37ea9232cbcace2d2458d49f44bb05a6b775fba7ddd40d8f92c": { + "EndpointID": "647c12443e91faf0fd508b6edfe59c30b642abb60dfab890b4bdccee38750bc1", + "MacAddress": "02:42:ac:11:00:02", + "IPv4Address": "172.17.0.2/16", + "IPv6Address": "" + }, + "94447ca479852d29aeddca75c28f7104df3c3196d7b6d83061879e339946805c": { + "EndpointID": "b047d090f446ac49747d3c37d63e4307be745876db7f0ceef7b311cbba615f48", + "MacAddress": "02:42:ac:11:00:03", + "IPv4Address": "172.17.0.3/16", + "IPv6Address": "" + } + }, + "Options": { + "com.docker.network.bridge.default_bridge": "true", + "com.docker.network.bridge.enable_icc": "true", + "com.docker.network.bridge.enable_ip_masquerade": "true", + "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", + "com.docker.network.bridge.name": "docker0", + "com.docker.network.driver.mtu": "9001" + } + } +] +``` + +You can remove a container from a network by disconnecting the container. To do this, you supply both the network name and the container name. You can also use the container id. In this example, though, the name is faster. + + $ docker network disconnect bridge networktest + +While you can disconnect a container from a network, you cannot remove the builtin `bridge` network named `bridge`. Networks are natural ways to isolate containers from other containers or other networks. So, as you get more experienced with Docker, you'll want to create your own networks. + +## Create your own bridge network + +Docker Engine natively supports both bridge networks and overlay networks. A bridge network is limited to a single host running Docker Engine. An overlay network can include multiple hosts and is a more advanced topic. For this example, you'll create a bridge network: + + $ docker network create -d bridge my-bridge-network + +The `-d` flag tells Docker to use the `bridge` driver for the new network. You could have left this flag off as `bridge` is the default value for this flag. Go ahead and list the networks on your machine: + + $ docker network ls + NETWORK ID NAME DRIVER + 7b369448dccb bridge bridge + 615d565d498c my-bridge-network bridge + 18a2866682b8 none null + c288470c46f6 host host + +If you inspect the network, you'll find that it has nothing in it. + + $ docker network inspect my-bridge-network + [ + { + "Name": "my-bridge-network", + "Id": "5a8afc6364bccb199540e133e63adb76a557906dd9ff82b94183fc48c40857ac", + "Scope": "local", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [ + {} + ] + }, + "Containers": {}, + "Options": {} + } + ] + +## Add containers to a network + +To build web applications that act in concert but do so securely, create a +network. Networks, by definition, provide complete isolation for containers. You +can add containers to a network when you first run a container. + +Launch a container running a PostgreSQL database and pass it the `--net=my-bridge-network` flag to connect it to your new network: + + $ docker run -d --net=my-bridge-network --name db training/postgres + +If you inspect your `my-bridge-network` you'll see it has a container attached. +You can also inspect your container to see where it is connected: + + $ docker inspect --format='{{json .NetworkSettings.Networks}}' db + {"bridge":{"EndpointID":"508b170d56b2ac9e4ef86694b0a76a22dd3df1983404f7321da5649645bf7043","Gateway":"172.17.0.1","IPAddress":"172.17.0.3","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:11:00:02"}} + +Now, go ahead and start your by now familiar web application. This time leave off the `-P` flag and also don't specify a network. + + $ docker run -d --name web training/webapp python app.py + +Which network is your `web` application running under? Inspect the application and you'll find it is running in the default `bridge` network. + + $ docker inspect --format='{{json .NetworkSettings.Networks}}' web + {"bridge":{"EndpointID":"508b170d56b2ac9e4ef86694b0a76a22dd3df1983404f7321da5649645bf7043","Gateway":"172.17.0.1","IPAddress":"172.17.0.3","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:11:00:02"}} + +Then, get the IP address of your `web` + + $ docker inspect '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web + 172.17.0.2 + +Now, open a shell to your running `db` container: + + $ docker exec -it db bash + root@a205f0dd33b2:/# ping 172.17.0.2 + ping 172.17.0.2 + PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data. + ^C + --- 172.17.0.2 ping statistics --- + 44 packets transmitted, 0 received, 100% packet loss, time 43185ms + +After a bit, use CTRL-C to end the `ping` and you'll find the ping failed. That is because the two container are running on different networks. You can fix that. Then, use CTRL-C to exit the container. + +Docker networking allows you to attach a container to as many networks as you like. You can also attach an already running container. Go ahead and attach your running `web` app to the `my-bridge-network`. + + $ docker network connect my-bridge-network Web + +Open a shell into the `db` application again and try the ping command. This time just use the container name `web` rather than the IP Address. + + $ docker exec -it db bash + root@a205f0dd33b2:/# ping web + PING web (172.19.0.3) 56(84) bytes of data. + 64 bytes from web (172.19.0.3): icmp_seq=1 ttl=64 time=0.095 ms + 64 bytes from web (172.19.0.3): icmp_seq=2 ttl=64 time=0.060 ms + 64 bytes from web (172.19.0.3): icmp_seq=3 ttl=64 time=0.066 ms + ^C + --- web ping statistics --- + 3 packets transmitted, 3 received, 0% packet loss, time 2000ms + rtt min/avg/max/mdev = 0.060/0.073/0.095/0.018 ms + +The `ping` shows it is contacting a different IP address, the address on the `my-bridge-network` which is different from its address on the `bridge` network. + +## Next steps + +Now that you know how to network containers, see [how to manage data in containers](dockervolumes.md). diff --git a/docs/userguide/storagedriver/aufs-driver.md b/docs/userguide/storagedriver/aufs-driver.md new file mode 100644 index 000000000..d072efacb --- /dev/null +++ b/docs/userguide/storagedriver/aufs-driver.md @@ -0,0 +1,197 @@ + + +# Docker and AUFS in practice + +AUFS was the first storage driver in use with Docker. As a result, it has a long and close history with Docker, is very stable, has a lot of real-world deployments, and has strong community support. AUFS has several features that make it a good choice for Docker. These features enable: + +- Fast container startup times. +- Efficient use of storage. +- Efficient use of memory. + +Despite its capabilities and long history with Docker, some Linux distributions do not support AUFS. This is usually because AUFS is not included in the mainline (upstream) Linux kernel. + +The following sections examine some AUFS features and how they relate to Docker. + +## Image layering and sharing with AUFS + +AUFS is a *unification filesystem*. This means that it takes multiple directories on a single Linux host, stacks them on top of each other, and provides a single unified view. To achieve this, AUFS uses *union mount*. + +AUFS stacks multiple directories and exposes them as a unified view through a single mount point. All of the directories in the stack, as well as the union mount point, must all exist on the same Linux host. AUFS refers to each directory that it stacks as a *branch*. + +Within Docker, AUFS union mounts enable image layering. The AUFS storage driver implements Docker image layers using this union mount system. AUFS branches correspond to Docker image layers. The diagram below shows a Docker container based on the `ubuntu:latest` image. + +![](images/aufs_layers.jpg) + +This diagram shows the relationship between the Docker image layers and the AUFS branches (directories) in `/var/lib/docker/aufs`. Each image layer and the container layer correspond to an AUFS branch (directory) in the Docker host's local storage area. The union mount point gives the unified view of all layers. + +AUFS also supports the copy-on-write technology (CoW). Not all storage drivers do. + +## Container reads and writes with AUFS + +Docker leverages AUFS CoW technology to enable image sharing and minimize the use of disk space. AUFS works at the file level. This means that all AUFS CoW operations copy entire files - even if only a small part of the file is being modified. This behavior can have a noticeable impact on container performance, especially if the files being copied are large, below a lot of image layers, or the CoW operation must search a deep directory tree. + +Consider, for example, an application running in a container needs to add a single new value to a large key-value store (file). If this is the first time the file is modified it does not yet exist in the container's top writable layer. So, the CoW must *copy up* the file from the underlying image. The AUFS storage driver searches each image layer for the file. The search order is from top to bottom. When it is found, the entire file is *copied up* to the container's top writable layer. From there, it can be opened and modified. + +Larger files obviously take longer to *copy up* than smaller files, and files that exist in lower image layers take longer to locate than those in higher layers. However, a *copy up* operation only occurs once per file on any given container. Subsequent reads and writes happen against the file's copy already *copied-up* to the container's top layer. + + +## Deleting files with the AUFS storage driver + +The AUFS storage driver deletes a file from a container by placing a *whiteout +file* in the container's top layer. The whiteout file effectively obscures the +existence of the file in image's lower, read-only layers. The simplified +diagram below shows a container based on an image with three image layers. + +![](images/aufs_delete.jpg) + +The `file3` was deleted from the container. So, the AUFS storage driver placed +a whiteout file in the container's top layer. This whiteout file effectively +"deletes" `file3` from the container by obscuring any of the original file's +existence in the image's read-only base layer. Of course, the image could have +been in any of the other layers instead or in addition depending on how the +layers are built. + +## Configure Docker with AUFS + +You can only use the AUFS storage driver on Linux systems with AUFS installed. Use the following command to determine if your system supports AUFS. + +```bash +$ grep aufs /proc/filesystems +nodev aufs +``` + +This output indicates the system supports AUFS. Once you've verified your +system supports AUFS, you can must instruct the Docker daemon to use it. You do +this from the command line with the `docker daemon` command: + +```bash +$ sudo docker daemon --storage-driver=aufs & +``` + +Alternatively, you can edit the Docker config file and add the +`--storage-driver=aufs` option to the `DOCKER_OPTS` line. + +```bash +# Use DOCKER_OPTS to modify the daemon startup options. +DOCKER_OPTS="--storage-driver=aufs" +``` + +Once your daemon is running, verify the storage driver with the `docker info` command. + +```bash +$ sudo docker info +Containers: 1 +Images: 4 +Storage Driver: aufs + Root Dir: /var/lib/docker/aufs + Backing Filesystem: extfs + Dirs: 6 + Dirperm1 Supported: false +Execution Driver: native-0.2 +...output truncated... +```` + +The output above shows that the Docker daemon is running the AUFS storage driver on top of an existing ext4 backing filesystem. + +## Local storage and AUFS + +As the `docker daemon` runs with the AUFS driver, the driver stores images and containers on within the Docker host's local storage area in the `/var/lib/docker/aufs` directory. + +### Images + +Image layers and their contents are stored under +`/var/lib/docker/aufs/mnt/diff/` directory. The contents of an image +layer in this location includes all the files and directories belonging in that +image layer. + +The `/var/lib/docker/aufs/layers/` directory contains metadata about how image +layers are stacked. This directory contains one file for every image or +container layer on the Docker host. Inside each file are the image layers names +that exist below it. The diagram below shows an image with 4 layers. + +![](images/aufs_metadata.jpg) + +Inspecting the contents of the file relating to the top layer of the image +shows the three image layers below it. They are listed in the order they are +stacked. + +```bash +$ cat /var/lib/docker/aufs/layers/91e54dfb11794fad694460162bf0cb0a4fa710cfa3f60979c177d920813e267c + +d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82 + +c22013c8472965aa5b62559f2b540cd440716ef149756e7b958a1b2aba421e87 + +d3a1f33e8a5a513092f01bb7eb1c2abf4d711e5105390a3fe1ae2248cfde1391 +``` + +The base layer in an image has no image layers below it, so its file is empty. + +### Containers + +Running containers are mounted at locations in the +`/var/lib/docker/aufs/mnt/` directory. This is the AUFS union +mount point that exposes the container and all underlying image layers as a +single unified view. If a container is not running, its directory still exists +but is empty. This is because containers are only mounted when they are running. + +Container metadata and various config files that are placed into the running +container are stored in `/var/lib/containers/`. Files in this +directory exist for all containers on the system, including ones that are +stopped. However, when a container is running the container's log files are also +in this directory. + +A container's thin writable layer is stored under +`/var/lib/docker/aufs/diff/`. This directory is stacked by AUFS as +the containers top writable layer and is where all changes to the container are +stored. The directory exists even if the container is stopped. This means that +restarting a container will not lose changes made to it. Once a container is +deleted this directory is deleted. + +Information about which image layers are stacked below a container's top +writable layer is stored in the following file +`/var/lib/docker/aufs/layers/`. The command below shows that the +container with ID `b41a6e5a508d` has 4 image layers below it: + +```bash +$ cat /var/lib/docker/aufs/layers/b41a6e5a508dfa02607199dfe51ed9345a675c977f2cafe8ef3e4b0b5773404e-init +91e54dfb11794fad694460162bf0cb0a4fa710cfa3f60979c177d920813e267c +d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82 +c22013c8472965aa5b62559f2b540cd440716ef149756e7b958a1b2aba421e87 +d3a1f33e8a5a513092f01bb7eb1c2abf4d711e5105390a3fe1ae2248cfde1391 +``` + +The image layers are shown in order. In the output above, the layer starting +with image ID "d3a1..." is the image's base layer. The image layer starting +with "91e5..." is the image's topmost layer. + + +## AUFS and Docker performance + +To summarize some of the performance related aspects already mentioned: + +- The AUFS storage driver is a good choice for PaaS and other similar use-cases where container density is important. This is because AUFS efficiently shares images between multiple running containers, enabling fast container start times and minimal use of disk space. + +- The underlying mechanics of how AUFS shares files between image layers and containers uses the systems page cache very efficiently. + +- The AUFS storage driver can introduce significant latencies into container write performance. This is because the first time a container writes to any file, the file has be located and copied into the containers top writable layer. These latencies increase and are compounded when these files exist below many image layers and the files themselves are large. + +One final point. Data volumes provide the best and most predictable performance. +This is because they bypass the storage driver and do not incur any of the +potential overheads introduced by thin provisioning and copy-on-write. For this +reason, you may want to place heavy write workloads on data volumes. + +## Related information + +* [Understand images, containers, and storage drivers](imagesandcontainers.md) +* [Select a storage driver](selectadriver.md) +* [BTRFS storage driver in practice](btrfs-driver.md) +* [Device Mapper storage driver in practice](device-mapper-driver.md) diff --git a/docs/userguide/storagedriver/btrfs-driver.md b/docs/userguide/storagedriver/btrfs-driver.md new file mode 100644 index 000000000..d7a622bff --- /dev/null +++ b/docs/userguide/storagedriver/btrfs-driver.md @@ -0,0 +1,280 @@ + + +# Docker and BTRFS in practice + +Btrfs is a next generation copy-on-write filesystem that supports many advanced +storage technologies that make it a good fit for Docker. Btrfs is included in +the mainline Linux kernel and it's on-disk-format is now considered stable. +However, many of its features are still under heavy development and users should +consider it a fast-moving target. + +Docker's `btrfs` storage driver leverages many Btrfs features for image and +container management. Among these features are thin provisioning, copy-on-write, +and snapshotting. + +This article refers to Docker's Btrfs storage driver as `btrfs` and the overall Btrfs Filesystem as Btrfs. + +>**Note**: The [Commercially Supported Docker Engine (CS-Engine)](https://www.docker.com/compatibility-maintenance) does not currently support the `btrfs` storage driver. + +## The future of Btrfs + +Btrfs has been long hailed as the future of Linux filesystems. With full support in the mainline Linux kernel, a stable on-disk-format, and active development with a focus on stability, this is now becoming more of a reality. + +As far as Docker on the Linux platform goes, many people see the `btrfs` storage driver as a potential long-term replacement for the `devicemapper` storage driver. However, at the time of writing, the `devicemapper` storage driver should be considered safer, more stable, and more *production ready*. You should only consider the `btrfs` driver for production deployments if you understand it well and have existing experience with Btrfs. + +## Image layering and sharing with Btrfs + +Docker leverages Btrfs *subvolumes* and *snapshots* for managing the on-disk components of image and container layers. Btrfs subvolumes look and feel like a normal Unix filesystem. As such, they can have their own internal directory structure that hooks into the wider Unix filesystem. + +Subvolumes are natively copy-on-write and have space allocated to them on-demand +from an underlying storage pool. They can also be nested and snapped. The +diagram blow shows 4 subvolumes. 'Subvolume 2' and 'Subvolume 3' are nested, +whereas 'Subvolume 4' shows its own internal directory tree. + +![](images/btfs_subvolume.jpg) + +Snapshots are a point-in-time read-write copy of an entire subvolume. They exist directly below the subvolume they were created from. You can create snapshots of snapshots as shown in the diagram below. + +![](images/btfs_snapshots.jpg) + +Btfs allocates space to subvolumes and snapshots on demand from an underlying pool of storage. The unit of allocation is referred to as a *chunk* and *chunks* are normally ~1GB in size. + +Snapshots are first-class citizens in a Btrfs filesystem. This means that they look, feel, and operate just like regular subvolumes. The technology required to create them is built directly into the Btrfs filesystem thanks to its native copy-on-write design. This means that Btrfs snapshots are space efficient with little or no performance overhead. The diagram below shows a subvolume and it's snapshot sharing the same data. + +![](images/btfs_pool.jpg) + +Docker's `btrfs` storage driver stores every image layer and container in its own Btrfs subvolume or snapshot. The base layer of an image is stored as a subvolume whereas child image layers and containers are stored as snapshots. This is shown in the diagram below. + +![](images/btfs_container_layer.jpg) + +The high level process for creating images and containers on Docker hosts running the `btrfs` driver is as follows: + +1. The image's base layer is stored in a Btrfs subvolume under +`/var/lib/docker/btrfs/subvolumes`. + + The image ID is used as the subvolume name. E.g., a base layer with image ID + "f9a9f253f6105141e0f8e091a6bcdb19e3f27af949842db93acba9048ed2410b" will be + stored in + `/var/lib/docker/btrfs/subvolumes/f9a9f253f6105141e0f8e091a6bcdb19e3f27af949842db93acba9048ed2410b` + +2. Subsequent image layers are stored as a Btrfs snapshot of the parent layer's subvolume or snapshot. + + The diagram below shows a three-layer image. The base layer is a subvolume. Layer 1 is a snapshot of the base layer's subvolume. Layer 2 is a snapshot of Layer 1's snapshot. + + ![](images/btfs_constructs.jpg) + +## Image and container on-disk constructs + +Image layers and containers are visible in the Docker host's filesystem at +`/var/lib/docker/btrfs/subvolumes/ OR `. Directories for +containers are present even for containers with a stopped status. This is +because the `btrfs` storage driver mounts a default, top-level subvolume at +`/var/lib/docker/subvolumes`. All other subvolumes and snapshots exist below +that as Btrfs filesystem objects and not as individual mounts. + +The following example shows a single Docker image with four image layers. + +```bash +$ sudo docker images -a +REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE +ubuntu latest 0a17decee413 2 weeks ago 188.3 MB + 3c9a9d7cc6a2 2 weeks ago 188.3 MB + eeb7cb91b09d 2 weeks ago 188.3 MB + f9a9f253f610 2 weeks ago 188.1 MB +``` + +Each image layer exists as a Btrfs subvolume or snapshot with the same name as it's image ID as illustrated by the `btrfs subvolume list` command shown below: + +```bash +$ sudo btrfs subvolume list /var/lib/docker +ID 257 gen 9 top level 5 path btrfs/subvolumes/f9a9f253f6105141e0f8e091a6bcdb19e3f27af949842db93acba9048ed2410b +ID 258 gen 10 top level 5 path btrfs/subvolumes/eeb7cb91b09d5de9edb2798301aeedf50848eacc2123e98538f9d014f80f243c +ID 260 gen 11 top level 5 path btrfs/subvolumes/3c9a9d7cc6a235eb2de58ca9ef3551c67ae42a991933ba4958d207b29142902b +ID 261 gen 12 top level 5 path btrfs/subvolumes/0a17decee4139b0de68478f149cc16346f5e711c5ae3bb969895f22dd6723751 +``` + +Under the `/var/lib/docker/btrfs/subvolumes` directoy, each of these subvolumes and snapshots are visible as a normal Unix directory: + +```bash +$ ls -l /var/lib/docker/btrfs/subvolumes/ +total 0 +drwxr-xr-x 1 root root 132 Oct 16 14:44 0a17decee4139b0de68478f149cc16346f5e711c5ae3bb969895f22dd6723751 +drwxr-xr-x 1 root root 132 Oct 16 14:44 3c9a9d7cc6a235eb2de58ca9ef3551c67ae42a991933ba4958d207b29142902b +drwxr-xr-x 1 root root 132 Oct 16 14:44 eeb7cb91b09d5de9edb2798301aeedf50848eacc2123e98538f9d014f80f243c +drwxr-xr-x 1 root root 132 Oct 16 14:44 f9a9f253f6105141e0f8e091a6bcdb19e3f27af949842db93acba9048ed2410b +``` + +Because Btrfs works at the filesystem level and not the block level, each image +and container layer can be browsed in the filesystem using normal Unix commands. +The example below shows a truncated output of an `ls -l` command against the +image's top layer: + +```bash +$ ls -l /var/lib/docker/btrfs/subvolumes/0a17decee4139b0de68478f149cc16346f5e711c5ae3bb969895f22dd6723751/ +total 0 +drwxr-xr-x 1 root root 1372 Oct 9 08:39 bin +drwxr-xr-x 1 root root 0 Apr 10 2014 boot +drwxr-xr-x 1 root root 882 Oct 9 08:38 dev +drwxr-xr-x 1 root root 2040 Oct 12 17:27 etc +drwxr-xr-x 1 root root 0 Apr 10 2014 home +...output truncated... +``` + +## Container reads and writes with Btrfs + +A container is a space-efficient snapshot of an image. Metadata in the snapshot +points to the actual data blocks in the storage pool. This is the same as with a +subvolume. Therefore, reads performed against a snapshot are essentially the +same as reads performed against a subvolume. As a result, no performance +overhead is incurred from the Btrfs driver. + +Writing a new file to a container invokes an allocate-on-demand operation to +allocate new data block to the container's snapshot. The file is then written to +this new space. The allocate-on-demand operation is native to all writes with +Btrfs and is the same as writing new data to a subvolume. As a result, writing +new files to a container's snapshot operate at native Btrfs speeds. + +Updating an existing file in a container causes a copy-on-write operation +(technically *redirect-on-write*). The driver leaves the original data and +allocates new space to the snapshot. The updated data is written to this new +space. Then, the driver updates the filesystem metadata in the snapshot to point +to this new data. The original data is preserved in-place for subvolumes and +snapshots further up the tree. This behavior is native to copy-on-write +filesystems like Btrfs and incurs very little overhead. + +With Btfs, writing and updating lots of small files can result in slow performance. More on this later. + +## Configuring Docker with Btrfs + +The `btrfs` storage driver only operates on a Docker host where `/var/lib/docker` is mounted as a Btrfs filesystem. The following procedure shows how to configure Btrfs on Ubuntu 14.04 LTS. + +### Prerequisites + +If you have already used the Docker daemon on your Docker host and have images you want to keep, `push` them to Docker Hub or your private Docker Trusted Registry before attempting this procedure. + +Stop the Docker daemon. Then, ensure that you have a spare block device at `/dev/xvdb`. The device identifier may be different in your environment and you should substitute your own values throughout the procedure. + +The procedure also assumes your kernel has the appropriate Btrfs modules loaded. To verify this, use the following command: + +```bash +$ cat /proc/filesystems | grep btrfs` +``` + +### Configure Btrfs on Ubuntu 14.04 LTS + +Assuming your system meets the prerequisites, do the following: + +1. Install the "btrfs-tools" package. + + $ sudo apt-get install btrfs-tools + Reading package lists... Done + Building dependency tree + + +2. Create the Btrfs storage pool. + + Btrfs storage pools are created with the `mkfs.btrfs` command. Passing multiple devices to the `mkfs.btrfs` command creates a pool across all of those devices. Here you create a pool with a single device at `/dev/xvdb`. + + $ sudo mkfs.btrfs -f /dev/xvdb + WARNING! - Btrfs v3.12 IS EXPERIMENTAL + WARNING! - see http://btrfs.wiki.kernel.org before using + + Turning ON incompat feature 'extref': increased hardlink limit per file to 65536 + fs created label (null) on /dev/xvdb + nodesize 16384 leafsize 16384 sectorsize 4096 size 4.00GiB + Btrfs v3.12 + + Be sure to substitute `/dev/xvdb` with the appropriate device(s) on your + system. + + > **Warning**: Take note of the warning about Btrfs being experimental. As + noted earlier, Btrfs is not currently recommended for production deployments + unless you already have extensive experience. + +3. If it does not already exist, create a directory for the Docker host's local storage area at `/var/lib/docker`. + + $ sudo mkdir /var/lib/docker + +4. Configure the system to automatically mount the Btrfs filesystem each time the system boots. + + a. Obtain the Btrfs filesystem's UUID. + + $ sudo blkid /dev/xvdb + /dev/xvdb: UUID="a0ed851e-158b-4120-8416-c9b072c8cf47" UUID_SUB="c3927a64-4454-4eef-95c2-a7d44ac0cf27" TYPE="btrfs" + + b. Create a `/etc/fstab` entry to automatically mount `/var/lib/docker` each time the system boots. + + /dev/xvdb /var/lib/docker btrfs defaults 0 0 + UUID="a0ed851e-158b-4120-8416-c9b072c8cf47" /var/lib/docker btrfs defaults 0 0 + +5. Mount the new filesystem and verify the operation. + + $ sudo mount -a + $ mount + /dev/xvda1 on / type ext4 (rw,discard) + + /dev/xvdb on /var/lib/docker type btrfs (rw) + + The last line in the output above shows the `/dev/xvdb` mounted at `/var/lib/docker` as Btrfs. + + +Now that you have a Btrfs filesystem mounted at `/var/lib/docker`, the daemon should automatically load with the `btrfs` storage driver. + +1. Start the Docker daemon. + + $ sudo service docker start + docker start/running, process 2315 + + The procedure for starting the Docker daemon may differ depending on the + Linux distribution you are using. + + You can start the Docker daemon with the `btrfs` storage driver by passing + the `--storage-driver=btrfs` flag to the `docker daemon` command or you can + add the `DOCKER_OPTS` line to the Docker config file. + +2. Verify the storage driver with the `docker info` command. + + $ sudo docker info + Containers: 0 + Images: 0 + Storage Driver: btrfs + [...] + +Your Docker host is now configured to use the `btrfs` storage driver. + +## BTRFS and Docker performance + +There are several factors that influence Docker's performance under the `btrfs` storage driver. + +- **Page caching**. Btrfs does not support page cache sharing. This means that *n* containers accessing the same file require *n* copies to be cached. As a result, the `btrfs` driver may not be the best choice for PaaS and other high density container use cases. + +- **Small writes**. Containers performing lots of small writes (including Docker hosts that start and stop many containers) can lead to poor use of Btrfs chunks. This can ultimately lead to out-of-space conditions on your Docker host and stop it working. This is currently a major drawback to using current versions of Btrfs. + + If you use the `btrfs` storage driver, closely monitor the free space on your Btrfs filesystem using the `btrfs filesys show` command. Do not trust the output of normal Unix commands such as `df`; always use the Btrfs native commands. + +- **Sequential writes**. Btrfs writes data to disk via journaling technique. This can impact sequential writes, where performance can be up to half. + +- **Fragmentation**. Fragmentation is a natural byproduct of copy-on-write filesystems like Btrfs. Many small random writes can compound this issue. It can manifest as CPU spikes on Docker hosts using SSD media and head thrashing on Docker hosts using spinning media. Both of these result in poor performance. + + Recent versions of Btrfs allow you to specify `autodefrag` as a mount option. This mode attempts to detect random writes and defragment them. You should perform your own tests before enabling this option on your Docker hosts. Some tests have shown this option has a negative performance impact on Docker hosts performing lots of small writes (including systems that start and stop many containers). + +- **Solid State Devices (SSD)**. Btrfs has native optimizations for SSD media. To enable these, mount with the `-o ssd` mount option. These optimizations include enhanced SSD write performance by avoiding things like *seek optimizations* that have no use on SSD media. + + Btfs also supports the TRIM/Discard primitives. However, mounting with the `-o discard` mount option can cause performance issues. Therefore, it is recommended you perform your own tests before using this option. + +- **Use Data Volumes**. Data volumes provide the best and most predictable performance. This is because they bypass the storage driver and do not incur any of the potential overheads introduced by thin provisioning and copy-on-write. For this reason, you may want to place heavy write workloads on data volumes. + +## Related Information + +* [Understand images, containers, and storage drivers](imagesandcontainers.md) +* [Select a storage driver](selectadriver.md) +* [AUFS storage driver in practice](aufs-driver.md) +* [Device Mapper storage driver in practice](device-mapper-driver.md) diff --git a/docs/userguide/storagedriver/device-mapper-driver.md b/docs/userguide/storagedriver/device-mapper-driver.md new file mode 100644 index 000000000..5d6df7dbb --- /dev/null +++ b/docs/userguide/storagedriver/device-mapper-driver.md @@ -0,0 +1,310 @@ + + +# Docker and the Device Mapper storage driver + +Device Mapper is a kernel-based framework that underpins many advanced +volume management technologies on Linux. Docker's `devicemapper` storage driver +leverages the thin provisioning and snapshotting capabilities of this framework +for image and container management. This article refers to the Device Mapper +storage driver as `devicemapper`, and the kernel framework as `Device Mapper`. + + +>**Note**: The [Commercially Supported Docker Engine (CS-Engine) running on RHEL and CentOS Linux](https://www.docker.com/compatibility-maintenance) requires that you use the `devicemapper` storage driver. + + +## An alternative to AUFS + +Docker originally ran on Ubuntu and Debian Linux and used AUFS for its storage +backend. As Docker became popular, many of the companies that wanted to use it +were using Red Hat Enterprise Linux (RHEL). Unfortunately, because the upstream +mainline Linux kernel did not include AUFS, RHEL did not use AUFS either. + +To correct this Red Hat developers investigated getting AUFS into the mainline +kernel. Ultimately, though, they decided a better idea was to develop a new +storage backend. Moreover, they would base this new storage backend on existing +`Device Mapper` technology. + +Red Hat collaborated with Docker Inc. to contribute this new driver. As a result +of this collaboration, Docker's Engine was re-engineered to make the storage +backend pluggable. So it was that the `devicemapper` became the second storage +driver Docker supported. + +Device Mapper has been included in the mainline Linux kernel since version +2.6.9. It is a core part of RHEL family of Linux distributions. This means that +the `devicemapper` storage driver is based on stable code that has a lot of +real-world production deployments and strong community support. + + +## Image layering and sharing + +The `devicemapper` driver stores every image and container on its own virtual +device. These devices are thin-provisioned copy-on-write snapshot devices. +Device Mapper technology works at the block level rather than the file level. +This means that `devicemapper` storage driver's thin provisioning and +copy-on-write operations work with blocks rather than entire files. + +>**Note**: Snapshots are also referred to as *thin devices* or *virtual devices*. They all mean the same thing in the context of the `devicemapper` storage driver. + +With the `devicemapper` the high level process for creating images is as follows: + +1. The `devicemapper` storage driver creates a thin pool. + + The pool is created from block devices or loop mounted sparse files (more on this later). + +2. Next it creates a *base device*. + + A base device is a thin device with a filesystem. You can see which filesystem is in use by running the `docker info` command and checking the `Backing filesystem` value. + +3. Each new image (and image layer) is a snapshot of this base device. + + These are thin provisioned copy-on-write snapshots. This means that they are initially empty and only consume space from the pool when data is written to them. + +With `devicemapper`, container layers are snapshots of the image they are created from. Just as with images, container snapshots are thin provisioned copy-on-write snapshots. The container snapshot stores all updates to the container. The `devicemapper` allocates space to them on-demand from the pool as and when data is written to the container. + +The high level diagram below shows a thin pool with a base device and two images. + +![](images/base_device.jpg) + +If you look closely at the diagram you'll see that it's snapshots all the way down. Each image layer is a snapshot of the layer below it. The lowest layer of each image is a snapshot of the the base device that exists in the pool. This base device is a `Device Mapper` artifact and not a Docker image layer. + +A container is a snapshot of the image it is created from. The diagram below shows two containers - one based on the Ubuntu image and the other based on the Busybox image. + +![](images/two_dm_container.jpg) + + +## Reads with the devicemapper + +Let's look at how reads and writes occur using the `devicemapper` storage driver. The diagram below shows the high level process for reading a single block (`0x44f`) in an example container. + +![](images/dm_container.jpg) + +1. An application makes a read request for block 0x44f in the container. + + Because the container is a thin snapshot of an image it does not have the data. Instead, it has a pointer (PTR) to where the data is stored in the image snapshot lower down in the image stack. + +2. The storage driver follows the pointer to block `0xf33` in the snapshot relating to image layer `a005...`. + +3. The `devicemapper` copies the contents of block `0xf33` from the image snapshot to memory in the container. + +4. The storage driver returns the data to the requesting application. + +### Write examples + +With the `devicemapper` driver, writing new data to a container is accomplished by an *allocate-on-demand* operation. Updating existing data uses a copy-on-write operation. Because Device Mapper is a block-based technology these operations occur at the block level. + +For example, when making a small change to a large file in a container, the `devicemapper` storage driver does not copy the entire file. It only copies the blocks to be modified. Each block is 64KB. + +#### Writing new data + +To write 56KB of new data to a container: + +1. An application makes a request to write 56KB of new data to the container. + +2. The allocate-on-demand operation allocates a single new 64KB block to the containers snapshot. + + If the write operation is larger than 64KB, multiple new blocks are allocated to the container snapshot. + +3. The data is written to the newly allocated block. + +#### Overwriting existing data + +To modify existing data for the first time: + +1. An application makes a request to modify some data in the container. + +2. A copy-on-write operation locates the blocks that need updating. + +3. The operation allocates new blocks to the container snapshot and copies the data into those blocks. + +4. The modified data is written into the newly allocated blocks. + +The application in the container is unaware of any of these +allocate-on-demand and copy-on-write operations. However, they may add latency +to the application's read and write operations. + +## Configuring Docker with Device Mapper + +The `devicemapper` is the default Docker storage driver on some Linux +distributions. This includes RHEL and most of its forks. Currently, the following distributions support the driver: + +* RHEL/CentOS/Fedora +* Ubuntu 12.04 +* Ubuntu 14.04 +* Debian + +Docker hosts running the `devicemapper` storage driver default to a +configuration mode known as `loop-lvm`. This mode uses sparse files to build +the thin pool used by image and container snapshots. The mode is designed to work out-of-the-box +with no additional configuration. However, production deployments should not run +under `loop-lvm` mode. + +You can detect the mode by viewing the `docker info` command: + + $ sudo docker info + Containers: 0 + Images: 0 + Storage Driver: devicemapper + Pool Name: docker-202:2-25220302-pool + Pool Blocksize: 65.54 kB + Backing Filesystem: xfs + ... + Data loop file: /var/lib/docker/devicemapper/devicemapper/data + Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata + Library Version: 1.02.93-RHEL7 (2015-01-28) + ... + +The output above shows a Docker host running with the `devicemapper` storage driver operating in `loop-lvm` mode. This is indicated by the fact that the `Data loop file` and a `Metadata loop file` are on files under `/var/lib/docker/devicemapper/devicemapper`. These are loopback mounted sparse files. + +### Configure direct-lvm mode for production + +The preferred configuration for production deployments is `direct lvm`. This +mode uses block devices to create the thin pool. The following procedure shows +you how to configure a Docker host to use the `devicemapper` storage driver in a +`direct-lvm` configuration. + +> **Caution:** If you have already run the Docker daemon on your Docker host and have images you want to keep, `push` them Docker Hub or your private Docker Trusted Registry before attempting this procedure. + +The procedure below will create a 90GB data volume and 4GB metadata volume to use as backing for the storage pool. It assumes that you have a spare block device at `/dev/xvdf` with enough free space to complete the task. The device identifier and volume sizes may be be different in your environment and you should substitute your own values throughout the procedure. The procedure also assumes that the Docker daemon is in the `stopped` state. + +1. Log in to the Docker host you want to configure and stop the Docker daemon. + +2. If it exists, delete your existing image store by removing the `/var/lib/docker` directory. + + $ sudo rm -rf /var/lib/docker + +3. Create an LVM physical volume (PV) on your spare block device using the `pvcreate` command. + + $ sudo pvcreate /dev/xvdf + Physical volume `/dev/xvdf` successfully created + + The device identifier may be different on your system. Remember to substitute your value in the command above. + +4. Create a new volume group (VG) called `vg-docker` using the PV created in the previous step. + + $ sudo vgcreate vg-docker /dev/xvdf + Volume group `vg-docker` successfully created + +5. Create a new 90GB logical volume (LV) called `data` from space in the `vg-docker` volume group. + + $ sudo lvcreate -L 90G -n data vg-docker + Logical volume `data` created. + + The command creates an LVM logical volume called `data` and an associated block device file at `/dev/vg-docker/data`. In a later step, you instruct the `devicemapper` storage driver to use this block device to store image and container data. + + If you receive a signature detection warning, make sure you are working on the correct devices before continuing. Signature warnings indicate that the device you're working on is currently in use by LVM or has been used by LVM in the past. + +6. Create a new logical volume (LV) called `metadata` from space in the `vg-docker` volume group. + + $ sudo lvcreate -L 4G -n metadata vg-docker + Logical volume `metadata` created. + + This creates an LVM logical volume called `metadata` and an associated block device file at `/dev/vg-docker/metadata`. In the next step you instruct the `devicemapper` storage driver to use this block device to store image and container metadata. + +5. Start the Docker daemon with the `devicemapper` storage driver and the `--storage-opt` flags. + + The `data` and `metadata` devices that you pass to the `--storage-opt` options were created in the previous steps. + + $ sudo docker daemon --storage-driver=devicemapper --storage-opt dm.datadev=/dev/vg-docker/data --storage-opt dm.metadatadev=/dev/vg-docker/metadata & + [1] 2163 + [root@ip-10-0-0-75 centos]# INFO[0000] Listening for HTTP on unix (/var/run/docker.sock) + INFO[0027] Option DefaultDriver: bridge + INFO[0027] Option DefaultNetwork: bridge + + INFO[0027] Daemon has completed initialization + INFO[0027] Docker daemon commit=0a8c2e3 execdriver=native-0.2 graphdriver=devicemapper version=1.8.2 + + It is also possible to set the `--storage-driver` and `--storage-opt` flags in the Docker config file and start the daemon normally using the `service` or `systemd` commands. + +6. Use the `docker info` command to verify that the daemon is using `data` and `metadata` devices you created. + + $ sudo docker info + INFO[0180] GET /v1.20/info + Containers: 0 + Images: 0 + Storage Driver: devicemapper + Pool Name: docker-202:1-1032-pool + Pool Blocksize: 65.54 kB + Backing Filesystem: xfs + Data file: /dev/vg-docker/data + Metadata file: /dev/vg-docker/metadata + [...] + + The output of the command above shows the storage driver as `devicemapper`. The last two lines also confirm that the correct devices are being used for the `Data file` and the `Metadata file`. + +### Examine devicemapper structures on the host + +You can use the `lsblk` command to see the device files created above and the `pool` that the `devicemapper` storage driver creates on top of them. + + $ sudo lsblk + NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT + xvda 202:0 0 8G 0 disk + └─xvda1 202:1 0 8G 0 part / + xvdf 202:80 0 100G 0 disk + ├─vg--docker-data 253:0 0 90G 0 lvm + │ └─docker-202:1-1032-pool 253:2 0 100G 0 dm + └─vg--docker-metadata 253:1 0 4G 0 lvm + └─docker-202:1-1032-pool 253:2 0 100G 0 dm + +The diagram below shows the image from prior examples updated with the detail from the `lsblk` command above. + +![](http://farm1.staticflickr.com/703/22116692899_0471e5e160_b.jpg) + +In the diagram, the pool is named `Docker-202:1-1032-pool` and spans the `data` and `metadata` devices created earlier. The `devicemapper` constructs the pool name as follows: + +``` +Docker-MAJ:MIN-INO-pool +``` + +`MAJ`, `MIN` and `INO` refer to the major and minor device numbers and inode. + +Because Device Mapper operates at the block level it is more difficult to see +diffs between image layers and containers. However, there are two key +directories. The `/var/lib/docker/devicemapper/mnt` directory contains the mount +points for images and containers. The `/var/lib/docker/devicemapper/metadata` +directory contains one file for every image and container snapshot. The files +contain metadata about each snapshot in JSON format. + +## Device Mapper and Docker performance + +It is important to understand the impact that allocate-on-demand and copy-on-write operations can have on overall container performance. + +### Allocate-on-demand performance impact + +The `devicemapper` storage driver allocates new blocks to a container via an allocate-on-demand operation. This means that each time an app writes to somewhere new inside a container, one or more empty blocks has to be located from the pool and mapped into the container. + +All blocks are 64KB. A write that uses less than 64KB still results in a single 64KB block being allocated. Writing more than 64KB of data uses multiple 64KB blocks. This can impact container performance, especially in containers that perform lots of small writes. However, once a block is allocated to a container subsequent reads and writes can operate directly on that block. + +### Copy-on-write performance impact + +Each time a container updates existing data for the first time, the `devicemapper` storage driver has to perform a copy-on-write operation. This copies the data from the image snapshot to the container's snapshot. This process can have a noticeable impact on container performance. + +All copy-on-write operations have a 64KB granularity. As a results, updating 32KB of a 1GB file causes the driver to copy a single 64KB block into the container's snapshot. This has obvious performance advantages over file-level copy-on-write operations which would require copying the entire 1GB file into the container layer. + +In practice, however, containers that perform lots of small block writes (<64KB) can perform worse with `devicemapper` than with AUFS. + +### Other device mapper performance considerations + +There are several other things that impact the performance of the `devicemapper` storage driver.. + +- **The mode.** The default mode for Docker running the `devicemapper` storage driver is `loop-lvm`. This mode uses sparse files and suffers from poor performance. It is **not recommended for production**. The recommended mode for production environments is `direct-lvm` where the storage driver writes directly to raw block devices. + +- **High speed storage.** For best performance you should place the `Data file` and `Metadata file` on high speed storage such as SSD. This can be direct attached storage or from a SAN or NAS array. + +- **Memory usage.** `devicemapper` is not the most memory efficient Docker storage driver. Launching *n* copies of the same container loads *n* copies of its files into memory. This can have a memory impact on your Docker host. As a result, the `devicemapper` storage driver may not be the best choice for PaaS and other high density use cases. + +One final point, data volumes provide the best and most predictable performance. This is because they bypass the storage driver and do not incur any of the potential overheads introduced by thin provisioning and copy-on-write. For this reason, you may want to place heavy write workloads on data volumes. + +## Related Information + +* [Understand images, containers, and storage drivers](imagesandcontainers.md) +* [Select a storage driver](selectadriver.md) +* [AUFS storage driver in practice](aufs-driver.md) +* [BTRFS storage driver in practice](btrfs-driver.md) diff --git a/docs/userguide/storagedriver/images/aufs_delete.jpg b/docs/userguide/storagedriver/images/aufs_delete.jpg new file mode 100644 index 000000000..fa0f0d2e9 Binary files /dev/null and b/docs/userguide/storagedriver/images/aufs_delete.jpg differ diff --git a/docs/userguide/storagedriver/images/aufs_layers.jpg b/docs/userguide/storagedriver/images/aufs_layers.jpg new file mode 100644 index 000000000..933b0b3bd Binary files /dev/null and b/docs/userguide/storagedriver/images/aufs_layers.jpg differ diff --git a/docs/userguide/storagedriver/images/aufs_metadata.jpg b/docs/userguide/storagedriver/images/aufs_metadata.jpg new file mode 100644 index 000000000..378e36b84 Binary files /dev/null and b/docs/userguide/storagedriver/images/aufs_metadata.jpg differ diff --git a/docs/userguide/storagedriver/images/base_device.jpg b/docs/userguide/storagedriver/images/base_device.jpg new file mode 100644 index 000000000..3db490d83 Binary files /dev/null and b/docs/userguide/storagedriver/images/base_device.jpg differ diff --git a/docs/userguide/storagedriver/images/btfs_constructs.jpg b/docs/userguide/storagedriver/images/btfs_constructs.jpg new file mode 100644 index 000000000..4ceeb6ad1 Binary files /dev/null and b/docs/userguide/storagedriver/images/btfs_constructs.jpg differ diff --git a/docs/userguide/storagedriver/images/btfs_container_layer.jpg b/docs/userguide/storagedriver/images/btfs_container_layer.jpg new file mode 100644 index 000000000..1905119e0 Binary files /dev/null and b/docs/userguide/storagedriver/images/btfs_container_layer.jpg differ diff --git a/docs/userguide/storagedriver/images/btfs_layers.png b/docs/userguide/storagedriver/images/btfs_layers.png new file mode 100644 index 000000000..12b61e5b5 Binary files /dev/null and b/docs/userguide/storagedriver/images/btfs_layers.png differ diff --git a/docs/userguide/storagedriver/images/btfs_pool.jpg b/docs/userguide/storagedriver/images/btfs_pool.jpg new file mode 100644 index 000000000..5295b9fb4 Binary files /dev/null and b/docs/userguide/storagedriver/images/btfs_pool.jpg differ diff --git a/docs/userguide/storagedriver/images/btfs_snapshots.jpg b/docs/userguide/storagedriver/images/btfs_snapshots.jpg new file mode 100644 index 000000000..94c7797ac Binary files /dev/null and b/docs/userguide/storagedriver/images/btfs_snapshots.jpg differ diff --git a/docs/userguide/storagedriver/images/btfs_subvolume.jpg b/docs/userguide/storagedriver/images/btfs_subvolume.jpg new file mode 100644 index 000000000..715199d43 Binary files /dev/null and b/docs/userguide/storagedriver/images/btfs_subvolume.jpg differ diff --git a/docs/userguide/storagedriver/images/container-layers.jpg b/docs/userguide/storagedriver/images/container-layers.jpg new file mode 100644 index 000000000..00c30a204 Binary files /dev/null and b/docs/userguide/storagedriver/images/container-layers.jpg differ diff --git a/docs/userguide/storagedriver/images/dm_container.jpg b/docs/userguide/storagedriver/images/dm_container.jpg new file mode 100644 index 000000000..2a5cd58cc Binary files /dev/null and b/docs/userguide/storagedriver/images/dm_container.jpg differ diff --git a/docs/userguide/storagedriver/images/image-layers.jpg b/docs/userguide/storagedriver/images/image-layers.jpg new file mode 100644 index 000000000..378e36b84 Binary files /dev/null and b/docs/userguide/storagedriver/images/image-layers.jpg differ diff --git a/docs/userguide/storagedriver/images/overlay_constructs.jpg b/docs/userguide/storagedriver/images/overlay_constructs.jpg new file mode 100644 index 000000000..fffe07f56 Binary files /dev/null and b/docs/userguide/storagedriver/images/overlay_constructs.jpg differ diff --git a/docs/userguide/storagedriver/images/overlay_constructs2.jpg b/docs/userguide/storagedriver/images/overlay_constructs2.jpg new file mode 100644 index 000000000..bbcd6e005 Binary files /dev/null and b/docs/userguide/storagedriver/images/overlay_constructs2.jpg differ diff --git a/docs/userguide/storagedriver/images/saving-space.jpg b/docs/userguide/storagedriver/images/saving-space.jpg new file mode 100644 index 000000000..721e90c30 Binary files /dev/null and b/docs/userguide/storagedriver/images/saving-space.jpg differ diff --git a/docs/userguide/storagedriver/images/shared-uuid.jpg b/docs/userguide/storagedriver/images/shared-uuid.jpg new file mode 100644 index 000000000..2ac68bf53 Binary files /dev/null and b/docs/userguide/storagedriver/images/shared-uuid.jpg differ diff --git a/docs/userguide/storagedriver/images/shared-volume.jpg b/docs/userguide/storagedriver/images/shared-volume.jpg new file mode 100644 index 000000000..cd39b86a1 Binary files /dev/null and b/docs/userguide/storagedriver/images/shared-volume.jpg differ diff --git a/docs/userguide/storagedriver/images/sharing-layers.jpg b/docs/userguide/storagedriver/images/sharing-layers.jpg new file mode 100644 index 000000000..e59fa833c Binary files /dev/null and b/docs/userguide/storagedriver/images/sharing-layers.jpg differ diff --git a/docs/userguide/storagedriver/images/two_dm_container.jpg b/docs/userguide/storagedriver/images/two_dm_container.jpg new file mode 100644 index 000000000..18d840d0b Binary files /dev/null and b/docs/userguide/storagedriver/images/two_dm_container.jpg differ diff --git a/docs/userguide/storagedriver/images/zfs_clones.jpg b/docs/userguide/storagedriver/images/zfs_clones.jpg new file mode 100644 index 000000000..0849aa245 Binary files /dev/null and b/docs/userguide/storagedriver/images/zfs_clones.jpg differ diff --git a/docs/userguide/storagedriver/images/zfs_zpool.jpg b/docs/userguide/storagedriver/images/zfs_zpool.jpg new file mode 100644 index 000000000..17ab2ace5 Binary files /dev/null and b/docs/userguide/storagedriver/images/zfs_zpool.jpg differ diff --git a/docs/userguide/storagedriver/images/zpool_blocks.jpg b/docs/userguide/storagedriver/images/zpool_blocks.jpg new file mode 100644 index 000000000..6fd2482ee Binary files /dev/null and b/docs/userguide/storagedriver/images/zpool_blocks.jpg differ diff --git a/docs/userguide/storagedriver/imagesandcontainers.md b/docs/userguide/storagedriver/imagesandcontainers.md new file mode 100644 index 000000000..6a29183a7 --- /dev/null +++ b/docs/userguide/storagedriver/imagesandcontainers.md @@ -0,0 +1,255 @@ + + + +# Understand images, containers, and storage drivers + +To use storage drivers effectively, you must understand how Docker builds and +stores images. Then, you need an understanding of how these images are used in containers. Finally, you'll need a short introduction to the technologies that enable both images and container operations. + +## Images and containers rely on layers + +Docker images are a series of read-only layers that are stacked +on top of each other to form a single unified view. The first image in the stack +is called a *base image* and all the other layers are stacked on top of this +layer. The diagram below shows the Ubuntu 15:04 image comprising 4 stacked image layers. + +![](images/image-layers.jpg) + +When you make a change inside a container by, for example, adding a new file to the Ubuntu 15.04 image, you add a new layer on top of the underlying image stack. This change creates a new image layer containing the newly added file. Each image layer has its own universal unique identifier (UUID) and each successive image layer builds on top of the image layer below it. + +Containers (in the storage context) are a combination of a Docker image with a +thin writable layer added to the top known as the *container layer*. The diagram below shows a container running the Ubuntu 15.04 image. + +![](images/container-layers.jpg) + +The major difference between a container and an image is this writable layer. All writes to the container that add new or modifying existing data are stored in this writable layer. When the container is deleted the writeable layer is also deleted. The image remains unchanged. + +Because each container has its own thin writable container layer and all data is stored this container layer, this means that multiple containers can share access to the same underlying image and yet have their own data state. The diagram below shows multiple containers sharing the same Ubuntu 15.04 image. + +![](images/sharing-layers.jpg) + +A storage driver is responsible for enabling and managing both the image layers and the writeable container layer. How a storage driver accomplishes these behaviors can vary. Two key technologies behind Docker image and container management are stackable image layers and copy-on-write (CoW). + + +## The copy-on-write strategy + +Sharing is a good way to optimize resources. People do this instinctively in +daily life. For example, twins Jane and Joseph taking an Algebra class at +different times from different teachers can share the same exercise book by +passing it between each other. Now, suppose Jane gets an assignment to complete +the homework on page 11 in the book. At that point, Jane copy page 11, complete the homework, and hand in her copy. The original exercise book is unchanged and only Jane has a copy of the changed page 11. + +Copy-on-write is a similar strategy of sharing and copying. In this strategy, +system processes that need the same data share the same instance of that data +rather than having their own copy. At some point, if one process needs to modify +or write to the data, only then does the operating system make a copy of the +data for that process to use. Only the process that needs to write has access to +the data copy. All the other processes continue to use the original data. + +Docker uses a copy-on-write technology with both images and containers. This CoW +strategy optimizes both image disk space usage and the performance of container +start times. The next sections look at how copy-on-write is leveraged with +images and containers thru sharing and copying. + +### Sharing promotes smaller images + +This section looks at image layers and copy-on-write technology. All image and container layers exist inside the Docker host's *local storage area* and are managed by the storage driver. It is a location on the host's +filesystem. + +The Docker client reports on image layers when instructed to pull and push +images with `docker pull` and `docker push`. The command below pulls the +`ubuntu:15.04` Docker image from Docker Hub. + + $ docker pull ubuntu:15.04 + 15.04: Pulling from library/ubuntu + 6e6a100fa147: Pull complete + 13c0c663a321: Pull complete + 2bd276ed39d5: Pull complete + 013f3d01d247: Pull complete + Digest: sha256:c7ecf33cef00ae34b131605c31486c91f5fd9a76315d075db2afd39d1ccdf3ed + Status: Downloaded newer image for ubuntu:15.04 + +From the output, you'll see that the command actually pulls 4 image layers. +Each of the above lines lists an image layer and its UUID. The combination of +these four layers makes up the `ubuntu:15.04` Docker image. + +The image layers are stored in the Docker host's local storage area. Typically, +the local storage area is in the host's `/var/lib/docker` directory. Depending +on which storage driver the local storage area may be in a different location. You can list the layers in the local storage area. The following example shows the storage as it appears under the AUFS storage driver: + + $ sudo ls /var/lib/docker/aufs/layers + 013f3d01d24738964bb7101fa83a926181d600ebecca7206dced59669e6e6778 2bd276ed39d5fcfd3d00ce0a190beeea508332f5aec3c6a125cc619a3fdbade6 + 13c0c663a321cd83a97f4ce1ecbaf17c2ba166527c3b06daaefe30695c5fcb8c 6e6a100fa147e6db53b684c8516e3e2588b160fd4898b6265545d5d4edb6796d + +If you `pull` another image that shares some of the same image layers as the `ubuntu:15.04` image, the Docker daemon recognize this, and only pull the layers it hasn't already stored. After the second pull, the two images will share any common image layers. + +You can illustrate this now for yourself. Starting the `ubuntu:15.04` image that +you just pulled, make a change to it, and build a new image based on the change. +One way to do this is using a Dockerfile and the `docker build` command. + +1. In an empty directory, create a simple `Dockerfile` that starts with the ubuntu:15.04 image. + + FROM ubuntu:15.04 + +2. Add a new file called "newfile" in the image's `/tmp` directory with the text "Hello world" in it. + + When you are done, the `Dockerfile` contains two lines: + + FROM ubuntu:15.04 + + RUN echo "Hello world" > /tmp/newfile + +3. Save and close the file. + +2. From a terminal in the same folder as your Dockerfile, run the following command: + + $ docker build -t changed-ubuntu . + Sending build context to Docker daemon 2.048 kB + Step 0 : FROM ubuntu:15.04 + ---> 013f3d01d247 + Step 1 : RUN echo "Hello world" > /tmp/newfile + ---> Running in 2023460815df + ---> 03b964f68d06 + Removing intermediate container 2023460815df + Successfully built 03b964f68d06 + + > **Note:** The period (.) at the end of the above command is important. It tells the `docker build` command to use the current working directory as its build context. + + The output above shows a new image with image ID `03b964f68d06`. + +3. Run the `docker images` command to verify the new image is in the Docker host's local storage area. + + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + changed-ubuntu latest 03b964f68d06 33 seconds ago 131.4 MB + ubuntu + +4. Run the `docker history` command to see which image layers were used to create the new `changed-ubuntu` image. + + $ docker history changed-ubuntu + IMAGE CREATED CREATED BY SIZE COMMENT + 03b964f68d06 About a minute ago /bin/sh -c echo "Hello world" > /tmp/newfile 12 B + 013f3d01d247 6 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B + 2bd276ed39d5 6 weeks ago /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/ 1.879 kB + 13c0c663a321 6 weeks ago /bin/sh -c echo '#!/bin/sh' > /usr/sbin/polic 701 B + 6e6a100fa147 6 weeks ago /bin/sh -c #(nop) ADD file:49710b44e2ae0edef4 131.4 MB + + The `docker history` output shows the new `03b964f68d06` image layer at the + top. You know that the `03b964f68d06` layer was added because it was created + by the `echo "Hello world" > /tmp/newfile` command in your `Dockerfile`. + The 4 image layers below it are the exact same image layers the make up the + ubuntu:15.04 image as their UUIDs match. + +5. List the contents of the local storage area to further confirm. + + $ sudo ls /var/lib/docker/aufs/layers + 013f3d01d24738964bb7101fa83a926181d600ebecca7206dced59669e6e6778 2bd276ed39d5fcfd3d00ce0a190beeea508332f5aec3c6a125cc619a3fdbade6 + 03b964f68d06a373933bd6d61d37610a34a355c168b08dfc604f57b20647e073 6e6a100fa147e6db53b684c8516e3e2588b160fd4898b6265545d5d4edb6796d + 13c0c663a321cd83a97f4ce1ecbaf17c2ba166527c3b06daaefe30695c5fcb8c + + Where before you had four layers stored, you now have 5. + +Notice the new `changed-ubuntu` image does not have its own copies of every layer. As can be seen in the diagram below, the new image is sharing it's four underlying layers with the `ubuntu:15.04` image. + +![](images/saving-space.jpg) + +The `docker history` command also shows the size of each image layer. The `03b964f68d06` is only consuming 13 Bytes of disk space. Because all of the layers below it already exist on the Docker host and are shared with the `ubuntu15:04` image, this means the entire `changed-ubuntu` image only consumes 13 Bytes of disk space. + +This sharing of image layers is what makes Docker images and containers so space +efficient. + +### Copying makes containers efficient + +You learned earlier that a container a Docker image with a thin writable, container layer added. The diagram below shows the layers of a container based on the `ubuntu:15.04` image: + +![](images/container-layers.jpg) + +All writes made to a container are stored in the thin writable container layer. The other layers are read-only (RO) image layers and can't be changed. This means that multiple containers can safely share a single underlying image. The diagram below shows multiple containers sharing a single copy of the `ubuntu:15.04` image. Each container has its own thin RW layer, but they all share a single instance of the ubuntu:15.04 image: + +![](images/sharing-layers.jpg) + +When a write operation occurs in a container, Docker uses the storage driver to perform a copy-on-write operation. The type of operation depends on the storage driver. For AUFS and OverlayFS storage drivers the copy-on-write operation is pretty much as follows: + +* Search through the layers for the file to update. The process starts at the top, newest layer and works down to the base layer one-at-a-time. +* Perform a "copy-up" operation on the first copy of the file that is found. A "copy up" copies the file up to the container's own thin writable layer. +* Modify the *copy of the file* in container's thin writable layer. + +BTFS, ZFS, and other drivers handle the copy-on-write differently. You can read more about the methods of these drivers later in their detailed descriptions. + +Containers that write a lot of data will consume more space than containers that do not. This is because most write operations consume new space in the containers thin writable top layer. If your container needs to write a lot of data, you can use a data volume. + +A copy-up operation can incur a noticeable performance overhead. This overhead is different depending on which storage driver is in use. However, large files, lots of layers, and deep directory trees can make the impact more noticeable. Fortunately, the operation only occurs the first time any particular file is modified. Subsequent modifications to the same file do not cause a copy-up operation and can operate directly on the file's existing copy already present in container layer. + +Let's see what happens if we spin up 5 containers based on our `changed-ubuntu` image we built earlier: + +1. From a terminal on your Docker host, run the following `docker run` command 5 times. + + $ docker run -dit changed-ubuntu bash + 75bab0d54f3cf193cfdc3a86483466363f442fba30859f7dcd1b816b6ede82d4 + $ docker run -dit changed-ubuntu bash + 9280e777d109e2eb4b13ab211553516124a3d4d4280a0edfc7abf75c59024d47 + $ docker run -dit changed-ubuntu bash + a651680bd6c2ef64902e154eeb8a064b85c9abf08ac46f922ad8dfc11bb5cd8a + $ docker run -dit changed-ubuntu bash + 8eb24b3b2d246f225b24f2fca39625aaad71689c392a7b552b78baf264647373 + $ docker run -dit changed-ubuntu bash + 0ad25d06bdf6fca0dedc38301b2aff7478b3e1ce3d1acd676573bba57cb1cfef + + This launches 5 containers based on the `changed-ubuntu` image. As the container is created, Docker adds a writable layer and assigns it a UUID. This is the value returned from the `docker run` command. + +2. Run the `docker ps` command to verify the 5 containers are running. + + $ docker ps + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + 0ad25d06bdf6 changed-ubuntu "bash" About a minute ago Up About a minute stoic_ptolemy + 8eb24b3b2d24 changed-ubuntu "bash" About a minute ago Up About a minute pensive_bartik + a651680bd6c2 changed-ubuntu "bash" 2 minutes ago Up 2 minutes hopeful_turing + 9280e777d109 changed-ubuntu "bash" 2 minutes ago Up 2 minutes backstabbing_mahavira + 75bab0d54f3c changed-ubuntu "bash" 2 minutes ago Up 2 minutes boring_pasteur + + The output above shows 5 running containers, all sharing the `changed-ubuntu` image. Each `CONTAINER ID` is derived from the UUID when creating each container. + +3. List the contents of the local storage area. + + $ sudo ls containers + 0ad25d06bdf6fca0dedc38301b2aff7478b3e1ce3d1acd676573bba57cb1cfef 9280e777d109e2eb4b13ab211553516124a3d4d4280a0edfc7abf75c59024d47 + 75bab0d54f3cf193cfdc3a86483466363f442fba30859f7dcd1b816b6ede82d4 a651680bd6c2ef64902e154eeb8a064b85c9abf08ac46f922ad8dfc11bb5cd8a + 8eb24b3b2d246f225b24f2fca39625aaad71689c392a7b552b78baf264647373 + +Docker's copy-on-write strategy not only reduces the amount of space consumed by containers, it also reduces the time required to start a container. At start time, Docker only has to create the thin writable layer for each container. The diagram below shows these 5 containers sharing a single read-only (RO) copy of the `changed-ubuntu` image. + +![](images/shared-uuid.jpg) + +If Docker had to make an entire copy of the underlying image stack each time it +started a new container, container start times and disk space used would be +significantly increased. + +## Data volumes and the storage driver + +When a container is deleted, any data written to the container that is not stored in a *data volume* is deleted along with the container. A data volume is directory or file that is mounted directly into a container. + +Data volumes are not controlled by the storage driver. Reads and writes to data +volumes bypass the storage driver and operate at native host speeds. You can mount any number of data volumes into a container. Multiple containers can also share one or more data volumes. + +The diagram below shows a single Docker host running two containers. Each container exists inside of its own address space within the Docker host's local storage area. There is also a single shared data volume located at `/data` on the Docker host. This is mounted directly into both containers. + +![](images/shared-volume.jpg) + +The data volume resides outside of the local storage area on the Docker host further reinforcing its independence from the storage driver's control. When a container is deleted, any data stored in shared data volumes persists on the Docker host. + +For detailed information about data volumes [Managing data in containers](https://docs.docker.com/userguide/dockervolumes/). + +## Related information + +* [Select a storage driver](selectadriver.md) +* [AUFS storage driver in practice](aufs-driver.md) +* [BTRFS storage driver in practice](btrfs-driver.md) +* [Device Mapper storage driver in practice](device-mapper-driver.md) diff --git a/docs/userguide/storagedriver/index.md b/docs/userguide/storagedriver/index.md new file mode 100644 index 000000000..254dfd328 --- /dev/null +++ b/docs/userguide/storagedriver/index.md @@ -0,0 +1,38 @@ + + + +# Docker storage drivers + +Docker relies on driver technology to manage the storage and interactions associated with images and they containers that run them. This section contains the following pages: + +* [Understand images, containers, and storage drivers](imagesandcontainers.md) +* [Select a storage driver](selectadriver.md) +* [AUFS storage driver in practice](aufs-driver.md) +* [BTRFS storage driver in practice](btrfs-driver.md) +* [Device Mapper storage driver in practice](device-mapper-driver.md) +* [OverlayFS in practice](overlayfs-driver.md) +* [FS storage in practice](zfs-driver.md) + +If you are new to Docker containers make sure you read ["Understand images, containers, and storage drivers"](imagesandcontainers.md) first. It explains key concepts and technologies that can help you when working with storage drivers. + +### Acknowledgement + +The Docker storage driver material was created in large part by our guest author +Nigel Poulton with a bit of help from Docker's own Jérôme Petazzoni. In his +spare time Nigel creates [IT training +videos](http://www.pluralsight.com/author/nigel-poulton), co-hosts the weekly +[In Tech We Trust podcast](http://intechwetrustpodcast.com/), and lives it large +on [Twitter](https://twitter.com/nigelpoulton). + + +  diff --git a/docs/userguide/storagedriver/overlayfs-driver.md b/docs/userguide/storagedriver/overlayfs-driver.md new file mode 100644 index 000000000..ee72e62bf --- /dev/null +++ b/docs/userguide/storagedriver/overlayfs-driver.md @@ -0,0 +1,190 @@ + + +# Docker and OverlayFS in practice + +OverlayFS is a modern *union filesystem* that is similar to AUFS. In comparison to AUFS, OverlayFS: + +* has a simpler design +* has been in the mainline Linux kernel since version 3.18 +* is potentially faster + +As a result, OverlayFS is rapidly gaining popularity in the Docker community and is seen by many as a natural successor to AUFS. As promising as OverlayFS is, it is still relatively young. Therefore caution should be taken before using it in production Docker environments. + +Docker's `overlay` storage driver leverages several OverlayFS features to build and manage the on-disk structures of images and containers. + +>**Note**: Since it was merged into the mainline kernel, the OverlayFS *kernel module* was renamed from "overlayfs" to "overlay". As a result you may see the two terms used interchangeably in some documentation. However, this document uses "OverlayFS" to refer to the overall filesystem, and `overlay` to refer to Docker's storage-driver. + + +## Image layering and sharing with OverlayFS + +OverlayFS takes two directories on a single Linux host, layers one on top of the other, and provides a single unified view. These directories are often referred to as *layers* and the technology used to layer them is is known as a *union mount*. The OverlayFS terminology is "lowerdir" for the bottom layer and "upperdir" for the top layer. The unified view is exposed through its own directory called "merged". + +The diagram below shows how a Docker image and a Docker container are layered. The image layer is the "lowerdir" and the container layer is the "upperdir". The unified view is exposed through a directory called "merged" which is effectively the containers mount point. The diagram shows how Docker constructs map to OverlayFS constructs. + +![](images/overlay_constructs.jpg) + +Notice how the image layer and container layer can contain the same files. When this happens, the files in the container layer ("upperdir") are dominant and obscure the existence of the same files in the image layer ("lowerdir"). The container mount ("merged") presents the unified view. + +OverlayFS only works with two layers. This means that multi-layered images cannot be implemented as multiple OverlayFS layers. Instead, each image layer is implemented as its own directory under `/var/lib/docker/overlay`. Hard links are then used as a space-efficient way to reference data shared with lower layers. The diagram below shows a four-layer image and how it is represented in the Docker host's filesystem. + +![](images/overlay_constructs2.jpg) + +To create a container, the `overlay` driver combines the directory representing the image's top layer plus a new directory for the container. The image's top layer is the "lowerdir" in the overlay and read-only. The new directory for the container is the "upperdir" and is writable. + +## Example: Image and container on-disk constructs + +The following `docker images -a` command shows a Docker host with a single image. As can be seen, the image consists of four layers. + + $ docker images -a + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + ubuntu latest 1d073211c498 7 days ago 187.9 MB + 5a4526e952f0 7 days ago 187.9 MB + 99fcaefe76ef 7 days ago 187.9 MB + c63fb41c2213 7 days ago 187.7 MB + +Below, the command's output illustrates that each of the four image layers has it's own directory under `/var/lib/docker/overlay/`. + + $ ls -l /var/lib/docker/overlay/ + total 24 + drwx------ 3 root root 4096 Oct 28 11:02 1d073211c498fd5022699b46a936b4e4bdacb04f637ad64d3475f558783f5c3e + drwx------ 3 root root 4096 Oct 28 11:02 5a4526e952f0aa24f3fcc1b6971f7744eb5465d572a48d47c492cb6bbf9cbcda + drwx------ 5 root root 4096 Oct 28 11:06 99fcaefe76ef1aa4077b90a413af57fd17d19dce4e50d7964a273aae67055235 + drwx------ 3 root root 4096 Oct 28 11:01 c63fb41c2213f511f12f294dd729b9903a64d88f098c20d2350905ac1fdbcbba + +Each directory is named after the image layer IDs in the previous `docker images -a` command. The image layer directories contain the files unique to that layer as well as hard links to the data that is shared with lower layers. This allows for efficient use of disk space. + +The following `docker ps` command shows the same Docker host running a single container. The container ID is "73de7176c223". + + $ docker ps + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + 73de7176c223 ubuntu "bash" 2 days ago Up 2 days stupefied_nobel + +This container exists on-disk in the Docker host's filesystem under `/var/lib/docker/overlay/73de7176c223...`. If you inspect this directory using the `ls -l` command you find the following file and directories. + + $ ls -l /var/lib/docker/overlay/73de7176c223a6c82fd46c48c5f152f2c8a7e49ecb795a7197c3bb795c4d879e + total 16 + -rw-r--r-- 1 root root 64 Oct 28 11:06 lower-id + drwxr-xr-x 1 root root 4096 Oct 28 11:06 merged + drwxr-xr-x 4 root root 4096 Oct 28 11:06 upper + drwx------ 3 root root 4096 Oct 28 11:06 work + +These four filesystem objects are all artifacts of OverlayFS. The "lower-id" file contains the ID of the top layer of the image the container is based on. This is used by OverlayFS as the "lowerdir". + + $ cat /var/lib/docker/overlay/73de7176c223a6c82fd46c48c5f152f2c8a7e49ecb795a7197c3bb795c4d879e/lower-id + 1d073211c498fd5022699b46a936b4e4bdacb04f637ad64d3475f558783f5c3e + +The "upper" directory is the containers read-write layer. Any changes made to the container are written to this directory. + +The "merged" directory is effectively the containers mount point. This is where the unified view of the image ("lowerdir") and container ("upperdir") is exposed. Any changes written to the container are immediately reflected in this directory. + +The "work" directory is required for OverlayFS to function. It is used for things such as *copy_up* operations. + +You can verify all of these constructs from the output of the `mount` command. (Ellipses and line breaks are used in the output below to enhance readability.) + + $ mount | grep overlay + overlay on /var/lib/docker/overlay/73de7176c223.../merged + type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay/1d073211c498.../root, + upperdir=/var/lib/docker/overlay/73de7176c223.../upper, + workdir=/var/lib/docker/overlay/73de7176c223.../work) + +The output reflects the overlay is mounted as read-write ("rw"). + +## Container reads and writes with overlay + +Consider three scenarios where a container opens a file for read access with overlay. + +- **The file does not exist in the container layer**. If a container opens a file for read access and the file does not already exist in the container ("upperdir") it is read from the image ("lowerdir"). This should incur very little performance overhead. + +- **The file only exists in the container layer**. If a container opens a file for read access and the file exists in the container ("upperdir") and not in the image ("lowerdir"), it is read directly from the container. + +- **The file exists in the container layer and the image layer**. If a container opens a file for read access and the file exists in the image layer and the container layer, the file's version in the container layer is read. This is because files in the container layer ("upperdir") obscure files with the same name in the image layer ("lowerdir"). + +Consider some scenarios where files in a container are modified. + +- **Writing to a file for the first time**. The first time a container writes to an existing file, that file does not exist in the container ("upperdir"). The `overlay` driver performs a *copy_up* operation to copy the file from the image ("lowerdir") to the container ("upperdir"). The container then writes the changes to the new copy of the file in the container layer. + + However, OverlayFS works at the file level not the block level. This means that all OverlayFS copy-up operations copy entire files, even if the file is very large and only a small part of it is being modified. This can have a noticeable impact on container write performance. However, two things are worth noting: + + * The copy_up operation only occurs the first time any given file is written to. Subsequent writes to the same file will operate against the copy of the file already copied up to the container. + + * OverlayFS only works with two layers. This means that performance should be better than AUFS which can suffer noticeable latencies when searching for files in images with many layers. + +- **Deleting files and directories**. When files are deleted within a container a *whiteout* file is created in the containers "upperdir". The version of the file in the image layer ("lowerdir") is not deleted. However, the whiteout file in the container obscures it. + + Deleting a directory in a container results in *opaque directory* being created in the "upperdir". This has the same effect as a whiteout file and effectively masks the existence of the directory in the image's "lowerdir". + +## Configure Docker with the overlay storage driver + +To configure Docker to use the overlay storage driver your Docker host must be running version 3.18 of the Linux kernel (preferably newer) with the overlay kernel module loaded. OverlayFS can operate on top of most supported Linux filesystems. However, ext4 is currently recommended for use in production environments. + +The following procedure shows you how to configure your Docker host to use OverlayFS. The procedure assumes that the Docker daemon is in a stopped state. + +> **Caution:** If you have already run the Docker daemon on your Docker host and have images you want to keep, `push` them Docker Hub or your private Docker Trusted Registry before attempting this procedure. + +1. If it is running, stop the Docker `daemon`. + +2. Verify your kernel version and that the overlay kernel module is loaded. + + $ uname -r + 3.19.0-21-generic + + $ lsmod | grep overlay + overlay + +3. Start the Docker daemon with the `overlay` storage driver. + + $ docker daemon --storage-driver=overlay & + [1] 29403 + root@ip-10-0-0-174:/home/ubuntu# INFO[0000] Listening for HTTP on unix (/var/run/docker.sock) + INFO[0000] Option DefaultDriver: bridge + INFO[0000] Option DefaultNetwork: bridge + + + Alternatively, you can force the Docker daemon to automatically start with + the `overlay` driver by editing the Docker config file and adding the + `--storage-driver=overlay` flag to the `DOCKER_OPTS` line. Once this option + is set you can start the daemon using normal startup scripts without having + to manually pass in the `--storage-driver` flag. + +4. Verify that the daemon is using the `overlay` storage driver + + $ docker info + Containers: 0 + Images: 0 + Storage Driver: overlay + Backing Filesystem: extfs + + + Notice that the *Backing filesystem* in the output above is showing as `extfs`. Multiple backing filesystems are supported but `extfs` (ext4) is recommended for production use cases. + +Your Docker host is now using the `overlay` storage driver. If you run the `mount` command, you'll find Docker has automatically created the `overlay` mount with the required "lowerdir", "upperdir", "merged" and "workdir" constructs. + +## OverlayFS and Docker Performance + +As a general rule, the `overlay` driver should be fast. Almost certainly faster than `aufs` and `devicemapper`. In certain circumstances it may also be faster than `btrfs`. That said, there are a few things to be aware of relative to the performance of Docker using the `overlay` storage driver. + +- **Page Caching**. OverlayFS supports page cache sharing. This means multiple containers accessing the same file can share a single page cache entry (or entries). This makes the `overlay` driver efficient with memory and a good option for PaaS and other high density use cases. + +- **copy_up**. As with AUFS, OverlayFS has to perform copy-up operations any time a container writes to a file for the first time. This can insert latency into the write operation — especially if the file being copied up is large. However, once the file has been copied up, all subsequent writes to that file occur without the need for further copy-up operations. + + The OverlayFS copy_up operation should be faster than the same operation with AUFS. This is because AUFS supports more layers than OverlayFS and it is possible to incur far larger latencies if searching through many AUFS layers. + +- **RPMs and Yum**. OverlayFS only implements a subset of the POSIX standards. This can result in certain OverlayFS operations breaking POSIX standards. One such operation is the *copy-up* operation. Therefore, using `yum` inside of a container on a Docker host using the `overlay` storage driver is unlikely to work without implementing workarounds. + +- **Inode limits**. Use of the `overlay` storage driver can cause excessive inode consumption. This is especially so as the number of images and containers on the Docker host grows. A Docker host with a large number of images and lots of started and stopped containers can quickly run out of inodes. + + Unfortunately you can only specify the number of inodes in a filesystem at the time of creation. For this reason, you may wish to consider putting `/var/lib/docker` on a separate device with its own filesystem or manually specifying the number of inodes when creating the filesystem. + +The following generic performance best practices also apply to OverlayFS. + +- **Solid State Devices (SSD)**. For best performance it is always a good idea to use fast storage media such as solid state devices (SSD). + +- **Use Data Volumes**. Data volumes provide the best and most predictable performance. This is because they bypass the storage driver and do not incur any of the potential overheads introduced by thin provisioning and copy-on-write. For this reason, you may want to place heavy write workloads on data volumes. diff --git a/docs/userguide/storagedriver/selectadriver.md b/docs/userguide/storagedriver/selectadriver.md new file mode 100644 index 000000000..38b037e40 --- /dev/null +++ b/docs/userguide/storagedriver/selectadriver.md @@ -0,0 +1,119 @@ + + +# Select a storage driver + +This page describes Docker's storage driver feature. It lists the storage +driver's that Docker supports and the basic commands associated with managing them. Finally, this page provides guidance on choosing a storage driver. + +The material on this page is intended for readers who already have an [understanding of the storage driver technology](imagesandcontainers.md). + +## A pluggable storage driver architecture + +The Docker has a pluggable storage driver architecture. This gives you the flexibility to "plug in" the storage driver is best for your environment and use-case. Each Docker storage driver is based on a Linux filesystem or volume manager. Further, each storage driver is free to implement the management of image layers and the container layer in it's own unique way. This means some storage drivers perform better than others in different circumstances. + +Once you decide which driver is best, you set this driver on the Docker daemon at start time. As a result, the Docker daemon can only run one storage driver, and all containers created by that daemon instance use the same storage driver. The table below shows the supported storage driver technologies and the driver names: + +|Technology |Storage driver name | +|--------------|---------------------| +|OverlayFS |`overlay` | +|AUFS |`aufs` | +|BTRFS |`btrfs` | +|Device Maper |`devicemapper` | +|VFS* |`vfs` | +|ZFS |`zfs` | + +To find out which storage driver is set on the daemon , you use the `docker info` command: + + $ docker info + Containers: 0 + Images: 0 + Storage Driver: overlay + Backing Filesystem: extfs + Execution Driver: native-0.2 + Logging Driver: json-file + Kernel Version: 3.19.0-15-generic + Operating System: Ubuntu 15.04 + ... output truncated ... + +The `info` subcommand reveals that the Docker daemon is using the `overlay` storage driver with a `Backing Filesystem` value of `extfs`. The `extfs` value means that the `overlay` storage driver is operating on top of an existing (ext) filesystem. The backing filesystem refers to the filesystem that was used to create the Docker host's local storage area under `/var/lib/docker`. + +Which storage driver you use, in part, depends on the backing filesystem you plan to use for your Docker host's local storage area. Some storage drivers can operate on top of different backing filesystems. However, other storage drivers require the backing filesystem to be the same as the storage driver. For example, the `btrfs` storage driver on a `btrfs` backing filesystem. The following table lists each storage driver and whether it must match the host's backing file system: + + |Storage driver |Must match backing filesystem | + |---------------|------------------------------| + |overlay |No | + |aufs |No | + |btrfs |Yes | + |devicemapper |No | + |vfs* |No | + |zfs |Yes | + + +You pass the `--storage-driver=` option to the `docker daemon` command line or by setting the option on the `DOCKER_OPTS` line in `/etc/defaults/docker` file. + +The following command shows how to start the Docker daemon with the `devicemapper` storage driver using the `docker daemon` command: + + $ docker daemon --storage-driver=devicemapper & + + $ docker info + Containers: 0 + Images: 0 + Storage Driver: devicemapper + Pool Name: docker-252:0-147544-pool + Pool Blocksize: 65.54 kB + Backing Filesystem: extfs + Data file: /dev/loop0 + Metadata file: /dev/loop1 + Data Space Used: 1.821 GB + Data Space Total: 107.4 GB + Data Space Available: 3.174 GB + Metadata Space Used: 1.479 MB + Metadata Space Total: 2.147 GB + Metadata Space Available: 2.146 GB + Udev Sync Supported: true + Deferred Removal Enabled: false + Data loop file: /var/lib/docker/devicemapper/devicemapper/data + Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata + Library Version: 1.02.90 (2014-09-01) + Execution Driver: native-0.2 + Logging Driver: json-file + Kernel Version: 3.19.0-15-generic + Operating System: Ubuntu 15.04 + + +Your choice of storage driver can affect the performance of your containerized applications. So it's important to understand the different storage driver options available and select the right one for your application. Later, in this page you'll find some advice for choosing an appropriate driver. + +## Shared storage systems and the storage driver + +Many enterprises consume storage from shared storage systems such as SAN and NAS arrays. These often provide increased performance and availability, as well as advanced features such as thin provisioning, deduplication and compression. + +The Docker storage driver and data volumes can both operate on top of storage provided by shared storage systems. This allows Docker to leverage the increased performance and availability these systems provide. However, Docker does not integrate with these underlying systems. + +Remember that each Docker storage driver is based on a Linux filesystem or volume manager. Be sure to follow existing best practices for operating your storage driver (filesystem or volume manager) on top of your shared storage system. For example, if using the ZFS storage driver on top of *XYZ* shared storage system, be sure to follow best practices for operating ZFS filesystems on top of XYZ shared storage system. + +## Which storage driver should you choose? + +As you might expect, the answer to this question is "it depends". While there are some clear cases where one particular storage driver outperforms other for certain workloads, you should factor all of the following into your decision: + +Choose a storage driver that you and your team/organization are comfortable with. Consider how much experience you have with a particular storage driver. There is no substitute for experience and it is rarely a good idea to try something brand new in production. That's what labs and laptops are for! + +If your Docker infrastructure is under support contracts, choose an option that will get you good support. You probably don't want to go with a solution that your support partners have little or no experience with. + +Whichever driver you choose, make sure it has strong community support and momentum. This is important because storage driver development in the Docker project relies on the community as much as the Docker staff to thrive. + + +## Related information + +* [Understand images, containers, and storage drivers](imagesandcontainers.md) +* [AUFS storage driver in practice](aufs-driver.md) +* [BTRFS storage driver in practice](btrfs-driver.md) +* [Device Mapper storage driver in practice](device-mapper-driver.md) diff --git a/docs/userguide/storagedriver/zfs-driver.md b/docs/userguide/storagedriver/zfs-driver.md new file mode 100644 index 000000000..75f0d3216 --- /dev/null +++ b/docs/userguide/storagedriver/zfs-driver.md @@ -0,0 +1,218 @@ + + +# Docker and ZFS in practice + +ZFS is a next generation filesystem that supports many advanced storage technologies such as volume management, snapshots, checksumming, compression and deduplication, replication and more. + +It was created by Sun Microsystems (now Oracle Corporation) and is open sourced under the CDDL license. Due to licensing incompatibilities between the CDDL and GPL, ZFS cannot be shipped as part of the mainline Linux kernel. However, the ZFS On Linux (ZoL) project provides an out-of-tree kernel module and userspace tools which can be installed separately. + +The ZFS on Linux (ZoL) port is healthy and maturing. However, at this point in time it is not recommended to use the `zfs` Docker storage driver for production use unless you have substantial experience with ZFS on Linux. + +> **Note:** There is also a FUSE implementation of ZFS on the Linux platform. This should work with Docker but is not recommended. The native ZFS driver (ZoL) is more tested, more performant, and is more widely used. The remainder of this document will relate to the native ZoL port. + + +## Image layering and sharing with ZFS + +The Docker `zfs` storage driver makes extensive use of three ZFS datasets: + +- filesystems +- snapshots +- clones + +ZFS filesystems are thinly provisioned and have space allocated to them from a ZFS pool (zpool) via allocate on demand operations. Snapshots and clones are space-efficient point-in-time copies of ZFS filesystems. Snapshots are read-only. Clones are read-write. Clones can only be created from snapshots. This simple relationship is shown in the diagram below. + +![](images/zfs_clones.jpg) + +The solid line in the diagram shows the process flow for creating a clone. Step 1 creates a snapshot of the filesystem, and step two creates the clone from the snapshot. The dashed line shows the relationship between the clone and the filesystem, via the snapshot. All three ZFS datasets draw space form the same underlying zpool. + +On Docker hosts using the `zfs` storage driver, the base layer of an image is a ZFS filesystem. Each child layer is a ZFS clone based on a ZFS snapshot of the layer below it. A container is a ZFS clone based on a ZFS Snapshot of the top layer of the image it's created from. All ZFS datasets draw their space from a common zpool. The diagram below shows how this is put together with a running container based on a two-layer image. + +![](images/zfs_zpool.jpg) + +The following process explains how images are layered and containers created. The process is based on the diagram above. + +1. The base layer of the image exists on the Docker host as a ZFS filesystem. + + This filesystem consumes space from the zpool used to create the Docker host's local storage area at `/var/lib/docker`. + +2. Additional image layers are clones of the dataset hosting the image layer directly below it. + + In the diagram, "Layer 1" is added by making a ZFS snapshot of the base layer and then creating a clone from that snapshot. The clone is writable and consumes space on-demand from the zpool. The snapshot is read-only, maintaining the base layer as an immutable object. + +3. When the container is launched, a read-write layer is added above the image. + + In the diagram above, the container's read-write layer is created by making a snapshot of the top layer of the image (Layer 1) and creating a clone from that snapshot. + + As changes are made to the container, space is allocated to it from the zpool via allocate-on-demand operations. By default, ZFS will allocate space in blocks of 128K. + +This process of creating child layers and containers from *read-only* snapshots allows images to be maintained as immutable objects. + +## Container reads and writes with ZFS + +Container reads with the `zfs` storage driver are very simple. A newly launched container is based on a ZFS clone. This clone initially shares all of its data with the dataset it was created from. This means that read operations with the `zfs` storage driver are fast – even if the data being read was copied into the container yet. This sharing of data blocks is shown in the diagram below. + +![](images/zpool_blocks.jpg) + +Writing new data to a container is accomplished via an allocate-on-demand operation. Every time a new area of the container needs writing to, a new block is allocated from the zpool. This means that containers consume additional space as new data is written to them. New space is allocated to the container (ZFS Clone) from the underlying zpool. + +Updating *existing data* in a container is accomplished by allocating new blocks to the containers clone and storing the changed data in those new blocks. The original are unchanged, allowing the underlying image dataset to remain immutable. This is the same as writing to a normal ZFS filesystem and is an implementation of copy-on-write semantics. + +## Configure Docker with the ZFS storage driver + +The `zfs` storage driver is only supported on a Docker host where `/var/lib/docker` is mounted as a ZFS filesystem. This section shows you how to install and configure native ZFS on Linux (ZoL) on an Ubuntu 14.04 system. + +### Prerequisites + +If you have already used the Docker daemon on your Docker host and have images you want to keep, `push` them Docker Hub or your private Docker Trusted Registry before attempting this procedure. + +Stop the Docker daemon. Then, ensure that you have a spare block device at `/dev/xvdb`. The device identifier may be be different in your environment and you should substitute your own values throughout the procedure. + +### Install Zfs on Ubuntu 14.04 LTS + +1. If it is running, stop the Docker `daemon`. + +1. Install `the software-properties-common` package. + + This is required for the `add-apt-repository` command. + + $ sudo apt-get install software-properties-common + Reading package lists... Done + Building dependency tree + + +2. Add the `zfs-native` package archive. + + $ sudo add-apt-repository ppa:zfs-native/stable + The native ZFS filesystem for Linux. Install the ubuntu-zfs package. + + gpg: key F6B0FC61: public key "Launchpad PPA for Native ZFS for Linux" imported + gpg: Total number processed: 1 + gpg: imported: 1 (RSA: 1) + OK + +3. Get the latest package lists for all registered repositories and package archives. + + $ sudo apt-get update + Ign http://us-west-2.ec2.archive.ubuntu.com trusty InRelease + Get:1 http://us-west-2.ec2.archive.ubuntu.com trusty-updates InRelease [64.4 kB] + + Fetched 10.3 MB in 4s (2,370 kB/s) + Reading package lists... Done + +4. Install the `ubuntu-zfs` package. + + $ sudo apt-get install -y ubuntu-zfs + Reading package lists... Done + Building dependency tree + + +5. Load the `zfs` module. + + $ sudo modprobe zfs + +6. Verify that it loaded correctly. + + $ lsmod | grep zfs + zfs 2768247 0 + zunicode 331170 1 zfs + zcommon 55411 1 zfs + znvpair 89086 2 zfs,zcommon + spl 96378 3 zfs,zcommon,znvpair + zavl 15236 1 zfs + +## Configure ZFS for Docker + +Once ZFS is installed and loaded, you're ready to configure ZFS for Docker. + + +1. Create a new `zpool`. + + $ sudo zpool create -f zpool-docker /dev/xvdb + + The command creates the `zpool` and gives it the name "zpool-docker". The name is arbitrary. + +2. Check that the `zpool` exists. + + $ sudo zfs list + NAME USED AVAIL REFER MOUNTPOINT + zpool-docker 55K 3.84G 19K /zpool-docker + +3. Create and mount a new ZFS filesystem to `/var/lib/docker`. + + $ sudo zfs create -o mountpoint=/var/lib/docker zpool-docker/docker + +4. Check that the previous step worked. + + $ sudo zfs list -t all + NAME USED AVAIL REFER MOUNTPOINT + zpool-docker 93.5K 3.84G 19K /zpool-docker + zpool-docker/docker 19K 3.84G 19K /var/lib/docker + + Now that you have a ZFS filesystem mounted to `/var/lib/docker`, the daemon should automatically load with the `zfs` storage driver. + +5. Start the Docker daemon. + + $ sudo service docker start + docker start/running, process 2315 + + The procedure for starting the Docker daemon may differ depending on the + Linux distribution you are using. It is possible to force the Docker daemon + to start with the `zfs` storage driver by passing the `--storage-driver=zfs` + flag to the `docker daemon` command, or to the `DOCKER_OPTS` line in the + Docker config file. + +6. Verify that the daemon is using the `zfs` storage driver. + + $ sudo docker info + Containers: 0 + Images: 0 + Storage Driver: zfs + Zpool: zpool-docker + Zpool Health: ONLINE + Parent Dataset: zpool-docker/docker + Space Used By Parent: 27648 + Space Available: 4128139776 + Parent Quota: no + Compression: off + Execution Driver: native-0.2 + [...] + + The output of the command above shows that the Docker daemon is using the + `zfs` storage driver and that the parent dataset is the `zpool-docker/docker` + filesystem created earlier. + +Your Docker host is now using ZFS to store to manage its images and containers. + +## ZFS and Docker performance + +There are several factors that influence the performance of Docker using the `zfs` storage driver. + +- **Memory**. Memory has a major impact on ZFS performance. This goes back to the fact that ZFS was originally designed for use on big Sun Solaris servers with large amounts of memory. Keep this in mind when sizing your Docker hosts. + +- **ZFS Features**. Using ZFS features, such as deduplication, can significantly increase the amount +of memory ZFS uses. For memory consumption and performance reasons it is +recommended to turn off ZFS deduplication. However, deduplication at other +layers in the stack (such as SAN or NAS arrays) can still be used as these do +not impact ZFS memory usage and performance. If using SAN, NAS or other hardware +RAID technologies you should continue to follow existing best practices for +using them with ZFS. + +* **ZFS Caching**. ZFS caches disk blocks in a memory structure called the adaptive replacement cache (ARC). The *Single Copy ARC* feature of ZFS allows a single cached copy of a block to be shared by multiple clones of a filesystem. This means that multiple running containers can share a single copy of cached block. This means that ZFS is a good option for PaaS and other high density use cases. + +- **Fragmentation**. Fragmentation is a natural byproduct of copy-on-write filesystems like ZFS. However, ZFS writes in 128K blocks and allocates *slabs* (multiple 128K blocks) to CoW operations in an attempt to reduce fragmentation. The ZFS intent log (ZIL) and the coalescing of writes (delayed writes) also help to reduce fragmentation. + +- **Use the native ZFS driver for Linux**. Although the Docker `zfs` storage driver supports the ZFS FUSE implementation, it is not recommended when high performance is required. The native ZFS on Linux driver tends to perform better than the FUSE implementation. + +The following generic performance best practices also apply to ZFS. + +- **Use of SSD**. For best performance it is always a good idea to use fast storage media such as solid state devices (SSD). However, if you only have a limited amount of SSD storage available it is recommended to place the ZIL on SSD. + +- **Use Data Volumes**. Data volumes provide the best and most predictable performance. This is because they bypass the storage driver and do not incur any of the potential overheads introduced by thin provisioning and copy-on-write. For this reason, you may want to place heavy write workloads on data volumes. diff --git a/docs/userguide/usingdocker.md b/docs/userguide/usingdocker.md index e27f7f719..e0aa6d2e8 100644 --- a/docs/userguide/usingdocker.md +++ b/docs/userguide/usingdocker.md @@ -1,41 +1,35 @@ -# Working with containers +# Run a simple application -In the [last section of the Docker User Guide](dockerizing.md) -we launched our first containers. We launched containers using the -`docker run` command: - -* Interactive container runs in the foreground. -* Daemonized container runs in the background. - -In the process we learned about several Docker commands: +In the ["*Hello world in a container*"](dockerizing.md) you launched your +first containers using the `docker run` command. You ran an *interactive container* that ran in the foreground. You also ran a *detached container* that ran in the background. In the process you learned about several Docker commands: * `docker ps` - Lists containers. * `docker logs` - Shows us the standard output of a container. * `docker stop` - Stops running containers. -> **Tip:** -> Another way to learn about `docker` commands is our -> [interactive tutorial](https://www.docker.com/tryit/). +## Learn about the Docker client -The `docker` client is pretty simple. Each action you can take -with Docker is a command and each command can take a series of -flags and arguments. +If you didn't realize it yet, you've been using the Docker client each time you +typed `docker` in your Bash terminal. The client is a simple command line client +also known as a command-line interface (CLI). Each action you can take with +the client is a command and each command can take a series of flags and arguments. - # Usage: [sudo] docker [command] [flags] [arguments] .. + # Usage: [sudo] docker [subcommand] [flags] [arguments] .. # Example: $ docker run -i -t ubuntu /bin/bash -Let's see this in action by using the `docker version` command to return +You can see this in action by using the `docker version` command to return version information on the currently installed Docker client and daemon. $ docker version @@ -43,7 +37,7 @@ version information on the currently installed Docker client and daemon. This command will not only provide you the version of Docker client and daemon you are using, but also the version of Go (the programming language powering Docker). - + Client: Version: 1.8.1 API version: 1.20 @@ -80,52 +74,52 @@ To see usage for a specific command, specify the command with the `--help` flag: --no-stdin=false Do not attach stdin --sig-proxy=true Proxy all received signals to the process -> **Note:** +> **Note:** > For further details and examples of each command, see the > [command reference](../reference/commandline/cli.md) in this guide. ## Running a web application in Docker -So now we've learnt a bit more about the `docker` client let's move onto +So now you've learned a bit more about the `docker` client you can move onto the important stuff: running more containers. So far none of the -containers we've run did anything particularly useful, so let's +containers you've run did anything particularly useful, so you can change that by running an example web application in Docker. For our web application we're going to run a Python Flask application. -Let's start with a `docker run` command. +Start with a `docker run` command. $ docker run -d -P training/webapp python app.py -Let's review what our command did. We've specified two flags: `-d` and -`-P`. We've already seen the `-d` flag which tells Docker to run the +Review what the command did. You've specified two flags: `-d` and +`-P`. You've already seen the `-d` flag which tells Docker to run the container in the background. The `-P` flag is new and tells Docker to map any required network ports inside our container to our host. This lets us view our web application. -We've specified an image: `training/webapp`. This image is a -pre-built image we've created that contains a simple Python Flask web +You've specified an image: `training/webapp`. This image is a +pre-built image you've created that contains a simple Python Flask web application. -Lastly, we've specified a command for our container to run: `python app.py`. This launches our web application. +Lastly, you've specified a command for our container to run: `python app.py`. This launches our web application. -> **Note:** +> **Note:** > You can see more detail on the `docker run` command in the [command > reference](../reference/commandline/run.md) and the [Docker Run > Reference](../reference/run.md). ## Viewing our web application container -Now let's see our running container using the `docker ps` command. +Now you can see your running container using the `docker ps` command. $ docker ps -l CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES bc533791f3f5 training/webapp:latest python app.py 5 seconds ago Up 2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse -You can see we've specified a new flag, `-l`, for the `docker ps` +You can see you've specified a new flag, `-l`, for the `docker ps` command. This tells the `docker ps` command to return the details of the *last* container started. -> **Note:** +> **Note:** > By default, the `docker ps` command only shows information about running > containers. If you want to see stopped containers too use the `-a` flag. @@ -139,7 +133,7 @@ column. When we passed the `-P` flag to the `docker run` command Docker mapped any ports exposed in our image to our host. -> **Note:** +> **Note:** > We'll learn more about how to expose ports in Docker images when > [we learn how to build images](dockerimages.md). @@ -158,12 +152,13 @@ This would map port 5000 inside our container to port 80 on our local host. You might be asking about now: why wouldn't we just want to always use 1:1 port mappings in Docker containers rather than mapping to high ports? Well 1:1 mappings have the constraint of only being able to map -one of each port on your local host. Let's say you want to test two -Python applications: both bound to port 5000 inside their own containers. -Without Docker's port mapping you could only access one at a time on the -Docker host. +one of each port on your local host. -So let's now browse to port 49155 in a web browser to +Suppose you want to test two Python applications: both bound to port 5000 inside +their own containers. Without Docker's port mapping you could only access one at +a time on the Docker host. + +So you can now browse to port 49155 in a web browser to see the application. ![Viewing the web application](webapp1.png). @@ -174,10 +169,10 @@ Our Python application is live! > If you have been using a virtual machine on OS X, Windows or Linux, > you'll need to get the IP of the virtual host instead of using localhost. > You can do this by running the `docker-machine ip your_vm_name` from your command line or terminal application, for example: -> +> > $ docker-machine ip my-docker-vm > 192.168.99.100 -> +> > In this case you'd browse to `http://192.168.99.100:49155` for the above example. ## A network port shortcut @@ -190,20 +185,20 @@ corresponding public-facing port. $ docker port nostalgic_morse 5000 0.0.0.0:49155 -In this case we've looked up what port is mapped externally to port 5000 inside +In this case you've looked up what port is mapped externally to port 5000 inside the container. ## Viewing the web application's logs -Let's also find out a bit more about what's happening with our application and -use another of the commands we've learnt, `docker logs`. +You can also find out a bit more about what's happening with our application and +use another of the commands you've learned, `docker logs`. $ docker logs -f nostalgic_morse * Running on http://0.0.0.0:5000/ 10.0.2.2 - - [23/May/2014 20:16:31] "GET / HTTP/1.1" 200 - 10.0.2.2 - - [23/May/2014 20:16:31] "GET /favicon.ico HTTP/1.1" 404 - -This time though we've added a new flag, `-f`. This causes the `docker +This time though you've added a new flag, `-f`. This causes the `docker logs` command to act like the `tail -f` command and watch the container's standard out. We can see here the logs from Flask showing the application running on port 5000 and the access log entries for it. @@ -228,7 +223,7 @@ configuration and status information for the specified container. $ docker inspect nostalgic_morse -Let's see a sample of that JSON output. +You can see a sample of that JSON output. [{ "ID": "bc533791f3f500b280a9626688bc79e342e3ea0d528efe3a86a51ecb28ea20", @@ -246,12 +241,12 @@ Let's see a sample of that JSON output. We can also narrow down the information we want to return by requesting a specific element, for example to return the container's IP address we would: - $ docker inspect -f '{{ .NetworkSettings.IPAddress }}' nostalgic_morse + $ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nostalgic_morse 172.17.0.5 ## Stopping our web application container -Okay we've seen web application working. Now let's stop it using the +Okay you've seen web application working. Now you can stop it using the `docker stop` command and the name of our container: `nostalgic_morse`. $ docker stop nostalgic_morse @@ -266,8 +261,8 @@ been stopped. Oops! Just after you stopped the container you get a call to say another developer needs the container back. From here you have two choices: you -can create a new container or restart the old one. Let's look at -starting our previous container back up. +can create a new container or restart the old one. Look at +starting your previous container back up. $ docker start nostalgic_morse nostalgic_morse @@ -276,21 +271,21 @@ Now quickly run `docker ps -l` again to see the running container is back up or browse to the container's URL to see if the application responds. -> **Note:** +> **Note:** > Also available is the `docker restart` command that runs a stop and > then start on the container. ## Removing our web application container Your colleague has let you know that they've now finished with the container -and won't need it again. So let's remove it using the `docker rm` command. +and won't need it again. Now, you can remove it using the `docker rm` command. $ docker rm nostalgic_morse Error: Impossible to remove a running container, please stop it first or use -f 2014/05/24 08:12:56 Error: failed to remove one or more containers What happened? We can't actually remove a running container. This protects -you from accidentally removing a running container you might need. Let's try +you from accidentally removing a running container you might need. You can try this again by stopping the container first. $ docker stop nostalgic_morse @@ -305,9 +300,7 @@ And now our container is stopped and deleted. # Next steps -Until now we've only used images that we've downloaded from -[Docker Hub](https://hub.docker.com). Next, let's get introduced to -building and sharing our own images. +Until now you've only used images that you've downloaded from Docker Hub. Next, +you can get introduced to building and sharing our own images. Go to [Working with Docker Images](dockerimages.md). - diff --git a/errors/daemon.go b/errors/daemon.go index 991388e4d..ea3158009 100644 --- a/errors/daemon.go +++ b/errors/daemon.go @@ -385,6 +385,14 @@ var ( HTTPStatusCode: http.StatusInternalServerError, }) + // ErrorCodeVolumeName is generated when the name of named volume isn't valid. + ErrorCodeVolumeName = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "VOLUME_NAME_INVALID", + Message: "%s includes invalid characters for a local volume name, only %s are allowed", + Description: "The name of volume is invalid", + HTTPStatusCode: http.StatusBadRequest, + }) + // ErrorCodeVolumeFromBlank is generated when path to a volume is blank. ErrorCodeVolumeFromBlank = errcode.Register(errGroup, errcode.ErrorDescriptor{ Value: "VOLUMEFROMBLANK", diff --git a/errors/server.go b/errors/server.go index 9dfcc02b5..1a7af00a1 100644 --- a/errors/server.go +++ b/errors/server.go @@ -24,4 +24,13 @@ var ( Description: "The client version is too old for the server", HTTPStatusCode: http.StatusBadRequest, }) + + // ErrorNetworkControllerNotEnabled is generated when the networking stack in not enabled + // for certain platforms, like windows. + ErrorNetworkControllerNotEnabled = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NETWORK_CONTROLLER_NOT_ENABLED", + Message: "the network controller is not enabled for this platform", + Description: "Docker's networking stack is disabled for this platform", + HTTPStatusCode: http.StatusNotFound, + }) ) diff --git a/experimental/README.md b/experimental/README.md index ca4f10226..d2eff37d8 100644 --- a/experimental/README.md +++ b/experimental/README.md @@ -1,4 +1,4 @@ -# Docker Experimental Features +# Docker Experimental Features This page contains a list of features in the Docker engine which are experimental. Experimental features are **not** ready for production. They are diff --git a/graph/graph.go b/graph/graph.go index cbdfeee1a..628954039 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -99,11 +99,16 @@ type Graph struct { root string idIndex *truncindex.TruncIndex driver graphdriver.Driver + imagesMutex sync.Mutex imageMutex imageMutex // protect images in driver. retained *retainedLayers tarSplitDisabled bool uidMaps []idtools.IDMap gidMaps []idtools.IDMap + + // access to parentRefs must be protected with imageMutex locking the image id + // on the key of the map (e.g. imageMutex.Lock(img.ID), parentRefs[img.ID]...) + parentRefs map[string]int } // file names for ./graph// @@ -141,12 +146,13 @@ func NewGraph(root string, driver graphdriver.Driver, uidMaps, gidMaps []idtools } graph := &Graph{ - root: abspath, - idIndex: truncindex.NewTruncIndex([]string{}), - driver: driver, - retained: &retainedLayers{layerHolders: make(map[string]map[string]struct{})}, - uidMaps: uidMaps, - gidMaps: gidMaps, + root: abspath, + idIndex: truncindex.NewTruncIndex([]string{}), + driver: driver, + retained: &retainedLayers{layerHolders: make(map[string]map[string]struct{})}, + uidMaps: uidMaps, + gidMaps: gidMaps, + parentRefs: make(map[string]int), } // Windows does not currently support tarsplit functionality. @@ -174,6 +180,17 @@ func (graph *Graph) restore() error { for _, v := range dir { id := v.Name() if graph.driver.Exists(id) { + img, err := graph.loadImage(id) + if err != nil { + if err != io.EOF { + return fmt.Errorf("could not restore image %s: %v", id, err) + } + logrus.Warnf("could not restore image %s due to corrupted files", id) + continue + } + graph.imageMutex.Lock(img.Parent) + graph.parentRefs[img.Parent]++ + graph.imageMutex.Unlock(img.Parent) ids = append(ids, id) } } @@ -262,6 +279,10 @@ func (graph *Graph) Register(im image.Descriptor, layerData io.Reader) (err erro return err } + // this is needed cause pull_v2 attemptIDReuse could deadlock + graph.imagesMutex.Lock() + defer graph.imagesMutex.Unlock() + // We need this entire operation to be atomic within the engine. Note that // this doesn't mean Register is fully safe yet. graph.imageMutex.Lock(imgID) @@ -302,10 +323,10 @@ func (graph *Graph) register(im image.Descriptor, layerData io.Reader) (err erro graph.driver.Remove(imgID) tmp, err := graph.mktemp() - defer os.RemoveAll(tmp) if err != nil { - return fmt.Errorf("mktemp failed: %s", err) + return err } + defer os.RemoveAll(tmp) parent := im.Parent() @@ -326,7 +347,13 @@ func (graph *Graph) register(im image.Descriptor, layerData io.Reader) (err erro if err := os.Rename(tmp, graph.imageRoot(imgID)); err != nil { return err } + graph.idIndex.Add(imgID) + + graph.imageMutex.Lock(parent) + graph.parentRefs[parent]++ + graph.imageMutex.Unlock(parent) + return nil } @@ -349,6 +376,7 @@ func (graph *Graph) TempLayerArchive(id string, sf *streamformatter.StreamFormat if err != nil { return nil, err } + defer os.RemoveAll(tmp) a, err := graph.TarLayer(image) if err != nil { return nil, err @@ -379,34 +407,37 @@ func (graph *Graph) mktemp() (string, error) { return dir, nil } -func (graph *Graph) newTempFile() (*os.File, error) { - tmp, err := graph.mktemp() - if err != nil { - return nil, err - } - return ioutil.TempFile(tmp, "") -} - // Delete atomically removes an image from the graph. func (graph *Graph) Delete(name string) error { id, err := graph.idIndex.Get(name) if err != nil { return err } - tmp, err := graph.mktemp() + img, err := graph.Get(id) + if err != nil { + return err + } graph.idIndex.Delete(id) - if err == nil { + tmp, err := graph.mktemp() + if err != nil { + tmp = graph.imageRoot(id) + } else { if err := os.Rename(graph.imageRoot(id), tmp); err != nil { // On err make tmp point to old dir and cleanup unused tmp dir os.RemoveAll(tmp) tmp = graph.imageRoot(id) } - } else { - // On err make tmp point to old dir for cleanup - tmp = graph.imageRoot(id) } // Remove rootfs data from the driver graph.driver.Remove(id) + + graph.imageMutex.Lock(img.Parent) + graph.parentRefs[img.Parent]-- + if graph.parentRefs[img.Parent] == 0 { + delete(graph.parentRefs, img.Parent) + } + graph.imageMutex.Unlock(img.Parent) + // Remove the trashed image directory return os.RemoveAll(tmp) } @@ -424,9 +455,11 @@ func (graph *Graph) Map() map[string]*image.Image { // The walking order is undetermined. func (graph *Graph) walkAll(handler func(*image.Image)) { graph.idIndex.Iterate(func(id string) { - if img, err := graph.Get(id); err != nil { + img, err := graph.Get(id) + if err != nil { return - } else if handler != nil { + } + if handler != nil { handler(img) } }) @@ -453,8 +486,11 @@ func (graph *Graph) ByParent() map[string][]*image.Image { } // HasChildren returns whether the given image has any child images. -func (graph *Graph) HasChildren(img *image.Image) bool { - return len(graph.ByParent()[img.ID]) > 0 +func (graph *Graph) HasChildren(imgID string) bool { + graph.imageMutex.Lock(imgID) + count := graph.parentRefs[imgID] + graph.imageMutex.Unlock(imgID) + return count > 0 } // Retain keeps the images and layers that are in the pulling chain so that @@ -472,11 +508,9 @@ func (graph *Graph) Release(sessionID string, layerIDs ...string) { // A head is an image which is not the parent of another image in the graph. func (graph *Graph) Heads() map[string]*image.Image { heads := make(map[string]*image.Image) - byParent := graph.ByParent() graph.walkAll(func(image *image.Image) { - // If it's not in the byParent lookup table, then - // it's not a parent -> so it's a head! - if _, exists := byParent[image.ID]; !exists { + // if it has no children, then it's not a parent, so it's an head + if !graph.HasChildren(image.ID) { heads[image.ID] = image } }) diff --git a/graph/list.go b/graph/list.go index a45cce7d2..8110beed2 100644 --- a/graph/list.go +++ b/graph/list.go @@ -51,8 +51,10 @@ func (s *TagStore) Images(filterArgs, filter string, all bool) ([]*types.Image, if i, ok := imageFilters["dangling"]; ok { for _, value := range i { - if strings.ToLower(value) == "true" { + if v := strings.ToLower(value); v == "true" { filtTagged = false + } else if v != "false" { + return nil, fmt.Errorf("Invalid filter 'dangling=%s'", v) } } } diff --git a/graph/pull_v2.go b/graph/pull_v2.go index 808f44575..346b7ee58 100644 --- a/graph/pull_v2.go +++ b/graph/pull_v2.go @@ -7,7 +7,6 @@ import ( "io" "io/ioutil" "os" - "sync" "github.com/Sirupsen/logrus" "github.com/docker/distribution" @@ -37,7 +36,7 @@ func (p *v2Puller) Pull(tag string) (fallback bool, err error) { // TODO(tiborvass): was ReceiveTimeout p.repo, err = NewV2Repository(p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull") if err != nil { - logrus.Debugf("Error getting v2 registry: %v", err) + logrus.Warnf("Error getting v2 registry: %v", err) return true, err } @@ -359,6 +358,9 @@ func (p *v2Puller) pullV2Tag(out io.Writer, tag, taggedName string) (tagUpdated Action: "Extracting", }) + p.graph.imagesMutex.Lock() + defer p.graph.imagesMutex.Unlock() + p.graph.imageMutex.Lock(d.img.id) defer p.graph.imageMutex.Unlock(d.img.id) @@ -549,8 +551,6 @@ func (p *v2Puller) getImageInfos(m *manifest.Manifest) ([]contentAddressableDesc return imgs, nil } -var idReuseLock sync.Mutex - // attemptIDReuse does a best attempt to match verified compatibilityIDs // already in the graph with the computed strongIDs so we can keep using them. // This process will never fail but may just return the strongIDs if none of @@ -561,8 +561,8 @@ func (p *v2Puller) attemptIDReuse(imgs []contentAddressableDescriptor) { // This function needs to be protected with a global lock, because it // locks multiple IDs at once, and there's no good way to make sure // the locking happens a deterministic order. - idReuseLock.Lock() - defer idReuseLock.Unlock() + p.graph.imagesMutex.Lock() + defer p.graph.imagesMutex.Unlock() idMap := make(map[string]struct{}) for _, img := range imgs { diff --git a/graph/push_v2.go b/graph/push_v2.go index 70cb2e42a..088a6c62e 100644 --- a/graph/push_v2.go +++ b/graph/push_v2.go @@ -1,6 +1,8 @@ package graph import ( + "bufio" + "compress/gzip" "fmt" "io" "io/ioutil" @@ -19,6 +21,8 @@ import ( "golang.org/x/net/context" ) +const compressionBufSize = 32768 + type v2Pusher struct { *TagStore endpoint registry.APIEndpoint @@ -169,15 +173,17 @@ func (p *v2Pusher) pushV2Tag(tag string) error { // if digest was empty or not saved, or if blob does not exist on the remote repository, // then fetch it. if !exists { - if pushDigest, err := p.pushV2Image(p.repo.Blobs(context.Background()), layer); err != nil { + var pushDigest digest.Digest + if pushDigest, err = p.pushV2Image(p.repo.Blobs(context.Background()), layer); err != nil { return err - } else if pushDigest != dgst { + } + if dgst == "" { // Cache new checksum if err := p.graph.SetLayerDigest(layer.ID, pushDigest); err != nil { return err } - dgst = pushDigest } + dgst = pushDigest } // read v1Compatibility config, generate new if needed @@ -236,11 +242,8 @@ func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (d } defer layerUpload.Close() - digester := digest.Canonical.New() - tee := io.TeeReader(arch, digester.Hash()) - reader := progressreader.New(progressreader.Config{ - In: ioutil.NopCloser(tee), // we'll take care of close here. + In: ioutil.NopCloser(arch), // we'll take care of close here. Out: out, Formatter: p.sf, @@ -254,8 +257,33 @@ func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (d Action: "Pushing", }) + digester := digest.Canonical.New() + // HACK: The MultiWriter doesn't write directly to layerUpload because + // we must make sure the ReadFrom is used, not Write. Using Write would + // send a PATCH request for every Write call. + pipeReader, pipeWriter := io.Pipe() + // Use a bufio.Writer to avoid excessive chunking in HTTP request. + bufWriter := bufio.NewWriterSize(io.MultiWriter(pipeWriter, digester.Hash()), compressionBufSize) + compressor := gzip.NewWriter(bufWriter) + + go func() { + _, err := io.Copy(compressor, reader) + if err == nil { + err = compressor.Close() + } + if err == nil { + err = bufWriter.Flush() + } + if err != nil { + pipeWriter.CloseWithError(err) + } else { + pipeWriter.Close() + } + }() + out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pushing", nil)) - nn, err := io.Copy(layerUpload, reader) + nn, err := layerUpload.ReadFrom(pipeReader) + pipeReader.Close() if err != nil { return "", err } diff --git a/graph/registry.go b/graph/registry.go index cb5eb01d8..599a807c5 100644 --- a/graph/registry.go +++ b/graph/registry.go @@ -57,7 +57,7 @@ func NewV2Repository(repoInfo *registry.RepositoryInfo, endpoint registry.APIEnd authTransport := transport.NewTransport(base, modifiers...) pingClient := &http.Client{ Transport: authTransport, - Timeout: 5 * time.Second, + Timeout: 15 * time.Second, } endpointStr := endpoint.URL + "/v2/" req, err := http.NewRequest("GET", endpointStr, nil) diff --git a/graph/service.go b/graph/service.go index 98a935c8d..3f83ea999 100644 --- a/graph/service.go +++ b/graph/service.go @@ -19,14 +19,19 @@ func (s *TagStore) Lookup(name string) (*types.ImageInspect, error) { return nil, fmt.Errorf("No such image: %s", name) } - var tags = make([]string, 0) + var repoTags = make([]string, 0) + var repoDigests = make([]string, 0) s.Lock() for repoName, repository := range s.Repositories { for ref, id := range repository { if id == image.ID { imgRef := utils.ImageReference(repoName, ref) - tags = append(tags, imgRef) + if utils.DigestReference(ref) { + repoDigests = append(repoDigests, imgRef) + } else { + repoTags = append(repoTags, imgRef) + } } } } @@ -34,7 +39,8 @@ func (s *TagStore) Lookup(name string) (*types.ImageInspect, error) { imageInspect := &types.ImageInspect{ ID: image.ID, - Tags: tags, + RepoTags: repoTags, + RepoDigests: repoDigests, Parent: image.Parent, Comment: image.Comment, Created: image.Created.Format(time.RFC3339Nano), diff --git a/hack/make/release-rpm b/hack/make/release-rpm index 648cec4b5..18b74f4db 100755 --- a/hack/make/release-rpm +++ b/hack/make/release-rpm @@ -67,7 +67,7 @@ for distro in "${distros[@]}"; do fi # copy the rpms to the packages folder - cp "$RPMFILE" "$REPO/$suite/Packages" + cp "${RPMFILE[@]}" "$REPO/$suite/Packages" # update the repo createrepo --pretty --update "$REPO/$suite" diff --git a/hack/make/test-docker-py b/hack/make/test-docker-py index 83fd62077..dece8315a 100644 --- a/hack/make/test-docker-py +++ b/hack/make/test-docker-py @@ -12,7 +12,7 @@ set -e } # exporting PYTHONPATH to import "docker" from our local docker-py - test_env PYTHONPATH="$dockerPy" NOT_ON_HOST=true python "$dockerPy/tests/integration_test.py" + test_env PYTHONPATH="$dockerPy" py.test "$dockerPy/tests/integration" bundle .integration-daemon-stop ) 2>&1 | tee -a "$DEST/test.log" diff --git a/hack/release.sh b/hack/release.sh index bd8d4c880..bdeabc8fe 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -311,7 +311,7 @@ EOF fi # Upload repo - s3cmd --acl-public "$s3Headers" sync "$APTDIR/" "s3://$BUCKET/ubuntu/" + s3cmd --acl-public $s3Headers sync "$APTDIR/" "s3://$BUCKET/ubuntu/" cat <@ reference + dockerCmd(c, "pull", imageReference) + + out, _ := dockerCmd(c, "inspect", imageReference) + + var imageJSON []types.ImageInspect + if err = json.Unmarshal([]byte(out), &imageJSON); err != nil { + c.Fatalf("unable to unmarshal body for latest version: %v", err) + } + + c.Assert(len(imageJSON), check.Equals, 1) + c.Assert(len(imageJSON[0].RepoDigests), check.Equals, 1) + c.Assert(stringutils.InSlice(imageJSON[0].RepoDigests, imageReference), check.Equals, true) +} + func (s *DockerRegistrySuite) TestPsListContainersFilterAncestorImageByDigest(c *check.C) { digest, err := setupImage(c) c.Assert(err, check.IsNil, check.Commentf("error setting up image: %v", err)) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 6567809bf..89d7aed94 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -16,6 +16,7 @@ import ( "strings" "time" + "github.com/docker/docker/pkg/integration/checker" "github.com/docker/libnetwork/iptables" "github.com/docker/libtrust" "github.com/go-check/check" @@ -302,7 +303,7 @@ func (s *DockerSuite) TestDaemonIPv6Enabled(c *check.C) { c.Fatalf("Could not run container: %s, %v", out, err) } - out, err := d.Cmd("inspect", "--format", "'{{.NetworkSettings.LinkLocalIPv6Address}}'", "ipv6test") + out, err := d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.LinkLocalIPv6Address}}'", "ipv6test") out = strings.Trim(out, " \r\n'") if err != nil { @@ -313,7 +314,7 @@ func (s *DockerSuite) TestDaemonIPv6Enabled(c *check.C) { c.Fatalf("Container should have a link-local IPv6 address") } - out, err = d.Cmd("inspect", "--format", "'{{.NetworkSettings.GlobalIPv6Address}}'", "ipv6test") + out, err = d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.GlobalIPv6Address}}'", "ipv6test") out = strings.Trim(out, " \r\n'") if err != nil { @@ -350,7 +351,7 @@ func (s *DockerSuite) TestDaemonIPv6FixedCIDR(c *check.C) { c.Fatalf("Could not run container: %s, %v", out, err) } - out, err := d.Cmd("inspect", "--format", "'{{.NetworkSettings.LinkLocalIPv6Address}}'", "ipv6test") + out, err := d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.LinkLocalIPv6Address}}'", "ipv6test") out = strings.Trim(out, " \r\n'") if err != nil { @@ -361,7 +362,7 @@ func (s *DockerSuite) TestDaemonIPv6FixedCIDR(c *check.C) { c.Fatalf("Container should have a link-local IPv6 address") } - out, err = d.Cmd("inspect", "--format", "'{{.NetworkSettings.GlobalIPv6Address}}'", "ipv6test") + out, err = d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.GlobalIPv6Address}}'", "ipv6test") out = strings.Trim(out, " \r\n'") if err != nil { @@ -793,6 +794,26 @@ func (s *DockerDaemonSuite) TestDaemonBridgeFixedCidr(c *check.C) { } } +func (s *DockerDaemonSuite) TestDaemonBridgeFixedCidrFixedCIDREqualBridgeNetwork(c *check.C) { + d := s.d + + bridgeName := "external-bridge" + bridgeIP := "172.27.42.1/16" + + out, err := createInterface(c, "bridge", bridgeName, bridgeIP) + c.Assert(err, check.IsNil, check.Commentf(out)) + defer deleteInterface(c, bridgeName) + + err = d.StartWithBusybox("--bridge", bridgeName, "--fixed-cidr", bridgeIP) + c.Assert(err, check.IsNil) + defer s.d.Restart() + + out, err = d.Cmd("run", "-d", "busybox", "top") + c.Assert(err, check.IsNil, check.Commentf(out)) + cid1 := strings.TrimSpace(out) + defer d.Cmd("stop", cid1) +} + func (s *DockerDaemonSuite) TestDaemonDefaultGatewayIPv4Implicit(c *check.C) { defaultNetworkBridge := "docker0" deleteInterface(c, defaultNetworkBridge) @@ -848,6 +869,29 @@ func (s *DockerDaemonSuite) TestDaemonDefaultGatewayIPv4ExplicitOutsideContainer s.d.Restart() } +func (s *DockerDaemonSuite) TestDaemonDefaultNetworkInvalidClusterConfig(c *check.C) { + testRequires(c, SameHostDaemon) + + // Start daemon without docker0 bridge + defaultNetworkBridge := "docker0" + deleteInterface(c, defaultNetworkBridge) + + d := NewDaemon(c) + discoveryBackend := "consul://consuladdr:consulport/some/path" + err := d.Start(fmt.Sprintf("--cluster-store=%s", discoveryBackend)) + c.Assert(err, checker.IsNil) + + // Start daemon with docker0 bridge + ifconfigCmd := exec.Command("ifconfig", defaultNetworkBridge) + _, err = runCommand(ifconfigCmd) + c.Assert(err, check.IsNil) + + err = d.Restart(fmt.Sprintf("--cluster-store=%s", discoveryBackend)) + c.Assert(err, checker.IsNil) + + d.Stop() +} + func (s *DockerDaemonSuite) TestDaemonIP(c *check.C) { d := s.d @@ -1723,3 +1767,37 @@ func (s *DockerDaemonSuite) TestDaemonStartWithoutHost(c *check.C) { }() c.Assert(s.d.Start(), check.IsNil) } + +func (s *DockerDaemonSuite) TestDaemonStartWithDefalutTlsHost(c *check.C) { + s.d.useDefaultTLSHost = true + defer func() { + s.d.useDefaultTLSHost = false + }() + if err := s.d.Start( + "--tlsverify", + "--tlscacert", "fixtures/https/ca.pem", + "--tlscert", "fixtures/https/server-cert.pem", + "--tlskey", "fixtures/https/server-key.pem"); err != nil { + c.Fatalf("Could not start daemon: %v", err) + } + + // The client with --tlsverify should also use default host localhost:2376 + tmpHost := os.Getenv("DOCKER_HOST") + defer func() { + os.Setenv("DOCKER_HOST", tmpHost) + }() + + os.Setenv("DOCKER_HOST", "") + + out, _ := dockerCmd( + c, + "--tlsverify", + "--tlscacert", "fixtures/https/ca.pem", + "--tlscert", "fixtures/https/client-cert.pem", + "--tlskey", "fixtures/https/client-key.pem", + "version", + ) + if !strings.Contains(out, "Server") { + c.Fatalf("docker version should return information of server side") + } +} diff --git a/integration-cli/docker_cli_diff_test.go b/integration-cli/docker_cli_diff_test.go index 60eff132c..42f1d89fb 100644 --- a/integration-cli/docker_cli_diff_test.go +++ b/integration-cli/docker_cli_diff_test.go @@ -61,6 +61,7 @@ func (s *DockerSuite) TestDiffEnsureOnlyKmsgAndPtmx(c *check.C) { "C /dev": true, "A /dev/full": true, // busybox "C /dev/ptmx": true, // libcontainer + "A /dev/mqueue": true, // lxc "A /dev/kmsg": true, // lxc "A /dev/fd": true, "A /dev/fuse": true, diff --git a/integration-cli/docker_cli_events_unix_test.go b/integration-cli/docker_cli_events_unix_test.go index ca7c09221..9115809b8 100644 --- a/integration-cli/docker_cli_events_unix_test.go +++ b/integration-cli/docker_cli_events_unix_test.go @@ -56,6 +56,7 @@ func (s *DockerSuite) TestEventsRedirectStdout(c *check.C) { func (s *DockerSuite) TestEventsOOMDisableFalse(c *check.C) { testRequires(c, DaemonIsLinux) + testRequires(c, NativeExecDriver) testRequires(c, oomControl) errChan := make(chan error) @@ -103,6 +104,7 @@ func (s *DockerSuite) TestEventsOOMDisableFalse(c *check.C) { func (s *DockerSuite) TestEventsOOMDisableTrue(c *check.C) { testRequires(c, DaemonIsLinux) + testRequires(c, NativeExecDriver) testRequires(c, oomControl) errChan := make(chan error) diff --git a/integration-cli/docker_cli_images_test.go b/integration-cli/docker_cli_images_test.go index 04bbce5b0..27fbfb721 100644 --- a/integration-cli/docker_cli_images_test.go +++ b/integration-cli/docker_cli_images_test.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/docker/docker/pkg/integration/checker" "github.com/docker/docker/pkg/stringid" "github.com/go-check/check" ) @@ -50,7 +51,6 @@ func (s *DockerSuite) TestImagesEnsureImageWithBadTagIsNotListed(c *check.C) { if strings.Contains(out, "busybox") { c.Fatal("images should not have listed busybox") } - } func (s *DockerSuite) TestImagesOrderedByCreationDate(c *check.C) { @@ -197,3 +197,51 @@ func (s *DockerSuite) TestImagesEnsureDanglingImageOnlyListedOnce(c *check.C) { c.Fatalf("expected 1 dangling image, got %d: %s", a, out) } } + +func (s *DockerSuite) TestImagesWithIncorrectFilter(c *check.C) { + out, _, err := dockerCmdWithError("images", "-f", "dangling=invalid") + c.Assert(err, check.NotNil) + c.Assert(out, checker.Contains, "Invalid filter") +} + +func (s *DockerSuite) TestImagesEnsureOnlyHeadsImagesShown(c *check.C) { + testRequires(c, DaemonIsLinux) + + dockerfile := ` + FROM scratch + MAINTAINER docker + ENV foo bar` + + head, out, err := buildImageWithOut("scratch-image", dockerfile, false) + c.Assert(err, check.IsNil) + + // this is just the output of docker build + // we're interested in getting the image id of the MAINTAINER instruction + // and that's located at output, line 5, from 7 to end + split := strings.Split(out, "\n") + intermediate := strings.TrimSpace(split[5][7:]) + + out, _ = dockerCmd(c, "images") + if strings.Contains(out, intermediate) { + c.Fatalf("images shouldn't show non-heads images, got %s in %s", intermediate, out) + } + if !strings.Contains(out, head[:12]) { + c.Fatalf("images should contain final built images, want %s in out, got %s", head[:12], out) + } +} + +func (s *DockerSuite) TestImagesEnsureImagesFromScratchShown(c *check.C) { + testRequires(c, DaemonIsLinux) + + dockerfile := ` + FROM scratch + MAINTAINER docker` + + id, _, err := buildImageWithOut("scratch-image", dockerfile, false) + c.Assert(err, check.IsNil) + + out, _ := dockerCmd(c, "images") + if !strings.Contains(out, id[:12]) { + c.Fatalf("images should contain images built from scratch (e.g. %s), got %s", id[:12], out) + } +} diff --git a/integration-cli/docker_cli_info_test.go b/integration-cli/docker_cli_info_test.go index c886619ef..2e0759026 100644 --- a/integration-cli/docker_cli_info_test.go +++ b/integration-cli/docker_cli_info_test.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "net" "strings" "github.com/docker/docker/pkg/integration/checker" @@ -45,12 +46,59 @@ func (s *DockerSuite) TestInfoDiscoveryBackend(c *check.C) { d := NewDaemon(c) discoveryBackend := "consul://consuladdr:consulport/some/path" - if err := d.Start(fmt.Sprintf("--cluster-store=%s", discoveryBackend), "--cluster-advertise=foo"); err != nil { - c.Fatal(err) - } + discoveryAdvertise := "1.1.1.1:2375" + err := d.Start(fmt.Sprintf("--cluster-store=%s", discoveryBackend), fmt.Sprintf("--cluster-advertise=%s", discoveryAdvertise)) + c.Assert(err, checker.IsNil) defer d.Stop() out, err := d.Cmd("info") c.Assert(err, checker.IsNil) c.Assert(out, checker.Contains, fmt.Sprintf("Cluster store: %s\n", discoveryBackend)) + c.Assert(out, checker.Contains, fmt.Sprintf("Cluster advertise: %s\n", discoveryAdvertise)) +} + +// TestInfoDiscoveryInvalidAdvertise verifies that a daemon run with +// an invalid `--cluster-advertise` configuration +func (s *DockerSuite) TestInfoDiscoveryInvalidAdvertise(c *check.C) { + testRequires(c, SameHostDaemon) + + d := NewDaemon(c) + discoveryBackend := "consul://consuladdr:consulport/some/path" + + // --cluster-advertise with an invalid string is an error + err := d.Start(fmt.Sprintf("--cluster-store=%s", discoveryBackend), "--cluster-advertise=invalid") + c.Assert(err, checker.Not(checker.IsNil)) + + // --cluster-advertise without --cluster-store is also an error + err = d.Start("--cluster-advertise=1.1.1.1:2375") + c.Assert(err, checker.Not(checker.IsNil)) +} + +// TestInfoDiscoveryAdvertiseInterfaceName verifies that a daemon run with `--cluster-advertise` +// configured with interface name properly show the advertise ip-address in info output. +func (s *DockerSuite) TestInfoDiscoveryAdvertiseInterfaceName(c *check.C) { + testRequires(c, SameHostDaemon) + + d := NewDaemon(c) + discoveryBackend := "consul://consuladdr:consulport/some/path" + discoveryAdvertise := "eth0" + + err := d.Start(fmt.Sprintf("--cluster-store=%s", discoveryBackend), fmt.Sprintf("--cluster-advertise=%s:2375", discoveryAdvertise)) + c.Assert(err, checker.IsNil) + defer d.Stop() + + iface, err := net.InterfaceByName(discoveryAdvertise) + c.Assert(err, checker.IsNil) + addrs, err := iface.Addrs() + c.Assert(err, checker.IsNil) + if len(addrs) <= 0 { + c.Fatalf("addrs %v has to have at least one element", addrs) + } + ip, _, err := net.ParseCIDR(addrs[0].String()) + c.Assert(err, checker.IsNil) + + out, err := d.Cmd("info") + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, fmt.Sprintf("Cluster store: %s\n", discoveryBackend)) + c.Assert(out, checker.Contains, fmt.Sprintf("Cluster advertise: %s:2375\n", ip.String())) } diff --git a/integration-cli/docker_cli_inspect_test.go b/integration-cli/docker_cli_inspect_test.go index f4a8a0d39..ee8597363 100644 --- a/integration-cli/docker_cli_inspect_test.go +++ b/integration-cli/docker_cli_inspect_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/integration/checker" "github.com/docker/docker/runconfig" "github.com/go-check/check" ) @@ -27,13 +28,9 @@ func (s *DockerSuite) TestInspectImage(c *check.C) { func (s *DockerSuite) TestInspectInt64(c *check.C) { testRequires(c, DaemonIsLinux) - out, _, err := dockerCmdWithError("run", "-d", "-m=300M", "busybox", "true") - if err != nil { - c.Fatalf("failed to run container: %v, output: %q", err, out) - } - out = strings.TrimSpace(out) - inspectOut, err := inspectField(out, "HostConfig.Memory") + dockerCmd(c, "run", "-d", "-m=300M", "--name", "inspectTest", "busybox", "true") + inspectOut, err := inspectField("inspectTest", "HostConfig.Memory") c.Assert(err, check.IsNil) if inspectOut != "314572800" { @@ -91,7 +88,7 @@ func (s *DockerSuite) TestInspectTypeFlagContainer(c *check.C) { dockerCmd(c, "run", "--name=busybox", "-d", "busybox", "top") - formatStr := fmt.Sprintf("--format='{{.State.Running}}'") + formatStr := "--format='{{.State.Running}}'" out, exitCode, err := dockerCmdWithError("inspect", "--type=container", formatStr, "busybox") if exitCode != 0 || err != nil { c.Fatalf("failed to inspect container: %s, %v", out, err) @@ -353,19 +350,15 @@ func (s *DockerSuite) TestInspectNoSizeFlagContainer(c *check.C) { dockerCmd(c, "run", "--name=busybox", "-d", "busybox", "top") - formatStr := fmt.Sprintf("--format='{{.SizeRw}},{{.SizeRootFs}}'") + formatStr := "--format='{{.SizeRw}},{{.SizeRootFs}}'" out, _ := dockerCmd(c, "inspect", "--type=container", formatStr, "busybox") c.Assert(strings.TrimSpace(out), check.Equals, ",", check.Commentf("Exepcted not to display size info: %s", out)) } func (s *DockerSuite) TestInspectSizeFlagContainer(c *check.C) { - - //Both the container and image are named busybox. docker inspect will fetch container - //JSON SizeRw and SizeRootFs field. If there is a flag --size/-s, the fields are not . - dockerCmd(c, "run", "--name=busybox", "-d", "busybox", "top") - formatStr := fmt.Sprintf("--format='{{.SizeRw}},{{.SizeRootFs}}'") + formatStr := "--format='{{.SizeRw}},{{.SizeRootFs}}'" out, _ := dockerCmd(c, "inspect", "-s", "--type=container", formatStr, "busybox") sz := strings.Split(out, ",") @@ -374,14 +367,25 @@ func (s *DockerSuite) TestInspectSizeFlagContainer(c *check.C) { } func (s *DockerSuite) TestInspectSizeFlagImage(c *check.C) { + dockerCmd(c, "run", "--name=busybox", "-d", "busybox", "top") - //Both the container and image are named busybox. docker inspect will fetch image - //JSON SizeRw and SizeRootFs field. There are no these fields since they are only in containers. + formatStr := "--format='{{.SizeRw}},{{.SizeRootFs}}'" + out, _, err := dockerCmdWithError("inspect", "-s", "--type=image", formatStr, "busybox") + + // Template error rather than + // This is a more correct behavior because images don't have sizes associated. + c.Assert(err, check.Not(check.IsNil)) + c.Assert(out, checker.Contains, "Template parsing error") +} + +func (s *DockerSuite) TestInspectTempateError(c *check.C) { + //Both the container and image are named busybox. docker inspect will fetch container + //JSON State.Running field. If the field is true, it's a container. dockerCmd(c, "run", "--name=busybox", "-d", "busybox", "top") - formatStr := fmt.Sprintf("--format='{{.SizeRw}},{{.SizeRootFs}}'") - out, _ := dockerCmd(c, "inspect", "-s", "--type=image", formatStr, "busybox") + out, _, err := dockerCmdWithError("inspect", "--type=container", "--format='Format container: {{.ThisDoesNotExist}}'", "busybox") - c.Assert(strings.TrimSpace(out), check.Equals, ",", check.Commentf("Fields SizeRw and SizeRootFs are not exepcted to exist")) + c.Assert(err, check.Not(check.IsNil)) + c.Assert(out, checker.Contains, "Template parsing error") } diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index edd8073a0..e42062d16 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -166,7 +166,7 @@ func (s *DockerSuite) TestLinksUpdateOnRestart(c *check.C) { out, _ := dockerCmd(c, "run", "-d", "--name", "two", "--link", "one:onetwo", "--link", "one:one", "busybox", "top") id := strings.TrimSpace(string(out)) - realIP, err := inspectField("one", "NetworkSettings.IPAddress") + realIP, err := inspectField("one", "NetworkSettings.Networks.bridge.IPAddress") if err != nil { c.Fatal(err) } @@ -189,10 +189,9 @@ func (s *DockerSuite) TestLinksUpdateOnRestart(c *check.C) { c.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip) } dockerCmd(c, "restart", "one") - realIP, err = inspectField("one", "NetworkSettings.IPAddress") - if err != nil { - c.Fatal(err) - } + realIP, err = inspectField("one", "NetworkSettings.Networks.bridge.IPAddress") + c.Assert(err, check.IsNil) + content, err = readContainerFileWithExec(id, "/etc/hosts") if err != nil { c.Fatal(err, string(content)) diff --git a/integration-cli/docker_cli_network_unix_test.go b/integration-cli/docker_cli_network_unix_test.go index c25bd8840..8cb96816a 100644 --- a/integration-cli/docker_cli_network_unix_test.go +++ b/integration-cli/docker_cli_network_unix_test.go @@ -13,11 +13,21 @@ import ( "strings" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/versions/v1p20" + "github.com/docker/docker/pkg/integration/checker" "github.com/docker/libnetwork/driverapi" + remoteapi "github.com/docker/libnetwork/drivers/remote/api" + "github.com/docker/libnetwork/ipamapi" + remoteipam "github.com/docker/libnetwork/ipams/remote/api" + "github.com/docker/libnetwork/netlabel" "github.com/go-check/check" + "github.com/vishvananda/netlink" ) const dummyNetworkDriver = "dummy-network-driver" +const dummyIpamDriver = "dummy-ipam-driver" + +var remoteDriverNetworkRequest remoteapi.CreateNetworkRequest func init() { check.Suite(&DockerNetworkSuite{ @@ -43,21 +53,25 @@ func (s *DockerNetworkSuite) TearDownTest(c *check.C) { func (s *DockerNetworkSuite) SetUpSuite(c *check.C) { mux := http.NewServeMux() s.server = httptest.NewServer(mux) - if s.server == nil { - c.Fatal("Failed to start a HTTP Server") - } + c.Assert(s.server, check.NotNil, check.Commentf("Failed to start a HTTP Server")) mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") - fmt.Fprintf(w, `{"Implements": ["%s"]}`, driverapi.NetworkPluginEndpointType) + fmt.Fprintf(w, `{"Implements": ["%s", "%s"]}`, driverapi.NetworkPluginEndpointType, ipamapi.PluginEndpointType) }) + // Network driver implementation mux.HandleFunc(fmt.Sprintf("/%s.GetCapabilities", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") fmt.Fprintf(w, `{"Scope":"local"}`) }) mux.HandleFunc(fmt.Sprintf("/%s.CreateNetwork", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { + err := json.NewDecoder(r.Body).Decode(&remoteDriverNetworkRequest) + if err != nil { + http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) + return + } w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") fmt.Fprintf(w, "null") }) @@ -67,14 +81,129 @@ func (s *DockerNetworkSuite) SetUpSuite(c *check.C) { fmt.Fprintf(w, "null") }) - if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil { - c.Fatal(err) - } + mux.HandleFunc(fmt.Sprintf("/%s.CreateEndpoint", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") + fmt.Fprintf(w, `{"Interface":{"MacAddress":"a0:b1:c2:d3:e4:f5"}}`) + }) + + mux.HandleFunc(fmt.Sprintf("/%s.Join", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") + + veth := &netlink.Veth{ + LinkAttrs: netlink.LinkAttrs{Name: "randomIfName", TxQLen: 0}, PeerName: "cnt0"} + if err := netlink.LinkAdd(veth); err != nil { + fmt.Fprintf(w, `{"Error":"failed to add veth pair: `+err.Error()+`"}`) + } else { + fmt.Fprintf(w, `{"InterfaceName":{ "SrcName":"cnt0", "DstPrefix":"veth"}}`) + } + }) + + mux.HandleFunc(fmt.Sprintf("/%s.Leave", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") + fmt.Fprintf(w, "null") + }) + + mux.HandleFunc(fmt.Sprintf("/%s.DeleteEndpoint", driverapi.NetworkPluginEndpointType), func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") + if link, err := netlink.LinkByName("cnt0"); err == nil { + netlink.LinkDel(link) + } + fmt.Fprintf(w, "null") + }) + + // Ipam Driver implementation + var ( + poolRequest remoteipam.RequestPoolRequest + poolReleaseReq remoteipam.ReleasePoolRequest + addressRequest remoteipam.RequestAddressRequest + addressReleaseReq remoteipam.ReleaseAddressRequest + lAS = "localAS" + gAS = "globalAS" + pool = "172.28.0.0/16" + poolID = lAS + "/" + pool + gw = "172.28.255.254/16" + ) + + mux.HandleFunc(fmt.Sprintf("/%s.GetDefaultAddressSpaces", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") + fmt.Fprintf(w, `{"LocalDefaultAddressSpace":"`+lAS+`", "GlobalDefaultAddressSpace": "`+gAS+`"}`) + }) + + mux.HandleFunc(fmt.Sprintf("/%s.RequestPool", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) { + err := json.NewDecoder(r.Body).Decode(&poolRequest) + if err != nil { + http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") + if poolRequest.AddressSpace != lAS && poolRequest.AddressSpace != gAS { + fmt.Fprintf(w, `{"Error":"Unknown address space in pool request: `+poolRequest.AddressSpace+`"}`) + } else if poolRequest.Pool != "" && poolRequest.Pool != pool { + fmt.Fprintf(w, `{"Error":"Cannot handle explicit pool requests yet"}`) + } else { + fmt.Fprintf(w, `{"PoolID":"`+poolID+`", "Pool":"`+pool+`"}`) + } + }) + + mux.HandleFunc(fmt.Sprintf("/%s.RequestAddress", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) { + err := json.NewDecoder(r.Body).Decode(&addressRequest) + if err != nil { + http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") + // make sure libnetwork is now querying on the expected pool id + if addressRequest.PoolID != poolID { + fmt.Fprintf(w, `{"Error":"unknown pool id"}`) + } else if addressRequest.Address != "" { + fmt.Fprintf(w, `{"Error":"Cannot handle explicit address requests yet"}`) + } else { + fmt.Fprintf(w, `{"Address":"`+gw+`"}`) + } + }) + + mux.HandleFunc(fmt.Sprintf("/%s.ReleaseAddress", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) { + err := json.NewDecoder(r.Body).Decode(&addressReleaseReq) + if err != nil { + http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") + // make sure libnetwork is now asking to release the expected address fro mthe expected poolid + if addressRequest.PoolID != poolID { + fmt.Fprintf(w, `{"Error":"unknown pool id"}`) + } else if addressReleaseReq.Address != gw { + fmt.Fprintf(w, `{"Error":"unknown address"}`) + } else { + fmt.Fprintf(w, "null") + } + }) + + mux.HandleFunc(fmt.Sprintf("/%s.ReleasePool", ipamapi.PluginEndpointType), func(w http.ResponseWriter, r *http.Request) { + err := json.NewDecoder(r.Body).Decode(&poolReleaseReq) + if err != nil { + http.Error(w, "Unable to decode JSON payload: "+err.Error(), http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") + // make sure libnetwork is now asking to release the expected poolid + if addressRequest.PoolID != poolID { + fmt.Fprintf(w, `{"Error":"unknown pool id"}`) + } else { + fmt.Fprintf(w, "null") + } + }) + + err := os.MkdirAll("/etc/docker/plugins", 0755) + c.Assert(err, checker.IsNil) fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", dummyNetworkDriver) - if err := ioutil.WriteFile(fileName, []byte(s.server.URL), 0644); err != nil { - c.Fatal(err) - } + err = ioutil.WriteFile(fileName, []byte(s.server.URL), 0644) + c.Assert(err, checker.IsNil) + + ipamFileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", dummyIpamDriver) + err = ioutil.WriteFile(ipamFileName, []byte(s.server.URL), 0644) + c.Assert(err, checker.IsNil) } func (s *DockerNetworkSuite) TearDownSuite(c *check.C) { @@ -84,9 +213,8 @@ func (s *DockerNetworkSuite) TearDownSuite(c *check.C) { s.server.Close() - if err := os.RemoveAll("/etc/docker/plugins"); err != nil { - c.Fatal(err) - } + err := os.RemoveAll("/etc/docker/plugins") + c.Assert(err, checker.IsNil) } func assertNwIsAvailable(c *check.C, name string) { @@ -114,10 +242,10 @@ func isNwPresent(c *check.C, name string) bool { func getNwResource(c *check.C, name string) *types.NetworkResource { out, _ := dockerCmd(c, "network", "inspect", name) - nr := types.NetworkResource{} + nr := []types.NetworkResource{} err := json.Unmarshal([]byte(out), &nr) c.Assert(err, check.IsNil) - return &nr + return &nr[0] } func (s *DockerNetworkSuite) TestDockerNetworkLsDefault(c *check.C) { @@ -135,13 +263,37 @@ func (s *DockerNetworkSuite) TestDockerNetworkCreateDelete(c *check.C) { assertNwNotAvailable(c, "test") } +func (s *DockerSuite) TestDockerInspectMultipleNetwork(c *check.C) { + out, _ := dockerCmd(c, "network", "inspect", "host", "none") + networkResources := []types.NetworkResource{} + err := json.Unmarshal([]byte(out), &networkResources) + c.Assert(err, check.IsNil) + c.Assert(networkResources, checker.HasLen, 2) + + // Should print an error, return an exitCode 1 *but* should print the host network + out, exitCode, err := dockerCmdWithError("network", "inspect", "host", "nonexistent") + c.Assert(err, checker.NotNil) + c.Assert(exitCode, checker.Equals, 1) + c.Assert(out, checker.Contains, "Error: No such network: nonexistent") + networkResources = []types.NetworkResource{} + inspectOut := strings.SplitN(out, "\n", 2)[1] + err = json.Unmarshal([]byte(inspectOut), &networkResources) + c.Assert(networkResources, checker.HasLen, 1) + + // Should print an error and return an exitCode, nothing else + out, exitCode, err = dockerCmdWithError("network", "inspect", "nonexistent") + c.Assert(err, checker.NotNil) + c.Assert(exitCode, checker.Equals, 1) + c.Assert(out, checker.Contains, "Error: No such network: nonexistent") +} + func (s *DockerNetworkSuite) TestDockerNetworkConnectDisconnect(c *check.C) { dockerCmd(c, "network", "create", "test") assertNwIsAvailable(c, "test") nr := getNwResource(c, "test") - c.Assert(nr.Name, check.Equals, "test") - c.Assert(len(nr.Containers), check.Equals, 0) + c.Assert(nr.Name, checker.Equals, "test") + c.Assert(len(nr.Containers), checker.Equals, 0) // run a container out, _ := dockerCmd(c, "run", "-d", "--name", "test", "busybox", "top") @@ -153,20 +305,20 @@ func (s *DockerNetworkSuite) TestDockerNetworkConnectDisconnect(c *check.C) { // inspect the network to make sure container is connected nr = getNetworkResource(c, nr.ID) - c.Assert(len(nr.Containers), check.Equals, 1) + c.Assert(len(nr.Containers), checker.Equals, 1) c.Assert(nr.Containers[containerID], check.NotNil) // check if container IP matches network inspect ip, _, err := net.ParseCIDR(nr.Containers[containerID].IPv4Address) c.Assert(err, check.IsNil) - containerIP := findContainerIP(c, "test") - c.Assert(ip.String(), check.Equals, containerIP) + containerIP := findContainerIP(c, "test", "test") + c.Assert(ip.String(), checker.Equals, containerIP) // disconnect container from the network dockerCmd(c, "network", "disconnect", "test", containerID) nr = getNwResource(c, "test") - c.Assert(nr.Name, check.Equals, "test") - c.Assert(len(nr.Containers), check.Equals, 0) + c.Assert(nr.Name, checker.Equals, "test") + c.Assert(len(nr.Containers), checker.Equals, 0) // check if network connect fails for inactive containers dockerCmd(c, "stop", containerID) @@ -217,19 +369,34 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpamMultipleNetworks(c *check.C) { } } +func (s *DockerNetworkSuite) TestDockerNetworkCustomIpam(c *check.C) { + // Create a bridge network using custom ipam driver + dockerCmd(c, "network", "create", "--ipam-driver", dummyIpamDriver, "br0") + assertNwIsAvailable(c, "br0") + + // Verify expected network ipam fields are there + nr := getNetworkResource(c, "br0") + c.Assert(nr.Driver, checker.Equals, "bridge") + c.Assert(nr.IPAM.Driver, checker.Equals, dummyIpamDriver) + + // remove network and exercise remote ipam driver + dockerCmd(c, "network", "rm", "br0") + assertNwNotAvailable(c, "br0") +} + func (s *DockerNetworkSuite) TestDockerNetworkInspect(c *check.C) { // if unspecified, network gateway will be selected from inside preferred pool dockerCmd(c, "network", "create", "--driver=bridge", "--subnet=172.28.0.0/16", "--ip-range=172.28.5.0/24", "--gateway=172.28.5.254", "br0") assertNwIsAvailable(c, "br0") nr := getNetworkResource(c, "br0") - c.Assert(nr.Driver, check.Equals, "bridge") - c.Assert(nr.Scope, check.Equals, "local") - c.Assert(nr.IPAM.Driver, check.Equals, "default") - c.Assert(len(nr.IPAM.Config), check.Equals, 1) - c.Assert(nr.IPAM.Config[0].Subnet, check.Equals, "172.28.0.0/16") - c.Assert(nr.IPAM.Config[0].IPRange, check.Equals, "172.28.5.0/24") - c.Assert(nr.IPAM.Config[0].Gateway, check.Equals, "172.28.5.254") + c.Assert(nr.Driver, checker.Equals, "bridge") + c.Assert(nr.Scope, checker.Equals, "local") + c.Assert(nr.IPAM.Driver, checker.Equals, "default") + c.Assert(len(nr.IPAM.Config), checker.Equals, 1) + c.Assert(nr.IPAM.Config[0].Subnet, checker.Equals, "172.28.0.0/16") + c.Assert(nr.IPAM.Config[0].IPRange, checker.Equals, "172.28.5.0/24") + c.Assert(nr.IPAM.Config[0].Gateway, checker.Equals, "172.28.5.254") dockerCmd(c, "network", "rm", "br0") } @@ -255,3 +422,306 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpamInvalidCombinations(c *check.C c.Assert(err, check.NotNil) dockerCmd(c, "network", "rm", "test0") } + +func (s *DockerNetworkSuite) TestDockerNetworkDriverOptions(c *check.C) { + dockerCmd(c, "network", "create", "-d", dummyNetworkDriver, "-o", "opt1=drv1", "-o", "opt2=drv2", "testopt") + assertNwIsAvailable(c, "testopt") + gopts := remoteDriverNetworkRequest.Options[netlabel.GenericData] + c.Assert(gopts, checker.NotNil) + opts, ok := gopts.(map[string]interface{}) + c.Assert(ok, checker.Equals, true) + c.Assert(opts["opt1"], checker.Equals, "drv1") + c.Assert(opts["opt2"], checker.Equals, "drv2") + dockerCmd(c, "network", "rm", "testopt") + +} + +func (s *DockerDaemonSuite) TestDockerNetworkNoDiscoveryDefaultBridgeNetwork(c *check.C) { + // On default bridge network built-in service discovery should not happen + hostsFile := "/etc/hosts" + bridgeName := "external-bridge" + bridgeIP := "192.169.255.254/24" + out, err := createInterface(c, "bridge", bridgeName, bridgeIP) + c.Assert(err, check.IsNil, check.Commentf(out)) + defer deleteInterface(c, bridgeName) + + err = s.d.StartWithBusybox("--bridge", bridgeName) + c.Assert(err, check.IsNil) + defer s.d.Restart() + + // run two containers and store first container's etc/hosts content + out, err = s.d.Cmd("run", "-d", "busybox", "top") + c.Assert(err, check.IsNil) + cid1 := strings.TrimSpace(out) + defer s.d.Cmd("stop", cid1) + + hosts, err := s.d.Cmd("exec", cid1, "cat", hostsFile) + c.Assert(err, checker.IsNil) + + out, err = s.d.Cmd("run", "-d", "--name", "container2", "busybox", "top") + c.Assert(err, check.IsNil) + cid2 := strings.TrimSpace(out) + + // verify first container's etc/hosts file has not changed after spawning the second named container + hostsPost, err := s.d.Cmd("exec", cid1, "cat", hostsFile) + c.Assert(err, checker.IsNil) + c.Assert(string(hosts), checker.Equals, string(hostsPost), + check.Commentf("Unexpected %s change on second container creation", hostsFile)) + + // stop container 2 and verify first container's etc/hosts has not changed + _, err = s.d.Cmd("stop", cid2) + c.Assert(err, check.IsNil) + + hostsPost, err = s.d.Cmd("exec", cid1, "cat", hostsFile) + c.Assert(err, checker.IsNil) + c.Assert(string(hosts), checker.Equals, string(hostsPost), + check.Commentf("Unexpected %s change on second container creation", hostsFile)) + + // but discovery is on when connecting to non default bridge network + network := "anotherbridge" + out, err = s.d.Cmd("network", "create", network) + c.Assert(err, check.IsNil, check.Commentf(out)) + defer s.d.Cmd("network", "rm", network) + + out, err = s.d.Cmd("network", "connect", network, cid1) + c.Assert(err, check.IsNil, check.Commentf(out)) + + hostsPost, err = s.d.Cmd("exec", cid1, "cat", hostsFile) + c.Assert(err, checker.IsNil) + c.Assert(string(hosts), checker.Equals, string(hostsPost), + check.Commentf("Unexpected %s change on second network connection", hostsFile)) + + cName := "container3" + out, err = s.d.Cmd("run", "-d", "--net", network, "--name", cName, "busybox", "top") + c.Assert(err, check.IsNil, check.Commentf(out)) + cid3 := strings.TrimSpace(out) + defer s.d.Cmd("stop", cid3) + + // container1 etc/hosts file should contain an entry for the third container + hostsPost, err = s.d.Cmd("exec", cid1, "cat", hostsFile) + c.Assert(err, checker.IsNil) + c.Assert(string(hostsPost), checker.Contains, cName, + check.Commentf("Container 1 %s file does not contain entries for named container %q: %s", hostsFile, cName, string(hostsPost))) + + // on container3 disconnect, first container's etc/hosts should go back to original form + out, err = s.d.Cmd("network", "disconnect", network, cid3) + c.Assert(err, check.IsNil, check.Commentf(out)) + + hostsPost, err = s.d.Cmd("exec", cid1, "cat", hostsFile) + c.Assert(err, checker.IsNil) + c.Assert(string(hosts), checker.Equals, string(hostsPost), + check.Commentf("Unexpected %s content after disconnecting from second network", hostsFile)) +} + +func (s *DockerNetworkSuite) TestDockerNetworkAnonymousEndpoint(c *check.C) { + hostsFile := "/etc/hosts" + cstmBridgeNw := "custom-bridge-nw" + + dockerCmd(c, "network", "create", "-d", "bridge", cstmBridgeNw) + assertNwIsAvailable(c, cstmBridgeNw) + + // run two anonymous containers and store their etc/hosts content + out, _ := dockerCmd(c, "run", "-d", "--net", cstmBridgeNw, "busybox", "top") + cid1 := strings.TrimSpace(out) + + hosts1, err := readContainerFileWithExec(cid1, hostsFile) + c.Assert(err, checker.IsNil) + + out, _ = dockerCmd(c, "run", "-d", "--net", cstmBridgeNw, "busybox", "top") + cid2 := strings.TrimSpace(out) + + hosts2, err := readContainerFileWithExec(cid2, hostsFile) + c.Assert(err, checker.IsNil) + + // verify first container etc/hosts file has not changed + hosts1post, err := readContainerFileWithExec(cid1, hostsFile) + c.Assert(err, checker.IsNil) + c.Assert(string(hosts1), checker.Equals, string(hosts1post), + check.Commentf("Unexpected %s change on anonymous container creation", hostsFile)) + + // start a named container + cName := "AnyName" + out, _ = dockerCmd(c, "run", "-d", "--net", cstmBridgeNw, "--name", cName, "busybox", "top") + cid3 := strings.TrimSpace(out) + + // verify etc/hosts file for first two containers contains the named container entry + hosts1post, err = readContainerFileWithExec(cid1, hostsFile) + c.Assert(err, checker.IsNil) + c.Assert(string(hosts1post), checker.Contains, cName, + check.Commentf("Container 1 %s file does not contain entries for named container %q: %s", hostsFile, cName, string(hosts1post))) + + hosts2post, err := readContainerFileWithExec(cid2, hostsFile) + c.Assert(err, checker.IsNil) + c.Assert(string(hosts2post), checker.Contains, cName, + check.Commentf("Container 2 %s file does not contain entries for named container %q: %s", hostsFile, cName, string(hosts2post))) + + // Stop named container and verify first two containers' etc/hosts entries are back to original + dockerCmd(c, "stop", cid3) + hosts1post, err = readContainerFileWithExec(cid1, hostsFile) + c.Assert(err, checker.IsNil) + c.Assert(string(hosts1), checker.Equals, string(hosts1post), + check.Commentf("Unexpected %s change on anonymous container creation", hostsFile)) + + hosts2post, err = readContainerFileWithExec(cid2, hostsFile) + c.Assert(err, checker.IsNil) + c.Assert(string(hosts2), checker.Equals, string(hosts2post), + check.Commentf("Unexpected %s change on anonymous container creation", hostsFile)) +} + +func (s *DockerNetworkSuite) TestDockerNetworkLinkOndefaultNetworkOnly(c *check.C) { + // Link feature must work only on default network, and not across networks + cnt1 := "container1" + cnt2 := "container2" + network := "anotherbridge" + + // Run first container on default network + dockerCmd(c, "run", "-d", "--name", cnt1, "busybox", "top") + + // Create another network and run the second container on it + dockerCmd(c, "network", "create", network) + assertNwIsAvailable(c, network) + dockerCmd(c, "run", "-d", "--net", network, "--name", cnt2, "busybox", "top") + + // Try launching a container on default network, linking to the first container. Must succeed + dockerCmd(c, "run", "-d", "--link", fmt.Sprintf("%s:%s", cnt1, cnt1), "busybox", "top") + + // Try launching a container on default network, linking to the second container. Must fail + _, _, err := dockerCmdWithError("run", "-d", "--link", fmt.Sprintf("%s:%s", cnt2, cnt2), "busybox", "top") + c.Assert(err, checker.NotNil) + + // Connect second container to default network. Now a container on default network can link to it + dockerCmd(c, "network", "connect", "bridge", cnt2) + dockerCmd(c, "run", "-d", "--link", fmt.Sprintf("%s:%s", cnt2, cnt2), "busybox", "top") +} + +func (s *DockerNetworkSuite) TestDockerNetworkOverlayPortMapping(c *check.C) { + // Verify exposed ports are present in ps output when running a container on + // a network managed by a driver which does not provide the default gateway + // for the container + nwn := "ov" + ctn := "bb" + port1 := 80 + port2 := 443 + expose1 := fmt.Sprintf("--expose=%d", port1) + expose2 := fmt.Sprintf("--expose=%d", port2) + + dockerCmd(c, "network", "create", "-d", dummyNetworkDriver, nwn) + assertNwIsAvailable(c, nwn) + + dockerCmd(c, "run", "-d", "--net", nwn, "--name", ctn, expose1, expose2, "busybox", "top") + + // Check docker ps o/p for last created container reports the unpublished ports + unpPort1 := fmt.Sprintf("%d/tcp", port1) + unpPort2 := fmt.Sprintf("%d/tcp", port2) + out, _ := dockerCmd(c, "ps", "-n=1") + // Missing unpublished ports in docker ps output + c.Assert(out, checker.Contains, unpPort1) + // Missing unpublished ports in docker ps output + c.Assert(out, checker.Contains, unpPort2) +} + +func (s *DockerNetworkSuite) TestDockerNetworkMacInspect(c *check.C) { + // Verify endpoint MAC address is correctly populated in container's network settings + nwn := "ov" + ctn := "bb" + + dockerCmd(c, "network", "create", "-d", dummyNetworkDriver, nwn) + assertNwIsAvailable(c, nwn) + + dockerCmd(c, "run", "-d", "--net", nwn, "--name", ctn, "busybox", "top") + + mac, err := inspectField(ctn, "NetworkSettings.Networks."+nwn+".MacAddress") + c.Assert(err, checker.IsNil) + c.Assert(mac, checker.Equals, "a0:b1:c2:d3:e4:f5") +} + +func (s *DockerSuite) TestInspectApiMultipeNetworks(c *check.C) { + dockerCmd(c, "network", "create", "mybridge1") + dockerCmd(c, "network", "create", "mybridge2") + out, _ := dockerCmd(c, "run", "-d", "busybox", "top") + id := strings.TrimSpace(out) + c.Assert(waitRun(id), check.IsNil) + + dockerCmd(c, "network", "connect", "mybridge1", id) + dockerCmd(c, "network", "connect", "mybridge2", id) + + body := getInspectBody(c, "v1.20", id) + var inspect120 v1p20.ContainerJSON + err := json.Unmarshal(body, &inspect120) + c.Assert(err, checker.IsNil) + + versionedIP := inspect120.NetworkSettings.IPAddress + + body = getInspectBody(c, "v1.21", id) + var inspect121 types.ContainerJSON + err = json.Unmarshal(body, &inspect121) + c.Assert(err, checker.IsNil) + c.Assert(inspect121.NetworkSettings.Networks, checker.HasLen, 3) + + bridge := inspect121.NetworkSettings.Networks["bridge"] + c.Assert(bridge.IPAddress, checker.Equals, versionedIP) + c.Assert(bridge.IPAddress, checker.Equals, inspect121.NetworkSettings.IPAddress) +} + +func connectContainerToNetworks(c *check.C, d *Daemon, cName string, nws []string) { + // Run a container on the default network + out, err := d.Cmd("run", "-d", "--name", cName, "busybox", "top") + c.Assert(err, checker.IsNil, check.Commentf(out)) + + // Attach the container to other three networks + for _, nw := range nws { + out, err = d.Cmd("network", "create", nw) + c.Assert(err, checker.IsNil, check.Commentf(out)) + out, err = d.Cmd("network", "connect", nw, cName) + c.Assert(err, checker.IsNil, check.Commentf(out)) + } +} + +func verifyContainerIsConnectedToNetworks(c *check.C, d *Daemon, cName string, nws []string) { + // Verify container is connected to all three networks + for _, nw := range nws { + out, err := d.Cmd("inspect", "-f", fmt.Sprintf("{{.NetworkSettings.Networks.%s}}", nw), cName) + c.Assert(err, checker.IsNil, check.Commentf(out)) + c.Assert(out, checker.Not(checker.Equals), "\n") + } +} + +func (s *DockerNetworkSuite) TestDockerNetworkMultipleNetworksGracefulDaemonRestart(c *check.C) { + cName := "bb" + nwList := []string{"nw1", "nw2", "nw3"} + + s.d.Start() + + connectContainerToNetworks(c, s.d, cName, nwList) + verifyContainerIsConnectedToNetworks(c, s.d, cName, nwList) + + // Reload daemon + s.d.Restart() + + _, err := s.d.Cmd("start", cName) + c.Assert(err, checker.IsNil) + + verifyContainerIsConnectedToNetworks(c, s.d, cName, nwList) +} + +func (s *DockerNetworkSuite) TestDockerNetworkMultipleNetworksUngracefulDaemonRestart(c *check.C) { + cName := "cc" + nwList := []string{"nw1", "nw2", "nw3"} + + s.d.Start() + + connectContainerToNetworks(c, s.d, cName, nwList) + verifyContainerIsConnectedToNetworks(c, s.d, cName, nwList) + + // Kill daemon and restart + if err := s.d.cmd.Process.Kill(); err != nil { + c.Fatal(err) + } + s.d.Restart() + + // Restart container + _, err := s.d.Cmd("start", cName) + c.Assert(err, checker.IsNil) + + verifyContainerIsConnectedToNetworks(c, s.d, cName, nwList) +} diff --git a/integration-cli/docker_cli_rename_test.go b/integration-cli/docker_cli_rename_test.go index a1bf4b742..df0b26862 100644 --- a/integration-cli/docker_cli_rename_test.go +++ b/integration-cli/docker_cli_rename_test.go @@ -38,6 +38,27 @@ func (s *DockerSuite) TestRenameRunningContainer(c *check.C) { c.Assert(name, checker.Equals, "/"+newName, check.Commentf("Failed to rename container %s", name)) } +func (s *DockerSuite) TestRenameRunningContainerAndReuse(c *check.C) { + testRequires(c, DaemonIsLinux) + out, _ := dockerCmd(c, "run", "--name", "first_name", "-d", "busybox", "top") + c.Assert(waitRun("first_name"), check.IsNil) + + newName := "new_name" + ContainerID := strings.TrimSpace(out) + dockerCmd(c, "rename", "first_name", newName) + + name, err := inspectField(ContainerID, "Name") + c.Assert(err, checker.IsNil, check.Commentf("Failed to rename container %s", name)) + c.Assert(name, checker.Equals, "/"+newName, check.Commentf("Failed to rename container")) + + out, _ = dockerCmd(c, "run", "--name", "first_name", "-d", "busybox", "top") + c.Assert(waitRun("first_name"), check.IsNil) + newContainerID := strings.TrimSpace(out) + name, err = inspectField(newContainerID, "Name") + c.Assert(err, checker.IsNil, check.Commentf("Failed to reuse container name")) + c.Assert(name, checker.Equals, "/first_name", check.Commentf("Failed to reuse container name")) +} + func (s *DockerSuite) TestRenameCheckNames(c *check.C) { testRequires(c, DaemonIsLinux) dockerCmd(c, "run", "--name", "first_name", "-d", "busybox", "sh") diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index e7029591c..1f5a5e46c 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -253,6 +253,37 @@ func (s *DockerSuite) TestRmiWithMultipleRepositories(c *check.C) { } } +func (s *DockerSuite) TestRmiForceWithMultipleRepositories(c *check.C) { + testRequires(c, DaemonIsLinux) + imageName := "rmiimage" + tag1 := imageName + ":tag1" + tag2 := imageName + ":tag2" + + _, err := buildImage(tag1, + `FROM scratch + MAINTAINER "docker"`, + true) + if err != nil { + c.Fatal(err) + } + + dockerCmd(c, "tag", tag1, tag2) + + out, _ := dockerCmd(c, "rmi", "-f", tag2) + if !strings.Contains(out, "Untagged: "+tag2) { + c.Fatalf("should contain Untagged: %s", tag2) + } + if strings.Contains(out, "Untagged: "+tag1) { + c.Fatalf("should not contain Untagged: %s", tag1) + } + + // Check built image still exists + images, _ := dockerCmd(c, "images", "-a") + if !strings.Contains(images, imageName) { + c.Fatalf("Built image missing %q; Images: %q", imageName, images) + } +} + func (s *DockerSuite) TestRmiBlank(c *check.C) { testRequires(c, DaemonIsLinux) // try to delete a blank image name @@ -354,3 +385,15 @@ RUN echo 2 #layer2 c.Fatalf("%q should be allowed to untag with the -f flag", newTag) } } + +func (*DockerSuite) TestRmiParentImageFail(c *check.C) { + testRequires(c, DaemonIsLinux) + + parent, err := inspectField("busybox", "Parent") + c.Assert(err, check.IsNil) + out, _, err := dockerCmdWithError("rmi", parent) + c.Assert(err, check.NotNil) + if !strings.Contains(out, "image has dependent child images") { + c.Fatalf("rmi should have failed because it's a parent image, got %s", out) + } +} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 0ceb768e9..d0292ea9e 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -184,7 +184,7 @@ func (s *DockerSuite) TestRunLinksContainerWithContainerName(c *check.C) { testRequires(c, DaemonIsLinux) dockerCmd(c, "run", "-i", "-t", "-d", "--name", "parent", "busybox") - ip, err := inspectField("parent", "NetworkSettings.IPAddress") + ip, err := inspectField("parent", "NetworkSettings.Networks.bridge.IPAddress") c.Assert(err, check.IsNil) out, _ := dockerCmd(c, "run", "--link", "parent:test", "busybox", "/bin/cat", "/etc/hosts") @@ -201,7 +201,7 @@ func (s *DockerSuite) TestRunLinksContainerWithContainerId(c *check.C) { cID, _ := dockerCmd(c, "run", "-i", "-t", "-d", "busybox") cID = strings.TrimSpace(cID) - ip, err := inspectField(cID, "NetworkSettings.IPAddress") + ip, err := inspectField(cID, "NetworkSettings.Networks.bridge.IPAddress") c.Assert(err, check.IsNil) out, _ := dockerCmd(c, "run", "--link", cID+":test", "busybox", "/bin/cat", "/etc/hosts") @@ -1197,7 +1197,7 @@ func (s *DockerSuite) TestRunNonRootUserResolvName(c *check.C) { // uses the host's /etc/resolv.conf and does not have any dns options provided. func (s *DockerSuite) TestRunResolvconfUpdate(c *check.C) { // Not applicable on Windows as testing unix specific functionality - testRequires(c, SameHostDaemon, DaemonIsLinux) + testRequires(c, SameHostDaemon, DaemonIsLinux, NativeExecDriver) tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78\n") tmpLocalhostResolvConf := []byte("nameserver 127.0.0.1") @@ -1833,7 +1833,7 @@ func (s *DockerSuite) TestRunInspectMacAddress(c *check.C) { out, _ := dockerCmd(c, "run", "-d", "--mac-address="+mac, "busybox", "top") id := strings.TrimSpace(out) - inspectedMac, err := inspectField(id, "NetworkSettings.MacAddress") + inspectedMac, err := inspectField(id, "NetworkSettings.Networks.bridge.MacAddress") c.Assert(err, check.IsNil) if inspectedMac != mac { c.Fatalf("docker inspect outputs wrong MAC address: %q, should be: %q", inspectedMac, mac) @@ -1856,7 +1856,7 @@ func (s *DockerSuite) TestRunDeallocatePortOnMissingIptablesRule(c *check.C) { out, _ := dockerCmd(c, "run", "-d", "-p", "23:23", "busybox", "top") id := strings.TrimSpace(out) - ip, err := inspectField(id, "NetworkSettings.IPAddress") + ip, err := inspectField(id, "NetworkSettings.Networks.bridge.IPAddress") c.Assert(err, check.IsNil) iptCmd := exec.Command("iptables", "-D", "DOCKER", "-d", fmt.Sprintf("%s/32", ip), "!", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-m", "tcp", "--dport", "23", "-j", "ACCEPT") @@ -3403,7 +3403,7 @@ func (s *DockerSuite) TestRunNetworkNotInitializedNoneMode(c *check.C) { testRequires(c, DaemonIsLinux) out, _ := dockerCmd(c, "run", "-d", "--net=none", "busybox", "top") id := strings.TrimSpace(out) - res, err := inspectField(id, "NetworkSettings.IPAddress") + res, err := inspectField(id, "NetworkSettings.Networks.none.IPAddress") c.Assert(err, check.IsNil) if res != "" { c.Fatalf("For 'none' mode network must not be initialized, but container got IP: %s", res) @@ -3425,13 +3425,10 @@ func (s *DockerSuite) TestContainersInUserDefinedNetwork(c *check.C) { dockerCmd(c, "run", "-d", "--net=testnetwork", "--name=first", "busybox", "top") c.Assert(waitRun("first"), check.IsNil) dockerCmd(c, "run", "-t", "--net=testnetwork", "--name=second", "busybox", "ping", "-c", "1", "first") - dockerCmd(c, "stop", "first") - dockerCmd(c, "stop", "second") - dockerCmd(c, "network", "rm", "testnetwork") } func (s *DockerSuite) TestContainersInMultipleNetworks(c *check.C) { - testRequires(c, DaemonIsLinux, NotUserNamespace) + testRequires(c, DaemonIsLinux, NotUserNamespace, NativeExecDriver) // Create 2 networks using bridge driver dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1") dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork2") @@ -3447,14 +3444,10 @@ func (s *DockerSuite) TestContainersInMultipleNetworks(c *check.C) { dockerCmd(c, "network", "connect", "testnetwork2", "second") // Check connectivity between containers dockerCmd(c, "exec", "second", "ping", "-c", "1", "first.testnetwork2") - dockerCmd(c, "stop", "first") - dockerCmd(c, "stop", "second") - dockerCmd(c, "network", "rm", "testnetwork1") - dockerCmd(c, "network", "rm", "testnetwork2") } func (s *DockerSuite) TestContainersNetworkIsolation(c *check.C) { - testRequires(c, DaemonIsLinux, NotUserNamespace) + testRequires(c, DaemonIsLinux, NotUserNamespace, NativeExecDriver) // Create 2 networks using bridge driver dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1") dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork2") @@ -3478,11 +3471,6 @@ func (s *DockerSuite) TestContainersNetworkIsolation(c *check.C) { // ping must fail again _, _, err = dockerCmdWithError("exec", "first", "ping", "-c", "1", "second") c.Assert(err, check.NotNil) - - dockerCmd(c, "stop", "first") - dockerCmd(c, "stop", "second") - dockerCmd(c, "network", "rm", "testnetwork1") - dockerCmd(c, "network", "rm", "testnetwork2") } func (s *DockerSuite) TestNetworkRmWithActiveContainers(c *check.C) { @@ -3501,17 +3489,14 @@ func (s *DockerSuite) TestNetworkRmWithActiveContainers(c *check.C) { dockerCmd(c, "stop", "first") _, _, err = dockerCmdWithError("network", "rm", "testnetwork1") c.Assert(err, check.NotNil) - - dockerCmd(c, "stop", "second") - // Network delete must succeed after all the connected containers are inactive - dockerCmd(c, "network", "rm", "testnetwork1") } func (s *DockerSuite) TestContainerRestartInMultipleNetworks(c *check.C) { - testRequires(c, DaemonIsLinux, NotUserNamespace) + testRequires(c, DaemonIsLinux, NotUserNamespace, NativeExecDriver) // Create 2 networks using bridge driver dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork1") dockerCmd(c, "network", "create", "-d", "bridge", "testnetwork2") + // Run and connect containers to testnetwork1 dockerCmd(c, "run", "-d", "--net=testnetwork1", "--name=first", "busybox", "top") c.Assert(waitRun("first"), check.IsNil) @@ -3536,11 +3521,6 @@ func (s *DockerSuite) TestContainerRestartInMultipleNetworks(c *check.C) { dockerCmd(c, "start", "second") dockerCmd(c, "exec", "first", "ping", "-c", "1", "second.testnetwork1") dockerCmd(c, "exec", "second", "ping", "-c", "1", "first.testnetwork2") - - dockerCmd(c, "stop", "first") - dockerCmd(c, "stop", "second") - dockerCmd(c, "network", "rm", "testnetwork1") - dockerCmd(c, "network", "rm", "testnetwork2") } func (s *DockerSuite) TestContainerWithConflictingHostNetworks(c *check.C) { @@ -3555,8 +3535,6 @@ func (s *DockerSuite) TestContainerWithConflictingHostNetworks(c *check.C) { // Connecting to the user defined network must fail _, _, err := dockerCmdWithError("network", "connect", "testnetwork1", "first") c.Assert(err, check.NotNil) - dockerCmd(c, "stop", "first") - dockerCmd(c, "network", "rm", "testnetwork1") } func (s *DockerSuite) TestContainerWithConflictingSharedNetwork(c *check.C) { @@ -3574,10 +3552,6 @@ func (s *DockerSuite) TestContainerWithConflictingSharedNetwork(c *check.C) { out, _, err := dockerCmdWithError("network", "connect", "testnetwork1", "second") c.Assert(err, check.NotNil) c.Assert(out, checker.Contains, runconfig.ErrConflictSharedNetwork.Error()) - - dockerCmd(c, "stop", "first") - dockerCmd(c, "stop", "second") - dockerCmd(c, "network", "rm", "testnetwork1") } func (s *DockerSuite) TestContainerWithConflictingNoneNetwork(c *check.C) { @@ -3600,10 +3574,6 @@ func (s *DockerSuite) TestContainerWithConflictingNoneNetwork(c *check.C) { // Connect second container to none network. it must fail as well _, _, err = dockerCmdWithError("network", "connect", "none", "second") c.Assert(err, check.NotNil) - - dockerCmd(c, "stop", "first") - dockerCmd(c, "stop", "second") - dockerCmd(c, "network", "rm", "testnetwork1") } // #11957 - stdin with no tty does not exit if stdin is not closed even though container exited diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 785aad410..694890271 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -17,6 +17,7 @@ import ( "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/sysinfo" + "github.com/docker/docker/pkg/units" "github.com/go-check/check" "github.com/kr/pty" ) @@ -419,7 +420,7 @@ func (s *DockerSuite) TestRunInvalidCpusetMemsFlagValue(c *check.C) { } func (s *DockerSuite) TestRunInvalidCPUShares(c *check.C) { - testRequires(c, cpuShare) + testRequires(c, cpuShare, NativeExecDriver) out, _, err := dockerCmdWithError("run", "--cpu-shares", "1", "busybox", "echo", "test") c.Assert(err, check.NotNil, check.Commentf(out)) expected := "The minimum allowed cpu-shares is 2" @@ -435,3 +436,22 @@ func (s *DockerSuite) TestRunInvalidCPUShares(c *check.C) { expected = "The maximum allowed cpu-shares is" c.Assert(out, checker.Contains, expected) } + +func (s *DockerSuite) TestRunWithCorrectMemorySwapOnLXC(c *check.C) { + testRequires(c, memoryLimitSupport) + testRequires(c, swapMemorySupport) + testRequires(c, SameHostDaemon) + + out, _ := dockerCmd(c, "run", "-d", "-m", "16m", "--memory-swap", "64m", "busybox", "top") + if _, err := os.Stat("/sys/fs/cgroup/memory/lxc"); err != nil { + c.Skip("Excecution driver must be LXC for this test") + } + id := strings.TrimSpace(out) + memorySwap, err := ioutil.ReadFile(fmt.Sprintf("/sys/fs/cgroup/memory/lxc/%s/memory.memsw.limit_in_bytes", id)) + c.Assert(err, check.IsNil) + cgSwap, err := strconv.ParseInt(strings.TrimSpace(string(memorySwap)), 10, 64) + c.Assert(err, check.IsNil) + swap, err := units.RAMInBytes("64m") + c.Assert(err, check.IsNil) + c.Assert(cgSwap, check.Equals, swap) +} diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 7ade4706c..36e19c8f1 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -26,7 +26,9 @@ import ( "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/integration" "github.com/docker/docker/pkg/ioutils" + "github.com/docker/docker/pkg/sockets" "github.com/docker/docker/pkg/stringutils" + "github.com/docker/docker/pkg/tlsconfig" "github.com/go-check/check" ) @@ -37,19 +39,26 @@ type Daemon struct { Command string GlobalFlags []string - id string - c *check.C - logFile *os.File - folder string - root string - stdin io.WriteCloser - stdout, stderr io.ReadCloser - cmd *exec.Cmd - storageDriver string - execDriver string - wait chan error - userlandProxy bool - useDefaultHost bool + id string + c *check.C + logFile *os.File + folder string + root string + stdin io.WriteCloser + stdout, stderr io.ReadCloser + cmd *exec.Cmd + storageDriver string + execDriver string + wait chan error + userlandProxy bool + useDefaultHost bool + useDefaultTLSHost bool +} + +type clientConfig struct { + transport *http.Transport + scheme string + addr string } // NewDaemon returns a Daemon instance to be used for testing. @@ -92,6 +101,50 @@ func NewDaemon(c *check.C) *Daemon { } } +func (d *Daemon) getClientConfig() (*clientConfig, error) { + var ( + transport *http.Transport + scheme string + addr string + proto string + ) + if d.useDefaultTLSHost { + option := &tlsconfig.Options{ + CAFile: "fixtures/https/ca.pem", + CertFile: "fixtures/https/client-cert.pem", + KeyFile: "fixtures/https/client-key.pem", + } + tlsConfig, err := tlsconfig.Client(*option) + if err != nil { + return nil, err + } + transport = &http.Transport{ + TLSClientConfig: tlsConfig, + } + addr = fmt.Sprintf("%s:%d", opts.DefaultHTTPHost, opts.DefaultTLSHTTPPort) + scheme = "https" + proto = "tcp" + } else if d.useDefaultHost { + addr = opts.DefaultUnixSocket + proto = "unix" + scheme = "http" + transport = &http.Transport{} + } else { + addr = filepath.Join(d.folder, "docker.sock") + proto = "unix" + scheme = "http" + transport = &http.Transport{} + } + + sockets.ConfigureTCPTransport(transport, proto, addr) + + return &clientConfig{ + transport: transport, + scheme: scheme, + addr: addr, + }, nil +} + // Start will start the daemon and return once it is ready to receive requests. // You can specify additional daemon flags. func (d *Daemon) Start(arg ...string) error { @@ -106,7 +159,7 @@ func (d *Daemon) Start(arg ...string) error { "--pidfile", fmt.Sprintf("%s/docker.pid", d.folder), fmt.Sprintf("--userland-proxy=%t", d.userlandProxy), ) - if !d.useDefaultHost { + if !(d.useDefaultHost || d.useDefaultTLSHost) { args = append(args, []string{"--host", d.sock()}...) } if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" { @@ -170,27 +223,21 @@ func (d *Daemon) Start(arg ...string) error { case <-time.After(2 * time.Second): return fmt.Errorf("[%s] timeout: daemon does not respond", d.id) case <-tick: - var ( - c net.Conn - err error - ) - if d.useDefaultHost { - c, err = net.Dial("unix", "/var/run/docker.sock") - } else { - c, err = net.Dial("unix", filepath.Join(d.folder, "docker.sock")) - } + clientConfig, err := d.getClientConfig() if err != nil { - continue + return err } - client := httputil.NewClientConn(c, nil) - defer client.Close() + client := &http.Client{ + Transport: clientConfig.transport, + } req, err := http.NewRequest("GET", "/_ping", nil) if err != nil { d.c.Fatalf("[%s] could not create new request: %v", d.id, err) } - + req.URL.Host = clientConfig.addr + req.URL.Scheme = clientConfig.scheme resp, err := client.Do(req) if err != nil { continue @@ -301,34 +348,28 @@ func (d *Daemon) Restart(arg ...string) error { func (d *Daemon) queryRootDir() (string, error) { // update daemon root by asking /info endpoint (to support user // namespaced daemon with root remapped uid.gid directory) - var ( - conn net.Conn - err error - ) - if d.useDefaultHost { - conn, err = net.Dial("unix", "/var/run/docker.sock") - } else { - conn, err = net.Dial("unix", filepath.Join(d.folder, "docker.sock")) - } + clientConfig, err := d.getClientConfig() if err != nil { return "", err } - client := httputil.NewClientConn(conn, nil) + + client := &http.Client{ + Transport: clientConfig.transport, + } req, err := http.NewRequest("GET", "/info", nil) if err != nil { - client.Close() return "", err } req.Header.Set("Content-Type", "application/json") + req.URL.Host = clientConfig.addr + req.URL.Scheme = clientConfig.scheme resp, err := client.Do(req) if err != nil { - client.Close() return "", err } body := ioutils.NewReadCloserWrapper(resp.Body, func() error { - defer client.Close() return resp.Body.Close() }) @@ -506,6 +547,42 @@ func deleteAllContainers() error { return nil } +func deleteAllNetworks() error { + networks, err := getAllNetworks() + if err != nil { + return err + } + var errors []string + for _, n := range networks { + if n.Name != "bridge" { + status, b, err := sockRequest("DELETE", "/networks/"+n.Name, nil) + if err != nil { + errors = append(errors, err.Error()) + continue + } + if status != http.StatusNoContent { + errors = append(errors, fmt.Sprintf("error deleting network %s: %s", n.Name, string(b))) + } + } + } + if len(errors) > 0 { + return fmt.Errorf(strings.Join(errors, "\n")) + } + return nil +} + +func getAllNetworks() ([]types.NetworkResource, error) { + var networks []types.NetworkResource + _, b, err := sockRequest("GET", "/networks", nil) + if err != nil { + return nil, err + } + if err := json.Unmarshal(b, &networks); err != nil { + return nil, err + } + return networks, nil +} + func deleteAllVolumes() error { volumes, err := getAllVolumes() if err != nil { @@ -717,19 +794,17 @@ func dockerCmdInDirWithTimeout(timeout time.Duration, path string, args ...strin return integration.DockerCmdInDirWithTimeout(dockerBinary, timeout, path, args...) } -func findContainerIP(c *check.C, id string, vargs ...string) string { - args := append(vargs, "inspect", "--format='{{ .NetworkSettings.IPAddress }}'", id) - cmd := exec.Command(dockerBinary, args...) - out, _, err := runCommandWithOutput(cmd) - if err != nil { - c.Fatal(err, out) - } - +func findContainerIP(c *check.C, id string, network string) string { + out, _ := dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.IPAddress }}'", network), id) return strings.Trim(out, " \r\n'") } func (d *Daemon) findContainerIP(id string) string { - return findContainerIP(d.c, id, "--host", d.sock()) + out, err := d.Cmd("inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.bridge.IPAddress }}'"), id) + if err != nil { + d.c.Log(err) + } + return strings.Trim(out, " \r\n'") } func getContainerCount() (int, error) { @@ -1517,3 +1592,11 @@ func waitInspect(name, expr, expected string, timeout time.Duration) error { } return nil } + +func getInspectBody(c *check.C, version, id string) []byte { + endpoint := fmt.Sprintf("/%s/containers/%s/json", version, id) + status, body, err := sockRequest("GET", endpoint, nil) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusOK) + return body +} diff --git a/man/docker-attach.1.md b/man/docker-attach.1.md index 658228cdd..96fb3756a 100644 --- a/man/docker-attach.1.md +++ b/man/docker-attach.1.md @@ -16,7 +16,7 @@ The **docker attach** command allows you to attach to a running container using the container's ID or name, either to view its ongoing output or to control it interactively. You can attach to the same contained process multiple times simultaneously, screen sharing style, or quickly view the progress of your -daemonized process. +detached process. You can detach from the container (and leave it running) with `CTRL-p CTRL-q` (for a quiet exit) or `CTRL-c` which will send a `SIGKILL` to the container. diff --git a/man/docker-build.1.md b/man/docker-build.1.md index 4bfadcbe4..876d6fa9b 100644 --- a/man/docker-build.1.md +++ b/man/docker-build.1.md @@ -7,7 +7,7 @@ docker-build - Build a new image from the source code at PATH # SYNOPSIS **docker build** [**--build-arg**[=*[]*]] -[**-c**|**--cpu-shares**[=*0*]] +[**--cpu-shares**[=*0*]] [**--cgroup-parent**[=*CGROUP-PARENT*]] [**--help**] [**-f**|**--file**[=*PATH/Dockerfile*]] @@ -90,7 +90,7 @@ set as the **URL**, the repository is cloned locally and then sent as the contex **--memory-swap**=*MEMORY-SWAP* Total memory (memory + swap), '-1' to disable swap. -**-c**, **--cpu-shares**=*0* +**--cpu-shares**=*0* CPU shares (relative weight). By default, all containers get the same proportion of CPU cycles. diff --git a/man/docker-create.1.md b/man/docker-create.1.md index e845befe9..ecea12c5f 100644 --- a/man/docker-create.1.md +++ b/man/docker-create.1.md @@ -9,7 +9,7 @@ docker-create - Create a new container [**-a**|**--attach**[=*[]*]] [**--add-host**[=*[]*]] [**--blkio-weight**[=*[BLKIO-WEIGHT]*]] -[**-c**|**--cpu-shares**[=*0*]] +[**--cpu-shares**[=*0*]] [**--cap-add**[=*[]*]] [**--cap-drop**[=*[]*]] [**--cgroup-parent**[=*CGROUP-PATH*]] @@ -83,7 +83,7 @@ The initial status of the container created with **docker create** is 'created'. **--blkio-weight**=0 Block IO weight (relative weight) accepts a weight value between 10 and 1000. -**-c**, **--cpu-shares**=0 +**--cpu-shares**=0 CPU shares (relative weight) **--cap-add**=[] diff --git a/man/docker-daemon.8.md b/man/docker-daemon.8.md index accc3b1db..b85a9a76f 100644 --- a/man/docker-daemon.8.md +++ b/man/docker-daemon.8.md @@ -81,8 +81,9 @@ format. URL of the distributed storage backend **--cluster-advertise**="" - Specifies the 'host:port' combination that this particular daemon instance should use when advertising - itself to the cluster. The daemon is reached by remote hosts on this 'host:port' combination. + Specifies the 'host:port' or `interface:port` combination that this particular + daemon instance should use when advertising itself to the cluster. The daemon + is reached through this value. **--cluster-store-opt**="" Specifies options for the Key/Value store. diff --git a/man/docker-info.1.md b/man/docker-info.1.md index 1aca0b5b2..f67a4fb00 100644 --- a/man/docker-info.1.md +++ b/man/docker-info.1.md @@ -33,7 +33,7 @@ Here is a sample output: # docker info Containers: 14 Images: 52 - Engine Version: 1.9.0 + Server Version: 1.9.0 Storage Driver: aufs Root Dir: /var/lib/docker/aufs Dirs: 80 diff --git a/man/docker-inspect.1.md b/man/docker-inspect.1.md index 82a7907d2..34dd04a93 100644 --- a/man/docker-inspect.1.md +++ b/man/docker-inspect.1.md @@ -194,7 +194,7 @@ To get information on a container use its ID or instance name: To get the IP address of a container use: - $ docker inspect --format='{{.NetworkSettings.IPAddress}}' d2cc496561d6 + $ docker inspect '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' d2cc496561d6 172.17.0.2 ## Listing all port bindings diff --git a/man/docker-network-connect.1.md b/man/docker-network-connect.1.md new file mode 100644 index 000000000..7dc23eb31 --- /dev/null +++ b/man/docker-network-connect.1.md @@ -0,0 +1,55 @@ +% DOCKER(1) Docker User Manuals +% Docker Community +% OCT 2015 +# NAME +docker-network-connect - connect a container to a network + +# SYNOPSIS +**docker network connect NAME CONTAINER** + +[**--help**] + +# DESCRIPTION + +Connects a running container to a network. You can connect a container by name +or by ID. Once connected, the container can communicate with other containers in +the same network. + +```bash +$ docker network connect multi-host-network container1 +``` + +You can also use the `docker run --net=` option to start a container and immediately connect it to a network. + +```bash +$ docker run -itd --net=multi-host-network busybox +``` + +You can pause, restart, and stop containers that are connected to a network. +Paused containers remain connected and a revealed by a `network inspect`. When +the container is stopped, it does not appear on the network until you restart +it. The container's IP address is not guaranteed to remain the same when a +stopped container rejoins the network. + +To verify the container is connected, use the `docker network inspect` command. Use `docker network disconnect` to remove a container from the network. + +Once connected in network, containers can communicate using only another +container's IP address or name. For `overlay` networks or custom plugins that +support multi-host connectivity, containers connected to the same multi-host +network but launched from different Engines can also communicate in this way. + +You can connect a container to one or more networks. The networks need not be the same type. For example, you can connect a single container bridge and overlay networks. + + +# OPTIONS +**NAME** + Specify network driver name + +**CONTAINER** + Specify container name + +**--help** + Print usage statement + +# HISTORY +OCT 2015, created by Mary Anthony diff --git a/man/docker-network-create.1.md b/man/docker-network-create.1.md new file mode 100644 index 000000000..308f2d6bf --- /dev/null +++ b/man/docker-network-create.1.md @@ -0,0 +1,149 @@ +% DOCKER(1) Docker User Manuals +% Docker Community +% OCT 2015 +# NAME +docker-network-create - create a new network + +# SYNOPSIS +**docker network create** + +**--aux-address=map[]** +**-d** | **--driver=DRIVER** +**--gateway=[]** +**--help=false** +**--ip-range=[]** +**--ipam-driver=default** +**-o** | **--opt=map[]** +**--subnet=[]** + +# DESCRIPTION + +Creates a new network. The `DRIVER` accepts `bridge` or `overlay` which are the +built-in network drivers. If you have installed a third party or your own custom +network driver you can specify that `DRIVER` here also. If you don't specify the +`--driver` option, the command automatically creates a `bridge` network for you. +When you install Docker Engine it creates a `bridge` network automatically. This +network corresponds to the `docker0` bridge that Engine has traditionally relied +on. When launch a new container with `docker run` it automatically connects to +this bridge network. You cannot remove this default bridge network but you can +create new ones using the `network create` command. + +```bash +$ docker network create -d bridge my-bridge-network +``` + +Bridge networks are isolated networks on a single Engine installation. If you +want to create a network that spans multiple Docker hosts each running an +Engine, you must create an `overlay` network. Unlike `bridge` networks overlay +networks require some pre-existing conditions before you can create one. These +conditions are: + +* Access to a key-value store. Engine supports Consul, Etcd, and Zookeeper (Distributed store) key-value stores. +* A cluster of hosts with connectivity to the key-value store. +* A properly configured Engine `daemon` on each host in the cluster. + +The `docker daemon` options that support the `overlay` network are: + +* `--cluster-store` +* `--cluster-store-opt` +* `--cluster-advertise` + +To read more about these options and how to configure them, see ["*Get started +with multi-host +network*"](https://www.docker.com/engine/userguide/networking/get-started-overlay.md). + +It is also a good idea, though not required, that you install Docker Swarm on to +manage the cluster that makes up your network. Swarm provides sophisticated +discovery and server management that can assist your implementation. + +Once you have prepared the `overlay` network prerequisites you simply choose a +Docker host in the cluster and issue the following to create the network: + +```bash +$ docker network create -d overlay my-multihost-network +``` + +Network names must be unique. The Docker daemon attempts to identify naming +conflicts but this is not guaranteed. It is the user's responsibility to avoid +name conflicts. + +## Connect containers + +When you start a container use the `--net` flag to connect it to a network. +This adds the `busybox` container to the `mynet` network. + +```bash +$ docker run -itd --net=mynet busybox +``` + +If you want to add a container to a network after the container is already +running use the `docker network connect` subcommand. + +You can connect multiple containers to the same network. Once connected, the +containers can communicate using only another container's IP address or name. +For `overlay` networks or custom plugins that support multi-host connectivity, +containers connected to the same multi-host network but launched from different +Engines can also communicate in this way. + +You can disconnect a container from a network using the `docker network +disconnect` command. + +## Specifying advanced options + +When you create a network, Engine creates a non-overlapping subnetwork for the +network by default. This subnetwork is not a subdivision of an existing network. +It is purely for ip-addressing purposes. You can override this default and +specify subnetwork values directly using the the `--subnet` option. On a +`bridge` network you can only create a single subnet: + +```bash +docker network create -d --subnet=192.168.0.0/16 +``` +Additionally, you also specify the `--gateway` `--ip-range` and `--aux-address` options. + +```bash +network create --driver=bridge --subnet=172.28.0.0/16 --ip-range=172.28.5.0/24 --gateway=172.28.5.254 br0 +``` + +If you omit the `--gateway` flag the Engine selects one for you from inside a +preferred pool. For `overlay` networks and for network driver plugins that +support it you can create multiple subnetworks. + +```bash +docker network create -d overlay + --subnet=192.168.0.0/16 --subnet=192.170.0.0/16 + --gateway=192.168.0.100 --gateway=192.170.0.100 + --ip-range=192.168.1.0/24 + --aux-address a=192.168.1.5 --aux-address b=192.168.1.6 + --aux-address a=192.170.1.5 --aux-address b=192.170.1.6 + my-multihost-newtork +``` +Be sure that your subnetworks do not overlap. If they do, the network create fails and Engine returns an error. + +# OPTIONS +**--aux-address=map[]** + Auxiliary ipv4 or ipv6 addresses used by network driver + +**-d** | **--driver=DRIVER** + Driver to manage the Network bridge or overlay. The default is bridge. + +**--gateway=[] ** + ipv4 or ipv6 Gateway for the master subnet + +**--help=false ** + Print usage + +**--ip-range=[] ** + Allocate container ip from a sub-range + +**--ipam-driver=default ** + IP Address Management Driver + +**-o | --opt=map[]** + Set custom network plugin options + +**--subnet=[]** + Subnet in CIDR format that represents a network segment + +# HISTORY +OCT 2015, created by Mary Anthony diff --git a/man/docker-network-disconnect.1.md b/man/docker-network-disconnect.1.md new file mode 100644 index 000000000..6cbc44119 --- /dev/null +++ b/man/docker-network-disconnect.1.md @@ -0,0 +1,32 @@ +% DOCKER(1) Docker User Manuals +% Docker Community +% OCT 2015 +# NAME +docker-network-disconnect - disconnect a container from a network + +# SYNOPSIS +**docker network disconnect NETWORK CONTAINER** + +[**--help**] + +# DESCRIPTION + +Disconnects a container from a network. The container must be running to disconnect it from the network. + +```bash + $ docker network disconnect multi-host-network container1 +``` + + +# OPTIONS +**NETWORK** + Specify network name + +**CONTAINER** + Specify container name + +**--help** + Print usage statement + +# HISTORY +OCT 2015, created by Mary Anthony diff --git a/man/docker-network-inspect.1.md b/man/docker-network-inspect.1.md new file mode 100644 index 000000000..3b128223c --- /dev/null +++ b/man/docker-network-inspect.1.md @@ -0,0 +1,58 @@ +% DOCKER(1) Docker User Manuals +% Docker Community +% OCT 2015 +# NAME +docker-network-inspect - inspect a network + +# SYNOPSIS +**docker network inspect NETWORK [NETWORK...]** + +[**--help**] + +# DESCRIPTION + +Returns information about one or more networks. By default, this command renders all results in a JSON object. For example, if you connect two containers to a network: + +```bash +$ sudo docker run -itd --name=container1 busybox +f2870c98fd504370fb86e59f32cd0753b1ac9b69b7d80566ffc7192a82b3ed27 + +$ sudo docker run -itd --name=container2 busybox +bda12f8922785d1f160be70736f26c1e331ab8aaf8ed8d56728508f2e2fd4727 +``` + +The `network inspect` command shows the containers, by id, in its results. + +```bash +$ sudo docker network inspect bridge +[ + { + "name": "bridge", + "id": "7fca4eb8c647e57e9d46c32714271e0c3f8bf8d17d346629e2820547b2d90039", + "driver": "bridge", + "containers": { + "bda12f8922785d1f160be70736f26c1e331ab8aaf8ed8d56728508f2e2fd4727": { + "endpoint": "e0ac95934f803d7e36384a2029b8d1eeb56cb88727aa2e8b7edfeebaa6dfd758", + "mac_address": "02:42:ac:11:00:03", + "ipv4_address": "172.17.0.3/16", + "ipv6_address": "" + }, + "f2870c98fd504370fb86e59f32cd0753b1ac9b69b7d80566ffc7192a82b3ed27": { + "endpoint": "31de280881d2a774345bbfb1594159ade4ae4024ebfb1320cb74a30225f6a8ae", + "mac_address": "02:42:ac:11:00:02", + "ipv4_address": "172.17.0.2/16", + "ipv6_address": "" + } + } + } +] +``` + + +# OPTIONS + +**--help** + Print usage statement + +# HISTORY +OCT 2015, created by Mary Anthony diff --git a/man/docker-network-ls.1.md b/man/docker-network-ls.1.md new file mode 100644 index 000000000..5dd2ad480 --- /dev/null +++ b/man/docker-network-ls.1.md @@ -0,0 +1,51 @@ +% DOCKER(1) Docker User Manuals +% Docker Community +% OCT 2015 +# NAME +docker-network-ls - list networks + +# SYNOPSIS +**docker network ls** + +[**--no-trunc**] +[**-q** | **--quiet**] +[**--help**] + +# DESCRIPTION + +Lists all the networks the Engine `daemon` knows about. This includes the +networks that span across multiple hosts in a cluster, for example: + +```bash + $ sudo docker network ls + NETWORK ID NAME DRIVER + 7fca4eb8c647 bridge bridge + 9f904ee27bf5 none null + cf03ee007fb4 host host + 78b03ee04fc4 multi-host overlay +``` + +Use the `--no-trunc` option to display the full network id: + +```bash +docker network ls --no-trunc +NETWORK ID NAME DRIVER +18a2866682b85619a026c81b98a5e375bd33e1b0936a26cc497c283d27bae9b3 none null +c288470c46f6c8949c5f7e5099b5b7947b07eabe8d9a27d79a9cbf111adcbf47 host host +7b369448dccbf865d397c8d2be0cda7cf7edc6b0945f77d2529912ae917a0185 bridge bridge +95e74588f40db048e86320c6526440c504650a1ff3e9f7d60a497c4d2163e5bd foo bridge +``` + +# OPTIONS + +[**--no-trunc**] + Do not truncate the output + +[**-q** | **--quiet**] + Only display numeric IDs + +**--help** + Print usage statement + +# HISTORY +OCT 2015, created by Mary Anthony diff --git a/man/docker-network-rm.1.md b/man/docker-network-rm.1.md new file mode 100644 index 000000000..d5c4515f6 --- /dev/null +++ b/man/docker-network-rm.1.md @@ -0,0 +1,29 @@ +% DOCKER(1) Docker User Manuals +% Docker Community +% OCT 2015 +# NAME +docker-network-rm - remove a new network + +# SYNOPSIS +**docker network rm NETWORK** + +[**--help**] + +# DESCRIPTION + +Removes a network by name or identifier. To remove a network, you must first disconnect any containers connected to it. + +``` + $ docker network rm my-network +``` + + +# OPTIONS +**NETWORK** + Specify network name + +**--help** + Print usage statement + +# HISTORY +OCT 2015, created by Mary Anthony diff --git a/man/docker-run.1.md b/man/docker-run.1.md index 443933457..0a1268292 100644 --- a/man/docker-run.1.md +++ b/man/docker-run.1.md @@ -9,7 +9,7 @@ docker-run - Run a command in a new container [**-a**|**--attach**[=*[]*]] [**--add-host**[=*[]*]] [**--blkio-weight**[=*[BLKIO-WEIGHT]*]] -[**-c**|**--cpu-shares**[=*0*]] +[**--cpu-shares**[=*0*]] [**--cap-add**[=*[]*]] [**--cap-drop**[=*[]*]] [**--cgroup-parent**[=*CGROUP-PATH*]] @@ -100,14 +100,14 @@ option can be set multiple times. **--blkio-weight**=0 Block IO weight (relative weight) accepts a weight value between 10 and 1000. -**-c**, **--cpu-shares**=0 +**--cpu-shares**=0 CPU shares (relative weight) By default, all containers get the same proportion of CPU cycles. This proportion can be modified by changing the container's CPU share weighting relative to the weighting of all other running containers. -To modify the proportion from the default of 1024, use the **-c** or **--cpu-shares** +To modify the proportion from the default of 1024, use the **--cpu-shares** flag to set the weighting to 2 or higher. The proportion will only apply when CPU-intensive processes are running. @@ -353,10 +353,14 @@ ports and the exposed ports, use `docker port`. **-p**, **--publish**=[] Publish a container's port, or range of ports, to the host. - format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort - Both hostPort and containerPort can be specified as a range of ports. - When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. (e.g., `-p 1234-1236:1234-1236/tcp`) - (use 'docker port' to see the actual mapping) + + Format: `ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort` +Both hostPort and containerPort can be specified as a range of ports. +When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. +(e.g., `docker run -p 1234-1236:1222-1224 --name thisWorks -t busybox` +but not `docker run -p 1230-1236:1230-1240 --name RangeContainerPortsBiggerThanRangeHostPorts -t busybox`) +With ip: `docker run -p 127.0.0.1:$HOSTPORT:$CONTAINERPORT --name CONTAINER -t someimage` +Use `docker port` to see the actual mapping: `docker port CONTAINER $CONTAINERPORT` **--pid**=host Set the PID mode for the container @@ -433,17 +437,17 @@ standard input. ""--ulimit""=[] Ulimit options -**-v**, **--volume**=[] Create a bind mount +**-v**, **--volume**=[] Create a bind mount (format: `[host-dir:]container-dir[:]`, where suffix options are comma delimited and selected from [rw|ro] and [z|Z].) - + (e.g., using -v /host-dir:/container-dir, bind mounts /host-dir in the host to /container-dir in the Docker container) - + If 'host-dir' is missing, then docker automatically creates the new volume on the host. **This auto-creation of the host path has been deprecated in Release: v1.9.** - + The **-v** option can be used one or more times to add one or more mounts to a container. These mounts can then be used in other containers using the **--volumes-from** option. @@ -465,31 +469,31 @@ content label. Shared volume labels allow all containers to read/write content. The `Z` option tells Docker to label the content with a private unshared label. Only the current container can use a private volume. -The `container-dir` must always be an absolute path such as `/src/docs`. -The `host-dir` can either be an absolute path or a `name` value. If you -supply an absolute path for the `host-dir`, Docker bind-mounts to the path +The `container-dir` must always be an absolute path such as `/src/docs`. +The `host-dir` can either be an absolute path or a `name` value. If you +supply an absolute path for the `host-dir`, Docker bind-mounts to the path you specify. If you supply a `name`, Docker creates a named volume by that `name`. -A `name` value must start with start with an alphanumeric character, -followed by `a-z0-9`, `_` (underscore), `.` (period) or `-` (hyphen). +A `name` value must start with start with an alphanumeric character, +followed by `a-z0-9`, `_` (underscore), `.` (period) or `-` (hyphen). An absolute path starts with a `/` (forward slash). -For example, you can specify either `/foo` or `foo` for a `host-dir` value. -If you supply the `/foo` value, Docker creates a bind-mount. If you supply +For example, you can specify either `/foo` or `foo` for a `host-dir` value. +If you supply the `/foo` value, Docker creates a bind-mount. If you supply the `foo` specification, Docker creates a named volume. **--volumes-from**=[] Mount volumes from the specified container(s) Mounts already mounted volumes from a source container onto another - container. You must supply the source's container-id. To share + container. You must supply the source's container-id. To share a volume, use the **--volumes-from** option when running - the target container. You can share volumes even if the source container + the target container. You can share volumes even if the source container is not running. - By default, Docker mounts the volumes in the same mode (read-write or - read-only) as it is mounted in the source container. Optionally, you - can change this by suffixing the container-id with either the `:ro` or + By default, Docker mounts the volumes in the same mode (read-write or + read-only) as it is mounted in the source container. Optionally, you + can change this by suffixing the container-id with either the `:ro` or `:rw ` keyword. If the location of the volume from the source container overlaps with @@ -554,7 +558,7 @@ Now run a regular container, and it correctly does NOT see the shared memory seg ``` $ docker run -it shm ipcs -m - ------ Shared Memory Segments -------- + ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status ``` @@ -633,6 +637,15 @@ Running the **env** command in the linker container shows environment variables When linking two containers Docker will use the exposed ports of the container to create a secure tunnel for the parent to access. +If a container is connected to the default bridge network and `linked` +with other containers, then the container's `/etc/hosts` file is updated +with the linked container's name. + +> **Note** Since Docker may live update the container’s `/etc/hosts` file, there +may be situations when processes inside the container can end up reading an +empty or incomplete `/etc/hosts` file. In most cases, retrying the read again +should fix the problem. + ## Mapping Ports for External Usage diff --git a/opts/opts.go b/opts/opts.go index 78059739a..3d8e4b481 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -16,7 +16,7 @@ var ( alphaRegexp = regexp.MustCompile(`[a-zA-Z]`) domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`) // DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. docker daemon -H tcp://:8080 - DefaultHTTPHost = "127.0.0.1" + DefaultHTTPHost = "localhost" // DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp:// // TODO Windows. DefaultHTTPPort is only used on Windows if a -H parameter @@ -342,7 +342,7 @@ func ValidateLabel(val string) (string, error) { // ValidateHost validates that the specified string is a valid host and returns it. func ValidateHost(val string) (string, error) { - _, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultUnixSocket, val) + _, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, "", val) if err != nil { return val, err } @@ -352,8 +352,8 @@ func ValidateHost(val string) (string, error) { } // ParseHost and set defaults for a Daemon host string -func ParseHost(val string) (string, error) { - host, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultUnixSocket, val) +func ParseHost(defaultHost, val string) (string, error) { + host, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, defaultHost, val) if err != nil { return val, err } diff --git a/opts/opts_test.go b/opts/opts_test.go index 0e4e9562e..baf5f5336 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -445,9 +445,9 @@ func TestParseHost(t *testing.T) { "fd://": "fd://", "fd://something": "fd://something", "tcp://host:": "tcp://host:2375", - "tcp://": "tcp://127.0.0.1:2375", - "tcp://:2375": "tcp://127.0.0.1:2375", // default ip address - "tcp://:2376": "tcp://127.0.0.1:2376", // default ip address + "tcp://": "tcp://localhost:2375", + "tcp://:2375": "tcp://localhost:2375", // default ip address + "tcp://:2376": "tcp://localhost:2376", // default ip address "tcp://0.0.0.0:8080": "tcp://0.0.0.0:8080", "tcp://192.168.0.0:12000": "tcp://192.168.0.0:12000", "tcp://192.168:8080": "tcp://192.168:8080", @@ -458,12 +458,12 @@ func TestParseHost(t *testing.T) { } for value, errorMessage := range invalid { - if _, err := ParseHost(value); err == nil || err.Error() != errorMessage { + if _, err := ParseHost(defaultHTTPHost, value); err == nil || err.Error() != errorMessage { t.Fatalf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err) } } for value, expected := range valid { - if actual, err := ParseHost(value); err != nil || actual != expected { + if actual, err := ParseHost(defaultHTTPHost, value); err != nil || actual != expected { t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err) } } diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 69b7beebf..4fa8cee53 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -20,6 +20,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/idtools" + "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/pools" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/system" @@ -116,10 +117,10 @@ func DetectCompression(source []byte) Compression { return Uncompressed } -func xzDecompress(archive io.Reader) (io.ReadCloser, error) { +func xzDecompress(archive io.Reader) (io.ReadCloser, <-chan struct{}, error) { args := []string{"xz", "-d", "-c", "-q"} - return CmdStream(exec.Command(args[0], args[1:]...), archive) + return cmdStream(exec.Command(args[0], args[1:]...), archive) } // DecompressStream decompress the archive and returns a ReaderCloser with the decompressed archive. @@ -148,12 +149,15 @@ func DecompressStream(archive io.Reader) (io.ReadCloser, error) { readBufWrapper := p.NewReadCloserWrapper(buf, bz2Reader) return readBufWrapper, nil case Xz: - xzReader, err := xzDecompress(buf) + xzReader, chdone, err := xzDecompress(buf) if err != nil { return nil, err } readBufWrapper := p.NewReadCloserWrapper(buf, xzReader) - return readBufWrapper, nil + return ioutils.NewReadCloserWrapper(readBufWrapper, func() error { + <-chdone + return readBufWrapper.Close() + }), nil default: return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) } @@ -910,7 +914,11 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { } }() - return archiver.Untar(r, filepath.Dir(dst), nil) + err = archiver.Untar(r, filepath.Dir(dst), nil) + if err != nil { + r.CloseWithError(err) + } + return err } // CopyFileWithTar emulates the behavior of the 'cp' command-line @@ -925,57 +933,33 @@ func CopyFileWithTar(src, dst string) (err error) { return defaultArchiver.CopyFileWithTar(src, dst) } -// CmdStream executes a command, and returns its stdout as a stream. +// cmdStream executes a command, and returns its stdout as a stream. // If the command fails to run or doesn't complete successfully, an error // will be returned, including anything written on stderr. -func CmdStream(cmd *exec.Cmd, input io.Reader) (io.ReadCloser, error) { - if input != nil { - stdin, err := cmd.StdinPipe() - if err != nil { - return nil, err - } - // Write stdin if any - go func() { - io.Copy(stdin, input) - stdin.Close() - }() - } - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - stderr, err := cmd.StderrPipe() - if err != nil { - return nil, err - } +func cmdStream(cmd *exec.Cmd, input io.Reader) (io.ReadCloser, <-chan struct{}, error) { + chdone := make(chan struct{}) + cmd.Stdin = input pipeR, pipeW := io.Pipe() - errChan := make(chan []byte) - // Collect stderr, we will use it in case of an error - go func() { - errText, e := ioutil.ReadAll(stderr) - if e != nil { - errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")") - } - errChan <- errText - }() + cmd.Stdout = pipeW + var errBuf bytes.Buffer + cmd.Stderr = &errBuf + + // Run the command and return the pipe + if err := cmd.Start(); err != nil { + return nil, nil, err + } + // Copy stdout to the returned pipe go func() { - _, err := io.Copy(pipeW, stdout) - if err != nil { - pipeW.CloseWithError(err) - } - errText := <-errChan if err := cmd.Wait(); err != nil { - pipeW.CloseWithError(fmt.Errorf("%s: %s", err, errText)) + pipeW.CloseWithError(fmt.Errorf("%s: %s", err, errBuf.String())) } else { pipeW.Close() } + close(chdone) }() - // Run the command and return the pipe - if err := cmd.Start(); err != nil { - return nil, err - } - return pipeR, nil + + return pipeR, chdone, nil } // NewTempArchive reads the content of src into a temporary file, and returns the contents diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index b9bfc2390..6c54c02d1 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -160,7 +160,7 @@ func TestExtensionXz(t *testing.T) { func TestCmdStreamLargeStderr(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") - out, err := CmdStream(cmd, nil) + out, _, err := cmdStream(cmd, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -181,7 +181,7 @@ func TestCmdStreamLargeStderr(t *testing.T) { func TestCmdStreamBad(t *testing.T) { badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") - out, err := CmdStream(badCmd, nil) + out, _, err := cmdStream(badCmd, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -196,7 +196,7 @@ func TestCmdStreamBad(t *testing.T) { func TestCmdStreamGood(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0") - out, err := CmdStream(cmd, nil) + out, _, err := cmdStream(cmd, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/archive/archive_windows_test.go b/pkg/archive/archive_windows_test.go index 72bc71e06..b7abc4022 100644 --- a/pkg/archive/archive_windows_test.go +++ b/pkg/archive/archive_windows_test.go @@ -3,10 +3,32 @@ package archive import ( + "io/ioutil" "os" + "path/filepath" "testing" ) +func TestCopyFileWithInvalidDest(t *testing.T) { + folder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(folder) + dest := "c:dest" + srcFolder := filepath.Join(folder, "src") + src := filepath.Join(folder, "src", "src") + err = os.MkdirAll(srcFolder, 0740) + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(src, []byte("content"), 0777) + err = CopyWithTar(src, dest) + if err == nil { + t.Fatalf("archiver.CopyWithTar should throw an error on invalid dest.") + } +} + func TestCanonicalTarNameForPath(t *testing.T) { cases := []struct { in, expected string diff --git a/pkg/chrootarchive/archive_unix.go b/pkg/chrootarchive/archive_unix.go index 83331425f..51a43f67d 100644 --- a/pkg/chrootarchive/archive_unix.go +++ b/pkg/chrootarchive/archive_unix.go @@ -8,6 +8,7 @@ import ( "flag" "fmt" "io" + "io/ioutil" "os" "runtime" "syscall" @@ -79,6 +80,11 @@ func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.T w.Close() if err := cmd.Wait(); err != nil { + // when `xz -d -c -q | docker-untar ...` failed on docker-untar side, + // we need to exhaust `xz`'s output, otherwise the `xz` side will be + // pending on write pipe forever + io.Copy(ioutil.Discard, decompressedArchive) + return fmt.Errorf("Untar re-exec error: %v: output: %s", err, output) } return nil diff --git a/pkg/discovery/backends.go b/pkg/discovery/backends.go index 3874a7d04..875a26c44 100644 --- a/pkg/discovery/backends.go +++ b/pkg/discovery/backends.go @@ -2,6 +2,7 @@ package discovery import ( "fmt" + "net" "strings" "time" @@ -39,6 +40,63 @@ func parse(rawurl string) (string, string) { return parts[0], parts[1] } +// ParseAdvertise parses the --cluster-advertise daemon config which accepts +// : or : +func ParseAdvertise(store, advertise string) (string, error) { + var ( + iface *net.Interface + addrs []net.Addr + err error + ) + + addr, port, err := net.SplitHostPort(advertise) + + if err != nil { + return "", fmt.Errorf("invalid --cluster-advertise configuration: %s: %v", advertise, err) + } + + ip := net.ParseIP(addr) + // If it is a valid ip-address, use it as is + if ip != nil { + return advertise, nil + } + + // If advertise is a valid interface name, get the valid ipv4 address and use it to advertise + ifaceName := addr + iface, err = net.InterfaceByName(ifaceName) + if err != nil { + return "", fmt.Errorf("invalid cluster advertise IP address or interface name (%s) : %v", advertise, err) + } + + addrs, err = iface.Addrs() + if err != nil { + return "", fmt.Errorf("unable to get advertise IP address from interface (%s) : %v", advertise, err) + } + + if addrs == nil || len(addrs) == 0 { + return "", fmt.Errorf("no available advertise IP address in interface (%s)", advertise) + } + + addr = "" + for _, a := range addrs { + ip, _, err := net.ParseCIDR(a.String()) + if err != nil { + return "", fmt.Errorf("error deriving advertise ip-address in interface (%s) : %v", advertise, err) + } + if ip.To4() == nil || ip.IsLoopback() { + continue + } + addr = ip.String() + break + } + if addr == "" { + return "", fmt.Errorf("couldnt find a valid ip-address in interface %s", advertise) + } + + addr = fmt.Sprintf("%s:%s", addr, port) + return addr, nil +} + // New returns a new Discovery given a URL, heartbeat and ttl settings. // Returns an error if the URL scheme is not supported. func New(rawurl string, heartbeat time.Duration, ttl time.Duration, clusterOpts map[string]string) (Backend, error) { diff --git a/pkg/parsers/parsers.go b/pkg/parsers/parsers.go index 75d54c54c..05a2401dd 100644 --- a/pkg/parsers/parsers.go +++ b/pkg/parsers/parsers.go @@ -16,9 +16,12 @@ import ( // Depending of the address specified, will use the defaultTCPAddr or defaultUnixAddr // defaultUnixAddr must be a absolute file path (no `unix://` prefix) // defaultTCPAddr must be the full `tcp://host:port` form -func ParseDockerDaemonHost(defaultTCPAddr, defaultUnixAddr, addr string) (string, error) { +func ParseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defaultAddr, addr string) (string, error) { addr = strings.TrimSpace(addr) if addr == "" { + if defaultAddr == defaultTLSHost { + return defaultTLSHost, nil + } if runtime.GOOS != "windows" { return fmt.Sprintf("unix://%s", defaultUnixAddr), nil } diff --git a/pkg/parsers/parsers_test.go b/pkg/parsers/parsers_test.go index 49cbdb163..f32569a0f 100644 --- a/pkg/parsers/parsers_test.go +++ b/pkg/parsers/parsers_test.go @@ -9,9 +9,10 @@ import ( func TestParseDockerDaemonHost(t *testing.T) { var ( - defaultHTTPHost = "tcp://127.0.0.1:2376" - defaultUnix = "/var/run/docker.sock" - defaultHOST = "unix:///var/run/docker.sock" + defaultHTTPHost = "tcp://localhost:2375" + defaultHTTPSHost = "tcp://localhost:2376" + defaultUnix = "/var/run/docker.sock" + defaultHOST = "unix:///var/run/docker.sock" ) if runtime.GOOS == "windows" { defaultHOST = defaultHTTPHost @@ -28,30 +29,33 @@ func TestParseDockerDaemonHost(t *testing.T) { "fd": "Invalid bind address format: fd", } valids := map[string]string{ - "0.0.0.1:": "tcp://0.0.0.1:2376", + "0.0.0.1:": "tcp://0.0.0.1:2375", "0.0.0.1:5555": "tcp://0.0.0.1:5555", "0.0.0.1:5555/path": "tcp://0.0.0.1:5555/path", - ":6666": "tcp://127.0.0.1:6666", - ":6666/path": "tcp://127.0.0.1:6666/path", + ":6666": "tcp://localhost:6666", + ":6666/path": "tcp://localhost:6666/path", "": defaultHOST, " ": defaultHOST, " ": defaultHOST, "tcp://": defaultHTTPHost, - "tcp://:7777": "tcp://127.0.0.1:7777", - "tcp://:7777/path": "tcp://127.0.0.1:7777/path", - " tcp://:7777/path ": "tcp://127.0.0.1:7777/path", + "tcp://:7777": "tcp://localhost:7777", + "tcp://:7777/path": "tcp://localhost:7777/path", + " tcp://:7777/path ": "tcp://localhost:7777/path", "unix:///run/docker.sock": "unix:///run/docker.sock", "unix://": "unix:///var/run/docker.sock", "fd://": "fd://", "fd://something": "fd://something", + "localhost:": "tcp://localhost:2375", + "localhost:5555": "tcp://localhost:5555", + "localhost:5555/path": "tcp://localhost:5555/path", } for invalidAddr, expectedError := range invalids { - if addr, err := ParseDockerDaemonHost(defaultHTTPHost, defaultUnix, invalidAddr); err == nil || err.Error() != expectedError { + if addr, err := ParseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", invalidAddr); err == nil || err.Error() != expectedError { t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr) } } for validAddr, expectedAddr := range valids { - if addr, err := ParseDockerDaemonHost(defaultHTTPHost, defaultUnix, validAddr); err != nil || addr != expectedAddr { + if addr, err := ParseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", validAddr); err != nil || addr != expectedAddr { t.Errorf("%v -> expected %v, got (%v) addr (%v)", validAddr, expectedAddr, err, addr) } } diff --git a/runconfig/hostconfig_unix.go b/runconfig/hostconfig_unix.go index b952b172c..fd0b988eb 100644 --- a/runconfig/hostconfig_unix.go +++ b/runconfig/hostconfig_unix.go @@ -66,6 +66,12 @@ func (n NetworkMode) IsUserDefined() bool { return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() } +// IsPreDefinedNetwork indicates if a network is predefined by the daemon +func IsPreDefinedNetwork(network string) bool { + n := NetworkMode(network) + return n.IsBridge() || n.IsHost() || n.IsNone() +} + //UserDefined indicates user-created network func (n NetworkMode) UserDefined() string { if n.IsUserDefined() { diff --git a/runconfig/hostconfig_windows.go b/runconfig/hostconfig_windows.go index 19c90bf72..d21e08228 100644 --- a/runconfig/hostconfig_windows.go +++ b/runconfig/hostconfig_windows.go @@ -27,3 +27,8 @@ func MergeConfigs(config *Config, hostConfig *HostConfig) *ContainerConfigWrappe hostConfig, } } + +// IsPreDefinedNetwork indicates if a network is predefined by the daemon +func IsPreDefinedNetwork(network string) bool { + return false +} diff --git a/utils/names.go b/utils/names.go new file mode 100644 index 000000000..e09e569b1 --- /dev/null +++ b/utils/names.go @@ -0,0 +1,9 @@ +package utils + +import "regexp" + +// RestrictedNameChars collects the characters allowed to represent a name, normally used to validate container and volume names. +const RestrictedNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.-]` + +// RestrictedNamePattern is a regular expression to validate names against the collection of restricted characters. +var RestrictedNamePattern = regexp.MustCompile(`^/?` + RestrictedNameChars + `+$`) diff --git a/vendor/src/github.com/docker/libkv/store/boltdb/boltdb.go b/vendor/src/github.com/docker/libkv/store/boltdb/boltdb.go index 457ac5b6f..94d01b4b0 100644 --- a/vendor/src/github.com/docker/libkv/store/boltdb/boltdb.go +++ b/vendor/src/github.com/docker/libkv/store/boltdb/boltdb.go @@ -330,6 +330,9 @@ func (b *BoltDB) AtomicDelete(key string, previous *store.KVPair) (bool, error) } val = bucket.Get([]byte(key)) + if val == nil { + return store.ErrKeyNotFound + } dbIndex := binary.LittleEndian.Uint64(val[:libkvmetadatalen]) if dbIndex != previous.LastIndex { return store.ErrKeyModified diff --git a/vendor/src/github.com/docker/libkv/store/consul/consul.go b/vendor/src/github.com/docker/libkv/store/consul/consul.go index a023ad8d0..c7693ca44 100644 --- a/vendor/src/github.com/docker/libkv/store/consul/consul.go +++ b/vendor/src/github.com/docker/libkv/store/consul/consul.go @@ -467,6 +467,13 @@ func (s *Consul) AtomicDelete(key string, previous *store.KVPair) (bool, error) } p := &api.KVPair{Key: s.normalize(key), ModifyIndex: previous.LastIndex} + + // Extra Get operation to check on the key + _, err := s.Get(key) + if err != nil && err == store.ErrKeyNotFound { + return false, err + } + if work, _, err := s.client.KV().DeleteCAS(p, nil); err != nil { return false, err } else if !work { diff --git a/vendor/src/github.com/docker/libkv/store/etcd/etcd.go b/vendor/src/github.com/docker/libkv/store/etcd/etcd.go index ca1ec5c8b..312bb0b65 100644 --- a/vendor/src/github.com/docker/libkv/store/etcd/etcd.go +++ b/vendor/src/github.com/docker/libkv/store/etcd/etcd.go @@ -368,6 +368,10 @@ func (s *Etcd) AtomicDelete(key string, previous *store.KVPair) (bool, error) { _, err := s.client.Delete(context.Background(), s.normalize(key), delOpts) if err != nil { if etcdError, ok := err.(etcd.Error); ok { + // Key Not Found + if etcdError.Code == etcd.ErrorCodeKeyNotFound { + return false, store.ErrKeyNotFound + } // Compare failed if etcdError.Code == etcd.ErrorCodeTestFailed { return false, store.ErrKeyModified diff --git a/vendor/src/github.com/docker/libkv/store/zookeeper/zookeeper.go b/vendor/src/github.com/docker/libkv/store/zookeeper/zookeeper.go index 9e999664e..502b1c6e8 100644 --- a/vendor/src/github.com/docker/libkv/store/zookeeper/zookeeper.go +++ b/vendor/src/github.com/docker/libkv/store/zookeeper/zookeeper.go @@ -347,9 +347,15 @@ func (s *Zookeeper) AtomicDelete(key string, previous *store.KVPair) (bool, erro err := s.client.Delete(s.normalize(key), int32(previous.LastIndex)) if err != nil { + // Key not found + if err == zk.ErrNoNode { + return false, store.ErrKeyNotFound + } + // Compare failed if err == zk.ErrBadVersion { return false, store.ErrKeyModified } + // General store error return false, err } return true, nil diff --git a/vendor/src/github.com/docker/libnetwork/Makefile b/vendor/src/github.com/docker/libnetwork/Makefile index 40079bd28..b1eabf522 100644 --- a/vendor/src/github.com/docker/libnetwork/Makefile +++ b/vendor/src/github.com/docker/libnetwork/Makefile @@ -1,6 +1,6 @@ .PHONY: all all-local build build-local check check-code check-format run-tests check-local integration-tests install-deps coveralls circle-ci start-services clean SHELL=/bin/bash -build_image=libnetwork-build +build_image=libnetworkbuild dockerargs = --privileged -v $(shell pwd):/go/src/github.com/docker/libnetwork -w /go/src/github.com/docker/libnetwork container_env = -e "INSIDECONTAINER=-incontainer=true" docker = docker run --rm -it ${dockerargs} ${container_env} ${build_image} diff --git a/vendor/src/github.com/docker/libnetwork/controller.go b/vendor/src/github.com/docker/libnetwork/controller.go index e80282501..a0cb4cb5d 100644 --- a/vendor/src/github.com/docker/libnetwork/controller.go +++ b/vendor/src/github.com/docker/libnetwork/controller.go @@ -74,7 +74,6 @@ type NetworkController interface { Config() config.Config // Create a new network. The options parameter carries network specific options. - // Labels support will be added in the near future. NewNetwork(networkType, name string, options ...NetworkOption) (Network, error) // Networks returns the list of Network(s) managed by this controller. @@ -101,6 +100,9 @@ type NetworkController interface { // SandboxByID returns the Sandbox which has the passed id. If not found, a types.NotFoundError is returned. SandboxByID(id string) (Sandbox, error) + // SandboxDestroy destroys a sandbox given a container ID + SandboxDestroy(id string) error + // Stop network controller Stop() } @@ -144,6 +146,9 @@ type controller struct { watchCh chan *endpoint unWatchCh chan *endpoint svcDb map[string]svcMap + nmap map[string]*netWatch + defOsSbox osl.Sandbox + sboxOnce sync.Once sync.Mutex } @@ -179,7 +184,7 @@ func New(cfgOptions ...config.Option) (NetworkController, error) { if err := c.initDiscovery(cfg.Cluster.Watcher); err != nil { // Failing to initalize discovery is a bad situation to be in. // But it cannot fail creating the Controller - log.Debugf("Failed to Initialize Discovery : %v", err) + log.Errorf("Failed to Initialize Discovery : %v", err) } } @@ -193,6 +198,7 @@ func New(cfgOptions ...config.Option) (NetworkController, error) { } c.sandboxCleanup() + c.cleanupLocalEndpoints() if err := c.startExternalKeyListener(); err != nil { return nil, err @@ -218,7 +224,14 @@ func (c *controller) initDiscovery(watcher discovery.Watcher) error { } c.discovery = hostdiscovery.NewHostDiscovery(watcher) - return c.discovery.Watch(c.hostJoinCallback, c.hostLeaveCallback) + return c.discovery.Watch(c.activeCallback, c.hostJoinCallback, c.hostLeaveCallback) +} + +func (c *controller) activeCallback() { + ds := c.getStore(datastore.GlobalScope) + if ds != nil && !ds.Active() { + ds.RestartWatch() + } } func (c *controller) hostJoinCallback(nodes []net.IP) { @@ -357,7 +370,7 @@ func (c *controller) NewNetwork(networkType, name string, options ...NetworkOpti } }() - if err := c.addNetwork(network); err != nil { + if err = c.addNetwork(network); err != nil { return nil, err } defer func() { @@ -468,27 +481,37 @@ func (c *controller) NewSandbox(containerID string, options ...SandboxOption) (S return nil, types.BadRequestErrorf("invalid container ID") } - var existing Sandbox - look := SandboxContainerWalker(&existing, containerID) - c.WalkSandboxes(look) - if existing != nil { - return nil, types.BadRequestErrorf("container %s is already present: %v", containerID, existing) + var sb *sandbox + c.Lock() + for _, s := range c.sandboxes { + if s.containerID == containerID { + // If not a stub, then we already have a complete sandbox. + if !s.isStub { + c.Unlock() + return nil, types.BadRequestErrorf("container %s is already present: %v", containerID, s) + } + + // We already have a stub sandbox from the + // store. Make use of it so that we don't lose + // the endpoints from store but reset the + // isStub flag. + sb = s + sb.isStub = false + break + } } + c.Unlock() // Create sandbox and process options first. Key generation depends on an option - sb := &sandbox{ - id: stringid.GenerateRandomID(), - containerID: containerID, - endpoints: epHeap{}, - epPriority: map[string]int{}, - config: containerConfig{}, - controller: c, - } - // This sandbox may be using an existing osl sandbox, sharing it with another sandbox - var peerSb Sandbox - c.WalkSandboxes(SandboxKeyWalker(&peerSb, sb.Key())) - if peerSb != nil { - sb.osSbox = peerSb.(*sandbox).osSbox + if sb == nil { + sb = &sandbox{ + id: stringid.GenerateRandomID(), + containerID: containerID, + endpoints: epHeap{}, + epPriority: map[string]int{}, + config: containerConfig{}, + controller: c, + } } heap.Init(&sb.endpoints) @@ -499,6 +522,19 @@ func (c *controller) NewSandbox(containerID string, options ...SandboxOption) (S return nil, err } + if sb.config.useDefaultSandBox { + c.sboxOnce.Do(func() { + c.defOsSbox, err = osl.NewSandbox(sb.Key(), false) + }) + + if err != nil { + c.sboxOnce = sync.Once{} + return nil, fmt.Errorf("failed to create default sandbox: %v", err) + } + + sb.osSbox = c.defOsSbox + } + if sb.osSbox == nil && !sb.config.useExternalKey { if sb.osSbox, err = osl.NewSandbox(sb.Key(), !sb.config.useDefaultSandBox); err != nil { return nil, fmt.Errorf("failed to create new osl sandbox: %v", err) @@ -530,6 +566,11 @@ func (c *controller) Sandboxes() []Sandbox { list := make([]Sandbox, 0, len(c.sandboxes)) for _, s := range c.sandboxes { + // Hide stub sandboxes from libnetwork users + if s.isStub { + continue + } + list = append(list, s) } @@ -557,6 +598,26 @@ func (c *controller) SandboxByID(id string) (Sandbox, error) { return s, nil } +// SandboxDestroy destroys a sandbox given a container ID +func (c *controller) SandboxDestroy(id string) error { + var sb *sandbox + c.Lock() + for _, s := range c.sandboxes { + if s.containerID == id { + sb = s + break + } + } + c.Unlock() + + // It is not an error if sandbox is not available + if sb == nil { + return nil + } + + return sb.Delete() +} + // SandboxContainerWalker returns a Sandbox Walker function which looks for an existing Sandbox with the passed containerID func SandboxContainerWalker(out *Sandbox, containerID string) SandboxWalker { return func(sb Sandbox) bool { diff --git a/vendor/src/github.com/docker/libnetwork/datastore/datastore.go b/vendor/src/github.com/docker/libnetwork/datastore/datastore.go index 200f5709d..0ba7b6ef2 100644 --- a/vendor/src/github.com/docker/libnetwork/datastore/datastore.go +++ b/vendor/src/github.com/docker/libnetwork/datastore/datastore.go @@ -34,6 +34,10 @@ type DataStore interface { Watchable() bool // Watch for changes on a KVObject Watch(kvObject KVObject, stopCh <-chan struct{}) (<-chan KVObject, error) + // RestartWatch retriggers stopped Watches + RestartWatch() + // Active returns if the store is active + Active() bool // List returns of a list of KVObjects belonging to the parent // key. The caller must pass a KVObject of the same type as // the objects that need to be listed @@ -53,9 +57,11 @@ var ( ) type datastore struct { - scope string - store store.Store - cache *cache + scope string + store store.Store + cache *cache + watchCh chan struct{} + active bool sync.Mutex } @@ -127,7 +133,7 @@ func makeDefaultScopes() map[string]*ScopeCfg { def := make(map[string]*ScopeCfg) def[LocalScope] = &ScopeCfg{ Client: ScopeClientCfg{ - Provider: "boltdb", + Provider: string(store.BOLTDB), Address: defaultPrefix + "/local-kv.db", Config: &store.Config{ Bucket: "libnetwork", @@ -138,7 +144,8 @@ func makeDefaultScopes() map[string]*ScopeCfg { return def } -var rootChain = []string{"docker", "network", "v1.0"} +var defaultRootChain = []string{"docker", "network", "v1.0"} +var rootChain = defaultRootChain func init() { consul.Register() @@ -188,7 +195,8 @@ func ParseKey(key string) ([]string, error) { } // newClient used to connect to KV Store -func newClient(scope string, kv string, addrs string, config *store.Config, cached bool) (DataStore, error) { +func newClient(scope string, kv string, addr string, config *store.Config, cached bool) (DataStore, error) { + if cached && scope != LocalScope { return nil, fmt.Errorf("caching supported only for scope %s", LocalScope) } @@ -196,12 +204,29 @@ func newClient(scope string, kv string, addrs string, config *store.Config, cach if config == nil { config = &store.Config{} } - store, err := libkv.NewStore(store.Backend(kv), []string{addrs}, config) + + var addrs []string + + if kv == string(store.BOLTDB) { + // Parse file path + addrs = strings.Split(addr, ",") + } else { + // Parse URI + parts := strings.SplitN(addr, "/", 2) + addrs = strings.Split(parts[0], ",") + + // Add the custom prefix to the root chain + if len(parts) == 2 { + rootChain = append([]string{parts[1]}, defaultRootChain...) + } + } + + store, err := libkv.NewStore(store.Backend(kv), addrs, config) if err != nil { return nil, err } - ds := &datastore{scope: scope, store: store} + ds := &datastore{scope: scope, store: store, active: true, watchCh: make(chan struct{})} if cached { ds.cache = newCache(ds) } @@ -236,6 +261,10 @@ func (ds *datastore) Scope() string { return ds.scope } +func (ds *datastore) Active() bool { + return ds.active +} + func (ds *datastore) Watchable() bool { return ds.scope != LocalScope } @@ -256,15 +285,34 @@ func (ds *datastore) Watch(kvObject KVObject, stopCh <-chan struct{}) (<-chan KV kvoCh := make(chan KVObject) go func() { + retry_watch: + var err error + + // Make sure to get a new instance of watch channel + ds.Lock() + watchCh := ds.watchCh + ds.Unlock() + + loop: for { select { case <-stopCh: close(sCh) return case kvPair := <-kvpCh: + // If the backend KV store gets reset libkv's go routine + // for the watch can exit resulting in a nil value in + // channel. + if kvPair == nil { + ds.Lock() + ds.active = false + ds.Unlock() + break loop + } + dstO := ctor.New() - if err := dstO.SetValue(kvPair.Value); err != nil { + if err = dstO.SetValue(kvPair.Value); err != nil { log.Printf("Could not unmarshal kvpair value = %s", string(kvPair.Value)) break } @@ -273,11 +321,31 @@ func (ds *datastore) Watch(kvObject KVObject, stopCh <-chan struct{}) (<-chan KV kvoCh <- dstO } } + + // Wait on watch channel for a re-trigger when datastore becomes active + <-watchCh + + kvpCh, err = ds.store.Watch(Key(kvObject.Key()...), sCh) + if err != nil { + log.Printf("Could not watch the key %s in store: %v", Key(kvObject.Key()...), err) + } + + goto retry_watch }() return kvoCh, nil } +func (ds *datastore) RestartWatch() { + ds.Lock() + defer ds.Unlock() + + ds.active = true + watchCh := ds.watchCh + ds.watchCh = make(chan struct{}) + close(watchCh) +} + func (ds *datastore) KVStore() store.Store { return ds.store } diff --git a/vendor/src/github.com/docker/libnetwork/default_gateway.go b/vendor/src/github.com/docker/libnetwork/default_gateway.go index 98cc21f73..5d58b0617 100644 --- a/vendor/src/github.com/docker/libnetwork/default_gateway.go +++ b/vendor/src/github.com/docker/libnetwork/default_gateway.go @@ -58,6 +58,8 @@ func (sb *sandbox) setupDefaultGW(srcEp *endpoint) error { } } + createOptions = append(createOptions, CreateOptionAnonymous()) + eplen := gwEPlen if len(sb.containerID) < gwEPlen { eplen = len(sb.containerID) diff --git a/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go b/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go index ecfd5d14c..6124ccb6c 100644 --- a/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go +++ b/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go @@ -338,16 +338,11 @@ func (c *networkConfiguration) conflictsWithNetworks(id string, others []*bridge } func (d *driver) configure(option map[string]interface{}) error { - var config *configuration - var err error - - err = d.initStore(option) - if err != nil { - return err - } - - d.Lock() - defer d.Unlock() + var ( + config *configuration + err error + natChain, filterChain *iptables.ChainInfo + ) genericData, ok := option[netlabel.GenericData] if !ok || genericData == nil { @@ -375,13 +370,23 @@ func (d *driver) configure(option map[string]interface{}) error { } if config.EnableIPTables { - d.natChain, d.filterChain, err = setupIPChains(config) + natChain, filterChain, err = setupIPChains(config) if err != nil { return err } } + d.Lock() + d.natChain = natChain + d.filterChain = filterChain d.config = config + d.Unlock() + + err = d.initStore(option) + if err != nil { + return err + } + return nil } @@ -989,7 +994,7 @@ func (d *driver) DeleteEndpoint(nid, eid string) error { d.Unlock() if !ok { - return types.NotFoundErrorf("network %s does not exist", nid) + return types.InternalMaskableErrorf("network %s does not exist", nid) } if n == nil { return driverapi.ErrNoNetwork(nid) @@ -1145,7 +1150,7 @@ func (d *driver) Leave(nid, eid string) error { network, err := d.getNetwork(nid) if err != nil { - return err + return types.InternalMaskableErrorf("%s", err) } endpoint, err := network.getEndpoint(eid) diff --git a/vendor/src/github.com/docker/libnetwork/drivers/overlay/joinleave.go b/vendor/src/github.com/docker/libnetwork/drivers/overlay/joinleave.go index 1ce123cc5..997a3e77b 100644 --- a/vendor/src/github.com/docker/libnetwork/drivers/overlay/joinleave.go +++ b/vendor/src/github.com/docker/libnetwork/drivers/overlay/joinleave.go @@ -118,10 +118,18 @@ func (d *driver) Leave(nid, eid string) error { return fmt.Errorf("could not find network with id %s", nid) } - d.notifyCh <- ovNotify{ - action: "leave", - nid: nid, - eid: eid, + ep := n.endpoint(eid) + + if ep == nil { + return types.InternalMaskableErrorf("could not find endpoint with id %s", eid) + } + + if d.notifyCh != nil { + d.notifyCh <- ovNotify{ + action: "leave", + nid: nid, + eid: eid, + } } n.leaveSandbox() diff --git a/vendor/src/github.com/docker/libnetwork/drivers/overlay/ov_network.go b/vendor/src/github.com/docker/libnetwork/drivers/overlay/ov_network.go index d09fd5697..e67757b4f 100644 --- a/vendor/src/github.com/docker/libnetwork/drivers/overlay/ov_network.go +++ b/vendor/src/github.com/docker/libnetwork/drivers/overlay/ov_network.go @@ -179,6 +179,7 @@ func (n *network) destroySandbox() { } } sbox.Destroy() + n.setSandbox(nil) } } @@ -193,7 +194,7 @@ func (n *network) initSubnetSandbox(s *subnet) error { if err := sbox.AddInterface(brName, "br", sbox.InterfaceOptions().Address(s.gwIP), sbox.InterfaceOptions().Bridge(true)); err != nil { - return fmt.Errorf("bridge creation in sandbox failed for subnet %q: %v", s.subnetIP.IP.String(), err) + return fmt.Errorf("bridge creation in sandbox failed for subnet %q: %v", s.subnetIP.String(), err) } vxlanName, err := createVxlan(n.vxlanID(s)) @@ -203,7 +204,7 @@ func (n *network) initSubnetSandbox(s *subnet) error { if err := sbox.AddInterface(vxlanName, "vxlan", sbox.InterfaceOptions().Master(brName)); err != nil { - return fmt.Errorf("vxlan interface creation failed for subnet %q: %v", s.subnetIP.IP.String(), err) + return fmt.Errorf("vxlan interface creation failed for subnet %q: %v", s.subnetIP.String(), err) } n.Lock() diff --git a/vendor/src/github.com/docker/libnetwork/drivers/overlay/ov_serf.go b/vendor/src/github.com/docker/libnetwork/drivers/overlay/ov_serf.go index 894cb582c..a10bbf8e3 100644 --- a/vendor/src/github.com/docker/libnetwork/drivers/overlay/ov_serf.go +++ b/vendor/src/github.com/docker/libnetwork/drivers/overlay/ov_serf.go @@ -48,6 +48,7 @@ func (d *driver) serfInit() error { config.UserQuiescentPeriod = 50 * time.Millisecond config.LogOutput = &logWriter{} + config.MemberlistConfig.LogOutput = config.LogOutput s, err := serf.Create(config) if err != nil { diff --git a/vendor/src/github.com/docker/libnetwork/drivers/overlay/overlay.go b/vendor/src/github.com/docker/libnetwork/drivers/overlay/overlay.go index c190d3986..982310d7a 100644 --- a/vendor/src/github.com/docker/libnetwork/drivers/overlay/overlay.go +++ b/vendor/src/github.com/docker/libnetwork/drivers/overlay/overlay.go @@ -2,6 +2,7 @@ package overlay import ( "fmt" + "net" "sync" "github.com/Sirupsen/logrus" @@ -120,8 +121,30 @@ func (d *driver) Type() string { return networkType } +func validateSelf(node string) error { + advIP := net.ParseIP(node) + if advIP == nil { + return fmt.Errorf("invalid self address (%s)", node) + } + + addrs, err := net.InterfaceAddrs() + if err != nil { + return fmt.Errorf("Unable to get interface addresses %v", err) + } + for _, addr := range addrs { + ip, _, err := net.ParseCIDR(addr.String()) + if err == nil && ip.Equal(advIP) { + return nil + } + } + return fmt.Errorf("Multi-Host overlay networking requires cluster-advertise(%s) to be configured with a local ip-address that is reachable within the cluster", advIP.String()) +} + func (d *driver) nodeJoin(node string, self bool) { if self && !d.isSerfAlive() { + if err := validateSelf(node); err != nil { + logrus.Errorf("%s", err.Error()) + } d.Lock() d.bindAddress = node d.Unlock() diff --git a/vendor/src/github.com/docker/libnetwork/endpoint.go b/vendor/src/github.com/docker/libnetwork/endpoint.go index 40459369f..de7b652dc 100644 --- a/vendor/src/github.com/docker/libnetwork/endpoint.go +++ b/vendor/src/github.com/docker/libnetwork/endpoint.go @@ -57,6 +57,7 @@ type endpoint struct { joinInfo *endpointJoinInfo sandboxID string exposedPorts []types.TransportPort + anonymous bool generic map[string]interface{} joinLeaveDone chan struct{} dbIndex uint64 @@ -77,6 +78,7 @@ func (ep *endpoint) MarshalJSON() ([]byte, error) { epMap["generic"] = ep.generic } epMap["sandbox"] = ep.sandboxID + epMap["anonymous"] = ep.anonymous return json.Marshal(epMap) } @@ -104,6 +106,55 @@ func (ep *endpoint) UnmarshalJSON(b []byte) (err error) { if v, ok := epMap["generic"]; ok { ep.generic = v.(map[string]interface{}) + + if opt, ok := ep.generic[netlabel.PortMap]; ok { + pblist := []types.PortBinding{} + + for i := 0; i < len(opt.([]interface{})); i++ { + pb := types.PortBinding{} + tmp := opt.([]interface{})[i].(map[string]interface{}) + + bytes, err := json.Marshal(tmp) + if err != nil { + log.Error(err) + break + } + err = json.Unmarshal(bytes, &pb) + if err != nil { + log.Error(err) + break + } + pblist = append(pblist, pb) + } + ep.generic[netlabel.PortMap] = pblist + } + + if opt, ok := ep.generic[netlabel.ExposedPorts]; ok { + tplist := []types.TransportPort{} + + for i := 0; i < len(opt.([]interface{})); i++ { + tp := types.TransportPort{} + tmp := opt.([]interface{})[i].(map[string]interface{}) + + bytes, err := json.Marshal(tmp) + if err != nil { + log.Error(err) + break + } + err = json.Unmarshal(bytes, &tp) + if err != nil { + log.Error(err) + break + } + tplist = append(tplist, tp) + } + ep.generic[netlabel.ExposedPorts] = tplist + + } + } + + if v, ok := epMap["anonymous"]; ok { + ep.anonymous = v.(bool) } return nil } @@ -122,6 +173,7 @@ func (ep *endpoint) CopyTo(o datastore.KVObject) error { dstEp.sandboxID = ep.sandboxID dstEp.dbIndex = ep.dbIndex dstEp.dbExists = ep.dbExists + dstEp.anonymous = ep.anonymous if ep.iface != nil { dstEp.iface = &endpointInterface{} @@ -161,6 +213,12 @@ func (ep *endpoint) Network() string { return ep.network.name } +func (ep *endpoint) isAnonymous() bool { + ep.Lock() + defer ep.Unlock() + return ep.anonymous +} + // endpoint Key structure : endpoint/network-id/endpoint-id func (ep *endpoint) Key() []string { if ep.network == nil { @@ -332,7 +390,7 @@ func (ep *endpoint) sbJoin(sbox Sandbox, options ...EndpointOption) error { if ip := ep.getFirstInterfaceAddress(); ip != nil { address = ip.String() } - if err = sb.updateHostsFile(address, network.getSvcRecords()); err != nil { + if err = sb.updateHostsFile(address, network.getSvcRecords(ep)); err != nil { return err } @@ -373,6 +431,51 @@ func (ep *endpoint) sbJoin(sbox Sandbox, options ...EndpointOption) error { return sb.clearDefaultGW() } +func (ep *endpoint) rename(name string) error { + var err error + n := ep.getNetwork() + if n == nil { + return fmt.Errorf("network not connected for ep %q", ep.name) + } + + n.getController().Lock() + netWatch, ok := n.getController().nmap[n.ID()] + n.getController().Unlock() + + if !ok { + return fmt.Errorf("watch null for network %q", n.Name()) + } + + n.updateSvcRecord(ep, n.getController().getLocalEps(netWatch), false) + + oldName := ep.name + ep.name = name + + n.updateSvcRecord(ep, n.getController().getLocalEps(netWatch), true) + defer func() { + if err != nil { + n.updateSvcRecord(ep, n.getController().getLocalEps(netWatch), false) + ep.name = oldName + n.updateSvcRecord(ep, n.getController().getLocalEps(netWatch), true) + } + }() + + // Update the store with the updated name + if err = n.getController().updateToStore(ep); err != nil { + return err + } + // After the name change do a dummy endpoint count update to + // trigger the service record update in the peer nodes + + // Ignore the error because updateStore fail for EpCnt is a + // benign error. Besides there is no meaningful recovery that + // we can do. When the cluster recovers subsequent EpCnt update + // will force the peers to get the correct EP name. + n.getEpCnt().updateStore() + + return err +} + func (ep *endpoint) hasInterface(iName string) bool { ep.Lock() defer ep.Unlock() @@ -425,33 +528,36 @@ func (ep *endpoint) sbLeave(sbox Sandbox, options ...EndpointOption) error { ep.processOptions(options...) - ep.Lock() - ep.sandboxID = "" - ep.network = n - ep.Unlock() - - if err := n.getController().updateToStore(ep); err != nil { - ep.Lock() - ep.sandboxID = sid - ep.Unlock() - return err - } - d, err := n.driver() if err != nil { return fmt.Errorf("failed to leave endpoint: %v", err) } + ep.Lock() + ep.sandboxID = "" + ep.network = n + ep.Unlock() + if err := d.Leave(n.id, ep.id); err != nil { - return err + if _, ok := err.(types.MaskableError); !ok { + log.Warnf("driver error disconnecting container %s : %v", ep.name, err) + } } if err := sb.clearNetworkResources(ep); err != nil { + log.Warnf("Could not cleanup network resources on container %s disconnect: %v", ep.name, err) + } + + // Update the store about the sandbox detach only after we + // have completed sb.clearNetworkresources above to avoid + // spurious logs when cleaning up the sandbox when the daemon + // ungracefully exits and restarts before completing sandbox + // detach but after store has been updated. + if err := n.getController().updateToStore(ep); err != nil { return err } - // unwatch for service records - n.getController().unWatchSvcRecord(ep) + sb.deleteHostsEntries(n.getSvcRecords(ep)) if sb.needDefaultGW() { ep := sb.getEPwithoutGateway() @@ -484,17 +590,6 @@ func (ep *endpoint) Delete() error { } ep.Unlock() - if err = n.getEpCnt().DecEndpointCnt(); err != nil { - return err - } - defer func() { - if err != nil { - if e := n.getEpCnt().IncEndpointCnt(); e != nil { - log.Warnf("failed to update network %s : %v", n.name, e) - } - } - }() - if err = n.getController().deleteFromStore(ep); err != nil { return err } @@ -507,6 +602,20 @@ func (ep *endpoint) Delete() error { } }() + if err = n.getEpCnt().DecEndpointCnt(); err != nil { + return err + } + defer func() { + if err != nil { + if e := n.getEpCnt().IncEndpointCnt(); e != nil { + log.Warnf("failed to update network %s : %v", n.name, e) + } + } + }() + + // unwatch for service records + n.getController().unWatchSvcRecord(ep) + if err = ep.deleteEndpoint(); err != nil { return err } @@ -532,7 +641,10 @@ func (ep *endpoint) deleteEndpoint() error { if _, ok := err.(types.ForbiddenError); ok { return err } - log.Warnf("driver error deleting endpoint %s : %v", name, err) + + if _, ok := err.(types.MaskableError); !ok { + log.Warnf("driver error deleting endpoint %s : %v", name, err) + } } return nil @@ -596,6 +708,14 @@ func CreateOptionPortMapping(portBindings []types.PortBinding) EndpointOption { } } +// CreateOptionAnonymous function returns an option setter for setting +// this endpoint as anonymous +func CreateOptionAnonymous() EndpointOption { + return func(ep *endpoint) { + ep.anonymous = true + } +} + // JoinOptionPriority function returns an option setter for priority option to // be passed to the endpoint.Join() method. func JoinOptionPriority(ep Endpoint, prio int) EndpointOption { @@ -704,3 +824,25 @@ func (ep *endpoint) releaseAddress() { } } } + +func (c *controller) cleanupLocalEndpoints() { + nl, err := c.getNetworksForScope(datastore.LocalScope) + if err != nil { + log.Warnf("Could not get list of networks during endpoint cleanup: %v", err) + return + } + + for _, n := range nl { + epl, err := n.getEndpointsFromStore() + if err != nil { + log.Warnf("Could not get list of endpoints in network %s during endpoint cleanup: %v", n.name, err) + continue + } + + for _, ep := range epl { + if err := ep.Delete(); err != nil { + log.Warnf("Could not delete local endpoint %s during endpoint cleanup: %v", ep.name, err) + } + } + } +} diff --git a/vendor/src/github.com/docker/libnetwork/endpoint_cnt.go b/vendor/src/github.com/docker/libnetwork/endpoint_cnt.go index 550a2a3cf..507de393b 100644 --- a/vendor/src/github.com/docker/libnetwork/endpoint_cnt.go +++ b/vendor/src/github.com/docker/libnetwork/endpoint_cnt.go @@ -108,6 +108,21 @@ func (ec *endpointCnt) EndpointCnt() uint64 { return ec.Count } +func (ec *endpointCnt) updateStore() error { + store := ec.n.getController().getStore(ec.DataScope()) + if store == nil { + return fmt.Errorf("store not found for scope %s on endpoint count update", ec.DataScope()) + } + for { + if err := ec.n.getController().updateToStore(ec); err == nil || err != datastore.ErrKeyModified { + return err + } + if err := store.GetObject(datastore.Key(ec.Key()...), ec); err != nil { + return fmt.Errorf("could not update the kvobject to latest on endpoint count update: %v", err) + } + } +} + func (ec *endpointCnt) atomicIncDecEpCnt(inc bool) error { retry: ec.Lock() diff --git a/vendor/src/github.com/docker/libnetwork/endpoint_info.go b/vendor/src/github.com/docker/libnetwork/endpoint_info.go index 3ca6b2b83..db23eb738 100644 --- a/vendor/src/github.com/docker/libnetwork/endpoint_info.go +++ b/vendor/src/github.com/docker/libnetwork/endpoint_info.go @@ -163,6 +163,17 @@ func (ep *endpoint) Info() EndpointInfo { } func (ep *endpoint) DriverInfo() (map[string]interface{}, error) { + ep, err := ep.retrieveFromStore() + if err != nil { + return nil, err + } + + if sb, ok := ep.getSandbox(); ok { + if gwep := sb.getEndpointInGWNetwork(); gwep != nil && gwep.ID() != ep.ID() { + return gwep.DriverInfo() + } + } + n, err := ep.getNetworkFromStore() if err != nil { return nil, fmt.Errorf("could not find network in store for driver info: %v", err) @@ -317,3 +328,11 @@ func (ep *endpoint) SetGatewayIPv6(gw6 net.IP) error { ep.joinInfo.gw6 = types.GetIPCopy(gw6) return nil } + +func (ep *endpoint) retrieveFromStore() (*endpoint, error) { + n, err := ep.getNetworkFromStore() + if err != nil { + return nil, fmt.Errorf("could not find network in store to get latest endpoint %s: %v", ep.Name(), err) + } + return n.getEndpointFromStore(ep.ID()) +} diff --git a/vendor/src/github.com/docker/libnetwork/etchosts/etchosts.go b/vendor/src/github.com/docker/libnetwork/etchosts/etchosts.go index 466143bae..92597b71b 100644 --- a/vendor/src/github.com/docker/libnetwork/etchosts/etchosts.go +++ b/vendor/src/github.com/docker/libnetwork/etchosts/etchosts.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "regexp" + "sync" ) // Record Structure for a single host record @@ -21,14 +22,47 @@ func (r Record) WriteTo(w io.Writer) (int64, error) { return int64(n), err } -// Default hosts config records slice -var defaultContent = []Record{ - {Hosts: "localhost", IP: "127.0.0.1"}, - {Hosts: "localhost ip6-localhost ip6-loopback", IP: "::1"}, - {Hosts: "ip6-localnet", IP: "fe00::0"}, - {Hosts: "ip6-mcastprefix", IP: "ff00::0"}, - {Hosts: "ip6-allnodes", IP: "ff02::1"}, - {Hosts: "ip6-allrouters", IP: "ff02::2"}, +var ( + // Default hosts config records slice + defaultContent = []Record{ + {Hosts: "localhost", IP: "127.0.0.1"}, + {Hosts: "localhost ip6-localhost ip6-loopback", IP: "::1"}, + {Hosts: "ip6-localnet", IP: "fe00::0"}, + {Hosts: "ip6-mcastprefix", IP: "ff00::0"}, + {Hosts: "ip6-allnodes", IP: "ff02::1"}, + {Hosts: "ip6-allrouters", IP: "ff02::2"}, + } + + // A cache of path level locks for synchronizing /etc/hosts + // updates on a file level + pathMap = make(map[string]*sync.Mutex) + + // A package level mutex to synchronize the cache itself + pathMutex sync.Mutex +) + +func pathLock(path string) func() { + pathMutex.Lock() + defer pathMutex.Unlock() + + pl, ok := pathMap[path] + if !ok { + pl = &sync.Mutex{} + pathMap[path] = pl + } + + pl.Lock() + return func() { + pl.Unlock() + } +} + +// Drop drops the path string from the path cache +func Drop(path string) { + pathMutex.Lock() + defer pathMutex.Unlock() + + delete(pathMap, path) } // Build function @@ -36,6 +70,8 @@ var defaultContent = []Record{ // IP, hostname, and domainname set main record leave empty for no master record // extraContent is an array of extra host records. func Build(path, IP, hostname, domainname string, extraContent []Record) error { + defer pathLock(path)() + content := bytes.NewBuffer(nil) if IP != "" { //set main record @@ -68,6 +104,8 @@ func Build(path, IP, hostname, domainname string, extraContent []Record) error { // Add adds an arbitrary number of Records to an already existing /etc/hosts file func Add(path string, recs []Record) error { + defer pathLock(path)() + if len(recs) == 0 { return nil } @@ -95,6 +133,8 @@ func Add(path string, recs []Record) error { // Delete deletes an arbitrary number of Records already existing in /etc/hosts file func Delete(path string, recs []Record) error { + defer pathLock(path)() + if len(recs) == 0 { return nil } @@ -118,6 +158,8 @@ func Delete(path string, recs []Record) error { // IP is new IP address // hostname is hostname to search for to replace IP func Update(path, IP, hostname string) error { + defer pathLock(path)() + old, err := ioutil.ReadFile(path) if err != nil { return err diff --git a/vendor/src/github.com/docker/libnetwork/hostdiscovery/hostdiscovery.go b/vendor/src/github.com/docker/libnetwork/hostdiscovery/hostdiscovery.go index cb29e4503..3fe2a64a1 100644 --- a/vendor/src/github.com/docker/libnetwork/hostdiscovery/hostdiscovery.go +++ b/vendor/src/github.com/docker/libnetwork/hostdiscovery/hostdiscovery.go @@ -34,7 +34,7 @@ func NewHostDiscovery(watcher discovery.Watcher) HostDiscovery { return &hostDiscovery{watcher: watcher, nodes: mapset.NewSet(), stopChan: make(chan struct{})} } -func (h *hostDiscovery) Watch(joinCallback JoinCallback, leaveCallback LeaveCallback) error { +func (h *hostDiscovery) Watch(activeCallback ActiveCallback, joinCallback JoinCallback, leaveCallback LeaveCallback) error { h.Lock() d := h.watcher h.Unlock() @@ -42,15 +42,16 @@ func (h *hostDiscovery) Watch(joinCallback JoinCallback, leaveCallback LeaveCall return types.BadRequestErrorf("invalid discovery watcher") } discoveryCh, errCh := d.Watch(h.stopChan) - go h.monitorDiscovery(discoveryCh, errCh, joinCallback, leaveCallback) + go h.monitorDiscovery(discoveryCh, errCh, activeCallback, joinCallback, leaveCallback) return nil } -func (h *hostDiscovery) monitorDiscovery(ch <-chan discovery.Entries, errCh <-chan error, joinCallback JoinCallback, leaveCallback LeaveCallback) { +func (h *hostDiscovery) monitorDiscovery(ch <-chan discovery.Entries, errCh <-chan error, + activeCallback ActiveCallback, joinCallback JoinCallback, leaveCallback LeaveCallback) { for { select { case entries := <-ch: - h.processCallback(entries, joinCallback, leaveCallback) + h.processCallback(entries, activeCallback, joinCallback, leaveCallback) case err := <-errCh: if err != nil { log.Errorf("discovery error: %v", err) @@ -71,7 +72,8 @@ func (h *hostDiscovery) StopDiscovery() error { return nil } -func (h *hostDiscovery) processCallback(entries discovery.Entries, joinCallback JoinCallback, leaveCallback LeaveCallback) { +func (h *hostDiscovery) processCallback(entries discovery.Entries, + activeCallback ActiveCallback, joinCallback JoinCallback, leaveCallback LeaveCallback) { updated := hosts(entries) h.Lock() existing := h.nodes @@ -79,6 +81,7 @@ func (h *hostDiscovery) processCallback(entries discovery.Entries, joinCallback h.nodes = updated h.Unlock() + activeCallback() if len(added) > 0 { joinCallback(added) } diff --git a/vendor/src/github.com/docker/libnetwork/hostdiscovery/hostdiscovery_api.go b/vendor/src/github.com/docker/libnetwork/hostdiscovery/hostdiscovery_api.go index 5be520fca..b9c17250c 100644 --- a/vendor/src/github.com/docker/libnetwork/hostdiscovery/hostdiscovery_api.go +++ b/vendor/src/github.com/docker/libnetwork/hostdiscovery/hostdiscovery_api.go @@ -5,13 +5,16 @@ import "net" // JoinCallback provides a callback event for new node joining the cluster type JoinCallback func(entries []net.IP) +// ActiveCallback provides a callback event for active discovery event +type ActiveCallback func() + // LeaveCallback provides a callback event for node leaving the cluster type LeaveCallback func(entries []net.IP) // HostDiscovery primary interface type HostDiscovery interface { //Watch Node join and leave cluster events - Watch(joinCallback JoinCallback, leaveCallback LeaveCallback) error + Watch(activeCallback ActiveCallback, joinCallback JoinCallback, leaveCallback LeaveCallback) error // StopDiscovery stops the discovery perocess StopDiscovery() error // Fetch returns a list of host IPs that are currently discovered diff --git a/vendor/src/github.com/docker/libnetwork/ipam/allocator.go b/vendor/src/github.com/docker/libnetwork/ipam/allocator.go index 119031da8..bec7c7534 100644 --- a/vendor/src/github.com/docker/libnetwork/ipam/allocator.go +++ b/vendor/src/github.com/docker/libnetwork/ipam/allocator.go @@ -84,10 +84,6 @@ func (a *Allocator) refresh(as string) error { return nil } - if err := a.updateBitMasks(aSpace); err != nil { - return fmt.Errorf("error updating bit masks during init: %v", err) - } - a.Lock() a.addrSpaces[as] = aSpace a.Unlock() @@ -199,7 +195,7 @@ func (a *Allocator) getAddrSpace(as string) (*addrSpace, error) { defer a.Unlock() aSpace, ok := a.addrSpaces[as] if !ok { - return nil, types.BadRequestErrorf("cannot find address space %s (most likey the backing datastore is not configured)", as) + return nil, types.BadRequestErrorf("cannot find address space %s (most likely the backing datastore is not configured)", as) } return aSpace, nil } @@ -250,11 +246,6 @@ func (a *Allocator) insertBitMask(key SubnetKey, pool *net.IPNet) error { ones, bits := pool.Mask.Size() numAddresses := uint64(1 << uint(bits-ones)) - if ipVer == v4 { - // Do not let broadcast address be reserved - numAddresses-- - } - // Allow /64 subnet if ipVer == v6 && numAddresses == 0 { numAddresses-- @@ -270,6 +261,11 @@ func (a *Allocator) insertBitMask(key SubnetKey, pool *net.IPNet) error { // Do the same for IPv6 so that bridge ip starts with XXXX...::1 h.Set(0) + // Do not let broadcast address be reserved + if ipVer == v4 { + h.Set(numAddresses - 1) + } + a.Lock() a.addresses[key] = h a.Unlock() diff --git a/vendor/src/github.com/docker/libnetwork/ipams/remote/remote.go b/vendor/src/github.com/docker/libnetwork/ipams/remote/remote.go index f9df525ce..3aefd430a 100644 --- a/vendor/src/github.com/docker/libnetwork/ipams/remote/remote.go +++ b/vendor/src/github.com/docker/libnetwork/ipams/remote/remote.go @@ -78,7 +78,11 @@ func (a *allocator) ReleasePool(poolID string) error { // RequestAddress requests an address from the address pool func (a *allocator) RequestAddress(poolID string, address net.IP, options map[string]string) (*net.IPNet, map[string]string, error) { - var prefAddress string + var ( + prefAddress string + retAddress *net.IPNet + err error + ) if address != nil { prefAddress = address.String() } @@ -87,7 +91,9 @@ func (a *allocator) RequestAddress(poolID string, address net.IP, options map[st if err := a.call("RequestAddress", req, res); err != nil { return nil, nil, err } - retAddress, err := types.ParseCIDR(res.Address) + if res.Address != "" { + retAddress, err = types.ParseCIDR(res.Address) + } return retAddress, res.Data, err } diff --git a/vendor/src/github.com/docker/libnetwork/ipamutils/utils_linux.go b/vendor/src/github.com/docker/libnetwork/ipamutils/utils_linux.go index d8c9eb8a1..9706cf39c 100644 --- a/vendor/src/github.com/docker/libnetwork/ipamutils/utils_linux.go +++ b/vendor/src/github.com/docker/libnetwork/ipamutils/utils_linux.go @@ -6,6 +6,7 @@ import ( "net" "github.com/docker/libnetwork/netutils" + "github.com/docker/libnetwork/osl" "github.com/docker/libnetwork/resolvconf" "github.com/vishvananda/netlink" ) @@ -21,6 +22,8 @@ func ElectInterfaceAddresses(name string) (*net.IPNet, []*net.IPNet, error) { err error ) + defer osl.InitOSContext()() + link, _ := netlink.LinkByName(name) if link != nil { v4addr, err := netlink.AddrList(link, netlink.FAMILY_V4) diff --git a/vendor/src/github.com/docker/libnetwork/netutils/utils.go b/vendor/src/github.com/docker/libnetwork/netutils/utils.go index 7d90b1f52..a1ead3618 100644 --- a/vendor/src/github.com/docker/libnetwork/netutils/utils.go +++ b/vendor/src/github.com/docker/libnetwork/netutils/utils.go @@ -161,8 +161,8 @@ func GenerateIfaceName(prefix string, len int) (string, error) { if err != nil { continue } - if _, err := net.InterfaceByName(name); err != nil { - if strings.Contains(err.Error(), "no such") { + if _, err := netlink.LinkByName(name); err != nil { + if strings.Contains(err.Error(), "not found") { return name, nil } return "", err diff --git a/vendor/src/github.com/docker/libnetwork/network.go b/vendor/src/github.com/docker/libnetwork/network.go index b9a2d5a1b..258460284 100644 --- a/vendor/src/github.com/docker/libnetwork/network.go +++ b/vendor/src/github.com/docker/libnetwork/network.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "strconv" + "strings" "sync" log "github.com/Sirupsen/logrus" @@ -33,7 +34,6 @@ type Network interface { // Create a new endpoint to this network symbolically identified by the // specified unique name. The options parameter carry driver specific options. - // Labels support will be added in the near future. CreateEndpoint(name string, options ...EndpointOption) (Endpoint, error) // Delete the network. @@ -58,7 +58,7 @@ type Network interface { // NetworkInfo returns some configuration and operational information about the network type NetworkInfo interface { IpamConfig() (string, []*IpamConf, []*IpamConf) - Labels() map[string]string + DriverOptions() map[string]string Scope() string } @@ -402,7 +402,7 @@ func (n *network) UnmarshalJSON(b []byte) (err error) { if v, ok := netMap["generic"]; ok { n.generic = v.(map[string]interface{}) - // Restore labels in their map[string]string form + // Restore opts in their map[string]string form if v, ok := n.generic[netlabel.GenericData]; ok { var lmap map[string]string ba, err := json.Marshal(v) @@ -484,19 +484,19 @@ func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ip } } -// NetworkOptionLabels function returns an option setter for any parameter described by a map -func NetworkOptionLabels(labels map[string]string) NetworkOption { +// NetworkOptionDriverOpts function returns an option setter for any parameter described by a map +func NetworkOptionDriverOpts(opts map[string]string) NetworkOption { return func(n *network) { if n.generic == nil { n.generic = make(map[string]interface{}) } - if labels == nil { - labels = make(map[string]string) + if opts == nil { + opts = make(map[string]string) } // Store the options - n.generic[netlabel.GenericData] = labels + n.generic[netlabel.GenericData] = opts // Decode and store the endpoint options of libnetwork interest - if val, ok := labels[netlabel.EnableIPv6]; ok { + if val, ok := opts[netlabel.EnableIPv6]; ok { var err error if n.enableIPv6, err = strconv.ParseBool(val); err != nil { log.Warnf("Failed to parse %s' value: %s (%s)", netlabel.EnableIPv6, val, err.Error()) @@ -686,6 +686,14 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi } }() + // Watch for service records + n.getController().watchSvcRecord(ep) + defer func() { + if err != nil { + n.getController().unWatchSvcRecord(ep) + } + }() + // Increment endpoint count to indicate completion of endpoint addition if err = n.getEpCnt().IncEndpointCnt(); err != nil { return nil, err @@ -754,6 +762,10 @@ func (n *network) EndpointByID(id string) (Endpoint, error) { } func (n *network) updateSvcRecord(ep *endpoint, localEps []*endpoint, isAdd bool) { + if ep.isAnonymous() { + return + } + c := n.getController() sr, ok := c.svcDb[n.ID()] if !ok { @@ -765,6 +777,12 @@ func (n *network) updateSvcRecord(ep *endpoint, localEps []*endpoint, isAdd bool var recs []etchosts.Record if iface := ep.Iface(); iface.Address() != nil { if isAdd { + // If we already have this endpoint in service db just return + if _, ok := sr[ep.Name()]; ok { + n.Unlock() + return + } + sr[ep.Name()] = iface.Address().IP sr[ep.Name()+"."+n.name] = iface.Address().IP } else { @@ -790,8 +808,12 @@ func (n *network) updateSvcRecord(ep *endpoint, localEps []*endpoint, isAdd bool } var sbList []*sandbox - for _, ep := range localEps { - if sb, hasSandbox := ep.getSandbox(); hasSandbox { + for _, lEp := range localEps { + if ep.ID() == lEp.ID() { + continue + } + + if sb, hasSandbox := lEp.getSandbox(); hasSandbox { sbList = append(sbList, sb) } } @@ -805,7 +827,7 @@ func (n *network) updateSvcRecord(ep *endpoint, localEps []*endpoint, isAdd bool } } -func (n *network) getSvcRecords() []etchosts.Record { +func (n *network) getSvcRecords(ep *endpoint) []etchosts.Record { n.Lock() defer n.Unlock() @@ -813,6 +835,10 @@ func (n *network) getSvcRecords() []etchosts.Record { sr, _ := n.ctrlr.svcDb[n.id] for h, ip := range sr { + if ep != nil && strings.Split(h, ".")[0] == ep.Name() { + continue + } + recs = append(recs, etchosts.Record{ Hosts: h, IP: ip.String(), @@ -1056,7 +1082,7 @@ func (n *network) Info() NetworkInfo { return n } -func (n *network) Labels() map[string]string { +func (n *network) DriverOptions() map[string]string { n.Lock() defer n.Unlock() if n.generic != nil { diff --git a/vendor/src/github.com/docker/libnetwork/sandbox.go b/vendor/src/github.com/docker/libnetwork/sandbox.go index 454ed5771..41074641d 100644 --- a/vendor/src/github.com/docker/libnetwork/sandbox.go +++ b/vendor/src/github.com/docker/libnetwork/sandbox.go @@ -34,6 +34,8 @@ type Sandbox interface { Refresh(options ...SandboxOption) error // SetKey updates the Sandbox Key SetKey(key string) error + // Rename changes the name of all attached Endpoints + Rename(name string) error // Delete destroys this container after detaching it from all connected endpoints. Delete() error } @@ -66,6 +68,8 @@ type sandbox struct { joinLeaveDone chan struct{} dbIndex uint64 dbExists bool + isStub bool + inDelete bool sync.Mutex } @@ -146,9 +150,26 @@ func (sb *sandbox) Statistics() (map[string]*types.InterfaceStatistics, error) { } func (sb *sandbox) Delete() error { + sb.Lock() + if sb.inDelete { + sb.Unlock() + return types.ForbiddenErrorf("another sandbox delete in progress") + } + // Set the inDelete flag. This will ensure that we don't + // update the store until we have completed all the endpoint + // leaves and deletes. And when endpoint leaves and deletes + // are completed then we can finally delete the sandbox object + // altogether from the data store. If the daemon exits + // ungracefully in the middle of a sandbox delete this way we + // will have all the references to the endpoints in the + // sandbox so that we can clean them up when we restart + sb.inDelete = true + sb.Unlock() + c := sb.controller // Detach from all endpoints + retain := false for _, ep := range sb.getConnectedEndpoints() { // endpoint in the Gateway network will be cleaned up // when when sandbox no longer needs external connectivity @@ -157,15 +178,27 @@ func (sb *sandbox) Delete() error { } if err := ep.Leave(sb); err != nil { + retain = true log.Warnf("Failed detaching sandbox %s from endpoint %s: %v\n", sb.ID(), ep.ID(), err) } if err := ep.Delete(); err != nil { + retain = true log.Warnf("Failed deleting endpoint %s: %v\n", ep.ID(), err) } } - if sb.osSbox != nil { + if retain { + sb.Lock() + sb.inDelete = false + sb.Unlock() + return fmt.Errorf("could not cleanup all the endpoints in container %s / sandbox %s", sb.containerID, sb.id) + } + // Container is going away. Path cache in etchosts is most + // likely not required any more. Drop it. + etchosts.Drop(sb.config.hostsPath) + + if sb.osSbox != nil && !sb.config.useDefaultSandBox { sb.osSbox.Destroy() } @@ -180,6 +213,30 @@ func (sb *sandbox) Delete() error { return nil } +func (sb *sandbox) Rename(name string) error { + var err error + + for _, ep := range sb.getConnectedEndpoints() { + if ep.endpointInGWNetwork() { + continue + } + + oldName := ep.Name() + lEp := ep + if err = ep.rename(name); err != nil { + break + } + + defer func() { + if err != nil { + lEp.rename(oldName) + } + }() + } + + return err +} + func (sb *sandbox) Refresh(options ...SandboxOption) error { // Store connected endpoints epList := sb.getConnectedEndpoints() @@ -355,6 +412,10 @@ func releaseOSSboxResources(osSbox osl.Sandbox, ep *endpoint) { joinInfo := ep.joinInfo ep.Unlock() + if joinInfo == nil { + return + } + // Remove non-interface routes. for _, r := range joinInfo.StaticRoutes { if err := osSbox.RemoveStaticRoute(r); err != nil { @@ -386,6 +447,7 @@ func (sb *sandbox) populateNetworkResources(ep *endpoint) error { sb.Unlock() return nil } + inDelete := sb.inDelete sb.Unlock() ep.Lock() @@ -418,14 +480,23 @@ func (sb *sandbox) populateNetworkResources(ep *endpoint) error { for _, gwep := range sb.getConnectedEndpoints() { if len(gwep.Gateway()) > 0 { if gwep != ep { - return nil + break } if err := sb.updateGateway(gwep); err != nil { return err } } } - return sb.storeUpdate() + + // Only update the store if we did not come here as part of + // sandbox delete. If we came here as part of delete then do + // not bother updating the store. The sandbox object will be + // deleted anyway + if !inDelete { + return sb.storeUpdate() + } + + return nil } func (sb *sandbox) clearNetworkResources(origEp *endpoint) error { @@ -437,6 +508,7 @@ func (sb *sandbox) clearNetworkResources(origEp *endpoint) error { sb.Lock() osSbox := sb.osSbox + inDelete := sb.inDelete sb.Unlock() if osSbox != nil { releaseOSSboxResources(osSbox, ep) @@ -480,7 +552,15 @@ func (sb *sandbox) clearNetworkResources(origEp *endpoint) error { sb.updateGateway(gwepAfter) } - return sb.storeUpdate() + // Only update the store if we did not come here as part of + // sandbox delete. If we came here as part of delete then do + // not bother updating the store. The sandbox object will be + // deleted anyway + if !inDelete { + return sb.storeUpdate() + } + + return nil } const ( diff --git a/vendor/src/github.com/docker/libnetwork/sandbox_store.go b/vendor/src/github.com/docker/libnetwork/sandbox_store.go index 2a86cbc4c..61eda408e 100644 --- a/vendor/src/github.com/docker/libnetwork/sandbox_store.go +++ b/vendor/src/github.com/docker/libnetwork/sandbox_store.go @@ -3,6 +3,7 @@ package libnetwork import ( "container/heap" "encoding/json" + "sync" "github.com/Sirupsen/logrus" "github.com/docker/libnetwork/datastore" @@ -119,11 +120,20 @@ func (sbs *sbState) DataScope() string { func (sb *sandbox) storeUpdate() error { sbs := &sbState{ - c: sb.controller, - ID: sb.id, + c: sb.controller, + ID: sb.id, + Cid: sb.containerID, } +retry: + sbs.Eps = nil for _, ep := range sb.getConnectedEndpoints() { + // If the endpoint is not persisted then do not add it to + // the sandbox checkpoint + if ep.Skip() { + continue + } + eps := epState{ Nid: ep.getNetwork().ID(), Eid: ep.ID(), @@ -132,7 +142,16 @@ func (sb *sandbox) storeUpdate() error { sbs.Eps = append(sbs.Eps, eps) } - return sb.controller.updateToStore(sbs) + err := sb.controller.updateToStore(sbs) + if err == datastore.ErrKeyModified { + // When we get ErrKeyModified it is sufficient to just + // go back and retry. No need to get the object from + // the store because we always regenerate the store + // state from in memory sandbox state + goto retry + } + + return err } func (sb *sandbox) storeDelete() error { @@ -175,6 +194,7 @@ func (c *controller) sandboxCleanup() { endpoints: epHeap{}, epPriority: map[string]int{}, dbIndex: sbs.dbIndex, + isStub: true, dbExists: true, } @@ -184,26 +204,28 @@ func (c *controller) sandboxCleanup() { continue } + c.Lock() + c.sandboxes[sb.id] = sb + c.Unlock() + for _, eps := range sbs.Eps { n, err := c.getNetworkFromStore(eps.Nid) + var ep *endpoint if err != nil { logrus.Errorf("getNetworkFromStore for nid %s failed while trying to build sandbox for cleanup: %v", eps.Nid, err) - continue - } - - ep, err := n.getEndpointFromStore(eps.Eid) - if err != nil { - logrus.Errorf("getEndpointFromStore for eid %s failed while trying to build sandbox for cleanup: %v", eps.Eid, err) - continue + n = &network{id: eps.Nid, ctrlr: c, drvOnce: &sync.Once{}} + ep = &endpoint{id: eps.Eid, network: n, sandboxID: sbs.ID} + } else { + ep, err = n.getEndpointFromStore(eps.Eid) + if err != nil { + logrus.Errorf("getEndpointFromStore for eid %s failed while trying to build sandbox for cleanup: %v", eps.Eid, err) + ep = &endpoint{id: eps.Eid, network: n, sandboxID: sbs.ID} + } } heap.Push(&sb.endpoints, ep) } - c.Lock() - c.sandboxes[sb.id] = sb - c.Unlock() - if err := sb.Delete(); err != nil { logrus.Errorf("failed to delete sandbox %s while trying to cleanup: %v", sb.id, err) } diff --git a/vendor/src/github.com/docker/libnetwork/store.go b/vendor/src/github.com/docker/libnetwork/store.go index d5eca874a..1ea2f7ae8 100644 --- a/vendor/src/github.com/docker/libnetwork/store.go +++ b/vendor/src/github.com/docker/libnetwork/store.go @@ -60,12 +60,11 @@ func (c *controller) getNetworkFromStore(nid string) (*network, error) { for _, store := range c.getStores() { n := &network{id: nid, ctrlr: c} err := store.GetObject(datastore.Key(n.Key()...), n) - if err != nil && err != datastore.ErrKeyNotFound { - return nil, fmt.Errorf("could not find network %s: %v", nid, err) - } - // Continue searching in the next store if the key is not found in this store - if err == datastore.ErrKeyNotFound { + if err != nil { + if err != datastore.ErrKeyNotFound { + log.Debugf("could not find network %s: %v", nid, err) + } continue } @@ -82,19 +81,49 @@ func (c *controller) getNetworkFromStore(nid string) (*network, error) { return nil, fmt.Errorf("network %s not found", nid) } +func (c *controller) getNetworksForScope(scope string) ([]*network, error) { + var nl []*network + + store := c.getStore(scope) + if store == nil { + return nil, nil + } + + kvol, err := store.List(datastore.Key(datastore.NetworkKeyPrefix), + &network{ctrlr: c}) + if err != nil && err != datastore.ErrKeyNotFound { + return nil, fmt.Errorf("failed to get networks for scope %s: %v", + scope, err) + } + + for _, kvo := range kvol { + n := kvo.(*network) + n.ctrlr = c + + ec := &endpointCnt{n: n} + err = store.GetObject(datastore.Key(ec.Key()...), ec) + if err != nil { + return nil, fmt.Errorf("could not find endpoint count key %s for network %s while listing: %v", datastore.Key(ec.Key()...), n.Name(), err) + } + + n.epCnt = ec + nl = append(nl, n) + } + + return nl, nil +} + func (c *controller) getNetworksFromStore() ([]*network, error) { var nl []*network for _, store := range c.getStores() { kvol, err := store.List(datastore.Key(datastore.NetworkKeyPrefix), &network{ctrlr: c}) - if err != nil && err != datastore.ErrKeyNotFound { - return nil, fmt.Errorf("failed to get networks for scope %s: %v", - store.Scope(), err) - } - // Continue searching in the next store if no keys found in this store - if err == datastore.ErrKeyNotFound { + if err != nil { + if err != datastore.ErrKeyNotFound { + log.Debugf("failed to get networks for scope %s: %v", store.Scope(), err) + } continue } @@ -117,22 +146,17 @@ func (c *controller) getNetworksFromStore() ([]*network, error) { } func (n *network) getEndpointFromStore(eid string) (*endpoint, error) { - for _, store := range n.ctrlr.getStores() { - ep := &endpoint{id: eid, network: n} - err := store.GetObject(datastore.Key(ep.Key()...), ep) - if err != nil && err != datastore.ErrKeyNotFound { - return nil, fmt.Errorf("could not find endpoint %s: %v", eid, err) - } - - // Continue searching in the next store if the key is not found in this store - if err == datastore.ErrKeyNotFound { - continue - } - - return ep, nil + store := n.ctrlr.getStore(n.Scope()) + if store == nil { + return nil, fmt.Errorf("could not find endpoint %s: datastore not found for scope %s", eid, n.Scope()) } - return nil, fmt.Errorf("endpoint %s not found", eid) + ep := &endpoint{id: eid, network: n} + err := store.GetObject(datastore.Key(ep.Key()...), ep) + if err != nil { + return nil, fmt.Errorf("could not find endpoint %s: %v", eid, err) + } + return ep, nil } func (n *network) getEndpointsFromStore() ([]*endpoint, error) { @@ -141,14 +165,12 @@ func (n *network) getEndpointsFromStore() ([]*endpoint, error) { tmp := endpoint{network: n} for _, store := range n.getController().getStores() { kvol, err := store.List(datastore.Key(tmp.KeyPrefix()...), &endpoint{network: n}) - if err != nil && err != datastore.ErrKeyNotFound { - return nil, - fmt.Errorf("failed to get endpoints for network %s scope %s: %v", - n.Name(), store.Scope(), err) - } - // Continue searching in the next store if no keys found in this store - if err == datastore.ErrKeyNotFound { + if err != nil { + if err != datastore.ErrKeyNotFound { + log.Debugf("failed to get endpoints for network %s scope %s: %v", + n.Name(), store.Scope(), err) + } continue } @@ -243,6 +265,7 @@ func (c *controller) networkWatchLoop(nw *netWatch, ep *endpoint, ecCh <-chan da var addEp []*endpoint delEpMap := make(map[string]*endpoint) + renameEpMap := make(map[string]bool) for k, v := range nw.remoteEps { delEpMap[k] = v } @@ -252,25 +275,40 @@ func (c *controller) networkWatchLoop(nw *netWatch, ep *endpoint, ecCh <-chan da continue } - if _, ok := nw.remoteEps[lEp.ID()]; ok { - delete(delEpMap, lEp.ID()) - continue + if ep, ok := nw.remoteEps[lEp.ID()]; ok { + // On a container rename EP ID will remain + // the same but the name will change. service + // records should reflect the change. + // Keep old EP entry in the delEpMap and add + // EP from the store (which has the new name) + // into the new list + if lEp.name == ep.name { + delete(delEpMap, lEp.ID()) + continue + } + renameEpMap[lEp.ID()] = true } - nw.remoteEps[lEp.ID()] = lEp addEp = append(addEp, lEp) + } + // EPs whose name are to be deleted from the svc records + // should also be removed from nw's remote EP list, except + // the ones that are getting renamed. + for _, lEp := range delEpMap { + if !renameEpMap[lEp.ID()] { + delete(nw.remoteEps, lEp.ID()) + } } c.Unlock() - for _, lEp := range addEp { - ep.getNetwork().updateSvcRecord(lEp, c.getLocalEps(nw), true) - } - for _, lEp := range delEpMap { ep.getNetwork().updateSvcRecord(lEp, c.getLocalEps(nw), false) } + for _, lEp := range addEp { + ep.getNetwork().updateSvcRecord(lEp, c.getLocalEps(nw), true) + } } } } @@ -286,6 +324,11 @@ func (c *controller) processEndpointCreate(nmap map[string]*netWatch, ep *endpoi c.Lock() nw.localEps[ep.ID()] = ep + + // If we had learned that from the kv store remove it + // from remote ep list now that we know that this is + // indeed a local endpoint + delete(nw.remoteEps, ep.ID()) c.Unlock() return } @@ -340,19 +383,24 @@ func (c *controller) processEndpointDelete(nmap map[string]*netWatch, ep *endpoi c.Lock() if len(nw.localEps) == 0 { close(nw.stopCh) + + // This is the last container going away for the network. Destroy + // this network's svc db entry + delete(c.svcDb, ep.getNetwork().ID()) + delete(nmap, ep.getNetwork().ID()) } } c.Unlock() } -func (c *controller) watchLoop(nmap map[string]*netWatch) { +func (c *controller) watchLoop() { for { select { case ep := <-c.watchCh: - c.processEndpointCreate(nmap, ep) + c.processEndpointCreate(c.nmap, ep) case ep := <-c.unWatchCh: - c.processEndpointDelete(nmap, ep) + c.processEndpointDelete(c.nmap, ep) } } } @@ -360,7 +408,7 @@ func (c *controller) watchLoop(nmap map[string]*netWatch) { func (c *controller) startWatch() { c.watchCh = make(chan *endpoint) c.unWatchCh = make(chan *endpoint) - nmap := make(map[string]*netWatch) + c.nmap = make(map[string]*netWatch) - go c.watchLoop(nmap) + go c.watchLoop() } diff --git a/vendor/src/github.com/docker/notary/client/changelist/change.go b/vendor/src/github.com/docker/notary/client/changelist/change.go index 867c23051..dfdaed5c3 100644 --- a/vendor/src/github.com/docker/notary/client/changelist/change.go +++ b/vendor/src/github.com/docker/notary/client/changelist/change.go @@ -1,5 +1,9 @@ package changelist +import ( + "github.com/endophage/gotuf/data" +) + // Scopes for TufChanges are simply the TUF roles. // Unfortunately because of targets delegations, we can only // cover the base roles. @@ -10,6 +14,17 @@ const ( ScopeTimestamp = "timestamp" ) +// Types for TufChanges are namespaced by the Role they +// are relevant for. The Root and Targets roles are the +// only ones for which user action can cause a change, as +// all changes in Snapshot and Timestamp are programatically +// generated base on Root and Targets changes. +const ( + TypeRootRole = "role" + TypeTargetsTarget = "target" + TypeTargetsDelegation = "delegation" +) + // TufChange represents a change to a TUF repo type TufChange struct { // Abbreviated because Go doesn't permit a field and method of the same name @@ -20,6 +35,13 @@ type TufChange struct { Data []byte `json:"data"` } +// TufRootData represents a modification of the keys associated +// with a role that appears in the root.json +type TufRootData struct { + Keys []data.TUFKey `json:"keys"` + RoleName string `json:"role"` +} + // NewTufChange initializes a tufChange object func NewTufChange(action string, role, changeType, changePath string, content []byte) *TufChange { return &TufChange{ diff --git a/vendor/src/github.com/docker/notary/client/client.go b/vendor/src/github.com/docker/notary/client/client.go index ee376053c..ca57a89f1 100644 --- a/vendor/src/github.com/docker/notary/client/client.go +++ b/vendor/src/github.com/docker/notary/client/client.go @@ -245,6 +245,7 @@ func (r *NotaryRepository) AddTarget(target *Target) error { if err != nil { return err } + defer cl.Close() logrus.Debugf("Adding target \"%s\" with sha256 \"%x\" and size %d bytes.\n", target.Name, target.Hashes["sha256"], target.Length) meta := data.FileMeta{Length: target.Length, Hashes: target.Hashes} @@ -258,7 +259,7 @@ func (r *NotaryRepository) AddTarget(target *Target) error { if err != nil { return err } - return cl.Close() + return nil } // RemoveTarget creates a new changelist entry to remove a target from the repository @@ -326,6 +327,17 @@ func (r *NotaryRepository) GetTargetByName(name string) (*Target, error) { return &Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, nil } +// GetChangelist returns the list of the repository's unpublished changes +func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) { + changelistDir := filepath.Join(r.tufRepoPath, "changelist") + cl, err := changelist.NewFileChangelist(changelistDir) + if err != nil { + logrus.Debug("Error initializing changelist") + return nil, err + } + return cl, nil +} + // Publish pushes the local changes in signed material to the remote notary-server // Conceptually it performs an operation similar to a `git rebase` func (r *NotaryRepository) Publish() error { @@ -371,11 +383,8 @@ func (r *NotaryRepository) Publish() error { return err } } - // load the changelist for this repo - changelistDir := filepath.Join(r.tufRepoPath, "changelist") - cl, err := changelist.NewFileChangelist(changelistDir) + cl, err := r.GetChangelist() if err != nil { - logrus.Debug("Error initializing changelist") return err } // apply the changelist to the repo @@ -445,7 +454,7 @@ func (r *NotaryRepository) Publish() error { // This is not a critical problem when only a single host is pushing // but will cause weird behaviour if changelist cleanup is failing // and there are multiple hosts writing to the repo. - logrus.Warn("Unable to clear changelist. You may want to manually delete the folder ", changelistDir) + logrus.Warn("Unable to clear changelist. You may want to manually delete the folder ", filepath.Join(r.tufRepoPath, "changelist")) } return nil } @@ -596,3 +605,55 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { r.fileStore, ), nil } + +// RotateKeys removes all existing keys associated with role and adds +// the keys specified by keyIDs to the role. These changes are staged +// in a changelist until publish is called. +func (r *NotaryRepository) RotateKeys() error { + for _, role := range []string{"targets", "snapshot"} { + key, err := r.cryptoService.Create(role, data.ECDSAKey) + if err != nil { + return err + } + err = r.rootFileKeyChange(role, changelist.ActionCreate, key) + if err != nil { + return err + } + } + return nil +} + +func (r *NotaryRepository) rootFileKeyChange(role, action string, key data.PublicKey) error { + cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) + if err != nil { + return err + } + defer cl.Close() + + k, ok := key.(*data.TUFKey) + if !ok { + return errors.New("Invalid key type found during rotation.") + } + + meta := changelist.TufRootData{ + RoleName: role, + Keys: []data.TUFKey{*k}, + } + metaJSON, err := json.Marshal(meta) + if err != nil { + return err + } + + c := changelist.NewTufChange( + action, + changelist.ScopeRoot, + changelist.TypeRootRole, + role, + metaJSON, + ) + err = cl.Add(c) + if err != nil { + return err + } + return nil +} diff --git a/vendor/src/github.com/docker/notary/client/helpers.go b/vendor/src/github.com/docker/notary/client/helpers.go index 476ef08b7..50be86c6c 100644 --- a/vendor/src/github.com/docker/notary/client/helpers.go +++ b/vendor/src/github.com/docker/notary/client/helpers.go @@ -7,7 +7,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/notary/client/changelist" - "github.com/endophage/gotuf" + tuf "github.com/endophage/gotuf" "github.com/endophage/gotuf/data" "github.com/endophage/gotuf/keys" "github.com/endophage/gotuf/store" @@ -38,14 +38,16 @@ func applyChangelist(repo *tuf.TufRepo, cl changelist.Changelist) error { } switch c.Scope() { case changelist.ScopeTargets: - err := applyTargetsChange(repo, c) - if err != nil { - return err - } + err = applyTargetsChange(repo, c) + case changelist.ScopeRoot: + err = applyRootChange(repo, c) default: logrus.Debug("scope not supported: ", c.Scope()) } index++ + if err != nil { + return err + } } logrus.Debugf("applied %d change(s)", index) return nil @@ -75,6 +77,40 @@ func applyTargetsChange(repo *tuf.TufRepo, c changelist.Change) error { return nil } +func applyRootChange(repo *tuf.TufRepo, c changelist.Change) error { + var err error + switch c.Type() { + case changelist.TypeRootRole: + err = applyRootRoleChange(repo, c) + default: + logrus.Debug("type of root change not yet supported: ", c.Type()) + } + return err // might be nil +} + +func applyRootRoleChange(repo *tuf.TufRepo, c changelist.Change) error { + switch c.Action() { + case changelist.ActionCreate: + // replaces all keys for a role + d := &changelist.TufRootData{} + err := json.Unmarshal(c.Content(), d) + if err != nil { + return err + } + k := []data.PublicKey{} + for _, key := range d.Keys { + k = append(k, data.NewPublicKey(key.Algorithm(), key.Public())) + } + err = repo.ReplaceBaseKeys(d.RoleName, k...) + if err != nil { + return err + } + default: + logrus.Debug("action not yet supported for root: ", c.Action()) + } + return nil +} + func nearExpiry(r *data.SignedRoot) bool { plus6mo := time.Now().AddDate(0, 6, 0) return r.Signed.Expires.Before(plus6mo) diff --git a/vendor/src/github.com/endophage/gotuf/client/client.go b/vendor/src/github.com/endophage/gotuf/client/client.go index 2bcda4b74..c0fcf7a83 100644 --- a/vendor/src/github.com/endophage/gotuf/client/client.go +++ b/vendor/src/github.com/endophage/gotuf/client/client.go @@ -175,19 +175,7 @@ func (c *Client) downloadRoot() error { var s *data.Signed var raw []byte if download { - logrus.Debug("downloading new root") - raw, err = c.remote.GetMeta(role, size) - if err != nil { - return err - } - hash := sha256.Sum256(raw) - if expectedSha256 != nil && !bytes.Equal(hash[:], expectedSha256) { - // if we don't have an expected sha256, we're going to trust the root - // based purely on signature and expiry time validation - return fmt.Errorf("Remote root sha256 did not match snapshot root sha256: %#x vs. %#x", hash, []byte(expectedSha256)) - } - s = &data.Signed{} - err = json.Unmarshal(raw, s) + raw, s, err = c.downloadSigned(role, size, expectedSha256) if err != nil { return err } @@ -247,6 +235,8 @@ func (c Client) verifyRoot(role string, s *data.Signed, minVersion int) error { } // downloadTimestamp is responsible for downloading the timestamp.json +// Timestamps are special in that we ALWAYS attempt to download and only +// use cache if the download fails (and the cache is still valid). func (c *Client) downloadTimestamp() error { logrus.Debug("downloadTimestamp") role := data.RoleName("timestamp") @@ -271,9 +261,7 @@ func (c *Client) downloadTimestamp() error { } // unlike root, targets and snapshot, always try and download timestamps // from remote, only using the cache one if we couldn't reach remote. - logrus.Debug("Downloading timestamp") - raw, err := c.remote.GetMeta(role, maxSize) - var s *data.Signed + raw, s, err := c.downloadSigned(role, maxSize, nil) if err != nil || len(raw) == 0 { if err, ok := err.(store.ErrMetaNotFound); ok { return err @@ -286,14 +274,10 @@ func (c *Client) downloadTimestamp() error { } return err } + logrus.Debug("using cached timestamp") s = old } else { download = true - s = &data.Signed{} - err = json.Unmarshal(raw, s) - if err != nil { - return err - } } err = signed.Verify(s, role, version, c.keysDB) if err != nil { @@ -315,10 +299,13 @@ func (c *Client) downloadTimestamp() error { func (c *Client) downloadSnapshot() error { logrus.Debug("downloadSnapshot") role := data.RoleName("snapshot") + if c.local.Timestamp == nil { + return ErrMissingMeta{role: "snapshot"} + } size := c.local.Timestamp.Signed.Meta[role].Length expectedSha256, ok := c.local.Timestamp.Signed.Meta[role].Hashes["sha256"] if !ok { - return fmt.Errorf("Sha256 is currently the only hash supported by this client. No Sha256 found for snapshot") + return ErrMissingMeta{role: "snapshot"} } var download bool @@ -351,17 +338,7 @@ func (c *Client) downloadSnapshot() error { } var s *data.Signed if download { - logrus.Debug("downloading new snapshot") - raw, err = c.remote.GetMeta(role, size) - if err != nil { - return err - } - genHash := sha256.Sum256(raw) - if !bytes.Equal(genHash[:], expectedSha256) { - return fmt.Errorf("Retrieved snapshot did not verify against hash in timestamp.") - } - s = &data.Signed{} - err = json.Unmarshal(raw, s) + raw, s, err = c.downloadSigned(role, size, expectedSha256) if err != nil { return err } @@ -390,10 +367,12 @@ func (c *Client) downloadSnapshot() error { } // downloadTargets is responsible for downloading any targets file -// including delegates roles. It will download the whole tree of -// delegated roles below the given one +// including delegates roles. func (c *Client) downloadTargets(role string) error { role = data.RoleName(role) // this will really only do something for base targets role + if c.local.Snapshot == nil { + return ErrMissingMeta{role: role} + } snap := c.local.Snapshot.Signed root := c.local.Root.Signed r := c.keysDB.GetRole(role) @@ -418,15 +397,32 @@ func (c *Client) downloadTargets(role string) error { return nil } +func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte) ([]byte, *data.Signed, error) { + raw, err := c.remote.GetMeta(role, size) + if err != nil { + return nil, nil, err + } + genHash := sha256.Sum256(raw) + if expectedSha256 != nil && !bytes.Equal(genHash[:], expectedSha256) { + return nil, nil, ErrChecksumMismatch{role: role} + } + s := &data.Signed{} + err = json.Unmarshal(raw, s) + if err != nil { + return nil, nil, err + } + return raw, s, nil +} + func (c Client) GetTargetsFile(role string, keyIDs []string, snapshotMeta data.Files, consistent bool, threshold int) (*data.Signed, error) { // require role exists in snapshots roleMeta, ok := snapshotMeta[role] if !ok { - return nil, fmt.Errorf("Snapshot does not contain target role") + return nil, ErrMissingMeta{role: role} } expectedSha256, ok := snapshotMeta[role].Hashes["sha256"] if !ok { - return nil, fmt.Errorf("Sha256 is currently the only hash supported by this client. No Sha256 found for targets role %s", role) + return nil, ErrMissingMeta{role: role} } // try to get meta file from content addressed cache @@ -454,25 +450,19 @@ func (c Client) GetTargetsFile(role string, keyIDs []string, snapshotMeta data.F } else { download = true } - } + size := snapshotMeta[role].Length var s *data.Signed if download { rolePath, err := c.RoleTargetsPath(role, hex.EncodeToString(expectedSha256), consistent) if err != nil { return nil, err } - raw, err = c.remote.GetMeta(rolePath, snapshotMeta[role].Length) + raw, s, err = c.downloadSigned(rolePath, size, expectedSha256) if err != nil { return nil, err } - s = &data.Signed{} - err = json.Unmarshal(raw, s) - if err != nil { - logrus.Error("Error unmarshalling targets file:", err) - return nil, err - } } else { logrus.Debug("using cached ", role) s = old diff --git a/vendor/src/github.com/endophage/gotuf/client/errors.go b/vendor/src/github.com/endophage/gotuf/client/errors.go index 776e6a69e..8c8ae527c 100644 --- a/vendor/src/github.com/endophage/gotuf/client/errors.go +++ b/vendor/src/github.com/endophage/gotuf/client/errors.go @@ -10,6 +10,22 @@ var ( ErrInsufficientKeys = errors.New("tuf: insufficient keys to meet threshold") ) +type ErrChecksumMismatch struct { + role string +} + +func (e ErrChecksumMismatch) Error() string { + return fmt.Sprintf("tuf: checksum for %s did not match", e.role) +} + +type ErrMissingMeta struct { + role string +} + +func (e ErrMissingMeta) Error() string { + return fmt.Sprintf("tuf: sha256 checksum required for %s", e.role) +} + type ErrMissingRemoteMetadata struct { Name string } diff --git a/vendor/src/github.com/endophage/gotuf/data/keys.go b/vendor/src/github.com/endophage/gotuf/data/keys.go index 3df1ce05c..eccccc420 100644 --- a/vendor/src/github.com/endophage/gotuf/data/keys.go +++ b/vendor/src/github.com/endophage/gotuf/data/keys.go @@ -71,7 +71,7 @@ func (k TUFKey) Public() []byte { return k.Value.Public } -func (k *TUFKey) Private() []byte { +func (k TUFKey) Private() []byte { return k.Value.Private } diff --git a/vendor/src/github.com/endophage/gotuf/data/roles.go b/vendor/src/github.com/endophage/gotuf/data/roles.go index d3047d784..1034393e1 100644 --- a/vendor/src/github.com/endophage/gotuf/data/roles.go +++ b/vendor/src/github.com/endophage/gotuf/data/roles.go @@ -24,7 +24,7 @@ var ValidRoles = map[string]string{ func SetValidRoles(rs map[string]string) { // iterate ValidRoles - for k, _ := range ValidRoles { + for k := range ValidRoles { if v, ok := rs[k]; ok { ValidRoles[k] = v } @@ -88,6 +88,7 @@ type Role struct { Name string `json:"name"` Paths []string `json:"paths,omitempty"` PathHashPrefixes []string `json:"path_hash_prefixes,omitempty"` + Email string `json:"email,omitempty"` } func NewRole(name string, threshold int, keyIDs, paths, pathHashPrefixes []string) (*Role, error) { diff --git a/vendor/src/github.com/endophage/gotuf/store/httpstore.go b/vendor/src/github.com/endophage/gotuf/store/httpstore.go index 1a82b094c..6b69683a8 100644 --- a/vendor/src/github.com/endophage/gotuf/store/httpstore.go +++ b/vendor/src/github.com/endophage/gotuf/store/httpstore.go @@ -90,6 +90,7 @@ func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) { if resp.StatusCode == http.StatusNotFound { return nil, ErrMetaNotFound{} } else if resp.StatusCode != http.StatusOK { + logrus.Debugf("received HTTP status %d when requesting %s.", resp.StatusCode, name) return nil, ErrServerUnavailable{code: resp.StatusCode} } if resp.ContentLength > size { @@ -98,10 +99,6 @@ func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) { logrus.Debugf("%d when retrieving metadata for %s", resp.StatusCode, name) b := io.LimitReader(resp.Body, size) body, err := ioutil.ReadAll(b) - if resp.ContentLength > 0 && int64(len(body)) < resp.ContentLength { - return nil, ErrShortRead{} - } - if err != nil { return nil, err } diff --git a/vendor/src/github.com/endophage/gotuf/store/memorystore.go b/vendor/src/github.com/endophage/gotuf/store/memorystore.go index d32c9a4f3..3baa576a5 100644 --- a/vendor/src/github.com/endophage/gotuf/store/memorystore.go +++ b/vendor/src/github.com/endophage/gotuf/store/memorystore.go @@ -31,7 +31,15 @@ type memoryStore struct { } func (m *memoryStore) GetMeta(name string, size int64) ([]byte, error) { - return m.meta[name], nil + d, ok := m.meta[name] + if ok { + if int64(len(d)) < size { + return d, nil + } + return d[:size], nil + } else { + return nil, ErrMetaNotFound{} + } } func (m *memoryStore) SetMeta(name string, meta []byte) error { diff --git a/vendor/src/github.com/endophage/gotuf/tuf.go b/vendor/src/github.com/endophage/gotuf/tuf.go index 4d226aceb..39af54018 100644 --- a/vendor/src/github.com/endophage/gotuf/tuf.go +++ b/vendor/src/github.com/endophage/gotuf/tuf.go @@ -71,24 +71,46 @@ func NewTufRepo(keysDB *keys.KeyDB, cryptoService signed.CryptoService) *TufRepo } // AddBaseKeys is used to add keys to the role in root.json -func (tr *TufRepo) AddBaseKeys(role string, keys ...*data.TUFKey) error { +func (tr *TufRepo) AddBaseKeys(role string, keys ...data.PublicKey) error { if tr.Root == nil { return ErrNotLoaded{role: "root"} } + ids := []string{} for _, k := range keys { // Store only the public portion - pubKey := *k - pubKey.Value.Private = nil - tr.Root.Signed.Keys[pubKey.ID()] = &pubKey - tr.keysDB.AddKey(&pubKey) + pubKey := data.NewPrivateKey(k.Algorithm(), k.Public(), nil) + tr.Root.Signed.Keys[pubKey.ID()] = pubKey + tr.keysDB.AddKey(k) tr.Root.Signed.Roles[role].KeyIDs = append(tr.Root.Signed.Roles[role].KeyIDs, pubKey.ID()) + ids = append(ids, pubKey.ID()) } + r, err := data.NewRole( + role, + tr.Root.Signed.Roles[role].Threshold, + ids, + nil, + nil, + ) + if err != nil { + return err + } + tr.keysDB.AddRole(r) tr.Root.Dirty = true return nil } -// RemoveKeys is used to remove keys from the roles in root.json +// ReplaceBaseKeys is used to replace all keys for the given role with the new keys +func (tr *TufRepo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error { + r := tr.keysDB.GetRole(role) + err := tr.RemoveBaseKeys(role, r.KeyIDs...) + if err != nil { + return err + } + return tr.AddBaseKeys(role, keys...) +} + +// RemoveBaseKeys is used to remove keys from the roles in root.json func (tr *TufRepo) RemoveBaseKeys(role string, keyIDs ...string) error { if tr.Root == nil { return ErrNotLoaded{role: "root"} @@ -119,7 +141,7 @@ func (tr *TufRepo) RemoveBaseKeys(role string, keyIDs ...string) error { } // remove keys no longer in use by any roles - for k, _ := range toDelete { + for k := range toDelete { delete(tr.Root.Signed.Keys, k) } tr.Root.Dirty = true diff --git a/volume/local/local.go b/volume/local/local.go index d3ec38b05..e0c3a6446 100644 --- a/volume/local/local.go +++ b/volume/local/local.go @@ -11,7 +11,9 @@ import ( "path/filepath" "sync" + derr "github.com/docker/docker/errors" "github.com/docker/docker/pkg/idtools" + "github.com/docker/docker/utils" "github.com/docker/docker/volume" ) @@ -23,8 +25,14 @@ const ( volumesPathName = "volumes" ) -// ErrNotFound is the typed error returned when the requested volume name can't be found -var ErrNotFound = errors.New("volume not found") +var ( + // ErrNotFound is the typed error returned when the requested volume name can't be found + ErrNotFound = errors.New("volume not found") + // volumeNameRegex ensures the name asigned for the volume is valid. + // This name is used to create the bind directory, so we need to avoid characters that + // would make the path to escape the root directory. + volumeNameRegex = utils.RestrictedNamePattern +) // New instantiates a new Root instance with the provided scope. Scope // is the base path that the Root instance uses to store its @@ -96,6 +104,10 @@ func (r *Root) Name() string { // the underlying directory tree required for this volume in the // process. func (r *Root) Create(name string, _ map[string]string) (volume.Volume, error) { + if err := r.validateName(name); err != nil { + return nil, err + } + r.m.Lock() defer r.m.Unlock() @@ -174,6 +186,13 @@ func (r *Root) Get(name string) (volume.Volume, error) { return v, nil } +func (r *Root) validateName(name string) error { + if !volumeNameRegex.MatchString(name) { + return derr.ErrorCodeVolumeName.WithArgs(name, utils.RestrictedNameChars) + } + return nil +} + // localVolume implements the Volume interface from the volume package and // represents the volumes created by Root. type localVolume struct { diff --git a/volume/local/local_test.go b/volume/local/local_test.go index 45fdd5ffc..2c5b800a5 100644 --- a/volume/local/local_test.go +++ b/volume/local/local_test.go @@ -79,3 +79,48 @@ func TestInitializeWithVolumes(t *testing.T) { t.Fatal("expected to re-initialize root with existing volumes") } } + +func TestCreate(t *testing.T) { + rootDir, err := ioutil.TempDir("", "local-volume-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(rootDir) + + r, err := New(rootDir, 0, 0) + if err != nil { + t.Fatal(err) + } + + cases := map[string]bool{ + "name": true, + "name-with-dash": true, + "name_with_underscore": true, + "name/with/slash": false, + "name/with/../../slash": false, + "./name": false, + "../name": false, + "./": false, + "../": false, + "~": false, + ".": false, + "..": false, + "...": false, + } + + for name, success := range cases { + v, err := r.Create(name, nil) + if success { + if err != nil { + t.Fatal(err) + } + if v.Name() != name { + t.Fatalf("Expected volume with name %s, got %s", name, v.Name()) + } + } else { + if err == nil { + t.Fatalf("Expected error creating volume with name %s, got nil", name) + } + } + } +}