深入剖析docker核心技术

  • 前言
  • docker概述
  • 为什么要用docker
  • Namespace
    • 概述
    • 实现
    • 操作方法
    • 隔离性
    • 常用操作
  • Cgroups
    • 概述
    • 实现
    • 配额和度量
    • CPU 子系统
      • Linux 调度器
      • CFS 调度器
      • 操作
    • cpuacct 子系统
    • Memory 子系统
      • 操作
    • Cgroup driver
  • 文件系统 Union fs
    • 概述
    • 容器镜像
    • Docker的文件系统
    • Docker 启动
    • 写操作
    • 存储驱动
    • OverlayFS
    • OverlayFS 文件系统例子操作
  • OCI 容器标准
  • Docker 引擎架构
  • 网络
    • 概述
    • 默认模式–Bridge和NAT
    • Null模式
    • Underlay
    • Overlay
      • VXLAN
      • Flannel

前言

本文对namespace,cgroup和union fs做深入介绍,而docker使用将不再赘述,具体的docker使用见该文docker汇总

docker概述

  Linux内核提供了namespace(进程隔离),cgroup(资源管控),以及union fs(联合文件系统),对进程进行封装隔离,属于操作系统层面的虚拟化技术,由于隔离的进程独立于宿主和其他的隔离的进程,因此也称其为容器。所以当我们谈容器技术的时候,其实就是在谈基于Linux内核的namespace,cgroup以及union fs等技术。而docker借助Linux内核的namespace和cgroup的技术优势,加上自己基于union fs独创镜像的方式,就火起来了。

  docker在容器的基础上,进一步的封装,从文件系统,网络互联到进程隔离等等,大大的简化了容器的创建和维护,使docker比虚拟机技术更轻便。docker的创新点就在文件系统。

为什么要用docker

  • 更高效地利用系统资源
docker的资源利用率高,虚拟机是要虚拟一个操作系统,操作系统本身是有开销的,比如开机占用几百兆内存。
但是容器不一样,它是基于namespace,cgroup和union fs,共享主机的内核。
不需要虚拟化任何东西,不需要加载额外的很多资源来模拟一个完整的操作系统,所以总体的资源利用率很高。
  • 更快速的启动时间
正因为不用模拟操作系统,所以不需要操作系统的启动时间,所以启动时间很快。
  • 一致的运行环境
  • 持续交付和部署
  • 更轻松地迁移
  • 更轻松地维护和扩展

  先来看虚拟机,最下面是物理服务器,上面是主机的操作系统,上面的Hypervisor是虚拟化技术,任何虚拟机都是在Hypervisor之上再装一个操作系统,从虚拟化层面来看调用链很长,并且有两个操作系统。
  而容器技术,下面两层一样 ,在主机的操作系统上面不需要再虚拟化任何的操作系统,只需要一个docker engine,所以中间就节省了很多东西,不仅仅是节省了操作系统的资源开销,问题排查也变得更简单了,不需要去排查操作系统的问题了。

性能对比

特性 容器 虚拟机
启动 秒级 分钟级
硬盘使用 一般为 MB 一般为 GB
性能 接近原生 弱于
系统支持量 单机支持上千个容器 一般几十个

Namespace

概述

Linux Namespace 是Linux内核提供的资源隔离方案:

  1. 系统可以为进程分配不同的 Namespace;
  2. 保证不同的 Namespace 资源独立分配、进程彼此隔离,即不同的Namespace 下的进程互不干扰 。

实现

进程数据结构

struct task_struct {.../* namespaces */struct nsproxy *nsproxy;...
}

Namespace 数据结构

struct nsproxy {atomic_t count;struct uts_namespace *uts_ns;struct ipc_namespace *ipc_ns;struct mnt_namespace *mnt_ns;struct pid_namespace *pid_ns_for_children;struct net *net_ns;
}

操作方法

• clone

# 在创建新进程的系统调用时,可以通过 flags 参数指定需要新建的 Namespace 类型
// CLONE_NEWCGROUP / CLONE_NEWIPC / CLONE_NEWNET / CLONE_NEWNS /CLONE_NEWPID /CLONE_NEWUSER / CLONE_NEWUTS
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg)

• setns

# 该系统调用可以让调用进程加入某个已经存在的 Namespace 中
int setns(int fd, int nstype)

• unshare

# 该系统调用可以将调用进程移动到新的 Namespace 下
int unshare(int flags)

隔离性

Namespace 类型 隔离资源
IPC System V IPC 和 POSIX 消息队列
Network 网络设备、网络协议栈、网络端口等
PID 进程
Mount 挂载点
UTS 主机名和域名
USR 用户和用户组


Pid namespace

  • 不同用户的进程就是通过 Pid namespace 隔离开的,且不同 namespace 中可以有相同 Pid。
  • 有了 Pid namespace, 每个 namespace 中的 Pid 能够相互隔离。

net namespace

  • 网络隔离是通过 net namespace 实现的, 每个 net namespace 有独立的network devices, IP addresses, IP routing tables, /proc/net 目录。
  • Docker 默认采用 veth 的方式将 container 中的虚拟网卡同 host 上的一个 docker bridge: docker0 连接在一起。

ipc namespace

  • Container 中进程交互还是采用 linux 常见的进程间交互方法 (interprocess communication – IPC), 包括常见的信号量、消息队列和共享内存。
  • container 的进程间交互实际上还是 host上 具有相同 Pid namespace 中的进程间交互,因此需要在 IPC 资源申请时加入 namespace 信息 - 每个 IPC 资源有一个唯一的 32 位 ID。

mnt namespace

  • mnt namespace 允许不同 namespace 的进程看到的文件结构不同,这样每个 namespace 中的进程所看到的文件目录就被隔离开了。

uts namespace

  • UTS(“UNIX Time-sharing System”) namespace允许每个 container 拥有独立的 hostname 和 domain name, 使其在网络上可以被视作一个独立的节点而非 Host 上的一个进程。

user namespace

  • 每个 container 可以有不同的 user 和 group id, 也就是说可以在 container 内部用 container 内部的用户执行程序而非 Host 上的用户。

常用操作

查看当前系统的 namespace:

# lsns –t <type>
[root@node2 ~]# lsns -t netNS TYPE NPROCS PID USER COMMAND
4026531956 net     121   1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 22
[root@node2 ~]# lsns -t pidNS TYPE NPROCS PID USER COMMAND
4026531836 pid     121   1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 22

查看某进程的 namespace:

# ls -la /proc/<pid>/ns/
[root@node2 ~]# ls -la /proc/1/ns/
总用量 0
dr-x--x--x 2 root root 0 3月  14 20:43 .
dr-xr-xr-x 9 root root 0 3月  14 20:43 ..
lrwxrwxrwx 1 root root 0 3月  14 20:44 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 3月  14 20:44 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 3月  14 20:44 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 3月  14 20:43 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 3月  14 20:44 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 3月  14 20:44 uts -> uts:[4026531838]

进入某 namespace 运行命令:

# nsenter -t <pid> -n ip addr
# -n ip addr 表示查看net 的ns 的网络配置
[root@node2 ~]# nsenter -t 1 -n ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state
...
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc
...
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc
...

下次测试通过nsenter看容器内的网路

[root@node2 ~]# docker run -it --name centos -P centos /bin/bash
[root@9d204d09c27e /]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lo
14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet 172.17.0.2/16 scope global eth0
[root@node2 /]# docker ps|grep centos
9d204d09c27e        centos              "/bin/bash"         44 seconds ago      Up 43 seconds                           centos
[root@node2 /]# docker inspect 9d204d09c27e | grep -i pid"Pid": 4159,"PidMode": "","PidsLimit": 0,
[root@node2 /]# nsenter -t 4159 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lo
14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet 172.17.0.2/16 scope global eth0

Cgroups

概述

  • Cgroups (Control Groups)是 Linux 下用于对一个或一组进程进行资源控制和监控的机制;
  • 可以对诸如 CPU 使用时间、内存、磁盘 I/O 等进程所需的资源进行限制;
  • 不同资源的具体管理工作由相应的 Cgroup 子系统(Subsystem)来实现 ;
  • 针对不同类型的资源限制,只要将限制策略在不同的的子系统上进行关联即可 ;
  • Cgroups 在不同的系统资源管理子系统中以层级树(Hierarchy)的方式来组织管理:每个 Cgroup 都可以包含其他的子 Cgroup,因此子 Cgroup 能使用的资源除了受本 Cgroup 配置的资源参数限制,还受到父Cgroup 设置的资源限制 。

实现

进程数据结构

struct task_struct
{#ifdef CONFIG_CGROUPSstruct css_set __rcu *cgroups;struct list_head cg_list;#endif
}

css_set 是 cgroup_subsys_state 对象的集合数据结构

struct css_set {/** Set of subsystem states, one for each subsystem. This array is* immutable after creation apart from the init_css_set during* subsystem registration (at boot time).*/struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
};

配额和度量


cgroups 实现了对资源的配额和度量

  • blkio: 这个子系统设置限制每个块设备的输入输出控制。例如:磁盘,光盘以及 USB 等等。

  • CPU: 这个子系统使用调度程序为 cgroup 任务提供 CPU 的访问。

  • cpuacct: 产生 cgroup 任务的 CPU 资源报告。

  • cpuset: 如果是多核心的 CPU,这个子系统会为cgroup 任务分配单独的 CPU 和内存。

  • devices: 允许或拒绝 cgroup 任务对设备的访问。

  • freezer: 暂停和恢复 cgroup 任务。

  • memory: 设置每个 cgroup 的内存限制以及产生内存资源报告。

  • net_cls: 标记每个网络包以供 cgroup 方便使用。

  • ns: 名称空间子系统。

  • pid: 进程标识子系统。

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(纳秒)。

Linux 调度器

内核默认提供了5个调度器,Linux 内核使用 struct sched_class 来对调度器进行抽象:

  • Stop 调度器,stop_sched_class:优先级最高的调度类,可以抢占其他所有进程,不能被其他进程抢占;
  • Deadline 调度器,dl_sched_class:使用红黑树,把进程按照绝对截止期限进行排序,选择最小进程进行调度运行;
  • RT 调度器, rt_sched_class:实时调度器,为每个优先级维护一个队列;
  • CFS 调度器, cfs_sched_class:完全公平调度器,采用完全公平调度算法,引入虚拟运行时间概念;
  • IDLE-Task 调度器, idle_sched_class:空闲调度器,每个 CPU 都会有一个 idle 线程,当没有其他进程可以调度时,调度运行 idle 线程。

CFS 调度器

https://www.cnblogs.com/XiaoliBoy/p/10410686.html

直接看上面这篇文章。

操作

# 在 cgroup cpu 子系统目录中创建目录结构
[root@node2 ~]# cd /sys/fs/cgroup/cpu
[root@node2 cpu]# mkdir cpudemo# 运行 busyloop,go程序两个线程两个for死循环
# 执行 top 查看 CPU 使用情况,CPU 占用 200%
# 通过 cgroup 限制 cpu
[root@node2 cpu]# cd cpudemo/
# 获取busyloop的进程号,把进程添加到 cgroup 进程配置组
[root@node2 cpudemo]# echo 16872 >cgroup.procs
# 设置 cpuquota
[root@node2 cpudemo]# echo 10000 > cpu.cfs_quota_us
# 执行 top 查看 CPU 使用情况,CPU 占用变为10%

cpuacct 子系统

用于统计 Cgroup 及其子 Cgroup 下进程的 CPU 的使用情况。

  • cpuacct.usage
    包含该 Cgroup 及其子 Cgroup 下进程使用 CPU 的时间,单位是 ns(纳秒)。
  • cpuacct.stat
    包含该 Cgroup 及其子 Cgroup 下进程使用的 CPU 时间,以及用户态和内核态的时间。

Memory 子系统

  • memory.usage_in_bytes
      cgroup 下进程使用的内存,包含 cgroup 及其子 cgroup 下的进程使用的内存
  • memory.max_usage_in_bytes
      cgroup 下进程使用内存的最大值,包含子 cgroup 的内存使用量。
  • memory.limit_in_bytes
      设置 Cgroup 下进程最多能使用的内存。如果设置为 -1,表示对该 cgroup 的内存使用不做限制。
  • memory.soft_limit_in_bytes
      这个限制并不会阻止进程使用超过限额的内存,只是在系统内存足够时,会优先回收超过限额的内存,使之向限定值靠拢。
  • memory.oom_control
      设置是否在 Cgroup 中使用 OOM(Out of Memory)Killer,默认为使用。当属于该 cgroup 的进程使用的内存超过最大的限定值时,会立刻被 OOM Killer 处理。

操作

# 在 cgroup memory 子系统目录中创建目录结构
[root@node2 ~]# cd /sys/fs/cgroup/memory/
[root@node2 memory]# mkdir memorydemo
# 运行 malloc(一个不断申请heap的程序)
# 查看内存使用情况
[root@node2 memory]# watch 'ps -aux|grep malloc|grep -v grep'# 通过 cgroup 限制 memory
# 把进程添加到cgroup进程配置组
[root@node2 memory]# cd memorydemo/
[root@node2 memorydemo]# echo 11111 >cgroup.procs
设置 memory.limit_in_bytes
[root@node2 memorydemo]# echo 104960000 > memory.limit_in_bytes
# 等待进程被 oom kill

Cgroup driver

systemd:

  • 当操作系统使用 systemd 作为 init system 时,初始化进程生成一个根 cgroup 目录结构并作为 cgroup
    管理器。
  • systemd 与 cgroup 紧密结合,并且为每个 systemd unit 分配 cgroup。

cgroupfs:

  • docker 默认用 cgroupfs 作为 cgroup 驱动。

k8s和docker存在问题:

  • 在 systemd 作为 init system 的系统中,默认并存着两套 groupdriver。
  • 这会使得系统中 Docker 和 kubelet 管理的进程被 cgroupfs 驱动管,而 systemd 拉起的服务由systemd 驱动管,让 cgroup 管理混乱且容易在资源紧张时引发问题。

  因此 kubelet 会默认--cgroup-driver=systemd,若运行时 cgroup 不一致时,kubelet 会报错。

文件系统 Union fs

概述

Union FS

  • 将不同目录挂载到同一个虚拟文件系统下 (unite several directories into a single virtual filesystem)的文件系统(rootfs)
  • 支持为每一个成员目录(类似Git Branch)设定 readonly、readwrite 和 whiteout-able 权限
  • 文件系统分层, 对 readonly 权限的 branch 可以逻辑上进行修改(增量地, 不影响 readonly 部分的)。
  • 通常 Union FS 有两个用途, 一方面可以将多个 disk 挂到同一个目录下, 另一个更常用的就是将一个readonly 的 branch 和一个 writeable 的 branch 联合在一起。

  容器使用了union fs文件系统,可以将多个目录,mount成为一个目录。就是将两个不同的目录,挂载到同一个目录下面,然后通过读写权限的设置,使得呈现最终的文件目录给容器。

容器镜像

Docker的文件系统

docker本质是在主机上启的一个进程,这个进程本质上是没有单独的kernel的,也就是说对于容器来说是哦没有bootfs的,但是每一个进程需要看到自己的文件系统,所有它有自己单独的rootfs。没有bootfs,复用主机的kernel,有自己的rootfs,这个rootfs通过容器驱动加载出来,被这个进程所拥有,这个进程真正看到的就是这个容器的rootfs。

标准的Linux文件系统组成:

  1. Bootfs(boot file system)
Bootloader - 引导加载 kernel,
Kernel - 当 kernel 被加载到内存中后 umount(卸载) bootfs。
  1. rootfs (root file system)
/dev,/proc,/bin,/etc 等标准目录和文件。
对于不同的 linux 发行版, bootfs 基本是一致的,但 rootfs 会有差别。

Docker 启动

Linux

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

Docker 启动

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

写操作

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

写时复制

共享基础层,所有的变更都是在可写层做的

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

用时分配

  • 按需分配空间,而非提前分配,即当一个文件被创建出来后,才会分配空间。

存储驱动

目前的容器驱动基本都是OverlayFS

存储驱动 优点 缺点 应用场景
OverlayFS 并入主线内核,可以在目前几乎所有发行版本上使用。实现上只有两层,因此性能比AUFS 高 写时复制机制需要复制整个文件, 而不能只针对修改部分进行复制,因此对大文件操作会需要比较长的时间。其中Overlay 在 Docker 的后续版本中被移除 少 I/O 的场景

OverlayFS

大白话:就是把不同的目录结构,merge到一个目录去,并且把这个目录挂载到这个容器里面,这样每一个容器看到的都是自己独立的一个文件系统。

OverlayFS 是一种与 AUFS 类似的联合文件系统,同样属于文件级的存储驱动,包含了最初的 Overlay 和 更新更稳定的 overlay2。

Overlay 只有两层:upper 层和 lower 层,Lower 层代表镜像层,upper 层代表容器可写层。

upper和lower会一起合并成一个合并层,就是mount指定两个层级,最后会被合并成一个目录。

  • 如果一个目录在下层,那么在合并层是可见的(文件1)
  • 如果一个目录在上层,那么在合并层也是可见的(文件4)
  • 如果一个文件在上下层都存在,那么在合并层中用的是上层文件(文件2)

那么这就给容器机会了, 在dockerfile中的每一条指令,都是原来层的上一层,每一层都往上叠一次,那么最终容器加载看到的文件项目就是文件Mount。

OverlayFS 文件系统例子操作

[root@node2 /]# mkdir upper lower merged work
[root@node2 /]# echo "from lower" > lower/in_lower.txt
[root@node2 /]# echo "from upper" > upper/in_upper.txt
[root@node2 /]# echo "from lower" > lower/in_both.txt
[root@node2 /]# echo "from upper" > upper/in_both.txt
[root@node2 /]# sudo mount -t overlay overlay -o lowerdir=`pwd`/lower,upperdir=`pwd`/upper,workdir=`pwd`/work `pwd`/merged
[root@node2 /]# cd merged/
[root@node2 merged]# ls
in_both.txt  in_lower.txt  in_upper.txt
# 当有同名文件的时候以上层为主
[root@node2 merged]# cat in_both.txt
from upper

查看docker容器是怎么用的

可以看到GraphDriver这里就定义了文件系统,可以看到lowerdir有很多不同的部分,事实上,dockerfile中的每一层都做了一次monut,中间的指令都是一次次mount出来的。

[root@node2 /]# docker run -it --name centos -P centos /bin/bash
[root@26cb16ab2fc0 /]# exit
exit
[root@node2 /]# docker inspect 26cb16ab2fc0
"GraphDriver": {"Name": "overlay2","Data": {"LowerDir": "/var/lib/docker/overlay2/bd7b9b6577911e31ba997a11022b8241cebd8d6b435aa8d8b109f18f41d9b7f0-init/diff:/var/lib/docker/overlay2/7da4ae3d97674a0cdfe8af9f9b341f75c3a436cf8538325b9e2263fbc043a990/diff","MergedDir": "/var/lib/docker/overlay2/bd7b9b6577911e31ba997a11022b8241cebd8d6b435aa8d8b109f18f41d9b7f0/merged","UpperDir": "/var/lib/docker/overlay2/bd7b9b6577911e31ba997a11022b8241cebd8d6b435aa8d8b109f18f41d9b7f0/diff","WorkDir": "/var/lib/docker/overlay2/bd7b9b6577911e31ba997a11022b8241cebd8d6b435aa8d8b109f18f41d9b7f0/work"}}

OCI 容器标准

Open Container Initiative

  1. OCI 组织于 2015 年创建,是一个致力于定义容器镜像标准和运行时标准的开放式组织。
  2. OCI 定义了运行时标准(Runtime Specification)、镜像标准(Image Specification)和分发标准(Distribution Specification)
  • 运行时标准定义如何解压应用包并运行
  • 镜像标准定义应用如何打包
  • 分发标准定义如何分发容器镜像

Docker 引擎架构

  在docker初期,docker daemon是主进程, 任何的container进程都是daemon fork出来的,这样就有一个问题,如果docker重启或者升级,那么docker daemon这个父进程是要销毁的,那么这些子进程怎么办。这是docker早期的问题。

  后面containerd出来了,他做了一个docker shim或者叫container shim,就是说containerd是一个单纯的daemon,所有fork出来的子进程的父进程不再说daemon,他为每一个容器进程构建了一个shim,作为他的父进程,那么 shim的父进程是谁呢,是systemd,这样的话containerd重启的话所有的容器是不受影响的。

容器里面的1号进程,是entrypoint指定的那个进程

网络

概述

  • Null(–net=None)
把容器放入独立的网络空间但不做任何网络配置;
用户需要通过运行 docker network 命令来完成网络配置。
  • Host
使用主机网络名空间,复用主机网络。
  • Container
重用其他容器的网络。
  • Bridge(–net=bridge)
使用 Linux 网桥和 iptables 提供容器互联,
Docker 在每台主机上创建一个名叫 docker0的网桥,
通过 veth pair 来连接该主机的每一个 EndPoint。

跨主机网络方式,下面两种

  • Overlay(libnetwork, libkv)
通过网络封包实现。
  • Remote(work with remote drivers)
Underlay:
• 使用现有底层网络,为每一个容器配置可路由的网络 IP。
Overlay:
• 通过网络封包实现。

默认模式–Bridge和NAT

在主机启动容器:

[root@node2 /]# docker run -it -p 8888:80 nginx

Docker 会以标准模式配置网络:

  • 创建 veth pair;
  • 将 veth pair的一端连接到 docker0 网桥;
  • veth pair 的另外一端设置为容器名空间的 eth0;
  • 为容器名空间的 eth0 分配 ip;
  • 主机上的 Iptables 规则:-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8888 -j DNAT --to-destination 172.17.0.2:80

  linux中是由bridge网桥设备的,图中的docker0就是网桥,docker驱动CNM会自动给容器分配ip(bridge模式下),任何会构建一个veth pair,是一个虚拟的网络设备,可以理解为是一个网线,有A,B两端,双向的。只需要创建一个veth pair,一端插在docker0网桥上面,一端查到容器里面,作为容器的虚拟网口,并且把ip地址配置在上面,这样从主机到容器的网络就联通了。

下面来查看这些信息

# 运行一个容器
# -p 8888:80的意思就是将主机的8888端口映射到容器的80端口
[root@node2 /]# docker run -it -p 8888:80 nginx
...
# 可以看到docker0这个网桥
[root@node2 /]# brctl show
bridge name bridge id       STP enabled interfaces
docker0     8000.0242463a9968   no      veth5cecfe5
# 查看容器进程号
[root@node2 /]# docker inspect 6ef7f2dd7253|grep -i pid"Pid": 99551,"PidMode": "","PidsLimit": 0,
# 查看该pid的net namespace下的网络
[root@node2 /]# nsenter -t 99551 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope host valid_lft forever preferred_lft forever
18: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet 172.17.0.2/16 scope global eth0valid_lft forever preferred_lft foreverinet6 fe80::42:acff:fe11:2/64 scope link valid_lft forever preferred_lft forever
[root@node2 /]# nsenter -t 99551 -n ip r
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2

  可以看到这个容器有自己的interface和自己的路由表,那么在主机上就能访问到这个ip地址,网络是通的。这一切都是容器驱动帮我们做的,下面介绍Null的时候将介绍自己创建网络

[root@node2 /]# iptables-save
...
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8888 -j DNAT --to-destination 172.17.0.2:80
...

iptable是linux内核里面处理数据包的时候,做包过滤和包修改的工具,可以定义一些规则,linux在处理这些数据包的时候来读这些规则,按照规则去修改数据包的包头。

Null模式

Null 模式是一个空实现,即容器没有配置任何网络。可以通过 Null 模式启动容器并在宿主机上通过命令为容器配置网络。

# 创建net namespace 的目录
[root@node2 /]# mkdir -p /var/run/netns
[root@node2 /]# find -L /var/run/netns -type l -delete
# 以Null网络模式启动nginx docker
[root@node2 /]# docker run --network=none  -d nginx
9ebdcd509ea7c11f454dc981808799183e847b03d6c0f519fd7316ea1f7f1818
# 找到容器对应的pid
[root@node2 /]# docker inspect 9ebdcd509|grep -i pid"Pid": 127513,"PidMode": "","PidsLimit": 0,
# 检查容器的网络配置
[root@node2 /]# nsenter -t 127513 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope host valid_lft forever preferred_lft forever
# 链接网络名称空间
[root@node2 /]# export pid=127513
[root@node2 /]# ln -s /proc/$pid/ns/net /var/run/netns/$pid
[root@node2 /]# ip netns list
127513
# 创建veth pair
[root@node2 /]# ip link add A type veth peer name B
# 往docker0这个网桥上插A口
[root@node2 /]# brctl addif docker0 A
# 启动A口
[root@node2 /]# ip link set A up[root@node2 /]# SETIP=172.17.0.10
[root@node2 /]# SETMASK=16
[root@node2 /]# GATEWAY=172.17.0.1
# 将B口放到pid的namespace里面去
[root@node2 /]# ip link set B netns $pid
# 可以看到B口已经在这个namespace里面了
[root@node2 /]# nsenter -t $pid -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope host valid_lft forever preferred_lft forever
20: B@if21: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000link/ether 9a:39:63:07:3c:a2 brd ff:ff:ff:ff:ff:ff link-netnsid 0
# 将B口改名为eth0
[root@node2 /]# ip netns exec $pid ip link set dev B name eth0
# 启动eth0口
[root@node2 /]# ip netns exec $pid ip link set eth0 up
# 配置ip、mark
[root@node2 /]# ip netns exec $pid ip addr add $SETIP/$SETMASK dev eth0
# 配置路由
[root@node2 /]# ip netns exec $pid ip route add default via $GATEWAY
# 查看
[root@node2 /]# nsenter -t $pid -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state
20: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000link/ether 9a:39:63:07:3c:a2 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet 172.17.0.10/16 scope global eth0
# 成功使用容器提供的服务
[root@node2 /]# curl 172.17.0.10
<!DOCTYPE html>
...
</html>

上面的命令其实就是默认的网桥模式做的事情

Underlay

  上面介绍的都是解决了容器和主机的网络联通,那么如果想跨主机联通如何实现呢。第一种方法是,物理网络认识一个网络段,能不能预留一个网络段分配给容器呢,那么这样通过ip分配的机制把ip分配给容器,那么这样跨主机的网络就互通了,因为这个ip段是物理网络认识的,物理机知道路由怎么走。相当于容器的网络依托于物理的网络。这种模式就是Underlay。

  • 采用 Linux 网桥设备(sbrctl),通过物理网络连通容器;
  • 创建新的网桥设备 mydr0;
  • 将主机网卡加入网桥;
  • 把主机网卡的地址配置到网桥,并把默认路由规则转移到网桥 mydr0;
  • 启动容器;
  • 创建 veth 队,并且把一个 peer 添加到网桥 mydr0;
  • 配置容器把 veth 的另一个 peer 分配给容器网卡;

Overlay

  • Docker overlay 网络驱动原生支持多主机网络;
  • Libnetwork 是一个内置的基于 VXLAN 的网络驱动

VXLAN

  overlay就是隧道模式,容器A和容器B是不互通的,但是主机上的底层网络是互通的,overlay就是主机上的包发出去的时候,从主机A容器访问主机B容器,本身是不互通的。
  overlay就是在每一个主机上面有一个设备,这个设备会处理每一个数据包,如果是容器网络的数据包,他会在容器网络的原始数据包上面加一层,在linux里面看到的任何的数据包,看到的是skb,skb会看到每一层header,ipheader,tcpheader,他可以在不破坏原来的header基础之上,在上面再加一层header,那么加上的这一层的原始地址就是主机A的地址,目标地址就是主机B的地址,这样数据包就可以传到对端了。
  传到对端后,主机B的虚拟设备再把外层的header剥掉,剩下的数据包就是原来的数据包了,这样的话在主机上处理原始数据包的时候,看到目标地址,就知道是主机B的容器,就知道走哪个口了。所以所谓的overlay就是封包解包的过程。

Flannel

  比较出名的flannel就是走overlay的,默认的主机上装的就是overlay的模式。flannel在每个主机上就会有flannel这样一个设备,任何数据包出去的时候,会被flannel封一层,这样数据包就能到达对端。


深入剖析docker核心技术(namespace、cgroups、union fs、网络)相关推荐

  1. Docker核心技术Namespace浅析

    简介 Linux `Namespace`提供了一种内核级别隔离系统资源的方法,通过将系统的全局资源放在不同的`Namespace`中,来实现资源隔离的目的.不同`Namespace`的程序,可以享有一 ...

  2. 【重识云原生】第六章容器6.1.8节——Docker核心技术UnionFS

    <重识云原生系列>专题索引: 第一章--不谋全局不足以谋一域 第二章计算第1节--计算虚拟化技术总述 第二章计算第2节--主流虚拟化技术之VMare ESXi 第二章计算第3节--主流虚拟 ...

  3. Docker 核心技术与实现原理

    提到虚拟化技术,我们首先想到的一定是 Docker,经过四年的快速发展 Docker 已经成为了很多公司的生产环境中大规模使用,也不再是一个只能在开发阶段使用的玩具了.作为在生产环境中广泛应用的产品, ...

  4. 深入浅出Docker(一):Docker核心技术预览

    [编者按]Docker是PaaS供应商dotCloud开源的一个基于LXC 的高级容器引擎,源代码托管在 GitHub 上, 基于Go语言开发并遵从Apache 2.0协议开源.Docker提供了一种 ...

  5. Docker(一):Docker核心技术预览

    开始学习docker了,想写一篇docker技术介绍的纯理论文章,发现以下网站的文档写的特别好,就直接引用了,文章转载自: http://www.infoq.com/cn/DockerDeep htt ...

  6. 容器技术介绍之docker核心技术概述

    容器简单来说是一种沙盒技术,将应用"装"进沙盒中,像集装箱一样,把应用封装起来,使得应用之间不会相互干扰,而放进沙盒中的应用也方便"搬家".本文基于docker ...

  7. docker技术剖析--docker资源限制及应用总结 for centos7.2

    http://hongge.blog.51cto.com/ Docker(linux container)所依赖的底层技术 1 Namespace 用来做容器的隔离,有了namespace,在dock ...

  8. 后端技术杂谈10:Docker 核心技术与实现原理

    Docker 核心技术与实现原理 30 NOV 2017  docker  namespaces  cgroups  unionfs  aufs  server Namespaces 进程 网络 挂载 ...

  9. Docker核心技术

    整理自<Docker技术入门与实践>(杨保华 戴王剑 曹亚仑) - Docker核心技术一文. Docker是一种基于Linux Container(LXC)技术实现的容器虚拟化技术,现又 ...

最新文章

  1. Android----PopupWindow
  2. 汇总|SLAM常用linux命令,包括文件查找、查看文件大小、设置环境变量等
  3. 网络编程学习笔记(tcp_connect函数)
  4. 面试之索引---------如果不能吊打面试官,找我扯皮*
  5. 解决placeholder兼容性问题
  6. HDU 1618 Oulipo KMP解决问题的方法
  7. clone是深拷贝还是浅拷贝_go-clone:深拷贝 Go 数据结构
  8. 谈谈GIS三维渲染引擎
  9. 如何用FPGA实现算法的硬件加速
  10. P3531-[POI2012]LIT-Letters【逆序对,树状数组】
  11. 两相流计算中,如何用Tecplot提取水相断面平均物理量?
  12. 使用Hutool来实现深拷贝
  13. [导入]基于Spring+zk的WebDisk系统研究.pdf(462.84 KB)
  14. VMware Ubuntu 18.04 搭建个人Hustoj教程
  15. 中维高清监控录像被覆盖故障排除
  16. 江苏事业单位计算机类考申论吗,笔试将近!2020年江苏事业单位统考你要注意!...
  17. 解决linux下sudo更改文件权限报错xxxis not in the sudoers file. This incident will be reported.
  18. 完全免费无限量京东联盟高级API - 高并发京东联盟转链接口 京东客转链接口 京粉转链接口 京东联盟返利接口 京东返利接口,线报无广告接口
  19. python访问陌生人qq空间_用Python登录好友QQ空间点赞
  20. Grid 不能动态添加数据这是为什么呢!

热门文章

  1. C/C++ #与##的作用与应用
  2. C语言-定义一个字符串
  3. 优秀的装机必备「系统「效率「笔记「程序员」mac软件集
  4. RationalDMIS 2020测量圆
  5. 宝塔服务器建2个网站,宝塔面板2个不同域名和网站,显示了一个网站的内容
  6. 苹果手机左上角的数字怎么弄_这些iPhone自带软件,学会一个少装十几个APP,16G手机也够用...
  7. DataStage作业开发步骤
  8. 【Shell】判断是否包含给定字符串
  9. 经验分享——如何创建拼团活动?
  10. 周期性定时备份及邮件通知管理员