bingoogolapple / bingoogolapple.github.io

个人主页。同时也通过 Issues 记录学习笔记
http://www.bingoogolapple.cn
86 stars 22 forks source link

Docker 网络 #149

Open bingoogolapple opened 7 years ago

bingoogolapple commented 7 years ago

基本概念

查看系统网络设备,存在一个叫 docker0 的虚拟网桥,Docker 守护进程是通过 docker0 为容器提供网络连接的各种服务

➜  ~ ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:e4:b8:02:1f
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

网桥

是 OSI 七层模型中数据链路层的一种设备,用来通过 MAC 地址,也就是网络设备的物理地址来对网络进行划分,并且在不同的网络之间传递数据

Linux 虚拟网桥的特点:

通常来说 IP 地址是三层协议的内容,也就是网络层协议的内容,不应该出现在二层设备上,但 Linux 的虚拟网桥是通用网络设备抽象的一种,只要是网络设备就能够设置 IP 地址,当虚拟网桥拥有 IP 后 Linux 就可以通过路由表或者 IP 表规则在网络层定位网桥,这就相当于拥有了一个隐藏的虚拟网卡,而这个网卡的名字就是虚拟网桥的名字,也就是上面的 docker0

docker 的地址划分

image

安装网桥管理工具

sudo apt-get install bridge-utils

启动容器前查看网桥设备,此时 docker0 对应的 interfaces 中什么都没有

➜  ~ sudo brctl show
bridge name bridge id       STP enabled interfaces
docker0     8000.0242e4b8021f   no

启动容器 nwt1 并查看容器中的网络设备

➜  ~ docker run -it --name nwt1 bingoogolapple/bga-ubuntu bash
root@dd631c5e2b62:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:ac:11:00:02
          inet addr:172.17.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:14 errors:0 dropped:0 overruns:0 frame:0
          TX packets:7 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:1156 (1.1 KB)  TX bytes:578 (578.0 B)

...

启动容器后查看网桥设备,此时 docker0 对应的 interfaces 中多了一个 vethe2c5eb0,这就是 Docker 在容器创建时为容器连接 docker0 所创建的网络接口

➜  ~ sudo brctl show
bridge name bridge id       STP enabled interfaces
docker0     8000.0242e4b8021f   no      vethe2c5eb0

此时通过 ifconfig 命令也能看到这个网络接口

➜  ~ ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:e4:b8:02:1f
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:e4ff:feb8:21f/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:32 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:2144 (2.1 KB)  TX bytes:648 (648.0 B)

vethe2c5eb0 Link encap:Ethernet  HWaddr 32:89:30:0d:2b:27
          inet6 addr: fe80::3089:30ff:fe0d:2b27/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8 errors:0 dropped:0 overruns:0 frame:0
          TX packets:24 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:648 (648.0 B)  TX bytes:1944 (1.9 KB)

...
bingoogolapple commented 7 years ago

自定义 Docker 网桥

当 docker0 所提供的默认 ip 地址范围不能满足我们希望为容器分配的 ip 地址时,可以通过 Linux 自带的 ifconfig 命令来修改 docker0。不知为何在 Ubuntu 虚拟机中使用这种方式没生效,重启 Docker 守护进程后就失效了

➜  ~ sudo ifconfig docker0 192.168.200.1 netmask 255.255.255.0
[sudo] password for ubuntuone:
➜  ~ ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:e4:b8:02:1f
          inet addr:192.168.200.1  Bcast:192.168.200.255  Mask:255.255.255.0
          inet6 addr: fe80::42:e4ff:feb8:21f/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:32 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:2144 (2.1 KB)  TX bytes:648 (648.0 B)

...

添加虚拟网桥

➜  ~ sudo brctl addbr br0
➜  ~ sudo ifconfig br0 192.168.100.1 netmask 255.255.255.0
➜  ~ ifconfig
br0       Link encap:Ethernet  HWaddr aa:cc:31:3c:b4:ba
          inet addr:192.168.100.1  Bcast:192.168.100.255  Mask:255.255.255.0
          inet6 addr: fe80::a8cc:31ff:fe3c:b4ba/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:508 (508.0 B)

在 Docker 守护进程启动配置文件 /lib/systemd/system/docker.service 中的 ExecStart 末尾增加参数 -b=br0 或者用空格隔开 -b br0,并重启 Docker 守护进程

sudo vim /lib/systemd/system/docker.service

ExecStart=/usr/bin/dockerd --label=name=docker_server_ubuntutwo -H fd:// -H tcp://0.0.0.0:2375 -b=br0
ExecStart=/usr/bin/dockerd --label=name=docker_server_ubuntutwo -H fd:// -H tcp://0.0.0.0:2375 -b br0

sudo systemctl daemon-reload
sudo service docker restart
ps -ef | grep docker

此时新建一个容器就能看见上面的配置已经生效

➜  ~ docker run -it --name nwt1 bingoogolapple/bga-ubuntu bash
root@4c07917612af:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:c0:a8:64:02
          inet addr:192.168.100.2  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::42:c0ff:fea8:6402/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:6 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:508 (508.0 B)  TX bytes:508 (508.0 B)

...
bingoogolapple commented 7 years ago

Docker 容器的互联

允许所有容器互联

容器启动时的选项 --link 参数。先启动容器 cct1,然后启动容器 cct2 并让它连接到 cct1 上指定别名为 web_server,此时 cct2 中会有很多以 WEB_SERVER 开头的环境变量,在 hosts 中也添加了 web_server 的地址映射,这样的话重启 Docker 守护进程后在 cct2 中还是能通过 web_server 来访问 cct1

需要注意的是:容器 cct2 依赖容器 cct1,所以必须先启动容器 cct1 然后再启动容器 cct2

docker run --link=[CONTAINER_NAME]:[ALIAS]

➜  test docker run -it -d --name cct1 -p 8080:80 bingoogolapple/bga-ubuntu nginx -g "daemon off;"

➜  test docker run -it --link=cct1:web_server --name cct2 bingoogolapple/bga-ubuntu

root@fe4dafdfb110:/# ping web_server
PING web_server (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: icmp_seq=0 ttl=64 time=0.168 ms
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.077 ms
^C--- web_server ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.077/0.122/0.168/0.046 ms

root@fe4dafdfb110:/# env
WEB_SERVER_PORT_80_TCP=tcp://172.17.0.2:80
HOSTNAME=fe4dafdfb110
TERM=xterm
WEB_SERVER_PORT=tcp://172.17.0.2:80
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
WEB_SERVER_PORT_80_TCP_PROTO=tcp
PWD=/
SHLVL=1
WEB_SERVER_PORT_80_TCP_ADDR=172.17.0.2
HOME=/root
WEB_SERVER_NAME=/cct2/web_server
WEB_SERVER_PORT_80_TCP_PORT=80
_=/usr/bin/env

root@fe4dafdfb110:/# cat /etc/hosts
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2  web_server fc743f8120fc cct1
172.17.0.3  fe4dafdfb110

拒绝容器间互联

修改 Docker 守护进程的启动选项 --icc=false 即可拒绝所有容器间的互联

允许特定容器间的互联

  1. 修改 Docker 守护进程的启动选项 --icc=false --iptables=true
  2. 启动容器时使用 --link 选项

在 --icc=false 时阻断所有容器之间的访问,设置 --iptables=true 时,仅仅允许使用 --link 选项配置的容器进行相互的访问

sudo vim /lib/systemd/system/docker.service

ExecStart=/usr/bin/dockerd --label=name=docker_server_ubuntutwo -H fd:// -H tcp://0.0.0.0:2375 --icc=false --iptables=true

sudo systemctl daemon-reload
sudo service docker restart

创建4个测试容器

docker run -it -d --name cct1 -p 8080:80 bingoogolapple/bga-ubuntu nginx -g "daemon off;"
docker run -it  --name cct2 bingoogolapple/bga-ubuntu
docker run -it --link=cct1:web_server --name cct3 bingoogolapple/bga-ubuntu

解决 Docker 的 bug

看到 Chain FORWARD (policy ACCEPT) 里有一行将所有的都 DROP 了,这是 Docker 的 bug,接下来清空 iptables 并重启 Docker 守护进程

sudo iptables -L -n

sudo iptables -F
sudo service docker restart
docker restart cct1 cct2 cct3 cct4

sudo iptables -L -n
此时从 Chain DOCKER 能看到 cct3 和 cct1 的链接、cct4 和 cct1 的链接
Chain DOCKER (1 references)
target     prot opt source               destination
ACCEPT     tcp  --  0.0.0.0/0            172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.4           172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.2           172.17.0.4           tcp spt:80
ACCEPT     tcp  --  172.17.0.5           172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.2           172.17.0.5           tcp spt:80

此时 cct4 的 hosts 文件内容,此时就能通过 curl web_server 或 curl cct1 或 curl 207b9ac09b69 来访问 cct1 中的服务,「注意只是访问 cct1 里面的服务,使用 ping 命令还是 ping 不通 cct1 的」

root@a7e8760c52e1:/# cat /etc/hosts
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2  web_server 207b9ac09b69 cct1
172.17.0.5  a7e8760c52e1

容器与外部网络的链接

查看 filter 表,也可以忽略 -t 参数,因为 iptables 默认指定的就是 filter 表。包含 INPUT、FORWARD、OUTPUT 三个链,也包含了一个名为 DOCKER 的链,这个 DOCKER 的链实际上就是 FORWARD 链中 DOCKER 操作的子链

➜  ~ sudo iptables -t filter -L -n
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
DOCKER-ISOLATION  all  --  0.0.0.0/0            0.0.0.0/0
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0
DROP       all  --  0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain DOCKER (1 references)
target     prot opt source               destination
ACCEPT     tcp  --  0.0.0.0/0            172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.4           172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.2           172.17.0.4           tcp spt:80
ACCEPT     tcp  --  172.17.0.5           172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.2           172.17.0.5           tcp spt:80

Chain DOCKER-ISOLATION (1 references)
target     prot opt source               destination
RETURN     all  --  0.0.0.0/0            0.0.0.0/0

限制 192.168.56.112 通过 TCP 协议访问 172.17.0.2 的 80 端口

➜  ~ sudo iptables -I DOCKER -s 192.168.56.112 -d 172.17.0.2 -p TCP --dport 80 -j DROP
➜  ~ sudo iptables -L -n
...

Chain DOCKER (1 references)
target     prot opt source               destination
DROP       tcp  --  192.168.56.112       172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  0.0.0.0/0            172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.4           172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.2           172.17.0.4           tcp spt:80
ACCEPT     tcp  --  172.17.0.5           172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.2           172.17.0.5           tcp spt:80

...

删除上一步添加的规则。删除 filter 表中 DOCKER 链表的第一条规则

➜  ~ sudo iptables -t filter -D DOCKER 1
➜  ~ sudo iptables -L -n
...

Chain DOCKER (1 references)
target     prot opt source               destination
ACCEPT     tcp  --  0.0.0.0/0            172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.4           172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.2           172.17.0.4           tcp spt:80
ACCEPT     tcp  --  172.17.0.5           172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.2           172.17.0.5           tcp spt:80

...
bingoogolapple commented 7 years ago

容器的跨主机访问

增加别名方便重置测试环境 vim + .zshrc 【注意是单引号】

alias stopWeave='weave stop-plugin && weave stop-router && weave stop-proxy'
alias stopAllContainer='docker stop $(docker ps -aq)'
alias rmAllContainer='docker rm -v $(docker ps -aq)'
alias stopAndRmAllContainer='docker stop $(docker ps -aq) && docker rm -v $(docker ps -aq)'
alias resetWeaveTest='weave stop-plugin && weave stop-router && weave stop-proxy && docker stop $(docker ps -aq) && docker rm -v $(docker ps -aq)'

alias reloadDaemonAndRestartDocker='sudo systemctl daemon-reload && sudo service docker restart'

使用网桥实现跨主机容器连接

两台主机的 ip

10.211.55.3
10.211.55.5

修改主机 /etc/network/interfaces 文件

auto br0
iface br0 inet static
address 10.211.55.3
netmask 255.255.255.0
gateway 10.211.55.1
bridge_ports eth0

在 Docker 守护进程启动配置文件 /lib/systemd/system/docker.service 中的 ExecStart 末尾增加参数,并重启 Docker 守护进程

vim /lib/systemd/system/docker.service

ExecStart=/usr/bin/dockerd --label=name=docker_server_ubuntutwo -H fd:// -H tcp://0.0.0.0:2375 -b=br0 --fixed-cidr='10.211.55.64/26'

sudo systemctl daemon-reload
sudo service docker restart
ps -ef | grep docker

使用 Open vSwitch(ovs) 实现跨主机的容器连接

它是一个高质量的,多层的虚拟交换机,使用开源 Apache2.0 许可协议,主要实现代码是可移植的 C 代码。目的是让大规模网络自动化可以通过编程扩展,同时仍然支持标准的管理接口和协议

  1. 双网卡,Host-Only & NAT
  2. 安装 Open vSwitch: apt-get install openvswitch-switch

操作步骤:

  1. 在虚拟机中建立ovs网桥
  2. 添加gre连接
  3. 配置docker容器虚拟网桥
  4. 为虚拟网桥添加ovs接口
  5. 添加不同Docker容器网段路由

在 192.168.56.111 上操作

sudo ovs-vsctl show
建立一个名为 obr0 的 ovs 网桥
sudo ovs-vsctl add-br obr0
为 obr0 网桥添加一个名为 gre0 的 gre 接口,如果报错请看 http://stackoverflow.com/questions/35689488/ovs-vsctl-error-detected-while-setting-up-bridge
sudo ovs-vsctl add-port obr0 gre0
设置 gre0 接口,类型为 gre,指定要连接的远程服务器的地址
sudo ovs-vsctl set interface gre0 type=gre options:remote_ip=192.168.56.112
sudo ovs-vsctl show

建立本机 Docker 容器需要使用的虚拟网桥
新建一个名为 br0 的网桥
sudo brctl addbr br0
为 br0 网桥设置网络地址
sudo ifconfig br0 192.168.1.1 netmask 255.255.255.0
为 br0 网桥添加 ovs 网桥的链接
sudo brctl addif br0 obr0
查看当前网桥链接状态
sudo brctl show

编辑 Docker 守护进程启动配置选项,设置 Docker 容器使用 br0 网桥
sudo vim /lib/systemd/system/docker.service

ExecStart=/usr/bin/dockerd --label=name=docker_server_ubuntutwo -H fd:// -H tcp://0.0.0.0:2375 -b=br0

sudo systemctl daemon-reload
sudo service docker restart
ps -ef | grep docker

此时运行一台容器就能 ping 同 192.168.56.112 了
docker run -it --name nt1 bga/tv
ping 192.168.56.112

如果 ping 不通就添加一条路由信息,告诉路由表通过本机的 enp0s8 网卡来连接,在 192.168.56.112 这台机器上能找到 192.168.2.0/24 这个网段
route  
sudo ip route add 192.168.2.0/24 via 192.168.56.112 dev enp0s8

在 192.168.56.112 上操作

sudo ovs-vsctl show
建立一个名为 obr0 的 ovs 网桥
sudo ovs-vsctl add-br obr0
为 obr0 网桥添加一个名为 gre0 的 gre 接口    http://stackoverflow.com/questions/35689488/ovs-vsctl-error-detected-while-setting-up-bridge
sudo ovs-vsctl add-port obr0 gre0
设置 gre0 接口,类型为 gre,指定要连接的远程服务器的地址
sudo ovs-vsctl set interface gre0 type=gre options:remote_ip=192.168.56.111
sudo ovs-vsctl show

建立本机 Docker 容器需要使用的虚拟网桥
新建一个名为 br0 的网桥
sudo brctl addbr br0
为 br0 网桥设置网络地址
sudo ifconfig br0 192.168.2.1 netmask 255.255.255.0
为 br0 网桥添加 ovs 网桥的链接
sudo brctl addif br0 obr0
查看当前网桥链接状态
sudo brctl show

编辑 Docker 守护进程启动配置选项,设置 Docker 容器使用 br0 网桥
sudo vim /lib/systemd/system/docker.service

ExecStart=/usr/bin/dockerd --label=name=docker_server_ubuntutwo -H fd:// -H tcp://0.0.0.0:2375 -b=br0

重启 Docker 守护进程
sudo systemctl daemon-reload
sudo service docker restart
ps -ef | grep docker

此时运行一台容器就能 ping 同 192.168.56.111 了
docker run -it --name nt1 bga/tv
ping 192.168.56.111

如果 ping 不通就添加一条路由信息,告诉路由表通过本机的 enp0s8 网卡来连接,在 192.168.56.111 这台机器上能找到 192.168.1.0/24 这个网段
route  
sudo ip route add 192.168.1.0/24 via 192.168.56.111 dev enp0s8

在实际的生成环境中通过 shell 脚本来讲这一系列的操作自动化

使用 weave 实现跨主机容器连接

建立一个虚拟的网络,用于将运行在不同主机的 Docker 容器连接起来,通过 weave 来为容器指定希望的 ip 地址和 ip 地址段

  1. 安装 weave
  2. 启动 weave weave launch
  3. 连接不同主机
  4. 通过 weave 启动容器

安装 weave

sudo curl -L git.io/weave -o /usr/local/bin/weave
sudo chmod a+x /usr/local/bin/weave

测试1

192.168.56.111 上运行

启动 weave
weave launch

启动名为 test_weave_11 的容器,并设置 ethwe 网络设备对应的 ip
weave run 192.168.5.11/24 -itd --name test_weave_11 bga/tv
或
docker run -itd --name test_weave_11 bga/tv
weave attach 192.168.5.11/24 test_weave_11

进入容器
docker attach test_weave_11

执行 ifconfig 命令能看到多了一个叫 ethwe 网络设备,在这个设备中的 ip 地址就是执行 wave 命令启动容器时指定的 ip
ifconfig

ping 192.168.5.22
ping test_weave_21

192.168.56.112 上运行

启动 weave 并指定另一台主机的 ip 地址
weave launch 192.168.56.111

启动名为 test_weave_21 的容器,并设置 ethwe 网络设备对应的 ip
cid=$(weave run 192.168.5.22/24 -it --name test_weave_21 bga/tv)
这里的 cid 就是使用 weave 命令运行的容器的 id
echo $cid

进入容器
docker attach $cid
执行 ifconfig 命令能看到多了一个叫 ethwe 网络设备,在这个设备中的 ip 地址就是执行 wave 命令启动容器时指定的 ip
ifconfig

ping 192.168.5.11
ping test_weave_11