# Host cgroup management

## Introduction

In Kata Containers, workloads run in a virtual machine that is managed by a virtual
machine monitor (VMM) running on the host. As a result, Kata Containers run over two layers of cgroups. The
first layer is in the guest where the workload is placed, while the second layer is on the host where the
VMM and associated threads are running.

The OCI [runtime specification][linux-config] provides guidance on where the container cgroups should be placed:

  > [`cgroupsPath`][cgroupspath]: (string, OPTIONAL) path to the cgroups. It can be used to either control the cgroups
  > hierarchy for containers or to run a new process in an existing container

The cgroups are hierarchical, and this can be seen with the following pod example:

- Pod 1: `cgroupsPath=/kubepods/pod1`
  - Container 1: `cgroupsPath=/kubepods/pod1/container1`
  - Container 2: `cgroupsPath=/kubepods/pod1/container2`

- Pod 2: `cgroupsPath=/kubepods/pod2`
  - Container 1: `cgroupsPath=/kubepods/pod2/container1`
  - Container 2: `cgroupsPath=/kubepods/pod2/container2`

Depending on the upper-level orchestration layers, the cgroup under which the pod is placed is
managed by the orchestrator or not. In the case of Kubernetes, the pod cgroup is created by Kubelet,
while the container cgroups are to be handled by the runtime.
Kubelet will size the pod cgroup based on the container resource requirements, to which it may add
a configured set of [pod resource overheads](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-overhead/).

Kata Containers introduces a non-negligible resource overhead for running a sandbox (pod). Typically, the Kata shim,
through its underlying VMM invocation, will create many additional threads compared to process based container runtimes:
the para-virtualized I/O back-ends, the VMM instance or even the Kata shim process, all of those host processes consume
memory and CPU time not directly tied to the container workload, and introduces a sandbox resource overhead.
In order for a Kata workload to run without significant performance degradation, its sandbox overhead must be
provisioned accordingly. Two scenarios are possible:

 1) The upper-layer orchestrator takes the overhead of running a sandbox into account when sizing the pod cgroup.
    For example, Kubernetes [`PodOverhead`](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-overhead/)
	feature lets the orchestrator add a configured sandbox overhead to the sum of all its containers resources. In
	that case, the pod sandbox is properly sized and all Kata created processes will run under the pod cgroup
	defined constraints and limits.
 2) The upper-layer orchestrator does **not** take the sandbox overhead into account and the pod cgroup is not
	sized to properly run all Kata created processes. With that scenario, attaching all the Kata processes to the sandbox
	cgroup may lead to non-negligible workload performance degradations. As a consequence, Kata Containers will move
	all processes but the vCPU threads into a dedicated overhead cgroup under `/kata_overhead`. The Kata runtime will
	not apply any constraints or limits to that cgroup, it is up to the infrastructure owner to optionally set it up.

Those 2 scenarios are not dynamically detected by the Kata Containers runtime implementation, and thus the
infrastructure owner must configure the runtime according to how the upper-layer orchestrator creates and sizes the
pod cgroup. That configuration selection is done through the `sandbox_cgroup_only` flag within the Kata Containers
[configuration](../../src/runtime/README.md#configuration) file.

## `sandbox_cgroup_only = true`

Setting `sandbox_cgroup_only` to `true` from the Kata Containers configuration file means that the pod cgroup is
properly sized and takes the pod overhead into account. This is ideal, as all the applicable Kata Containers processes
can simply be placed within the given cgroup path.

In the context of Kubernetes, Kubelet can size the pod cgroup to take the overhead of running a Kata-based sandbox
into account. This has been supported since the 1.16 Kubernetes release, through the
[`PodOverhead`](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-overhead/) feature.

```
┌─────────────────────────────────────────┐
│                                         │
│  ┌──────────────────────────────────┐   │
│  │                                  │   │
│  │ ┌─────────────────────────────┐  │   │
│  │ │                             │  │   │
│  │ │ ┌─────────────────────┐     │  │   │
│  │ │ │ vCPU threads        │     │  │   │
│  │ │ │ I/O threads         │     │  │   │
│  │ │ │ VMM                 │     │  │   │
│  │ │ │ Kata Shim           │     │  │   │
│  │ │ │                     │     │  │   │
│  │ │ │ /kata_<sandbox_id>  │     │  │   │
│  │ │ └─────────────────────┘     │  │   │
│  │ │Pod 1                        │  │   │
│  │ └─────────────────────────────┘  │   │
│  │                                  │   │
│  │ ┌─────────────────────────────┐  │   │
│  │ │                             │  │   │
│  │ │ ┌─────────────────────┐     │  │   │
│  │ │ │ vCPU threads        │     │  │   │
│  │ │ │ I/O threads         │     │  │   │
│  │ │ │ VMM                 │     │  │   │
│  │ │ │ Kata Shim           │     │  │   │
│  │ │ │                     │     │  │   │
│  │ │ │ /kata_<sandbox_id>  │     │  │   │
│  │ │ └─────────────────────┘     │  │   │
│  │ │Pod 2                        │  │   │
│  │ └─────────────────────────────┘  │   │
│  │                                  │   │
│  │/kubepods                         │   │
│  └──────────────────────────────────┘   │
│                                         │
│ Node                                    │
└─────────────────────────────────────────┘
```

### Implementation details

When `sandbox_cgroup_only` is enabled, the Kata shim will create a per pod
sub-cgroup under the pod's dedicated cgroup. For example, in the Kubernetes context,
it will create a `/kata_<PodSandboxID>` under the `/kubepods` cgroup hierarchy.
On a typical cgroup v1 hierarchy mounted under `/sys/fs/cgroup/`, the memory cgroup
subsystem for a pod with sandbox ID `12345678` would live under
`/sys/fs/cgroup/memory/kubepods/kata_12345678`.

In most cases, the `/kata_<PodSandboxID>` created cgroup is unrestricted and inherits and shares all
constraints and limits from the parent cgroup (`/kubepods` in the Kubernetes case). The exception is
for the `cpuset` and `devices` cgroup subsystems, which are managed by the Kata shim.

After creating the `/kata_<PodSandboxID>` cgroup, the Kata Containers shim will move itself to it, **before** starting
the virtual machine. As a consequence all processes subsequently created by the Kata Containers shim (the VMM itself, and
all vCPU and I/O related threads) will be created in the `/kata_<PodSandboxID>` cgroup.

### Why create a kata-cgroup under the parent cgroup?

And why not directly adding the per sandbox shim directly to the pod cgroup (e.g.
`/kubepods` in the Kubernetes context)?

The Kata Containers shim implementation creates a per-sandbox cgroup
(`/kata_<PodSandboxID>`) to support the `Docker` use case. Although `Docker` does not
have a notion of pods, Kata Containers still creates a sandbox to support the pod-less,
single container use case that `Docker` implements. Since `Docker` does create any
cgroup hierarchy to place a container into, it would be very complex for Kata to map
a particular container to its sandbox without placing it under a `/kata_<containerID>>`
sub-cgroup first.

### Advantages

Keeping all Kata Containers processes under a properly sized pod cgroup is ideal
and makes for a simpler Kata Containers implementation. It also helps with gathering
accurate statistics and preventing Kata workloads from being noisy neighbors.

#### Pod resources statistics

If the Kata caller wants to know the resource usage on the host it can get
statistics from the pod cgroup. All cgroups stats in the hierarchy will include
the Kata overhead. This gives the possibility of gathering usage-statics at the
pod level and the container level.

#### Better host resource isolation

Because the Kata runtime will place all the Kata processes in the pod cgroup,
the resource limits that the caller applies to the pod cgroup will affect all
processes that belong to the Kata sandbox in the host. This will improve the
isolation in the host preventing Kata to become a noisy neighbor.

## `sandbox_cgroup_only = false` (Default setting)

If the cgroup provided to Kata is not sized appropriately, Kata components will
consume resources that the actual container workloads expect to see and use.
This can cause instability and performance degradations.

To avoid that situation, Kata Containers creates an unconstrained overhead
cgroup and moves all non workload related processes (Anything but the virtual CPU
threads) to it. The name of this overhead cgroup is `/kata_overhead` and a per
sandbox sub cgroup will be created under it for each sandbox Kata Containers creates.

Kata Containers does not add any constraints or limitations on the overhead cgroup. It is up to the infrastructure
owner to either:

- Provision nodes with a pre-sized `/kata_overhead` cgroup. Kata Containers will
  load that existing cgroup and move all non workload related processes to it.
- Let Kata Containers create the `/kata_overhead` cgroup, leave it
  unconstrained or resize it a-posteriori.


```
┌────────────────────────────────────────────────────────────────────┐
│                                                                    │
│  ┌─────────────────────────────┐    ┌───────────────────────────┐  │
│  │                             │    │                           │  │
│  │   ┌─────────────────────────┼────┼─────────────────────────┐ │  │
│  │   │                         │    │                         │ │  │
│  │   │ ┌─────────────────────┐ │    │ ┌─────────────────────┐ │ │  │
│  │   │ │  vCPU threads       │ │    │ │  VMM                │ │ │  │
│  │   │ │                     │ │    │ │  I/O threads        │ │ │  │
│  │   │ │                     │ │    │ │  Kata Shim          │ │ │  │
│  │   │ │                     │ │    │ │                     │ │ │  │
│  │   │ │ /kata_<sandbox_id>  │ │    │ │ /<sandbox_id>       │ │ │  │
│  │   │ └─────────────────────┘ │    │ └─────────────────────┘ │ │  │
│  │   │                         │    │                         │ │  │
│  │   │  Pod 1                  │    │                         │ │  │
│  │   └─────────────────────────┼────┼─────────────────────────┘ │  │
│  │                             │    │                           │  │
│  │                             │    │                           │  │
│  │   ┌─────────────────────────┼────┼─────────────────────────┐ │  │
│  │   │                         │    │                         │ │  │
│  │   │ ┌─────────────────────┐ │    │ ┌─────────────────────┐ │ │  │
│  │   │ │  vCPU threads       │ │    │ │  VMM                │ │ │  │
│  │   │ │                     │ │    │ │  I/O threads        │ │ │  │
│  │   │ │                     │ │    │ │  Kata Shim          │ │ │  │
│  │   │ │                     │ │    │ │                     │ │ │  │
│  │   │ │ /kata_<sandbox_id>  │ │    │ │ /<sandbox_id>       │ │ │  │
│  │   │ └─────────────────────┘ │    │ └─────────────────────┘ │ │  │
│  │   │                         │    │                         │ │  │
│  │   │  Pod 2                  │    │                         │ │  │
│  │   └─────────────────────────┼────┼─────────────────────────┘ │  │
│  │                             │    │                           │  │
│  │ /kubepods                   │    │ /kata_overhead            │  │
│  └─────────────────────────────┘    └───────────────────────────┘  │
│                                                                    │
│                                                                    │
│ Node                                                               │
└────────────────────────────────────────────────────────────────────┘

```

### Implementation Details

When `sandbox_cgroup_only` is disabled, the Kata Containers shim will create a per pod
sub-cgroup under the pods dedicated cgroup, and another one under the overhead cgroup.
For example, in the Kubernetes context, it will create a `/kata_<PodSandboxID>` under
the `/kubepods` cgroup hierarchy, and a `/<PodSandboxID>` under the `/kata_overhead` one.

On a typical cgroup v1 hierarchy mounted under `/sys/fs/cgroup/`, for a pod which sandbox
ID is `12345678`, create with `sandbox_cgroup_only` disabled, the 2 memory subsystems
for the sandbox cgroup and the overhead cgroup would respectively live under
`/sys/fs/cgroup/memory/kubepods/kata_12345678` and `/sys/fs/cgroup/memory/kata_overhead/12345678`.

Unlike when `sandbox_cgroup_only` is enabled, the Kata Containers shim will move itself
to the overhead cgroup first, and then move the vCPU threads to the sandbox cgroup as
they're created. All Kata processes and threads will run under the overhead cgroup except for
the vCPU threads.

With `sandbox_cgroup_only` disabled, Kata Containers assumes the pod cgroup is only sized
to accommodate for the actual container workloads processes. For Kata, this maps
to the VMM created virtual CPU threads and so they are the only ones running under the pod
cgroup. This mitigates the risk of the VMM, the Kata shim and the I/O threads going through
a catastrophic out of memory scenario (`OOM`).

#### Pros and Cons

Running all non vCPU threads under an unconstrained overhead cgroup could lead to workloads
potentially consuming a large amount of host resources.

On the other hand, running all non vCPU threads under a dedicated overhead cgroup can provide
accurate metrics on the actual Kata Container pod overhead, allowing for tuning the overhead
cgroup size and constraints accordingly.

[linux-config]: https://github.com/opencontainers/runtime-spec/blob/main/config-linux.md
[cgroupspath]: https://github.com/opencontainers/runtime-spec/blob/main/config-linux.md#cgroups-path

# Supported cgroups

Kata Containers currently supports cgroups `v1` and `v2`.

In the following sections each cgroup is described briefly.

## cgroups v1

`cgroups v1` are under a [`tmpfs`][1] filesystem mounted at `/sys/fs/cgroup`, where each cgroup is
mounted under a separate cgroup filesystem. A `cgroups v1` hierarchy may look like the following
diagram:

```
/sys/fs/cgroup/
├── blkio
│   ├── cgroup.procs
│   └── tasks
├── cpu -> cpu,cpuacct
├── cpuacct -> cpu,cpuacct
├── cpu,cpuacct
│   ├── cgroup.procs
│   └── tasks
├── cpuset
│   ├── cgroup.procs
│   └── tasks
├── devices
│   ├── cgroup.procs
│   └── tasks
├── freezer
│   ├── cgroup.procs
│   └── tasks
├── hugetlb
│   ├── cgroup.procs
│   └── tasks
├── memory
│   ├── cgroup.procs
│   └── tasks
├── net_cls -> net_cls,net_prio
├── net_cls,net_prio
│   ├── cgroup.procs
│   └── tasks
├── net_prio -> net_cls,net_prio
├── perf_event
│   ├── cgroup.procs
│   └── tasks
├── pids
│   ├── cgroup.procs
│   └── tasks
└── systemd
    ├── cgroup.procs
    └── tasks
```

A process can join a cgroup by writing its process id (`pid`) to `cgroup.procs` file,
or join a cgroup partially by writing the task (thread) id (`tid`) to the `tasks` file.

To know more about `cgroups v1`, see [cgroupsv1(7)][2].

## cgroups v2

`cgroups v2` are also known as unified cgroups, unlike `cgroups v1`, the cgroups are
mounted under the same cgroup filesystem. A `cgroups v2` hierarchy may look like the following
diagram:

```
/sys/fs/cgroup/system.slice
├── cgroup.controllers
├── cgroup.events
├── cgroup.freeze
├── cgroup.max.depth
├── cgroup.max.descendants
├── cgroup.procs
├── cgroup.stat
├── cgroup.subtree_control
├── cgroup.threads
├── cgroup.type
├── cpu.max
├── cpu.pressure
├── cpu.stat
├── cpu.weight
├── cpu.weight.nice
├── io.bfq.weight
├── io.latency
├── io.max
├── io.pressure
├── io.stat
├── memory.current
├── memory.events
├── memory.events.local
├── memory.high
├── memory.low
├── memory.max
├── memory.min
├── memory.oom.group
├── memory.pressure
├── memory.stat
├── memory.swap.current
├── memory.swap.events
├── memory.swap.max
├── pids.current
├── pids.events
└── pids.max
```

Same as `cgroups v1`, a process can join the cgroup by writing its process id (`pid`) to
`cgroup.procs` file, or join a cgroup partially by writing the task (thread) id (`tid`) to
`cgroup.threads` file.

### Distro Support

Many Linux distributions do not yet support `cgroups v2`, as it is quite a recent addition.
For more information about the status of this feature see [issue #2494][4].


[1]: http://man7.org/linux/man-pages/man5/tmpfs.5.html
[2]: http://man7.org/linux/man-pages/man7/cgroups.7.html#CGROUPS_VERSION_1
[3]: http://man7.org/linux/man-pages/man7/cgroups.7.html#CGROUPS_VERSION_2
[4]: https://github.com/kata-containers/runtime/issues/2494
