docker——网络配置
一、网络启动与配置参数
Docker启动时会在主机上自动创建一个docker0虚拟网桥,实际上是一个Linux网桥,
可以理解为一个软件交换机,它会在挂载其上的接口之间进行数据转发。
同时,Docker随机分配一个本地未占用的私有网段(在RFC1918中定义)中的一个地址给docker0接口。
当创建一个Docker容器时,同时会创建一对veth pair接口,
当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包。
这对接口一端在容器内,即eth0,另一端在本地并被挂载到docker0网桥,
名称以veth开头,通过这种方式,主机可以跟容器通讯,容器之间也可以相互通信。
如此一来,Docker就创建了在主机和所有容器之间的一个虚拟共享网络。
下面是Docker网络相关的参数命令,其中有些命令选项只有在Docker服务启动的时候才能配置,而且不能马上生效。
-b BRIDGE or --bridge=BRIDGE 指定容器挂载的网桥
--bip=CIDR 定制docker0的掩码
-H SOCKET ... or --host=SOCKET... docker服务端接收命令的通道
--icc=true|false 是否支持容器之间进行通信
--ip-forward=true|false 启用net.ipv4.ip_forward
--iptables=true|false 禁止Docker添加iptables规则
--mtu=BYTRS 容器网络中的MTU
下面两个命令既可以在启动服务时指定,也可以Docker容器启动时(docker run)指定。
在docker服务启动的时候指定则会成为默认值,后续执行docker run时可以覆盖设置的默认值。
--dns=IP_ADDRESS 使用指定的DNS服务器
--dns=search=DOMAIN 指定DNS搜索域
最后这些选项只能在docker run执行时使用,因为他是针对容器的特性内容:
-h HOSTNAME or --hostname=HOSTNAME 配置容器主机名
--link=CONTAINER_NAME:ALIAS 添加到另一个容器的连接
--net=bridge|none|container:NAME_or_ID|host|user_defined_network 配置容器的桥接模式
-p SPEC or --publish=SPEC 映射容器端口到宿主主机
-P or --publish-all=true|false 映射容器所有端口到宿主主机
其中 --net选项支持五种模式,如下所示:
--net=bridge 默认选项,为容器创建一个独立的网络命名空间,分配网卡、ip地址等网络配置。
并通过veth接口对将容器挂载到一个虚拟网桥(默认为docker0)上。
--net=none 为容器创建一个独立的网络命名空间,但不进行网络配置,即容器内没有创建网卡、IP等
--net=container:NAME_or_ID 意味着新创建的容器共享指定的已存在容器的网络命名空间,
两个容器内的网络配置共享,但其它资源(进程空间、文件系统等)还是隔离的。
--net=host 意味着不为容器创建独立的网络命名空间,容器内看到的网络配置均与主机保持一致。
--net=user_defined_network 用户自行用network相关命令创建一个网络,
同一个网络内的容器彼此可见,可以采用更多类型的网络插件。
二、配置容器DNC和主机名
Docker支持自定义容器的主机名和DNS配置。
1.相关配置文件
实际上,容器中主机名和DNS配置信息都是通过三个系统配置文件来维护的:/etc/resolv.conf、/etc/hostname、/etc/hosts。
启动一个容器,在容器中使用mount命令可以看到这三个文件的挂载信息。
其中,/etc/resolv.conf文件在创建容器的时候,默认会与宿主机/etc/resolv.conf文件内容保持一致。
这样也可以说明容器和宿主的文件系统是共享的。
/etc/hosts文件默认中只记录容器自身的一些地址和名称。
2.容器内修改配置文件
Docker1.2.0开始支持在运行中的容器里直接编辑/etc/hosts、/etc/hostname和/etc/resolv.conf文件。
但是这些修改只是临时的,只是在运行的容器中保留,容器终止或重启后并不会被保存下来。也不会被docker commit提交。
3.通过参数指定
如果用户想要自定义容器的配置,可以在创建或启动容器的时候利用下面的参数指定。
指定主机名
-h HOSTNAME或--hostname=HOSTNAME。设定容器的主机名,它会被写到容器内的/etc/hosts和/etc/hostname文件中。
但是这个主机名只有在容器内才能看到,在容器外看不到,既不会在docker ps中显示,也不会在其它容器的/etc/hosts中看到。
记录其它容器主机名
--link=CONTAINER_NAME:ALLAS。在创建容器的时候,添加一个所连接容器的主机名到容器内/etc/hosts文件中。
这样新创建容器可以直接使用主机名来与所连接容器通信。
指定DNS服务器
--dns=IP_ADDRESS。添加DNS服务器到容器的/etc/resolv.con中,
容器会用指定的服务器来解析所有不在/etc/hosts中的主机名。
指定DNS搜索域
--dns-search=DOMAIN。设定容器的搜索域,当设定搜索域为.example.com时,在搜索一个名为host的主机时,
DNS不仅搜索host,还会搜索host.example.com。
三、容器访问控制
容器的访问控制主要通过Linux上的iptables防火墙软件来进行管理和实现。
iptables是系统流行的防火墙软件,在大部分发行版本中都会自带。
1.容器访问外部网络
容器默认指定了网关为docker0网桥上的docker0内部接口。
docker0内部接口也是宿主机的一个本地接口,因此默认情况下是可以访问到宿主机本地的。
容器想要通过宿主机访问到外部网络,需要宿主机进行转发。
在Linux系统中,检查转发是否打开(默认是打开的):
如果为0,说明没有开启转发,则需要手动打开:
更简单的,在启动Docker服务的时候设定--ip-forward=true,docker服务会自动打开宿主机系统的转发服务。
2.容器之间的访问
容器之间相互访问,需要两方面的支持:
网络拓朴是否已经连通。默认情况下,所有容器都会连接到docker0网桥上,这意味着默认情况下拓扑是互通的;
本地系统的防火墙软件iptables是否允许访问通过。这取决于防火墙的默认规则是允许还是禁止。
下面分两种情况介绍容器之间的访问。
(1)访问所有端口
当启动Docker服务的时候,默认会添加一条“允许”转发策略到iptables的FORWARD链上。
通过配置--icc=true|false(默认为true)参数可以控制默认的策略。
为了安全考虑,可以在Docker配置文件中配置DOCKER_OPTS=--icc=false来默认禁止容器之间的相互访问。
同时,如果启动Docker服务时手动指定--iptables=false参数则不会修改宿主机系统上的iptables规则。
(2)访问指定端口
在通过-icc=false禁止容器间相互访问后,仍可以通过--link=CONTAINER_NAME:ALIAS选项来允许访问指定容器的开放端口。
--link=CONTAINER_NAME:ALIAS中的CONTAINER_NAME必须是docker自动分配的主机名或使用--name指定的主机名,不能使用-h参数配置的主机名。
四、映射容器端口到宿主机的实现
默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。
1.容器访问外部实现
假设容器内部的网络地址为172.12.0.2,本地网络地址为10.0.2.2。
容器要能访问外部网络,源地址不能为172.12.0.2,需要进行源地址映射(Source NAT,SNAT),
修改为本地系统的IP地址10.0.2.2。
映射是通过iptables的源地址伪装操作实现的。
查看主机nat表上POSTROUTING链的规则。该链负责网包离开主机之前,还写其源地址。
其中,上述规则将所有源地址为172.12.0.0/16网段,而不是从docker0接口发出的流量,动态伪装为系统网卡发出。
MASQUERADE行动跟传统SNAT行动相比,好处是它能从网卡动态获取地址。
2.外部访问容器实现
容器允许外部访问,可以在docker run时候通过-p或-P参数来启用。
不管用那种方法,其实也是在本地的iptable的nat表中添加相应的规则,
将访问外部IP地址的网包进行目标地址DANT,将目标地址修改为容器的IP地址。
以一个开放的8080端口为例,使用-P会自动映射本地的一个随即端口到容器的8080端口。
可以看到,nat中涉及两条链,PREROUTING链负责包到达网络接口时,改写其目标地址。
其中规则将所有流量都仍到DOCKER链。而DOCKER链中所有不是从docker0进来的网包(意味着不是本地主机产生),
将目标端口为49153的,修改目标地址为172.17.0.11,目标端口修改为80。
需要注意的是,这里的而规则映射了0.0.0.0,意味着将接收主机来自所有网络接口上的流量。
可以通过-p IP:host_port:container_port或-p IP::port来指定绑定的外部网络接口,以制定更严格的访问规则。
如果希望映射永久绑定到某个固定的IP地址,可以在docker配置文件/etc/default/docker中指定DOCKER_OPTS="--ip=IP_ADDRESS",之后重启Docker服务即可生效。
五、配置docker0网桥
Docker服务默认会创建一个名称为docker0的Linux网桥,它在内核层连通了其它的物理或虚拟网卡。
这就将所有容器和本地主机都放到了同一个物理网络。用户使用Docker创建多个自定义网络时可能会出现多个容器网桥。
Docker默认制定了docker0接口的IP地址和子网掩码,让主机和容器之间可以通过网桥相互通信。
它还给出了MTU(接口允许接收的最大传输单元),通常是1500字节,或宿主网络路由上支持的默认值。
这些值都可以在服务启动的时候进行配置:
--bip=CIDR:IP地址加掩码格式,例如192.168.1.5/24
--mtu=BYTES:覆盖默认的Docker mtu配置。
也可以在配置文件中配置DOCKER_OPTS,然后重启服务。
由于目前Docker网桥是Linux网桥,用户可以使用brctl show来查看网桥和端口连接信息。
每次创建一个新的容器时,Docker从可用的地址段中选择一个空闲的IP地址来分配给容器的eth0端口。
并且使用本地主机上docker0接口的IP作为容器的默认网关:
六、自定义网桥
除了默认使用docker0网桥,用户也可以指定网桥来连接各个容器。
在启动docker服务的时候,使用-b BRIDGE或--bridge=BRIDGE来指定使用的网桥。
停止服务:
service docker stop
关闭docker网桥:
ip link set dev docker0 down
删除旧的网桥:
brctl delbr docker0
新建网桥:
brctl addbr bridge0
给新的网桥配置参数:
ip addr add 192.168.5.1/24 dev bridge0
ip link set dev bridge0 up
ip addr show bridge0
这是传统的方法,现在直接使用docker create network。
七、使用OpenvSwitch网桥
Docker默认使用的是Linux自带的网桥实现,实际上,OpenvSwitch项目作为一个成熟的虚拟交换机,具备更丰富的功能。
1.安装OpenvSwitch
安装依赖包:
yum -y install openssl-devel wget kernel-devel
安装开发工具:
yum groupinstall "Development Tools"
下载源码:
wget http://openvswitch.org/releases/openvswitch-2.3.1.tar.gz
解压:
tar xfz openvswitch-2.3.1.tar.gz
创建编译目录:
mkdir -p ~/rpmbuild/SOURCES
从spec文件中删除openvswitch-kmod的依赖包,并创建一个新的spec文件:
sed 's/openvswitch-kmod, //g' openvswitch-2.3.1/rhel/openvswitch.spec > openvswitch-2.3.1/rhel/openvswitch_no_kmod.spec
开始编译:
cp openvswitch-2.3.1.tar.gz rpmbuild/SOURCES
rpmbuild -bb --without=check ~/openvswitch-2.3.1/rhel/openvswitch_no_kmod.spec
安装编译生成的rpm文件:
yum localinstall /home/ovswitch/rpmbuild/RPMS/x86_64/openvswitch-2.3.1-1.x86_64.rpm
启动服务:systemctl start openvswitch.service
查看服务状态:systemctl -l status openvswitch.service
2.配置容器连接到OpenvSwitch
目前OpenvSwitch网桥还不能直接支持挂载容器,需要手动在OpenSwitch网桥上创建虚拟网口被挂载到容器中。
步骤如下:
(1)创建无网络容器
docker run --net=noen --privileged=true -it base /bin/bash
查看网络信息:
只有一个本地网卡lo
(2)手动为容器添加网络
下载辅助脚本ovs-docker:
wget https://github.com/openvswitch/ovs/raw/master/utilities/ovs-docker
chmod a+x ovs-docker
添加网桥br1,并挂载:
ovs-vsctl add-br br1
ovs-vsctl show(查看)
./ovs-docker add-port br1 eth0 c26f1bbdb3f5 --ipaddress=172.17.0.2/24
可以看到容器内多了一个网卡:
在容器外,为br1配置接口地址。
ifconfig br1 172.17.0.10/24
八、创建一个点到点的连接
默认情况下,Docker会将所有容器连接由docker0提供的虚拟子网中,
有时候需要两个容器之间可以直接通讯,而不同通过主机网桥进行桥接。
创建一对peer接口,分别放到两个容器中,配置成点到点链路类型即可。
启动两个容器:
docker run -it --rm --net=none centos /bin/bash
找到进程号,然后创建网络命名空间的跟踪文件:
docker inspect -f "{{.State.Pid}}" d3e573778246 docker inspect -f "{{.State.Pid}}" 3882bde4d974 mkdir -p /var/run/netns ln -s /proc/25974/ns/net /var/run/netns/25974 ln -s /proc/26023/ns/net /var/run/netns/26023 ip link add AA type veth peer name BB
添加IP地址和路由信息:
ip link set AA netns 25974 ip netns exec 25974 ip addr add 10.1.1.1/32 dev AA ip netns exec 25974 ip link set AA up ip netns exec 25974 ip route add 10.1.1.2/32 dev AA ip link set BB netns 26023 ip netns exec 26023 ip addr add 10.1.1.2/32 dev BB ip netns exec 26023 ip link set BB up ip netns exec 26023 ip route add 10.1.1.1/32 dev BB
ping:
此外也可以不指定--net=none来创建点链路。