Docker學習之四:容器虛擬化網絡與docker網絡

虛擬化網絡的原理簡述

Linux內核支持六種名稱空間,只要在用戶空間有相應的客戶端工具,都可以對對應的名稱空間進行操作。

假如物理機有四塊網卡,可以創建兩個名稱空間,此時可靈活將網卡分配到單獨的名稱空間中。

一般一個設備只能屬於一個名稱空間,如此每個名稱空間都可以配置IP地址,並且和外部通信。

假如名稱空間數量超過物理網卡數量,而名稱空間中的進程必須通過物理網卡對外通信,如何處理?可以使用虛擬網卡設備,使用純軟件的方式模擬出網卡設備。

在Linux內核級別,支持兩種模擬:二層設備和三層設備。

二層設備(鏈路層)

鏈路層,實現報文轉發的設備。

利用內核對二層設備的模擬,創建虛擬網卡接口,這種網絡接口是成對出現的,模擬爲一根網線的兩頭。其中一頭插在主機上,另一頭插在交換機上。

內核原生支持二層虛擬網橋設備,用軟件來構建交換機。比如bridge-utils工具的brctl來實現。

利用軟件交換機和軟件實現的名稱空間,如此就可模擬一個主機連接到交換機中,以實現網絡連接的功能。兩個名稱空間相當於連接到同一臺交換機中的兩臺主機。

以上就是虛擬化網絡,是網絡虛擬化技術的一種簡單實現。

三層設備(軟件交換機)

OVS:Open VSwitch 開源的虛擬交換機,能模擬實現高級的三層網絡設備如VLAN,VxLAN,GRE等等,它不屬於Linux內核本身的模塊,因此需要額外安裝。它是由思科等衆多網絡設備生產公司開發的,其功能非常強大。

SDN:軟件定義網絡/軟件驅動網絡,需要在硬件層面上支持虛擬化網絡,還需要在每個主機上構建複雜的虛擬化網絡,來運行多個虛擬機或者容器。

容器間通信

如果在同一個host上的兩個namespace需要通信,可以在host上建立虛擬交換機,使用純軟件的方式建立一對網卡,一半在容器上,一半在交換機上。此時只要容器配置同子網的IP地址,即可通信。

如果存在多個交換機,比如兩個軟件交換機,各自連接不同的容器。比如:

在這裏插入圖片描述

可以創建一對網卡,一半在SW1上,一半在容器中。(docker的默認網絡模型就是這個)

如果希望C1僅僅和C3通信,則需要在交換機中間添加路由器,而路由器是三層設備,可以在Linux內核的模塊來單獨來支持,比如第三個容器(中的內核)來實現報文轉發。

如果希望跨主機通信,比如C1和C5通信,如何實現?
在這裏插入圖片描述

方式有:

  • 橋接:

    把host的物理網卡當做交換機使用,各個容器都有自己的虛擬網卡/mac地址,如果需要和本host通信,則將目的mac指向host的虛擬網卡即可。如此可以讓C1和C5通信。

    然而這種方式的代價很大,如果都是橋接則所有容器/主機都在同一網絡平面上,非常容易產生風暴,隔離性很差。

    因此,在大規模的虛擬化或者容器的場景中,不能選用這種方式。除非使用大二層的網絡技術將其隔離,否則,都不應該直接橋接。

  • NAT

    如果希望對外通信,則應該使用nat技術而不是橋接。

    如果C3和C5通信,C3將網關指向了S1,物理機上打開核心轉發功能。報文:C3 -> S1 ->(路由表,轉發) -> 外部網絡。但是報文無法回來,因爲C3是私有地址。因此,在C3的報文離開主機前,將IP轉換爲S1主機上的IP地址,這就是源地址轉換。

    如此C5可直接回復給S1。而S1內部的nat表可知道,該報文實際上是屬於C3的,因此會自動轉發給C3。

    上述通信必須經過nat實現,而且兩級的nat代理。因爲C5可能也是nat內部。因此S1並看不到C5,除非將C5 DNAT發佈出去。

    因此,比如將C5發佈到S2的地址和端口,由S2自動將其請求轉換到C5中。

    經過SNAT和DNAT轉換,效率不高。而且通信的雙方並看不到真正的對方。其好處是,網絡易於管理。

  • 疊加網絡Overlay Network

    這種網絡方式不用完全暴露主機,也不用完全隱藏主機。其方式如下:

    • 多個host,創建虛擬橋,讓VM連接到虛擬橋上。
    • 在虛擬橋上創建隧道,讓C3直接看到C5
    • 物理機可直接通信,C3和C5在同一個地址段內。C3先將報文送給橋,而橋知道C5並不在本地,於是將報文通過物理網卡發出去:在使用隧道轉發出去前,報文C3|C5再封裝一層IP首部:H1|H2
    • H2拆開第一層封裝,看到C3|C5,於是將報文轉發給C5.

    上述實現兩級的三層封裝,使用一個IP承載另一個IP,這就是隧道技術。如此C3和C5就可以直接通信。

模擬容器間通信

使用ip命令,可以在一臺主機中構建出一個網絡名稱空間,並且可模擬容器間通信。

root@eto:~# ip netns help
Usage: ip netns list
       ip netns add NAME
       ip netns set NAME NETNSID
       ip [-all] netns delete [NAME]
       ip netns identify [PID]
       ip netns pids NAME
       ip [-all] netns exec [NAME] cmd ...
       ip netns monitor
       ip netns list-id

通過這種方式只有網絡名稱空間是隔離的,而其他名稱空間都是共享的。和容器略有不同。

  • 創建名稱空間

    root@eto:~# ip netns add r1
    root@eto:~# ip netns add r2
    root@eto:~# ip netns list
    r2
    r1
    
  • 創建對應的虛擬網卡對,然後分配到名稱空間中

    root@eto:~# ip link add name veth1.1 type veth peer name veth1.2
    root@eto:~# ip link show
    ...
    9: [email protected]: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
        link/ether 6a:f0:ef:a0:76:80 brd ff:ff:ff:ff:ff:ff
    10: [email protected]: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    
    # 把veth1.2分配到r1中去
    root@eto:~# ip link set dev veth1.2 netns r1
    root@eto:~# ip a s
    ...
    10: veth1.1@if9: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
        link/ether 0e:33:73:c9:73:fc brd ff:ff:ff:ff:ff:ff link-netnsid 0
        
    
    root@eto:~# ip netns exec r1 ifconfig -a
    lo: flags=8<LOOPBACK>  mtu 65536
            loop  txqueuelen 1000  (Local Loopback)
            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
    
    veth1.2: flags=4098<BROADCAST,MULTICAST>  mtu 1500
            ether 6a:f0:ef:a0:76:80  txqueuelen 1000  (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
    
  • 修改網卡名稱,設置IP地址並激活

    root@eto:~# ip netns exec r1 ip link set dev veth1.2 name eth0
    root@eto:~# ip netns exec r1 ifconfig eth0 10.1.0.2/24 up
    root@eto:~# ifconfig veth1.1 10.1.0.1/24 up
    root@eto:~# ping 10.1.0.2
    PING 10.1.0.2 (10.1.0.2) 56(84) bytes of data.
    64 bytes from 10.1.0.2: icmp_seq=1 ttl=64 time=0.054 ms
    64 bytes from 10.1.0.2: icmp_seq=2 ttl=64 time=0.036 ms
    

    此時兩張網卡可通信,有宿主機 -> r1

  • 將另一半網卡挪到r2,並激活

    root@eto:~# ip link set dev veth1.1 netns r2
    root@eto:~# ip netns exec r2 ifconfig veth1.1 10.1.0.3/24 up
    root@eto:~# ip netns exec r2 ping 10.1.0.2
    PING 10.1.0.2 (10.1.0.2) 56(84) bytes of data.
    64 bytes from 10.1.0.2: icmp_seq=1 ttl=64 time=0.037 ms
    64 bytes from 10.1.0.2: icmp_seq=2 ttl=64 time=0.034 ms
    

    使用這種方式來測試網絡通信模型,和虛擬機幾乎無區別。可以模擬物理橋,僅主機和nat橋。

Docker的網絡

Docker安裝完成後,自動提供三種網絡:

  • bridge
  • host
  • none
root@eto:~# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
fd3713beb21e        bridge              bridge              local
509f1fdd50fb        host                host                local
83fea3e78ba1        none                null                local

Bridge:本機上創建的軟交換機,也可以當做網卡使用。當啓動容器時,可自動讓容器自動分配一對網卡,一半在容器上,一半在交換機上。比如:

# 啓動容器
root@eto:~# docker run --name t1 -it jaywin/httpd:v0.1-1
/ # 

# 宿主機上,關聯到docker0上的網卡設備,其中
root@eto:~# ip a s
...
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:72:ff:55:a2 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
...
6: veth2fadfaf@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 

veth2fadfaf@if5 中的if5在容器中,另一端veth2fadfaf在宿主機中。它們連接到了docker0橋上。

root@eto:~# brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.024272ff55a2       no              veth2fadfaf(宿主機的接口)

Docker0橋默認是一個nat橋,創建容器後會自動分配iptables規則:

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0    

只要從docker0橋的網段發出的報文,自動選擇一個合適的源地址做SNAT(MASQUERADE)。如此同一個宿主機下的容器,都可以相互通信(屬於docker0的二層網絡),並且容器可以和外部通信(實現了SNAT)。

如果有一個nginx容器,提供服務。另外一個容器也是連接到橋上來的,則可以直接當客戶端來訪問nginx。通過如下方式:

  • 物理機上也能直接訪問(可以看成是僅主機的橋)
  • 如果是外部的物理機訪問nginx,它是無法看到nginx容器,因爲nat將容器隱藏在背後。此時需要使用dnat將nginx發佈

因此,當使用默認的橋接網絡時,只能通過dnat規則以便讓外部的客戶端訪問該容器。

如果同一臺主機擁有兩個web容器,如何對外做dnat?畢竟只有一個80端口,如何能正常提供訪問?如果使用默認的網絡模型,並沒有很好的解決方法。

如果使用overlay網絡,則無需有上述的困擾,直接訪問對應的web服務即可。關於overlay網絡,暫不表述。

除了上述描述的方式,還有什麼方法可以使得容器可以對外服務?比如:

讓每個容器僅擁有user,mount,pid是隔離的,兩個容器共享UTS,Net,IPC。如此,容器的文件系統,pid和用戶是隔離的,而網絡是同一組,同一個網絡協議棧,同一個域名主機名。容器間的通信可直接通過lo實現。

共享的一般是和網絡通信相關的資源。

也可以讓容器直接使用物理名稱空間。

如果容器僅使用none網絡,則容器僅有lo,沒有網卡,無網絡通信功能

Docker網絡模型

Docker創建容器時,使用選項–network來指定使用哪種網絡模型。默認default爲bridge (docker0)

查看網絡的詳細信息:

root@eto:~# docker network inspect bridge

Docker的網絡名稱空間和UTS,IPC是可以讓容器共享的,因此可構建出隔離,橋接,nat等網絡模型。

docker網絡模型

img

四種網絡模型:

  • 封閉式容器
  • 橋接式容器:容器包含虛擬網絡設備,一半在docker0上,一半在名稱空間中。默認情況下,容器是啓動爲橋接式的網絡模型。
  • 聯盟式容器:容器a有自己的名稱空間,容器b共享容器a的名稱空間。可以讓a和b的容器應用在本地通信
  • 容器共享宿主機的名稱空間

可以指定爲封閉式容器:

root@eto:~# docker run --name t4 -it --network none --rm busybox:latest
/ # ifconfig -a
lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          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:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

Bridge可以實現不同容器間通信,而主機名默認爲容器id,可使用hostname設置或者run的時候指定hostname,從容器外注入主機名。

# 查看默認主機名
root@eto:~# docker run --name t4 -it --network bridge --rm busybox:latest hostname
3ccdfa0745a3


# 注入主機名
root@eto:~# docker run --name t4 -it --network bridge -h t2.jaywin.com --rm busybox:latest 
/ # hostname
t2.jaywin.com

也可以注入解析,默認情況下容器使用宿主機的DNS配置。

/ # 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.2      t2.jaywin.com t2

可自定義DNS服務器

root@eto:~# docker run --name t4 -it --network bridge -h t2.jaywin.com --dns 114.114.114.114 --rm busybox:latest cat /etc/resolv.conf
search midea.com.cn
nameserver 114.114.114.114

定義搜索域

root@eto:~# docker run --name t4 -it --network bridge -h t2.jaywin.com --dns 114.114.114.114 --dns-search ilinux.io --rm busybox:latest cat /etc/resolv.conf
search ilinux.io
nameserver 114.114.114.114

添加hosts記錄

root@eto:~# docker run --name t4 -it --network bridge -h t2.jaywin.com --dns 114.114.114.114 --dns-search ilinux.io --add-host www.jaywin.com:1.1.1.1 --rm busybox:latest 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
1.1.1.1 www.jaywin.com
172.17.0.2      t2.jaywin.com t2

服務的暴露方式

使用-p選項進行服務暴露

動態端口暴露

root@eto:~# docker run --name w1 --rm -p 80 jaywin/httpd:v0.2

root@eto:~# docker port w1
80/tcp -> 0.0.0.0:32768
# 暴露隨機端口

實際上,通過DNAT規則將服務發佈出去。

Chain DOCKER (2 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:32768 to:172.17.0.2:80
# 只要訪問本機的32768端口,將DNAT到172.17.0.2:80
# 當刪除容器時,nat規則也會一併移除

固定地址的動態端口暴露

root@eto:~# docker run --name w1 --rm -p 192.168.2.10::80 jaywin/httpd:v0.2

root@eto:~# docker port w1
80/tcp -> 192.168.2.10:32768

固定端口暴露

# 將端口暴露在宿主機的指定端口:
root@eto:~# docker run --name w1 --rm -p 80:80 jaywin/httpd:v0.2

root@eto:~# docker port w1
80/tcp -> 0.0.0.0:80

-p選項可以使用多次,以暴露多個端口。

暴露所有端口

-P選項或者–publish-all,將容器所有計劃要暴露的端口全部映射至主機端口。

計劃 要暴露的端口需要使用–expose選項指定

# docker run -d -P --expose 2222 --expose 3333 --name web busybox:latest /bin/httpd -p 2222 -f

聯盟式容器Joined containers

聯盟式容器是指使用某個已存在容器的網絡接口的容器,接口被聯盟內各容器共享使用。因此,聯盟式容器彼此間完全無隔離。

聯盟式容器彼此間雖然共享同一個網絡名詞空間,但其他名稱空間如User、Mount等還是隔離的

聯盟式容器彼此間存在端口衝突的可能。因此,通常只會在多個容器上的程序需要程序loopback接口相互通信,或者對某個已存在的容器的網絡屬性進行監控時,才使用這種模式的網絡模型

創建聯盟式容器

root@eto:~# docker run --name b2 --rm --network container:w1 -it busybox:latest
/ # ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02  
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:16 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:1256 (1.2 KiB)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          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:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


# 此時b2和W1共享網絡名稱空間,而其他資源是隔離的。此時,b2可通過lo口訪問w1的httpd服務。
# 如此相當於同一主機上的兩個進程。

/ # wget -O - -q 127.0.0.1
busybox http server
# 容器和宿主機的聯盟式網絡
root@eto:~# docker run --name b2 --rm --network host -it busybox:latest
/ # ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:72:FF:55:A2  
          inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0
          inet6 addr: fe80::42:72ff:feff:55a2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:19 errors:0 dropped:0 overruns:0 frame:0
          TX packets:38 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1289 (1.2 KiB)  TX bytes:3578 (3.4 KiB)

如此一來,將十分簡便地部署應用,並利用宿主機的網絡。

修改docker0上的網絡信息

自定義docker0橋的網絡屬性信息:

root@eto:~# cat /etc/docker/daemon.json 
{
  "exec-opts": ["native.cgrounpdriver=systemd"],
  "registry-mirrors": ["https://e2615hzs.mirror.aliyuncs.com"],
  "bip": "192.168.1.0/24"

}

# 核心選項bip,即bridge ip之意,用於指定docker0橋自身的IP地址
# 修改docker0的地址後,啓動容器的地址也會隨之改變。

dockerd默認監聽在Unix Socket格式的地址:/var/run/docker.sock。如果使用TCP套接字,則修改配置文件選項爲:

"hosts": ["tcp://0.0.0.0:2375","unix:///var/run/docker.sock"]

也可以向dockerd直接傳遞"-H | --host"選項。在ubuntu上,如果docker.service指定了-H fd,此處再設置hosts時會無法啓動。此時可以將docker.service中的-H選項去掉即可。

此時客戶端就可以通過-H,指定要連接的服務器地址:

root@eto:~# docker -H 192.168.2.10:2375 ps 
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

創建自定義橋設備

root@eto:~# docker network create -d bridge --subnet "192.168.100.0/24" --gateway "192.168.100.1" mybr0
eb582031756ecb64541e6c95dd23caf1ece1c5b87378438cf6661b250b887052
root@eto:~# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
f3f514b6596c        bridge              bridge              local
509f1fdd50fb        host                host                local
eb582031756e        mybr0               bridge              local
83fea3e78ba1        none                null                local

將容器和mybr0橋接

root@eto:~# docker run --name t4 -it --net mybr0 busybox
/ # ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:C0:A8:64:02  
          inet addr:192.168.100.2  Bcast:192.168.100.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:12 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:1032 (1.0 KiB)  TX bytes:0 (0.0 B)

再創建一個容器,橋接到docker0

root@eto:~# docker run --name b2 -it --rm busybox
/ # ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02  
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8 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:696 (696.0 B)  TX bytes:0 (0.0 B)

這兩個容器可以相互通信,默認做nat地址轉換,只需要宿主機打開forward轉發,並清空此規則:

root@eto:~# iptables -F  DOCKER-ISOLATION-STAGE-1

此規則包含限制不同容器網絡之間的訪問的規則:

target     prot opt source               destination         
DOCKER-ISOLATION-STAGE-2  all  --  0.0.0.0/0            0.0.0.0/0           
DOCKER-ISOLATION-STAGE-2  all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0  

如此就可以通信了:

/ # ping 192.168.100.2
PING 192.168.100.2 (192.168.100.2): 56 data bytes
ip64 bytes from 192.168.100.2: seq=125 ttl=63 time=0.133 ms
64 bytes from 192.168.100.2: seq=126 ttl=63 time=0.074 ms
64 bytes from 192.168.100.2: seq=127 ttl=63 time=0.168 ms
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章