1.docker的网络
1.1 本地docker网络的拓扑结构
docker关心两种类型的网络:单主机虚拟网络和多主机虚拟网络。本地虚拟网络用来提供容器的隔离。多主机虚拟网络构建了一个抽象的覆盖网络,在这个网络中,任何容器相对于网络上的其他容器都是独立的、可路由的IP地址。
本文将深入讨论了单主机虚拟网络,理解docker如何在网络上将容器隔离,相对于那些关心安全的人是非常重要的。同时,那些构建网络应用的人需要知道容器化将会如何影响他们的部署要求。
docker使用操作系统的底层特性构建了一个特殊的、可定制的虚拟网络和主机所连接的网络之间的路由构成。你可以改变这个网络结构的行为,甚至在某些情况下,可以使用启动docker后台进程和容器的命令行的选项来改变网络结构本身。
每个容器各自拥有一个本地回环接口和一个分离的以太网接口,其中以太网接口连接着主机命名空间上的另一个虚拟接口。这两个互连的接口在主机网络栈和每个容器的网络栈之间建立了连接。就像经典的家庭网络,每一个容器都被赋予了一个唯一的私有IP地址,从外部的网络不能直接连接到该私有IP。网络连接需要经过 docker 网桥接口路由到另一网络,这个网络接口被称为 docker0。你可以把 docker0 想象层家庭中的路由器。为每个容器创建的虚拟接口都会连接到 docker0,这样它们就构成了一个网络。最后,这个网桥接口 docker0 会连接到主机所连接的网络上。
使用 docker 命令行工具,你可以自定义IP地址、网桥接口 docker0 连接的主机接口、容器之间通信的方式。接口之间的连接决定了容器如何隔离或暴露在网络中。docker使用内核命名空间来创建这些私有的虚拟接口,但是命名空间本身并不提供网络的隔离。网络暴露或者隔离是通过主机的防火墙规则(每一个主流的Linux发行版都运行有一个防火墙)来实现的。docker的命令行选项提供了四种网络容器原型。
1.2 四种网络容器原型
所有的docker容器都要符合这四种原型中的一种。这些原型定义了一个容器如何与其他的本地容器、主机网络进行通信。每一种原型有不同的目的,你可以认为它们拥有不同程度的隔离。当使用docker创建容器时,仔细思考你的目标是非常重要的,然后在不影响目标情况下,尽可能地使用最强力的容器。
四种原型如下:
- Closed 容器
- Joined 容器
- Bridged 容器
- Open 容器
查看容器网络:这儿列出了3个,详细我们后文会说到
[root@docker ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
f179f27492d9 bridge bridge local
613c5f5f5a46 host host local
cff2d4d1b4fa none null local
[root@docker ~]# docker network inspect [ bridge | none | host ]
...
拓扑图:隔离性越强,容器越强力
1.3 Closed 容器
最强大的网络容器意味着不允许任何的网络流量。这一种被称为Closed容器。运行在这一种容器中的进程只能访问本地回环接口。如果进程只需要和本身或者和其他本地进程通信的话,选择这一种是非常合适的。但是,如果容器中有任何进程想访问这种容器不支持的网络时,比如说软件想从互联网下载更新,因为进程不能访问互联网,因此使用这种原型就是不合适的。
docker在创建这一种容器时,跳过了创建外部可访问网络接口的步骤。可以在上图中看到,Closed容器和docker网桥接口之间没有任何连接。在这种容器中的程序只能和本地程序进行通信。
对于一个Closed容器,并没有太多的方式来自定义网络配置。尽管这种容器看起来被限制过度了,但是它是四种原型中最安全的,并且也能够被扩展来适应特殊情况。这并不是Docker容器的默认选项,但是在实际生产环境中,在使用其他更脆弱的选项前,你必须找到这么做的充分理由。
创建Closed容器:
~]# docker container run --name bbox -it --rm --network none busybox:latest
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
1.4 Bridged 容器
Bridged 容器放开了网络的隔离程度,因此这种容器入手更加容易。这种原型可定制性最高,并且被认为是最佳实践。Bridged 容器拥有两个接口(总是成对出现),一个是私有的本地回环接口,另一个私有接口通过网桥(docker0桥)连接到主机的其他容器,相当于把一个接口放在docker0桥上。
查看docker0桥上的接口:
~]# yum install -y bridge-utils
~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242b5bbb70b no veth76efb60
veth7f8a506
注:从结果可以看出,这儿启动了两个容器
创建Bridged容器:
~]# docker container run --name bbox --rm --network bridge -it busybox:latest #不指定network,默认为bridge
/# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
16: eth0@if17: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
可以看到,上面列出了两个接口:一个以太网接口和一个本地回环接口。输出结果包括IP地址、子网掩码、最大传输单元(MTU)和不同接口的权值。而–network 默认指定为bridge模式,所以我们没有特殊指定,所创建的容器都是Bridged容器。
自定义域名解析:
域名系统(DNS)是一种能够将主机名映射成IP地址的协议。使用这种协议,客户端就能从某一个IP地址的依赖中解耦出来,转而依赖于一个固定的主机名,不管主机的IP地址如何改变,主机名都会负责映射到这个IP。一个改变对外通信的最基础方式就是为IP地址创建名字。
- 设置容器主机名
~]# docker container run --hostname www.test.com ...
注:不设置主机名将会使用容器id做为主机名
- 设置容器主机名与ip映射(写入容器hosts文件)
~]# docker container run --add-host www.test.com:172.17.0.2 ...
注:直接将主机名与ip写入hosts文件,该参数可多次使用
- 指定容器DNS服务器(写入容器resolv.conf)
~]# docker run --dns 8.8.8.8 [--dns...]...
注:不设置dns地址,将会使用宿主机dns地址,该参数可多次使用
注:
1.值必须是IP 地址。因为容器需要一个DNS服务器来查找某一个名字的IP地址。
2. --dns=[] 选项可以被使用多次来设置多个DNS服务器(防止一个或者多个服务器不可用)
3. --dns=[] 选项可以在启动后台进程Docker daemon时进行设置。这么做之后,这些DNS服务器会默认配置到每一个容器上。如果跟后台程序相关的容器依旧运行,这时候你停止该后台程序并且修改默认的DNS服务器,当你重新启动该后台程序时,运行中的容器依旧会保留老的DNS服务器设置。你需要重新启动这些容器来让DNS服务器改动生效
- 指定一个DNS查找域
~]# docker run --dns-search docker.com --rm busybox:latest
这个查找域就像host名的一个默认后缀。当该选项被设置,在查询时,任何不包括已知顶级域名(如.com或.net)的主机名都会自动加上该后缀。
开放对容器的访问:
Bridged容器在默认情况下不能被主机外部网络访问。容器被主机的防火墙保护了起来。默认的网络拓扑结构没有提供任何从主机外部接口到容器接口的路由。这意味着想要从主机外部访问到容器是不能的
如果不能通过网络访问容器,那么容器的功能会变得非常有限。所以docker run 命令提供了一个 -p(–publish=[])选项,它能够在主机网络栈上的端口和容器端口之间创建映射关系。映射的格式有如下四种:
- 将容器端口绑定到主机所有接口的一个动态(随机)端口上
~]# docker container run -p 3333
- 将容器端口绑定到主机所有接口的某一个具体端口上
~]# docker container run -p 3333:6666 #宿主机端口:容器端口
- 将容器端口绑定到指定 IP 地址的主机接口的一个动态(随机)端口上
~]# docker container run -p 192.168.179.110::6666 #宿主机ip::容器端口
- 将容器端口绑定到拥有指定 IP 地址的主机接口的一个具体端口上
~]# docker container run -p 192.168.179.110:3333:6666 #宿主机ip:宿主机端口:容器端口
以上的每一个命令都会创建从主机接口的端口到容器接口的端口的路由。不同的格式提供了不同程度的粒度和控制。当你想要提供多个映射关系时,这个选项同时也能设置多次
除此之外,docker run 命令提供了另外一个可替代的方式来打开网路通道。如果你能够接受主机上动态或者短暂的端口,那么你可以使用-P (–publish-all)选项。这个选项会告诉Docker daemon 去创建端口映射关系,效果类似于-p 命令的第一种格式作用于容器所有的端口,将容器的端口都暴露出去。镜像自带了一组开放端口,一方面为了简单实用,另一方面可以提醒使用者容器中的服务监听了哪些端口
example:例如nginx服务 开放了80端口,如下两个命令效果相同
~]# docker container run --name test -p 80 nginx:latest
~]# docker container run --name test -P nginx:latest
若我们还想要额外开放8080端口怎么办?使用 --expose
~]# docker container run --name test -P --expose 8080 nginx
查看容器开放端口:
~]# docker container port CONTAINER_NAME
1.5 Joined 容器
Joined容器隔离程度低于Bridged容器。Joined容器共享一个网络栈(即拥有自己的mount,user,pid 这三个名称空间,共享IPC,net,UTS 这三个名称空间),在这种情况下,容器之间没有任何的隔离。这意味着更少的控制和安全。尽管这不是最安全的原型,但它是第一个打破容器之间界限的。
创建Joined容器:
~]# docker container run -it --name b1 --network bridge --rm busybox:latest #首先启动一个容器
~]# docker container run -it --name b2 --network container:bbox1 --rm busybox:latest #将新容器添加进bbox1
以上两个命令将会创建两个共享相同网络接口的容器。因为第一个命令创建了一个Bridged容器,因此第二个容器将共享网络接口。-- network选项中容器的值决定了新容器要和哪一个容器进行连接,容器名字或者ID都能识别容器的身份。
在这种情况下的容器维持了另一种形式的隔离。它们各自维持有不同的文件系统、不同的内存等,但是它们的确共享了同一个网络组件。这可能听起来令人有些担心,但是这种容器的确是非常有用的。
适用环境:
- 当你想要不同容器上的程序通过本地回环接口进行通信时,可使用Joined容器。
- 当一个容器中的程序将要改变Joined网络栈,而另外一个程序将要适用那个被改变的网络栈时,可使用Joined容器。
- 当你想要监控另外一个容器中某个程序的网络流量时,可使用Joined容器。
1.6 Open 容器
Open容器非常的危险。他没有网络容器,并且对主机网络有完全的访问权。包括对重要主机服务的访问权。Open容器没有提供任何隔离,当你没有其他选择时它才应该被考虑。Open容器就像是Joined容器的更近一步开放,直接共享宿主机的网络空间。
创建Open容器:
docker run --name test --rm --net host busybox:latest
运行这个命令会创建一个Open容器,并且没有任何的网络隔离(但仍然有其他名称空间隔离,如mount,user,pid等)。当你在这个容器中执行ip addr命令时,你可以看到所有主机上的网络接口,包括docker0。但Open容器也并不是没有好处,比如我们要在宿主机上启动httpd服务,我们需要安装配置,启动服务等等一系列操作,而使用Open容器直接docker run即可,且当宿主机发生故障,可以快速切换到其他物理机继续提供服务。