diff --git a/apis/swagger.yml b/apis/swagger.yml index c864eb566..9af513f33 100644 --- a/apis/swagger.yml +++ b/apis/swagger.yml @@ -2258,17 +2258,7 @@ definitions: A list of resource limits to set in the container. For example: `{"Name": "nofile", "Soft": 1024, "Hard": 2048}`" type: "array" items: - type: "object" - properties: - Name: - description: "Name of ulimit" - type: "string" - Soft: - description: "Soft limit" - type: "integer" - Hard: - description: "Hard limit" - type: "integer" + $ref: "#/definitions/Ulimit" # Applicable to Windows CpuCount: description: | @@ -2372,6 +2362,20 @@ definitions: PathInContainer: "/dev/deviceName" CgroupPermissions: "mrw" + Ulimit: + type: "object" + description: "A list of resource limits" + properties: + Name: + description: "Name of ulimit" + type: "string" + Soft: + description: "Soft limit" + type: "integer" + Hard: + description: "Hard limit" + type: "integer" + Container: description: | an array of Container contains response of Engine API: diff --git a/apis/types/resources.go b/apis/types/resources.go index 076a1e83f..0a8ceb62e 100644 --- a/apis/types/resources.go +++ b/apis/types/resources.go @@ -154,7 +154,7 @@ type Resources struct { // A list of resource limits to set in the container. For example: `{"Name": "nofile", "Soft": 1024, "Hard": 2048}`" // - Ulimits []*ResourcesUlimitsItems0 `json:"Ulimits"` + Ulimits []*Ulimit `json:"Ulimits"` } /* polymorph Resources BlkioDeviceReadBps false */ @@ -662,52 +662,3 @@ func (m *Resources) UnmarshalBinary(b []byte) error { *m = res return nil } - -// ResourcesUlimitsItems0 resources ulimits items0 -// swagger:model ResourcesUlimitsItems0 - -type ResourcesUlimitsItems0 struct { - - // Hard limit - Hard int64 `json:"Hard,omitempty"` - - // Name of ulimit - Name string `json:"Name,omitempty"` - - // Soft limit - Soft int64 `json:"Soft,omitempty"` -} - -/* polymorph ResourcesUlimitsItems0 Hard false */ - -/* polymorph ResourcesUlimitsItems0 Name false */ - -/* polymorph ResourcesUlimitsItems0 Soft false */ - -// Validate validates this resources ulimits items0 -func (m *ResourcesUlimitsItems0) Validate(formats strfmt.Registry) error { - var res []error - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -// MarshalBinary interface implementation -func (m *ResourcesUlimitsItems0) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *ResourcesUlimitsItems0) UnmarshalBinary(b []byte) error { - var res ResourcesUlimitsItems0 - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/apis/types/ulimit.go b/apis/types/ulimit.go new file mode 100644 index 000000000..378af6b02 --- /dev/null +++ b/apis/types/ulimit.go @@ -0,0 +1,62 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package types + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + strfmt "github.com/go-openapi/strfmt" + + "github.com/go-openapi/errors" + "github.com/go-openapi/swag" +) + +// Ulimit A list of resource limits +// swagger:model Ulimit + +type Ulimit struct { + + // Hard limit + Hard int64 `json:"Hard,omitempty"` + + // Name of ulimit + Name string `json:"Name,omitempty"` + + // Soft limit + Soft int64 `json:"Soft,omitempty"` +} + +/* polymorph Ulimit Hard false */ + +/* polymorph Ulimit Name false */ + +/* polymorph Ulimit Soft false */ + +// Validate validates this ulimit +func (m *Ulimit) Validate(formats strfmt.Registry) error { + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// MarshalBinary interface implementation +func (m *Ulimit) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Ulimit) UnmarshalBinary(b []byte) error { + var res Ulimit + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/cli/common_flags.go b/cli/common_flags.go index 952eb8c2c..23e0ac749 100644 --- a/cli/common_flags.go +++ b/cli/common_flags.go @@ -11,6 +11,10 @@ func addCommonFlags(flagSet *pflag.FlagSet) *container { // blkio flagSet.Uint16Var(&c.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable") flagSet.Var(&c.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)") + flagSet.Var(&c.blkioDeviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device") + flagSet.Var(&c.blkioDeviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device") + flagSet.Var(&c.blkioDeviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) from a device") + flagSet.Var(&c.blkioDeviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) from a device") // capbilities flagSet.StringSliceVar(&c.capAdd, "cap-add", nil, "Add Linux capabilities") @@ -25,10 +29,6 @@ func addCommonFlags(flagSet *pflag.FlagSet) *container { // device related options flagSet.StringSliceVarP(&c.devices, "device", "", nil, "Add a host device to the container") - flagSet.Var(&c.blkioDeviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device") - flagSet.Var(&c.blkioDeviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device") - flagSet.Var(&c.blkioDeviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) from a device") - flagSet.Var(&c.blkioDeviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) from a device") flagSet.BoolVar(&c.enableLxcfs, "enableLxcfs", false, "Enable lxcfs for the container, only effective when enable-lxcfs switched on in Pouchd") flagSet.StringVar(&c.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image") @@ -83,6 +83,7 @@ func addCommonFlags(flagSet *pflag.FlagSet) *container { flagSet.StringSliceVar(&c.volumesFrom, "volumes-from", nil, "set volumes from other containers, format is [:mode]") flagSet.StringVarP(&c.workdir, "workdir", "w", "", "Set the working directory in a container") + flagSet.Var(&c.ulimit, "ulimit", "Set container ulimit") flagSet.BoolVar(&c.rich, "rich", false, "Start container in rich container mode. (default false)") flagSet.StringVar(&c.richMode, "rich-mode", "", "Choose one rich container mode. dumb-init(default), systemd, sbin-init") diff --git a/cli/container.go b/cli/container.go index 35509d1b4..bbec790c8 100644 --- a/cli/container.go +++ b/cli/container.go @@ -68,6 +68,7 @@ type container struct { oomScoreAdj int64 specAnnotation []string cgroupParent string + ulimit Ulimit //add for rich container mode rich bool @@ -221,6 +222,7 @@ func (c *container) config() (*types.ContainerCreateConfig, error) { Devices: deviceMappings, IntelRdtL3Cbm: intelRdtL3Cbm, CgroupParent: c.cgroupParent, + Ulimits: c.ulimit.value(), }, EnableLxcfs: c.enableLxcfs, Privileged: c.privileged, diff --git a/cli/ulimit.go b/cli/ulimit.go new file mode 100644 index 000000000..724a2e53c --- /dev/null +++ b/cli/ulimit.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + + "github.com/alibaba/pouch/apis/types" + + units "github.com/docker/go-units" +) + +// Ulimit defines ulimit options. +type Ulimit struct { + values map[string]*units.Ulimit +} + +// Set implement Ulimit as pflag.Value interface. +func (u *Ulimit) Set(val string) error { + ul, err := units.ParseUlimit(val) + if err != nil { + return err + } + + if u.values == nil { + u.values = make(map[string]*units.Ulimit) + } + + u.values[ul.Name] = ul + return nil +} + +// String implement Ulimit as pflag.Value interface. +func (u *Ulimit) String() string { + var str []string + for _, ul := range u.values { + str = append(str, ul.String()) + } + + return fmt.Sprintf("%v", str) +} + +// Type implement Ulimit as pflag.Value interface. +func (u *Ulimit) Type() string { + return "value" +} + +// value return ulimit values as type ResourcesUlimitsItems0 +func (u *Ulimit) value() []*types.Ulimit { + var ulimit []*types.Ulimit + for _, ul := range u.values { + ulimit = append(ulimit, &types.Ulimit{ + Name: ul.Name, + Hard: ul.Hard, + Soft: ul.Soft, + }) + } + + return ulimit +} diff --git a/daemon/mgr/container.go b/daemon/mgr/container.go index ffcba21a4..ffa0e7ef7 100644 --- a/daemon/mgr/container.go +++ b/daemon/mgr/container.go @@ -318,6 +318,11 @@ func (mgr *ContainerManager) StartExec(ctx context.Context, execid string, confi return err } + // set exec process ulimit + if err := setupRlimits(ctx, c.meta.HostConfig, &specs.Spec{Process: process}); err != nil { + return err + } + execConfig.Running = true defer func() { if err != nil { diff --git a/daemon/mgr/spec_process.go b/daemon/mgr/spec_process.go index f231d1b94..023d5652f 100644 --- a/daemon/mgr/spec_process.go +++ b/daemon/mgr/spec_process.go @@ -4,6 +4,7 @@ import ( "context" "io/ioutil" "os" + "strings" "github.com/alibaba/pouch/apis/types" "github.com/alibaba/pouch/pkg/user" @@ -53,6 +54,10 @@ func setupProcess(ctx context.Context, c *ContainerMeta, s *specs.Spec) error { return err } + if err := setupRlimits(ctx, c.HostConfig, s); err != nil { + return err + } + if err := setupAppArmor(ctx, c, s); err != nil { return err } @@ -158,3 +163,17 @@ func setupAppArmor(ctx context.Context, c *ContainerMeta, s *specs.Spec) error { return nil } + +func setupRlimits(ctx context.Context, hostConfig *types.HostConfig, s *specs.Spec) error { + var rlimits []specs.POSIXRlimit + for _, ul := range hostConfig.Ulimits { + rlimits = append(rlimits, specs.POSIXRlimit{ + Type: "RLIMIT_" + strings.ToUpper(ul.Name), + Hard: uint64(ul.Hard), + Soft: uint64(ul.Soft), + }) + } + + s.Process.Rlimits = rlimits + return nil +} diff --git a/test/cli_create_test.go b/test/cli_create_test.go index e4cfedbac..ea74a1050 100644 --- a/test/cli_create_test.go +++ b/test/cli_create_test.go @@ -434,3 +434,19 @@ func (suite *PouchCreateSuite) TestCreateWithAnnotation(c *check.C) { c.Assert(util.PartialEqual(annotationStr, "a=b"), check.IsNil) c.Assert(util.PartialEqual(annotationStr, "foo=bar"), check.IsNil) } + +// TestCreateWithUlimit tests creating container with annotation. +func (suite *PouchCreateSuite) TestCreateWithUlimit(c *check.C) { + cname := "TestCreateWithUlimit" + command.PouchRun("create", "--ulimit", "nproc=21", "--name", cname, busyboxImage).Assert(c, icmd.Success) + + output := command.PouchRun("inspect", cname).Stdout() + result := []types.ContainerJSON{} + if err := json.Unmarshal([]byte(output), &result); err != nil { + c.Errorf("failed to decode inspect output: %v", err) + } + ul := result[0].HostConfig.Ulimits[0] + c.Assert(ul.Name, check.Equals, "nproc") + c.Assert(int(ul.Hard), check.Equals, 21) + c.Assert(int(ul.Soft), check.Equals, 21) +} diff --git a/test/cli_exec_test.go b/test/cli_exec_test.go index e4624322d..8523ed494 100644 --- a/test/cli_exec_test.go +++ b/test/cli_exec_test.go @@ -117,3 +117,13 @@ func (suite *PouchExecSuite) TestExecAfterContainerRestart(c *check.C) { c.Errorf("failed to exec in container: %s", out) } } + +// TestExecUlimit test ulimit set container. +func (suite *PouchExecSuite) TestExecUlimit(c *check.C) { + name := "TestExecUlimit" + command.PouchRun("run", "-d", "--name", name, "--ulimit", "nproc=256", busyboxImage, "top").Assert(c, icmd.Success) + defer DelContainerForceMultyTime(c, name) + + out := command.PouchRun("exec", name, "sh", "-c", "ulimit -p").Stdout() + c.Assert(out, check.Equals, "256\n") +} diff --git a/test/cli_run_test.go b/test/cli_run_test.go index 35a4b72c9..933b4edf2 100644 --- a/test/cli_run_test.go +++ b/test/cli_run_test.go @@ -1027,3 +1027,24 @@ func (suite *PouchRunSuite) TestRunWithVolumesFromWithDupclicate(c *check.C) { c.Assert(volumeFound, check.Equals, true) } + +// TestRunWithUlimit tests running container with --ulimit flag. +func (suite *PouchRunSuite) TestRunWithUlimit(c *check.C) { + cname := "TestRunWithUlimit" + res := command.PouchRun("run", "--ulimit", "nproc=256", "--name", cname, busyboxImage, "sh", "-c", "ulimit -p") + res.Assert(c, icmd.Success) + + out := res.Stdout() + c.Assert(out, check.Equals, "256\n") + + output := command.PouchRun("inspect", cname).Stdout() + result := []types.ContainerJSON{} + if err := json.Unmarshal([]byte(output), &result); err != nil { + c.Errorf("failed to decode inspect output: %v", err) + } + ul := result[0].HostConfig.Ulimits[0] + c.Assert(ul.Name, check.Equals, "nproc") + c.Assert(int(ul.Hard), check.Equals, 256) + c.Assert(int(ul.Soft), check.Equals, 256) + +} diff --git a/test/cli_start_test.go b/test/cli_start_test.go index 647d0b470..e3789f702 100644 --- a/test/cli_start_test.go +++ b/test/cli_start_test.go @@ -267,3 +267,14 @@ func (suite *PouchStartSuite) TestStartWithExitCode(c *check.C) { } c.Assert(result[0].State.ExitCode, check.Equals, int64(101)) } + +// TestStartWithUlimit starts a container with --ulimit. +func (suite *PouchStartSuite) TestStartWithUlimit(c *check.C) { + name := "start-ulimit" + + res := command.PouchRun("create", "--name", name, "--ulimit", "nproc=256", busyboxImage) + res.Assert(c, icmd.Success) + defer DelContainerForceMultyTime(c, name) + + command.PouchRun("start", name).Assert(c, icmd.Success) +}