Loading

容器技术:网络管理、存储管理(四)

11. 网络管理

11.1 解析Docker与iptables

Docker 能为我们提供很强大和灵活的网络能力,很大程度上要归功于与 iptables 的结合。在使用时,你可能没有太关注到 iptables 的作用,这是因为 Docker 已经帮我们自动完成了相关的配置。


在Docker 18.05.0(2018.5)及之后的版本中,提供如下4个chain:

  • DOCKER
  • DOCKER-ISOLATION-STAGE-1
  • DOCKER-ISOLATION-STAGE-2
  • DOCKER-USER

先查看NAT表,一个默认的bridge网络和新建的bridge网络会自动在NAT表中的POSTROUTING链添加规则,执行MASQUERADE动作(这里就简单的将它理解为 SNAT就可以),也就是该容器网络里出来的数据都进行源地址转换。

image-20230408212915695

image-20230408212241819


再看看filter表,添加了四个自定义链。

image-20230408214617327


更加详细的filter表规则及动作

image-20230408215344107


DOCKER-USER 链

第一条是 -A FORWARD -j DOCKER-USER 这表示流量进入 FORWARD 链后,直接进入到 DOCKER-USER 链;

最后一条 -A DOCKER-USER -j RETURN 这表示流量进入 DOCKER-USER 链处理后,(如果无其他处理)可以再 RETURN 回原先的链,进行后续规则的匹配。

这其实是 Docker 预留的一个链,供用户来自行配置的一些额外的规则的。


所以例子来了,Docker 默认的路由规则是允许所有客户端访问的, 如果你的 Docker 运行在公网,或者你希望避免 Docker 中容器被局域网内的其他客户端访问,那么你需要在这里添加一条规则。 比如, 你仅仅允许 10.0.0.1 访问,但是要拒绝其他客户端访问:

iptables -I DOCKER-USER -i <net interface> ! -s 10.0.0.1 -j DROP

DOCKER-ISOLATION-STAGE-1/2 链

*filter
...
:DOCKER - [0:0]
:DOCKER-ISOLATION-STAGE-1 - [0:0]
:DOCKER-ISOLATION-STAGE-2 - [0:0]
:DOCKER-USER - [0:0]

-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1

-A DOCKER-ISOLATION-STAGE-1 -i br-b23805eab40e ! -o br-b23805eab40e -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o br-b23805eab40e -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
...

这两条链主要是分两个阶段进行了桥接网络隔离。从DOCKER-USER链出来,匹配到下一条DOCKER-ISOLATION-STAGE-1链。就拿docker0网桥来说,如果数据包从docker0网桥去往其他接口则进入DOCKER-ISOLATION-STAGE-2链。进入该链如果匹配上了,那就意味着数据包始于一个桥接网络的网桥,目的是另外一个桥接网络的网桥,就会执行DROP动作,丢弃掉数据包。接着就匹配到该链最后RETURN动作,返回父链。


DOCKER 链

这是 Docker 中使用最为频繁的一个链,也是规则最多的链,但它却很好理解。 通常情况下,如果不小心删掉了这个链的内容,可能会导致容器的网络出现问题,手动修复下,或者重启 Docker 均可解决。


下面启动一个新容器并指定端口映射,对比新增容器前后的iptables规则的变化。

image-20230409101356460


新增条目如下

#filter表
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT

#nat表
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 80 -j MASQUERADE
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 4444 -j DNAT --to-destination 172.17.0.2:80

filter 表中新增的这条规则表示:在自定义的 DOCKER链中,对于目标地址是 172.17.0.2 且不是从 docker0 进入的但从 docker0出去的,目标端口是 80的 TCP 协议则接收。简单点来说就是放行通过 docker0流出的,目标为 172.18.0.2:80 的 TCP 协议的流量。

Tips:该网络其他容器访问172.17.0.2的80端口就匹配上了这条啦!


nat表中这两条规则的表示:为 172.17.0.2 上目标端口为 80 的流量执行 MASQUERADE 动作(这里就简单的将它理解为 SNAT 也可以)。

最好的猜测是当你有iptables的POSTROUTING表默认为DENY任何与规则不匹配的数据包时,规则是修复边缘情况,这允许从容器到映射端口上的连接。 在正常操作中,规则不起作用。 (网上查到的回到,说实话我也没看懂!暂且略过这种边缘情况吧~)


在自定义的 DOCKER 链中,如果入口不是 docker0 并且目标端口是 4444 则进行 DNAT 动作,将目标地址转换为 172.17.0.2:80 。简单点来说,这条规则就是为我们提供了 Docker 容器端口转发的能力,将访问主机本地 4444 端口流量的目标地址转换为 172.17.0.2:80 。


11.2 bridge网络

11.2.1 birdge网络实现
11.2.1.1 bridge网络原理

Docker安装时会创建一个命名为docker0的Linux bridge。如果不指定--network,创建的容器默认都会挂到docker0上。

image-20230408143609490


两个网络接口veth1388366、vethd9fcaa7被挂到了docker0上。

image-20230408143646543

image-20230408143914949


查看bridge网络的配置信息,原来bridge网络配置的subnet就是172.17.0.0/16,并且网关是172.17.0.1。这个网关在哪儿呢?其实就是docker0。

image-20230408144620993


查看容器中的网络情况,与上述配置信息是一致的。

image-20230408144905345


11.2.1.2 理解veth pair

其实容器中的eth0与宿主机的veth虚拟网卡是一对pair设备。怎么理解呢?veth pair是一种成对出现的特殊网络设备,可以把它们想象成由一根虚拟网线连接起来的一对网卡,网卡的一头(eth0@if58)在容器中。

Tips:57是容器中eth0接口的index,58是它成对的veth设备的index。

image-20230408153115563

另一头(veth1388366)挂在网桥docker0上,其效果就是将eth0@if58也挂在了docker0上。

image-20230408153336290


11.2.2 理解容器间通信
11.2.2.1 单主机的同一bridge网络

下面我通过创建一个新的bridge的网络,然后创建两个容器指定该bridge网络并运行,学习一下这两个容器背后通信的原理。


创建docker自定义网络,bridge方式。已经能看到br-b23805eab40e就是新创的。

#指定bridge方式,指定子网和网关,如果不指定就是随机生成。最后是yinjay-bridge是该网络的名称
docker network create --driver bridge --subnet 10.10.10.0/24 --gateway 10.10.10.1 yinjay-bridge

image-20230408160217377

image-20230408160533557


再查看ifconfig情况,虚拟网卡设备也起来了。

image-20230408160555225


下面创建两个容器并运行(指定网络),已经挂在这个新创建的网络上。

image-20230408161251045


同时也会创建veth pair设备

image-20230408161332986


查看veth pair设备对应关系

image-20230408164725725

image-20230408164944804


test-01容器ping通test-02容器

image-20230408165444185


背后数据流是这样子的

image-20230408170850887

  1. test-01容器首先没有test-02的IP对应MAC地址。那就先要构造一个ARP请求,那数据包该往哪走?就是通过目的IP进行路由表查询,得知需要从eth0网卡出去。(见上图)
  2. 容器里eth0虚拟网卡对应的pair设备是宿主机上的veth10e6d67虚拟网卡,veth10e6d67虚拟网卡(相当于该容器连线)插在br-b23805eab40e网桥(相当于交换机),此时test-02容器也是以此方式 ”插“ 在br-b23805eab40e(交换机)。
  3. 所以br-b23805eab40e网桥扮演二层交换机的角色,把ARP广播发给其他插在br-b23805eab40e网桥的虚拟网卡上。
  4. 这样test-02容器就收到了这个广播,并将其MAC地址返回给容器1。有了该MAC地址,test-02容器后续就能正常的构造ICMP请求。
  5. test-01容器构造ICMP请求,通过veth pair对端设备直接交给br-b23805eab40e网桥,此时该网桥会根据CAM表查到对应的端口,然后把该数据包转发给test-02容器。这样test-02容器收到后就会响应给test-01容器。

Tips:CAM就是交换机通过MAC地址学习,维护端口和MAC地址的对应表,详细可学习Linux网桥的实现细节!


12. 存储管理

目前 Docker 的数据类型分为两种,一是数据卷,二是数据容器,数据卷类似于挂载的一块磁盘,数据容器是将数据保存在一个容器上。

假设一个宿主机里面同时启动了4个Nginx容器,一个nginx运行时完整环境有100MB,那么4个Nginx容器会占用多少的磁盘空间呢?

4*100?如果是这样计算的话,那就说明这个四个Nginx容器都占用了独立的空间,每启动了一个Nginx容器都会启用一个小型的Linux系统,这时如果同样的一个软件,我们启动成百上千个,那么每一个都占用独立的空间,这样算下来就会占用很大的磁盘空间。

然而并非如此,请看下面Docker存储原理。


12.1 回顾容器与镜像的关系

image-20230410210752042

容器由最上面一个可写的容器层和多个只读的镜像层组成,添加新数据或者修改现有数据的所有写入容器都存储在容器可写层中。当容器被删除时,可写层也被删除,底层的image保持不变。

所有的变化都存储在容器层中,所以多个容器可以共享同一个底层镜像的访问,同时又能达到数据隔离的效果。


12.2 了解Docker的存储驱动程序

通过docker info查到Docker的Storage Driver的信息,使用的是overlay2来管理镜像层和可写容器层的数据。

image-20230410211230021

存储驱动是overlay2,底层文件系统是xfs,各层数据是存放在/var/lib/docker/overlay2下面。


12.3 overlay2工作原理

overlay2将所有的目录称之为层,它是镜像和容器分层的基础。overlay2将目录下一层叫做LowerDir,上一层叫做UpperDir,将这两层联合挂载后的结果叫做MergedDir。

image-20230410212104192

上面这个图展示了Docker结构和OverlayFS结构的映射关系,镜像层对应着lowerdir,容器层对应着upperdir。我们的容器可写层、镜像层都一起被挂载到Merged目录下。


目前的镜像为空,容器未运行,下面拉取一个镜像查看一下有什么变化。

image-20230410212750466


可以看出这个镜像总共有6层,已拉取到本地。

image-20230410213111966


查看该镜像的详细信息,RootFS明确镜像有六层,再看Data信息,,镜像层对应着LowerDir,容器层对应着UpperDir。我们的容器可写层、镜像层都一起被挂载到MergedDir下。

image-20230410213327707


再看看/var/lib/docker/overlay2/ 目录下也多了6个目录,这就说明这个Nginx镜像被分为了6层。

image-20230410213854522


查看/var/lib/docker/overlay2/l 这个目录,可以看到 l 目录是一堆软连接,把一些较短的随机串软连到镜像层的 diff 目录下,这样做是为了避免达到mount命令参数的长度限制。

image-20230410214329803


通过tree命令将这6个目录全部展开,可以看到目录结构几乎都是一致的,需要重点关注的是diff目录和lower文件。可以看到d488da0fadf4b91e25b1cdb1959b5f05192747a9ce1522d9d7495eb438149da2目录中不存在lower文件,说明它是最底层的,等于是根镜像,即docker pull时下载的第一层。

image-20230410214628566

Tips:diff目录为该镜像层的改动内容!


同时,diff文件夹下的文件,正是Linux文件目录结构。说明在nginx的dockerfile中,肯定有FROM 操作系统。

image-20230410215042845

实例说明:Dockerfile的每一个命令都可能引起了系统的变化,它的每一个变化都会记录一层diff文件。


启动一个容器,看看还有什么变化?

image-20230410220100748


那就先查看一下该容器的详细信息,此部分相比之前查看镜像的详细信息来说,首先尾部c87目录也就是镜像的最顶层也加入在LowerDir中,在容器的详细信息视角,它必须在这,因为是镜像层。

image-20230410220222000


查看/var/lib/docker/overlay2/ 目录,发现新增了两个目录:其中带-init的目录是只读的;没有init的容器目录才是容器的读写目录。(也就是容器读写层)

image-20230410221748600


进入容器层目录,看看里面的结构link和lower文件与镜像层的功能一致,link文件内容为该容器层的短 ID,lower文件为该层的所有父层镜像的短 ID 。diff目录为容器的读写层,容器内修改的文件都会在 diff中出现,merged 目录为分层文件联合挂载后的结果,也是容器内的工作目录。

image-20230410222208346

image-20230410222438736


当我们进入容器创建文件时,文件也会出现在这里。

image-20230410222726113


看看merged目录,可以看到他将lowerdir和upperdir的文件合并在了一起

image-20230410222758905

overlay2将镜像层和容器层都放在单独的目录,并且有唯一 ID,每一层仅存储发生变化的文件,最终使用联合挂载技术将容器层和镜像层的所有文件统一挂载到容器中,使得容器中看到完整的系统文件。


根据镜像启动多个容器,再查看各容器的详细信息,共用该镜像,最后彼此有独立的容器读写层。

image-20230411141714131


就算镜像有130MB,启动了100个,它也不会占用十几G的磁盘空间。一个容器其实就占用了几个k的大小。(容器可写层的大小)

image-20230411141934691


12.4 验证写时复制技术

为了保证容器的修改不会互相影响,Docker采用了写时复制技术。Copy-on-Write特性:当容器启动时,一个新的可写层被加载到镜像的顶部。这一层被称之为“容器层”,容器层之下的都叫做“镜像层”。

所有对容器的改动,无论添加、删除,还是修改文件都只会发生在容器层中。只有容器层是可以写的,容器层下面的所有镜像层都是只读的。

我们在容器中进行操作时:

  • 添加文件。在容器中创建文件时,新文件被添加到容器层中。
  • 读取文件。在容器中读取某个文件时,Docker会从上往下依次在各镜像层中查找此文件。一旦找到,打开并读入内存。
  • 修改文件。在容器中修改已存在的文件时,Docker会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改。
  • 删除文件。在容器中删除文件时,Docker也是从上往下依次在各镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。

只有当需要修改时才复制一份数据,这种特性被称作Copy-on-Write。可见,容器层保存的镜像变化的部分,不会对镜像本身进行任何修改。


现在进入一个容器里面,先查看未修改前文件的inode。

image-20230411142846197


查看镜像层,inode是对上的。

image-20230411143321216


查看该容器的读写层,还没有nginx.conf。

image-20230411143421997


下面往nginx.conf追加内容,再重新查看以上镜像层和容器层的变化。镜像层未改变,容器层就已经多了被修改文件,同时inode也不一样了。

image-20230411145255492

当容器需要修改文件时,他会采用写时复制技术,将镜像的文件复制到自己容器的上层路径下,然后修改。如果下次要用这个文件,如果在容器层有就用容器层的,如果没有就看底层目录镜像层的。

容器无论怎么修改,底层镜像的内容永远不会变,一旦容器移除了,容器修改的所有配置全部丢失,并不会映射到镜像里面,所以就需要docker commit来提交容器变化为一个新的镜像。


12.5 数据存储方式

默认情况下,在容器内创建的所有文件都存储在可写容器层上。这意味着:

  • 当该容器不再存在时,数据不会持续存在,并且如果另一个进程需要数据,则可能很难将数据从容器中取出。容器的可写层与运行容器的主机紧密耦合。您无法轻松地将数据移动到其他地方。
  • 写入容器的可写层需要存储驱动程序来管理文件系统。存储驱动程序提供了一个联合文件系统,使用 Linux
    内核。与使用直接写入主机文件系统的数据卷相比,这种额外的抽象会降低性能 。
  • Docker 有两个选项让容器在主机上存储文件,以便即使在容器停止后文件也能持久保存:volumes和 bind mounts。

image-20230411200249649

Tips:还有一种是tmpfs mounts,挂载仅存储在主机系统的内存中,永远不会写入主机系统的文件系统,但此方式不推荐!


12.5.1 bind mounts

挂载宿主机的一个目录,可以用-v参数指定。-v 宿主机目录:容器目录

image-20230411194453326

Tips:容器目录不可以为相对路径!


如果宿主机不存在的目录,也会自动创建。

image-20230411195127572

image-20230411195243538


12.5.2 Volume

bind mount需要指定host文件系统的特定路径,这就限制了容器的可移植性,当需要将容器迁移到其他host,而该host没有要mount的数据或者数据不在相同的路径时,操作会失败。

volume与bind mount在使用上的最大区别是不需要指定mount源,指明容器挂载点就行了。


下面通过启动一个容器,-v指明挂载点。告诉docker需要一个volume,并将其mount到/usr/share/nginx/html上。通过下图的信息来看,vloume创建在/var/lib/docker/volumes下。

image-20230411211102217

Source就是该volume在host上的目录,这个目录就是mount源。


下面继续研究这个volume,看看里面有些什么东西。volume的内容跟容器原有/usr/share/nginx/html目录下的内容完全一样,这是怎么回事?这是因为挂载点指向的是容器中已有目录的话,原有数据会被复制到volume中。

image-20230411211417229

此时的/usr/share/nginx/html已经不再是由storage driver管理的层数据了,它已经是一个数据卷(data volume)。


宿主机的目录如果为相对路径,通过docker inspect查看容器的存储信息,创建在/var/lib/docker/volumes/目录下就是直接该名称命名。

image-20230411200630839

image-20230411200703437


通过docker volume ls查看现有数据卷的情况

image-20230411211859928

docker volume只能查看docker 管理的volume,还看不到bind mount,同时也无法知道volume对应的容器,这些信息还得靠docker inspect。


bind mount 与 docker managed volume的不同点

image-20230411212057556


12.6 数据共享方式

12.6.1 容器与host共享数据

对于bind mount方式就非常简单,直接把宿主机的某目录或文件直接挂载给容器。如果是docker管理的volume,可以使用docker cp进行数据共享。

image-20230411212553791


12.6.2 容器之间共享数据

第一种方法是将共享数据放在bind mount中,然后将其mount到多个容器。另一种在容器之间共享数据的方式是使用volume container。volume container是专门为其他容器提供volume的容器。它提供的卷可以是bind mount,也可以是docker managed volume。


下面创建一个volume container,一个bind mount,一个是volume。运行三个容器可以通过--volumes-from使用vc_data这个volume container。

image-20230411214205229


检查bind mount的数据共享,没问题!

image-20230411214354030


检查volume的数据共享,没问题!

image-20230411214707473

posted @ 2023-09-16 12:53  YinJayChen  阅读(121)  评论(0编辑  收藏  举报