容器化-Docker-02容器隔离实现原理

Docker容器隔离的实现原理

Namespace

先认识Linux 提供的Namespace机制: Linux的 Namespace 提供了7种不同的命名空间:

  • PID namespace: 隔离进程ID,每个容器有自己的pid命名空间
  • UTS namespace: 隔离主机名 & 域名, ….有自己的主机名和NIS域名
  • IPC namespace: 隔离进程间通信, 相同IPC Ns的进程之间才可以使用(共享内存/信号量/消息队列)通讯
  • MNT namespace: 隔离文件系统挂载点

Docker 如何实现进程隔离 (宿主机进程 & 容器内进程的隔离) ?

  • 调用clone创建进程时传入 CLONE_NEWPID, 这样创建出的进程PID和宿主机是隔离的
  • 下图中, docker containterd 进程就是在clone时指定CLONE_NEWPID创建的, 所以其子进程和宿主机是隔离的:

docker-container-process

Linux 两个重要进程: pid=1的init(使用命令ps -p 1可以看到) & pid=2的 kthreadd进程, 前者用于执行一部分内核的初始化和系统配置, 后者负责管理和调度其他内核进程;

  • Docker 的挂载点隔离的实现:
    • 一个容器需要一个rootfs才可以正常启动 (rootfs, 包括/proc, /dev等等..)
    • 调用clone创建进程时传入 CLONE_NEWNS, 这样子进程就能得到父进程挂载点的拷贝, 如果不传入CLONE_NEWNS这个参数, 子进程对文件系统的读写都会同步回父进程以及整个主机的文件系统
  • Docker 的网络隔离的实现:
    • Docker 提供了四种网络模式(Host、Container、None 和 Bridge)
    • 如果是网桥模式, 除了分配网络的Namespace, 还会给容器分配虚拟网卡, 在宿主机安装虚拟网桥, 虚拟网卡和网桥通过iptables连接;
    • 例如docker run -d -p 6379:6379 redis启动一个容器, 查看 iptables 的 NAT 配置就会看到在 DOCKER 的链中出现了一条新的规则

以上, Docker通过Linux 提供的 Namespace为新创建的进程隔离了 进程ID/网络/文件系统…

@ref:

  • Clone()函数: [[../21.Operating-System/APUE.03a.进程#clone]]
  • Namespace: [[../21.Operating-System/APUE.03a.进程#Namespace]]

CGroup

  • Docker 对容器的资源使用限额(CPU/RAM..)是通过 Controll Group (简称 CGroup)实现的:

    • 创建一个CGroup 并指定其(CPU/RAM等)限额, 实现不同CGroup 之间资源隔离
    • 进程可以随时加入一个CGroup or 退出
    • 查看当前系统中的CGroup: lssubsys -m

      $ lssubsys -m
      cpuset /sys/fs/cgroup/cpuset
      cpu /sys/fs/cgroup/cpu
      cpuacct /sys/fs/cgroup/cpuacct
      memory /sys/fs/cgroup/memory
    • 继续查看 /sys/fs/cgroup/cpu/目录, 下面有个名为docker的文件夹, 再向下层是docker container id命名的目录

    • 创建一个Docker容器并指定配额: docker run -it -d --cpu-quota=50000 image_tag

docker-cgroup

UnionFS

综上所述, Namespace解决了…, CGroup解决了… , 还有一个文件系统的隔离问题, 是通过UnionFS实现的 (是一种为 Linux 操作系统设计的用于把多个文件系统『联合』到同一个挂载点的文件系统服务)

  • Dockerfile里每一个命令都会创建一个只读层, 当使用docker run命令启动一个容器时, 会在镜像最上层添加一个可写的层(也就是容器层), 容器运行时对容器的修改都是对这个层的修改;
  • docker镜像的每一个层, 都对应宿主机/var/lib/docker/ 下面的一个目录
  • 通过AUFS(Advanced UnionFS) , 把多个文件目录”联合”到同一个挂载点;
  • 除了AUFS之外, Docker 还支持aufs、devicemapper、overlay2、zfs 和 vfs 等等不同驱动
  • 通过这种层(layer) 的设计, 不同tag的docker镜像, 可以公用同一个层, 减少了镜像的磁盘占用

综述

Docker 的隔离技术实现的基础: Namespace/CGroup/UnionFS

../_images/docker-fs-layer

参考

@ref:

Docker运行性能分析

  • 与虚拟机技术不同, Docker使用Ns和CGroup实现隔离和资源配额控制, 不需要用额外的性能消耗运行vm
  • Docker启动的进程和宿主机共享一个内核.
  • 对Docker容器内进程的优化(内核参数优化), 也可以在容器内执行sysctl命令更改, 或者在Docker启动时: docker run --sysctl key=value IMAGE:TAG CMD
  • Docker有一个白名单, 定义哪些sysctl参数可以在docker run时更改, 不在白名单内的参数, 在docker容器内sysctl -a也看不到
  • 有些参数可以在容器内使用 sysctl 更改(如 kernel.pid_max), 但是容器内更改后会影响到主机, 原因是这类参数没有 Namespace 隔离(大部分 kernel 开头的), docker 和宿主机共享内核. 这类参数也不建议通过容器更改, sysctl 命令参考:[[../21.Operating-System/Linux.04a.Sysctl]]
  • 所以针对docker的内核类优化:
    • 最好是在宿主机内修改, 然后作用于docker进程;
    • 如果需要针对docker修改, 可以通过docker run传入参数, 并注意这些参数会不会影响到宿主机

@ref: