本文通过docker的网络介绍容器网络的原理以及一些实践,通过实践一遍相信大家会对网络底层的原理有个更深的理解,最后给出对接ovs的教程,这对下一篇k8s对接ovn的原理理解打下一个基础。
端口映射:
$ docker run -p 8080:80 nginx:latest
如果没有这个-p,会发现启动了nginx但是无法通过宿主机访问到web服务,而使用了-p参数后就可以通过访问主机的8080断开去访问nginx了。 端口映射的原理是作了net转发
共享主机网络:
$ docker run --net=host nginx:latest
这种容器没有自己的网络,完全共享主机的网络,所以可以通过主机ip直接访问容器服务。 坏处是容器与其它容器端口冲突
link网络
$ docker run --name mysql mysql:latest
$ docker run --link=mysql nginx:latest
这样nginx可以通过容器名去访问mysql,其原理是在nginx容器中的/etc/hosts中加入了mysql主机名解析。这种共享不可跨主机
$ docker run --rm -it --name c1 centos:latest /bin/bash
$ docker run --rm -it --name c2 --link c1 centos:latest /bin/bash
[root@178d290d873c /]# 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.4 c1 3b7b15fa7e20 # 看这里
172.17.0.5 178d290d873c
none模式
容器不创建网络,需要自行定义
overlay网络
进群中常用的网络模式,使用vxlan等技术作了一层覆盖,使每个容器有自己独立的ip并可跨主机通信。
共享容器网络
如kubernetes里pod的实现,pod是多个容器的集合,这些容器都共享了同一个容器的网络,那么这些容器就如同在一个host上一样。
在宿主机上ifconfig:
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 0.0.0.0
inet6 fe80::42:a4ff:fe60:b79d prefixlen 64 scopeid 0x20<link>
ether 02:42:a4:60:b7:9d txqueuelen 0 (Ethernet)
RX packets 23465 bytes 3407255 (3.2 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 24676 bytes 22031766 (21.0 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
vethcd2d45d: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet6 fe80::c4d6:dcff:fe7d:5f44 prefixlen 64 scopeid 0x20<link>
ether c6:d6:dc:7d:5f:44 txqueuelen 0 (Ethernet)
RX packets 415 bytes 82875 (80.9 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 372 bytes 379450 (370.5 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
docker0是一个虚拟网桥,类似一个交换机的存在。 veth开头的网卡就是为容器分配的一个设备,但是要注意这不是容器中的设备。由于linux物理网卡只能出现在一个namespace中,所以只能用虚拟设备给容器创建独立的网卡。
docker network inspect bridge 看一下,这是给容器内部分配的地址:
"Containers": {
"ac8c983592f06d585a75184b4dcd012338645fb7fa60b07c722f59ce43ceb807": {
"Name": "sick_snyder",
"EndpointID": "0755707344f30c40d686a2b4fdcabf45d6e1a64f8de8618b9a3a8c8e5b203ddc",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
再引入一个概念:linux设备对,类似管道一样,在一端写另一端就可以读,容器内的eth0就与这个veth是一对设备对
docker0 eth0 -> 宿主机
--------------- ----
| |
vethx vethy
---- ----
| | ---->设备对
+----+---+ +----+---+
| eth0 | | eth0 |
+--------+ +--------+
容器1 容器2
docker inspect --format '{{ .State.Pid }}' test1
mkdir /var/run/netns # ip命令的网络命名空间
ln -s /proc/1231221/ns/net /var/run/netns/test1
ip netns list
ip netns exec test1 ip link
使用ip命令,如果没有的话安装一下:yum install net-tools
基本命令:
ip netns add nstest # 创建一个net namespace
ip netns list # 查看net namespace列表
ip netns delete nstest # 删除
ip netns exec [ns name] command # 到对应的ns里去执行命令
ip netns exec [ns name] bash # 在ns中使用bash,需要要ns中做一系列操作时方便
开启ns中的回环设备,以创建的nstest为例
ip netns exec nstest ip link set dev lo u
在主机上创建两个虚拟网卡两张网卡是linux设备对
ip link set add veth-a type veth peer name veth-b
添加veth-b到nstest中
ip link set veth-b netns nstes
验证:
[root@dev-86-208 ~]# ip netns exec nstest ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
251: veth-b@if252: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
link/ether aa:0a:7d:01:06:d3 brd ff:ff:ff:ff:ff:ff link-netnsid 0
为网卡设置ip并启动:
[root@dev-86-208 ~]# ip addr add 10.0.0.1/24 dev veth-a
[root@dev-86-208 ~]# ip link set dev veth-a up
[root@dev-86-208 ~]# ip netns exec nstest ip addr add 10.0.0.2/24 dev veth-b
[root@dev-86-208 ~]# ip netns exec nstest ip link set dev veth-b up
设置完ip,自动添加了这个路由
[root@dev-86-208 ~]# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 10.1.86.1 0.0.0.0 UG 100 0 0 eth0
10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 veth-a # 目的地址是10.0.0.0/24的就从这张网卡发出
10.1.86.0 0.0.0.0 255.255.255.0 U 100 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-4b03f208bc30
ns里面的路由表
[root@dev-86-208 ~]# ip netns exec nstest ip route
10.0.0.0/24 dev veth-b proto kernel scope link src 10.0.0.2
验证相互ping:
[root@dev-86-208 ~]# ip netns exec nstest ping 10.0.0.1
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.043 ms
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=0.032 ms
[root@dev-86-208 ~]# ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.069 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.024 ms
我们去创建两个ns(ns1 与 ns2)模拟两个容器,创建四张网卡(两对设备对)模仿容器网卡。
brtest
| +-------------+
|-veth1 <--|--> eth1 ns1 |
| |-------------+
|-veth2 <--|--> eth1 ns2 |
| +-------------+
再在宿主机上创建一个网桥brtest模拟docker0网桥,将veth1和veth2桥接到上面。
添加namespace:
[root@dev-86-208 ~]# ip netns add ns1
[root@dev-86-208 ~]# ip netns add ns2
[root@dev-86-208 ~]# ip netns list
ns2
ns1
test1 (id: 3)
nstest (id: 2)
[root@dev-86-208 ~]# ip netns exec ns1 ip link set dev lo up
[root@dev-86-208 ~]# ip netns exec ns2 ip link set dev lo up
添加网卡对:
[root@dev-86-208 ~]# ip link add veth1 type veth peer name eth1
[root@dev-86-208 ~]# ip link set eth1 netns ns1
[root@dev-86-208 ~]# ip link add veth2 type veth peer name eth1
[root@dev-86-208 ~]# ip link set eth1 netns ns2
[root@dev-86-208 ~]# ip netns exec ns1 ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
255: eth1@if256: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
link/ether ae:93:ba:2c:54:93 brd ff:ff:ff:ff:ff:ff link-netnsid 0
[root@dev-86-208 ~]# ip netns exec ns2 ip link
257: eth1@if258: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
link/ether 3a:a6:f3:27:9d:83 brd ff:ff:ff:ff:ff:ff link-netnsid 0
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
配置地址:
[root@dev-86-208 ~]# ip netns exec ns1 ip addr add 172.17.1.1/24 dev eth1
[root@dev-86-208 ~]# ip netns exec ns2 ip addr add 172.17.1.2/24 dev eth1
[root@dev-86-208 ~]# ip netns exec ns1 ip link set dev eth1 up
[root@dev-86-208 ~]# ip netns exec ns2 ip link set dev eth1 up
创建网桥:
[root@dev-86-208 ~]# brctl addbr brtest
[root@dev-86-208 ~]# ifconfig brtest
brtest: flags=4098<BROADCAST,MULTICAST> mtu 1500
ether 1e:60:eb:c1:e6:d0 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@dev-86-208 ~]# brctl addif brtest veth1
[root@dev-86-208 ~]# brctl addif brtest veth2
[root@dev-86-208 ~]# ifconfig brtest up
[root@dev-86-208 ~]# ifconfig veth1 up # 主机上这两张网卡工作在数据链路层,因此不需要设置ip也能通
[root@dev-86-208 ~]# ifconfig veth2 up
恭喜两个eth1之间可以通了:
[root@dev-86-208 ~]# ip netns exec ns1 ping 172.17.1.2
PING 172.17.1.2 (172.17.1.2) 56(84) bytes of data.
64 bytes from 172.17.1.2: icmp_seq=1 ttl=64 time=0.063 ms
64 bytes from 172.17.1.2: icmp_seq=2 ttl=64 time=0.022 ms
[root@dev-86-208 ~]# ip netns exec ns2 ping 172.17.1.1
PING 172.17.1.1 (172.17.1.1) 56(84) bytes of data.
64 bytes from 172.17.1.1: icmp_seq=1 ttl=64 time=0.038 ms
64 bytes from 172.17.1.1: icmp_seq=2 ttl=64 time=0.041 ms
当然想在主机上能ping通容器的话需要给brtest加ip:
[root@dev-86-208 ~]# ip addr add 172.17.1.254/24 dev brtest
[root@dev-86-208 ~]# route -n # 上面动作设置了路由
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
172.17.1.0 0.0.0.0 255.255.255.0 U 0 0 0 brtest
[root@dev-86-208 ~]# ping 172.17.1.1
PING 172.17.1.1 (172.17.1.1) 56(84) bytes of data.
64 bytes from 172.17.1.1: icmp_seq=1 ttl=64 time=0.046 ms
64 bytes from 172.17.1.1: icmp_seq=2 ttl=64 time=0.030 ms
以上操作就是docker bridge模式的模型
我们如果需要对容器网络进行配置,如修改ip地址,进入到容器里面显然不合适,而且有时不使用特权模式时是操作不了的,不过我们可以使用ip命令对其进行操作。
docker run -itd --name test ubuntu:14.04 /bin/bash
这时namespace其实已经建立了,不过使用ip命令看不到
docker inspect --format '{{ .State.Pid }}' test
3847
mkdir /var/run/netns # 如果不存在才创建
ln -s /proc/3847/ns/net /var/run/netns/test
测试:
# ip netns list
test
[root@dev-86-208 ~]# ip netns exec test1 ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
253: eth0@if254: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
升级内核:
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm
yum --enablerepo=elrepo-kernel install kernel-ml-devel kernel-ml
awk -F\' '$1=="menuentry " {print $2}' /etc/grub2.cfg
grub2-set-default 0
reboot
uname -a
安装docker:
yum install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum-config-manager --disable docker-ce-edge
yum makecache fast
yum install docker-ce
service docker start
安装open vswitch:
yum -y install wget openssl-devel gcc make python-devel openssl-devel kernel-devel graphviz kernel-debug-devel autoconf automake rpm-build redhat-rpm-config libtool python-twisted-core python-zope-interface PyQt4 desktop-file-utils libcap-ng-devel groff checkpolicy selinux-policy-devel
adduser ovs
su - ovs
yum localinstall /home/ovs/rpmbuild/RPMS/x86_64/openvswitch-2.5.0-1.el7.centos.x86_64.rpm -y
systemctl start openvswitch.service
systemctl is-active openvswitch
ovs-vsctl -V
systemctl enable openvswitch
安装pipework:
yum install git
git clone https://github.com/jpetazzo/pipework
cp pipework/pipework /bin
一些工具安装: yum install bridge-utils # 如果brctl不能用
yum install net-tools # 如果route命令不能用
启动四个容器:
docker run -itd --name con1 ubuntu:14.04 /bin/bash
docker run -itd --name con2 ubuntu:14.04 /bin/bash
docker run -itd --name con3 ubuntu:14.04 /bin/bash
docker run -itd --name con4 ubuntu:14.04 /bin/bash
创建ovs网桥并绑定端口
pipework ovs0 con1 192.168.0.1/24 @100
pipework ovs0 con2 192.168.0.2/24 @100
pipework ovs0 con3 192.168.0.3/24 @200
pipework ovs0 con4 192.168.0.4/24 @200
这样con1 和 con2是通的,con3和con4是通的,这个比较简单。pipework干的具体的事是:
ovs-vsctl add-port ovs0 [容器的虚拟网卡设备] tag=100
ovs划分vlan处理的原理也非常简单,包进入到switch时打上tag,发出去时去掉tag,发出去的端口与包的tag不匹配时不处理,这便实现了二层隔离。
access端口与trunk端口的区别是,trunk端口可接受多个tag。
准备两个主机,在host1上:
docker run -itd --name con1 ubuntu:14.04 /bin/bash
docker run -itd --name con2 ubuntu:14.04 /bin/bash
pipework ovs0 con1 192.168.0.1/24 @100
pipework ovs0 con2 192.168.0.2/24 @200
如果是单张网卡的话,把eth0桥接到switch上时会造成网络中断,所以以下几步不要通过ssh操作: 如果非得ssh去操作的话把以下命令放在一条命令中执行(用&&连接各个命令)
ovs-vsctl add-port ovs0 eth0
ifconfig ovs0 10.1.86.201 netmask 255.255.255.0 # 这里地址和掩码与eth0的配置一致
ifconfig ovs0 up
ifconfig eth0 0.0.0.0
route add default gw 10.1.86.1 # 执行之前看看eth0的gw是什么,保持一致,这样eth0就桥接到ovs0上去了。
查看switch端口:
[root@dev-86-204 ~]# ovs-vsctl show
c5ddf9e8-daac-4ed2-80f5-16e6365425fa
Bridge "ovs0"
Port "veth1pl41885"
tag: 100
Interface "veth1pl41885"
Port "ovs0"
Interface "ovs0"
type: internal
Port "eth0"
Interface "eth0"
Port "veth1pl41805"
tag: 200
Interface "veth1pl41805"
ovs_version: "2.5.1"
在host2上:
docker run -itd --name con3 ubuntu:14.04 /bin/bash
docker run -itd --name con4 ubuntu:14.04 /bin/bash
pipework ovs0 con3 192.168.0.3/24 @100
pipework ovs0 con4 192.168.0.4/24 @200
同样要桥接eth0到ovs0上,同host1的操作,然后con1与con3可通,con2与con4可通.
linux内核需要3.11以上,本尊在3.10内核上实践失败,在虚拟机中升级内核时虚拟机启动不了,CPU飙到100%,以后再试。
发现把容器直接挂ovs网桥上是可以通的
gre与下面的vxlan非常类似,只需要在添加往外连的端口时改成下面命令即可:
ovs-vsctl add-port ovs0 gre0 -- set interface gre0 type=gre options:remote_ip=172.31.244.185
host1:10.1.86.203
ovs0
|
|-veth1 <-------> eth1 192.168.0.3 con3
|
|-vxlan1-------------+
| |
|
host2:10.1.86.204 |
ovs0 |
| |
|-vxlan1------------+
|
|-veth1 <--------> eth1 192.168.0.4 con4
|
可以看到con3和con4在搭建vxlan之前是无法通信的。
在host1上:
[root@dev-86-203 ~]# docker run --name con3 -itd ubuntu:14.04 /bin/bash
[root@dev-86-203 ~]# ovs-vsctl add-br ovs0
[root@dev-86-203 ~]# pipework ovs0 con3 192.168.0.3/24 # 给容器分配地址并挂到ovs0上
[root@dev-86-203 ~]# ovs-vsctl add-port ovs0 vxlan1 -- set interface vxlan1 type=vxlan options:remote_ip=10.1.86.204 options:key=flow # 创建vxlan
[root@dev-86-203 ~]# ovs-vsctl show
5e371797-db70-451c-a0f2-d70c6d00cd05
Bridge "ovs0"
Port "veth1pl3342"
Interface "veth1pl3342"
Port "ovs0"
Interface "ovs0"
type: internal
Port "vxlan1"
Interface "vxlan1"
type: vxlan
options: {key=flow, remote_ip="10.1.86.204"}
ovs_version: "2.5.1"
host2上同理:
[root@dev-86-204 ~]# docker run --name con4 -itd ubuntu:14.04 /bin/bash
[root@dev-86-204 ~]# ovs-vsctl add-br ovs0
[root@dev-86-204 ~]# pipework ovs0 con4 192.168.0.4/24 # 给容器分配地址并挂到ovs0上
[root@dev-86-204 ~]# ovs-vsctl add-port ovs0 vxlan1 -- set interface vxlan1 type=vxlan options:remote_ip=10.1.86.203 options:key=flow # 创建vxlan
[root@dev-86-204 ~]# ovs-vsctl show
c5ddf9e8-daac-4ed2-80f5-16e6365425fa
Bridge "ovs0"
Port "ovs0"
Interface "ovs0"
type: internal
Port "veth1pl52846"
Interface "veth1pl52846"
Port "vxlan1"
Interface "vxlan1"
type: vxlan
options: {key=flow, remote_ip="10.1.86.203"}
ovs_version: "2.5.1"
验证:
[root@dev-86-204 ~]# docker exec con4 ping 192.168.0.3 # con4容器中ping con3的地址,可通
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.251 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.170 ms