Loading

Docker卷和网络配置——Docker测试

卷简要介绍

ADD和COPY命令都可以将某一个目录或文件复制到Docker镜像中。不管怎样,这个文件最终都会成为镜像中的一个层。

卷则不同,卷是将一个宿主机上的目录挂载到容器中,你可以将一个目录挂载到多个容器中,使多个容器可以共同访问这个目录,并且该目录不受分层限制。

下面命令将宿主机当前目录下的notes文件夹挂载到容器的/root/notes

❯ docker run --name ubuntu1 -v $PWD/notes:/root/notes -it ubuntu /bin/bash
root@6dd21418356d:/# cd
root@6dd21418356d:~# ls notes
network_config.md

同样的手段,创建一个ubuntu2容器,并向notes中写入一个文件

在之前的ubuntu1中查看

在宿主机中查看

卷可以让我们的开发和容器分离,我们可以在一个容器或者宿主机中开发,然后让另一个容器提供服务,它们都基于同一个文件夹来工作。

网络

网络驱动(Network Drivers)

使用驱动,可以让Docker具有一个可插拔式的网络子系统!默认情况下,Docker具有几个提供核心网络功能的驱动:

  1. bridge:默认网络驱动,如果你不指定一个驱动,这个就是你创建的默认网络类型。Bridge网络通常在你运行在独立容器中的应用需要通信时使用
  2. host:对于独立的容器,移除容器和Docker宿主间的隔离,直接使用宿主网络。
  3. overlay:Overlay网络将多个Docker守护进程连接在一起,并启动集群服务(swarm services,这里不知道怎么翻译)来互相通信。你也可以使用overlay网络来促进集群服务和独立容器之间的通信,或在不同docker守护进程间的两个独立容器之间的通信。这个策略消除了在这些容器间进行OS层面路由的需要。
  4. ipvlan:为用户提供了IPv4和IPv6地址的完全控制。The VLAN driver builds on top of that in giving operators complete control of layer 2 VLAN tagging and even IPvlan L3 routing for users interested in underlay network integration. See IPvlan networks.
  5. macvlan:允许你为容器设置一个mac地址,使它以一个物理设备的身份出现在你的网络中。The Docker daemon routes traffic to containers by their MAC addresses. Using the macvlan driver is sometimes the best choice when dealing with legacy applications that expect to be directly connected to the physical network, rather than routed through the Docker host’s network stack. See Macvlan networks.
  6. none:取消所有网络。通常与自定义网络驱动程序一起使用,none对于集群服务不可用。
  7. 网络插件(Network Plugins):你可以在Docker中安装并使用第三方网络插件,详见DockerHub

网络驱动总结

  • 用户定义bridge网络在你希望一个Docker宿主上的多个容器之间通信时是最好的选择
  • host网络在你不希望容器与Docker宿主的网络隔离时是最好的选择,but you want other aspects of the container to be isolated.
  • overlay网络在你需要一些运行在不同Docker宿主中的容器互相通信,或者使用集群服务的多个应用之间互相通信时是最好的选择
  • Macvlan网络在你从虚拟机迁移过来,或者需要容器看起来像网络上的物理主机一样,每一个容器具有唯一的MAC地址时是最好的选择。
  • 第三方网络插件允许你将Docker与其它特定网络栈整合。

使用Bridge网络

后面我们将称Bridge网络为桥接网络,称Bridge为网桥。

用网络术语来说,一个桥接网络是一个在网段之间转发流量的链路层设备。一个网桥可以是一个硬件设备或一个运行在主机内核中的软件设备。

而用Docker的术语来说,桥接网络使用软件网桥,允许容器连接到同一个桥接网络中,同时提供与未连接到该网桥的容器之间的隔离。Docker网桥驱动自动地在宿主中安装了规则,从而使得不同桥接网络之间的容器不能直接相互通信。

当你启动Docker,一个默认桥接网络(也称为bridge)会自动被创建,一个新建的容器会连接到它除非它指定了其它的。你也可以创建自定义的桥接网络。用户自定义桥接网络优先于默认桥接网络

下面对于网络的介绍都基于bridge网络,其它网络在官方文档中也都有介绍,不过目前我还用不到。

用户自定义桥接网络和默认桥接网络的区别

  • 用户自定义桥接网络提供容器间的自动DNS解析
    默认桥接网络下的容器只能通过IP地址互相连接,除非你使用--link选项(该选项被认定为遗留选项,不应该使用),但在用户自定义桥接网络中,容器可以通过容器名或别名来互相解析。

    想象一个具有web前端和数据库后端的应用,如果你给你的容器命名为webdb,web容器可以通过db来连接到db容器,不管应用程序栈运行在什么docker宿主上。

    如果你使用默认桥接网络,你需要双向使用--link,或者编辑它们的/etc/hosts,但当容器重启,IP改变,/etc/hosts并不会更新,所以这可能导致一些难以显现的bug。

  • 用户自定义桥接网络提供更好的隔离
    所有没有指定--network的容器,都会被附加到默认桥接网络上,这可能很危险,无关的栈/服务/容器间也能相互通信

    使用用户自定义桥接网络,只有被附加到该网络中的容器间才可以相互通信

  • 容器可以动态的与用户定义的网络连接或分离
    在容器的生命周期间,你可以动态的将它附加到一个用户自定义网络中,或者从中分离,而要从默认网桥中删除容器,需要停止容器并使用不同的网络选项重新创建。

  • 每一个用户自定义网络都创建了一个可配置的网桥
    你也可以配置默认网桥,但是所有使用该网桥的容器都会使用相同的设置,如MTU和iptables规则。并且,配置默认网桥的操作在Docker外发生,需要重启Docker。

  • 在默认网桥上连接的容器共享环境变量
    在以前,在两个容器之间共享环境变量的唯一做法就是使用--link标志来连接它们,但这种类型的变量共享在用户自定义网络上是不可能的。不过不管怎样,你还是有一些更好的方法来共享环境变量。下面是一些思路:

    • 多个容器可以使用一个Docker卷来关在一个包含共享信息的文件或目录
    • 多个容器可以使用docker-compose共同启动,组合文件可以定义共享变量
    • 你可以使用集群服务来代替独立容器,并且利用共享的secretsconfigs
      连接到同一个用户定义桥接网络的容器实际上对彼此暴露了所有端口。对于一个要在不同网络的容器或非Docker主机上也可以访问的端口,你必须使用-p--publish来发布它。

管理用户自定义网桥

使用docker network create命令来创建一个用户自动逸桥接网络

你可以定义子网,IP地址范围,网关和其它选项。详情请看docker network createdocker network create --help命令的输出。

使用docker network rm命令可以移除一个用户自定义桥接网络,如果当前有容器连接到该网络,先取消连接它们。

当你创建或移除一个用户自定义网桥或者使一个容器连接或从一个用户自定义网桥断开时,Docker使用工具来指定操作系统去管理底层的网络基础设施(比如在Linux上添加或移除一个桥接设备或配置iptables规则)。这些细节应该被当成实现细节,让Docker来帮你管理它们吧。

连接一个容器到用户自定义网桥

当你创建一个新的容器,你可以指定一个或多个--network标志。这个示例连接一个Nginx容器到my-net网络。并且它发布了容器中的80端口到Docker宿主的8080端口。所以外部客户端可以访问那个(8080)端口。任何其它的连接到my-net的容器都可以访问my-nginx容器开放的所有端口。

docker create --name my-nginx \
  --network my-net \
  --publish 8080:80 \
  nginx:latest

如果你想连接一个运行中的容器到一个已经存在的用户定义网桥,应该使用docker network connect命令。下面的命令连接一个已经运行的my-nginx容器到一个已经存在的my-net网络。

docker network connect my-net my-nginx

断开一个容器与用户自定义网桥的连接

docker network disconnect my-net my-nginx

打开Docker容器到外界的转发

默认情况下,来自连接到默认桥接网络的容器的流量不会转发到外部世界。想要打开这个转发,你应该改动两处设置。它们不是Docker命令而且它们会影响Docker宿主的内核。

  1. 配置Linux内核允许IP转发
    sysctl net.ipv4.conf.all.forwarding=1
    
  2. 改变iptables的政策,FORWARD政策从DROP改为ACCEPT
    sudo iptables -P FORWARD ACCEPT
    

这两个设置在重启后会失效,所以你可能会需要将它们添加到一个启动脚本中。

使用默认桥接网络

默认桥接网络被认为是Docker的遗留细节并且不推荐在生产中使用。配置它是一项手动操作并且存在技术缺陷

Docker和iptables

在Linux上,Docker通过掌管iptables规则来提供网络隔离。这是一个实现细节,所以你不应该去修改Docker插入到你的iptables中的规则,如果你想在Docker的管理之上拥有自己的政策,那么这可能会对一些你想要做的事产生一些影响。
如果你在一个暴露在互联网上的宿主机中运行Docker,你可能会想要一些iptables政策来阻止对容器或系统中运行的其它服务的未认证的访问。这个页面向你展示如何做到,并且有什么需要注意的事项。

在Docker的规则之前添加iptables政策

Docker安装了两个自定义的iptables链,一个是DOCKER-USER,一个是DOCKER,并且确保到来的数据包总是会优先检查这两个链。

所有Docker的iptables规则都将被添加到DOCKER这条链中,去不要手动的控制这条链,如果你需要添加在Docker规则之前被加载的规则,把它们添加到DOCKER-USER链中。这些规则会自动的在任何Docker创建的规则之前被应用。

手动或被其它基于iptables的防火墙添加到FORWARD链中的规则在这些链之后被评估(evaluated)。这意味着如果你通过Docker暴露一个端口,不论你的防火墙配置了什么规则,这个端口都会被暴露。如果你想要这些规则被应用,你必须将这些规则添加到DOCKER-USER中。

限制到Docker主机的连接

默认情况下,所有外部源IP都被允许访问Docker宿主,为了仅仅允许一个特定IP或者网络来访问容器,添加一个否定规则到DOCKER-USER的顶端,比如,下面的规则限制所有的IP的外部访问,除了192.168.1.1

iptables -I DOCKER-USER -i ext_if ! -s 192.168.1.1 -j DROP

请注意,你需要把ext_if改编成你的机器上实际的外部接口(external interface)。你也可以允许来自一个源子网的连接,下面的规则仅仅允许192.168.1.0/24的访问:

iptables -I DOCKER-USER -i ext_if ! -s 192.168.1.0/24 -j DROP

最后,你可以使用--src-range定义一个IP地址范围(记得当你使用--src-range--dst-range时将-m iprange一并加入):

iptables -I DOCKER-USER -m iprange -i ext_if ! --src-range 192.168.1.1-192.168.1.3 -j DROP

你可以将-d--dst-range-s--src-range混合使用来控制源和目的。比如,如果Docker守护进程正在监听192.168.1.9910.1.2.3,你可以定制特定于10.1.2.3的规则,然后让192.168.1.99保持开启。

iptables的更多内容已经超出了这个主题所讨论的范围,更多内容请看Netfilter.org HOWTO

路由上的Docker

Dokcer还将FORWARD链上的政策设为DROP。如果你的Docker宿主扮演着路由器的角色,这将导致你的路由器不转发任何流量,如果你想要你的系统继续路由器的功能,你需要显式的添加ACCEPT规则到DOCKER-USER链中。

iptables -I DOCKER-USER -i src_if -o dst_if -j ACCEPT

阻止Docker管理iptables

可以在Docker引擎的配置文件/etc/docker/daemon.json中将iptables设为false,但是这个选项不适用于大部分用户。不可能完全地阻止Docker创建iptables。设置iptablesfalse将很有可能破坏Docker引擎中的容器网络。

对于想要构建Docker运行时到它们的应用中的系统整合者,请看mobyproject

设置容器的默认绑定

默认情况下,Docker守护进程将在0.0.0.0上暴露端口,即主机上的任何地址。如果你想改变这个行为,仅仅在某个内部IP地址上暴露端口,你可以使用--ip选项去指定不同的IP地址。 However, setting --ip only changes the default, it does not restrict services to that IP.

与Firewalld整合

如果你运行Docker20.10.0或更高的版本,并且在启用了--iptables的系统上使用了firewalld,Docker会自动创建一个名为dockerfirewalld区域(zone)并插入所有它创建的网络接口(比如docker0)到docker区域中以允许无缝联网。

考虑运行下列的firewalld命令从区域中移除docker接口。

# Please substitute the appropriate zone and docker interface
$ firewall-cmd --zone=trusted --remove-interface=docker0 --permanent
$ firewall-cmd --reload

重启dockerd守护进程,插入接口到docker区域中。

实例:使用Docker构建Sinatra应用程序

这里我因为从DockerDesktop换到直接在WSL下安装的Docker,所以我正好把之前的镜像打包上传到DockerHub了,也能方便一些。

运行下面两条命令:

docker pull yudoge/sinatra
docker pull yudoge/redis

这其中sinatra是使用nginx的一个Web应用,它在4567端口开放,如果你向它的/json路径下发送一个POST请求,请求体以URL参数格式编码,那么它就会返回给你你的参数的JSON形式。redis开放了一个redis服务器,在6379端口,sinatra每次转换一个URL参数到JSON都会向数据库中记录此次转换。

下面,我们先创建一个自定义桥接网络,你可以通过docker network inspect来查看该网络的细节。

❯ docker network create sinatra-app
d7dfc96210894e5d1090038a9e7b72385ede372072f2a521f456eb0708d85c56
1 ❯ docker network inspect sinatra-app
[
    {
        "Name": "sinatra-app",
        "Id": "d7dfc96210894e5d1090038a9e7b72385ede372072f2a521f456eb0708d85c56",
        "Created": "2022-04-30T14:43:26.8648256+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

通过查看当前系统中所有的网络接口,能够看出,Docker默认情况下的桥接网络接口是docker0,它的IP是172.17.0.1,我们新创建的是br-d7dfc9621089,它的IP是172.18.0.1。这里要注意的是,如果你使用DockerDesktop,你应该看不到这些网络接口,它好像使用的是和Docker引擎不太一样的技术,具体我也不知道,但是除了看不到接口之外我也没发现使用上有什么大的区别。

启动yudoge/sinatra容器,这里首先使用了--name来为容器命名,--net来指定容器的网络,-p来向宿主机暴露端口以及映射端口,-v来为容器添加卷,这个宿主机中的$PWD/webapp_redis文件夹是sinatraweb应用的源码,这个代码在该书的仓库中:webapp_redis,在下载下来之后你可能需要将该文件夹赋予执行权限。

docker run --name web --net=sinatra-app -p 4567:4567 -v $PWD/webapp_redis:/opt/webapp yudoge/sinatra

现在查看主机中的所有网络接口,发现多了一个:

每当你创建一个容器,Docker就会为你创建这么一个虚拟接口,这个虚拟接口的一端插在容器中,另一端插在宿主机上我们建立的Docker虚拟网络中,这里就是sinatra-app,默认情况下插在docker0上。这样,Docker中的虚拟网络都作为宿主机的接口,这使得宿主机可以和Docker容器的每一个虚拟网络相连,而容器又通过将它的eth0连接到veth*来与这个虚拟网络相连。最终,你的Docker宿主机可以通过Docker虚拟网络和任何一个Docker容器相连,而处于不同Docker虚拟网络中的容器互相隔离。

这里测试一下web容器是否能正常工作:

下面我们启动redis容器,命名为db--protected-mode no作为CMD命令传入,让Redis关闭保护模式,以在远端连接并执行命令:

docker run --name db \
  --net=sinatra-app \
  -p 6379:6379 \
  yudoge/redis --protected-mode no

测试db是否启动成功

通过该容器的IP地址也可以连接

而下面我们通过docker exec -it web /bin/bash进入web的bash时,我们发现我们可以通过处于同一个Docker网络中的容器名来访问另一台容器,这样当容器下线并重新上线后IP发生变化时,我们也能通过容器名来解析出它的IP

实际上,webapp_redis中的代码也是这么访问的redis容器:

现在我们通过浏览器进入web容器提供的网站,这里因为我有一层WSL,所以你权当192.168.50.2localhost就好,不必在意它。

我们向/jsonpost了一些URL参数,它给我们转成了JSON并正常返回。

下面我们通过GET请求访问/json,看看上次的记录是否被存到了数据库中,并正确查找返回。

参考

posted @ 2022-04-30 15:27  yudoge  阅读(246)  评论(0编辑  收藏  举报