Files
coder/cli/clistat/container.go
Danielle Maywood 13f6645ab9 fix(cli): improve container detection when cgroupns=private (#15156)
Fixes https://github.com/coder/coder/issues/12721

If a container in docker is started with `--cgroupns=private` (which is
the default behaviour in docker) then `/proc/1/cgroup` has the following
content:
```
0::/
```

If a container in docker is started with `--cgroupns=host` then
`/proc/1/cgroup` has the following content (hash will vary):
```
0::/docker/aa86ac98959eeedeae0ecb6e0c9ddd8ae8b97a9d0fdccccf7ea7a474f4e0bb1f
```

Currently we are determining if a host is containerized by assuming the
second scenario. This means the existing behaviour of sniffing
`/proc/1/cgroup` is not always sufficient for checking if a host is
containerized.

According to [the cgroups(7)
man-page](https://man7.org/linux/man-pages/man7/cgroups.7.html) there
exists a `cgroup.type` file in a nonroot cgroup. This exists in Linux
versions after `4.14`.

> Linux 4.14 added thread mode for cgroups v2.

> With the addition of thread mode, each nonroot cgroup now contains a
new file, cgroup.type

This means we can check for the existence of
`/sys/fs/cgroup/cgroup.type` to see if we are in a container or not.
2024-10-21 15:28:32 +01:00

83 lines
2.5 KiB
Go

package clistat
import (
"bufio"
"bytes"
"os"
"github.com/spf13/afero"
"golang.org/x/xerrors"
)
const (
procMounts = "/proc/mounts"
procOneCgroup = "/proc/1/cgroup"
sysCgroupType = "/sys/fs/cgroup/cgroup.type"
kubernetesDefaultServiceAccountToken = "/var/run/secrets/kubernetes.io/serviceaccount/token" //nolint:gosec
)
// IsContainerized returns whether the host is containerized.
// This is adapted from https://github.com/elastic/go-sysinfo/tree/main/providers/linux/container.go#L31
// with modifications to support Sysbox containers.
// On non-Linux platforms, it always returns false.
func IsContainerized(fs afero.Fs) (ok bool, err error) {
cgData, err := afero.ReadFile(fs, procOneCgroup)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, xerrors.Errorf("read file %s: %w", procOneCgroup, err)
}
scn := bufio.NewScanner(bytes.NewReader(cgData))
for scn.Scan() {
line := scn.Bytes()
if bytes.Contains(line, []byte("docker")) ||
bytes.Contains(line, []byte(".slice")) ||
bytes.Contains(line, []byte("lxc")) ||
bytes.Contains(line, []byte("kubepods")) {
return true, nil
}
}
// Sometimes the above method of sniffing /proc/1/cgroup isn't reliable.
// If a Kubernetes service account token is present, that's
// also a good indication that we are in a container.
_, err = afero.ReadFile(fs, kubernetesDefaultServiceAccountToken)
if err == nil {
return true, nil
}
// Last-ditch effort to detect Sysbox containers.
// Check if we have anything mounted as type sysboxfs in /proc/mounts
mountsData, err := afero.ReadFile(fs, procMounts)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, xerrors.Errorf("read file %s: %w", procMounts, err)
}
scn = bufio.NewScanner(bytes.NewReader(mountsData))
for scn.Scan() {
line := scn.Bytes()
if bytes.Contains(line, []byte("sysboxfs")) {
return true, nil
}
}
// Adapted from https://github.com/systemd/systemd/blob/88bbf187a9b2ebe0732caa1e886616ae5f8186da/src/basic/virt.c#L603-L605
// The file `/sys/fs/cgroup/cgroup.type` does not exist on the root cgroup.
// If this file exists we can be sure we're in a container.
cgTypeExists, err := afero.Exists(fs, sysCgroupType)
if err != nil {
return false, xerrors.Errorf("check file exists %s: %w", sysCgroupType, err)
}
if cgTypeExists {
return true, nil
}
// If we get here, we are _probably_ not running in a container.
return false, nil
}