diff --git a/cmd/crio/config.go b/cmd/crio/config.go index 5d61a02e..bcd41042 100644 --- a/cmd/crio/config.go +++ b/cmd/crio/config.go @@ -98,6 +98,9 @@ apparmor_profile = "{{ .ApparmorProfile }}" # for the runtime. cgroup_manager = "{{ .CgroupManager }}" +# pids_limit is the number of processes allowed in a container +pids_limit = {{ .PidsLimit }} + # The "crio.image" table contains settings pertaining to the # management of OCI images. [crio.image] diff --git a/cmd/crio/main.go b/cmd/crio/main.go index 6d575c27..602d8174 100644 --- a/cmd/crio/main.go +++ b/cmd/crio/main.go @@ -103,6 +103,9 @@ func mergeConfig(config *server.Config, ctx *cli.Context) error { if ctx.GlobalIsSet("cgroup-manager") { config.CgroupManager = ctx.GlobalString("cgroup-manager") } + if ctx.GlobalIsSet("pids-limit") { + config.PidsLimit = ctx.GlobalInt64("pids-limit") + } if ctx.GlobalIsSet("cni-config-dir") { config.NetworkDir = ctx.GlobalString("cni-config-dir") } @@ -239,6 +242,11 @@ func main() { Name: "cgroup-manager", Usage: "cgroup manager (cgroupfs or systemd)", }, + cli.Int64Flag{ + Name: "pids-limit", + Value: server.DefaultPidsLimit, + Usage: "maximum number of processes allowed in a container", + }, cli.StringFlag{ Name: "cni-config-dir", Usage: "CNI configuration files directory", diff --git a/docs/crio.8.md b/docs/crio.8.md index 0fb0131f..904064b8 100644 --- a/docs/crio.8.md +++ b/docs/crio.8.md @@ -91,6 +91,9 @@ set the CPU profile file path **--pause-image**="" Image which contains the pause executable (default: "kubernetes/pause") +**--pids-limit**="" + Maximum number of processes allowed in a container (default: 1024) + **--root**="" CRIO root dir (default: "/var/lib/containers/storage") diff --git a/docs/crio.conf.5.md b/docs/crio.conf.5.md index 07950d70..93c30ebf 100644 --- a/docs/crio.conf.5.md +++ b/docs/crio.conf.5.md @@ -54,6 +54,9 @@ The `crio` table supports the following options: **conmon_env**=[] Environment variable list for conmon process (default: ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",]) +**pids_limit**="" + Maximum number of processes allowed in a container (default: 1024) + **runtime**="" OCI runtime path (default: "/usr/bin/runc") diff --git a/server/config.go b/server/config.go index c15e62e7..c158b043 100644 --- a/server/config.go +++ b/server/config.go @@ -43,6 +43,12 @@ const ( ImageVolumesIgnore ImageVolumesType = "ignore" ) +const ( + // DefaultPidsLimit is the default value for maximum number of processes + // allowed inside a container + DefaultPidsLimit = 1024 +) + // This structure is necessary to fake the TOML tables when parsing, // while also not requiring a bunch of layered structs for no good // reason. @@ -133,6 +139,10 @@ type RuntimeConfig struct { // CgroupManager is the manager implementation name which is used to // handle cgroups for containers. CgroupManager string `toml:"cgroup_manager"` + + // PidsLimit is the number of processes each container is restricted to + // by the cgroup process number controller. + PidsLimit int64 `toml:"pids_limit"` } // ImageConfig represents the "crio.image" TOML config table. @@ -261,6 +271,7 @@ func DefaultConfig() *Config { SeccompProfile: seccompProfilePath, ApparmorProfile: apparmorProfileName, CgroupManager: cgroupManager, + PidsLimit: DefaultPidsLimit, }, ImageConfig: ImageConfig{ DefaultTransport: defaultTransport, diff --git a/server/container_create.go b/server/container_create.go index d336563a..28b75acc 100644 --- a/server/container_create.go +++ b/server/container_create.go @@ -18,6 +18,7 @@ import ( "github.com/kubernetes-incubator/cri-o/server/apparmor" "github.com/kubernetes-incubator/cri-o/server/seccomp" "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/devices" "github.com/opencontainers/runc/libcontainer/user" rspec "github.com/opencontainers/runtime-spec/specs-go" @@ -323,6 +324,9 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string, return nil, err } + // Add cgroup mount so container process can introspect its own limits + specgen.AddCgroupsMount("ro") + if err := addDevices(sb, containerConfig, &specgen); err != nil { return nil, err } @@ -673,6 +677,12 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string, } } + // Set up pids limit if pids cgroup is mounted + _, err = cgroups.FindCgroupMountpoint("pids") + if err == nil { + specgen.SetLinuxResourcesPidsLimit(s.config.PidsLimit) + } + // by default, the root path is an empty string. set it now. specgen.SetRootPath(mountPoint) diff --git a/test/cgroups.bats b/test/cgroups.bats new file mode 100644 index 00000000..44d1acfc --- /dev/null +++ b/test/cgroups.bats @@ -0,0 +1,40 @@ +#!/usr/bin/env bats + +load helpers + +function teardown() { + cleanup_test +} + +@test "pids limit" { + if ! grep pids /proc/self/cgroup; then + skip "pids cgroup controller is not mounted" + fi + PIDS_LIMIT=1234 start_crio + run crioctl pod run --config "$TESTDATA"/sandbox_config.json + echo "$output" + [ "$status" -eq 0 ] + pod_id="$output" + pids_limit_config=$(cat "$TESTDATA"/container_config.json | python -c 'import json,sys;obj=json.load(sys.stdin); obj["command"] = ["/bin/sleep", "600"]; json.dump(obj, sys.stdout)') + echo "$pids_limit_config" > "$TESTDIR"/container_pids_limit.json + run crioctl ctr create --config "$TESTDIR"/container_pids_limit.json --pod "$pod_id" + echo "$output" + [ "$status" -eq 0 ] + ctr_id="$output" + run crioctl ctr start --id "$ctr_id" + echo "$output" + [ "$status" -eq 0 ] + run crioctl ctr execsync --id "$ctr_id" cat /sys/fs/cgroup/pids/pids.max + echo "$output" + [ "$status" -eq 0 ] + [[ "$output" =~ "1234" ]] + run crioctl pod stop --id "$pod_id" + echo "$output" + [ "$status" -eq 0 ] + run crioctl pod remove --id "$pod_id" + echo "$output" + [ "$status" -eq 0 ] + cleanup_ctrs + cleanup_pods + stop_crio +} \ No newline at end of file diff --git a/test/helpers.bash b/test/helpers.bash index f7703cea..8bf17c62 100644 --- a/test/helpers.bash +++ b/test/helpers.bash @@ -53,6 +53,8 @@ DEFAULT_LOG_PATH=/var/log/crio/pods CGROUP_MANAGER=${CGROUP_MANAGER:-cgroupfs} # Image volumes handling IMAGE_VOLUMES=${IMAGE_VOLUMES:-mkdir} +# Container pids limit +PIDS_LIMIT=${PIDS_LIMIT:-1024} TESTDIR=$(mktemp -d) if [ -e /usr/sbin/selinuxenabled ] && /usr/sbin/selinuxenabled; then @@ -203,7 +205,7 @@ function start_crio() { "$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTS --runroot "$TESTDIR/crio-run" --image-name=mrunalp/image-volume-test --import-from=dir:"$ARTIFACTS_PATH"/image-volume-test-image --add-name=docker.io/library/mrunalp/image-volume-test --signature-policy="$INTEGRATION_ROOT"/policy.json "$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTS --runroot "$TESTDIR/crio-run" --image-name=busybox:latest --import-from=dir:"$ARTIFACTS_PATH"/busybox-image --add-name=docker.io/library/busybox:latest --signature-policy="$INTEGRATION_ROOT"/policy.json "$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTS --runroot "$TESTDIR/crio-run" --image-name=runcom/stderr-test:latest --import-from=dir:"$ARTIFACTS_PATH"/stderr-test --add-name=docker.io/runcom/stderr-test:latest --signature-policy="$INTEGRATION_ROOT"/policy.json - "$CRIO_BINARY" --conmon "$CONMON_BINARY" --listen "$CRIO_SOCKET" --cgroup-manager "$CGROUP_MANAGER" --runtime "$RUNTIME_BINARY" --root "$TESTDIR/crio" --runroot "$TESTDIR/crio-run" $STORAGE_OPTS --seccomp-profile "$seccomp" --apparmor-profile "$apparmor" --cni-config-dir "$CRIO_CNI_CONFIG" --signature-policy "$INTEGRATION_ROOT"/policy.json --image-volumes "$IMAGE_VOLUMES" --config /dev/null config >$CRIO_CONFIG + "$CRIO_BINARY" --conmon "$CONMON_BINARY" --listen "$CRIO_SOCKET" --cgroup-manager "$CGROUP_MANAGER" --runtime "$RUNTIME_BINARY" --root "$TESTDIR/crio" --runroot "$TESTDIR/crio-run" $STORAGE_OPTS --seccomp-profile "$seccomp" --apparmor-profile "$apparmor" --cni-config-dir "$CRIO_CNI_CONFIG" --signature-policy "$INTEGRATION_ROOT"/policy.json --image-volumes "$IMAGE_VOLUMES" --pids-limit "$PIDS_LIMIT" --config /dev/null config >$CRIO_CONFIG # Prepare the CNI configuration files, we're running with non host networking by default if [[ -n "$4" ]]; then