#container #fuse #mount #nested #exec #host

bin+lib cntr

基于 FUSE 的容器调试工具

9 个稳定版本

1.5.3 2023 年 12 月 21 日
1.5.1 2021 年 3 月 3 日
1.4.1 2020 年 12 月 30 日
1.1.2 2018 年 3 月 15 日

#64调试 分类中

Download history 6/week @ 2024-03-27 6/week @ 2024-04-03 2/week @ 2024-06-05 3/week @ 2024-06-26 86/week @ 2024-07-03

89 每月下载量

MIT 许可证

150KB
4K SLoC

cntr

拒绝在容器中使用 $ apt install vimcntrdocker exec 的替代品,可以将所有开发工具随身携带。这是通过使用 FUSE 文件系统创建一个嵌套容器,将一个容器的文件系统挂载到目标容器中实现的。这允许在生产环境中使用最小的运行时镜像,并限制攻击面。

Cntr 还在 Usenix ATC 2018 上发布。有关引用,请参阅 bibtex

演示

在这两分钟的视频中,您将了解 cntr 的所有基础知识

asciicast

功能

  • 为了方便起见,cntr 原生支持以下容器引擎的容器名称/标识符:
    • docker
    • podman
    • LXC
    • LXD
    • rkt
    • systemd-nspawn
    • containerd
  • 对于其他容器引擎,cntr 也接受进程 ID (PID) 而不是容器名称。

安装

Cntr 仅支持 Linux。

预构建的静态链接二进制文件

对于 Linux x86_64,我们为每个版本构建静态二进制文件。根据需求可以添加更多平台。请参阅 发布标签 获取预构建的 tar 包。运行时只需要容器引擎的命令行工具。

从源代码构建

您需要 rust + cargo 进行编译。请查阅 rustup.rs 了解如何获取一个有效的 rust 工具链。然后运行

要么

$ cargo install cntr

或者最新的 master

$ cargo install --git https://github.com/Mic92/cntr

对于离线构建,我们还提供了一个包含所有依赖项的tarball,可以在这里找到,用于使用cargo-vendor进行编译。

使用方法

从高层次来看,cntr提供了两个子命令:attachexec

  • attach:允许您使用自己的本地shell/命令连接到容器。Cntr将容器挂载到/var/lib/cntr。容器本身将不受影响运行,因为挂载更改对容器进程不可见。
    • 示例:cntr attach <container_id>其中container_id可以是容器标识符或进程ID(请参阅以下示例)。
  • exec:一旦您在容器中,您也可以从容器文件系统中运行命令。由于这些命令可能需要在/而不是/var/lib/cntr上的本地挂载布局中运行,cntr提供了exec子命令以再次chroot到容器中,并重置可能已被shell更改的环境变量。
    • 示例:cntr exec <command>其中command是容器中的可执行文件。

注意:Cntr需要在与容器相同的宿主上运行。如果容器在虚拟机中运行,而cntr在管理程序上运行,则不会工作。

$ cntr --help
Cntr 1.5.1
Jörg Thalheim <[email protected]>
Enter or executed in container

USAGE:
    cntr <SUBCOMMAND>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

SUBCOMMANDS:
    attach    Enter container
    exec      Execute command in container filesystem
    help      Prints this message or the help of the given subcommand(s)
$ cntr attach --help
cntr-attach 1.5.1
Jörg Thalheim <[email protected]>
Enter container

USAGE:
    cntr attach [OPTIONS] <id> [command]...

FLAGS:
    -h, --help    Prints help information

OPTIONS:
        --effective-user <EFFECTIVE_USER>    effective username that should be owner of new created files on the host
    -t, --type <TYPE>                        Container types to try (sperated by ','). [default: all but command]
                                             [possible values: process_id, rkt, podman, docker, nspawn, lxc, lxd,
                                             containerd, command]

ARGS:
    <id>            container id, container name or process id
    <command>...    Command and its arguments to execute after attach. Consider prepending it with '-- ' to prevent
                    parsing of '-x'-like flags. [default: $SHELL]
$ cntr exec --help
cntr-exec 1.5.1
Jörg Thalheim <[email protected]>
Execute command in container filesystem

USAGE:
    cntr exec [command]...

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

ARGS:
    <command>...    Command and its arguments to execute after attach. Consider prepending it with '-- ' to prevent
                    parsing of '-x'-like flags. [default: $SHELL]

Docker

1:找出容器名称/容器ID

$ docker run --name boxbusy -ti busybox
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
55a93d71b53b        busybox             "sh"                22 seconds ago      Up 20 seconds                           boxbusy

提供容器ID...

$ cntr attach 55a93d71b53b
[root@55a93d71b53b:/var/lib/cntr]# echo "I am in a container!"
[root@55a93d71b53b:/var/lib/cntr]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
40: eth0@if41: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
[root@55a93d71b53b:/var/lib/cntr]# vim etc/resolv.conf

...或容器名称。使用cntr exec在cntr shell中执行容器本地命令。

$ cntr attach boxbusy
[root@55a93d71b53b:/var/lib/cntr]# cntr exec -- sh -c 'busybox | head -1'

您还可以使用此存储库中的Dockerfile构建包含cntr的docker容器

$ docker build -f Dockerfile . -t cntr
# boxbusy here is the name of the target container to attach to
$ docker run --pid=host --privileged=true -v /var/run/docker.sock:/var/run/docker.sock -ti --rm cntr attach boxbusy /bin/sh

Podman

请参阅Docker的使用方法,只需将docker替换为podman命令。

LXD

1:创建容器并启动它

$ lxc image import images:/alpine/edge
$ lxc launch images:alpine/edge
$ lxc list
+-----------------+---------+------+------+------------+-----------+
|      NAME       |  STATE  | IPV4 | IPV6 |    TYPE    | SNAPSHOTS |
+-----------------+---------+------+------+------------+-----------+
| amazed-sailfish | RUNNING |      |      | PERSISTENT | 0         |
+-----------------+---------+------+------+------------+-----------+

2:使用cntr连接到容器

$ cntr attach amazed-sailfish
$ cat etc/hostname
amazed-sailfish

LXC

1:创建容器并启动它

$ lxc-create --name ubuntu -t download -- -d ubuntu -r xenial -a amd64
$ lxc-start --name ubuntu -F
...
Ubuntu 16.04.4 LTS ubuntu console
ubuntu login:
$ lxc-ls
ubuntu

2:使用cntr连接到容器

$ cntr attach ubuntu
[root@ubuntu2:/var/lib/cntr]# cat etc/os-release
NAME="Ubuntu"
VERSION="16.04.4 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.4 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial

rkt

1:找出容器UUID

$ rkt run --interactive=true docker://busybox
$ rkt list
UUID            APP     IMAGE NAME                                      STATE   CREATED         STARTED         NETWORKS
c2d2e87e        busybox registry-1.docker.io/library/busybox:latest     running 6 minutes ago   6 minutes ago   default:ip4=172.16.28.3

2:使用cntr连接

# make sure your container is still running!
$ cntr attach c2d2e87e
# Finally not the old ugly top!
[gen0@rkt-c2d2e87e-e798-4341-ae93-26f6cbb7c017:/var/lib/cntr]# htop
...

使用cntr还可以调试rkt的stage1,即使rkt本身没有提供支持。

$ ps aux | grep stage1
joerg    13546  0.0  0.0 120808  1608 pts/12   S+   11:10   0:00 grep --binary-files=without-match --directories=skip --color=auto stage1
root     22232  0.0  0.0  54208  2656 pts/7    S+   10:54   0:00 stage1/rootfs/usr/lib/ld-linux-x86-64.so.2 stage1/rootfs/usr/bin/systemd-nspawn --boot --notify-ready=yes --register=true --link-journal=try-guest --quiet --uuid=c2d2e87e-e798-4341-ae93-26f6cbb7c017 --machine=rkt-c2d2e87e-e798-4341-ae93-26f6cbb7c017 --directory=stage1/rootfs --capability=CAP_AUDIT_WRITE,CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FSETID,CAP_FOWNER,CAP_KILL,CAP_MKNOD,CAP_NET_RAW,CAP_NET_BIND_SERVICE,CAP_SETUID,CAP_SETGID,CAP_SETPCAP,CAP_SETFCAP,CAP_SYS_CHROOT -- --default-standard-output=tty --log-target=null --show-status=0

因此我们使用进程ID而不是容器UUID

$ cntr attach 22232
# new and exiting territory!
[root@turingmachine:/var/lib/cntr]# mount | grep pods
sysfs on /var/lib/cntr/var/lib/rkt/pods/run/c2d2e87e-e798-4341-ae93-26f6cbb7c017/stage1/rootfs/sys type sysfs (ro,nosuid,nodev,noexec,relatime)
tmpfs on /var/lib/cntr/var/lib/rkt/pods/run/c2d2e87e-e798-4341-ae93-26f6cbb7c017/stage1/rootfs/sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /var/lib/cntr/var/lib/rkt/pods/run/c2d2e87e-e798-4341-ae93-26f6cbb7c017/stage1/rootfs/sys/fs/cgroup/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory)

systemd-nspawn

1:启动容器

$ wget https://cloud-images.ubuntu.com/releases/16.04/release/ubuntu-16.04-server-cloudimg-amd64-root.tar.xz
$ mkdir /var/lib/machines/ubuntu
$ tar -xf ubuntu-16.04-server-cloudimg-amd64-root.tar.xz -C /var/lib/machines/ubuntu
$ systemd-nspawn -b -M ubuntu
$ machinectl list
MACHINE CLASS     SERVICE        OS     VERSION ADDRESSES
ubuntu  container systemd-nspawn ubuntu 16.04   -

2:连接

$ cntr attach ubuntu

通用进程ID

cntr所需的最小信息是您想要连接到的容器进程的进程ID。

# Did you now chromium uses namespaces too?
$ ps aux | grep 'chromium --type=renderer'
joerg    17498 11.7  1.0 1394504 174256 ?      Sl   15:16   0:08 /usr/bin/chromium

在这种情况下,17498是我们正在寻找的PID。

$ cntr attach 17498
# looks quite similar to our system, but with less users
[joerg@turingmachine cntr]$ ls -la /
total 240
drwxr-xr-x   23 nobody nogroup    23 Mar 13 15:05 .
drwxr-xr-x   23 nobody nogroup    23 Mar 13 15:05 ..
drwxr-xr-x    2 nobody nogroup     3 Mar 13 15:14 bin
drwxr-xr-x    4 nobody nogroup 16384 Jan  1  1970 boot
drwxr-xr-x   24 nobody nogroup  4120 Mar 13 14:56 dev
drwxr-xr-x   52 nobody nogroup   125 Mar 13 15:14 etc
drwxr-xr-x    3 nobody nogroup     3 Jan  8 16:17 home
drwxr-xr-x    8 nobody nogroup     8 Feb  9 22:10 mnt
dr-xr-xr-x  306 nobody nogroup     0 Mar 13 09:38 proc
drwx------   22 nobody nogroup    43 Mar 13 15:09 root
...

Containerd

对于containerd集成,需要ctr二进制文件。您可以通过运行以下命令来获取二进制文件

$ GOPATH=$(mktemp -d)
$ go get github.com/containerd/containerd/cmd/ctr
$ $GOPATH/bin/ctr --help

将生成的ctr二进制文件放在您的$PATH

1:启动容器

$ ctr images pull docker.io/library/busybox:latest
$ ctr run docker.io/library/busybox:latest boxbusy
$ ctr tasks lists
TASK        PID      STATUS
boxbusy    24310    RUNNING

2:连接

$ cntr attach boxbusy

您还可以从容器本身运行cntr。此存储库包含一个示例Dockerfile

$ docker build -f Dockerfile.example . -t cntr
$ docker save cntr > cntr.tar
$ ctr images import --base-name cntr ./cntr.tar

在此示例中,我们通过进程ID连接到containerd。任务的进程ID在ctr tasks list中给出。

$ ctr run --privileged --with-ns pid:/proc/1/ns/pid --tty docker.io/library/cntr:latest cntr /usr/bin/cntr attach 31523 /bin/sh

为了解析containerd名称,还需要将ctr二进制文件(约12MB)添加到Dockerfile中。

其他配置

ZFS

cntr需要在ZFS下启用POSIX ACL。默认情况下,Linux ZFS没有启用POSIX ACL。这会导致在尝试attach时出现以下错误

unable to move container mounts to new mountpoint: EOPNOTSUPP: Operation not supported on transport endpoint

要启用ZFS数据集的POSIX ACL

$ zfs set acltype=posixacl zpool/media
$ zfs set xattr=sa zpool/media              #  optional, but encouraged for best performance

工作原理

Cntr 是容器无关的:它不是与容器引擎交互,而是实现了底层操作系统的 API。它将每个容器视为一组进程,可以从其中继承属性。

Cntr 继承以下容器属性

  • 命名空间(挂载、uts、pid、net、cgroup、ipc)
  • Cgroups
  • Apparamor/selinux
  • 能力
  • 用户/组 ID
  • 环境变量
  • 以下文件:/etc/passwd、/etc/hostname、/etc/hosts、/etc/resolv.conf

在底层,它会启动一个 shell 或用户定义的程序,继承容器的全部上下文,并将其作为 fuse 文件系统挂载。

我们广泛评估了 cntr 文件系统的正确性和性能,使用了 xfstests 和一系列文件系统性能基准测试(iozone、pgbench、dbench、fio、fs-mark、postmark 等)。

相关项目

  • nsenter
    • 仅覆盖 Linux 命名空间,用户仅限于容器中安装的工具
  • toolbox
    • 从容器连接到主机,这与 Cntr 所做的是相反的

Bibtex

我们在 Usenix ATC 2018 发表了一篇关于 Cntr 的所有技术细节的论文。

@inproceedings{cntr-atc18,
  author = {J{\"o}rg Thalheim and Pramod Bhatotia and Pedro Fonseca and Baris Kasikci},
  title = {Cntr: Lightweight {OS} Containers},
  booktitle = {2018 {USENIX} Annual Technical Conference ({USENIX} {ATC} 18)},
  year = {2018},
}

依赖项

~4–10MB
~104K SLoC