1. 程式人生 > >Docker叢集(一) —— Docker網路及flannel介紹

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。“通常”的意思是如果dockerhost模式啟動則與主機在同一個名稱空間,後面還會講到。

這樣每個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

事實上,kubernetesservice-Pod之間的定址也是通過掛載路由規則實現的。Flannel也是通過路由規則使得docker0上的訊息可以發到flannel0,進而實現docker叢集中Node結點之間的互通。因本文並不是介紹這些開源工具的原理,所以不在路由表這上面做過多展開,我們只需知道kubernetesflannel等都是通過修改路由規則來解決他們的問題的,這樣在使用他們的時候不至於總是思考“咋通的呢”,“他背後是什麼東西實現的呢”。

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介紹

FlannelCoreOS團隊針對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檢視容器被分配的IPflanneldockerip172.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