From db46eff9990cfff1be3a16fcb7e47518d10893ae Mon Sep 17 00:00:00 2001 From: Eric Li Date: Thu, 24 May 2018 11:28:53 +0800 Subject: [PATCH] feature: add container's network files Signed-off-by: Eric Li --- apis/swagger.yml | 5 +++ apis/types/container_config.go | 5 +++ cli/common_flags.go | 1 + cli/container.go | 56 ++++++++++++++-------------- cri/v1alpha1/cri_utils.go | 3 ++ cri/v1alpha2/cri_utils.go | 3 ++ daemon/mgr/container.go | 61 +++++++++++++++++++++--------- daemon/mgr/spec_mount.go | 68 ++++++++++++++++++++++++++++++++++ test/cli_run_test.go | 43 +++++++++++++++++++++ 9 files changed, 200 insertions(+), 45 deletions(-) diff --git a/apis/swagger.yml b/apis/swagger.yml index 7ae1bf3b9..2fab996a6 100644 --- a/apis/swagger.yml +++ b/apis/swagger.yml @@ -1787,6 +1787,11 @@ definitions: type: "boolean" x-nullable: false default: true + DisableNetworkFiles: + description: "Whether to generate the network files(/etc/hostname, /etc/hosts and /etc/resolv.conf) for container." + type: "boolean" + x-nullable: false + default: false ExposedPorts: description: "An object mapping ports to an empty object in the form:`{/: {}}`" type: "object" diff --git a/apis/types/container_config.go b/apis/types/container_config.go index 5e100d2e2..b2193788b 100644 --- a/apis/types/container_config.go +++ b/apis/types/container_config.go @@ -35,6 +35,9 @@ type ContainerConfig struct { // Command to run specified an array of strings. Cmd []string `json:"Cmd"` + // Whether to generate the network files(/etc/hostname, /etc/hosts and /etc/resolv.conf) for container. + DisableNetworkFiles bool `json:"DisableNetworkFiles,omitempty"` + // Set disk quota for container DiskQuota map[string]string `json:"DiskQuota,omitempty"` @@ -126,6 +129,8 @@ type ContainerConfig struct { /* polymorph ContainerConfig Cmd false */ +/* polymorph ContainerConfig DisableNetworkFiles false */ + /* polymorph ContainerConfig DiskQuota false */ /* polymorph ContainerConfig Domainname false */ diff --git a/cli/common_flags.go b/cli/common_flags.go index fdbcace74..5aab5f008 100644 --- a/cli/common_flags.go +++ b/cli/common_flags.go @@ -34,6 +34,7 @@ func addCommonFlags(flagSet *pflag.FlagSet) *container { flagSet.StringVar(&c.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image") flagSet.StringSliceVarP(&c.env, "env", "e", nil, "Set environment variables for container") flagSet.StringVar(&c.hostname, "hostname", "", "Set container's hostname") + flagSet.BoolVar(&c.disableNetworkFiles, "disable-network-files", false, "Disable the generation of network files(/etc/hostname, /etc/hosts and /etc/resolv.conf) for container. If true, no network files will be generated. Default false") // Intel RDT flagSet.StringVar(&c.IntelRdtL3Cbm, "intel-rdt-l3-cbm", "", "Limit container resource for Intel RDT/CAT which introduced in Linux 4.10 kernel") diff --git a/cli/container.go b/cli/container.go index ff3743a6d..6750e3ace 100644 --- a/cli/container.go +++ b/cli/container.go @@ -10,19 +10,20 @@ import ( ) type container struct { - labels []string - name string - tty bool - volume []string - volumesFrom []string - runtime string - env []string - entrypoint string - workdir string - user string - groupAdd []string - hostname string - rm bool + labels []string + name string + tty bool + volume []string + volumesFrom []string + runtime string + env []string + entrypoint string + workdir string + user string + groupAdd []string + hostname string + rm bool + disableNetworkFiles bool blkioWeight uint16 blkioWeightDevice WeightDevice @@ -173,20 +174,21 @@ func (c *container) config() (*types.ContainerCreateConfig, error) { config := &types.ContainerCreateConfig{ ContainerConfig: types.ContainerConfig{ - Tty: c.tty, - Env: c.env, - Entrypoint: strings.Fields(c.entrypoint), - WorkingDir: c.workdir, - User: c.user, - Hostname: strfmt.Hostname(c.hostname), - Labels: labels, - Rich: c.rich, - RichMode: c.richMode, - InitScript: c.initScript, - ExposedPorts: ports, - DiskQuota: diskQuota, - QuotaID: c.quotaID, - SpecAnnotation: specAnnotation, + Tty: c.tty, + Env: c.env, + Entrypoint: strings.Fields(c.entrypoint), + WorkingDir: c.workdir, + User: c.user, + Hostname: strfmt.Hostname(c.hostname), + DisableNetworkFiles: c.disableNetworkFiles, + Labels: labels, + Rich: c.rich, + RichMode: c.richMode, + InitScript: c.initScript, + ExposedPorts: ports, + DiskQuota: diskQuota, + QuotaID: c.quotaID, + SpecAnnotation: specAnnotation, }, HostConfig: &types.HostConfig{ diff --git a/cri/v1alpha1/cri_utils.go b/cri/v1alpha1/cri_utils.go index 56bd160fb..190462995 100644 --- a/cri/v1alpha1/cri_utils.go +++ b/cri/v1alpha1/cri_utils.go @@ -216,6 +216,9 @@ func applySandboxSecurityContext(lc *runtime.LinuxPodSandboxConfig, config *apit // applySandboxLinuxOptions applies LinuxPodSandboxConfig to pouch's HostConfig and ContainerCreateConfig. func applySandboxLinuxOptions(hc *apitypes.HostConfig, lc *runtime.LinuxPodSandboxConfig, createConfig *apitypes.ContainerCreateConfig, image string) error { + // apply the sandbox network_mode, "none" is default. + hc.NetworkMode = namespaceModeNone + if lc == nil { return nil } diff --git a/cri/v1alpha2/cri_utils.go b/cri/v1alpha2/cri_utils.go index 927239f24..d65b879ad 100644 --- a/cri/v1alpha2/cri_utils.go +++ b/cri/v1alpha2/cri_utils.go @@ -216,6 +216,9 @@ func applySandboxSecurityContext(lc *runtime.LinuxPodSandboxConfig, config *apit // applySandboxLinuxOptions applies LinuxPodSandboxConfig to pouch's HostConfig and ContainerCreateConfig. func applySandboxLinuxOptions(hc *apitypes.HostConfig, lc *runtime.LinuxPodSandboxConfig, createConfig *apitypes.ContainerCreateConfig, image string) error { + // apply the sandbox network_mode, "none" is default. + hc.NetworkMode = namespaceModeNone + if lc == nil { return nil } diff --git a/daemon/mgr/container.go b/daemon/mgr/container.go index 421003a1f..8ff4bc08a 100644 --- a/daemon/mgr/container.go +++ b/daemon/mgr/container.go @@ -250,6 +250,12 @@ func (mgr *ContainerManager) Create(ctx context.Context, name string, config *ty return nil, errors.Wrap(errtypes.ErrAlreadyExisted, "container name: "+name) } + // set hostname. + if config.Hostname.String() == "" { + // if hostname is empty, take the part of id as the hostname + config.Hostname = strfmt.Hostname(id[:12]) + } + // set container runtime if config.HostConfig.Runtime == "" { config.HostConfig.Runtime = mgr.Config.DefaultRuntime @@ -322,13 +328,12 @@ func (mgr *ContainerManager) Create(ctx context.Context, name string, config *ty networkMode := config.HostConfig.NetworkMode if networkMode == "" { config.HostConfig.NetworkMode = "bridge" - container.Config.NetworkDisabled = true } container.NetworkSettings = new(types.NetworkSettings) if len(config.NetworkingConfig.EndpointsConfig) > 0 { container.NetworkSettings.Networks = config.NetworkingConfig.EndpointsConfig } - if container.NetworkSettings.Networks == nil && networkMode != "" && !IsContainer(networkMode) { + if container.NetworkSettings.Networks == nil && !IsContainer(config.HostConfig.NetworkMode) { container.NetworkSettings.Networks = make(map[string]*types.EndpointSettings) container.NetworkSettings.Networks[config.HostConfig.NetworkMode] = new(types.EndpointSettings) } @@ -454,26 +459,31 @@ func (mgr *ContainerManager) start(ctx context.Context, c *Container, detachKeys c.ResolvConfPath = origContainer.ResolvConfPath c.Config.Hostname = origContainer.Config.Hostname c.Config.Domainname = origContainer.Config.Domainname - } + } else { + // initialise host network mode + if IsHost(networkMode) { + hostname, err := os.Hostname() + if err != nil { + return err + } + c.Config.Hostname = strfmt.Hostname(hostname) + } - // initialise host network mode - if IsHost(networkMode) { - hostname, err := os.Hostname() - if err != nil { + // build the network related path. + if err := mgr.buildNetworkRelatedPath(c); err != nil { return err } - c.Config.Hostname = strfmt.Hostname(hostname) - } - // initialise network endpoint - if c.NetworkSettings != nil { - for name, endpointSetting := range c.NetworkSettings.Networks { - endpoint := mgr.buildContainerEndpoint(c) - endpoint.Name = name - endpoint.EndpointConfig = endpointSetting - if _, err := mgr.NetworkMgr.EndpointCreate(ctx, endpoint); err != nil { - logrus.Errorf("failed to create endpoint: %v", err) - return err + // initialise network endpoint + if c.NetworkSettings != nil { + for name, endpointSetting := range c.NetworkSettings.Networks { + endpoint := mgr.buildContainerEndpoint(c) + endpoint.Name = name + endpoint.EndpointConfig = endpointSetting + if _, err := mgr.NetworkMgr.EndpointCreate(ctx, endpoint); err != nil { + logrus.Errorf("failed to create endpoint: %v", err) + return err + } } } } @@ -481,6 +491,21 @@ func (mgr *ContainerManager) start(ctx context.Context, c *Container, detachKeys return mgr.createContainerdContainer(ctx, c) } +// buildNetworkRelatedPath builds the network related path. +func (mgr *ContainerManager) buildNetworkRelatedPath(c *Container) error { + // set the hosts file path. + c.HostsPath = path.Join(mgr.Store.Path(c.ID), "hosts") + + // set the resolv.conf file path. + c.ResolvConfPath = path.Join(mgr.Store.Path(c.ID), "resolv.conf") + + // set the hostname file path. + c.HostnamePath = path.Join(mgr.Store.Path(c.ID), "hostname") + + // write the hostname file, other files are filled by libnetwork. + return ioutil.WriteFile(c.HostnamePath, []byte(c.Config.Hostname+"\n"), 0644) +} + func (mgr *ContainerManager) createContainerdContainer(ctx context.Context, c *Container) error { // CgroupParent from HostConfig will be first priority to use, // then will be value from mgr.Config.CgroupParent diff --git a/daemon/mgr/spec_mount.go b/daemon/mgr/spec_mount.go index f532e9b9a..c972adf21 100644 --- a/daemon/mgr/spec_mount.go +++ b/daemon/mgr/spec_mount.go @@ -3,8 +3,12 @@ package mgr import ( "context" "fmt" + "os" + + "github.com/alibaba/pouch/apis/types" specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" ) func clearReadonly(m *specs.Mount) { @@ -31,6 +35,11 @@ func setupMounts(ctx context.Context, c *Container, s *specs.Spec) error { return nil } for _, mp := range c.Mounts { + if trySetupNetworkMount(mp, c) { + // ignore the network mount, we will handle it later. + continue + } + // check duplicate mountpoint for _, sm := range mounts { if sm.Destination == mp.Destination { @@ -69,6 +78,12 @@ func setupMounts(ctx context.Context, c *Container, s *specs.Spec) error { Options: opts, }) } + + // if disable hostfiles, we will not mount the hosts files into container. + if !c.Config.DisableNetworkFiles { + mounts = append(mounts, generateNetworkMounts(c)...) + } + s.Mounts = mounts if c.HostConfig.Privileged { @@ -83,3 +98,56 @@ func setupMounts(ctx context.Context, c *Container, s *specs.Spec) error { } return nil } + +// generateNetworkMounts will generate network mounts. +func generateNetworkMounts(c *Container) []specs.Mount { + mounts := make([]specs.Mount, 0) + + fileBinds := []struct { + Name string + Source string + Dest string + }{ + {"HostnamePath", c.HostnamePath, "/etc/hostname"}, + {"HostsPath", c.HostsPath, "/etc/hosts"}, + {"ResolvConfPath", c.ResolvConfPath, "/etc/resolv.conf"}, + } + + for _, bind := range fileBinds { + if bind.Source != "" { + _, err := os.Stat(bind.Source) + if err != nil { + logrus.Warnf("%s set to %s, but stat error: %v, skip it", bind.Name, bind.Source, err) + } else { + mounts = append(mounts, specs.Mount{ + Source: bind.Source, + Destination: bind.Dest, + Type: "bind", + Options: []string{"rbind", "rprivate"}, + }) + } + } + } + + return mounts +} + +// trySetupNetworkMount will try to set network mount. +func trySetupNetworkMount(mount *types.MountPoint, c *Container) bool { + if mount.Destination == "/etc/hostname" { + c.HostnamePath = mount.Source + return true + } + + if mount.Destination == "/etc/hosts" { + c.HostsPath = mount.Source + return true + } + + if mount.Destination == "/etc/resolv.conf" { + c.ResolvConfPath = mount.Source + return true + } + + return false +} diff --git a/test/cli_run_test.go b/test/cli_run_test.go index 42445777b..eb13a4d52 100644 --- a/test/cli_run_test.go +++ b/test/cli_run_test.go @@ -332,3 +332,46 @@ func (suite *PouchRunSuite) TestRunWithRM(c *check.C) { output := command.PouchRun("inspect", cname).Stderr() c.Assert(util.PartialEqual(output, cname+": not found"), check.IsNil) } + +// TestRunWithDisableNetworkFiles is to verify running container with disable-network-files flag. +func (suite *PouchRunSuite) TestRunWithDisableNetworkFiles(c *check.C) { + // Run a container with disable-network-files flag + cname1 := "RunWithDisableNetworkFiles" + res := command.PouchRun("run", "--disable-network-files", "--name", cname1, + busyboxImage, "ls", "/etc") + defer DelContainerForceMultyTime(c, cname1) + + res.Assert(c, icmd.Success) + output := res.Stdout() + if strings.Contains(output, "hostname") { + c.Fatal("expected no /etc/hostname, but the file exists") + } + + if strings.Contains(output, "hosts") { + c.Fatal("expected no /etc/hosts, but the file exists") + } + + if strings.Contains(output, "resolv.conf") { + // ignore checking the existence of /etc/resolv.conf, because the busybox + // contains the file. + } + + // Run a container without disable-network-files flag + cname2 := "RunWithoutDisableNetworkFiles" + res = command.PouchRun("run", "--name", cname2, busyboxImage, "ls", "/etc") + defer DelContainerForceMultyTime(c, cname2) + + res.Assert(c, icmd.Success) + output = res.Stdout() + if !strings.Contains(output, "hostname") { + c.Fatal("expected /etc/hostname, but the file does not exist") + } + + if !strings.Contains(output, "hosts") { + c.Fatal("expected /etc/hosts, but the file does not exist") + } + + if !strings.Contains(output, "resolv.conf") { + c.Fatal("expected /etc/resolv.conf, but the file does not exist") + } +}