Skip to content

Commit 99a98cc

Browse files
committed
Add support for docker run in swarm mode overlay
This PR adds support for running regular containers to be connected to swarm mode multi-host network so that: - containers connected to the same network across the cluster can discover and connect to each other. - Get access to services(and their associated loadbalancers) connected to the same network Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
1 parent 99c3968 commit 99a98cc

File tree

23 files changed

+606
-104
lines changed

23 files changed

+606
-104
lines changed

api/client/network/create.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type createOptions struct {
2323
labels []string
2424
internal bool
2525
ipv6 bool
26+
attachable bool
2627

2728
ipamDriver string
2829
ipamSubnet []string
@@ -55,6 +56,7 @@ func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
5556
flags.StringSliceVar(&opts.labels, "label", []string{}, "Set metadata on a network")
5657
flags.BoolVar(&opts.internal, "internal", false, "Restrict external access to the network")
5758
flags.BoolVar(&opts.ipv6, "ipv6", false, "Enable IPv6 networking")
59+
flags.BoolVar(&opts.attachable, "attachable", false, "Enable manual container attachment")
5860

5961
flags.StringVar(&opts.ipamDriver, "ipam-driver", "default", "IP Address Management Driver")
6062
flags.StringSliceVar(&opts.ipamSubnet, "subnet", []string{}, "Subnet in CIDR format that represents a network segment")
@@ -87,6 +89,7 @@ func runCreate(dockerCli *client.DockerCli, opts createOptions) error {
8789
CheckDuplicate: true,
8890
Internal: opts.internal,
8991
EnableIPv6: opts.ipv6,
92+
Attachable: opts.attachable,
9093
Labels: runconfigopts.ConvertKVStringsToMap(opts.labels),
9194
}
9295

api/client/service/opts.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,20 +451,21 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
451451
Mounts: opts.mounts.Value(),
452452
StopGracePeriod: opts.stopGrace.Value(),
453453
},
454+
Networks: convertNetworks(opts.networks),
454455
Resources: opts.resources.ToResourceRequirements(),
455456
RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
456457
Placement: &swarm.Placement{
457458
Constraints: opts.constraints,
458459
},
459460
LogDriver: opts.logDriver.toLogDriver(),
460461
},
461-
Mode: swarm.ServiceMode{},
462+
Networks: convertNetworks(opts.networks),
463+
Mode: swarm.ServiceMode{},
462464
UpdateConfig: &swarm.UpdateConfig{
463465
Parallelism: opts.update.parallelism,
464466
Delay: opts.update.delay,
465467
FailureAction: opts.update.onFailure,
466468
},
467-
Networks: convertNetworks(opts.networks),
468469
EndpointSpec: opts.endpoint.ToEndpointSpec(),
469470
}
470471

api/server/router/network/network_routes.go

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package network
22

33
import (
44
"encoding/json"
5-
"fmt"
65
"net/http"
76

87
"golang.org/x/net/context"
@@ -11,7 +10,6 @@ import (
1110
"github.com/docker/docker/api/types"
1211
"github.com/docker/docker/api/types/filters"
1312
"github.com/docker/docker/api/types/network"
14-
"github.com/docker/docker/errors"
1513
"github.com/docker/libnetwork"
1614
)
1715

@@ -116,17 +114,7 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
116114
return err
117115
}
118116

119-
nw, err := n.backend.FindNetwork(vars["id"])
120-
if err != nil {
121-
return err
122-
}
123-
124-
if nw.Info().Dynamic() {
125-
err := fmt.Errorf("operation not supported for swarm scoped networks")
126-
return errors.NewRequestForbiddenError(err)
127-
}
128-
129-
return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name(), connect.EndpointConfig)
117+
return n.backend.ConnectContainerToNetwork(connect.Container, vars["id"], connect.EndpointConfig)
130118
}
131119

132120
func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@@ -143,13 +131,6 @@ func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.Respon
143131
return err
144132
}
145133

146-
nw, _ := n.backend.FindNetwork(vars["id"])
147-
148-
if nw != nil && nw.Info().Dynamic() {
149-
err := fmt.Errorf("operation not supported for swarm scoped networks")
150-
return errors.NewRequestForbiddenError(err)
151-
}
152-
153134
return n.backend.DisconnectContainerFromNetwork(disconnect.Container, vars["id"], disconnect.Force)
154135
}
155136

container/container.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,9 @@ func (container *Container) BuildEndpointInfo(n libnetwork.Network, ep libnetwor
700700
}
701701

702702
if _, ok := networkSettings.Networks[n.Name()]; !ok {
703-
networkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
703+
networkSettings.Networks[n.Name()] = &network.EndpointSettings{
704+
EndpointSettings: &networktypes.EndpointSettings{},
705+
}
704706
}
705707
networkSettings.Networks[n.Name()].NetworkID = n.ID()
706708
networkSettings.Networks[n.Name()].EndpointID = ep.ID()

daemon/cluster/cluster.go

Lines changed: 137 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/Sirupsen/logrus"
1717
apitypes "github.com/docker/docker/api/types"
1818
"github.com/docker/docker/api/types/filters"
19+
"github.com/docker/docker/api/types/network"
1920
types "github.com/docker/docker/api/types/swarm"
2021
"github.com/docker/docker/daemon/cluster/convert"
2122
executorpkg "github.com/docker/docker/daemon/cluster/executor"
@@ -126,6 +127,18 @@ type Cluster struct {
126127
stop bool
127128
err error
128129
cancelDelay func()
130+
attachers map[string]*attacher
131+
}
132+
133+
// attacher manages the in-memory attachment state of a container
134+
// attachment to a global scope network managed by swarm manager. It
135+
// helps in identifying the attachment ID via the taskID and the
136+
// corresponding attachment configuration obtained from the manager.
137+
type attacher struct {
138+
taskID string
139+
config *network.NetworkingConfig
140+
attachWaitCh chan *network.NetworkingConfig
141+
detachWaitCh chan struct{}
129142
}
130143

131144
type node struct {
@@ -154,6 +167,7 @@ func New(config Config) (*Cluster, error) {
154167
config: config,
155168
configEvent: make(chan struct{}, 10),
156169
runtimeRoot: config.RuntimeRoot,
170+
attachers: make(map[string]*attacher),
157171
}
158172

159173
st, err := c.loadState()
@@ -1212,6 +1226,120 @@ func (c *Cluster) GetNetworks() ([]apitypes.NetworkResource, error) {
12121226
return networks, nil
12131227
}
12141228

1229+
func attacherKey(target, containerID string) string {
1230+
return containerID + ":" + target
1231+
}
1232+
1233+
// UpdateAttachment signals the attachment config to the attachment
1234+
// waiter who is trying to start or attach the container to the
1235+
// network.
1236+
func (c *Cluster) UpdateAttachment(target, containerID string, config *network.NetworkingConfig) error {
1237+
c.RLock()
1238+
attacher, ok := c.attachers[attacherKey(target, containerID)]
1239+
c.RUnlock()
1240+
if !ok || attacher == nil {
1241+
return fmt.Errorf("could not find attacher for container %s to network %s", containerID, target)
1242+
}
1243+
1244+
attacher.attachWaitCh <- config
1245+
close(attacher.attachWaitCh)
1246+
return nil
1247+
}
1248+
1249+
// WaitForDetachment waits for the container to stop or detach from
1250+
// the network.
1251+
func (c *Cluster) WaitForDetachment(ctx context.Context, networkName, networkID, taskID, containerID string) error {
1252+
c.RLock()
1253+
attacher, ok := c.attachers[attacherKey(networkName, containerID)]
1254+
if !ok {
1255+
attacher, ok = c.attachers[attacherKey(networkID, containerID)]
1256+
}
1257+
if c.node == nil || c.node.Agent() == nil {
1258+
c.RUnlock()
1259+
return fmt.Errorf("invalid cluster node while waiting for detachment")
1260+
}
1261+
1262+
agent := c.node.Agent()
1263+
c.RUnlock()
1264+
1265+
if ok && attacher != nil && attacher.detachWaitCh != nil {
1266+
select {
1267+
case <-attacher.detachWaitCh:
1268+
case <-ctx.Done():
1269+
return ctx.Err()
1270+
}
1271+
}
1272+
1273+
return agent.ResourceAllocator().DetachNetwork(ctx, taskID)
1274+
}
1275+
1276+
// AttachNetwork generates an attachment request towards the manager.
1277+
func (c *Cluster) AttachNetwork(target string, containerID string, addresses []string) (*network.NetworkingConfig, error) {
1278+
aKey := attacherKey(target, containerID)
1279+
c.Lock()
1280+
if c.node == nil || c.node.Agent() == nil {
1281+
c.Unlock()
1282+
return nil, fmt.Errorf("invalid cluster node while attaching to network")
1283+
}
1284+
if attacher, ok := c.attachers[aKey]; ok {
1285+
c.Unlock()
1286+
return attacher.config, nil
1287+
}
1288+
1289+
agent := c.node.Agent()
1290+
attachWaitCh := make(chan *network.NetworkingConfig)
1291+
detachWaitCh := make(chan struct{})
1292+
c.attachers[aKey] = &attacher{
1293+
attachWaitCh: attachWaitCh,
1294+
detachWaitCh: detachWaitCh,
1295+
}
1296+
c.Unlock()
1297+
1298+
ctx, cancel := c.getRequestContext()
1299+
defer cancel()
1300+
1301+
taskID, err := agent.ResourceAllocator().AttachNetwork(ctx, containerID, target, addresses)
1302+
if err != nil {
1303+
c.Lock()
1304+
delete(c.attachers, aKey)
1305+
c.Unlock()
1306+
return nil, fmt.Errorf("Could not attach to network %s: %v", target, err)
1307+
}
1308+
1309+
logrus.Debugf("Successfully attached to network %s with tid %s", target, taskID)
1310+
1311+
var config *network.NetworkingConfig
1312+
select {
1313+
case config = <-attachWaitCh:
1314+
case <-ctx.Done():
1315+
return nil, fmt.Errorf("attaching to network failed, make sure your network options are correct and check manager logs: %v", ctx.Err())
1316+
}
1317+
1318+
c.Lock()
1319+
c.attachers[aKey].taskID = taskID
1320+
c.attachers[aKey].config = config
1321+
c.Unlock()
1322+
return config, nil
1323+
}
1324+
1325+
// DetachNetwork unblocks the waiters waiting on WaitForDetachment so
1326+
// that a request to detach can be generated towards the manager.
1327+
func (c *Cluster) DetachNetwork(target string, containerID string) error {
1328+
aKey := attacherKey(target, containerID)
1329+
1330+
c.Lock()
1331+
attacher, ok := c.attachers[aKey]
1332+
delete(c.attachers, aKey)
1333+
c.Unlock()
1334+
1335+
if !ok {
1336+
return fmt.Errorf("could not find network attachment for container %s to network %s", containerID, target)
1337+
}
1338+
1339+
close(attacher.detachWaitCh)
1340+
return nil
1341+
}
1342+
12151343
// CreateNetwork creates a new cluster managed network.
12161344
func (c *Cluster) CreateNetwork(s apitypes.NetworkCreateRequest) (string, error) {
12171345
c.RLock()
@@ -1262,7 +1390,14 @@ func (c *Cluster) RemoveNetwork(input string) error {
12621390
}
12631391

12641392
func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.ControlClient, s *types.ServiceSpec) error {
1265-
for i, n := range s.Networks {
1393+
// Always prefer NetworkAttachmentConfigs from TaskTemplate
1394+
// but fallback to service spec for backward compatibility
1395+
networks := s.TaskTemplate.Networks
1396+
if len(networks) == 0 {
1397+
networks = s.Networks
1398+
}
1399+
1400+
for i, n := range networks {
12661401
apiNetwork, err := getNetwork(ctx, client, n.Target)
12671402
if err != nil {
12681403
if ln, _ := c.config.Backend.FindNetwork(n.Target); ln != nil && !ln.Info().Dynamic() {
@@ -1271,7 +1406,7 @@ func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.Control
12711406
}
12721407
return err
12731408
}
1274-
s.Networks[i].Target = apiNetwork.ID
1409+
networks[i].Target = apiNetwork.ID
12751410
}
12761411
return nil
12771412
}

daemon/cluster/convert/network.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func networkFromGRPC(n *swarmapi.Network) types.Network {
2727
Spec: types.NetworkSpec{
2828
IPv6Enabled: n.Spec.Ipv6Enabled,
2929
Internal: n.Spec.Internal,
30+
Attachable: n.Spec.Attachable,
3031
IPAMOptions: ipamFromGRPC(n.Spec.IPAM),
3132
},
3233
IPAMOptions: ipamFromGRPC(n.IPAM),
@@ -155,6 +156,7 @@ func BasicNetworkFromGRPC(n swarmapi.Network) basictypes.NetworkResource {
155156
EnableIPv6: spec.Ipv6Enabled,
156157
IPAM: ipam,
157158
Internal: spec.Internal,
159+
Attachable: spec.Attachable,
158160
Labels: n.Spec.Annotations.Labels,
159161
}
160162

@@ -179,6 +181,7 @@ func BasicNetworkCreateToGRPC(create basictypes.NetworkCreateRequest) swarmapi.N
179181
},
180182
Ipv6Enabled: create.EnableIPv6,
181183
Internal: create.Internal,
184+
Attachable: create.Attachable,
182185
}
183186
if create.IPAM != nil {
184187
ns.IPAM = &swarmapi.IPAMOptions{

daemon/cluster/convert/service.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@ func ServiceFromGRPC(s swarmapi.Service) types.Service {
1515
spec := s.Spec
1616
containerConfig := spec.Task.Runtime.(*swarmapi.TaskSpec_Container).Container
1717

18-
networks := make([]types.NetworkAttachmentConfig, 0, len(spec.Networks))
18+
serviceNetworks := make([]types.NetworkAttachmentConfig, 0, len(spec.Networks))
1919
for _, n := range spec.Networks {
20-
networks = append(networks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
20+
serviceNetworks = append(serviceNetworks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
2121
}
22+
23+
taskNetworks := make([]types.NetworkAttachmentConfig, 0, len(spec.Task.Networks))
24+
for _, n := range spec.Task.Networks {
25+
taskNetworks = append(taskNetworks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
26+
}
27+
2228
service := types.Service{
2329
ID: s.ID,
2430

@@ -29,9 +35,10 @@ func ServiceFromGRPC(s swarmapi.Service) types.Service {
2935
RestartPolicy: restartPolicyFromGRPC(s.Spec.Task.Restart),
3036
Placement: placementFromGRPC(s.Spec.Task.Placement),
3137
LogDriver: driverFromGRPC(s.Spec.Task.LogDriver),
38+
Networks: taskNetworks,
3239
},
3340

34-
Networks: networks,
41+
Networks: serviceNetworks,
3542
EndpointSpec: endpointSpecFromGRPC(s.Spec.Endpoint),
3643
},
3744
Endpoint: endpointFromGRPC(s.Endpoint),
@@ -99,9 +106,14 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
99106
name = namesgenerator.GetRandomName(0)
100107
}
101108

102-
networks := make([]*swarmapi.ServiceSpec_NetworkAttachmentConfig, 0, len(s.Networks))
109+
serviceNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.Networks))
103110
for _, n := range s.Networks {
104-
networks = append(networks, &swarmapi.ServiceSpec_NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
111+
serviceNetworks = append(serviceNetworks, &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
112+
}
113+
114+
taskNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.TaskTemplate.Networks))
115+
for _, n := range s.TaskTemplate.Networks {
116+
taskNetworks = append(taskNetworks, &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
105117
}
106118

107119
spec := swarmapi.ServiceSpec{
@@ -112,8 +124,9 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
112124
Task: swarmapi.TaskSpec{
113125
Resources: resourcesToGRPC(s.TaskTemplate.Resources),
114126
LogDriver: driverToGRPC(s.TaskTemplate.LogDriver),
127+
Networks: taskNetworks,
115128
},
116-
Networks: networks,
129+
Networks: serviceNetworks,
117130
}
118131

119132
containerSpec, err := containerToGRPC(s.TaskTemplate.ContainerSpec)

daemon/cluster/convert/task.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import (
1212
func TaskFromGRPC(t swarmapi.Task) types.Task {
1313
containerConfig := t.Spec.Runtime.(*swarmapi.TaskSpec_Container).Container
1414
containerStatus := t.Status.GetContainer()
15+
networks := make([]types.NetworkAttachmentConfig, 0, len(t.Spec.Networks))
16+
for _, n := range t.Spec.Networks {
17+
networks = append(networks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
18+
}
19+
1520
task := types.Task{
1621
ID: t.ID,
1722
ServiceID: t.ServiceID,
@@ -23,6 +28,7 @@ func TaskFromGRPC(t swarmapi.Task) types.Task {
2328
RestartPolicy: restartPolicyFromGRPC(t.Spec.Restart),
2429
Placement: placementFromGRPC(t.Spec.Placement),
2530
LogDriver: driverFromGRPC(t.Spec.LogDriver),
31+
Networks: networks,
2632
},
2733
Status: types.TaskStatus{
2834
State: types.TaskState(strings.ToLower(t.Status.State.String())),

daemon/cluster/executor/backend.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,6 @@ type Backend interface {
4040
IsSwarmCompatible() error
4141
SubscribeToEvents(since, until time.Time, filter filters.Args) ([]events.Message, chan interface{})
4242
UnsubscribeFromEvents(listener chan interface{})
43+
UpdateAttachment(string, string, string, *network.NetworkingConfig) error
44+
WaitForDetachment(context.Context, string, string, string, string) error
4345
}

0 commit comments

Comments
 (0)