Linux容器实现隔离的技术基石:Namespace,Namespace技术修改了应用进程能够看到的计算机的试图,只能看到某些指定的内容,对于宿主机来说,被隔离的进程和其他进程并没有太大的区别
我们再来看一下上节课的画出的图
我们不应该吧Docker和Hypervisor放在同一个级别,因为Docker并不是隔离了一个物理环境,真正隔离的还是操作系统本身
这就意味着,用户在运行在容器内的应用进程,跟宿主机上其他的进程一样,都交给宿主机一同管理,不过这些被隔离的进程被设置了单独的Namespace参数,而Docker项目就是一些辅助和管理的工作
如果不使用Docker项目,来直接使用虚拟化技术作为应用沙盒,就必须要有Hypervisor来创建虚拟机,这个虚拟机是真实存在的,必须要运行一个完整的Guest OS才能运行对应的用户的应用进程,不可避免的带来了额外的资源消耗和占用
一个运行着CentOS的KVM,本身就会占用一定的内存,而且,对宿主机的操作系统的调用不可避免的经过虚拟机软件的拦截和处理,又是一层性能消耗
而使用NameSpaces作为隔离手段的容器并不需要单独的GuestOS,使得容器额外的资源占用几乎可以忽略不计
敏捷和高性能是最大的优势,也是可以再Pass这种细粒度的资源管理平台上大行其道的原因
不过这就需要说下NameSpace的一些弊端,主要的一个问题就是隔离的不彻底
因为容器只是宿主机上的一种特殊的进程,多个容器之间使用的就是同一个宿主机的操作内核
这就导致了在Window上运行Linux容器是不可行的,但是可以再不同的宿主机上运行Linux的虚拟机,比如微软的Azure,就是在Windows宿主机上运行的Linux容器
而且Linux内核中,很多资源和对象是不能被Namespace隔离化的,比如宿主机上的时间
容器内的程序使用settimeofday(2)系统调用修改了时间,整个宿主机的事件都会被随之修改,那么这就不符合容器的预期了
由于上述的问题,尤其是共享宿主机内核的事实,容器给应用暴露的问题还是很多的
更为棘手的是,实践中我们可以Seccomp等技术,对系统调用来甄别和加固,但是无脑的加固必然会拖累容器的性能,谁也不知道哪些要开
这也导致了物理机上的Linux无法很好的直接暴露到公网上
所以我们说完容器的隔离技术之后,说下容器的限制问题
我们来做一下Linxu的限制,比如,在容器之中,虽然PID 1看不到其他的进程,但是在宿主机上,其还是作为100号进程和其他的进程处于同等的竞争关系,这就意味着,100号进程还是去和其他进程去竞争CPU 内存等
所以这不符合一个沙盒的预期
于是Linux给出了Linux Cgroups 来作为Linux内核中给进程设置资源限制的重要功能
Linxu Cgourps的全程是Linux Control Group,最主要的作用,限制一个进程组的使用资源上限,包括CPU 内存 磁盘 带宽等
Cgroups 可以对进程进行优先级的设置,审计,以及将及挂起和恢复等操作,
在Linux中,Cgroups给用户暴露的操作接口是文件系统,即文件和目录的方式组织在操作系统的/sys/fs/cgroup路径下
可以利用mount -t cgroup来进行展示出来
$ mount -t cgroup cpuset on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset) cpu on /sys/fs/cgroup/cpu type cgroup (rw,nosuid,nodev,noexec,relatime,cpu) cpuacct on /sys/fs/cgroup/cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct) blkio on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio) memory on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory) … |
其输出结果,是一系列文件系统目录
在/sys/fs/cgroup 下有着诸如cpuset cpu memory等子目录,也是子系统,都是可以限制的资源种类
比如在cpu目录下,我们可以看到有如下的配置文件,这个指令是
$ ls /sys/fs/cgroup/cpu cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks |
其中有cfs_period 和 cfs_quofa这样的关键词,这两个参数组合起来,可以限制在长度为cfs_period的一段时间内,只能分配到cfs_quofa单位的CPU时间
那么我们想要约束一个进程该怎么用呢?
我们进入 /sys/fs/cgroup/cpu的目录下
然后mkdir一下,就可以自动的创建一个新的控制组
sudo mkdir container
cd container/
这样的一个目录就是控制组,操作系统在新的container目录下,会自动生成子系统对应的资源限制文件
然后我们执行一个进程
while : ; do : ; done &
这样会进行执行死循环,将CPU吃到100%,然后查到对应的PID
然后看下这个cpu有没有被打满
CPU使用率已经100%
然后我们看container目录下的文件
.
我们可以修改这些文件来设置限制
在container组中写入 cfs_quota文件中写入 20ms的限制
echo 20000 > cpu.cfs_quota_us
这样,这个控制组控制的进程只能使用20ms的CPU时间,也就是单位时间内只能打满20%的CPU
然后我们写入PID到对应的tasks文件,就会生效了
echo $PID > tasks
这样这个PID就会被限制住了
除了Cpu的限制,Cgroups上还能限制
blkio 为块设备分配IO限制
cpuset 为进程分配单独的CPU核和对应的内存节点
memory,设置进程内存上限
Linux Cgroups就是在子系统目录上加上一组资源限制文件的组合,对于Docker Linux容器项目来说,在每个子系统下,为每个容器创建一个控制组,在启动容器进程后,将这个进程的PID,填到这个控制组的task文件中就可以了
如何控制,可以在docker run时候指定
docker run -it –cpu-period=100000 –cpu-quota=20000 /bin.bash
我们就可以查看Cgroups文件系统下,CPU子系统中,docker的资源控制文件来确认
这样,我们就了解了Linux Namespace作为隔离手段的优势和劣势,对比了Linux Namespace作为隔离手段的优势和劣势
然后就是Cgroups的实现,如何实现资源的限制,
一个正在运行的Docker容器,就是启动了多个Linux Namespace的应用进程,而且受Cgroups的限制
那么容器现在可以说,就是一个单进程的模型
一个容器本质就是一个进程,其他的配合进程,只不过是这个父进程的创建的子进程
这就是一个容器里,无法创建多个不同的应用,除非可以有一个父进程来连接他们
而且,我们还需要保证容器可以和内部的应用同生命周期,这对后续的容器编排很重要,不然,一旦出现了 容器活着,就比较难处理了
跟Namespace类似,Cgroups也并不是很完善,比如/proc,Linux下的/proc目录下的存储的是内核的状态信息,比如CPU使用状态,内存占用率,这些文件也是top的指令数据来源
如果执行top命令,其显示的是宿主机的,而不是当前容器内的
毕竟 /proc文件系统不知道用户通过Cgroups给容器做了什么样的资源限制
课后思考:
1.然后修复容器中的top指令和/proc文件系统的信息呢?
2.从虚拟机向着容器环境迁移的过程,有什么容器和虚拟机不一致的问题呢?
1.利用lxcfs,看起来可以解决这个问题,top是从/prof/stats目录下获取数据,lxcfs就是实现这个功能的,将宿主机的/var/lib/lxcfs/proc/memoinfo位置,这样读取进程相对应的文件内容时候,LXCFS的FUSE实现会实现从Cgroup读取正确内存限制,从而使得应用获得正确的资源约束设定
2.可以使用vanila kubernetes,遇到的主要挑战就是性能损失和多租户隔离问题