Docker实现原理

来源: “极客时间”:深入剖析K8S

 

目录

Docker核心技术

Namespace:

Cgroups

作用

这里一些常用的子系统:

Cgroups使用

Docker设定

其他问题

Mount Namespace

chroot

层(Layer)

总结

Docker和虚拟机比较

容器是一个“单进程”模型


 

 

Docker核心技术

 

容器核心技术: 通过约束和修改进程的动态表现,从而为其创造出一个“边界”;

对于Docker等大多数Linux容器来说,

  • Cgroups:用来制造约束
  • Namespace:用来修改进程视图
    其实是Linux创建新进程的一个可选参数
  • Mount NameSpace:
    挂载不同的目录;

 

Namespace:

Linux提供了多种Namespace

  • PID:
    进程ID,在当前Namespace中新创建的进程PID重新从1开始;
  • Mount:
    用于让被隔离进程只看到当前 Namespace 里的挂载点信息
  • UTS
  • IPC
  • Network:
    用于让被隔离进程看到当前 Namespace 里的网络设备和配置
  • User

 

Cgroups

Cgroups的全称: Linux Control Group;

作用

主要作用:限制一个进程组能够使用的资源上限,包括CPU/内存/磁盘/带宽等等;此外,Cgroups 还能够对进程进行优先级设置、审计,以及将进程挂起和恢复等操作。

在 Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。

$ mount -t cgroup

这里一些常用的子系统:

  • cpu:
    限制cpu资源;
  • cpuset:
    为进程分配单独的cpu核和对应的内存节点;
  • blkio:
    为块设备设定I/O限制,一般用于磁盘的设备;
  • memory:
    为进程设定内存使用的限制

Cgroups使用

Linux Cgroups 的设计还是比较易用的,简单粗暴地理解呢,它就是一个子系统目录加上一组资源限制文件的组合。而对于 Docker 等 Linux 容器项目来说,它们只需要在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录),然后在启动容器进程之后,把这个进程的 PID 填写到对应控制组的 tasks 文件中就可以了。

Docker设定

在Docker启动时,可以设置对应的参数即可:

$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash

其他问题

在Docker中抓取性能问题:

   /proc 文件系统并不知道用户通过 Cgroups 给这个容器做了什么样的资源限制,即:/proc 文件系统不了解 Cgroups 限制的存在,所以未加处理,在docker中进行性能统计,还是获取的宿主机的性能;这个问题必须进行修正;

使用lxcfs:

Mount Namespace

Mount Namspace: 它对容器进程试图的改变,一定是伴随着挂载操作(mount)才能生效;

chroot

在Linux操作系统中,可以通过chroot命令(change root file system),可以轻松的重新挂载根目录“/",将进程的根目录改变到你指定的位置;

为了能够让容器的根目录看起来更“真实”,我们一般会在这个容器的根目录下挂载一个完整操作系统的文件系统,比如 Ubuntu16.04 的 ISO。这样,在容器启动之后,我们在容器里通过执行 "ls /" 查看根目录下的内容,就是 Ubuntu 16.04 的所有目录和文件。
而这个挂载在容器根目录上、用来为容器进程提供隔离后执行环境的文件系统,就是所谓的“容器镜像”。它还有一个更为专业的名字,叫作:rootfs(根文件系统)。


所以,一个最常见的 rootfs,或者说容器镜像,会包括如下所示的一些目录和文件,比如/bin,/etc,/proc 等等:

注:

rootfs 只是一个操作系统所包含的文件、配置和目录,并不包括操作系统内核。在 Linux 操作系统中,这两部分是分开存放的,操作系统只有在开机启动时才会加载指定版本的内核镜像。所以说,rootfs 只包括了操作系统的“躯壳”,并没有包括操作系统的“灵魂”。

那么,对于容器来说,这个操作系统的“灵魂”又在哪里呢?
实际上,同一台机器上的所有容器,都共享宿主机操作系统的内核。

 

层(Layer)

Docker 在镜像的设计中,引入了层(layer)的概念。也就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs。

当然,这个想法不是凭空臆造出来的,而是用到了一种叫作联合文件系统(Union FileSystem: UnionFS)的能力。

Union File System 也叫 UnionFS,最主要的功能是将多个不同位置的目录联合挂载(union mount)到同一个目录下

 

 

总结

对 Docker 项目来说,它最核心的原理实际上就是为待创建的用户进程:

  1.  启用 Linux Namespace 配置;
  2. 设置指定的 Cgroups 参数;
  3. 切换进程的根目录(Change Root)。

这样,一个完整的容器就诞生了。不过,Docker 项目在最后一步的切换上会优先使用pivot_root 系统调用,如果系统不支持,才会使用 chroot。这两个系统调用虽然功能类似,但是也有细微的区别;

 

 

Docker和虚拟机比较

以前常用的比较虚拟机和Docer的图:

Docker实现原理_第1张图片

其实,更能表现他们之间差别的图:

Docker实现原理_第2张图片

说明:

     Docker运用了 (Namespace+Cgroups)技术来创建进程,创建的进程还是直接运行在Linux操作系统中,都是有宿主机的操作系统统一管理,而并不是运行在Docker之上(并没有一个真正的Docker容器运行在宿主机里面);

Docker相对虚拟机优势: 轻,“敏捷”和“高性能”

缺点:

  • 隔离不彻底:
    说到底,docker启动的进程还是运行在宿主机操作系统上,多个容器之间使用的哈市同一个操作系统内核;
  • 环境:
    在Windows上运行Linux容器,或在低版本Linux宿主机上运行高版本Linux容器,都行不通的;

而 拥有硬件虚拟化技术和独立Guest OS的虚拟机就没这样面问题;--- 有利就有弊;

因此,Docker要考虑:什么能做,什么不能做 --- 譬如修改时间,修改的就是宿主机的系统,对宿主机上的所有应用都有影响;

前面讲过,Docker只有创建的rootfs,只是一个操作系统锁包含的文件/配置/目录,并不包含操作系统内核;同一个宿主机上的docker,都是共享宿主机的操作系统内核;

这就意味着,如果你的应用程序需要配置内核参数、加载额外的内核模块,以及跟内核进行直接的交互,你就需要注意了:这些操作和依赖的对象,都是宿主机操作系统的内核,它对于该机器上的所有容器来说是一个“全局变量”,牵一发而动全身。


这也是容器相比于虚拟机的主要缺陷之一:毕竟后者不仅有模拟出来的硬件机器充当沙盒,而且每个沙盒里还运行着一个完整的 Guest OS 给应用随便折腾。

另一方面来讲,由于 rootfs 里打包的不只是应用,而是整个操作系统的文件和目录,也就意味着,应用以及它运行所需要的所有依赖,都被封装在了一起。容器才有了一个被反复宣传至今的重要特性:一致性。

这种深入到操作系统级别的运行环境一致性,打通了应用在本地开发和远端执行环境之间难以逾越的鸿沟。

 

容器是一个“单进程”模型

由于一个容器的本质就是一个进程,用户的应用进程实际上就是容器里 PID=1 的进程,也是其他后续创建的所有进程的父进程。这就意味着,在一个容器中,你没办法同时运行两个不同的应用,除非你能事先找到一个公共的 PID=1 的程序来充当两个不同应用的父进程,这也是为什么很多人都会用 systemd 或者 supervisord 这样的软件来代替应用本身作为容器的启动进程。

(后续会有推荐其他更好的解决方法)

 

你可能感兴趣的:(docker/k8s,技术区)