From 8782f0e287df2a2fd9f9dfb3f0e1589cc15a4f91 Mon Sep 17 00:00:00 2001 From: Aleksandr Razumov Date: Sun, 15 Dec 2019 20:57:23 +0300 Subject: Set CPU number to CPU quota When application is not cgroups-aware, it can spawn excessive threads which often defaults to CPU number. Introduce a opt-in flag that will set CPU number accordingly to CPU quota (if available). Fixes #1391 --- runsc/sandbox/sandbox.go | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'runsc/sandbox') diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index 805233184..cbfb873d1 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -18,6 +18,7 @@ package sandbox import ( "context" "fmt" + "math" "os" "os/exec" "strconv" @@ -631,6 +632,15 @@ func (s *Sandbox) createSandboxProcess(conf *boot.Config, args *Args, startSyncF if err != nil { return fmt.Errorf("getting cpu count from cgroups: %v", err) } + if conf.CPUNumFromQuota { + quota, err := s.Cgroup.CPUQuota() + if err != nil { + return fmt.Errorf("getting cpu qouta from cgroups: %v", err) + } + if quota > 0 { + cpuNum = int(math.Ceil(quota)) + } + } cmd.Args = append(cmd.Args, "--cpu-num", strconv.Itoa(cpuNum)) mem, err := s.Cgroup.MemoryLimit() -- cgit v1.2.3 From b661434202672f920291bf5685b68772103c66cb Mon Sep 17 00:00:00 2001 From: Aleksandr Razumov Date: Tue, 17 Dec 2019 13:06:42 +0300 Subject: Add minimum CPU number and only lower CPUs on --cpu-num-from-quota * Add `--cpu-num-min` flag to control minimum CPUs * Only lower CPU count * Fix comments --- runsc/boot/config.go | 12 ++++++++++-- runsc/main.go | 4 +++- runsc/sandbox/sandbox.go | 10 ++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) (limited to 'runsc/sandbox') diff --git a/runsc/boot/config.go b/runsc/boot/config.go index 7841d1a7a..d9f5b67c0 100644 --- a/runsc/boot/config.go +++ b/runsc/boot/config.go @@ -254,8 +254,14 @@ type Config struct { // CPUNumFromQuota sets CPU number count to available CPU quota, using // least integer value greater than or equal to quota. // - // E.g. 0.2 CPU quota would result in 1, and 1.9 in 2. + // E.g. 0.2 CPU quota will result in 1, and 1.9 in 2. CPUNumFromQuota bool + + // CPUNumMin is minimum value of CPU number setting when CPUNumFromQuota + // strategy is active. + // + // E.g. when CPUNumMin is 2, 0.2 CPU quota will result in 2 instead of 1. + CPUNumMin int } // ToFlags returns a slice of flags that correspond to the given Config. @@ -289,7 +295,9 @@ func (c *Config) ToFlags() []string { "--overlayfs-stale-read=" + strconv.FormatBool(c.OverlayfsStaleRead), } if c.CPUNumFromQuota { - f = append(f, "--cpu-num-from-quota") + f = append(f, "--cpu-num-from-quota", + "--cpu-num-min="+strconv.Itoa(c.CPUNumMin), + ) } // Only include these if set since it is never to be used by users. if c.TestOnlyAllowRunAsCurrentUserWithoutChroot { diff --git a/runsc/main.go b/runsc/main.go index febd59aed..7c60cbb4b 100644 --- a/runsc/main.go +++ b/runsc/main.go @@ -82,7 +82,8 @@ var ( numNetworkChannels = flag.Int("num-network-channels", 1, "number of underlying channels(FDs) to use for network link endpoints.") rootless = flag.Bool("rootless", false, "it allows the sandbox to be started with a user that is not root. Sandbox and Gofer processes may run with same privileges as current user.") referenceLeakMode = flag.String("ref-leak-mode", "disabled", "sets reference leak check mode: disabled (default), log-names, log-traces.") - cpuNumFromQuota = flag.Bool("cpu-num-from-quota", false, "set cpu number to cpu quota (least integer greater than quota value)") + cpuNumFromQuota = flag.Bool("cpu-num-from-quota", false, "set cpu number to cpu quota (least integer greater or equal to quota value)") + cpuNumMin = flag.Int("cpu-num-min", 2, "minimum number of cpu to use with --cpu-num-from-quota") // Test flags, not to be used outside tests, ever. testOnlyAllowRunAsCurrentUserWithoutChroot = flag.Bool("TESTONLY-unsafe-nonroot", false, "TEST ONLY; do not ever use! This skips many security measures that isolate the host from the sandbox.") @@ -227,6 +228,7 @@ func main() { ReferenceLeakMode: refsLeakMode, OverlayfsStaleRead: *overlayfsStaleRead, CPUNumFromQuota: *cpuNumFromQuota, + CPUNumMin: *cpuNumMin, TestOnlyAllowRunAsCurrentUserWithoutChroot: *testOnlyAllowRunAsCurrentUserWithoutChroot, TestOnlyTestNameEnv: *testOnlyTestNameEnv, diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index cbfb873d1..f6feadf75 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -637,8 +637,14 @@ func (s *Sandbox) createSandboxProcess(conf *boot.Config, args *Args, startSyncF if err != nil { return fmt.Errorf("getting cpu qouta from cgroups: %v", err) } - if quota > 0 { - cpuNum = int(math.Ceil(quota)) + if n := int(math.Ceil(quota)); n > 0 { + if n < conf.CPUNumMin { + n = conf.CPUNumMin + } + if n < cpuNum { + // Only lower the cpu number. + cpuNum = n + } } } cmd.Args = append(cmd.Args, "--cpu-num", strconv.Itoa(cpuNum)) -- cgit v1.2.3 From 67f678be27b3f4545d41539bd6855527da53a250 Mon Sep 17 00:00:00 2001 From: Aleksandr Razumov Date: Tue, 17 Dec 2019 20:41:02 +0300 Subject: Leave minimum CPU number as a constant Remove introduced CPUNumMin config and hard-code it as 2. --- runsc/boot/config.go | 10 +--------- runsc/main.go | 4 +--- runsc/sandbox/sandbox.go | 9 +++++++-- 3 files changed, 9 insertions(+), 14 deletions(-) (limited to 'runsc/sandbox') diff --git a/runsc/boot/config.go b/runsc/boot/config.go index d9f5b67c0..a878bc2ce 100644 --- a/runsc/boot/config.go +++ b/runsc/boot/config.go @@ -256,12 +256,6 @@ type Config struct { // // E.g. 0.2 CPU quota will result in 1, and 1.9 in 2. CPUNumFromQuota bool - - // CPUNumMin is minimum value of CPU number setting when CPUNumFromQuota - // strategy is active. - // - // E.g. when CPUNumMin is 2, 0.2 CPU quota will result in 2 instead of 1. - CPUNumMin int } // ToFlags returns a slice of flags that correspond to the given Config. @@ -295,9 +289,7 @@ func (c *Config) ToFlags() []string { "--overlayfs-stale-read=" + strconv.FormatBool(c.OverlayfsStaleRead), } if c.CPUNumFromQuota { - f = append(f, "--cpu-num-from-quota", - "--cpu-num-min="+strconv.Itoa(c.CPUNumMin), - ) + f = append(f, "--cpu-num-from-quota") } // Only include these if set since it is never to be used by users. if c.TestOnlyAllowRunAsCurrentUserWithoutChroot { diff --git a/runsc/main.go b/runsc/main.go index 7c60cbb4b..abf929511 100644 --- a/runsc/main.go +++ b/runsc/main.go @@ -82,8 +82,7 @@ var ( numNetworkChannels = flag.Int("num-network-channels", 1, "number of underlying channels(FDs) to use for network link endpoints.") rootless = flag.Bool("rootless", false, "it allows the sandbox to be started with a user that is not root. Sandbox and Gofer processes may run with same privileges as current user.") referenceLeakMode = flag.String("ref-leak-mode", "disabled", "sets reference leak check mode: disabled (default), log-names, log-traces.") - cpuNumFromQuota = flag.Bool("cpu-num-from-quota", false, "set cpu number to cpu quota (least integer greater or equal to quota value)") - cpuNumMin = flag.Int("cpu-num-min", 2, "minimum number of cpu to use with --cpu-num-from-quota") + cpuNumFromQuota = flag.Bool("cpu-num-from-quota", false, "set cpu number to cpu quota (least integer greater or equal to quota value, but not less than 2)") // Test flags, not to be used outside tests, ever. testOnlyAllowRunAsCurrentUserWithoutChroot = flag.Bool("TESTONLY-unsafe-nonroot", false, "TEST ONLY; do not ever use! This skips many security measures that isolate the host from the sandbox.") @@ -228,7 +227,6 @@ func main() { ReferenceLeakMode: refsLeakMode, OverlayfsStaleRead: *overlayfsStaleRead, CPUNumFromQuota: *cpuNumFromQuota, - CPUNumMin: *cpuNumMin, TestOnlyAllowRunAsCurrentUserWithoutChroot: *testOnlyAllowRunAsCurrentUserWithoutChroot, TestOnlyTestNameEnv: *testOnlyTestNameEnv, diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index f6feadf75..ce1452b87 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -633,13 +633,18 @@ func (s *Sandbox) createSandboxProcess(conf *boot.Config, args *Args, startSyncF return fmt.Errorf("getting cpu count from cgroups: %v", err) } if conf.CPUNumFromQuota { + // Dropping below 2 CPUs can trigger application to disable + // locks that can lead do hard to debug errors, so just + // leaving two cores as reasonable default. + const minCPUs = 2 + quota, err := s.Cgroup.CPUQuota() if err != nil { return fmt.Errorf("getting cpu qouta from cgroups: %v", err) } if n := int(math.Ceil(quota)); n > 0 { - if n < conf.CPUNumMin { - n = conf.CPUNumMin + if n < minCPUs { + n = minCPUs } if n < cpuNum { // Only lower the cpu number. -- cgit v1.2.3