docker网络再详解


1. 网络常用命令

ip addr#查看ip地址
ip a
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:70:df:b0:c5 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:70ff:fedf:b0c5/64 scope link 
       valid_lft forever preferred_lft forever

如何测试端口的连通性?可以使用telnet来实现,可以检查一个服务器的端口是否打开?

telnet www.baidu.com 80
Connected to 192.168.132.128. 

如何追踪路径?

tracepath

如何请求web服务?

curl www.baidu.com

2. 容器网络涉及到哪些问题?

16: eth0@if17: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.3/16 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe11:3/64 scope link 
       valid_lft forever preferred_lft forever
/ # exit
[root@192 ~]# ping 172.17.0.3
PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data.
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.129 ms
64 bytes from 172.17.0.3: icmp_seq=2 ttl=64 time=0.095 ms
64 bytes from 172.17.0.3: icmp_seq=3 ttl=64 time=0.097 ms
64 bytes from 172.17.0.3: icmp_seq=4 ttl=64 time=0.097 ms

为什么在本地能ping通这个地址?

为什么在本宿主机上的各个容器能够互相访问

容器的IP地址为什么是这样分配的?

3. 容器间的通信网络问题

为什么容器间可以互相通信?

3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:70:df:b0:c5 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:70ff:fedf:b0c5/64 scope link 
       valid_lft forever preferred_lft forever
"IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },

当选定了这个网络之后,如果有容器试图加入这个网络的话,那么就会从这个具有16个主机位的网段中获取一个IP地址,然后执行分配

"Containers": {
    "61eb0057c21dd80a9cf10a58d6da600d388617b70513b90d1ecc1b2f55a79049": {
        "Name": "box1",
        "EndpointID": "216ba94af6bd325c74fbed195c24d8a752fcc2b4321fba2f133dfda9fc755130",
        "MacAddress": "02:42:ac:11:00:03",
        "IPv4Address": "172.17.0.3/16",
        "IPv6Address": ""
    },
    "6b81c358b2160057f0674859e2330550354ac8263441843869eb1ebbf0f40c77": {
        "Name": "redis",
        "EndpointID": "de81d4aeb2101a4e18f8c7b84991b0bfeccb74c1ea9dc34bfd22ba9b60f14bd5",
        "MacAddress": "02:42:ac:11:00:02",
        "IPv4Address": "172.17.0.2/16",
        "IPv6Address": ""
    },
    "720a52bc4d8e191dde31ff2a3283ea21d3651067f677f676b3583430362f693b": {
        "Name": "box2",
        "EndpointID": "dd926d2d1ad9d732a5633146ad6c99a79aa1c1c8ebcea73beb3bcb01605cb79b",
        "MacAddress": "02:42:ac:11:00:04",
        "IPv4Address": "172.17.0.4/16",
        "IPv6Address": ""
    }
},

这个bridge就类似于一个网桥/交换机,我们的容器加入到这个网络之后,就相当于连接到这个交换机上了,然后通过交换机进行通信

简单来理解一下这个图,图中的eth0均是网卡,其中容器内部的为虚拟网卡,而与外部通信的是实际宿主机上的网卡(当然也有可能是通过虚拟机技术虚拟出来的网卡),vethx称为是插槽,它就类似于docker0网桥中的一个插槽,它能够将容器中的网卡以插槽的形式插入到网桥中,然后网桥在与外部网卡进行通信,它可以显示Linux-Bridge,能够显示这个Bridge中插槽的占用情况

bridge name     bridge id               STP enabled     interfaces
docker0         8000.024270dfb0c5       no              veth26415ab
                                                        veth2b12f72
                                                        veth95b4392

4. 容器对外进行通信

[root@localhost ~]# ip route
default via 192.168.132.2 dev ens33 proto static metric 100 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
192.168.132.0/24 dev ens33 proto kernel scope link src 192.168.132.128 metric 100 

在容器中ping www.baidu.com,发生了什么?

首先看到下面的图,假设使用Test1ping www.baidu.com,然后通过插槽veth1将数据包发送到docker0,docker0中的状态机会判断当前数据包的去向

如果docker0发现数据包是去向本局域网的,那么就查找端口表中的IP地址,将数据包送达对应的端口

如果docker0发现数据包是去向外网的,它首先会查询本主机上的路由表上的默认路由所对应的网卡,将数据包发送到对应的网卡设备上,这个过程会经历一个NAT,这是因为dockerBridge形成的网络通常是一个私有网络,出得去,但是回不来,它会将原本的数据报的源地址包装成eth0的地址,比如说网桥中的网段是172.168.132.0/16,然后iptables中定义,如果你的源地址是这个网段下的话,那么就会统统包装成对应的eth0的网卡地址,然后发送出去

  • 从外部来看,无论是通过容器内部往外面ping,还是说从宿主机直接往外面ping,它的源地址都会变成对外路由的地址

5. NAT的原理是什么?

NAT主要是用在路由器上的,主要是用来解决什么问题呢?用来解决IPV4数量不足的问题, 通常是通过私有网络+NAT模式来解决的

iptables可以通过软件的方式来实现ip地址的转换,

  • Private to Public
  • Public to Private
  • Private to Private

只要设置合理的规则,就能够实现这几种映射

注意,docker容器可以连接多个网络,就相当于说一个容器中虚拟出来了多张网卡,然后这几张网卡通过插槽,插入到了对应的网桥上,通过网桥实现对应的传输

在容器内ping一个网段中的其他容器的名称,也可以互相ping通,这就相当于容器之间提供了一种DNS的机制,通过这种机制就能够实现容器网段内互相连通的形式

6. docker的端口转发是怎么回事?

思考一下,我们通常会这样启动一个nginx

docker container run -d --rm --name -p 8080:80 web nginx

其中-p 8080:80是指:宿主机会监听本地的8080端口,然后会将发送到本地的8080端口上的数据包转发到内部容器的80端口上,格式:外部端口:内部端口

而其原理是基于iptables来实现的,Docker在我们使用-p参数的时候,会添加一条规则,通常格式是:

tcp dpt:8080 to:172.167.0.2:80

它就是说做了一个地址绑定,它会将一个ip:port的数据包转发到ip:port

EXPOSE?

它简单来说,就是给了一个备注,就是说你这个镜像在运行的时候,要暴露出去哪个端口

EXPOSE 80/udp#可以指定暴露端口的处理协议栈
EXPOSE 80/tcp

7. host网络模式是怎么回事?

简单来说就是说不再虚拟出来一张eth0网卡出来,而是和主机共享一个网络命名空间

使用场景:当容器内需要修改主机的网络配置的时候,就应当要使用这种模式

Linux中的网络命名空间

Linuxnamespace(命名空间)技术是一种隔离技术,常用的Namespaceuser namespace,process namespace,network namespace

Docker容器中,不同的容器通过Network namespace进行了隔离,也就是说不同的容器有各自的IP地址,路由表等,互不影响

  • 创建bridge
[root@localhost ~]# brctl addbr mydocker0 
[root@localhost ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
br-4bc785c32314         8000.0242c47294f2       no              vethe25c480
                                                        vethede6974
docker0         8000.024270dfb0c5       no              veth26415ab
                                                        veth2b12f72
                                                        veth95b4392
mydocker0               8000.000000000000       no
  • 准备一个shell脚本
#!/bin/bash

bridge=$1
namespace=$2
addr=$3
#插槽的名字
vethA=veth-$namespace
vethB=eth00-$namespace
#创建网络命名空间
sudo ip netns add $namespace
#添加一个link,相当于用网线将插槽连起来
sudo ip link add $vethA type veth peer name $vethB
#将接口放入到namespace
sudo ip link set $vethB netns $namespace
#配置一个ip地址,然后up起来
sudo ip netns exec $namespace ip addr add $addr dev $vethB
sudo ip netns exec $namespace ip link set $vethB up

sudo ip link set $vethA up
#将接口加入进去
sudo brctl addif $bridge $vethA

大致就是创建了一个namespace1,创建一条link,将这条link的一个接口放到一个namespace1中,一个接口放到MyBridge

接着namespace2也是做一样的操作

8. namespace

通过clone()在创建新进程的同时创建namespace

int clone(int (*child_finc)(void *))

linux是否只能有一个PID为1的进程?

上帝进程:init进程,它的PID为1,每新创建一个进程,都会使得PID+1

首先先说答案,可以有多个PID的进程,这是因为存在Namespace这样的一种进程组隔离技术

这种技术将原本的三层架构,也就是进程运行于操作系统内核之上,操作系统内核运行于硬件之上,变成了

进程运行于Namespace之上,Namespace运行于操作系统内核之上,操作系统内核运行于硬件之上

那么这也就意味着,namespace中的每一个进程组,都可以有一个PID=1的进程

Linux Namespace是什么?

八股文:Namespace机制提供了一种资源隔离和虚拟化的特性,PID,IPC,network等系统资源得不再是全局性的了,而是属于某个特定的namespace,每个namespace下的资源对于其他的namespace都是不可见的,因此从操作系统的层面上看,就会出现多个相同PID的进程,系统中可以同时存在多个相同PID的进程,由于资源属于不同的namespace,因此它们并不冲突,而是在用户层面上只能看到属于用户自己namespace下的资源,例如使用ps,命令只能够列出自己namespace下的进程

该功能对内核资源进行分区,使得一组进程看到一组资源,而另外一组进程看到另外一组的资源,Namespace的工作方式通过一组资源和进程设置在相同的Namespace而其作用,但是这些Namespace引用了不同的资源,资源可能存在于多个Namespace中,这些资源可以是进程ID、主机名、用户ID、文件名、与网络访问相关的名称和进程间通信

但是要注意,root namespace可以看到其他namespace下的进程,但是子namespace下的ID是从1开始的,也就是可能和其他的namespaceID冲突,因此这里的话还做了一个映射,也就是说子namespace的进程号映射到rootnamespace中有自己的一个PID

基于namespace的容器技术如何保障安全性?

尽管目前namespace已经提供了非常多的资源隔离类型,但是依然有部分关键内容没有被完全隔离,其中包括有一些系统的关键性目录/sys、/proc

由于同一宿主机上的所有容器都共享了主机的内核,攻击者可以利用一些特殊手段导致内核崩溃,比如说恶意占用大量CPU

Docker自身是基于Linux的多种Namespace

  • User Namespace主要设计用来做容器内用户和主机的用户隔离的,Docker从1.10版本开始,使用这个命名空间,从而实现了容器中的root用户映射到主机上的非root用户,安装扫描镜像的组件,与镜像仓库

  • 宿主机的内核应该要尽量安装最新的补丁

  • 使用Capabilities划分权限,在虚拟机内可以赋予用户所有权限,不使用--privileged以及其他特殊权限可以使用--cap-add参数

  • 使用资源限制

docker run --cpus=1 -m=2048m --pid-limit=1000
# 启动一个1核2G的容器
# --cpus:限制CPU的配额
# -m:限制内存的配额
# --pid-limit:限制容器的PID的个数
  • 使用安全容器,安全容器内的每个容器都运行在一个单独的微型虚拟机中,拥有独立的操作系统和内核,并且有虚拟化层的安全隔离

为什么构建容器需要Namespace?

  • Mount Namespace:隔离不同的进程或者进程组看到的挂载点,实现容器内只能看到自己的挂载信息,在容器内的挂载操作不会影响主机的挂载目录,简单来说,就是在不同的进程中可以看到不同的挂载目录

什么叫做挂载?

挂载就是说将设备文件中的顶级目录连接到Linux根目录下的某一个目录(最好是空的目录),访问这个目录就相当于访问设备文件,就好像是windows中的快捷访问方式,本质应该是软连接

比如说,当你插入一个U盘的时候,这时候由于Linux系统和U盘分属于两个文件系统,因此通过Linux系统无法直接访问到U盘中的系统目录,因此在这种情况下Linux系统使用任何硬件设备,都必须建立一个连接,使得访问Linux下的文件就好像在访问U盘上的文件一样

  • unshare:是util-linux工具包中的一个工具,可以实现创建并且访问不同类型的Namespace
unshare --mount --fork /bin/bash

创建一个bash进程并且新建一个Mount Namespace

什么是PID Namespace

PID Namespace的作用是用来隔离进程的,你如何来验证?

可以通过对照法,就是一个通过unshare --pid --fork --mount-proc /bin/bash后查询当前命名空间的ps aux,就可以看到当前命名空间的所有进程信息了

什么是UTS Namespacet

它是用来隔离主机名的,它允许每个UTS Namespace拥有一个独立的主机名,可以使用hostname来查看

IPC是用来隔离进程间的通信的

PID NamespaceIPC Namespace一起使用可以实现同一个IPC Namespace内的进程彼此可以通信,不同的IPC Namespace的进程却不能够通信,当起来容器的时候,那么这时候容器就互不可见了

UserNamespace主要是用来隔离用户和用户组的

使用User Namespace可以实现进程在容器内拥有root权限,而在主机上只是普通用户,实现了用户组和用户组之间的隔离

NetNamespace用来隔离网络设备,IP地址和端口信息

NetNamespace可以让每个进程拥有自己独立的IP地址,端口和网卡信息

例如说主机的IP地址为172.16.4.1,容器内可以设置独立的IP地址为192.168.1.1

总而言之,通过Namespace之间的组合和搭配,可以实现同一主机系统中对进程ID,主机名,用户ID,文件名,网络和进程间通信等资源的隔离

9. 容器监控原理以及cAdvisor

  • 容器是短期存活的,并且可以动态调度
  • 容器的本质是进程,而不是一个完整的操作系统
  • 由于容器非常轻量,因此其基本操作也会非常频繁
docker stats#命令

CAdvisor可以采集及其上所有运行的容器信息,提供了基础的查询界面和HTTP接口

docker run \
  --volume=/:/rootfs:ro \
  --volume=/var/run:/var/run:rw \
  --volume=/sys:/sys:ro \
  --volume=/var/lib/docker/:/var/lib/docker:ro \
  --volume=/dev/disk/:/dev/disk:ro \
  --publish=8080:8080 \
  --detach=true \
  --name=cadvisor \
  --privileged=true \
  google/cadvisor:latest

这些监控数据是怎么来的?

  • Docker是基于NamespaceCGroups和联合文件系统实现的

CGroup不仅可以用于容器资源的限制,还可以提供容器的资源使用率

CGroup的工作目录/sys/fs/cgroup下包含了Cgroups的所有内容

当我们创建一个容器的时候,就会得到一个容器的ID,这个ID作为Docker area的存储凭证,它会将容器相关信息存储在工作目录/sys/fs/cgroup/memory/docker/ID下,其中文件memory.limit_in_bytes就存储了memory中的相关信息,/sys/fs/cgroup/memory/docker/ID

通过这个例子就可以知道,它是通过读取cgroup下的文件信息来获取的

如何查看网络的数据?

sudo cat /proc/PID/net/dev

容器的监控原理其实就是定时读取Linux主机上的相关的文件并且展示给用户

10. 如何通过CGroups机制实现资源限制?

使用不同的Namespace,可以实现容器中的进程看不到其他容器的资源,但是却无法限制容器的资源使用情况。

主要是用来限制单个docker容器可使用硬件资源

什么是CGaroups?

它是内核中的一个功能,可以实现限制进程或者进程组的资源(比如说CPU、内存、磁盘IO等)

  • 资源限制:限制容器的使用上限
  • 优先级控制:不同的组可以有不同的资源使用优先级
  • 审计:计算控制组的资源使用情况
  • 控制:控制进程的挂起或者恢复

核心概念

  • 子系统(subSystem):它本质上一个内核的组件,一个子系统就代表着一类资源调度控制器,比如说内存子系统可以限制内存的使用量,进程子系统,控制了进程的调度,CPU子系统控制了CPU的使用时间,它是真正实现某一个资源限制的基础
  • 控制组:表示了一组进程和一组带有参数的子系统的关联关系,这个就是说,假设需要用子系统来控制某一组进程的使用情况,那么一个子系统和这一组进程构成了一个控制组,比如说使用内存子系统限制了某一组进程的内存使用量,那么内存子系统和这一组进程称为是一个控制组
  • 层次树:是由一系列的控制组按照树状结构排列组成的,简单来说,就是当一个进程组作为父进程组,一个进程组作为子进程组的情况下,子进程组自动获得了父进程组的一些属性,比如说有一个控制组C1(限制一个进程组只能使用1GB的内存),而控制组C2希望(限制一个进程组在使用1GB内存的前提下使用2GB的磁盘),那么C2就可以作为C1的子控制组,形成了一个层次树

如何使用CGroup?

  • 在CPU子系统下创建测试文件
总用量 0
-rw-r--r--. 1 root root 0 43 19:57 cgroup.clone_children
--w--w--w-. 1 root root 0 43 19:57 cgroup.event_control
-rw-r--r--. 1 root root 0 43 19:57 cgroup.procsw
-r--r--r--. 1 root root 0 43 19:57 cpuacct.stat
-rw-r--r--. 1 root root 0 43 19:57 cpuacct.usage
-r--r--r--. 1 root root 0 43 19:57 cpuacct.usage_percpu
-rw-r--r--. 1 root root 0 43 19:57 cpu.cfs_period_us
-rw-r--r--. 1 root root 0 43 19:57 cpu.cfs_quota_us
-rw-r--r--. 1 root root 0 43 19:57 cpu.rt_period_us
-rw-r--r--. 1 root root 0 43 19:57 cpu.rt_runtime_us
-rw-r--r--. 1 root root 0 43 19:57 cpu.shares
-r--r--r--. 1 root root 0 43 19:57 cpu.stat
-rw-r--r--. 1 root root 0 43 19:57 notify_on_release
-rw-r--r--. 1 root root 0 43 19:57 tasks
  • 将进程加入到cgroup中
25055 # shell的主进程
25147

这里简单讲一下原理是什么,就是创建一个cgroup,然后将你需要绑定的PID输入到这个tasks中去,然后对应地修改相关的cpu.shares等文件内容,就可以执行实现相关进程的资源限制了

11. 剖析docker组件以及底层工作原理

docker:docker是一个二进制文件,对用户可见的操作形式为docker命令,通过docker命令可以完成所有的Docker客户端和服务端的通信,具体的流程如下:

docker组件向服务端发送请求后,服务端根据请求执行具体的动作并且将结果返回给docker,docker解析服务端返回的结果,并且将结果通过命令行标准输出展示给用户

dockerd(docker daemon):dockerd用来接收客户端发送的请求,执行具体的处理任务,处理完成后将结果返回到客户端

dockerClient和DockerServer是如何通信的?

  • 在同一台机器上使用UNIX套接字通信,配置格式为unix://socker_path,默认dockered生成的socket文件路径为/var/run/docker.sock,这个文件只有root用户才能访问,这也就是为什么初始情况下只有root用户才能访问
  • 通过TCP与服务端通信,配置格式为tcp://host:port,通过设置Docker的TLS相关参数,来保证数据传输的安全
  • 通过文件描述符的方式,配置格式为fd://,这种格式一般用于systemed管理的系统中

Docker客户端和服务端的通信形式必须保持一致,否则将无法通信,如果想要通过远程的方式访问dockerd,可以使用dockerd启动的时候添加-H参数指定监听的HostPost

docker-init:对标于Linux系统中的init进程,它可以杀死僵尸进程或者是托管孤儿进程,在容器的内部,当业务进程没有回收子进程的能力的时候,在执行docker run 启动容器的时候可以添加--init,它具体原理是:在docker所创建的进程组中,添加1号init进程

docker-proxy:主要是用来做端口映射的,当使用docker run命令启动容器的时候,如果使用了-p参数,docker proxy组件就会把容器内相应的端口映射到主机上来,底层是基于iptables实现的

具体就是说,主机会监听本机上的端口,当有数据发送到这个端口的时候,就会根据iptables中的规则,将数据报转发到对应的IP地址上

containerd(container daememon):它包含有一个后台常驻进程,默认的socket路径为/run/containerd/containerd.sock,dockerd通过unix套接字向containerd发送请求,containerd接收到请求后负责执行指令,并且将执行结果返回给dockerd

  • 镜像的管理,在容器启动前,一系列的操作,包括有在本地查询是否有镜像,在远程是否有镜像,然后根据镜像创建容器层等
  • 接收dockerd的请求,通过适当的参数调用runc启动容器
  • 管理存储相关资源
  • 管理网络相关的资源

containerd-shim:主要作用是将containerd和真正的容器进程解耦,使用containerd-shim作为容器进程的父进程,从而实现containerd不会影响已启动的容器进程

ctr:containerd-control,它是containerd的客户端,主要用来开发和调试,在没有dockerd的环境中,ctr可以直接向containerd守护进程发送操作容器的请求

runc:容器运行时组件,命令行可以用来直接创建和运行容器

总体来说,Docker相关的组件负责发送和接收Docker请求

Containerd相关的组件负责管理容器的生命周期

runc负责真正意义上创建和启动容器

12. 剖析Docker的网络实现和Libnetwork的底层原理

Docker定义的网络模型称为是Container Network Model

  • 沙箱:代表一系列网络堆栈的配置,其中就包括了路由信息、网络接口等网络资源的管理

  • 接入点:代表容器的网络接口,接入点的实现通常是Linuxveth设备对

  • 网络:一组可以互相通信的接入点,它将多接入点组成一个子网,并且多个加入点之间可以相互通信

基本网络模式

  • null空网络,可以构建一个没有网络接入的容器环境,保障数据安全

出于安全的考虑,比如说计算任务,就相当于一个没有联网的电脑,在这种模式下,Docker除了为容器创建了Net Namespace外,没有创建任何的网卡、IP地址、路由等网络配置

docker run --network=none busybox
  • container网络模式,可以将两个容器放在同一个网络命名空间内,但是要注意,其他资源依然是隔离的

  • bridge桥接模式,可以打通容器和容器之间的网络通信的需求

使用这个网络模式可以实现容器和容器之间的互通,可以从一个容器直接通过容器IP访问到另外一个容器,在容器内启动的业务,可以从主机直接请求

veth:是Linux中的虚拟设备接口,都是成对出现的,可以用来连接虚拟网络设备,比如说veth可以用来连通两个Net Namespace,从而使得两个Net Namespace之间可以互相访问

Linux bridge:是一个虚拟设备,用来连接网络的设备,可以用来转发两个Net Namespace中的流量

  • host主机网络模式,可以让容器内的进程共享主机网络,从而监听或者修改主机网络

Docker容器中的进程直接共享主机的网络配置,可以直接使用

13. Docker卷与持久化的底层原理

容器按照业务类型,总体可以分成:

  • 无状态的:数据不需要被持久化
  • 有状态的:数据需要被持久化

的本质是文件或者目录,它可以绕过默认的联合文件系统,直接以文件或者目录的形式存在于宿主机上

docker run -d --name=nginx --miunt
source = mybolume,
target=/usr/local/nginx/html
nginx

容器内产生的日志需要一个专门的日志采集程序去采集日志内容

  • 第一步:创建一个共享日志的数据卷
docker volume create log-vol
  • 第二步:启动容器,并且挂载
docker run -d --name=log-producer --mount
source = log-vol,
target=/tmp/log
busybox

启动消费者容器

docker run -it --name consumer --volumes-from log-producer busybox

怎么实现的?

在创建Docker卷的时候,Docker会将卷的数据全部放在/var/lib/docker/volumes目录下,并且在每个对应的卷目录下创建一个_data目录,然后将_data目录绑定到容器中

因此,在容器中操作卷中的目录,就是在操作这个目录(/var/lib/docker/volumes/_data)

volumes和bind mount有什么区别?

  • bind mount的src是可以由用户来指定的,而volumes则是由/var/lib/docker/volumes/下管理的
  • bind mount为中的src为空的时候,它会覆盖dest,而volume则是会保留原来dest中的内容
  • bind mount可以挂载文件或者目录,而volume只能挂载目录

14. 什么是联合文件系统?

联合文件系统(Unionfs):是一种分层的轻量级文件系统,它可以将多个目录联合挂载到同一个目录下,从而形成一个单一的文件系统,比如说,你电脑上插入了多个U盘,这各个U盘的文件系统都是各不相同的,如果你想要各自操作这些文件系统,那么就可以建立一个联合文件系统,将这多个U盘中的目录挂载到Linux的目录上,从而形成了联合文件系统

联合文件系统Docker镜像和容器的基础,因为它可以使得Docker可以把镜像做成一个分层的结构,从而使得镜像的每一层可以被共享

每一个目录在AUFS中都被叫做是分支,在Docker中称之为层,最终呈现给用户的则是一个普通的单层文件系统,我们将多层以单一层的方式呈现出来的过程叫做联合挂载

当一个镜像没有生成容器的时候,diff文件夹:它会存储镜像的内容,每一层中都存储在以镜像层ID命名的子文件夹中,layers文件夹:存储镜像层关系的元数据,文件的内容为该层镜像的父级镜像的ID,mnt文件夹:联合挂载点目录,当没有生成容器的时候,这个目录会是空的

当一个镜像生成容器之后,diff文件夹:当容器运行的时候,会在diff目录下生成容器层,layers会增加容器层相关的元数据,mnt文件夹:容器的联合挂载点,这和容器中看到的文件内容是一致的

  • 当文件在容器层存在的时候,直接从容器层中读取
  • 当文件在容器层不存在的时候,从镜像层查找该文件,然后读取该文件的内容

修改流程:基于写时复制技术实现的

  • 第一次修改文件,AUFS会触发写时复制操作,AUFS会首先从镜像层复制文件到容器层,然后执行对应的修改操作
  • 删除文件或者目录,AUFS会创建一个特殊的文件或者文件夹,这种特殊的文件或者文件夹会阻止容器的访问

这也就意味着并不会直接删除文件,而是会通过手段来隐藏这个文件夹

15. 通过unshare()在原先进程上进行namespace的隔离

unshre运行在原来的进程上,不需要启动一个新的进程

[root@localhost ~]# unshare --fork --pid --mount-proc bash
[root@localhost ~]# ps aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.3  0.0 115676  2084 pts/0    S    20:27   0:00 bash
root         12  0.0  0.0 155448  1856 pts/0    R+   20:27   0:00 ps aux

这样就打开了一个完全的隔离的空间,然后我们通过,就可以看到两个进程了

docker run -it busybox
ps aux

文章作者: 穿山甲
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 穿山甲 !
  目录