# 容器基础

\*\*容器（Container）\*\*是一个具有特殊属性的任务或任务集合，用于隔离任务并限制对系统资源的访问。

![container](/files/g0qWzF5mUsxBqXHNl7Gj)

## 进程描述符和任务结构

内核将进程列表存储在一个称为\*\*任务列表（task list）\*\*的循环双向链表中。

![task-list](/files/ZywuFsMnrCVFCgmeDEPG)

任务列表中的每个元素都是`struct task_struct`类型的进程描述符。进程描述符包含特定进程的所有信息。

![task-struct](/files/tLzlKb3lm7pfVagB05cE)

### /proc

![proc](https://drawings.jvns.ca/drawings/proc.jpeg)

`/proc`是一个特殊的文件系统挂载（procfs），通过读取"文件"条目直接从内核访问系统和进程信息。

![task-struct-example](/files/eu2aCQEPsS5XiGd5JGru)

## 属性

### 凭证（Credentials）

[凭证（Credentials）](https://man7.org/linux/man-pages/man7/credentials.7.html)是进程标识符。凭证描述任务的用户身份，该身份确定其对共享资源（如文件、信号量和共享内存）的权限。

任务的所有凭证都保存在一个名为`struct cred`的引用计数结构中。每个任务通过其`task_struct`中的`cred`指针指向其凭证。

传统UNIX权限实现区分两类：

* 用户ID为0（root）的特权进程
* 所有其他进程

### 权能（Capabilities）

从内核2.2开始，Linux将与超级用户关联的权限分解为称为[权能（Capabilities）](https://man7.org/linux/man-pages/man7/capabilities.7.html)的不同单元。

Linux权能的引入是为了将root的角色分解为离散的子部分，这些子部分可以授予非root进程，使其能够执行特权操作。

![capabilities](/files/Xt09UAhsUPVKAA9zp17q)

进程具有权能"允许集合"的概念，该集合充当其可能拥有的权能的限制性超集。重要的是，默认情况下，这个边界集合会传递给任何子进程，因此容器的"init"进程为容器内的所有进程创建了一个限制性权能集合（因为所有进程都从PID 1派生）。

容器是应该以受限权能集合运行的任务。

![restricted-capabilities](/files/etSSa3QwuoSpcamlqyzv)

要查看权能列表，可以使用`capsh`：

```bash
$ capsh --print
```

### 文件系统

容器的文件系统根目录通常通过[pivot\_root](https://man7.org/linux/man-pages/man2/pivot_root.2.html)系统调用与其他容器和主机的根文件系统隔离。

容器的根挂载通常植根于容器专用文件系统，如AUFS或OverlayFS。在OverlayFS的情况下，容器的`/`根目录实际上位于`/var/lib/docker/overlay2`中。

![overlayfs-root-path](/files/usPno1IXzPbtkQoyVmxJ)

### 命名空间（Namespaces）

[命名空间（Namespace）](https://man7.org/linux/man-pages/man7/namespaces.7.html)将全局系统资源包装在抽象中，使得命名空间内的进程看起来拥有自己的全局资源隔离实例。对全局资源的更改对命名空间的其他成员进程可见，但对其他进程不可见。

Linux上有8种类型的命名空间。

| 命名空间    | 隔离内容                                                                                                              |
| ------- | ----------------------------------------------------------------------------------------------------------------- |
| Cgroup  | Cgroup根目录                                                                                                         |
| IPC     | 提供SystemV IPC和POSIX消息队列的命名空间版本                                                                                    |
| Network | 提供命名空间和隔离的网络栈。大多数容器用例涉及网络服务，因此这将成为容器的核心功能                                                                         |
| Mount   | 提供挂载点的命名空间视图。结合[pivot\_root()](https://man7.org/linux/man-pages/man2/pivot_root.2.html)系统调用，这将用于隔离容器的文件系统与主机的文件系统 |
| PID     | 提供进程ID（PID）的命名空间树。这允许每个容器拥有完整的隔离进程树，其中它有一个'init'进程，在该命名空间内作为PID 1运行。在容器中运行的进程在主机上的PID与在容器PID命名空间内的PID不同           |
| Time    | 启动和单调时钟                                                                                                           |
| User    | 提供用户ID（UID）和组ID（GID）的命名空间版本。这是现代容器系统最重要的功能之一，因为它用于提供"非特权容器"。这些容器中，容器内的root（UID 0）在容器外部不是root，大大提高了容器的安全性          |
| UTS     | 提供系统标识符的命名空间版本                                                                                                    |

![namespace-user-example](/files/HNGHy6o1EkN6Lcu9cETS)

在大多数情况下，使用[clone(2)](https://man7.org/linux/man-pages/man2/clone.2.html)和[unshare(2)](https://man7.org/linux/man-pages/man2/unshare.2.html)创建新命名空间需要`CAP_SYS_ADMIN`权能。用户命名空间是例外：从Linux 3.8开始，创建用户命名空间不需要特权。

每个进程都有一个`/proc/[pid]/ns/`子目录，为每个命名空间包含一个条目。

参考资料：

* [Quarkslab博客：深入Linux命名空间 - 第1部分](https://blog.quarkslab.com/digging-into-linux-namespaces-part-1.html)
* [Quarkslab博客：深入Linux命名空间 - 第2部分](https://blog.quarkslab.com/digging-into-linux-namespaces-part-2.html)

### Cgroups

[Cgroups](https://man7.org/linux/man-pages/man7/cgroups.7.html)是Linux控制组。

控制组（通常称为cgroups）是一个Linux内核功能，允许将进程组织到层次组中，然后可以限制和监控这些组对各种类型资源的使用。内核的cgroup接口通过称为**cgroupfs**的伪文件系统提供，其中层次结构通过每个挂载中的目录树表达。

![cgroupfs](/files/O2tz2y6cnvFc2DuN4Zo9)

分组在核心cgroup内核代码中实现，而资源跟踪和限制在一组按资源类型的子系统（内存、CPU等）中实现。

cgroup文件系统最初包含一个根cgroup `/`，所有进程都属于它。通过在cgroup文件系统中创建目录来创建新的cgroup。

```bash
$ mkdir /sys/fs/cgroup/cpu/cg1
```

这创建了一个新的空cgroup。可以通过将其PID写入cgroup的`cgroup.procs`文件将进程移动到此cgroup：

```bash
$ echo $$ > /sys/fs/cgroup/cpu/cg1/cgroup.procs
```

### Linux安全模块

[AppArmor](https://apparmor.net/)和[SELinux](https://www.redhat.com/en/topics/linux/what-is-selinux)是Linux安全模块，提供强制访问控制（MAC），其中程序的访问规则由配置文件描述。

Docker和LXC在强制模式下启用默认的LSM配置文件，主要用于限制容器对敏感的`/proc`和`/sys`条目的访问。

该配置文件还拒绝mount系统调用。

### seccomp

从内核3.17开始，Linux有一种通过[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html)子系统过滤系统调用访问的机制。

![seccomp](/files/NaZ6HEVUX85yaHPhVHHu)

Seccomp策略有两个版本：

* **严格模式（Strict mode）** - 一小部分允许的系统调用，无法自定义
* **过滤模式（Filter mode）** - 系统调用过滤器编写为伯克利数据包过滤器（BPF）程序；这允许对系统调用使用设置更细粒度的策略（有一些注意事项，seccomp-bpf过滤器可以检查系统调用参数，但无法解引用指针）

## 容器安全模型

![container-security-model](/files/zjV3f11yZdmUZSh8LMlC)

## 参考资料

* [容器逃逸汇编](https://capsule8.com/assets/ug/us-19-Edwards-Compendium-Of-Container-Escapes.pdf)
* [滥用特权和非特权Linux容器](https://www.nccgroup.com/globalassets/our-research/us/whitepapers/2016/june/container_whitepaper.pdf)
* [理解和强化Linux容器](https://research.nccgroup.com/wp-content/uploads/2020/07/ncc_group_understanding_hardening_linux_containers-1-1.pdf)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gitbook.cdxiaodong.life/rong-qi-an-quan/gai-shu/basics.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
