> For the complete documentation index, see [llms.txt](https://darren.gitbook.io/project/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://darren.gitbook.io/project/k8s-yun-wei-pai-cha/untitled-1/untitled-3.md).

# 如何在Pod中执行宿主机上的命令

基础知识回顾

要回答标题中的疑问，我们首先要清楚，Pod是什么？

Pod的翻译叫容器组，顾名思义，是一组容器。叫做“组”是因为这些容器：

1. 总是被同时调度，调度到同一节点
2. 共享网络，具有相同的IP地址和端口空间，可以通过localhost相互访问
3. 可以基于SystemV信号量、POSIX消息队列等方式，进行进程间通信
4. 共享存储卷（需要各自分别挂载）

从效果上看，容器组运行在一个虚拟的“主机”中。这个“主机”基于Linux命名空间、cgroups等机制和宿主机相互隔离。

虽说容器具有隔离性，但是这种隔离程度远远不如虚拟机，容器本质上就是进程。内核也提供了接口，允许你切换命名空间。只需要切换到宿主机的初始（Initial）命名空间，理论上就可以运行宿主机文件系统中的任何程序，并保证程序的行为正常。Linux命名空间

所谓命名空间，是Linux系统资源的隔离机制。进程可以加入到命名空间，并共享其中的系统资源。命名空间是容器技术的基础之一。

对于外面的进程，命名空间中的资源不可见的， 反之，内部的进程感觉自己拥有完整的全局资源。举例来说，PID是一种系统资源，每个命名空间都可以拥有自己的、值为1的PID，而不会出现混乱。Cgroup命名空间

该命名空间虚拟化进程的Cgroups视图，也就是通过 /proc/\[pid]/cgroup、 /proc/\[pid]/mountinfo看到的Cgroup路径。

每个Cgroup命名空间具有自己的Cgroup根目录集合，文件/proc\[pid]/cgrpup中的路径，都是相对于这些根目录。在 clone或 unshare进程时，你可以指定 CLONE\_NEWCGROUP标记，这会导致进程当前的cgroups目录变为新命名空间的cgroup根目录。此规则对于cgroups v1 v2均适用。

当你通过/proc\[pid]/cgrpup查看目标进程都归属于哪个Cgroup时，该文件的每一行的第3字段相对于当前（读取Cgroup文件的）进程的对应Cgroup子系统根目录。如果目标进程所属Cgroup目录在当前进程Cgroup对应子系统根目录之外，则第3字段中会出现 ../表示上级目录。

上面这段规则很拗口，我们结合例子看： Shell

| 12345678910111213141516171819202122232425262728 | # 在freezer子系统中创建一个子组mkdir -p /sys/fs/cgroup/freezer/sub1  # 创建一个长时间运行的进程sleep 10000 &\[1] 20124# 将上述进程加入新创建的子组echo 20124 > /sys/fs/cgroup/freezer/sub1/cgroup.procs  # 现在，创建一个新的子组mkdir -p /sys/fs/cgroup/freezer/sub2# 将当前Shell加入到新子组echo $$30655echo 30655 > /sys/fs/cgroup/freezer/sub2/cgroup.procs# 查看当前进程所属freezer组cat /proc/self/cgroup \| grep freezer# 输出     相对于当前组Cgroup根目录，也就是 /sys/fs/cgroup/freezer/sub2/7:freezer:/sub2  # 最后，在一个新的，在新的Cgroups中执行Shell：cat /proc/self/cgroup \| grep freezer7:freezer:/cat /proc/20124/cgroup \| grep freezer7:freezer:/../sub1 |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |

IPC命名空间

该命名空间隔离进程间通信资源，也就是System V IPC对象、 POSIX消息队列。以下/proc接口在每个IPC命名空间都是独立的：

1. /proc/sys/fs/mqueue POSIX消息队列
2. /proc/sys/kernel下的System V接口，包括msgmax, msgmnb, msgmni, sem, shmall, shmmax, shmmni,shm\_rmid\_forced
3. /proc/sysvipc下的System V接口

要启用IPC命名空间，在构建内核时需要指定 CONFIG\_IPC\_NS选项。

创建新进程时，使用 CLONE\_NEWIPC标记可以启用新的IPC命名空间。Network命名空间

该命名空间隔离：

1. 网络设备
2. IPv4/IPv6网络栈
3. IP路由表
4. IPtables
5. 端口（套接字）
6. /proc/net（/proc/self/net）目录
7. /sys/class/net目录
8. /proc/sys/net下若干文件
9. Unix Domain Socket

等网络相关资源。创建新进程时，使用 CLONE\_NEWNET标记可以开启新的网络命名空间。

每个物理网络设备，仅仅能存在于单个网络命名空间中。当网络命名空间终结（命名空间中最后一个进程退出）后，其中的网络设备归还到初始网络命名空间（而不是最后一个进程的父进程所属的网络命名空间）。

一个虚拟网络设备（veth）对，可以用于创建两个网络命名空间之间的、行为类似于管道的隧道，也可以用于创建到其它网络命名空间物理设备的网桥。当网络命名空间终结时，其veth设备自动销毁。

任何网络设备，包括veth都可以在不同网络命名空间中移动。在Kubernetes中基于Calico构建CNI时，对于每个Pod都会创建一个veth对，在Pod网络命名空间为eth0，在宿主机网络命名空间为cali\*\*\*：Shell

| 123456789101112131415161718 | # 在宿主机上执行ip link list# ...34: cali84f62caf29f\@if4: \<BROADCAST,MULTICAST,UP,LOWER\_UP> mtu 1440 qdisc noqueue state UP mode DEFAULT group default     link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 16  # 在容器中执行ip link list# ...4: eth0\@if34: \<BROADCAST,MULTICAST,UP,LOWER\_UP> mtu 1440 qdisc noqueue state UP mode DEFAULT group default    link/ether c2:8a:a9:d4:b5:46 brd ff:ff:ff:ff:ff:ff  # 容器以宿主机为路由器，和外部通信# 在宿主机执行route -n172.27.68.135   0.0.0.0         255.255.255.255 UH    0      0        0 cali84f62caf29f |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

要启用网络命名空间，在构建内核时需要指定 CONFIG\_NET\_NS选项。Mount命名空间

该命名空间隔离挂载点，也就是隔离文件系统目录树中可以看到的内容。

文件 /proc/\[pid]/mounts、 /proc/\[pid]/mountinfo、 /proc/\[pid]/mountstats中的内容，取决于目标进程所属的Mount命名空间。

创建新进程时，使用标记 CLONE\_NEWNS可以创建新的Mount命名空间。新Mount命名空间的初始化挂载点列表如下：

1. 如果命名空间通过 clone创建，则挂载点列表是父进程Mount命名空间挂载点列表的副本
2. 如果命名空间通过 unshare创建，则挂载点列表取决于调用者的Mount命名空间

默认情况下，使用mount/umount系统调用修改挂载点列表，不会影响其它命名空间。注意点

关于Mount命名空间，需要注意：

1. 每个Mount命名空间归属于一个User命名空间。上文提到新创建Mount命名空间会复制挂载点，如果两个Mount命名空间所属的User命名空间不同，则新命名空间是less\
   privileged的
2. 当前创建了less privileged的Mount命名空间时，Shared Mount退化为Slave Mount，以确保在less privileged空间进行的mapping不会传播到more priviledeged命名空间
3. 来自more privileged命名空间的挂载点，是一个不可分割的整体，不能在less privileged命名空间中被分离
4. mount调用的选项MS\_RDONLY、MS\_NOSUID、MS\_NOEXEC，以及MS\_NOATIME、MS\_NODIRATIME、MS\_RELATIME被锁定，不能在less privileged中被修改
5. 一个文件或目录，在命名空间A中可能是挂载点，在命名空间B则不是挂载点。这些目录或文件可能被重命名、unlink或者删除，其结果是，那些将其作为挂载点的命名空间，对应的挂载点会被删除。在3.18-版本中，重命名、unlink、删除这种挂载点目录，会导致EBUSY错误

共享子树

在某些情况下，Mount命名空间提供的隔离太重了。举例来说，要让一个新载入的光盘能够在所有命名空间可见，必须在每个命名空间执行挂载操作。为了避免这种麻烦，从2.6.15开始，内核引入了共享子树特性，该特性允许跨越命名空间的、受控传播的自动mount/umount。

每个挂载点的传播类型，可以设置为：

1. MS\_SHARED：表示该挂载点在对等组（Peer Group）成员之间共享mount/umount事件。也就是说组中任何命名空间进行了mount，其它命名空间自动的也进行mount
2. MS\_PRIVATE：表示该挂载点是私有的，不具有对等组
3. MS\_SLAVE：允许来自（Master）共享对等组的mount/umount事件传播到此挂载点。反之，该挂载点发起的mount/umount事件则不会传播。注意一个对等组可以是另外一个的Slave
4. MS\_UNBINDABLE：类似于MS\_PRIVATE，增加一个额外限制，禁止绑定挂载（mount --bind，用于将某个目录挂载到另一个位置，两个地方内容一致）

新创建的挂载点加入旧挂载点所属的对等组的条件是：

1. 旧挂载点被设置为MS\_SHARED
2. 并且，满足以下之一：
   1. 创建新命名空间时旧挂载点被复制
   2. 从旧挂载点创建了一个新的绑定挂载

PID命名空间

该命名空间隔离进程ID空间，允许不同命名空间中的进程拥有相同的ID。要启用此特性，编译内核时需要指定 CONFIG\_PID\_NS选项。

PID命名空间允许容器：

1. 暂停/恢复容器中的一组进程
2. 将容器迁移到新的宿主机，而保持容器中进程的PID不变

利用PID命名空间，你可以暂停容器中的一组进程，并且在另外一台机器上恢复它们，而保持PID不变。

在新的PID命名空间中，生成的PID从1开始，看起来就像是独立的系统。fork/vfork/clone等系统调用会生成在当前命名空间中唯一的PID。命名空间的Init进程

新命名空间中创建（执行clone/unshare系统调用时指定CLONE\_NEWPID标记）的第一个进程，具有PID 1，作为命名空间的"init"进程。此进程将作为命名空间中任何孤儿进程（其父进程提前死亡）的养父进程。

如果PID 1死亡，则内核以SIGKILL信号杀死命名空间中所有其它进程，此行为遵循规则：Init进程是PID命名空间执行正确行为的基础。在此情况下，针对命名空间的后续fork调用会导致ENOMEM错误。

只有已经被Init进程设置了处理器的信号，才可以从命名空间其它进程发送给Init进程。即使是特权进程也不能违反此规则，这防止Init进程被意外的杀死。

类似的，祖先命名空间中的进程，也只有在Init进程设置了处理器的前提下，才能发送信号给Init。但是SIGKILL、SIGSTOP不受限制，这些信号被强制的从祖先命名空间递送给Init进程，并且这两个信号不能被Init进程捕获处理，结果就是导致Init进程终结。

从内核3.4开始，系统调用reboot会导致命名空间中的Init进程接收到信号。嵌套PID命名空间

PID命名空间可以形成树状层次，除了初始（root，initial）命名空间之外，所有PID命名空间都具有一个父命名空间。父命名空间就是调用clone/unshare生成新PID命名空间的那个进程，所属的PID命名空间。从3.7开始，PID命名空间树的深度被限制为最多32。

一个进程可以被以下进程看到：

1. 同一命名空间内的进程
2. 直接或者间接的祖先命名空间中的进程

这意味着初始命名空间可以看到所有进程。反之，子代命名空间则不能看到祖代命名空间中的进程。

所谓“看到”，是指可以以PID为参数，针对目标进程执行系统调用，例如kill、 setpriority。很显然，在每个祖代命名空间看到的，同一个实际进程的PID是不一样的。执行系统调用时，必须使用调用者所在命名空间的PID“视图”。

通过系统调用setns，进程可以自由的加入子代PID命名空间，但是却不能加入祖代命名空间。UTS命名空间

该命名空间提供两种系统标识符 —— 主机名（hostname）、NIS域名——的隔离。

通过clone/unshare系统调用创建新的UTS命名空间时，来自调用者UTS命名空间的标识符会自动拷贝过来。User命名空间

该命名空间隔离安全相关的标识符、属性。包括User IDs、Group IDs、root目录、密钥（keyrings）、能力（ Capabilities）。

User命名空间是一个特殊的命名空间，它和其它命名空间具有交互性，它可以拥有（Own）其它命名空间。

一个进程的UID、GID在命名空间内外可以不同。一个进程可以在命名空间内部具有UID 0，而在外部仅仅具有一个非特权UID，这意味着进程可以在一个命名空间内进行特权操作，而在此命名空间之外却不可以。嵌套User命名空间

类似于PID命名空间，User命名空间也是可以嵌套的。除了初始命名空间之外，所有User命名空间都具有一个父命名空间。父命名空间就是创建新命名空间（clone/unshares时指定标记CLONE\_NEWUSER）的那个进程所属的User命名空间。

从3.11版本开始，内核限制User命名空间树的最大深度为32。

每个进程都是单个User命名空间的成员，单线程的进程如果具有CAP\_SYS\_ADMIN能力则可以调用setns来加入其它User命名空间，从而获得目标命名空间的所有能力（capabilities）。

执行系统调用execve会导致能力的重新计算，结果就是，除非进程在当前命名空间具有UID 0或者进程的可执行文件提供了一个非空的可继承的能力掩码（Capabilities mask），进程会丢失所有能力。能力

基于CLONE\_NEWUSER创建的进程，拥有新创建的命名空间的所有能力。类似的，通过setns加入即有命名空间后，进程具有目标命名空间的所有能力。相反的，进程在它的祖代命名空间、或者先前的命名空间，则没有任何能力。

确定命名空间中一个进程是否具有某项能力的规则如下：

1. 如果进程的有效能力集（Effective capability set）中设置了能力A，则它具有所在命名空间的能力A。进程的有效能力集可以因多种原因设置：
   1. 执行了Set User ID的程序
   2. 执行了具有关联的能力的可执行文件
   3. 因为clone/unshare/setns调用而设置的能力
2. 如果进程在命名空间中具有能力，则在该命名空间的所有子代命名空间中同样具有相同的能力
3. 当User命名空间被创建时，内核将创建它的进程的有效UID记录为命名空间的Owner。此命名空间的父命名空间中 ，任何有效UID为Owner的进程，都具有该User命名空间的所有能力

能力的作用

如果进程在User命名空间中具有能力，那么它就可以对该命名空间管理的资源执行相应的特权操作。更精确的说，是对该User命名空间所拥有（关联）的（非User）命名空间所管理的资源具有特权操作。举例来说，进程P1所于UTS命名空间N1，N1属于User命名空间N2，那么，P1必须在N2中持有CAP\_SYS\_ADMIN能力，才能执行sethostname系统调用。

从另一角度来说，很多特权操作会影响到，不被任何命名空间管理的资源。例如：

1. 修改时间（对应能力CAP\_SYS\_TIME）
2. 加载内核模块（对应能力CAP\_SYS\_MODULE）
3. 创建设备（对应能力CAP\_MKNOD）

这些操作，仅仅能由位于初始User命名空间中的特权进程执行。

在拥有进程所属Mount命名空间的User命名空间中，持有CAP\_SYS\_ADMIN能力，允许进程：

1. 创建Bind挂载
2. 挂载以下类型的文件系统
   1. /proc，要求内核3.8+
   2. /sys，要求内核3.8+
   3. devpts，要求内核3.9+
   4. tmpfs，要求内核3.9+
   5. ramfs，要求内核3.9+
   6. mqueue，要求内核3.9+
   7. bpf，要求内核4.4+

在拥有进程所属Cgroup命名空间的User命名空间中，持有CAP\_SYS\_ADMIN能力，允许进程：

1. 从内核4.6开始，挂载Cgroup v2文件系统
2. 挂载Cgroup v1命名结构（即通过选项none,name=挂载的Cgroups文件系统）

但是，挂载块设备的文件系统，必须要求进程具有初始User命名空间的CAP\_SYS\_ADMIN能力。

在拥有进程所属PID命名空间的User命名空间中，持有CAP\_SYS\_ADMIN能力，允许进程：

1. 挂载/proc文件系统

和其它命名空间的交互

从3.8开始，非特权进程可以创建User命名空间，而其它类型的命名空间，只需要调用者进程具有所在User命名空间中的CAP\_SYS\_ADMIN能力。

当一个非User命名空间被创建时，它被创建者进程，在创建它的那个时刻，所属于的User命名空间，所拥有（Own）。

非User命名空间管理了某些系统资源，要对这些资源进行会特权操作，则操作者进程必须在拥有这些命名空间的User命名空间中，持有必要的能力。

执行clone/unshare系统调用时，如果指定了CLONE\_NEWUSER的同时，也指定了其它CLONE\_NEW\*标记，那么内核会确保User命名空间首先被创建。并且将随后创建的其它类型的命名空间的特权赋予子进程（clone）、调用者进程（unshare）。

执行clone/unshare系统调用时，如果指定了非CLONE\_NEWUSER之外的CLONE\_NEW\*标记，则内核会将调用者进程的User命名空间作为新创建的命名空间的Owner。这种Owner关系是可以被改变的。UID/GID映射

对于新创建的User命名空间，它的UID/GID到父User命名空间对应的UID/GID的映射关系，是空的。

UID/GID映射关系，通过文件 /proc/\[pid]/uid\_map 和 /proc/\[pid]/gid\_map暴露。这些文件可以在一个User命名空间中读取，可以被写入单次以定义映射关系。

注意，uid\_map文件中定义的是，以读取者进程的视角来看，PID进程所属的User命名空间的UID ⇨ 读取者进程所属的User命名空间的UID的映射关系。对于gid\_map类似。相关命令

参考[Linux命令知识集锦](https://blog.gmem.cc/linux-command-faq#nsenter)。相关系统调用setns

该系统调用用于将一个线程关联到命名空间：C

| 12345678910111213141516171819 | #define \_GNU\_SOURCE#include \<sched.h> // fd：引用目标命名空间的文件描述符，对应/proc/\[pid]/ns/目录中的一个条目// nstype：执行此系统调用的线程可以关联那些类型的命名空间：//   0 允许任何类型的命名空间//   CLONE\_NEWIPC   fd必须是IPC命名空间//   CLONE\_NEWNET   fd必须是网络命名空间//   CLONE\_NEWUTS   fd必须是UTS命名空间//   CLONE\_NEWNS    fd必须是Mount命名空间//   CLONE\_NEWPID   fd必须是PID命名空间//   CLONE\_NEWUSER  fd必须是User命名空间//   CLONE\_NEWCGROUP fd必须是Cgroup命名空间（4.6+）// 返回值：0表示成功，-1表示错误，errno：//   EBADF 无效文件描述符//   EINVAL fd引用的命名空间类型和nstype不匹配，或者关联线程到命名空间时出现错误//   ENOMEM 没有足够内存//   EPERM 调用者线程缺少必要的权限（CAP\_SYS\_ADMIN）int setns(int fd, int nstype); |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

下面是一个例子：C

| 1234567891011121314151617 | #define \_GNU\_SOURCE#include \<fcntl.h>#include \<sched.h>#include \<unistd.h>#include \<stdlib.h>#include \<stdio.h> int main(int argc, char \*argv\[]){    int fd = open("/proc/1/ns/mnt", O\_RDONLY);   /\* 读取文件描述符 \*/    if (fd == -1) exit(EXIT\_FAILURE);    // 加入命名空间    if (setns(fd, 0) == -1)   exit(EXIT\_FAILURE);    // 在命名空间中执行命令    char \* argv\[] = {"ls", "-l", "/proc", 0};    execvp( "ls", argv);} |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

注意各种加入各种命名空间，都具有很多限制条件：

| 命名空间    | 说明                                                                                                                                                                                                                                                                     |
| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| User    | <p>当前进程必须具有目标User命名空间的CAP\_SYS\_ADMIN权限，这意味着只能进入子代User命名空间</p><p>加入命名空间后，将获得目标命名空间的所有能力，不论进程的UID/PID</p><p>多线程上下文下（也就是多线程进程）不能通过setns修改User命名空间</p><p>进程也不能重新进入自己本来的User命名空间，这个限制防止已经丢弃某种能力的进程通过setns重新获得该能力</p><p>出于安全的考虑，如果进程和其它进程共享了文件系统有关的属性，则不能加入到新的mount命名空间</p> |
| Mount   | <p>当前进程必须具有在其本身的User命名空间中具有CAP\_SYS\_CHROOT、CAP\_SYS\_ADMIN能力。同时，在拥有目标Mount命名空间的User命名空间中具有CAP\_SYS\_ADMIN能力</p><p>出于安全的考虑，如果进程和其它进程共享了文件系统有关的属性，则不能加入到新的mount命名空间</p>                                                                                                 |
| PID     | <p>当前进程在本身的User命名空间，拥有目标PID命名空间的User命名空间中都需要CAP\_SYS\_ADMIN能力</p><p>只有调用者创建的子进程的PID命名空间才被修改，调用者本身的不会修改，这是setns针对PID命名空间的特殊之处</p><p>只能切换到子代PID命名空间中</p>                                                                                                                 |
| Cgroup  | <p>当前进程在本身的User命名空间，拥有目标PID命名空间的User命名空间中都需要CAP\_SYS\_ADMIN能力</p><p>此操作不会改变调用者的Cgroups成员关系 —— 也就是说不会将其移动到其它Cgourp</p>                                                                                                                                                  |
| Network | 当前进程在本身的User命名空间，拥有目标PID命名空间的User命名空间中都需要CAP\_SYS\_ADMIN能力                                                                                                                                                                                                             |
| IPC     |                                                                                                                                                                                                                                                                        |
| UTS     |                                                                                                                                                                                                                                                                        |

K8S共享命名空间相关Pod字段

在K8S的Pod的Spec中，和命名空间有关的编排配置包括：Go

| 1234567891011 | type PodSpec struct {    // 使用宿主机的网络命名空间    HostNetwork bool \`json:"hostNetwork,omitempty" protobuf:"varint,11,opt,name=hostNetwork"\`    // 使用宿主机的PID命名空间    HostPID bool \`json:"hostPID,omitempty" protobuf:"varint,12,opt,name=hostPID"\`    // 使用宿主机的IPC命名空间    HostIPC bool \`json:"hostIPC,omitempty" protobuf:"varint,13,opt,name=hostIPC"\`    // 让所有容器共享同一个PID命名空间，此选项不能和HostPID同时设置    // 启用该选项后，容器的第一个进程不会赋予PID 1    ShareProcessNamespace \*bool \`json:"shareProcessNamespace,omitempty" protobuf:"varint,27,opt,name=shareProcessNamespace"\`} |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

创建一个启用上述配置，和宿主机共享网络、PID、IPC命名空间的Pod后，通过 kubectl exec执行 ps aux，你会看到宿主机上的进程。

其它User、Mount、Cgroup等几种命名空间，没有相应的配置字段。需要强调的是，Mount命名空间无法共享，容器需要独立的文件系统树来挂载镜像。仅仅通过K8S提供的编排配置，无法实现我们的目标 —— 因为看不到和宿主机一样的文件系统树。K8S安全配置特权模式

上文提到过，内核允许切换到某个命名空间，然后执行应用程序。系统调用setns、unshare、clone等提供了切换命名空间的接口，命令行工具nsenter、unshare也可以实现相同的功能。

如果我们在启用上述配置的容器中执行 nsenter，尝试切换到初始Mount命名空间，会提示Permission denied错误：Shell

| 12 | nsenter -m -t 1                                                                                                                                                      nsenter: cannot open /proc/1/ns/mnt: Permission denied |
| -- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

这说明容器没有足够的权限进行操作。

Kubernets允许容器以特权模式运行，你只需要配置安全上下文即可。安全上下文包括Pod、Container两个级别。Pod安全上下文Go

| 1234567891011121314151617181920212223242526272829303132 | type PodSpec struct {    // 提供Pod级别的安全属性，并为容器安全属性提供默认值    SecurityContext \*PodSecurityContext \`json:"securityContext,omitempty" protobuf:"bytes,14,opt,name=securityContext"\`}  type PodSecurityContext struct {    // 应用到所有容器的SELinux上下文，如果不指定，则容器运行时为每个容器指定随机的SELinux上下文    SELinuxOptions \*SELinuxOptions \`json:"seLinuxOptions,omitempty" protobuf:"bytes,1,opt,name=seLinuxOptions"\`    // 运行容器进程入口点使用的UID，默认从镜像元数据中获取UID    RunAsUser \*int64 \`json:"runAsUser,omitempty" protobuf:"varint,2,opt,name=runAsUser"\`    // 运行容器进程入口点使用的GID，默认从使用容器运行时的默认值    RunAsGroup \*int64 \`json:"runAsGroup,omitempty" protobuf:"varint,6,opt,name=runAsGroup"\`    // 提示容器必须以非Root身份运行，如果设置为true，则Kubelet会在运行时校验镜像    // 确保它不以UID 0运行，如果发现镜像以UID 0 进行则导致启动失败    RunAsNonRoot \*bool \`json:"runAsNonRoot,omitempty" protobuf:"varint,3,opt,name=runAsNonRoot"\`    // 额外的补充组，赋予容器的第一个进程，作为组GID的补充    SupplementalGroups \[]int64 \`json:"supplementalGroups,omitempty" protobuf:"varint,4,rep,name=supplementalGroups"\`    // 一个特殊的、应用到所有容器的补充组    // 某些类型的卷，允许Kubelet修改卷的所有者，这可以确保容器有权访问卷的内容    // 该选项导致：    // 1. 卷的所有者GID设置为FSGroup    // 2. setgid位被启用，这导致卷中新创建的文件的所有者为FSGroup    // 3. 卷中文件的模式和rw-rw----进行或操作，也就是启用所有者、所在组的读写权限     // 如果不配置，kubelet不会修改任何卷的所有者和文件模式     FSGroup \*int64 \`json:"fsGroup,omitempty" protobuf:"varint,5,opt,name=fsGroup"\`    // 指定一系列命名空间化的Sysctl键值    // 如果容器运行时不支持某个Sysctl则可能导致启动失败    Sysctls \[]Sysctl \`json:"sysctls,omitempty" protobuf:"bytes,7,rep,name=sysctls"\`} |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

容器安全上下文

容器安全上下文中，有一部分字段和Pod安全上下文一样，它们会覆盖Pod安全上下文中的对应设置。 Go

| 1234567891011121314151617181920212223242526 | type Container struct {    SecurityContext \*SecurityContext \`json:"securityContext,omitempty" protobuf:"bytes,15,opt,name=securityContext"\`}  type SecurityContext struct {    // 需要给容器添加/删除的能力列表，默认能力取决于容器运行时    Capabilities \*Capabilities \`json:"capabilities,omitempty" protobuf:"bytes,1,opt,name=capabilities"\`    // 以特权模式运行容器。这种模式下，容器中进程的身份等价于宿主机的root    Privileged \*bool \`json:"privileged,omitempty" protobuf:"varint,2,opt,name=privileged"\`    // 覆盖Pod上下文设置    SELinuxOptions \*SELinuxOptions \`json:"seLinuxOptions,omitempty" protobuf:"bytes,3,opt,name=seLinuxOptions"\`    // 覆盖Pod上下文设置    RunAsUser \*int64 \`json:"runAsUser,omitempty" protobuf:"varint,4,opt,name=runAsUser"\`    // 覆盖Pod上下文设置    RunAsGroup \*int64 \`json:"runAsGroup,omitempty" protobuf:"varint,8,opt,name=runAsGroup"\`    // 覆盖Pod上下文设置    RunAsNonRoot \*bool \`json:"runAsNonRoot,omitempty" protobuf:"varint,5,opt,name=runAsNonRoot"\`    // 容器的根文件系统是否设置为只读    ReadOnlyRootFilesystem \*bool \`json:"readOnlyRootFilesystem,omitempty" protobuf:"varint,6,opt,name=readOnlyRootFilesystem"\`    // 是否允许子进程获得比父进程更多的特权，控制容器进程的no\_new\_privs标记是否被设置    // 如果容器是运行在特权模式，或者具有CAP\_SYS\_ADMIN能力，则该配置自动为true    AllowPrivilegeEscalation \*bool \`json:"allowPrivilegeEscalation,omitempty" protobuf:"varint,7,opt,name=allowPrivilegeEscalation"\`    // 指定该容器的proc挂载类型，默认的    ProcMount \*ProcMountType \`json:"procMount,omitempty" protobuf:"bytes,9,opt,name=procMount"\`} |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

Privileged&#x20;

要满足我们的需求，只需要设置容器安全上下文的Privileged为True就足够了。这样你就可以通过nsenter进入宿主机的Mount命名空间，并且随意的运行命令了，例如通过systemctl判断某些服务是否正常运行。K8S完整配置样例

这个样例允许我们在容器中访问宿主机的日志、控制宿主机的systemd，而不需要切换整个Mount命名空间。我们目前项目的一个需求就是，能够读取节点的内核日志环、Journald日志，可以用下面这种卷挂载的方式满足：YAML

```
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: centos
  namespace: kube-system
spec:
  selector:
    matchLabels:
      name: centos
  template:
    metadata:
      labels:
        name: centos
    spec:
      # 加入宿主机网络命名空间
      hostNetwork: true
      # 加入宿主机PID命名空间
      hostPID: true
      # 加入宿主机IPC命名空间
      hostIPC: true 
      containers:
      - image: docker.gmem.cc/centos:7.6
        imagePullPolicy: Always
        name: centos
        securityContext:
          # 设置PID为root
          runAsUser: 0
          # 特权模式
          privileged: true
        volumeMounts:
        # 这个挂载允许容器中的systemctl和宿主机的systemd通信
        - name: dbus
          mountPath: /var/run/dbus
        - name: run-systemd
          mountPath: /run/systemd
        # 这个挂载允许查看宿主机的systemd配置
        - name: etc-systemd
          mountPath: /etc/systemd
        # 这个挂载允许容器读取非journald管理的日志
        - name: var-log
          mountPath: /var/log
        - name: var-run
          mountPath: /var/run
        - name: run
          mountPath: /run
        - name: usr-lib-systemd
          mountPath: /usr/lib/systemd
      volumes:
      - name: dbus
        hostPath:
          path: /var/run/dbus
          type: Directory
      - name: run-systemd
        hostPath:
          path: /run/systemd
          type: Directory
      - name: etc-systemd
        hostPath:
          path: /etc/systemd
          type: Directory
      - name: var-log
        hostPath:
          path: /var/log
          type: Directory
      - name: var-run
        hostPath:
          path: /var/run
          type: Directory
      # /var/run 是 /run的符号链接
      - name: run
        hostPath:
          path: /run
          type: Directory
      - name: usr-lib-systemd
        hostPath:
          path: /usr/lib/systemd
          type: Directory
```

切换命名空间nsenter

这是一个命令行工具，能够在指定的命名空间中执行命令。K8S的[utils/nsenter](https://github.com/kubernetes/utils/blob/master/nsenter/nsenter.go)包对该命令进行了封装，可以参考。setns

要编程式的切换命名空间，可以利用这个系统调用。&#x20;

在Go语言下，你需要注意的一点是，setns调用可能需要单线程上下文。而Go运行时是多线程的，你必须在Go运行时启动之前，执行setns调用。要实现这种提前调用，可以利用cgo的constructor技巧，该技巧能够在Go运行时启动之前，执行一个任意的C函数：Go

| 1234567 | /\*\_\_attribute\_\_((constructor)) void init() {    // 这里的代码会在Go运行时启动前执行    // 它会在单线程的C上下文中运行}\*/import "C" |
| ------- | ------------------------------------------------------------------------------------------------------------ |

libcontainer提供了[基于此技巧的例子](https://github.com/opencontainers/runc/tree/master/libcontainer/nsenter)。
