kubenetes学习笔记(2)

kubenetes学习笔记(2)

一、前言

    在上一篇文章中,我详细介绍了 Linux 容器中用来实现“隔离”的技术手段:Namespace。
Namespace 技术实际上修改了应用进程看待整个计算机“视图”,即它的“视线”被操作系统做了
限制,只能“看到”某些指定的内容。但对于宿主机来说,这些被“隔离”了的进程跟其他进程并
没有太大区别。
    说到这一点,相信你也能够知道我在上一篇文章最后给你留下的第一个思考题的答案了:
在之前虚拟机与容器技术的对比图里,不应该把 Docker Engine 或者任何容器管理工具放在
跟 Hypervisor 相同的位置,因为它们并不像 Hypervisor 那样对应用进程的隔离环境负责,
也不会创建任何实体的“容器”,真正对隔离环境负责的是宿主机操作系统本身:	

kubenetes学习笔记(2)

    所以,在这个对比图里,我们应该把 Docker 画在跟应用同级别并且靠边的位置。这意
味着,用户运行在容器里的应用进程,跟宿主机上的其他进程一样,都由宿主机操作系统统
一管理,只不过这些被隔离的进程拥有额外设置过的 Namespace 参数。而 Docker 项目在
这里扮演的角色,更多的是旁路式的辅助和管理工作。
    这样的架构也解释了为什么 Docker 项目比虚拟机更受欢迎的原因。
    这是因为,使用虚拟化技术作为应用沙盒,就必须要由 Hypervisor 来负责创建虚拟
机,这个虚拟机是真实存在的,并且它里面必须运行一个完整的 Guest OS 才能执行用户
的应用进程。这就不可避免地带来了额外的资源消耗和占用。
    根据实验,一个运行着 CentOS 的 KVM 虚拟机启动后,在不做优化的情况下,虚拟机
自己就需要占用 100~200  MB 内存。此外,用户应用运行在虚拟机里面,它对宿主机操作
系统的调用就不可避免地要经过虚拟化软件的拦截和处理,这本身又是一层性能损耗,尤其
对计算资源、网络和磁盘 I/O 的损耗非常大。
    而相比之下,容器化后的用户应用,却依然还是一个宿主机上的普通进程,这就意味
着这些因为虚拟化而带来的性能损耗都是不存在的;而另一方面,使用 Namespace 作为
隔离手段的容器并不需要单独的 Guest OS,这就使得容器额外的资源占用几乎可以忽略
不计。
    所以说,“敏捷”和“高性能”是容器相较于虚拟机最大的优势,也是它能够在 PaaS 这
种更细粒度的资源管理平台上大行其道的重要原因。

二、隔离

    不过,有利就有弊,基于 Linux Namespace 的隔离机制相比于虚拟化技术也有很多
不足之处,其中最主要的问题就是:隔离得不彻底。首先,既然容器只是运行在宿主机上
的一种特殊的进程,那么多个容器之间使用的就还是同一个宿主机的操作系统内核。
    尽管你可以在容器里通过 Mount Namespace 单独挂载其他不同版本的操作系统文
件,比如 CentOS 或者 Ubuntu,但这并不能改变共享宿主机内核的事实。这意味着,如
果你要在 Windows 宿主机上运行 Linux 容器,或者在低版本的 Linux 宿主机上运行
高版本的 Linux 容器,都是行不通的。
    而相比之下,拥有硬件虚拟化技术和独立 Guest OS 的虚拟机就要方便得多了。最极
端的例子是,Microsoft 的云计算平台 Azure,实际上就是运行在 Windows 服务器集群
上的,但这并不妨碍你在它上面创建各种 Linux 虚拟机出来。
    其次,在 Linux 内核中,有很多资源和对象是不能被 Namespace 化的,最典型的
例子就是:时间。这就意味着,如果你的容器中的程序使用 settimeofday(2) 系统调用
修改了时间,整个宿主机的时间都会被随之修改,这显然不符合用户的预期。相比于在虚拟
机里面可以随便折腾的*度,在容器里部署应用的时候,“什么能做,什么不能做”,就是
用户必须考虑的一个问题。
    此外,由于上述问题,尤其是共享宿主机内核的事实,容器给应用暴露出来的攻击面
是相当大的,应用“越狱”的难度自然也比虚拟机低得多。更为棘手的是,尽管在实践中我
们确实可以使用 Seccomp 等技术,对容器内部发起的所有系统调用进行过滤和甄别来进
行安全加固,但这种方法因为多了一层对系统调用的过滤,必然会拖累容器的性能。
    何况,默认情况下,谁也不知道到底该开启哪些系统调用,禁止哪些系统调用。
    所以,在生产环境中,没有人敢把运行在物理机上的 Linux 容器直接暴露到公网上。
当然,我后续会讲到的基于虚拟化或者独立内核技术的容器实现,则可以比较好地在隔离
与性能之间做出平衡。

三、限制

    我还是以 PID Namespace 为例,虽然容器内的第 1 号进程在“障眼法”的干扰下只
能看到容器里的情况,但是宿主机上,它作为第 100 号进程与其他所有进程之间依然是
平等的竞争关系。这就意味着,虽然第 100 号进程表面上被隔离了起来,但是它所能够
使用到的资源(比如 CPU、内存),却是可以随时被宿主机上的其他进程(或者其他容器)
占用的。
    当然,这个 100 号进程自己也可能把所有资源吃光。这些情况,显然都不是一个
“沙盒”应该表现出来的合理行为。而 Linux Cgroups 就是 Linux 内核中用来为进程
设置资源限制的一个重要功能。有意思的是,Google 的工程师在 2006 年发起这项特性
的时候,曾将它命名为进程容器processcontainer
    实际上,在 Google 内部,“容器”这个术语长期以来都被用于形容被 Cgroups 限
制过的进程组。Cgroup它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 
CPU、内存、磁盘、网络带宽等等。此外,Cgroups 还能够对进程进行优先级设置、审计,
以及将进程挂起和恢复等操作
    Linux Cgroups 的设计还是比较易用的,简单粗暴地理解呢,它就是一个子系统目
录加上一组资源限制文件的组合。而对于 Docker 等 Linux 容器项目来说,它们只需要
在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录),然后在启动容器
进程之后,把这个进程的 PID 填写到对应控制组的 tasks 文件中就可以了。

四、单进程模型

   一个正在运行的 Docker 容器,其实就是一个启用了多个 Linux Namespace 的应
用进程,而这个进程能够使用的资源量,则受 Cgroups 配置的限制。这也是容器技术中
一个非常重要的概念,即:容器是一个“单进程”模型。
   由于一个容器的本质就是一个进程,用户的应用进程实际上就是容器里 PID=1 的
进程,也是其他后续创建的所有进程的父进程。这就意味着,在一个容器中,你没办法同
时运行两个不同的应用,除非你能事先找到一个公共的 PID=1 的程序来充当两个不同应
用的父进程,这也是为什么很多人都会用 systemd 或者 supervisord 这样的软件来
代替应用本身作为容器的启动进程。
   但是,在后面分享容器设计模式时,我还会推荐其他更好的解决办法。这是因为容器
本身的设计,就是希望容器和应用能够同生命周期,这个概念对后续的容器编排非常重要。
否则,一旦出现类似于“容器是正常运行的,但是里面的应用早已经挂了”的情况,编排系
统处理起来就非常麻烦了。另外,跟 Namespace 的情况类似,Cgroups 对资源的限制
能力也有很多不完善的地方,被提及最多的自然是 /proc 文件系统的问题。
   众所周知,Linux 下的 /proc 目录存储的是记录当前内核运行状态的一系列特殊
文件,用户可以通过访问这些文件,查看系统以及当前正在运行的进程的信息,比如 CPU 
使用情况、内存占用率等,这些文件也是 top 指令查看系统信息的主要数据来源。
   但是,你如果在容器里执行 top 指令,就会发现,它显示的信息居然是宿主机的 CPU 
和内存数据,而不是当前容器的数据。造成这个问题的原因就是,/proc 文件系统并不知
道用户通过 Cgroups 给这个容器做了什么样的资源限制,即:/proc 文件系统不了解
Cgroups 限制的存在。在生产环境中,这个问题必须进行修正,否则应用程序在容器里读
取到的 CPU 核数、可用内存等信息都是宿主机上的数据,这会给应用的运行带来非常大
的困惑和风险。这也是在企业中,容器化应用碰到的一个常见问题,也是容器相较于虚拟机
另一个不尽如人意的地方。
上一篇:C++——基础知识入门


下一篇:const那些事-初始化