瀏覽代碼

up: fix race condition on network connect

Engine API only allows at most one network to be connected as
part of the ContainerCreate API request. Compose will pick the
highest priority network.

Afterwards, the remaining networks (if any) are connected before
the container is actually started.

The big change here is that, previously, the highest-priority
network was connected in the create, and then disconnected and
immediately reconnected along with all the others. This was
racy because evidently connecting the container to the network
as part of the create isn't synchronous, so sometimes when Compose
tried to disconnect it, the API would return an error like:
```
container <id> is not connected to the network <network>
```

To avoid needing to disconnect and immediately reconnect, the
network config logic has been refactored to ensure that it sets
up the network config correctly the first time.

Signed-off-by: Milas Bowman <[email protected]>
Milas Bowman 2 年之前
父節點
當前提交
10b290e682
共有 4 個文件被更改,包括 170 次插入156 次删除
  1. 31 82
      pkg/compose/convergence.go
  2. 97 68
      pkg/compose/create.go
  3. 34 4
      pkg/compose/create_test.go
  4. 8 2
      pkg/compose/run.go

+ 31 - 82
pkg/compose/convergence.go

@@ -29,7 +29,6 @@ import (
 	"github.com/containerd/containerd/platforms"
 	"github.com/containerd/containerd/platforms"
 	moby "github.com/docker/docker/api/types"
 	moby "github.com/docker/docker/api/types"
 	containerType "github.com/docker/docker/api/types/container"
 	containerType "github.com/docker/docker/api/types/container"
-	"github.com/docker/docker/api/types/network"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
@@ -233,7 +232,13 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
 		name := getContainerName(project.Name, service, number)
 		name := getContainerName(project.Name, service, number)
 		i := i
 		i := i
 		eg.Go(func() error {
 		eg.Go(func() error {
-			container, err := c.service.createContainer(ctx, project, service, name, number, false, true, false)
+			opts := createOptions{
+				AutoRemove:        false,
+				AttachStdin:       false,
+				UseNetworkAliases: true,
+				Labels:            mergeLabels(service.Labels, service.CustomLabels),
+			}
+			container, err := c.service.createContainer(ctx, project, service, name, number, opts)
 			updated[actual+i] = container
 			updated[actual+i] = container
 			return err
 			return err
 		})
 		})
@@ -399,12 +404,11 @@ func getScale(config types.ServiceConfig) (int, error) {
 }
 }
 
 
 func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig,
 func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig,
-	name string, number int, autoRemove bool, useNetworkAliases bool, attachStdin bool) (container moby.Container, err error) {
+	name string, number int, opts createOptions) (container moby.Container, err error) {
 	w := progress.ContextWriter(ctx)
 	w := progress.ContextWriter(ctx)
 	eventName := "Container " + name
 	eventName := "Container " + name
 	w.Event(progress.CreatingEvent(eventName))
 	w.Event(progress.CreatingEvent(eventName))
-	container, err = s.createMobyContainer(ctx, project, service, name, number, nil,
-		autoRemove, useNetworkAliases, attachStdin, w, mergeLabels(service.Labels, service.CustomLabels))
+	container, err = s.createMobyContainer(ctx, project, service, name, number, nil, opts, w)
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
@@ -429,9 +433,13 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
 	}
 	}
 	name := getContainerName(project.Name, service, number)
 	name := getContainerName(project.Name, service, number)
 	tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name)
 	tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name)
-	created, err = s.createMobyContainer(ctx, project, service, tmpName, number, inherited,
-		false, true, false, w,
-		mergeLabels(service.Labels, service.CustomLabels).Add(api.ContainerReplaceLabel, replaced.ID))
+	opts := createOptions{
+		AutoRemove:        false,
+		AttachStdin:       false,
+		UseNetworkAliases: true,
+		Labels:            mergeLabels(service.Labels, service.CustomLabels).Add(api.ContainerReplaceLabel, replaced.ID),
+	}
+	created, err = s.createMobyContainer(ctx, project, service, tmpName, number, inherited, opts, w)
 	if err != nil {
 	if err != nil {
 		return created, err
 		return created, err
 	}
 	}
@@ -484,19 +492,18 @@ func (s *composeService) startContainer(ctx context.Context, container moby.Cont
 	return nil
 	return nil
 }
 }
 
 
-func (s *composeService) createMobyContainer(ctx context.Context, //nolint:gocyclo
+func (s *composeService) createMobyContainer(ctx context.Context,
 	project *types.Project,
 	project *types.Project,
 	service types.ServiceConfig,
 	service types.ServiceConfig,
 	name string,
 	name string,
 	number int,
 	number int,
 	inherit *moby.Container,
 	inherit *moby.Container,
-	autoRemove, useNetworkAliases, attachStdin bool,
+	opts createOptions,
 	w progress.Writer,
 	w progress.Writer,
-	labels types.Labels,
 ) (moby.Container, error) {
 ) (moby.Container, error) {
 	var created moby.Container
 	var created moby.Container
-	containerConfig, hostConfig, networkingConfig, err := s.getCreateOptions(ctx, project, service, number, inherit,
-		autoRemove, attachStdin, labels)
+	cfgs, err := s.getCreateConfigs(ctx, project, service, number, inherit, opts)
+
 	if err != nil {
 	if err != nil {
 		return created, err
 		return created, err
 	}
 	}
@@ -514,18 +521,7 @@ func (s *composeService) createMobyContainer(ctx context.Context, //nolint:gocyc
 		plat = &p
 		plat = &p
 	}
 	}
 
 
-	links, err := s.getLinks(ctx, project.Name, service, number)
-	if err != nil {
-		return created, err
-	}
-	if networkingConfig != nil {
-		for k, s := range networkingConfig.EndpointsConfig {
-			s.Links = links
-			networkingConfig.EndpointsConfig[k] = s
-		}
-	}
-
-	response, err := s.apiClient().ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, plat, name)
+	response, err := s.apiClient().ContainerCreate(ctx, cfgs.Container, cfgs.Host, cfgs.Network, plat, name)
 	if err != nil {
 	if err != nil {
 		return created, err
 		return created, err
 	}
 	}
@@ -548,29 +544,19 @@ func (s *composeService) createMobyContainer(ctx context.Context, //nolint:gocyc
 			Networks: inspectedContainer.NetworkSettings.Networks,
 			Networks: inspectedContainer.NetworkSettings.Networks,
 		},
 		},
 	}
 	}
-	for _, netName := range service.NetworksByPriority() {
-		netwrk := project.Networks[netName]
-		cfg := service.Networks[netName]
-		aliases := []string{getContainerName(project.Name, service, number)}
-		if useNetworkAliases {
-			aliases = append(aliases, service.Name)
-			if cfg != nil {
-				aliases = append(aliases, cfg.Aliases...)
-			}
-		}
-		if val, ok := created.NetworkSettings.Networks[netwrk.Name]; ok {
-			if shortIDAliasExists(created.ID, val.Aliases...) {
-				continue
-			}
-			err = s.apiClient().NetworkDisconnect(ctx, netwrk.Name, created.ID, false)
-			if err != nil {
+
+	// the highest-priority network is the primary and is included in the ContainerCreate API
+	// call via container.NetworkMode & network.NetworkingConfig
+	// any remaining networks are connected one-by-one here after creation (but before start)
+	serviceNetworks := service.NetworksByPriority()
+	if len(serviceNetworks) > 1 {
+		for _, networkKey := range serviceNetworks[1:] {
+			mobyNetworkName := project.Networks[networkKey].Name
+			epSettings := createEndpointSettings(project, service, number, networkKey, cfgs.Links, opts.UseNetworkAliases)
+			if err := s.apiClient().NetworkConnect(ctx, mobyNetworkName, created.ID, epSettings); err != nil {
 				return created, err
 				return created, err
 			}
 			}
 		}
 		}
-		err = s.connectContainerToNetwork(ctx, created.ID, netwrk.Name, cfg, links, aliases...)
-		if err != nil {
-			return created, err
-		}
 	}
 	}
 
 
 	err = s.injectSecrets(ctx, project, service, created.ID)
 	err = s.injectSecrets(ctx, project, service, created.ID)
@@ -635,43 +621,6 @@ func (s *composeService) getLinks(ctx context.Context, projectName string, servi
 	return links, nil
 	return links, nil
 }
 }
 
 
-func shortIDAliasExists(containerID string, aliases ...string) bool {
-	for _, alias := range aliases {
-		if alias == containerID[:12] {
-			return true
-		}
-	}
-	return false
-}
-
-func (s *composeService) connectContainerToNetwork(ctx context.Context, id string, netwrk string, cfg *types.ServiceNetworkConfig, links []string, aliases ...string) error {
-	var (
-		ipv4Address string
-		ipv6Address string
-		ipam        *network.EndpointIPAMConfig
-	)
-	if cfg != nil {
-		ipv4Address = cfg.Ipv4Address
-		ipv6Address = cfg.Ipv6Address
-		ipam = &network.EndpointIPAMConfig{
-			IPv4Address:  ipv4Address,
-			IPv6Address:  ipv6Address,
-			LinkLocalIPs: cfg.LinkLocalIPs,
-		}
-	}
-	err := s.apiClient().NetworkConnect(ctx, netwrk, id, &network.EndpointSettings{
-		Aliases:           aliases,
-		IPAddress:         ipv4Address,
-		GlobalIPv6Address: ipv6Address,
-		Links:             links,
-		IPAMConfig:        ipam,
-	})
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
 func (s *composeService) isServiceHealthy(ctx context.Context, containers Containers, fallbackRunning bool) (bool, error) {
 func (s *composeService) isServiceHealthy(ctx context.Context, containers Containers, fallbackRunning bool) (bool, error) {
 	for _, c := range containers {
 	for _, c := range containers {
 		container, err := s.apiClient().ContainerInspect(ctx, c.ID)
 		container, err := s.apiClient().ContainerInspect(ctx, c.ID)

+ 97 - 68
pkg/compose/create.go

@@ -48,6 +48,20 @@ import (
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/compose/v2/pkg/utils"
 )
 )
 
 
+type createOptions struct {
+	AutoRemove        bool
+	AttachStdin       bool
+	UseNetworkAliases bool
+	Labels            types.Labels
+}
+
+type createConfigs struct {
+	Container *container.Config
+	Host      *container.HostConfig
+	Network   *network.NetworkingConfig
+	Links     []string
+}
+
 func (s *composeService) Create(ctx context.Context, project *types.Project, options api.CreateOptions) error {
 func (s *composeService) Create(ctx context.Context, project *types.Project, options api.CreateOptions) error {
 	return progress.RunWithTitle(ctx, func(ctx context.Context) error {
 	return progress.RunWithTitle(ctx, func(ctx context.Context) error {
 		return s.create(ctx, project, options)
 		return s.create(ctx, project, options)
@@ -166,18 +180,16 @@ func (s *composeService) ensureProjectVolumes(ctx context.Context, project *type
 	return nil
 	return nil
 }
 }
 
 
-func (s *composeService) getCreateOptions(ctx context.Context,
+func (s *composeService) getCreateConfigs(ctx context.Context,
 	p *types.Project,
 	p *types.Project,
 	service types.ServiceConfig,
 	service types.ServiceConfig,
 	number int,
 	number int,
 	inherit *moby.Container,
 	inherit *moby.Container,
-	autoRemove, attachStdin bool,
-	labels types.Labels,
-) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
-
-	labels, err := s.prepareLabels(labels, service, number)
+	opts createOptions,
+) (createConfigs, error) {
+	labels, err := s.prepareLabels(opts.Labels, service, number)
 	if err != nil {
 	if err != nil {
-		return nil, nil, nil, err
+		return createConfigs{}, err
 	}
 	}
 
 
 	var (
 	var (
@@ -196,11 +208,6 @@ func (s *composeService) getCreateOptions(ctx context.Context,
 		stdinOpen = service.StdinOpen
 		stdinOpen = service.StdinOpen
 	)
 	)
 
 
-	binds, mounts, err := s.buildContainerVolumes(ctx, *p, service, inherit)
-	if err != nil {
-		return nil, nil, nil, err
-	}
-
 	proxyConfig := types.MappingWithEquals(s.configFile().ParseProxyConfig(s.apiClient().DaemonHost(), nil))
 	proxyConfig := types.MappingWithEquals(s.configFile().ParseProxyConfig(s.apiClient().DaemonHost(), nil))
 	env := proxyConfig.OverrideBy(service.Environment)
 	env := proxyConfig.OverrideBy(service.Environment)
 
 
@@ -211,8 +218,8 @@ func (s *composeService) getCreateOptions(ctx context.Context,
 		ExposedPorts:    buildContainerPorts(service),
 		ExposedPorts:    buildContainerPorts(service),
 		Tty:             tty,
 		Tty:             tty,
 		OpenStdin:       stdinOpen,
 		OpenStdin:       stdinOpen,
-		StdinOnce:       attachStdin && stdinOpen,
-		AttachStdin:     attachStdin,
+		StdinOnce:       opts.AttachStdin && stdinOpen,
+		AttachStdin:     opts.AttachStdin,
 		AttachStderr:    true,
 		AttachStderr:    true,
 		AttachStdout:    true,
 		AttachStdout:    true,
 		Cmd:             runCmd,
 		Cmd:             runCmd,
@@ -228,20 +235,7 @@ func (s *composeService) getCreateOptions(ctx context.Context,
 		StopTimeout:     ToSeconds(service.StopGracePeriod),
 		StopTimeout:     ToSeconds(service.StopGracePeriod),
 	}
 	}
 
 
-	portBindings := buildContainerPortBindingOptions(service)
-
-	resources := getDeployResources(service)
-
-	if service.NetworkMode == "" {
-		service.NetworkMode = getDefaultNetworkMode(p, service)
-	}
-
-	var networkConfig *network.NetworkingConfig
-	for _, id := range service.NetworksByPriority() {
-		networkConfig = s.createNetworkConfig(p, service, id)
-		break
-	}
-
+	// VOLUMES/MOUNTS/FILESYSTEMS
 	tmpfs := map[string]string{}
 	tmpfs := map[string]string{}
 	for _, t := range service.Tmpfs {
 	for _, t := range service.Tmpfs {
 		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
 		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
@@ -250,39 +244,47 @@ func (s *composeService) getCreateOptions(ctx context.Context,
 			tmpfs[arr[0]] = ""
 			tmpfs[arr[0]] = ""
 		}
 		}
 	}
 	}
-
-	var logConfig container.LogConfig
-	if service.Logging != nil {
-		logConfig = container.LogConfig{
-			Type:   service.Logging.Driver,
-			Config: service.Logging.Options,
-		}
+	binds, mounts, err := s.buildContainerVolumes(ctx, *p, service, inherit)
+	if err != nil {
+		return createConfigs{}, err
 	}
 	}
-
 	var volumesFrom []string
 	var volumesFrom []string
 	for _, v := range service.VolumesFrom {
 	for _, v := range service.VolumesFrom {
 		if !strings.HasPrefix(v, "container:") {
 		if !strings.HasPrefix(v, "container:") {
-			return nil, nil, nil, fmt.Errorf("invalid volume_from: %s", v)
+			return createConfigs{}, fmt.Errorf("invalid volume_from: %s", v)
 		}
 		}
 		volumesFrom = append(volumesFrom, v[len("container:"):])
 		volumesFrom = append(volumesFrom, v[len("container:"):])
 	}
 	}
 
 
+	// NETWORKING
 	links, err := s.getLinks(ctx, p.Name, service, number)
 	links, err := s.getLinks(ctx, p.Name, service, number)
 	if err != nil {
 	if err != nil {
-		return nil, nil, nil, err
+		return createConfigs{}, err
 	}
 	}
+	networkMode, networkingConfig := defaultNetworkSettings(p, service, number, links, opts.UseNetworkAliases)
+	portBindings := buildContainerPortBindingOptions(service)
 
 
+	// MISC
+	resources := getDeployResources(service)
+	var logConfig container.LogConfig
+	if service.Logging != nil {
+		logConfig = container.LogConfig{
+			Type:   service.Logging.Driver,
+			Config: service.Logging.Options,
+		}
+	}
 	securityOpts, unconfined, err := parseSecurityOpts(p, service.SecurityOpt)
 	securityOpts, unconfined, err := parseSecurityOpts(p, service.SecurityOpt)
 	if err != nil {
 	if err != nil {
-		return nil, nil, nil, err
+		return createConfigs{}, err
 	}
 	}
+
 	hostConfig := container.HostConfig{
 	hostConfig := container.HostConfig{
-		AutoRemove:     autoRemove,
+		AutoRemove:     opts.AutoRemove,
 		Binds:          binds,
 		Binds:          binds,
 		Mounts:         mounts,
 		Mounts:         mounts,
 		CapAdd:         strslice.StrSlice(service.CapAdd),
 		CapAdd:         strslice.StrSlice(service.CapAdd),
 		CapDrop:        strslice.StrSlice(service.CapDrop),
 		CapDrop:        strslice.StrSlice(service.CapDrop),
-		NetworkMode:    container.NetworkMode(service.NetworkMode),
+		NetworkMode:    networkMode,
 		Init:           service.Init,
 		Init:           service.Init,
 		IpcMode:        container.IpcMode(service.Ipc),
 		IpcMode:        container.IpcMode(service.Ipc),
 		CgroupnsMode:   container.CgroupnsMode(service.Cgroup),
 		CgroupnsMode:   container.CgroupnsMode(service.Cgroup),
@@ -317,12 +319,28 @@ func (s *composeService) getCreateOptions(ctx context.Context,
 		hostConfig.ReadonlyPaths = []string{}
 		hostConfig.ReadonlyPaths = []string{}
 	}
 	}
 
 
-	return &containerConfig, &hostConfig, networkConfig, nil
+	cfgs := createConfigs{
+		Container: &containerConfig,
+		Host:      &hostConfig,
+		Network:   networkingConfig,
+		Links:     links,
+	}
+	return cfgs, nil
+}
+
+func getAliases(project *types.Project, service types.ServiceConfig, serviceIndex int, networkKey string, useNetworkAliases bool) []string {
+	aliases := []string{getContainerName(project.Name, service, serviceIndex)}
+	if useNetworkAliases {
+		aliases = append(aliases, service.Name)
+		if cfg := service.Networks[networkKey]; cfg != nil {
+			aliases = append(aliases, cfg.Aliases...)
+		}
+	}
+	return aliases
 }
 }
 
 
-func (s *composeService) createNetworkConfig(p *types.Project, service types.ServiceConfig, networkID string) *network.NetworkingConfig {
-	net := p.Networks[networkID]
-	config := service.Networks[networkID]
+func createEndpointSettings(p *types.Project, service types.ServiceConfig, serviceIndex int, networkKey string, links []string, useNetworkAliases bool) *network.EndpointSettings {
+	config := service.Networks[networkKey]
 	var ipam *network.EndpointIPAMConfig
 	var ipam *network.EndpointIPAMConfig
 	var (
 	var (
 		ipv4Address string
 		ipv4Address string
@@ -337,15 +355,12 @@ func (s *composeService) createNetworkConfig(p *types.Project, service types.Ser
 			LinkLocalIPs: config.LinkLocalIPs,
 			LinkLocalIPs: config.LinkLocalIPs,
 		}
 		}
 	}
 	}
-	return &network.NetworkingConfig{
-		EndpointsConfig: map[string]*network.EndpointSettings{
-			net.Name: {
-				Aliases:     getAliases(service, config),
-				IPAddress:   ipv4Address,
-				IPv6Gateway: ipv6Address,
-				IPAMConfig:  ipam,
-			},
-		},
+	return &network.EndpointSettings{
+		Aliases:     getAliases(p, service, serviceIndex, networkKey, useNetworkAliases),
+		Links:       links,
+		IPAddress:   ipv4Address,
+		IPv6Gateway: ipv6Address,
+		IPAMConfig:  ipam,
 	}
 	}
 }
 }
 
 
@@ -404,17 +419,39 @@ func (s *composeService) prepareLabels(labels types.Labels, service types.Servic
 	return labels, nil
 	return labels, nil
 }
 }
 
 
-func getDefaultNetworkMode(project *types.Project, service types.ServiceConfig) string {
+// defaultNetworkSettings determines the container.NetworkMode and corresponding network.NetworkingConfig (nil if not applicable).
+func defaultNetworkSettings(
+	project *types.Project,
+	service types.ServiceConfig,
+	serviceIndex int,
+	links []string,
+	useNetworkAliases bool,
+) (container.NetworkMode, *network.NetworkingConfig) {
+	if service.NetworkMode != "" {
+		return container.NetworkMode(service.NetworkMode), nil
+	}
+
 	if len(project.Networks) == 0 {
 	if len(project.Networks) == 0 {
-		return "none"
+		return "none", nil
 	}
 	}
 
 
+	var networkKey string
 	if len(service.Networks) > 0 {
 	if len(service.Networks) > 0 {
-		name := service.NetworksByPriority()[0]
-		return project.Networks[name].Name
+		networkKey = service.NetworksByPriority()[0]
+	} else {
+		networkKey = "default"
 	}
 	}
-
-	return project.Networks["default"].Name
+	mobyNetworkName := project.Networks[networkKey].Name
+	epSettings := createEndpointSettings(project, service, serviceIndex, networkKey, links, useNetworkAliases)
+	networkConfig := &network.NetworkingConfig{
+		EndpointsConfig: map[string]*network.EndpointSettings{
+			mobyNetworkName: epSettings,
+		},
+	}
+	// From the Engine API docs:
+	// > Supported standard values are: bridge, host, none, and container:<name|id>.
+	// > Any other value is taken as a custom network's name to which this container should connect to.
+	return container.NetworkMode(mobyNetworkName), networkConfig
 }
 }
 
 
 func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
 func getRestartPolicy(service types.ServiceConfig) container.RestartPolicy {
@@ -1002,14 +1039,6 @@ func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
 	}
 	}
 }
 }
 
 
-func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
-	aliases := []string{s.Name}
-	if c != nil {
-		aliases = append(aliases, c.Aliases...)
-	}
-	return aliases
-}
-
 func (s *composeService) ensureNetwork(ctx context.Context, n *types.NetworkConfig) error {
 func (s *composeService) ensureNetwork(ctx context.Context, n *types.NetworkConfig) error {
 	if n.External.External {
 	if n.External.External {
 		return s.resolveExternalNetwork(ctx, n)
 		return s.resolveExternalNetwork(ctx, n)

+ 34 - 4
pkg/compose/create_test.go

@@ -22,6 +22,8 @@ import (
 	"sort"
 	"sort"
 	"testing"
 	"testing"
 
 
+	"gotest.tools/v3/assert/cmp"
+
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
 
 
 	composetypes "github.com/compose-spec/compose-go/types"
 	composetypes "github.com/compose-spec/compose-go/types"
@@ -203,7 +205,7 @@ func TestBuildContainerMountOptions(t *testing.T) {
 	assert.Equal(t, mounts[2].Target, "\\\\.\\pipe\\docker_engine")
 	assert.Equal(t, mounts[2].Target, "\\\\.\\pipe\\docker_engine")
 }
 }
 
 
-func TestGetDefaultNetworkMode(t *testing.T) {
+func TestDefaultNetworkSettings(t *testing.T) {
 	t.Run("returns the network with the highest priority when service has multiple networks", func(t *testing.T) {
 	t.Run("returns the network with the highest priority when service has multiple networks", func(t *testing.T) {
 		service := composetypes.ServiceConfig{
 		service := composetypes.ServiceConfig{
 			Name: "myService",
 			Name: "myService",
@@ -231,7 +233,10 @@ func TestGetDefaultNetworkMode(t *testing.T) {
 			}),
 			}),
 		}
 		}
 
 
-		assert.Equal(t, getDefaultNetworkMode(&project, service), "myProject_myNetwork2")
+		networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true)
+		assert.Equal(t, string(networkMode), "myProject_myNetwork2")
+		assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 1))
+		assert.Check(t, cmp.Contains(networkConfig.EndpointsConfig, "myProject_myNetwork2"))
 	})
 	})
 
 
 	t.Run("returns default network when service has no networks", func(t *testing.T) {
 	t.Run("returns default network when service has no networks", func(t *testing.T) {
@@ -256,7 +261,10 @@ func TestGetDefaultNetworkMode(t *testing.T) {
 			}),
 			}),
 		}
 		}
 
 
-		assert.Equal(t, getDefaultNetworkMode(&project, service), "myProject_default")
+		networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true)
+		assert.Equal(t, string(networkMode), "myProject_default")
+		assert.Check(t, cmp.Len(networkConfig.EndpointsConfig, 1))
+		assert.Check(t, cmp.Contains(networkConfig.EndpointsConfig, "myProject_default"))
 	})
 	})
 
 
 	t.Run("returns none if project has no networks", func(t *testing.T) {
 	t.Run("returns none if project has no networks", func(t *testing.T) {
@@ -270,6 +278,28 @@ func TestGetDefaultNetworkMode(t *testing.T) {
 			},
 			},
 		}
 		}
 
 
-		assert.Equal(t, getDefaultNetworkMode(&project, service), "none")
+		networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true)
+		assert.Equal(t, string(networkMode), "none")
+		assert.Check(t, cmp.Nil(networkConfig))
+	})
+
+	t.Run("returns defined network mode if explicitly set", func(t *testing.T) {
+		service := composetypes.ServiceConfig{
+			Name:        "myService",
+			NetworkMode: "host",
+		}
+		project := composetypes.Project{
+			Name:     "myProject",
+			Services: []composetypes.ServiceConfig{service},
+			Networks: composetypes.Networks(map[string]composetypes.NetworkConfig{
+				"default": {
+					Name: "myProject_default",
+				},
+			}),
+		}
+
+		networkMode, networkConfig := defaultNetworkSettings(&project, service, 1, nil, true)
+		assert.Equal(t, string(networkMode), "host")
+		assert.Check(t, cmp.Nil(networkConfig))
 	})
 	})
 }
 }

+ 8 - 2
pkg/compose/run.go

@@ -99,8 +99,14 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
 			return "", err
 			return "", err
 		}
 		}
 	}
 	}
-	created, err := s.createContainer(ctx, project, service, service.ContainerName, 1,
-		opts.AutoRemove, opts.UseNetworkAliases, opts.Interactive)
+	createOpts := createOptions{
+		AutoRemove:        opts.AutoRemove,
+		AttachStdin:       opts.Interactive,
+		UseNetworkAliases: opts.UseNetworkAliases,
+		Labels:            mergeLabels(service.Labels, service.CustomLabels),
+	}
+
+	created, err := s.createContainer(ctx, project, service, service.ContainerName, 1, createOpts)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}