Docker叢集(一) —— Docker網路及flannel介紹
【摘要】本文介紹docker網路原理和設定,以及在docker叢集中需要解決的問題。最後介紹flannel在解決docker網路問題中的作用。
1 基礎
在介紹docker的網路之前,必須先認識docker所依賴的幾個linux技術,這對理解docker的網路有幫助。因水平有限這一節僅簡單介紹,對linux網路原理感興趣的TX可以繼續深入研究。
1.1 網路名稱空間:
Linux Namespaces機制提供一種資源隔離方案。PID,IPC,Network等系統資源不再是全域性性的,而是屬於特定的Namespace。每個Namespace裡面的資源對其他Namespace都不可見。Linux提供6中名稱空間, 現在講的網路名稱空間是其中的一種。
一個Network Namespace提供了一份獨立的網路環境,包括網絡卡、路由、Iptable規則等都與其他的Network Namespace隔離。一個Docker容器“通常”會分配一個獨立的NetworkNamespace。“通常”的意思是如果docker以host模式啟動則與主機在同一個名稱空間,後面還會講到。
這樣每個docker容器就好像擁有了一套獨立的網路環境,甚至以為自己霸佔了全部的主機,也許這也是使人們經常認為容器就是虛機的原因之一吧~~~
1.2 Veth裝置對
Veth裝置對可以在不同的網路名稱空間之間通訊,用他們可以連線兩個網路名稱空間。一對veth裝置就像網線的兩頭一樣。
1.3 網橋
簡單來說,橋接就是把一臺機器上的若干個網路介面“連線”起來。其結果是,其中一個網口收到的報文會被複制給其他網口併發送出去。以使得網口之間的報文能夠互相轉發。類似交換機。
linux核心支援網口的橋接與交換機有一點點不同不同,交換機只是一個二層裝置,對於接收到的報文,要麼轉發、要麼丟棄。而執行著linux核心的機器本身就是一臺主機,有可能就是網路報文的目的地。其收到的報文除了轉發和丟棄,還可能被送到網路協議棧的上層(網路層),從而被自己消化。
在docker啟動時,會在主機上建立一個docker0網橋。通過docker0在同一個主機上的容器之間都可以通訊,外部的訊息也可以經過docker0
[email protected]:~$ ifconfig docker0 Link encap:乙太網 硬體地址 02:42:df:66:95:96 inet 地址:172.17.0.1 廣播:0.0.0.0 掩碼:255.255.0.0 inet6 地址: fe80::42:dfff:fe66:9596/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 躍點數:1 |
2 Docker網路模式
Docker有以下4種網路模式:
host模式,使用--net=host指定。
container模式,使用--net=container:NAME_or_ID指定。
none模式,使用--net=none指定。
bridge模式,使用--net=bridge指定,預設設定。
2.1 Bridge模式
2.1.1 模式介紹
我們重點講這個模式。Bridge是預設模式,正常docker啟動時都已這個模式啟動。在這個模式下當Docker server啟動時,會在主機上建立一個名為docker0的虛擬網橋,此主機上啟動的Docker容器會連線到這個虛擬網橋上。Docker0擁有一個自己的ip地址,有的書上說是172.17.42.1,但是我的機器上是172.17.0.1。總之是個172段的內部地址,機器外是訪問不了的。
[email protected]:~$ ifconfig docker0 Link encap:乙太網 硬體地址 02:42:df:66:95:96 inet 地址:172.17.0.1 廣播:0.0.0.0 掩碼:255.255.0.0 inet6 地址: fe80::42:dfff:fe66:9596/64 Scope:Link UP BROADCAST MULTICAST MTU:1500 躍點數:1 接收資料包:7 錯誤:0 丟棄:0 過載:0 幀數:0 傳送資料包:30 錯誤:0 丟棄:0 過載:0 載波:0 碰撞:0 傳送佇列長度:0 接收位元組:480 (480.0 B) 傳送位元組:4909 (4.9 KB) eth0 Link encap:乙太網 硬體地址 08:00:27:02:6c:8b inet 地址:10.43.86.110 廣播:10.43.86.255 掩碼:255.255.255.0 inet6 地址: fe80::a00:27ff:fe02:6c8b/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 躍點數:1 接收資料包:140296 錯誤:0 丟棄:0 過載:0 幀數:0 傳送資料包:30286 錯誤:0 丟棄:0 過載:0 載波:0 碰撞:0 傳送佇列長度:1000 接收位元組:9847208 (9.8 MB) 傳送位元組:2946052 (2.9 MB) lo Link encap:本地環回 inet 地址:127.0.0.1 掩碼:255.0.0.0 inet6 地址: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 躍點數:1 接收資料包:130 錯誤:0 丟棄:0 過載:0 幀數:0 傳送資料包:130 錯誤:0 丟棄:0 過載:0 載波:0 碰撞:0 傳送佇列長度:0 接收位元組:17199 (17.1 KB) 傳送位元組:17199 (17.1 KB) |
Docker容器處在自己的網路名稱空間中,容器之間怎麼互通呢,就是連這個docker0網橋。這裡就用到前面另一個概念veth對。可以把veth對看成網線的兩頭,他一頭在容器裡另一頭在主機上。下面我們建個容器,然後看看主機上ip的變化。
[email protected]:~$ docker run -it ubuntu //// 啟動一個ubuntu映象 [email protected]:/# ifconfig ///////// 容器裡的ip地址 eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:03// 這個硬體地址也是docker分配的 inet addr:172.17.0.3 Bcast:0.0.0.0 Mask:255.255.0.0 inet6 addr: fe80::42:acff:fe11:3/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:18 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:2912 (2.9 KB) TX bytes:508 (508.0 B) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 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) |
[email protected]:~$ ifconfig ///////主機的地址 docker0 Link encap:乙太網 硬體地址 02:42:df:66:95:96 inet 地址:172.17.0.1 廣播:0.0.0.0 掩碼:255.255.0.0 inet6 地址: fe80::42:dfff:fe66:9596/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 躍點數:1 接收資料包:14 錯誤:0 丟棄:0 過載:0 幀數:0 傳送資料包:30 錯誤:0 丟棄:0 過載:0 載波:0 碰撞:0 傳送佇列長度:0 接收位元組:960 (960.0 B) 傳送位元組:4909 (4.9 KB) eth0 Link encap:乙太網 硬體地址 08:00:27:02:6c:8b inet 地址:10.43.86.110 廣播:10.43.86.255 掩碼:255.255.255.0 inet6 地址: fe80::a00:27ff:fe02:6c8b/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 躍點數:1 接收資料包:207853 錯誤:0 丟棄:0 過載:0 幀數:0 傳送資料包:30418 錯誤:0 丟棄:0 過載:0 載波:0 碰撞:0 傳送佇列長度:1000 接收位元組:14099886 (14.0 MB) 傳送位元組:2966220 (2.9 MB) lo Link encap:本地環回 inet 地址:127.0.0.1 掩碼:255.0.0.0 inet6 地址: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 躍點數:1 接收資料包:154 錯誤:0 丟棄:0 過載:0 幀數:0 傳送資料包:154 錯誤:0 丟棄:0 過載:0 載波:0 碰撞:0 傳送佇列長度:0 接收位元組:20370 (20.3 KB) 傳送位元組:20370 (20.3 KB) veth8e0ee35 Link encap:乙太網 硬體地址 5a:34:bf:13:00:f5 /// 這時在主機名稱空間裡面出現了一個veth,即veth對的一頭。另一頭在容器裡呢,並且被容器改名為eth0了。 inet6 地址: fe80::5834:bfff:fe13:f5/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 躍點數:1 接收資料包:7 錯誤:0 丟棄:0 過載:0 幀數:0 傳送資料包:21 錯誤:0 丟棄:0 過載:0 載波:0 碰撞:0 傳送佇列長度:0 接收位元組:578 (578.0 B) 傳送位元組:3516 (3.5 KB) |
好了現在可以看到幾個結論。在bridge模式下:
主機上面會有一個docker0的網橋
每個容器都與docker0連通,所以同主機上的容器之間也連通
每個主機上容器的地址都是從172.17.0.2開始往後分
1.1.1 通訊
Docker會修改系統的路由規則來控制訊息收發,這節就表達這一個意思。
在docker啟動之後通過iptables-save檢視,可以看到關於docker0的幾個規則,他們使得docker0網橋可以收發訊息。同一主機中的容器之間就可以通訊。
[email protected]:~$ sudo iptables-save # Generated by iptables-save v1.4.21 on Wed Jan 27 10:49:15 2016 *nat :PREROUTING ACCEPT [10631:1010228] :INPUT ACCEPT [1268:199346] :OUTPUT ACCEPT [3169:190771] :POSTROUTING ACCEPT [3170:190855] :DOCKER - [0:0] -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE COMMIT # Completed on Wed Jan 27 10:49:15 2016 # Generated by iptables-save v1.4.21 on Wed Jan 27 10:49:15 2016 *filter :INPUT ACCEPT [15605:2501897] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [31535:2534237] :DOCKER - [0:0] -A FORWARD -o docker0 -j DOCKER -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -i docker0 ! -o docker0 -j ACCEPT -A FORWARD -i docker0 -o docker0 -j ACCEPT COMMIT # Completed on Wed Jan 27 10:49:15 2016 |
如果使用-p引數將容器埠暴露出來docker run -it -p 2200:22 -d ubuntu,再通過iptables-save檢視資訊,會發現多出了兩條規則。他們使得傳送到主機地址和2200埠的訊息可以送到docker0上,從而進入容器內。這樣就使得容器內可以和主機外通訊。
[email protected]:~$ sudo iptables-save # Generated by iptables-save v1.4.21 on Wed Jan 27 10:56:17 2016 *nat :PREROUTING ACCEPT [40:4571] :INPUT ACCEPT [4:623] :OUTPUT ACCEPT [9:558] :POSTROUTING ACCEPT [9:558] :DOCKER - [0:0] -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE -A POSTROUTING -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 22 -j MASQUERADE -A DOCKER ! -i docker0 -p tcp -m tcp --dport 2200 -j DNAT --to-destination 172.17.0.4:22 COMMIT # Completed on Wed Jan 27 10:56:17 2016 # Generated by iptables-save v1.4.21 on Wed Jan 27 10:56:17 2016 *filter :INPUT ACCEPT [49:8589] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [47:6227] :DOCKER - [0:0] -A FORWARD -o docker0 -j DOCKER -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -i docker0 ! -o docker0 -j ACCEPT -A FORWARD -i docker0 -o docker0 -j ACCEPT -A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 22 -j ACCEPT COMMIT # Completed on Wed Jan 27 10:56:17 2016 |
事實上,kubernetes的service-Pod之間的定址也是通過掛載路由規則實現的。Flannel也是通過路由規則使得docker0上的訊息可以發到flannel0,進而實現docker叢集中Node結點之間的互通。因本文並不是介紹這些開源工具的原理,所以不在路由表這上面做過多展開,我們只需知道kubernetes、flannel等都是通過修改路由規則來解決他們的問題的,這樣在使用他們的時候不至於總是思考“咋通的呢”,“他背後是什麼東西實現的呢”。
1.1.2 問題
Bridge模式可以解決同主機內容器的通訊,同時可以看到幾個問題:
docker容器之間想要連通需要在同一臺主機上,跨主機無法通訊
容器的ip地址都docker分配的,一般都從172.17.0.2開始,不同主機上的容器地址有可能是相同的。Docker叢集要解決這個問題,不能讓他們相同。
在解決這些問題之前,我們先把另外docker的三個模式簡單介紹一下。後面介紹的flannel是解決這些問題的方案之一。
1.2 Host模式
如果啟動容器的時候使用host模式,那麼這個容器將不會獲得一個獨立的Network Namespace,而是和宿主機共用一個Network Namespace。容器將不會虛擬出自己的網絡卡,配置自己的IP等,而是使用宿主機的IP和埠。
當我們在容器中執行任何類似ifconfig命令檢視網路環境時,看到的都是宿主機上的資訊。而外界訪問容器中的應用,則直接使用物理機器地址即可,就如直接跑在宿主機中一樣。但是,容器的其他方面,如檔案系統、程序列表等還是和宿主機隔離的。
1.3 Container模式
這個模式指定新建立的容器和已經存在的一個容器共享一個Network Namespace,而不是和宿主機共享。新建立的容器不會建立自己的網絡卡,配置自己的IP,而是和一個指定的容器共享IP、埠範圍等。同樣,兩個容器除了網路方面,其他的如檔案系統、程序列表等還是隔離的。
kubernetes中一個Pod內的容器共享網路,就是在pod內有容器使用了這種網路模式。
1.4 None模式
在這種模式下,Docker容器擁有自己的Network Namespace,但是,並不為Docker容器進行任何網路配置。也就是說,這個Docker容器沒有網絡卡、IP、路由等資訊。需要我們自己為Docker容器新增網絡卡、配置IP等。
2 Flannel介紹
Flannel是CoreOS團隊針對Kubernetes設計的一個網路規劃服務,簡單來說,它的功能是讓叢集中的不同節點主機建立的Docker容器都具有全叢集唯一的虛擬IP地址。並且連通主機節點的網路。
但在預設的Docker配置中,每個節點上的Docker服務會分別負責所在節點容器的IP分配。這樣導致的一個問題是,不同節點上容器可能獲得相同的內外IP地址。Flannel的設計目的就是為叢集中的所有節點重新規劃IP地址的使用規則,從而使得不同節點上的容器能夠獲得“同屬一個內網”且”不重複的”IP地址,並讓屬於不同節點上的容器能夠直接通過內網IP通訊。預設的節點間資料通訊方式是UDP轉發。
下圖來自網路:
簡單的說flannel做了三件事情:
1. 資料從源容器中發出後,經由所在主機的docker0虛擬網絡卡轉發到flannel0虛擬網絡卡,這是個P2P的虛擬網絡卡,flanneld服務監聽在網絡卡的另外一端。 Flannel也是通過修改Node的路由表實現這個效果的。
2. 源主機的flanneld服務將原本的資料內容UDP封裝後根據自己的路由表投遞給目的節點的flanneld服務,資料到達以後被解包,然後直接進入目的節點的flannel0虛擬網絡卡,然後被轉發到目的主機的docker0虛擬網絡卡,最後就像本機容器通訊一樣由docker0路由到達目標容器。
3. 使每個結點上的容器分配的地址不衝突。Flannel通過Etcd分配了每個節點可用的IP地址段後,再修改Docker的啟動引數。“--bip=X.X.X.X/X”這個引數,它限制了所在節點容器獲得的IP範圍。
1 Flannel安裝和使用
1.1 安裝flannel
解壓檔案,tar -zxvf xxx.tar
Flannel路徑在:
在系統中增加兩個檔案:
在 /etc/init/ 增加flanneld.conf檔案,內容見最下面。
在 /etc/default/ 增加flanneld檔案,內容見最下面。
1.2 啟動ETCD:
前面說到了,flannel需要通過ETCD管理每個結點分配的地址段。所以先啟動etcd。當然在kubernetes集群系統裡面,kubernetes也要求啟動etcd。這裡提一下,需要先啟動flannel之後再通過kubectl啟動容器,因為需要通過flannel限制docker容器的ip地址段。看完後面就理解了。
./etcd--listen-client-urls=http://0.0.0.0:4001 --listen-peer-urls=http://0.0.0.0:7001&
設定本叢集的容器ip地址段。
etcdctl rm /coreos.com/network/ --recursive
./etcdctlmk /coreos.com/network/config '{"Network":"172.200.0.0/16"}'
設定完可以檢視一下:
etcdctl get /coreos.com/network/config {"Network":"172.200.0.0/16"} |
Network是本叢集docker容器可分配的程式碼段,由flannel管理。不能和機器實際物理結點ip衝突,最好搞個和誰都不衝突的,隨便寫。
1.3 啟動flannel
執行:ip link set dev docker0 down
執行:brctl delbr docker0
進入flannel/bin 路徑,
sudo./flanneld -etcd-endpoints=http://ETCD所在機器的IP地址:4001-iface=eth0&
[email protected]:/usr/bin$ sudo ./flanneld -etcd-endpoints=http://10.43.86.110:4001 [sudo] password for het: I0120 21:31:00.282318 30969 main.go:275] Installing signal handlers I0120 21:31:00.788024 30969 main.go:130] Determining IP address of default interface I0120 21:31:00.925879 30969 main.go:188] Using 10.43.86.110 as external interface I0120 21:31:01.025180 30969 main.go:189] Using 10.43.86.110 as external endpoint I0120 21:31:01.331261 30969 etcd.go:204] Picking subnet in range 172.200.1.0 ... 172.200.255.0 I0120 21:31:01.418648 30969 etcd.go:84] Subnet lease acquired: 172.200.59.0/24 I0120 21:31:02.359643 30969 udp.go:222] Watching for new subnet leases |
Flannel啟動之後會建立一個檔案subnet.env ,可以開啟看一下
[email protected]:/run$ vi flannel/subnet.env FLANNEL_NETWORK=172.200.0.0/16 ////這個就是在etcd裡面設定的地址段 FLANNEL_SUBNET=172.200.59.1/24 /// 這個就是為本結點分配的容器地址段 FLANNEL_MTU=1472 FLANNEL_IPMASQ=false |
下面讓flannel產生的地址段生效,控制docker容器的ip地址分配。
執行:source /run/flannel/subnet.env
執行:sudo rm /var/run/docker.pid
執行:sudo docker -d --bip=${FLANNEL_SUBNET}--mtu=${FLANNEL_MTU} &
可以看到上面的操作就是把flannel啟動起來,把他的地址段提出來,然後設定一下docker的啟動引數。
好了,下面可以測試一下。
測試:執行docker run -it ubuntu /bin/bash
ifconfig檢視容器被分配的IP。非flannel時docker的ip是172.17.0.X,如果上面的成功,此時分配的IP應該為172.200.59.x。
至此一個docker叢集的網路就打通了,在另一個主機上也是按這樣操作,注意啟動flannel時要指定到同一個etcd。
附錄 flanneld.conf
description "Flannel service" author "@chenxingyu" start on (net-device-up and local-filesystems and runlevel [2345]) stop on runlevel [016] respawn respawn limit 10 5 pre-start script FLANNEL=/usr/bin/$UPSTART_JOB if [ -f /etc/default/$UPSTART_JOB ]; then . /etc/default/$UPSTART_JOB fi if [ -f $FLANNEL ]; then exit 0 fi exit 22 end script script # modify these in /etc/default/$UPSTART_JOB (/etc/default/docker) FLANNEL=/usr/bin/$UPSTART_JOB FLANNEL_OPTS="" if [ -f /etc/default/$UPSTART_JOB ]; then . /etc/default/$UPSTART_JOB fi exec "$FLANNEL" $FLANNEL_OPTS end script |