docker详解

前言

docker基础操作建议查看docker文档,这里不做叙述

理解docker的底层实现

docker基于 Linux 内核的 Cgroup,Namespace,以及 Union FS 等技术,对进程进行封装隔离,属于操作系统 层面的虚拟化技术,由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。 最初实现是基于 LXC,从 0.7 以后开始去除 LXC,转而使用自行开发的 Libcontainer,从 1.11 开始,则 进一步演进为使用 runC 和 Containerd。 Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容 器的创建和维护,使得 Docker 技术比虚拟机技术更为轻便、快捷。

1 NameSpace

linux namespace是一种linux kernel提供的资源隔离方案
系统可以为进程分配不同的 Namespace 并保证不同的 Namespace 资源独立分配、进程彼此隔离,即不同的 Namespace 下的进程互不干扰 对NameSpace主要的操作方法如下

#flages参数类型 CLONE_NEWCGROUP / CLONE_NEWIPC / CLONE_NEWNET / CLONE_NEWNS / CLONE_NEWUSER / CLONE_NEWUTS
1 clone 在创建进程的系统调用 可通过如下flags参数指定NameSpace类型  
2 setns #该系统调用使调用进程进入存在的NameSpace
3 unshare #该系统调用将进程移动到新的NameSpace下

通过man unshare可以看到 linux有7种namespace,我们着重介绍两种云原生常用的(pid/net namespace)

root@master:~# man unshare
#    PID namespace
#不同用户的进程就是通过 Pid namespace 隔离开的,且不同 namespace 中可以有相同 Pid。
#有了 Pid namespace, 每个 namespace 中的 Pid 能够相互隔离。
#    network namespace
#网络隔离是通过 net namespace 实现的, 每个 net namespace 有独立的 network devices, IP addresses, IP routing tables, /proc/net 目录。
#Docker 默认采用 veth 的方式将 container 中的虚拟网卡同 host 上的一个 docker bridge: docker0 连接 在一起
#    mount namespace
#    UTS namespace
#    IPC namespace
#    cgroup namespace
#    time namespace

下面我们来看下docker里是怎么使用namespace的

#docker运用任意一个容器均可
root@node1:~# docker ps
CONTAINER ID   IMAGE                  COMMAND                   CREATED      STATUS              PORTS     NAMES
bf486d477ffd   sanjusss/aliyun-ddns   "dotnet aliyun-ddns.…"   9 days ago   Up About a minute             compassionate_edison
root@node1:~# docker inspect bf486d477ffd | grep Pid #注意首字母大写
            "Pid": 930,
            "PidMode": "",
            "PidsLimit": null,
root@node1:~#  ls -la /proc/930/ns #查看该容器的namespace
总用量 0
dr-x--x--x 2 root root 0  5月 20 13:58 .
dr-xr-xr-x 9 root root 0  5月 20 13:53 ..
lrwxrwxrwx 1 root root 0  5月 20 13:58 cgroup -> 'cgroup:[4026532359]'
lrwxrwxrwx 1 root root 0  5月 20 13:58 ipc -> 'ipc:[4026532357]'
lrwxrwxrwx 1 root root 0  5月 20 13:58 mnt -> 'mnt:[4026532355]'
lrwxrwxrwx 1 root root 0  5月 20 13:58 net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0  5月 20 13:58 pid -> 'pid:[4026532358]'
lrwxrwxrwx 1 root root 0  5月 20 13:58 pid_for_children -> 'pid:[4026532358]'
lrwxrwxrwx 1 root root 0  5月 20 13:58 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0  5月 20 13:58 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0  5月 20 13:58 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0  5月 20 13:58 uts -> 'uts:[4026532356]'

#nsenter -t <pid> -n ip addr 进入该pid net namespace执行ip addr,更多操作可以man nsenter查看具体用法
#补充,我们很多时候需要对容器进行网络抓包,由该命令查看容器veth口,找对宿主机对应的veth口进行抓包。建立练习如下
unshare -fn sleep 60
ps -ef|grep sleep #获取pid填入下文$pid处
lsns -t net
nsenter -t '$pid' -n ip a #可看到网络namespace

2 Cgroup(我们重点看cpu相关的)

Cgroups (Control Groups)是 Linux 下用于对一个或一组进程进行资源控制和监控的机制,可以对诸如 CPU 使用时间、内存、磁盘 I/O 等进程所需的资源进行限制;

  • blkio: 这个子系统设置限制每个块设备的输入输出控制。例如:磁盘,光盘以及USB等等。
  • CPU: 这个子系统使用调度程序为cgroup任务提供CPU的访问。
  • cpuacct: 产生 cgroup 任务的 CPU 资源报告。
  • cpuset: 如果是多核心的 CPU,这个子系统会为 cgroup 任务分配单独的 CPU 和内存。
  • devices: 允许或拒绝 cgroup 任务对设备的访问。
  • freezer:暂停和恢复cgroup任务。
  • memory: 设置每个 cgroup 的内存限制以及产生内存资源报告。
  • net_cls 标记每个网络包以供cgroup方便使用。 名称空间子系统。
  • ns 名称空间子系统
  • pid 进程标识子系统。

2.1 cpu子系统

  • cpu.shares :可出让获得cpu使用时间的相对值
  • cpu.cfs_period_us :cfs_period_us 用来配置时间周期长度,单位为 us(微秒)。
  • cpu.cfs_quota_us: cfs_quota_us 用来配置当前 Cgroup 在 cfs_period_us 时间内最多能使用的 CPU 时间数(us)
  • cpu.stat:Cgroup 内的进程使用的 CPU 时间统计。
  • nr_periods:经过 cpu.cfs_period_us 的时间周期数量。
  • nr_throttled:在经过的周期内,有多少次因为进程在指定的时间周期内用光了配额时间而受到限制。
  • throttled_time :Cgroup 中的进程被限制使用 CPU 的总用时(ns)

2.2 linux cfs调度器,cfs_sched_class(红黑树实现,节点值为vruntime)

完全公平调度器(通过虚拟运行时间实现平衡),vruntime=实际运行时间*1024/进程权重,及优先级权重越高,获得的运行实际越多,具体的可看redhat文档 需要注意,cgroup分v1与v2 容器memory,cpu限制远离都在链接里

3 文件系统

3.1 OverlayFs

OverlayFS 也是一种与 AUFS 类似的联合文件系统,同样属于文件级的存储驱动,包含了最初的 Overlay 和 更新更稳定的 overlay2。 Overlay 只有两层:upper 层和 lower 层,Lower 层代表镜像层,upper 层代表容器可写层。

#可自行练习Overlay
 mkdir upper lower merged work
 echo "from lower" > lower/in_lower.txt
 echo "from upper" > upper/in_upper.txt
 echo "from lower" > lower/in_both.txt
 echo "from upper" > upper/in_both.txt
 sudo mount -t overlay overlay -o lowerdir=`pwd`/lower,upperdir=`pwd`/upper,workdir=`pwd`/work `pwd`/merged
 cat merged/in_both.txt
 delete merged/in_both.txt
 delete merged/in_lower.txt  
 delete merged/in_upper.txt

3.2 docker文件系统

典型的 Linux 文件系统组成:

  • Bootfs(boot file system)
  • Bootloader - 引导加载 kernel,
  • Kernel - 当 kernel 被加载到内存中后 umount bootfs。

rootfs (root file system)

  • /dev,/proc,/bin,/etc 等标准目录和文件。

linux在启动后,首先将 rootfs 设置为 readonly, 进行一系列检查, 然后将其切换为 “readwrite”供用户使用。 Docker 启动时

  • 初始化时也是将 rootfs 以 readonly 方式加载并检查,然而接下来利用 union mount 的方式将一个 readwrite 文件系统挂载在 readonly 的 rootfs 之上;
  • 并且允许再次将下层的 FS(file system) 设定为 readonly 并且向上叠加。
  • 这样一组 readonly 和一个 writeable 的结构构成一个 container 的运行时态, 每一个 FS 被称作一个 FS 层。

docker写操作:由于镜像具有共享特性,所以对容器可写层的操作需要依赖存储驱动提供的写时复制和用时分配机制,以此来 支持对容器可写层的修改,进而提高对存储和内存资源的利用率。

  • 写时复制,即 Copy-on-Write。
  • 一个镜像可以被多个容器使用,但是不需要在内存和磁盘上做多个拷贝。
  • 在需要对镜像提供的文件进行修改时,该文件会从镜像的文件系统被复制到容器的可写层的文件系统 进行修改,而镜像里面的文件不会改变。
  • 不同容器对文件的修改都相互独立、互不影响。
  • 用时分配 按需分配空间,而非提前分配,即当一个文件被创建出来后,才会分配空间。

我们生产环境使用的就是利用该原理,将相同的镜像层放在一层,去构建不同的应用层

4 docker网络

4.1 docker网络类型

  • null 把容器放入独立的网络空间但不做任何网络配置; 用户需要通过运行 docker network 命令来完成网络配置。
  • host 使用主机网络名空间,复用主机网络。
  • container 重用其它容器网络,及加入别的容器namespace
  • bridge(--net=bridge) 使用 Linux 网桥和 iptables 提供容器互联,Docker 在每台主机上创建一个名叫 docker0 的网桥,通过 veth pair 来连接该主机的每一个 EndPoint。
  • overlay 通过网络封包实现。(请参考后续手写cni部分)
  • remote Underlay:使用现有底层网络,为每一个容器配置可路由的网络 IP。 Overlay:通过网络封包实现

4.2 手动给docker配置网络

具体流程参考下图,增加网卡veth0,将一端设置给container1的eth0,另一端设置给docker0网桥,增加网卡veth1,另一端设置给 docker0网桥。并分配ip,veth创建默认是down的,将两个网卡手动up。并配置iptable规则,我们k8s里的cni就是负责这些。命令 如下,代码实现请参考后续文章,手写cni插件。

+------------------+           +--------------------+
|     container1   |           |      container2    |
|   +----------+   |           |     +----------+   |
|   |  eth0    |   |           |     |  eth0    |   |
|   |172.1.0.2 |   |           |     | 172.1.0.3|   |
+---+-----+----+---+           +-----+----------+---+
        |                                 |
        |                                 |
        |                                 |
  +-----+-----+     ++--------+    +-----------+
  |  veth0    +-----++docker0 +----+    veth1  |
  +-----------+     |172.1.0.1/16  +-----------+
                    +-----+----
                          |
                          |
                    +-----+----+
                    |   eth0   |        i
                    |192.168.0.11
                    +-----+-----
mkdir -p /var/run/netns
find -L /var/run/netns -type l -delete
ln -s /proc/$pid/ns/net /var/run/netns/$pid ip link add A type veth peer name B
brctl addif br0 A
ip link set A up
ip link set B netns $pid
ip netns exec $pid ip link set dev B name eth0 ip netns exec $pid ip link set eth0 up
ip netns exec $pid ip addr add $SETIP/$SETMASK dev eth0
ip netns exec $pid ip route add default via $GATEWAY