diff --git a/cmd/nerdctl/container/container_inspect_linux_test.go b/cmd/nerdctl/container/container_inspect_linux_test.go index 9528d0553e0..398caee7f72 100644 --- a/cmd/nerdctl/container/container_inspect_linux_test.go +++ b/cmd/nerdctl/container/container_inspect_linux_test.go @@ -18,6 +18,7 @@ package container import ( "fmt" + "os" "strings" "testing" @@ -68,13 +69,12 @@ func TestContainerInspectContainsMounts(t *testing.T) { testutil.NginxAlpineImage).AssertOK() inspect := base.InspectContainer(testContainer) - // convert array to map to get by key of Destination actual := make(map[string]dockercompat.MountPoint) for i := range inspect.Mounts { actual[inspect.Mounts[i].Destination] = inspect.Mounts[i] } - + t.Logf("actual in TestContainerInspectContainsMounts: %+v", actual) const localDriver = "local" expected := []struct { @@ -249,13 +249,11 @@ func TestContainerInspectHostConfig(t *testing.T) { "--add-host", "host2:10.0.0.2", "--ipc", "host", "--memory", "512m", - "--oom-kill-disable", "--read-only", - "--uts", "host", "--shm-size", "256m", - "--runtime", "io.containerd.runtime.v1.linux", + "--uts", "host", "--sysctl", "net.core.somaxconn=1024", - "--device", "/dev/null:/dev/null", + "--runtime", "io.containerd.runc.v2", testutil.AlpineImage, "sleep", "infinity").AssertOK() inspect := base.InspectContainer(testContainer) @@ -265,24 +263,16 @@ func TestContainerInspectHostConfig(t *testing.T) { assert.Equal(t, uint16(500), inspect.HostConfig.BlkioWeight) assert.Equal(t, uint64(1024), inspect.HostConfig.CPUShares) assert.Equal(t, int64(100000), inspect.HostConfig.CPUQuota) - assert.DeepEqual(t, []string{"1000", "2000"}, inspect.HostConfig.GroupAdd) + assert.Assert(t, contains(inspect.HostConfig.GroupAdd, "1000"), "Expected '1000' to be in GroupAdd") + assert.Assert(t, contains(inspect.HostConfig.GroupAdd, "2000"), "Expected '2000' to be in GroupAdd") expectedExtraHosts := []string{"host1:10.0.0.1", "host2:10.0.0.2"} assert.DeepEqual(t, expectedExtraHosts, inspect.HostConfig.ExtraHosts) assert.Equal(t, "host", inspect.HostConfig.IpcMode) - assert.Equal(t, "json-file", inspect.HostConfig.LogConfig.Driver) assert.Equal(t, int64(536870912), inspect.HostConfig.Memory) assert.Equal(t, int64(1073741824), inspect.HostConfig.MemorySwap) - assert.Equal(t, bool(true), inspect.HostConfig.OomKillDisable) assert.Equal(t, true, inspect.HostConfig.ReadonlyRootfs) assert.Equal(t, "host", inspect.HostConfig.UTSMode) assert.Equal(t, int64(268435456), inspect.HostConfig.ShmSize) - assert.Equal(t, "io.containerd.runtime.v1.linux", inspect.HostConfig.Runtime) - expectedSysctls := map[string]string{ - "net.core.somaxconn": "1024", - } - assert.DeepEqual(t, expectedSysctls, inspect.HostConfig.Sysctls) - expectedDevices := []string{"/dev/null:/dev/null"} - assert.DeepEqual(t, expectedDevices, inspect.HostConfig.Devices) } func TestContainerInspectHostConfigDefaults(t *testing.T) { @@ -291,26 +281,41 @@ func TestContainerInspectHostConfigDefaults(t *testing.T) { base := testutil.NewBase(t) defer base.Cmd("rm", "-f", testContainer).Run() + var hc hostConfigValues + + if testutil.GetTarget() == testutil.Docker { + hc.Driver = "" + hc.GroupAddSize = 0 + hc.ShmSize = int64(67108864) + hc.Runtime = "runc" + } else { + hc.GroupAddSize = 10 + hc.Driver = "json-file" + hc.ShmSize = int64(0) + hc.Runtime = "io.containerd.runc.v2" + } + // Run a container without specifying HostConfig options base.Cmd("run", "-d", "--name", testContainer, testutil.AlpineImage, "sleep", "infinity").AssertOK() inspect := base.InspectContainer(testContainer) + t.Logf("HostConfig in TestContainerInspectHostConfigDefaults: %+v", inspect.HostConfig) assert.Equal(t, "", inspect.HostConfig.CPUSetCPUs) assert.Equal(t, "", inspect.HostConfig.CPUSetMems) assert.Equal(t, uint16(0), inspect.HostConfig.BlkioWeight) assert.Equal(t, uint64(0), inspect.HostConfig.CPUShares) assert.Equal(t, int64(0), inspect.HostConfig.CPUQuota) - assert.Equal(t, 0, len(inspect.HostConfig.GroupAdd)) + assert.Equal(t, hc.GroupAddSize, len(inspect.HostConfig.GroupAdd)) assert.Equal(t, 0, len(inspect.HostConfig.ExtraHosts)) - assert.Equal(t, "", inspect.HostConfig.IpcMode) - assert.Equal(t, "json-file", inspect.HostConfig.LogConfig.Driver) + assert.Equal(t, "private", inspect.HostConfig.IpcMode) + assert.Equal(t, hc.Driver, inspect.HostConfig.LogConfig.Driver) assert.Equal(t, int64(0), inspect.HostConfig.Memory) assert.Equal(t, int64(0), inspect.HostConfig.MemorySwap) assert.Equal(t, bool(false), inspect.HostConfig.OomKillDisable) - assert.Equal(t, false, inspect.HostConfig.ReadonlyRootfs) + assert.Equal(t, bool(false), inspect.HostConfig.ReadonlyRootfs) assert.Equal(t, "", inspect.HostConfig.UTSMode) - assert.Equal(t, int64(67108864), inspect.HostConfig.ShmSize) - assert.Equal(t, "io.containerd.runc.v2", inspect.HostConfig.Runtime) + assert.Equal(t, hc.ShmSize, inspect.HostConfig.ShmSize) + assert.Equal(t, hc.Runtime, inspect.HostConfig.Runtime) assert.Equal(t, 0, len(inspect.HostConfig.Sysctls)) assert.Equal(t, 0, len(inspect.HostConfig.Devices)) } @@ -364,8 +369,8 @@ func TestContainerInspectHostConfigDNSDefaults(t *testing.T) { } func TestContainerInspectHostConfigPID(t *testing.T) { - testContainer1 := testutil.Identifier(t) - testContainer2 := testutil.Identifier(t) + testContainer1 := testutil.Identifier(t) + "-container1" + testContainer2 := testutil.Identifier(t) + "-container2" base := testutil.NewBase(t) defer base.Cmd("rm", "-f", testContainer1, testContainer2).Run() @@ -373,14 +378,23 @@ func TestContainerInspectHostConfigPID(t *testing.T) { // Run the first container base.Cmd("run", "-d", "--name", testContainer1, testutil.AlpineImage, "sleep", "infinity").AssertOK() - // Run a container with PID namespace options + containerID1 := strings.TrimSpace(base.Cmd("inspect", "-f", "{{.Id}}", testContainer1).Out()) + + var hc hostConfigValues + + if testutil.GetTarget() == testutil.Docker { + hc.PidMode = "container:" + containerID1 + } else { + hc.PidMode = containerID1 + } + base.Cmd("run", "-d", "--name", testContainer2, "--pid", fmt.Sprintf("container:%s", testContainer1), testutil.AlpineImage, "sleep", "infinity").AssertOK() inspect := base.InspectContainer(testContainer2) - assert.Equal(t, fmt.Sprintf("container:%s", testContainer1), inspect.HostConfig.PidMode) + assert.Equal(t, hc.PidMode, inspect.HostConfig.PidMode) } @@ -390,11 +404,59 @@ func TestContainerInspectHostConfigPIDDefaults(t *testing.T) { base := testutil.NewBase(t) defer base.Cmd("rm", "-f", testContainer).Run() - // Run a container without specifying PID options base.Cmd("run", "-d", "--name", testContainer, testutil.AlpineImage, "sleep", "infinity").AssertOK() inspect := base.InspectContainer(testContainer) - // Check that PID mode is empty (private) by default assert.Equal(t, "", inspect.HostConfig.PidMode) } + +func TestContainerInspectDevices(t *testing.T) { + testContainer := testutil.Identifier(t) + + base := testutil.NewBase(t) + defer base.Cmd("rm", "-f", testContainer).Run() + + // Create a temporary directory + dir, err := os.MkdirTemp(t.TempDir(), "device-dir") + if err != nil { + t.Fatal(err) + } + + if testutil.GetTarget() == testutil.Docker { + dir = "/dev/zero" + } + + // Run the container with the directory mapped as a device + base.Cmd("run", "-d", "--name", testContainer, + "--device", dir+":/dev/xvda", + testutil.AlpineImage, "sleep", "infinity").AssertOK() + + inspect := base.InspectContainer(testContainer) + + expectedDevices := []dockercompat.DeviceMapping{ + { + PathOnHost: dir, + PathInContainer: "/dev/xvda", + CgroupPermissions: "rwm", + }, + } + assert.DeepEqual(t, expectedDevices, inspect.HostConfig.Devices) +} + +func contains(slice []string, item string) bool { + for _, s := range slice { + if s == item { + return true + } + } + return false +} + +type hostConfigValues struct { + Driver string + ShmSize int64 + PidMode string + GroupAddSize int + Runtime string +} diff --git a/pkg/cmd/container/create.go b/pkg/cmd/container/create.go index 2e5675bec8b..44873a53c4d 100644 --- a/pkg/cmd/container/create.go +++ b/pkg/cmd/container/create.go @@ -224,6 +224,9 @@ func Create(ctx context.Context, client *containerd.Client, args []string, netMa } internalLabels.logURI = logConfig.LogURI internalLabels.logConfig = logConfig + if logConfig.Driver == "" && logConfig.Address == options.GOptions.Address { + internalLabels.logConfig.Driver = "json-file" + } restartOpts, err := generateRestartOpts(ctx, client, options.Restart, logConfig.LogURI, options.InRun) if err != nil { @@ -660,7 +663,7 @@ type internalLabels struct { groupAdd []string // label for device mapping set by the --device flag - deviceMapping []string + deviceMapping []dockercompat.DeviceMapping } // WithInternalLabels sets the internal labels for a container. @@ -770,7 +773,7 @@ func withInternalLabels(internalLabels internalLabels) (containerd.NewContainerO } if len(internalLabels.deviceMapping) > 0 { - hostConfigLabel.DeviceMapping = internalLabels.deviceMapping + hostConfigLabel.Devices = append(hostConfigLabel.Devices, internalLabels.deviceMapping...) } hostConfigJSON, err := json.Marshal(hostConfigLabel) diff --git a/pkg/cmd/container/run_cgroup_linux.go b/pkg/cmd/container/run_cgroup_linux.go index cfb668debde..a4d6fb7a266 100644 --- a/pkg/cmd/container/run_cgroup_linux.go +++ b/pkg/cmd/container/run_cgroup_linux.go @@ -32,6 +32,7 @@ import ( "github.com/containerd/nerdctl/v2/pkg/api/types" "github.com/containerd/nerdctl/v2/pkg/infoutil" + "github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat" "github.com/containerd/nerdctl/v2/pkg/rootlessutil" ) @@ -206,7 +207,11 @@ func generateCgroupOpts(id string, options types.ContainerCreateOptions, interna return nil, fmt.Errorf("failed to parse device %q: %w", f, err) } opts = append(opts, oci.WithDevices(devPath, conPath, mode)) - internalLabels.deviceMapping = append(internalLabels.deviceMapping, f) + var deviceMap dockercompat.DeviceMapping + deviceMap.PathOnHost = devPath + deviceMap.PathInContainer = conPath + deviceMap.CgroupPermissions = mode + internalLabels.deviceMapping = append(internalLabels.deviceMapping, deviceMap) } return opts, nil diff --git a/pkg/inspecttypes/dockercompat/dockercompat.go b/pkg/inspecttypes/dockercompat/dockercompat.go index d4fea7e667e..934c95d9db3 100644 --- a/pkg/inspecttypes/dockercompat/dockercompat.go +++ b/pkg/inspecttypes/dockercompat/dockercompat.go @@ -96,7 +96,7 @@ type ImageMetadata struct { LastTagTime time.Time `json:",omitempty"` } -type loggerLogConfig struct { +type LoggerLogConfig struct { Driver string `json:"driver"` Opts map[string]string `json:"opts,omitempty"` LogURI string `json:"-"` @@ -140,7 +140,7 @@ type Container struct { type HostConfig struct { ExtraHosts []string // List of extra hosts PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host - LogConfig loggerLogConfig // Configuration of the logs for this container + LogConfig LoggerLogConfig // Configuration of the logs for this container BlkioWeight uint16 // Block IO weight (relative weight vs. other containers) CPUSetMems string `json:"CpusetMems"` // CpusetMems 0-2, 0,1 CPUSetCPUs string `json:"CpusetCpus"` // CpusetCpus 0-2, 0,1 @@ -148,7 +148,7 @@ type HostConfig struct { CPUShares uint64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers) ContainerIDFile string // File (path) where the containerId is written GroupAdd []string // GroupAdd specifies additional groups to join - IpcMode string // IPC namespace to use for the container + IpcMode string `json:"IpcMode"` // IPC namespace to use for the container CgroupnsMode string // Cgroup namespace mode to use for the container Memory int64 // Memory limit (in bytes) MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap @@ -162,9 +162,9 @@ type HostConfig struct { ShmSize int64 // Size of /dev/shm in bytes. The size must be greater than 0. Sysctls map[string]string // List of Namespaced sysctls used for the container Runtime string // Runtime to use with this container - Devices []string // List of devices to map inside the container + Devices []DeviceMapping // List of devices to map inside the container PidMode string // PID namespace to use for the container - Tmpfs []MountPoint `json:",omitempty"` // List of tmpfs (mounts) used for the container + Tmpfs map[string]string `json:"Tmpfs,omitempty"` // List of tmpfs (mounts) used for the container } // From https://github.com/moby/moby/blob/v20.10.1/api/types/types.go#L416-L427 @@ -239,9 +239,15 @@ type DNSSettings struct { } type HostConfigLabel struct { - BlkioWeight uint16 - CidFile string - DeviceMapping []string + BlkioWeight uint16 + CidFile string + Devices []DeviceMapping +} + +type DeviceMapping struct { + PathOnHost string + PathInContainer string + CgroupPermissions string } type CPUSettings struct { @@ -335,19 +341,19 @@ func ContainerFromNative(n *native.Container) (*Container, error) { } } - var tmpfsMounts []MountPoint - + c.HostConfig.Tmpfs = make(map[string]string) if nerdctlMounts := n.Labels[labels.Mounts]; nerdctlMounts != "" { mounts, err := parseMounts(nerdctlMounts) if err != nil { return nil, err } c.Mounts = mounts - if len(mounts) > 0 { - tmpfsMounts = filterTmpfsMounts(mounts) + for _, mount := range mounts { + if mount.Type == "tmpfs" { + c.HostConfig.Tmpfs[mount.Destination] = mount.Mode + } } } - c.HostConfig.Tmpfs = tmpfsMounts if nedctlExtraHosts := n.Labels[labels.ExtraHosts]; nedctlExtraHosts != "" { c.HostConfig.ExtraHosts = parseExtraHosts(nedctlExtraHosts) @@ -357,7 +363,7 @@ func ContainerFromNative(n *native.Container) (*Container, error) { c.HostConfig.LogConfig.LogURI = nerdctlLoguri } if logConfigJSON, ok := n.Labels[labels.LogConfig]; ok { - var logConfig loggerLogConfig + var logConfig LoggerLogConfig err := json.Unmarshal([]byte(logConfigJSON), &logConfig) if err != nil { return nil, fmt.Errorf("failed to unmarshal log config: %v", err) @@ -367,7 +373,7 @@ func ContainerFromNative(n *native.Container) (*Container, error) { c.HostConfig.LogConfig = logConfig } else { // If LogConfig label is not present, set default values - c.HostConfig.LogConfig = loggerLogConfig{ + c.HostConfig.LogConfig = LoggerLogConfig{ Driver: "json-file", Opts: make(map[string]string), } @@ -385,6 +391,7 @@ func ContainerFromNative(n *native.Container) (*Container, error) { } c.HostConfig.GroupAdd = groupAdd + c.HostConfig.ShmSize = 0 if ipcMode := n.Labels[labels.IPC]; ipcMode != "" { ipc, err := ipcutil.DecodeIPCLabel(ipcMode) @@ -392,6 +399,13 @@ func ContainerFromNative(n *native.Container) (*Container, error) { return nil, fmt.Errorf("failed to Decode IPC Label: %v", err) } c.HostConfig.IpcMode = string(ipc.Mode) + if ipc.ShmSize != "" { + shmSize, err := units.RAMInBytes(ipc.ShmSize) + if err != nil { + return nil, fmt.Errorf("failed to parse ShmSize: %v", err) + } + c.HostConfig.ShmSize = shmSize + } } cs := new(ContainerState) @@ -467,9 +481,6 @@ func ContainerFromNative(n *native.Container) (*Container, error) { utsMode, _ := getUtsModeFromNative(n.Spec.(*specs.Spec)) c.HostConfig.UTSMode = utsMode - shmSize, _ := getShmSizeFromNative(n.Spec.(*specs.Spec)) - c.HostConfig.ShmSize = shmSize - sysctls, _ := getSysctlFromNative(n.Spec.(*specs.Spec)) c.HostConfig.Sysctls = sysctls @@ -486,7 +497,7 @@ func ContainerFromNative(n *native.Container) (*Container, error) { } c.Config.Hostname = hostname - c.HostConfig.Devices = hostConfigLabel.DeviceMapping + c.HostConfig.Devices = hostConfigLabel.Devices var pidMode string if n.Labels[labels.PIDContainer] != "" { @@ -563,18 +574,6 @@ func mountsFromNative(spMounts []specs.Mount) []MountPoint { return mountpoints } -// filterTmpfsMounts filters the tmpfs mounts -func filterTmpfsMounts(spMounts []MountPoint) []MountPoint { - mountpoints := make([]MountPoint, 0, len(spMounts)) - for _, m := range spMounts { - if m.Type == "tmpfs" { - mountpoints = append(mountpoints, m) - } - } - - return mountpoints -} - func statusFromNative(x containerd.Status, labels map[string]string) string { switch s := x.Status; s { case containerd.Stopped: @@ -794,27 +793,27 @@ func getUtsModeFromNative(sp *specs.Spec) (string, error) { return "host", nil } -func getShmSizeFromNative(sp *specs.Spec) (int64, error) { - var res int64 - - if len(sp.Mounts) > 0 { - for _, mount := range sp.Mounts { - if mount.Destination == "/dev/shm" { - for _, option := range mount.Options { - if strings.HasPrefix(option, "size=") { - sizeStr := strings.TrimPrefix(option, "size=") - size, err := units.RAMInBytes(sizeStr) - if err != nil { - return 0, fmt.Errorf("failed to parse shm size: %v", err) - } - res = size - } - } - } - } - } - return res, nil -} +// func getShmSizeFromNative(sp *specs.Spec) (int64, error) { +// var res int64 + +// if len(sp.Mounts) > 0 { +// for _, mount := range sp.Mounts { +// if mount.Destination == "/dev/shm" { +// for _, option := range mount.Options { +// if strings.HasPrefix(option, "size=") { +// sizeStr := strings.TrimPrefix(option, "size=") +// size, err := units.RAMInBytes(sizeStr) +// if err != nil { +// return 0, fmt.Errorf("failed to parse shm size: %v", err) +// } +// res = size +// } +// } +// } +// } +// } +// return res, nil +// } func getSysctlFromNative(sp *specs.Spec) (map[string]string, error) { var res map[string]string diff --git a/pkg/inspecttypes/dockercompat/dockercompat_test.go b/pkg/inspecttypes/dockercompat/dockercompat_test.go index ff84a5bd891..a85fdd24ea0 100644 --- a/pkg/inspecttypes/dockercompat/dockercompat_test.go +++ b/pkg/inspecttypes/dockercompat/dockercompat_test.go @@ -78,12 +78,12 @@ func TestContainerFromNative(t *testing.T) { HostConfig: &HostConfig{ PortBindings: nat.PortMap{}, GroupAdd: []string{}, - LogConfig: loggerLogConfig{ + LogConfig: LoggerLogConfig{ Driver: "json-file", Opts: map[string]string{}, }, UTSMode: "host", - Tmpfs: []MountPoint{}, + Tmpfs: map[string]string{}, }, Mounts: []MountPoint{ { @@ -163,11 +163,12 @@ func TestContainerFromNative(t *testing.T) { HostConfig: &HostConfig{ PortBindings: nat.PortMap{}, GroupAdd: []string{}, - LogConfig: loggerLogConfig{ + LogConfig: LoggerLogConfig{ Driver: "json-file", Opts: map[string]string{}, }, UTSMode: "host", + Tmpfs: map[string]string{}, }, Mounts: []MountPoint{ { @@ -244,11 +245,12 @@ func TestContainerFromNative(t *testing.T) { HostConfig: &HostConfig{ PortBindings: nat.PortMap{}, GroupAdd: []string{}, - LogConfig: loggerLogConfig{ + LogConfig: LoggerLogConfig{ Driver: "json-file", Opts: map[string]string{}, }, UTSMode: "host", + Tmpfs: map[string]string{}, }, Mounts: []MountPoint{ {