• 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权限的行为被阻止

  • 相关阅读:
    通过mysql命令行理解mysql
    yii2.0框架安装心得
    浅谈我的编程之路——感谢引领我的leader
    谈谈我的编程之路---WAMP(四)
    谈谈我的编程之路---WAMP(三)
    Sql语句优化之explan分析案例
    mysql技术内幕笔记
    Sql语句优化
    linux变量
    sql查询慢优化
  • 原文地址:https://www.cnblogs.com/tiandz/p/12444354.html
Copyright © 2020-2023  润新知