最近在看docker,觉得docker重点就是在做隔离,隔离网络,隔离文件系统,隔离进程,这就是docker集装箱的比喻的来源吧。
对docker网络比较感兴趣,所以想着自己用linux 的虚拟网络设备模拟一下docker的网络隔离,参考了网上许多优秀的文章,具体实现的时候碰到了不少问题,比如iptables防火墙的问题、DNS的问题,最后都解决了,现把整个过程记录如下(可以先看原理,文末有脚本可以直接复制)。
首先环境的话,我是在win10上面装了个vmware虚拟机,然后里面装的是ubuntu的系统,在ubuntu里面做这个docker网络模拟实验
sudo ip netns add ns1
首先,先创建一个 network space ,它的名字叫做ns1, 这个相当于docker里面的容器 container
创建完之后,可以用命令 sudo ip netns list 来查看 (Note: 删除的命令是 sudo ip netns del ns1)
sudo ip netns list
之后我们再创建一个veth, veth就是一种虚拟网络设备,成对出现,在一端写会在另一端出
sudo ip link add veth0 type veth peer name veth1
这里的veth0 和veth1 是表示veth两端设备的名字,可以改, veth是一种类型
这句代码的意思是创建一对veth, 一端叫veth0 ,一端叫veth1
然后我们把veth1端放进ns1 空间
sudo ip link set veth1 netns ns1
接着我们创建网桥br0 ,这个就相当于docker里面的docker0网桥 (Note:没有安装brctl的可以先安装)
sudo brctl addbr br0
将刚才的veth0一端接入网桥
sudo brctl addif br0 veth0
这个时候,我们配置一下veth1 和 br0 的 ip 地址并启动,启动veth0
sudo ip netns exec ns1 ifconfig veth1 172.8.0.8 up
sudo ifconfig br0 172.8.0.1 up
sudo ifconfig veth0 up
启动之后可以用ifconfig ,看到已经启动的虚拟设备,其中br0有刚才配置的地址,而veth0没有
veth0配不配地址都无所谓,已经接入了br0 ,所以在这里没有给veth0配地址
这个时候我们已经能够从宿主机ping到ns1了
sudo ping -c 1 172.8.0.8
接着我们从ns1 尝试 ping到宿主机,发现ping不通
ip netns exec ns1 ping -c 1 192.168.225.128 #这里的宿主机地址可以用ipconfig查看,我网卡是ens33
我们再配置一下ns1 的网关为br0
sudo ip netns exec ns1 route add default gw 172.8.0.1
再ping一次,就能ping通宿主机了
ip netns exec ns1 ping -c 1 192.168.225.128 #这里的宿主机地址可以用ipconfig查看,我网卡是ens33
到这里,我们已经使得ns1与宿主机能够互相通信了,我们的图是下面这个样子的
这个图片是借鉴别人的,画得很好,这里的ip跟我上面设置的不一样,主要是明白结构!
接着我们尝试ping到外网
sudo ip netns exec ns1 ping -c 1 14.215.177.38
这里的14.215.177.38是百度的ip地址,发现ping不通,这是为什么呢?
所以我们要在iptables上做下手脚,做下NAT,当数据包出去的时候,把源地址修改成宿主机的ip地址,做个伪装,这样回来的时候就能找到我们的机器了,才会收到ICMP的reply包
sudo iptables -A POSTROUTING -t nat -s 172.8.0.0/16 -o ens33 -j MASQUERADE
这句话的意思,就是 -A 表示是一个ADD操作,增加一个规则
POSTROUTING 是iptable中的一个链,位于数据包发出去最后一个位置
-t 是table的意思,iptables里面有链和表的概念,一个链上的规则可以根据不同的用户分成不同的表
-s表示匹配到源地址是172.8.0.0, 就是说匹配从ns1那边发出来的数据
-o output的意思,表示输出设备是ens33, 宿主机的网卡
-j 是策略的意思,选择MASQUERADE,就是伪装策略,NAT
所以整句代码的意思,就是,增加一条POSTROUTING的规则,这条规则属于nat表,规则的具体内容是当网卡ens33要发送源地址为172.8.0.0/16的数据包时,先做个NAT, 换成本机ip地址再发出去
这样我们的ICMP请求包源地址就不再是172.***,而是我们的宿主机啦,所以去公网游了一圈之后,回来的时候就能找到我们的宿主机,这样我们就能ping通外网了
注意,这里还要记得宿主机要打开ip_forward功能,具体为什么要开,下文会讲
cat /proc/sys/net/ipv4/ip_forward
可以用上面命令查看是否打开了这个功能,如果输出0就是没有,1就是有,打开的命令是
sudo sysctl -w net.ipv4.conf.all.forwarding=1
当然echo 1> 到那个文件,也可以打开,反正就是一个配置文件,内容1 开,0关
尝试ping一下外网
sudo ip netns exec ns1 ping -c 1 14.215.177.38
可以先看一下,iptables的工作流程图(这张图并非原创,来源于朱双印博客,文末会有参考文献链接)
当我们的ens33网卡接受 ICMP相应数据包的时候,会依次经过prerouting->判断目标是否为本机->forward->postrouting,如果ipforward设成0,也就是没打开ip转发功能的话,到forward那里就过不去,只有打开了ipforward,我们的数据包才能发到br0网桥上。
这个时候,我们再进一步,ping一下百度域名,这个因机器而异,我就是因为这里ping不同,所以研究了好久
sudo ip netns exec ns1 ping -c 1 www.baidu.com
最后发现是DNS的设置问题,我发现在ns1里面ping不通主机的DNS
cat /etc/resolv.conf
用上面的命令查看一下,主机的DNS服务器,配置的是 127.0.0.53
但是这个地址,我们在ns1里面并ping不同,所以就DNS这个过程没有办法完成
所以后来我就把127.0.0.53修改成了谷歌免费的DNS服务器 8.8.8.8
之后就能直接ping通百度的域名啦
#!/usr/bin/env bash
#output the command
set -x
#create network namespace ns1
sudo ip netns add ns1
#add a veth
sudo ip link add veth0 type veth peer name veth1
#put veth to ns1
sudo ip link set veth1 netns ns1
#add bridge and add veth0
sudo brctl addbr br0
sudo brctl addif br0 veth0
#set ns1's ip and start up
sudo ip netns exec ns1 ifconfig veth1 172.8.0.8 up
#start up the br0 and veth0
sudo ifconfig br0 172.8.0.1 up
sudo ifconfig veth0 up
#set the ns1's default gw
sudo ip netns exec ns1 route add default gw 172.8.0.1
#start up the ip forwarding
sudo sysctl -w net.ipv4.conf.all.forwarding=1
#modify the iptables rule, make sure receive the icmp reply data
sudo iptables -A POSTROUTING -t nat -s 172.8.0.0/16 -o ens33 -j MASQUERADE
#enter the ns1
ip netns exec ns1 /bin/bash --rcfile <(echo "PS1=\"ns1> \"")
【朱双印讲解iptables博文】:http://www.zsythink.net/archives/1199
【自己动手配置docker网络】:https://hiberabyss.github.io/2018/02/02/docker-bridge-network-practice/
【思否上面很好的网桥讲解】:https://segmentfault.com/a/1190000009491002