Signed-off-by: Brian Goff <cpuguy83@gmail.com>
| ... | ... |
@@ -6,6 +6,7 @@ import ( |
| 6 | 6 |
"github.com/docker/docker/daemon/networkdriver" |
| 7 | 7 |
"github.com/docker/docker/opts" |
| 8 | 8 |
flag "github.com/docker/docker/pkg/mflag" |
| 9 |
+ "github.com/docker/docker/pkg/ulimit" |
|
| 9 | 10 |
) |
| 10 | 11 |
|
| 11 | 12 |
const ( |
| ... | ... |
@@ -44,6 +45,7 @@ type Config struct {
|
| 44 | 44 |
Context map[string][]string |
| 45 | 45 |
TrustKeyPath string |
| 46 | 46 |
Labels []string |
| 47 |
+ Ulimits map[string]*ulimit.Ulimit |
|
| 47 | 48 |
} |
| 48 | 49 |
|
| 49 | 50 |
// InstallFlags adds command-line options to the top-level flag parser for |
| ... | ... |
@@ -75,6 +77,8 @@ func (config *Config) InstallFlags() {
|
| 75 | 75 |
opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use")
|
| 76 | 76 |
opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use")
|
| 77 | 77 |
opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
|
| 78 |
+ config.Ulimits = make(map[string]*ulimit.Ulimit) |
|
| 79 |
+ opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers")
|
|
| 78 | 80 |
} |
| 79 | 81 |
|
| 80 | 82 |
func getDefaultNetworkMtu() int {
|
| ... | ... |
@@ -31,6 +31,7 @@ import ( |
| 31 | 31 |
"github.com/docker/docker/pkg/networkfs/resolvconf" |
| 32 | 32 |
"github.com/docker/docker/pkg/promise" |
| 33 | 33 |
"github.com/docker/docker/pkg/symlink" |
| 34 |
+ "github.com/docker/docker/pkg/ulimit" |
|
| 34 | 35 |
"github.com/docker/docker/runconfig" |
| 35 | 36 |
"github.com/docker/docker/utils" |
| 36 | 37 |
) |
| ... | ... |
@@ -276,11 +277,34 @@ func populateCommand(c *Container, env []string) error {
|
| 276 | 276 |
return err |
| 277 | 277 |
} |
| 278 | 278 |
|
| 279 |
+ var rlimits []*ulimit.Rlimit |
|
| 280 |
+ ulimits := c.hostConfig.Ulimits |
|
| 281 |
+ |
|
| 282 |
+ // Merge ulimits with daemon defaults |
|
| 283 |
+ ulIdx := make(map[string]*ulimit.Ulimit) |
|
| 284 |
+ for _, ul := range ulimits {
|
|
| 285 |
+ ulIdx[ul.Name] = ul |
|
| 286 |
+ } |
|
| 287 |
+ for name, ul := range c.daemon.config.Ulimits {
|
|
| 288 |
+ if _, exists := ulIdx[name]; !exists {
|
|
| 289 |
+ ulimits = append(ulimits, ul) |
|
| 290 |
+ } |
|
| 291 |
+ } |
|
| 292 |
+ |
|
| 293 |
+ for _, limit := range ulimits {
|
|
| 294 |
+ rl, err := limit.GetRlimit() |
|
| 295 |
+ if err != nil {
|
|
| 296 |
+ return err |
|
| 297 |
+ } |
|
| 298 |
+ rlimits = append(rlimits, rl) |
|
| 299 |
+ } |
|
| 300 |
+ |
|
| 279 | 301 |
resources := &execdriver.Resources{
|
| 280 | 302 |
Memory: c.Config.Memory, |
| 281 | 303 |
MemorySwap: c.Config.MemorySwap, |
| 282 | 304 |
CpuShares: c.Config.CpuShares, |
| 283 | 305 |
Cpuset: c.Config.Cpuset, |
| 306 |
+ Rlimits: rlimits, |
|
| 284 | 307 |
} |
| 285 | 308 |
|
| 286 | 309 |
processConfig := execdriver.ProcessConfig{
|
| ... | ... |
@@ -2,14 +2,16 @@ package execdriver |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"errors" |
| 5 |
- "github.com/docker/docker/daemon/execdriver/native/template" |
|
| 6 |
- "github.com/docker/libcontainer" |
|
| 7 |
- "github.com/docker/libcontainer/devices" |
|
| 8 | 5 |
"io" |
| 9 | 6 |
"os" |
| 10 | 7 |
"os/exec" |
| 11 | 8 |
"strings" |
| 12 | 9 |
"time" |
| 10 |
+ |
|
| 11 |
+ "github.com/docker/docker/daemon/execdriver/native/template" |
|
| 12 |
+ "github.com/docker/docker/pkg/ulimit" |
|
| 13 |
+ "github.com/docker/libcontainer" |
|
| 14 |
+ "github.com/docker/libcontainer/devices" |
|
| 13 | 15 |
) |
| 14 | 16 |
|
| 15 | 17 |
// Context is a generic key value pair that allows |
| ... | ... |
@@ -99,10 +101,11 @@ type NetworkInterface struct {
|
| 99 | 99 |
} |
| 100 | 100 |
|
| 101 | 101 |
type Resources struct {
|
| 102 |
- Memory int64 `json:"memory"` |
|
| 103 |
- MemorySwap int64 `json:"memory_swap"` |
|
| 104 |
- CpuShares int64 `json:"cpu_shares"` |
|
| 105 |
- Cpuset string `json:"cpuset"` |
|
| 102 |
+ Memory int64 `json:"memory"` |
|
| 103 |
+ MemorySwap int64 `json:"memory_swap"` |
|
| 104 |
+ CpuShares int64 `json:"cpu_shares"` |
|
| 105 |
+ Cpuset string `json:"cpuset"` |
|
| 106 |
+ Rlimits []*ulimit.Rlimit `json:"rlimits"` |
|
| 106 | 107 |
} |
| 107 | 108 |
|
| 108 | 109 |
type ResourceStats struct {
|
| ... | ... |
@@ -58,6 +58,8 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e |
| 58 | 58 |
return nil, err |
| 59 | 59 |
} |
| 60 | 60 |
|
| 61 |
+ d.setupRlimits(container, c) |
|
| 62 |
+ |
|
| 61 | 63 |
cmds := make(map[string]*exec.Cmd) |
| 62 | 64 |
d.Lock() |
| 63 | 65 |
for k, v := range d.activeContainers {
|
| ... | ... |
@@ -172,6 +174,16 @@ func (d *driver) setCapabilities(container *libcontainer.Config, c *execdriver.C |
| 172 | 172 |
return err |
| 173 | 173 |
} |
| 174 | 174 |
|
| 175 |
+func (d *driver) setupRlimits(container *libcontainer.Config, c *execdriver.Command) {
|
|
| 176 |
+ if c.Resources == nil {
|
|
| 177 |
+ return |
|
| 178 |
+ } |
|
| 179 |
+ |
|
| 180 |
+ for _, rlimit := range c.Resources.Rlimits {
|
|
| 181 |
+ container.Rlimits = append(container.Rlimits, libcontainer.Rlimit((*rlimit))) |
|
| 182 |
+ } |
|
| 183 |
+} |
|
| 184 |
+ |
|
| 175 | 185 |
func (d *driver) setupMounts(container *libcontainer.Config, c *execdriver.Command) error {
|
| 176 | 186 |
for _, m := range c.Mounts {
|
| 177 | 187 |
container.MountConfig.Mounts = append(container.MountConfig.Mounts, &mount.Mount{
|
| ... | ... |
@@ -51,6 +51,12 @@ You can still call an old version of the API using |
| 51 | 51 |
**New!** |
| 52 | 52 |
This endpoint now returns `Os`, `Arch` and `KernelVersion`. |
| 53 | 53 |
|
| 54 |
+`POST /containers/create` |
|
| 55 |
+`POST /containers/(id)/start` |
|
| 56 |
+ |
|
| 57 |
+**New!** |
|
| 58 |
+You can set ulimit settings to be used within the container. |
|
| 59 |
+ |
|
| 54 | 60 |
## v1.17 |
| 55 | 61 |
|
| 56 | 62 |
### Full Documentation |
| ... | ... |
@@ -86,7 +92,6 @@ root filesystem as read only. |
| 86 | 86 |
**New!** |
| 87 | 87 |
This endpoint returns a live stream of a container's resource usage statistics. |
| 88 | 88 |
|
| 89 |
- |
|
| 90 | 89 |
## v1.16 |
| 91 | 90 |
|
| 92 | 91 |
### Full Documentation |
| ... | ... |
@@ -155,7 +155,8 @@ Create a container |
| 155 | 155 |
"CapDrop": ["MKNOD"], |
| 156 | 156 |
"RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
|
| 157 | 157 |
"NetworkMode": "bridge", |
| 158 |
- "Devices": [] |
|
| 158 |
+ "Devices": [], |
|
| 159 |
+ "Ulimits": [{}]
|
|
| 159 | 160 |
} |
| 160 | 161 |
} |
| 161 | 162 |
|
| ... | ... |
@@ -244,6 +245,9 @@ Json Parameters: |
| 244 | 244 |
- **Devices** - A list of devices to add to the container specified in the |
| 245 | 245 |
form |
| 246 | 246 |
`{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
|
| 247 |
+ - **Ulimits** - A list of ulimits to be set in the container, specified as |
|
| 248 |
+ `{ "Name": <name>, "Soft": <soft limit>, "Hard": <hard limit> }`, for example:
|
|
| 249 |
+ `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
|
|
| 247 | 250 |
|
| 248 | 251 |
Query Parameters: |
| 249 | 252 |
|
| ... | ... |
@@ -337,7 +341,8 @@ Return low-level information on the container `id` |
| 337 | 337 |
"Name": "on-failure" |
| 338 | 338 |
}, |
| 339 | 339 |
"SecurityOpt": null, |
| 340 |
- "VolumesFrom": null |
|
| 340 |
+ "VolumesFrom": null, |
|
| 341 |
+ "Ulimits": [{}]
|
|
| 341 | 342 |
}, |
| 342 | 343 |
"HostnamePath": "/var/lib/docker/containers/ba033ac4401106a3b513bc9d639eee123ad78ca3616b921167cd74b20e25ed39/hostname", |
| 343 | 344 |
"HostsPath": "/var/lib/docker/containers/ba033ac4401106a3b513bc9d639eee123ad78ca3616b921167cd74b20e25ed39/hosts", |
| ... | ... |
@@ -109,6 +109,7 @@ expect an integer, and they can only be specified once. |
| 109 | 109 |
--tlskey="~/.docker/key.pem" Path to TLS key file |
| 110 | 110 |
--tlsverify=false Use TLS and verify the remote |
| 111 | 111 |
-v, --version=false Print version information and quit |
| 112 |
+ --default-ulimit=[] Set default ulimit settings for containers. |
|
| 112 | 113 |
|
| 113 | 114 |
Options with [] may be specified multiple times. |
| 114 | 115 |
|
| ... | ... |
@@ -404,6 +405,14 @@ This will only add the proxy and authentication to the Docker daemon's requests |
| 404 | 404 |
your `docker build`s and running containers will need extra configuration to use |
| 405 | 405 |
the proxy |
| 406 | 406 |
|
| 407 |
+### Default Ulimits |
|
| 408 |
+ |
|
| 409 |
+`--default-ulimit` allows you to set the default `ulimit` options to use for all |
|
| 410 |
+containers. It takes the same options as `--ulimit` for `docker run`. If these |
|
| 411 |
+defaults are not set, `ulimit` settings will be inheritted, if not set on |
|
| 412 |
+`docker run`, from the Docker daemon. Any `--ulimit` options passed to |
|
| 413 |
+`docker run` will overwrite these defaults. |
|
| 414 |
+ |
|
| 407 | 415 |
### Miscellaneous options |
| 408 | 416 |
|
| 409 | 417 |
IP masquerading uses address translation to allow containers without a public IP to talk |
| ... | ... |
@@ -1974,6 +1983,23 @@ You can add other hosts into a container's `/etc/hosts` file by using one or mor |
| 1974 | 1974 |
> $ alias hostip="ip route show 0.0.0.0/0 | grep -Eo 'via \S+' | awk '{ print \$2 }'"
|
| 1975 | 1975 |
> $ docker run --add-host=docker:$(hostip) --rm -it debian |
| 1976 | 1976 |
|
| 1977 |
+### Setting ulimits in a container |
|
| 1978 |
+ |
|
| 1979 |
+Since setting `ulimit` settings in a container requires extra privileges not |
|
| 1980 |
+available in the default container, you can set these using the `--ulimit` flag. |
|
| 1981 |
+`--ulimit` is specified with a soft and hard limit as such: |
|
| 1982 |
+`<type>=<soft limit>[:<hard limit>]`, for example: |
|
| 1983 |
+ |
|
| 1984 |
+``` |
|
| 1985 |
+ $ docker run --ulimit nofile=1024:1024 --rm debian ulimit -n |
|
| 1986 |
+ 1024 |
|
| 1987 |
+``` |
|
| 1988 |
+ |
|
| 1989 |
+>**Note:** |
|
| 1990 |
+> If you do not provide a `hard limit`, the `soft limit` will be used for both |
|
| 1991 |
+values. If no `ulimits` are set, they will be inherited from the default `ulimits` |
|
| 1992 |
+set on the daemon. |
|
| 1993 |
+ |
|
| 1977 | 1994 |
## save |
| 1978 | 1995 |
|
| 1979 | 1996 |
Usage: docker save [OPTIONS] IMAGE [IMAGE...] |
| ... | ... |
@@ -480,3 +480,56 @@ func TestDaemonUpgradeWithVolumes(t *testing.T) {
|
| 480 | 480 |
|
| 481 | 481 |
logDone("daemon - volumes from old(pre 1.3) daemon work")
|
| 482 | 482 |
} |
| 483 |
+ |
|
| 484 |
+func TestDaemonUlimitDefaults(t *testing.T) {
|
|
| 485 |
+ d := NewDaemon(t) |
|
| 486 |
+ |
|
| 487 |
+ if err := d.StartWithBusybox("--default-ulimit", "nofile=42:42", "--default-ulimit", "nproc=1024:1024"); err != nil {
|
|
| 488 |
+ t.Fatal(err) |
|
| 489 |
+ } |
|
| 490 |
+ |
|
| 491 |
+ out, err := d.Cmd("run", "--ulimit", "nproc=2048", "--name=test", "busybox", "/bin/sh", "-c", "echo $(ulimit -n); echo $(ulimit -p)")
|
|
| 492 |
+ if err != nil {
|
|
| 493 |
+ t.Fatal(out, err) |
|
| 494 |
+ } |
|
| 495 |
+ |
|
| 496 |
+ outArr := strings.Split(out, "\n") |
|
| 497 |
+ if len(outArr) < 2 {
|
|
| 498 |
+ t.Fatal("got unexpected output: %s", out)
|
|
| 499 |
+ } |
|
| 500 |
+ nofile := strings.TrimSpace(outArr[0]) |
|
| 501 |
+ nproc := strings.TrimSpace(outArr[1]) |
|
| 502 |
+ |
|
| 503 |
+ if nofile != "42" {
|
|
| 504 |
+ t.Fatalf("expected `ulimit -n` to be `42`, got: %s", nofile)
|
|
| 505 |
+ } |
|
| 506 |
+ if nproc != "2048" {
|
|
| 507 |
+ t.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc)
|
|
| 508 |
+ } |
|
| 509 |
+ |
|
| 510 |
+ // Now restart daemon with a new default |
|
| 511 |
+ if err := d.Restart("--default-ulimit", "nofile=43"); err != nil {
|
|
| 512 |
+ t.Fatal(err) |
|
| 513 |
+ } |
|
| 514 |
+ |
|
| 515 |
+ out, err = d.Cmd("start", "-a", "test")
|
|
| 516 |
+ if err != nil {
|
|
| 517 |
+ t.Fatal(err) |
|
| 518 |
+ } |
|
| 519 |
+ |
|
| 520 |
+ outArr = strings.Split(out, "\n") |
|
| 521 |
+ if len(outArr) < 2 {
|
|
| 522 |
+ t.Fatal("got unexpected output: %s", out)
|
|
| 523 |
+ } |
|
| 524 |
+ nofile = strings.TrimSpace(outArr[0]) |
|
| 525 |
+ nproc = strings.TrimSpace(outArr[1]) |
|
| 526 |
+ |
|
| 527 |
+ if nofile != "43" {
|
|
| 528 |
+ t.Fatalf("expected `ulimit -n` to be `43`, got: %s", nofile)
|
|
| 529 |
+ } |
|
| 530 |
+ if nproc != "2048" {
|
|
| 531 |
+ t.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc)
|
|
| 532 |
+ } |
|
| 533 |
+ |
|
| 534 |
+ logDone("daemon - default ulimits are applied")
|
|
| 535 |
+} |
| ... | ... |
@@ -91,3 +91,18 @@ func TestRunWithVolumesIsRecursive(t *testing.T) {
|
| 91 | 91 |
|
| 92 | 92 |
logDone("run - volumes are bind mounted recursively")
|
| 93 | 93 |
} |
| 94 |
+ |
|
| 95 |
+func TestRunWithUlimits(t *testing.T) {
|
|
| 96 |
+ defer deleteAllContainers() |
|
| 97 |
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name=testulimits", "--ulimit", "nofile=42", "busybox", "/bin/sh", "-c", "ulimit -n")) |
|
| 98 |
+ if err != nil {
|
|
| 99 |
+ t.Fatal(err, out) |
|
| 100 |
+ } |
|
| 101 |
+ |
|
| 102 |
+ ul := strings.TrimSpace(out) |
|
| 103 |
+ if ul != "42" {
|
|
| 104 |
+ t.Fatalf("expected `ulimit -n` to be 42, got %s", ul)
|
|
| 105 |
+ } |
|
| 106 |
+ |
|
| 107 |
+ logDone("run - ulimits are set")
|
|
| 108 |
+} |
| ... | ... |
@@ -11,6 +11,7 @@ import ( |
| 11 | 11 |
"github.com/docker/docker/api" |
| 12 | 12 |
flag "github.com/docker/docker/pkg/mflag" |
| 13 | 13 |
"github.com/docker/docker/pkg/parsers" |
| 14 |
+ "github.com/docker/docker/pkg/ulimit" |
|
| 14 | 15 |
"github.com/docker/docker/utils" |
| 15 | 16 |
) |
| 16 | 17 |
|
| ... | ... |
@@ -43,6 +44,10 @@ func LabelListVar(values *[]string, names []string, usage string) {
|
| 43 | 43 |
flag.Var(newListOptsRef(values, ValidateLabel), names, usage) |
| 44 | 44 |
} |
| 45 | 45 |
|
| 46 |
+func UlimitMapVar(values map[string]*ulimit.Ulimit, names []string, usage string) {
|
|
| 47 |
+ flag.Var(NewUlimitOpt(values), names, usage) |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 46 | 50 |
// ListOpts type |
| 47 | 51 |
type ListOpts struct {
|
| 48 | 52 |
values *[]string |
| 49 | 53 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,44 @@ |
| 0 |
+package opts |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/docker/docker/pkg/ulimit" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+type UlimitOpt struct {
|
|
| 9 |
+ values map[string]*ulimit.Ulimit |
|
| 10 |
+} |
|
| 11 |
+ |
|
| 12 |
+func NewUlimitOpt(ref map[string]*ulimit.Ulimit) *UlimitOpt {
|
|
| 13 |
+ return &UlimitOpt{ref}
|
|
| 14 |
+} |
|
| 15 |
+ |
|
| 16 |
+func (o *UlimitOpt) Set(val string) error {
|
|
| 17 |
+ l, err := ulimit.Parse(val) |
|
| 18 |
+ if err != nil {
|
|
| 19 |
+ return err |
|
| 20 |
+ } |
|
| 21 |
+ |
|
| 22 |
+ o.values[l.Name] = l |
|
| 23 |
+ |
|
| 24 |
+ return nil |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+func (o *UlimitOpt) String() string {
|
|
| 28 |
+ var out []string |
|
| 29 |
+ for _, v := range o.values {
|
|
| 30 |
+ out = append(out, v.String()) |
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+ return fmt.Sprintf("%v", out)
|
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+func (o *UlimitOpt) GetList() []*ulimit.Ulimit {
|
|
| 37 |
+ var ulimits []*ulimit.Ulimit |
|
| 38 |
+ for _, v := range o.values {
|
|
| 39 |
+ ulimits = append(ulimits, v) |
|
| 40 |
+ } |
|
| 41 |
+ |
|
| 42 |
+ return ulimits |
|
| 43 |
+} |
| 0 | 44 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,106 @@ |
| 0 |
+package ulimit |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "strconv" |
|
| 5 |
+ "strings" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// Human friendly version of Rlimit |
|
| 9 |
+type Ulimit struct {
|
|
| 10 |
+ Name string |
|
| 11 |
+ Hard int64 |
|
| 12 |
+ Soft int64 |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+type Rlimit struct {
|
|
| 16 |
+ Type int `json:"type,omitempty"` |
|
| 17 |
+ Hard uint64 `json:"hard,omitempty"` |
|
| 18 |
+ Soft uint64 `json:"soft,omitempty"` |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+const ( |
|
| 22 |
+ // magic numbers for making the syscall |
|
| 23 |
+ // some of these are defined in the syscall package, but not all. |
|
| 24 |
+ // Also since Windows client doesn't get access to the syscall package, need to |
|
| 25 |
+ // define these here |
|
| 26 |
+ RLIMIT_AS = 9 |
|
| 27 |
+ RLIMIT_CORE = 4 |
|
| 28 |
+ RLIMIT_CPU = 0 |
|
| 29 |
+ RLIMIT_DATA = 2 |
|
| 30 |
+ RLIMIT_FSIZE = 1 |
|
| 31 |
+ RLIMIT_LOCKS = 10 |
|
| 32 |
+ RLIMIT_MEMLOCK = 8 |
|
| 33 |
+ RLIMIT_MSGQUEUE = 12 |
|
| 34 |
+ RLIMIT_NICE = 13 |
|
| 35 |
+ RLIMIT_NOFILE = 7 |
|
| 36 |
+ RLIMIT_NPROC = 6 |
|
| 37 |
+ RLIMIT_RSS = 5 |
|
| 38 |
+ RLIMIT_RTPRIO = 14 |
|
| 39 |
+ RLIMIT_RTTIME = 15 |
|
| 40 |
+ RLIMIT_SIGPENDING = 11 |
|
| 41 |
+ RLIMIT_STACK = 3 |
|
| 42 |
+) |
|
| 43 |
+ |
|
| 44 |
+var ulimitNameMapping = map[string]int{
|
|
| 45 |
+ //"as": RLIMIT_AS, // Disbaled since this doesn't seem usable with the way Docker inits a container. |
|
| 46 |
+ "core": RLIMIT_CORE, |
|
| 47 |
+ "cpu": RLIMIT_CPU, |
|
| 48 |
+ "data": RLIMIT_DATA, |
|
| 49 |
+ "fsize": RLIMIT_FSIZE, |
|
| 50 |
+ "locks": RLIMIT_LOCKS, |
|
| 51 |
+ "memlock": RLIMIT_MEMLOCK, |
|
| 52 |
+ "msgqueue": RLIMIT_MSGQUEUE, |
|
| 53 |
+ "nice": RLIMIT_NICE, |
|
| 54 |
+ "nofile": RLIMIT_NOFILE, |
|
| 55 |
+ "nproc": RLIMIT_NPROC, |
|
| 56 |
+ "rss": RLIMIT_RSS, |
|
| 57 |
+ "rtprio": RLIMIT_RTPRIO, |
|
| 58 |
+ "rttime": RLIMIT_RTTIME, |
|
| 59 |
+ "sigpending": RLIMIT_SIGPENDING, |
|
| 60 |
+ "stack": RLIMIT_STACK, |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+func Parse(val string) (*Ulimit, error) {
|
|
| 64 |
+ parts := strings.SplitN(val, "=", 2) |
|
| 65 |
+ if len(parts) != 2 {
|
|
| 66 |
+ return nil, fmt.Errorf("invalid ulimit argument: %s", val)
|
|
| 67 |
+ } |
|
| 68 |
+ |
|
| 69 |
+ if _, exists := ulimitNameMapping[parts[0]]; !exists {
|
|
| 70 |
+ return nil, fmt.Errorf("invalid ulimit type: %s", parts[0])
|
|
| 71 |
+ } |
|
| 72 |
+ |
|
| 73 |
+ limitVals := strings.SplitN(parts[1], ":", 2) |
|
| 74 |
+ if len(limitVals) > 2 {
|
|
| 75 |
+ return nil, fmt.Errorf("too many limit value arguments - %s, can only have up to two, `soft[:hard]`", parts[1])
|
|
| 76 |
+ } |
|
| 77 |
+ |
|
| 78 |
+ soft, err := strconv.ParseInt(limitVals[0], 10, 64) |
|
| 79 |
+ if err != nil {
|
|
| 80 |
+ return nil, err |
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ hard := soft // in case no hard was set |
|
| 84 |
+ if len(limitVals) == 2 {
|
|
| 85 |
+ hard, err = strconv.ParseInt(limitVals[1], 10, 64) |
|
| 86 |
+ } |
|
| 87 |
+ if soft > hard {
|
|
| 88 |
+ return nil, fmt.Errorf("ulimit soft limit must be less than or equal to hard limit: %d > %d", soft, hard)
|
|
| 89 |
+ } |
|
| 90 |
+ |
|
| 91 |
+ return &Ulimit{Name: parts[0], Soft: soft, Hard: hard}, nil
|
|
| 92 |
+} |
|
| 93 |
+ |
|
| 94 |
+func (u *Ulimit) GetRlimit() (*Rlimit, error) {
|
|
| 95 |
+ t, exists := ulimitNameMapping[u.Name] |
|
| 96 |
+ if !exists {
|
|
| 97 |
+ return nil, fmt.Errorf("invalid ulimit name %s", u.Name)
|
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ return &Rlimit{Type: t, Soft: uint64(u.Soft), Hard: uint64(u.Hard)}, nil
|
|
| 101 |
+} |
|
| 102 |
+ |
|
| 103 |
+func (u *Ulimit) String() string {
|
|
| 104 |
+ return fmt.Sprintf("%s=%s:%s", u.Name, u.Soft, u.Hard)
|
|
| 105 |
+} |
| 0 | 106 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,41 @@ |
| 0 |
+package ulimit |
|
| 1 |
+ |
|
| 2 |
+import "testing" |
|
| 3 |
+ |
|
| 4 |
+func TestParseInvalidLimitType(t *testing.T) {
|
|
| 5 |
+ if _, err := Parse("notarealtype=1024:1024"); err == nil {
|
|
| 6 |
+ t.Fatalf("expected error on invalid ulimit type")
|
|
| 7 |
+ } |
|
| 8 |
+} |
|
| 9 |
+ |
|
| 10 |
+func TestParseBadFormat(t *testing.T) {
|
|
| 11 |
+ if _, err := Parse("nofile:1024:1024"); err == nil {
|
|
| 12 |
+ t.Fatal("expected error on bad syntax")
|
|
| 13 |
+ } |
|
| 14 |
+ |
|
| 15 |
+ if _, err := Parse("nofile"); err == nil {
|
|
| 16 |
+ t.Fatal("expected error on bad syntax")
|
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 19 |
+ if _, err := Parse("nofile="); err == nil {
|
|
| 20 |
+ t.Fatal("expected error on bad syntax")
|
|
| 21 |
+ } |
|
| 22 |
+ if _, err := Parse("nofile=:"); err == nil {
|
|
| 23 |
+ t.Fatal("expected error on bad syntax")
|
|
| 24 |
+ } |
|
| 25 |
+ if _, err := Parse("nofile=:1024"); err == nil {
|
|
| 26 |
+ t.Fatal("expected error on bad syntax")
|
|
| 27 |
+ } |
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+func TestParseHardLessThanSoft(t *testing.T) {
|
|
| 31 |
+ if _, err := Parse("nofile:1024:1"); err == nil {
|
|
| 32 |
+ t.Fatal("expected error on hard limit less than soft limit")
|
|
| 33 |
+ } |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+func TestParseInvalidValueType(t *testing.T) {
|
|
| 37 |
+ if _, err := Parse("nofile:asdf"); err == nil {
|
|
| 38 |
+ t.Fatal("expected error on bad value type")
|
|
| 39 |
+ } |
|
| 40 |
+} |
| ... | ... |
@@ -5,6 +5,7 @@ import ( |
| 5 | 5 |
|
| 6 | 6 |
"github.com/docker/docker/engine" |
| 7 | 7 |
"github.com/docker/docker/nat" |
| 8 |
+ "github.com/docker/docker/pkg/ulimit" |
|
| 8 | 9 |
"github.com/docker/docker/utils" |
| 9 | 10 |
) |
| 10 | 11 |
|
| ... | ... |
@@ -119,6 +120,7 @@ type HostConfig struct {
|
| 119 | 119 |
RestartPolicy RestartPolicy |
| 120 | 120 |
SecurityOpt []string |
| 121 | 121 |
ReadonlyRootfs bool |
| 122 |
+ Ulimits []*ulimit.Ulimit |
|
| 122 | 123 |
} |
| 123 | 124 |
|
| 124 | 125 |
// This is used by the create command when you want to set both the |
| ... | ... |
@@ -156,6 +158,9 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
|
| 156 | 156 |
job.GetenvJson("PortBindings", &hostConfig.PortBindings)
|
| 157 | 157 |
job.GetenvJson("Devices", &hostConfig.Devices)
|
| 158 | 158 |
job.GetenvJson("RestartPolicy", &hostConfig.RestartPolicy)
|
| 159 |
+ |
|
| 160 |
+ job.GetenvJson("Ulimits", &hostConfig.Ulimits)
|
|
| 161 |
+ |
|
| 159 | 162 |
hostConfig.SecurityOpt = job.GetenvList("SecurityOpt")
|
| 160 | 163 |
if Binds := job.GetenvList("Binds"); Binds != nil {
|
| 161 | 164 |
hostConfig.Binds = Binds |
| ... | ... |
@@ -10,6 +10,7 @@ import ( |
| 10 | 10 |
"github.com/docker/docker/opts" |
| 11 | 11 |
flag "github.com/docker/docker/pkg/mflag" |
| 12 | 12 |
"github.com/docker/docker/pkg/parsers" |
| 13 |
+ "github.com/docker/docker/pkg/ulimit" |
|
| 13 | 14 |
"github.com/docker/docker/pkg/units" |
| 14 | 15 |
"github.com/docker/docker/utils" |
| 15 | 16 |
) |
| ... | ... |
@@ -32,6 +33,9 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe |
| 32 | 32 |
flEnv = opts.NewListOpts(opts.ValidateEnv) |
| 33 | 33 |
flDevices = opts.NewListOpts(opts.ValidatePath) |
| 34 | 34 |
|
| 35 |
+ ulimits = make(map[string]*ulimit.Ulimit) |
|
| 36 |
+ flUlimits = opts.NewUlimitOpt(ulimits) |
|
| 37 |
+ |
|
| 35 | 38 |
flPublish = opts.NewListOpts(nil) |
| 36 | 39 |
flExpose = opts.NewListOpts(nil) |
| 37 | 40 |
flDns = opts.NewListOpts(opts.ValidateIPAddress) |
| ... | ... |
@@ -82,6 +86,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe |
| 82 | 82 |
cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities")
|
| 83 | 83 |
cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities")
|
| 84 | 84 |
cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options")
|
| 85 |
+ cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
|
|
| 85 | 86 |
|
| 86 | 87 |
cmd.Require(flag.Min, 1) |
| 87 | 88 |
|
| ... | ... |
@@ -309,6 +314,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe |
| 309 | 309 |
RestartPolicy: restartPolicy, |
| 310 | 310 |
SecurityOpt: flSecurityOpt.GetAll(), |
| 311 | 311 |
ReadonlyRootfs: *flReadonlyRootfs, |
| 312 |
+ Ulimits: flUlimits.GetList(), |
|
| 312 | 313 |
} |
| 313 | 314 |
|
| 314 | 315 |
// When allocating stdin in attached mode, close stdin at client disconnect |