Docker高級網絡實踐之 玩轉Linux network namespace & pipework
在上一篇文章中 《“深入淺出”來解讀Docker網絡核心原理》 大家了解了Docker中libnetwrok提供的4種驅動,它們各有千秋,但實際上每一種方式都有一定的局限性。假設需要運營一個數據中心的網絡,我們有許多的宿主機,每臺宿主機上運行了成千上萬個Docker容器,如果使用4種網絡驅動的話會是怎麽樣的呢,我們來分析一下:
- 使用host驅動可以讓容器與宿主機共用同一個網絡棧,這麽做看似解決了網絡問題,可實際上並未使用network namespace的隔離,缺乏安全性。
- 使用Docker默認的bridge驅動,容器沒有對外IP,只能通過NAT來實現對外通信。這種方式不能解決跨主機容器間直接通信的問題,難以滿足復雜場景下的業務需求。
- 使用overlay驅動,可以用於支持跨主機的網絡通信,但必須配合swarm進行配置和使用才能實現跨主機的網絡通信。
- 使用null驅動實際上不進行任何網絡配置。
?可見,為了實現數據中心大量容器間的跨主機網絡通信,為了更靈活地實現容器間網絡的共享與隔離,也為了在管理成千上萬個容器時可以更加自動化地進行網絡配置,我們需要來了解更高級的網絡方案。
?本文及後期的文章將通過一些工具和額外的操作來突破Docker網絡原有的限制,實現一些更高級的功能來滿足實際運用中的復雜需求。
把Linux network namespace玩起來
?在上一篇文章中已經介紹過了linux network namespace,在本文中我們將從實踐的角度來了解如何在linux系統下操作linux network namespace。
?ip是linux系統下一個強大的網絡配置工具,它不僅可以替代一些傳統的網絡管理工具,如ifconfig、route等,還可以實現更豐富的功能。下面將介紹如何使用ip命令來管理network namespace。
使用ip netns來操作network namespace
ip netns命令是用來操作network namespace的指令,具體使用方法如下。
-
創建一個network namespace:
#創建一個名為net-test的network namespace [root@ganbing ~]# ip netns add net-test
-
列出系統中已存在的network namespace:
[root@ganbing ~]# ip netns ls net-test
-
刪除一個network namespace:
[root@ganbing ~]# ip netns delete net-test
- 在network namespace中執行一條命令:
#命令格式
ip netns exec <network nameapce name> <command>
#比如顯示net-test namespace的網卡信息,路由信息
[root@ganbing ~]# ip netns exec net-test ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
[root@ganbing ~]# ip netns exec net-test route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
其實,你如果覺得ip netns exec 來執行命令比較麻煩,還可以使用啟動一個shell來配合:
#命令格式
ip netns exec <network nameapce name> bash
這樣,就可以在上面執行命令,就好像使用者進入了這個network namespace中;如果要退出這個bash,則輸入exit即可。
使用ip為network namespace配置網卡
?當使用ip netns add命令創建了一個network namespace後,就擁有了一個獨立的網絡空間,可以根據需求來配置該網絡空間,如添加網卡,配置IP,設置路由等。下面以之前建立的名為net-test的network namespace為例來演示如何進行這些操作。
?當使用ip命令創建一個network namespace時,會默認創建一個回環設備(loopback interface:lo)。該設備默認不啟動,最好將其啟動。
[root@ganbing ~]# ip netns exec net-test ip link set dev lo up
在主機上創建兩張虛擬網卡veth-1 和 veth-2:
[root@ganbing ~]# ip link add veth-1 type veth peer name veth-2
將veth-2設備添加到net-test這個network namespace中,veth-1留在宿主機中:
[root@ganbing ~]# ip link set veth-2 netns net-test
現在net-test這個network namespace就有兩塊網卡了(lo和veth-2),驗證看一下:
[root@ganbing ~]# ip netns exec net-test ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
222: veth-2@if223: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
link/ether 92:24:fd:44:c6:00 brd ff:ff:ff:ff:ff:ff link-netnsid 0
接下來可以為網卡分配IP並啟動網卡:
#在主機上為veth-1配置IP並啟動
[root@ganbing ~]# ip addr add 10.0.0.1/24 dev veth-1
[root@ganbing ~]# ip link set dev veth-1 up
#為net-test中的veth-2配置IP並啟動
[root@ganbing ~]# ip netns exec net-test ip addr add 10.0.0.2/24 dev veth-2
[root@ganbing ~]# ip netns exec net-test ip link set dev veth-2 up
?給兩張網卡配置了IP後,會在各自的network namespace中生成一條路由,用ip route 或者 route -n查看:
#在主機中查看路由
[root@ganbing ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
...
10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 veth-1
...
#在net-test中查看路由
[root@ganbing ~]# ip netns exec net-test route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 veth-2
?上面這兩條路由表明的意義是目的地址 10.0.0.0/24網絡的IP包分別從veth-1和veth-2發出。
?現在net-test這個network namespace有了自己的網卡、IP地址、路由表等信息,就相當於成了一臺小型的“虛擬機”了。測試一下它的連通性,來檢查配置是否正確。
?從主機的veth-1網卡ping net-test的veth-2網卡:
?從net-test的veth-2網卡ping主機的veth-1網卡:
將兩個network namespace連接起來
?很多時候,想搭建一個復雜的網絡環境來測試數據,往往受困於沒有足夠的資源來創建虛擬機。我們掌握了配置network namespace後,便可以解決這個問題。可以在一臺普通的機器上,以簡單的方式創建多個相互隔離的network namespace,然後通過網卡、網橋等虛擬設備將它們連接起來,組成想要的網絡拓撲。
?下面我們來演示一個簡單的例子,將兩個network namespace通過veth pair設備連起來。過程如下:
1、創建兩個network namespace ns1、ns2,名稱可自行定義:
[root@ganbing ~]# ip netns add ns1
[root@ganbing ~]# ip netns add ns2
[root@ganbing ~]# ip netns ls
ns2
ns1
2、創建veth pair設備veth-a,veth-b:
[root@ganbing ~]# ip link add veth-a type veth peer name veth-b
3、將網卡分別放到兩個network namespace中:
[root@ganbing ~]# ip link set veth-a netns ns1
[root@ganbing ~]# ip link set veth-b netns ns2
4、啟動這兩個網張:
[root@ganbing ~]# ip netns exec ns1 ip link set dev lo up
[root@ganbing ~]# ip netns exec ns1 ip link set dev veth-a up
[root@ganbing ~]# ip netns exec ns2 ip link set dev lo up
[root@ganbing ~]# ip netns exec ns2 ip link set dev veth-b up
5、分配IP:
[root@ganbing ~]# ip netns exec ns1 ip addr add 10.0.0.1/24 dev veth-a
[root@ganbing ~]# ip netns exec ns2 ip addr add 10.0.0.2/24 dev veth-b
6、驗證連通
?通過veth pair設備連接起來的兩個network namespace就好像直接通過網線連接起來的兩臺機器,它的拓撲圖如下所示:
?大家想一下,如果有更多的network namespace需要連接怎麽辦?是不是就需要引入虛擬網橋了,就如同Docker網絡一樣。
使用ip命令配置Docker容器網絡
?在上一篇文章 <“深入淺出”來解讀Docker網絡核心原理> 介紹過,Docker是使用Linux namespace技術進行資源隔離的,網絡也是如此。當用默認網絡模式(bridge模式)啟動一個Docker容器時,一定是在主機上新建了一個Linux network namespace。我們可以按照在network namespace中配置網絡的方法來配置Docker 容器的網絡。
?首先,啟動一個名為test1的Docker容器:
[root@ganbing ~]# docker run -itd --name test1 busybox
?然後,使用ip netns list命令查看是否可以看到新建的network namespace。執行命令後發現並沒有看到新建的network namespace。這並不代表Docker容器沒有創建network namespace,只是ip netns 命令無法查看而以,這個與ip netns命令工作方式有關。
?當使用ip netns命令創建了兩個network namespace(ns1、ns2)後,會在/var/run/netns目錄下看到ns1和ns2:
[root@ganbing ~]# ls -la /var/run/netns/
total 0
drwxr-xr-x 2 root root 80 Mar 19 18:25 .
drwxr-xr-x 40 root root 1240 Mar 19 15:08 ..
-r--r--r-- 1 root root 0 Mar 19 18:22 ns1
-r--r--r-- 1 root root 0 Mar 19 18:22 ns2
?ip netns list命令在/var/run/netns目錄下查找network namespace。由於Docker創建的network namespace並不在此目錄下創建任何選項,因此,需要一些額外的操作來使ip命令可以操縱Docker創建的network namespace。
?Linux下的每一個進程都會屬於一個特定的network namespace,來看一下不同network namespace環境中/pro/$PID/ns目錄下有何區別。
#/proc/self 鏈接到當前正在運行的進程
[root@ganbing ~]# ls -la /proc/self/ns/
......
lrwxrwxrwx 1 root root 0 Mar 19 19:17 net -> net:[4026531956]
......
#在ns1和ns2中
[root@ganbing ~]# ip netns exec ns1 ls -la /proc/self/ns
......
lrwxrwxrwx 1 root root 0 Mar 19 19:18 net -> net:[4026533018]
......
[root@ganbing ~]# ip netns exec ns2 ls -la /proc/self/ns
lrwxrwxrwx 1 root root 0 Mar 19 19:18 net -> net:[4026533116]
?從上面可以發現,不同network namespace中的進程有不同的net:[]號碼發配。這些號碼代表著不同的network namespace,擁有相同net:[]號碼的進程屬於同一個network namesapce。只要將代表Docker創建的network namesapce的文件鏈接到/var/run/netns目錄下,就可以使用ip netns命令操作了,步驟方法如下:
1、用docker inspect查看test1容器的PID
[root@ganbing ~]# docker inspect --format ‘{{.State.Pid}}‘ test1
17037
2、如果/var/run/netns目錄不存在,就要手工創建(一般都有),然後在/var/run/netns目錄下創建軟鏈接,指向test1容器的network namespace
[root@ganbing ~]# ln -s /proc/17037/ns/net /var/run/netns/test1
3、測試是否成功
[root@ganbing ~]# ip netns list
test1
ns2
ns1
[root@ganbing ~]# ip netns exec test1 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
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
226: eth0@if227: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
?完成上面的配置後,就可以自行配置Docker的網絡環境了。除了ip netns命令外,還有一些工具可以進入linux network namespace,比如nsenter。但需要額外的安裝這個工具。
把pipework玩起來
?Docker現有的網絡比較簡單,擴展性和靈活性都不能滿足很多復雜的應用場景。很多時候都需要自定義Docker容器的網絡。比如,為了使容器各節點之間通信、各節點和本地主機之間的通信,比較簡單的做法就是將Docker容器網絡配置到本地主機網絡的網段中。我們來看一下怎麽實現。
將Docker容器配置到本地網絡環境中
?如果想要使Docker容器和容器主機處於同一網絡,那麽容器和主機應該處在一個二層網絡中。就是把兩臺機器連在同一個交換機上,或者連在不同的級聯交換機上。在虛擬場影 下,虛擬網橋可以將容器連在一個二層網絡中,只要將主機的網卡橋接到虛擬網橋中,就能將容器和主機的網絡連起來,再給Docker容器分配一個本地局域網IP就OK了。
我們來通個一個例子分析一下這個過程 :本地網絡為 172.18.18.0/24,網關為 172.18.18.1,宿主機IP為172.18.18.34(網卡ens160),要在這臺宿主機上啟動一個名為test的Docker容器,並給它配置IP為 172.18.18.36。由於並不需要Docker提供的網絡,所以用--net=none參數來啟動容器。操作如下:
1、啟動一個test容器
[root@docker ~]# docker run -itd --name test01 --network none busybox
39ea5fac5ebb8bd25372d04efb6b662a18cd6fdf85105c22df0796087d776280
2、創建一個供容器連接的網橋br0
[root@docker ~]# brctl addbr br0
[root@docker ~]# ip link set dev br0
3、將主機ens160網卡橋接到br0上,並把ens160的IP配置在br0上。由於筆者是遠程操作服務器,所以執行這一步的時候會導致網絡斷開,因此這裏放在一條命令執行
[root@docker ~]# ip addr add 172.18.18.34/24 dev br0; > ip addr del 172.18.18.34/24 dev ens160; > brctl addif br0 ens160; > ip route del default; > ip route add default via 172.18.18.1 dev br0
4、找到test01的PID
[root@docker ~]# docker inspect --format ‘{{.State.Pid}}‘ test01
4557
5、將容器的network namespace添加到/var/run/netns/目錄下
[root@docker ~]# mkdir /var/run/netns
[root@docker netns]# ln -s /proc/4557/ns/net /var/run/netns/test01
6、創建用於連接網橋和Docker容器的網卡設備
#將veth-a連接到br0網橋中
[root@docker ~]# ip link add veth-a type veth peer name veth-b
[root@docker ~]# brctl addif br0 veth-a
[root@docker ~]# ip link set dev veth-a up
#將veth-b放在test的network namespace中,重命令eth0,並為其配置IP和默認路由
[root@docker ~]# ip netns exec test01 ip link set dev lo up
[root@docker ~]# ip link set veth-b netns test01
[root@docker ~]# ip netns exec test01 ip link set dev veth-b name eth0
[root@docker ~]# ip netns exec test01 ip link set eth0 up
[root@docker ~]# ip netns exec test01 ip addr add 172.18.18.36/24 dev eth0
[root@docker ~]# ip netns exec test01 ip route add default via 172.18.18.1
7、查看一下test01的網卡情況,並測試
[root@docker ~]# ip netns exec test01 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
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
9: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
link/ether 66:fa:71:ba:0e:fb brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.18.18.36/24 scope global eth0
valid_lft forever preferred_lft forever
[root@docker ~]# ip netns exec test01 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.18.18.1 0.0.0.0 UG 0 0 0 eth0
172.18.18.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
完成配置扣,Docker容器和宿主機連接的網絡圖如下所示:
現在test01容器可以與本地主機相互訪問,並且test01容器可以通過本地網絡的網關172.18.18.1訪問外部網絡。
來解析一下pipework
從上面的過程可以發現,配置Docker容器的網絡是相當繁瑣的。如果需要經常自定義Docker網絡,可以把上面的步驟編寫成shell腳本,這樣方便操作。事實上,目前已有了一個這樣的工具解脫我們繁瑣的步驟,就是由Docker公司工程師Jerome Petazzoni在Githu上發布的pipework的工具。pipwork號稱是容器的SDN解決方案,可以在復場景下將容器連接起來。其實隨著Docker網絡的不斷改進,piipwork工具的很多功能會被Docker原生支持,因此pipework當初只是過渡方案之一而以,大家只要知道了解就行了。下面來看一下pipework的功能。
* 支持linux網橋連接到容器並配置容器IP
1、下載pipework
[root@docker ~]# git clone https://github.com/jpetazzo/pipework
2、將pipework腳本放處指定的目錄,/usr/local/bin
[root@docker ~]# cp ./pipework/pipework /usr/local/bin/
3、對test01容器進行配置
[root@docker /]# docker run -itd --name test01 --network none busybox
[root@docker /]# pipework br0 test01 172.18.18.36/[email protected]
上面配置命令操作如下:
- 查看主機是否存在br0網橋,不存在就創建;
- 向test01中加入一塊名為eth1的網卡,並配置IP172.18.18.36/24;
- 若test01中已有默認路由,就刪除,把172.18.18.1設為默認路由l
- 將test01容器連接到之前創建的網橋上br0;
這個過程和之前采用ip命令配置的過程類似,pipework其實就是用shell寫的代碼。
pipework其實還有其它的很多功能,比如還支持open vswitch、支持dhcp獲取容器的IP等等,本文只要是和大家了解一下它的作用和功能,其它詳細的功能就不作介紹了。
看到這裏的朋友應該對network namespace和pipework有了更好的理解,喜歡我的文章朋友,請點擊最上方右角處的《關註》支持一下!
Docker高級網絡實踐之 玩轉Linux network namespace & pipework