返回顶部

RedHat OpenShift QuickStart 2.4 容器主机

原文:https://learn.openshift.com/subsystems/container-internals-lab-2-0-part-4

 

一、容器引擎&Linux内核

如果在搜索引擎上搜索docker架构,会出现大量描述错误的架构设计或者只讲对了一部分。

为什么人们总是理解错误,有两个主要原因:

首先,大多数架构图都将docker守护进程画为容器主机上的蓝色框,显示容器运行在docker守护进程之上,那是错误的,容器不在docker上运行,docker引擎只是通用容器引擎的一个例子,人们将命令传达给docker引擎,docker引擎在将其传给Linux内核-事实上容器是由Linux内核创建、运行的。即使画对了容器引擎和内核的架构关系,他们也从未显示和容器引擎并排运行的容器。

其次,当设计图显示容器是Linux进程时,它们从不并排显示容器引擎。这导致人们从不把这两件事放在一起考虑,因此用户会对一部分内容感到困惑:

做一个简单的实验,使用top命令运行三个容器

docker run -td registry.access.redhat.com/ubi7/ubi top
docker run -td registry.access.redhat.com/ubi7/ubi top
podman run -td registry.access.redhat.com/ubi7/ubi top

现在来检查容器主机的进程表:

ps -efZ | grep -v grep | grep " top"

ps -efZ # 查看主体(进程)的安全上下文

grep -v grep # 排除掉带grep命令的进程

grep " top " # 查看带有top的进程

 

现在在每个容器中都使用了 top 命令,两个使用docker启动的,一个使用podman启动的。通过ps 命令我们发现他们就是常规的进程,因为容器化进程只是花哨的Linux进程,和普通进程之间存在额外的隔离。docker 守护进程和容器化进程是并排运行的,就像下图这样:

在内核中,没有单一的数据结构表示容器是什么。目前Linux社区的思想是提供许多从试验性的到非常成熟的技术,使用户能够以创造性的方式来将其混合在一起。这正是容器引擎所做的(docker,podman,CRI-O等),它利用内核去创建我们称之为容器等东西。容器的概念是人们创造出来的,而不是内核。这是Linux常见的模式:区分底层(kernel)技术和上层(userspace)技术,这允许内核开发者专注于新增技术,而用户可以尝试使用这些技术去更好的工作。

 

 

Linux内核只有一个主要的数据结构来追踪进程--进程id表。ps命令转储此数据结构的内容。但是这不是容器的全部定义:容器引擎追踪使用了哪些内核隔离技术,甚至是挂载了哪些数据卷,这些可以看作是容器的元数据。目前为止,我们应该明白容器化进程就是常规的Linux进程,使用namespace、selinux和cgroups内核技术将其隔离。有时将其描述为虚拟化的沙盒、隔离或者幻觉。

最后,容器化进程只是常规的Linux进程。所有的进程并排运行,无论是常规的Linux进程、长期运行的守护进程、批处理进程、你手动运行的交互式命令或者是虚拟化进程。所有这些进程都会向Linux内核请求受保护的资源像存储,RAM,TCP套接字等。

 

二、一步一步来创建一个容器

这一节看一下容器等基本构造,几乎所有符合OCI的容器引擎的容器基本构造都相同。

  1. 拉取/扩展/挂载镜像
  2. 创建符合OCI规范的文件
  3. 使用spec文件调用runc

1. 拉取/扩展/挂载镜像

Podman可让您轻松分解容器构造的每个步骤以进行学习。首先拉取镜像,拓展它,给容器创建一个新的覆盖文件系统作为容器的读/写根文件系统。为了做到这一点,使用一个特殊构造的容器镜像,来分解步骤,而不是一次全都开始。

podman create -dt --name on-off-container -v /mnt:/mnt:Z quay.io/fatherlinux/on-off-container

使用上面命令使用本地镜像创建一个容器,使用 podman ps -a 命令来查看状态,容器是created状态,而不是running。

 使用mount命令来查看存储:mount | grep -v docker | grep merged 。发现没有任何结果,这是因为它被挂载在所谓的mount namespace中,你只能在容器内看到挂载。podman提供了叫做podman-mount的功能,你可以用这个功能在容器外查看挂载的路径:

podman mount on-off-container

返回的目录是容器使用的覆盖文件系统中的系统级挂载点。 您现在可以立即更改容器文件系统中的任何内容。 可以通过下面命令创建/test文件夹:

touch $(podman mount on-off-container)/test
ls $(podman mount on-off-container)

 

现在可以看见test文件,下面在容器里运行shell的时候你同样可以看到这个test文件。

 

2. 创建spec文件

容器已经存在本地并挂载了数据卷,但是目前还没有runc的spec文件。手动创建spec文件非常繁琐,因为他们是由许多不同选项的复杂JSON组成的(由OCI 运行时规范控制),幸运的是,容器引擎会帮我们创建这个spec文件,任何OCI兼容的运行时都可以使用此完全相同的规范文件(runc,crun,katacontainer,gvisor等)。首先来检查一下它在哪里:

cat /var/lib/containers/storage/overlay-containers/$(podman ps -l -q --no-trunc)/userdata/config.json|jq .

#jq . 规范json文件

这命令现在会报错因为容器引擎还没有创建它,现在使用podman和一个特点的容器镜像来创建这个文件:

podman start on-off-container

现在config.json文件已经被创建,继续使用上面的cat 命令检查这个文件,其中有一些选项与podman选项十分相似,这spec文件真正突出了API。

podman此时并未启动容器,使用podman ps -a查看 发现STATUS是Exited。使用inspect命令检查细节,发现:

 

# -f filename 如果 filename为常规文件,则为真 。shell 命令

 

即如果/mnt下面由on文件,容器才会运行top命令。

 

3. 调用运行时

现在我们有了存储和一个config.json,通过config.json创建一个虚拟化进程。我们已经构建了一个容器镜像,它只有在/mnt/on文件存在的情况下才会创建一个进程,现在来创建这个on文件:touch /mnt/on,并启动容器podman start on-off-container,继续使用podman ps -a命令查看容器状态,发现此时的状态是up。使用exec命令在容器中创建交互式终端:

podman exec -it on-off-container bash

使用ls -alh命令查看,发现结果和预期的一样,test文件就存在那。我们通过三个基本步骤才真正创建了一个容器

 

三、ELinux和sVirt:动态生成上下文来保护您的容器

运行下面命令,得到如下结果:

$ podman run -dt registry.access.redhat.com/ubi7/ubi sleep 10
244b6bda438e899db9d055335dbd015b93a50e5de7277f8ac0919c65f9b32470
$ podman run -dt registry.access.redhat.com/ubi7/ubi sleep 10
a2652ee6f9e4b4b849fedcbad4872c36e1cef398fa6b00e90988811237bfbbf8
$ sleep 3
$ ps -efZ | grep container_t | grep sleep
system_u:system_r:container_t:s0:c666,c809 root 25067 25023  7 14:03 pts/0 00:00:00 sleep 10
system_u:system_r:container_t:s0:c471,c835 root 25189 25174  7 14:03 pts/0 00:00:00 sleep 10
$

请注意,每个容器都标有动态生成的多级安全性(Multi Level Security (MLS))标签。例如上面结果第一个MLS标签是c66,c809,第二个MLS标签是c471,c835。因此每个容器启动都会生成一个不同的MLS标签,它们被阻止访问彼此的存储和文件等。

SELinux不只是标记进程,它还必须标记进程访问的文件,下面为数据创建一个/tmp/selinux-test目录,并检查目录上的SELinux标签。注意,类型设置为“user_tmp_t”,但没有设置MLS标签:

$ ls -alhZ /tmp/selinux-test/
drwxr-xr-x. root root unconfined_u:object_r:user_tmp_t:s0 .
drwxrwxrwt. root root system_u:object_r:tmp_t:s0       ..
$

多启动几次同一个镜像,发现每次多MLS都不一样,这是sVirt在工作:

$ podman run -t -v /tmp/selinux-test:/tmp/selinux-test:Z registry.access.redhat.com/ubi7/ubi ls -alhZ /tmp/selinux-test
drwxr-xr-x. root root system_u:object_r:container_file_t:s0:c729,c736 .
drwxrwxrwt. root root system_u:object_r:container_file_t:s0:c729,c736 ..
$ podman run -t -v /tmp/selinux-test:/tmp/selinux-test:Z registry.access.redhat.com/ubi7/ubi ls -alhZ /tmp/selinux-test
drwxr-xr-x. root root system_u:object_r:container_file_t:s0:c363,c698 .
drwxrwxrwt. root root system_u:object_r:container_file_t:s0:c363,c698 ..
$ podman run -t -v /tmp/selinux-test:/tmp/selinux-test:Z registry.access.redhat.com/ubi7/ubi ls -alhZ /tmp/selinux-test
drwxr-xr-x. root root system_u:object_r:container_file_t:s0:c479,c515 .
drwxrwxrwt. root root system_u:object_r:container_file_t:s0:c479,c515 ..
$

最终,查看/tmp/selinux-test目录多MLS,发现始终和最后一次运行的容器的MLS相同。:Z选项会自动标记和绑定挂载,以便容器可以访问和更改挂载点上的文件。 这样可以防止任何其他进程访问此数据,并且对最终用户是透明的。

note:MLS变更后,之前启动的容器便无法访问挂载的容器数据卷。只有MLS相同才可以。

 

四、Cgroups:使用容器实例动态创建

本节的目的是为了了解容器如何防止使用彼此的保留资源.Linux内核有一个称为cgroups (control groups的缩写)的特性,它限制、说明和隔离进程的资源使用(CPU、内存、磁盘I/O、网络等)。通常,这些控制组是由系统管理员(使用cgexec)设置的,或者使用systemd (system -run--slice)配置的,但是使用容器引擎时,会自动处理这种配置。

podman run -dt registry.access.redhat.com/ubi7/ubi sleep 10
podman run -dt registry.access.redhat.com/ubi7/ubi sleep 10
sleep 3
for i in $(podman ps | grep sleep | awk '{print $1}' | grep [0-9]); do find /sys/fs/cgroup/ | grep $i; done

容器引擎自动将容器化进程放进自己的cgroup中,这是十分方便的,类似sVirt。

 

五、SECCOMP:限制容器化进程与内核交互的方式

可以将SECCOMP看过是防火墙,它可以阻止某些系统调用。虽然这是可选的,并且默认情况下是关闭的。但它是一个可以用来阻挡不正常容器的十分强大的工具。

$ cat ~/labs/lab3-step5/chmod.json
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "name": "fchmodat",
      "action": "SCMP_ACT_ERRNO"
    }
  ]
}
$ podman run -it --security-opt seccomp=./labs/lab3-step5/chmod.json registry.access.redhat.com/ubi7/ubi chmod 777 /etc/hosts
chmod: changing permissions of '/etc/hosts': Operation not permitted
$

# fchmodat - change permissions of a file relative to a directory file descriptor

更改/etc/hosts权限的行为被阻止

 

posted @ 2020-03-12 23:06  _懒惰的猫  阅读(325)  评论(0编辑  收藏  举报