K8S学习笔记(一):进程和容器

原创
10/11 19:20
阅读数 72

    开始学习容器时,总会看到一张这样的图:

    

    在这张图里,容器似乎等同于 Hypervisor + GusetOS,那么容器在linux上面,究竟是什么身份?

    对操作系统稍微有点概念的同学应该知道,在linux系统里并没有 “容器” 这一原生概念,但是容器又的确是运行在操作系统上的,那么容器是以什么身份在操作系统里运行的?又是通过哪些机制来实现容器的各种特性和能力?

1 容器的本质

    容器本质上是一个进程

    在linux里,启动一个容器,实际上就是启动了一个进程。容器拥有进程所有的特性,被操作系统所管理。

    而众所周知,进程是linux操作系统进行资源分配的基本单位,是资源管理的基本单元。既然容器本质上是一个进程,那么显而易见,容器也是依托操作系统管理进程的机制,来实现资源的分配、隔离和管理。那么自然就会延伸出另外一个问题:操作系统通过什么机制来限制和隔离进程所能使用的资源?

    要解答上面的问题,首先需要了解操作系统都有哪些资源可供一个进程使用,此处将其大致划分如下:

  1. 内核空间资源:指的是PCB相关信息,即进程PID、PPID、UID等,包括进程控制块本身、打开的文件表项等等。简而言之,就是内核通过PCB可以访问到的资源。
  2. 用户空间资源:一般指进程的代码段、数据段、堆、栈,以及可共享访问的库的内存空间、CPU等等。这些资源在进程退出的时候主动释放。

    与之相对的,既然有了资源,就会有对应的管理机制。在linux系统中,一般是通过rootfs、namespace、cgroup等机制来管理资源。

    到此为止,可以得出一条初步的结论:容器本质上就是一个特殊的进程,利用Linux管理进程的各种机制来实现资源的分配和隔离,其中最主要的机制是rootfs、namespace、cgroup等。接下来简单了解一下这三个机制。

2 资源的限制与隔离   

    2.1 cgroups

        在linux中,如果不加以限制的话,每个进程都可以自由的竞争系统资源,有时会导致进程间互相影响,拖累系统运行效率。cgroup就是linux为了解决这一问题而引入的资源管理机制,是control groups的简称,用来限制、控制与分离一个进程组的资源(如CPU、内存、磁盘输入输出等)。

        cgroup的组成:

        在这里插入图片描述

            1. task(任务):在cgroups里,task实际代表的就是linux里的一个进程,也就是一个容器。

            2. cgroup(控制族群):cgroup表示按照某种资源控制标准划分而成的任务组,包含一个或多个Subsystems。一个task可以加入某个cgroup,也可以从某个cgroup迁移到另外一个cgroup。Cgroups中的资源控制都以cgroup为单位实现的。

            3. hierarchy(层级):hierarchy由一系列cgroup以一个树状结构排列而成,每个hierarchy通过绑定对应的subsystem进行资源调度。hierarchy中的cgroup节点可以包含零或多个子节点,子节点继承父节点的属性。整个系统可以有多个hierarchy。

            4. subsystem(子系统):一个子系统就是一个资源控制器,比如cpu子系统就是控制cpu时间分配的一个控制器。子系统必须附加(attach)到一个hierarchy上才能起作用,一个子系统附加到某个hierarchy以后,这个hierarchy上的所有控制族群都受到这个子系统的控制。

        cgroup的作用:

            1. 限制进程组可以使用的资源数量(Resource limiting )。比如:memory子系统可以为进程组设定一个memory使用上限,一旦进程组使用的内存达到限额再申请内存,就会触发OOM(out of memory)。

            2. 进程组的优先级控制(Prioritization )。比如:可以使用cpu子系统为某个进程组分配特定cpu share。

            3. 记录进程组使用的资源数量(Accounting )。比如:可以使用cpuacct子系统记录某个进程组使用的cpu时间

            4. 进程组隔离(Isolation)。比如:使用ns子系统可以使不同的进程组使用不同的namespace,以达到隔离的目的,不同的进程组有各自的进程、网络、文件系统挂载空间。

            5. 进程组控制(Control)。比如:使用freezer子系统可以将进程组挂起和恢复。

        cgroup的子系统,可配置内容较多,挑重点的列举:

            1. blkio:包括按比例分配块设备IO资源、控制IO读写速度上限、针对特定操作 (read, write, sync, 或 async) 设定读写速度上限、统计和监控等

            2. cpu:主要是控制cpu的资源分配,包含完全公平调度策略(CFS,提供了限额和按比例分配两种方式进行资源控制)和实时调度策略(RT,针对实时进程按周期分配固定的运行时间。配置时间都以微秒(µs)为单位,文件名中用us表示

            3. cpuacct:主要是cpu的资源报告,这个子系统的配置是cpu子系统的补充,提供 CPU 资源用量的统计,时间单位都是纳秒。

            4. cpuset:作用是绑定cpu,为 task 分配独立 CPU 资源的子系统。目前docker容器主要使用该子系统下的两个参数:cpuset.cpus(表示可以使用的cpu编号)、cpuset.mems(表示可以使用的内存节点编号)

            5. device:该子系统主要是限制task对device的使用。

            6. freezer:只有一个属性,表示进程的状态。把 task 放到 freezer 所在的 cgroup时,通过属性的配置可以暂停该进程。

            7. memory:内存资源管理,包括限制内存的使用,限制swap区的使用,内存报警和自动关停进程,统计和监控等等诸多功能。

        cgroups建立起了不同的资源组,并为每个资源组分配资源,解决了容器的资源限制问题,是容器技术的重要组成部分。

    2.2 namespace

        namespace是linux提供的一种内核级别的环境隔离的机制,作用是隔离进程所能感知的系统资源,让同一台服务器上的不同进程可以保持隔离不互相影响。早期连cgroup都可以通过namespace机制进行管理,但由于带来了一系列问题,目前已移除该特性。结构如下演示:

            

        linux namespace目前支持的六种命名空间:

            

            IPC:系统参数为CLONE_NEWIPC,进程间通信的命名空间,可以将 SystemV 的 IPC 和 POSIX 的消息队列独立出来。

            PID:系统参数为CLONE_NEWPID,进程命名空间。空间内的PID 是独立分配的,意思就是命名空间内的虚拟 PID 可能会与命名空间外的 PID 相冲突,于是命名空间内的 PID 映射到命名空间外时会使用另外一个 PID。比如说,命名空间内第一个 PID 为1,而在命名空间外就是该 PID 已被 init 进程所使用。

            Network:系统参数为CLONE_NEWNET,网络命名空间,用于隔离网络资源(/proc/net、IP 地址、网卡、路由等)。后台进程可以运行在不同命名空间内的相同端口上,用户还可以虚拟出一块网卡。

            Mount:系统参数为CLONE_NEWNS,挂载命名空间,进程运行时可以将挂载点与系统分离,使用这个功能时,我们可以达到 chroot 的功能,而在安全性方面比 chroot 更高。

            UTS:系统参数为CLONE_NEWUTS,UTS 命名空间,主要目的是独立出主机名和网络信息服务(NIS)。

            User:系统参数为CLONE_NEWUSER,用户命名空间,同进程 ID 一样,用户 ID 和组 ID 在命名空间内外是不一样的,并且在不同命名空间内可以存在相同的 ID

        linux namespace主要提供了三个API:

            1. clone():可以构建一个新的子进程,然后让子进程加入新的namespace,而当前进程保持不变

            2. unshare():使某进程脱离某个namespace

            3. setns():把某进程加入到某个namespace

        namespace建立起了不同的系统视图,站在应用进程的视角来看,就像是自身独享了某台服务器一样。解决了容器的隔离问题,是容器技术的重要组成部分。

    2.3 rootfs

        通过cgroups和namespace解决了进程(也就是容器)资源的隔离和限制问题后,还存在第三个问题,那就是进程仍然与服务器共享文件系统。任何进程对服务器文件系统和文件的修改,仍然能够被其他的进程所感知。为了解决这个问题,把进程彻底的放进一个笼子,就需要用到rootfs机制。

        rootfs又称为根文件系统,它是内核启动时所挂载的第一个文件系统,它是其它文件系统的根,包含系统启动时所必须的目录和关键性的文件。通俗的说,就是linux系统下所能看到的根目录:

        

        rootfs一般包含bin、sbin 、dev、etc、lib、home、root、usr、var、proc、mnt、tmp等目录,每个目录的具体作用此处不展开。

        可以通过chroot或者pivot root命令将进程(容器)的rootfs指定到服务器的具体的某个目录下,这样在进程的视角里,就看不见服务器的其它文件系统和目录,只能以指定的目标作为根目录。

        以这样的方式,让进程具备独占的文件系统。rootfs解决了容器的文件系统视角下的隔离问题,至此笼子的最后一角被堵上。

3 总结   

    最后再回顾一次最初的问题:什么是容器?

    根据目前学习到的知识,似乎可以下一个定义:容器一个特殊的系统进程,这个进程创建时需要启动namespace配置进行命名空间隔离,设置cgroups进行资源分配和限制,切换进程的根文件系统到指定的目录下。

展开阅读全文
打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部