個人Docker之路(四):網絡模式與容器間鏈接

在使用Docker容器來部署應用時大部分狀況下都是在一個容器中只部署一個應用程序,好比不少狀況下服務A和服務B,或者應用和數據庫都存在於不一樣的容器中,這就涉及到了容器網絡模式與容器間鏈接相關的內容。在Linux系統中是經過network namespace來進行網絡隔離,運行在Liunx中的Docker亦是如此。對於網絡一個network namespace提供一個徹底獨立的網絡,固然這是在宿主機中虛擬出的環境,包括獨立的IP、端口、路由表和防火牆等等。這樣Docker就能夠經過這種方法爲每一個容器提供一份獨立的網絡配置。Docker中提供了多種網絡模式,在研究這些網絡模式以前,咱們先看下Linux中如何建立虛擬網絡並通訊。html

引例實驗1:建立虛擬網絡並通訊

本實驗主要參照參考文獻[3]的博文。在linux系統中建立兩個虛擬網絡並使之可以鏈接到彼此,實驗的操做環境是Ubuntu主機。linux

使用命令 ip netns ls 或 ip netns list 可查看當前的network namespace列表。nginx

建立第一個network namespace命名爲ns1git

~$ sudo ip netns add ns1
~$ sudo ip netns list
ns1

查看ns1虛擬網卡信息,當前狀態爲Down,表示該網卡還沒有啓動github

~$ sudo ip netns exec ns1 ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

啓動ns1中這個虛擬網卡loweb

~$ sudo ip netns exec ns1 ip link set lo up

以後再查看ns1中的網卡信息,如今狀態變爲UNKNOWN,網卡的本地迴環地址爲127.0.0.1/8docker

~$ sudo ip netns exec ns1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever

使用上面一樣的流程建立另外一個network namespace命名爲ns2並啓動lo。shell

在Linux中提供了一種 Virtual Ethernet Pair 的技術用來建立網絡鏈接接口,接口都是成對建立的。建立完成後的veth-ns1與veth-ns1分別放在兩個network namespace中,向veth-ns1中發送數據包即可以在veth-ns1中收到包數據,這樣兩個namespace下即可以實現通訊了。如同veth-ns1和veth-ns2這種對子稱爲 veth pair 。數據庫

建立  veth pair  ubuntu

~$ sudo ip link add veth-ns1 type veth peer name veth-ns2

查看當前的link,能夠看到剛剛建立的 veth pair 

~$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:08:39:2b brd ff:ff:ff:ff:ff:ff
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:3c:aa:35:f0 brd ff:ff:ff:ff:ff:ff
5: vethc81c504@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
    link/ether 9a:6d:d1:c1:30:ec brd ff:ff:ff:ff:ff:ff link-netnsid 0
6: veth-ns2@veth-ns1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether d2:c2:5f:2f:91:3b brd ff:ff:ff:ff:ff:ff 7: veth-ns1@veth-ns2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 8a:36:d3:11:7b:33 brd ff:ff:ff:ff:ff:ff

把虛擬網卡veth-ns1加到ns1中,將veth-ns2加到ns2中

~$ sudo ip link set veth-ns1 netns ns1
~$ sudo ip link set veth-ns2 netns ns2

查看ns1上的虛擬網卡信息,發現多出一個 veth-ns1@if6 。

~$ sudo ip netns exec ns1 ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
7: veth-ns1@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 8a:36:d3:11:7b:33 brd ff:ff:ff:ff:ff:ff link-netnsid 1

一樣查看ns2上的虛擬網卡信息,發現多出一個 veth-ns2@if7 。

~$ sudo ip netns exec ns2 ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
6: veth-ns2@if7: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether d2:c2:5f:2f:91:3b brd ff:ff:ff:ff:ff:ff link-netnsid 0

啓動這兩個虛擬網卡

~$ sudo ip netns exec ns1 ip link set veth-ns1 up
~$ sudo ip netns exec ns2 ip link set veth-ns2 up

爲兩個網卡分配IP地址。

~$ sudo ip netns exec ns1 ip addr add 192.168.0.11/24 dev veth-ns1
~$ sudo ip netns exec ns2 ip addr add 192.168.0.12/24 dev veth-ns2

如今再來查看下ns1中的網卡的信息,已獲得咱們分配的IP地址。

~$ sudo ip netns exec ns1 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
7: veth-ns1@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 8a:36:d3:11:7b:33 brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet 192.168.0.11/24 scope global veth-ns1
       valid_lft forever preferred_lft forever
    inet6 fe80::8836:d3ff:fe11:7b33/64 scope link
       valid_lft forever preferred_lft forever

如今使用ns1去ping一下ns2能夠發現已經能夠鏈接上了。

ns1去鏈接ns2
:~$ sudo ip netns exec ns1 ping 192.168.0.12
PING 192.168.0.12 (192.168.0.12) 56(84) bytes of data.
64 bytes from 192.168.0.12: icmp_seq=1 ttl=64 time=0.037 ms
64 bytes from 192.168.0.12: icmp_seq=2 ttl=64 time=0.049 ms
64 bytes from 192.168.0.12: icmp_seq=3 ttl=64 time=0.031 ms

引例實驗2:建立Bridge

上面經過建立 veth-pair 鏈接兩個network namespace的網絡,至關因而使用了一根網線直接插在兩臺電腦的網口上進行鏈接。可是要搭建起一個局域網絡,存在多臺主機須要通訊一般的作法可不是用網線對插了,而是會使用到交換機。在Linux中 bridege 的功能就相似於咱們的交換機功能。接下來試着建立Bridge並鏈接兩個網卡。首先建立3個namespace,其中 bridge 即是隨後要做爲交換機的namespace。

~$ sudo ip netns add ns3
~$ sudo ip netns add ns4
~$ sudo ip netns add bridge
~$ ip netns ls
bridge
ns4
ns3
ns2 (id: 2)
ns1 (id: 1)

建立一對 veth-pair

~$ sudo ip link add type veth
~$ ip link
... ...
8: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether c2:aa:ec:3f:db:7d brd ff:ff:ff:ff:ff:ff 9: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether ca:aa:3f:60:2c:2d brd ff:ff:ff:ff:ff:ff

看到生成veth0和veth1,如今咱們將veth0這頭「插」到ns3中,將veth1這頭「插」在bridge上

~$ sudo ip link set dev veth0 name ns3-bridge netns ns3
~$ sudo ip link set dev veth1 name bridge netns bridge    #這裏應該將bridge上的網卡命名爲「bridge-ns3」更貼切一點

接着對ns4進行相似的操做

~$ sudo ip link add type veth
~$ sudo ip link set dev veth0 name ns4-bridge netns ns4
~$ sudo ip link set dev veth1 name bridge-ns4 netns bridge

查看ns3上的網卡和bridge上的網卡。

~$ sudo ip netns exec ns3 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
8: ns3-bridge@if9: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether c2:aa:ec:3f:db:7d brd ff:ff:ff:ff:ff:ff link-netnsid 1

~$ sudo ip netns exec bridge ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
9: bridge@if8: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether ca:aa:3f:60:2c:2d brd ff:ff:ff:ff:ff:ff link-netnsid 0
11: bridge-ns4@if10: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 02:cb:c9:5b:10:8d brd ff:ff:ff:ff:ff:ff link-netnsid 1

如今的鏈接狀況以下圖所示

在bridge這個namespace中建立網橋設備(br)並啓動來使其變成咱們的「交換機」。

~$ sudo apt-get install bridge-utils    #安裝工具 ~$ sudo ip netns exec bridge brctl addbr brTest    #建立網橋  
~$ sudo ip netns exec bridge ip link set dev brTest up  #啓動網橋

啓動bridge上的兩個網卡 bridge 和 bridge-ns4 

~$ sudo ip netns exec bridge ip link set dev bridge up
~$ sudo ip netns exec bridge ip link set dev bridge-ns4 up

把bridge上的兩個網卡 bridge 和 bridge-ns4 連到網橋br上

~$ sudo ip netns exec bridge brctl addif brTest bridge
~$ sudo ip netns exec bridge brctl addif brTest bridge-ns4

啓動ns3和ns4中的兩個網卡並分配IP地址

~$ sudo ip netns exec ns3 ip link set dev ns3-bridge up
~$ sudo ip netns exec ns3 ip address add 192.168.0.13/24 dev ns3-bridge
~$ sudo ip netns exec ns4 ip link set dev ns4-bridge up
~$ sudo ip netns exec ns4 ip address add 192.168.0.14/24 dev ns4-bridge

當前示意圖

測試鏈接,成功。

~$ sudo ip netns exec ns3 ping 192.168.0.14
PING 192.168.0.14 (192.168.0.14) 56(84) bytes of data.
64 bytes from 192.168.0.14: icmp_seq=1 ttl=64 time=0.045 ms
64 bytes from 192.168.0.14: icmp_seq=2 ttl=64 time=0.060 ms
64 bytes from 192.168.0.14: icmp_seq=3 ttl=64 time=0.043 ms
64 bytes from 192.168.0.14: icmp_seq=4 ttl=64 time=0.059 ms

 Docker中的網絡模式

在進行了上述的實驗後對理解Docker的網絡模式會有很好的幫助,在Docker中提供了5中網絡模式分別是

  1. bridge模式
    § 默認模式,若是啓動容器時不指定任何網絡模式,就會使用默認bridge模式。這種模式下Docker會爲全部容器分配一個獨立的網絡,並將全部這些網絡併到一個網橋上。這種模式經常使用在獨立容器間通訊的情形。
  2. host模式
    § 去除容器與宿主機器的網絡隔離,容器直接使用宿主的網絡,也就是說容器與主機在同一個network namespace下,而且Docker也不會爲容器再虛擬出網絡環境相關的配置。
  3. overlay模式
    § 若已經存在了一個容器,那麼新建立的容器使用此模式可讓其與使用已存在的這個容器的網絡,新建立的容器Docker將不會再爲其虛擬網絡環境。共享了網絡的兩個容器可使用lo網卡(迴環網絡)通訊。
  4. macvlan模式
    § 這種模式下可讓你爲容器分配mac地址。
  5. none模式
    § 這種模式下每一個容器擁有本身的network namespace,可是Docker不會爲容器配置網絡信息,須要咱們本身來爲容器配置網絡。

bridge模式

當咱們啓動Docker時,Docker會幫咱們自動建立一個默認的網橋網絡命名爲bridge。在啓動容器時若是不顯式的指定網絡模式,則都會採用bridge模式,容器擁有本身獨立的network namespace並經過veth pair鏈接上默認bridge網絡上的虛擬網橋。這個虛擬網橋默認叫docker0,就象是這個網絡的交換機,每一個使用默認bridge網絡的容器都連到這部交換機上。固然用戶也能夠本身自定義網橋網絡並供容器鏈接,自定義的網橋網絡的優先級是高於默認的網橋網絡的。咱們先來研究一番bridge模式下的工做原理。

首先咱們經過命令查看下Docker默認建立的網絡列表,能夠看到其中已經包含了咱們說的默認的bridge。

> docker network ls

NETWORK ID          NAME                DRIVER              SCOPE
366865106dfe bridge bridge local
c9f4882bd348        host                host                local
ddbca9f4bc4a        none                null                local

咱們啓動一個容器並命名爲server1,並使用端口映射將容器的8081端口映射到主機的8080端口,隨後查看容器的網絡信息,能夠看到一個已開啓並已分配了IP的虛擬網卡。

> docker run -t -i --rm -p 8081:8080 --name server1 ubuntu
# apt-get update
# apt-get install iproute2
/# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1000
    link/tunnel6 :: brd ::
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 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

接着再啓動第二個容器命名爲server2,一樣使用端口映射。檢查server2的網絡信息可發現狀況和server1相似。

> docker run -t -i --rm -p 8082:8080 --name server2 ubuntu
# apt-get update
# apt-get install iproute2
/# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1000
    link/tunnel6 :: brd ::
8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever

能夠看到兩個 server1 和 server2 的兩個網卡是 eth0@if7 和 eth0@if9,這兩個虛擬網卡不是連續的說明不是一對veth pair,不能直接通訊,而是經過bridge間接鏈接的。
咱們查看下宿主機(若window系統上即是Docker Desktop在Hyper-V中啓動的Linux虛擬機)上的網卡信息。

/ # ip a
… …
4: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 1000
    link/ether 02:50:00:00:00:01 brd ff:ff:ff:ff:ff:ff
    inet 192.168.65.3/28 brd 192.168.65.15 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::50:ff:fe00:1/64 scope link
       valid_lft forever preferred_lft forever
5: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:b9:5f:a1:6d brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0 valid_lft forever preferred_lft forever inet6 fe80::42:b9ff:fe5f:a16d/64 scope link valid_lft forever preferred_lft forever 7: veth2ee4a60@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 72:95:52:63:55:45 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::7095:52ff:fe63:5545/64 scope link valid_lft forever preferred_lft forever 9: veth79a9b67@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 32:d3:5f:94:24:fc brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet6 fe80::30d3:5fff:fe94:24fc/64 scope link valid_lft forever preferred_lft forever

能夠看到Docker默認建立的docker0,就相似咱們在前面實驗2中建立的bridge。並看到server1和server2的veth-pair的「另外一半」。

使用Docker的命令能夠查看到接入到默認網橋網絡bridge的容器server1和server2。

> docker network inspect bridge
… …
"Containers": {
            "058a70d5ebe51d97f9810cb4b5ec17df82668ba7b09a3bb906d52d79fee0c659": {
                "Name": "server1",
                "EndpointID": "38986e3b75ab68f0c2aeb32773512149c52eaea7988f4e0d9965f3d08a6d2dda",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            },
            "6c7215c324a993134945d8ba965800bf421715df750eca190940fce5fadbe7fa": {
                "Name": "server2",
                "EndpointID": "d648e845488f48df36dac6c8cf3214ff4acf99a4989a2f947e61e2eedc3675b7",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            }
        }
… …

試下使用server1容器鏈接server2容器,發現能夠ping通server2的IP地址。一樣的server2也能夠ping到server1的IP。可是直接server1直接ping容器server2的容器名是ping不通的。

/# ping 172.17.0.3

PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data.
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.061 ms
64 bytes from 172.17.0.3: icmp_seq=2 ttl=64 time=0.040 ms
64 bytes from 172.17.0.3: icmp_seq=3 ttl=64 time=0.044 ms
^C
--- 172.17.0.3 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2077ms
rtt min/avg/max/mdev = 0.040/0.048/0.061/0.009 ms

若是你的主機能夠訪問外網的話,主機中的Docker容器也是能夠訪問外網的。

/# ping google.com
PING google.com (216.58.197.110) 56(84) bytes of data.
64 bytes from 216.58.197.110 (216.58.197.110): icmp_seq=1 ttl=37 time=11.8 ms
64 bytes from 216.58.197.110 (216.58.197.110): icmp_seq=2 ttl=37 time=21.0 ms

咱們在宿主機(Windows下是Linux虛擬機)上查看iptables的規則能夠看到存在多出這幾條。

# iptables -t nat -S
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8081 -j DNAT --to-destination 172.17.0.2:8080
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8082 -j DNAT --to-destination 172.17.0.3:8080

說明將源地址爲172.17.0.0/16的包(Docker容器發出的包),而且不是從docker0網卡發出的,進行源地址轉換,轉換成主機網卡的地址。
另外的兩個規則能夠看出,因爲咱們在啓動容器時進行了端口映射,對eth0收到的目的端口爲8081或8082的tcp流量進行DNAT轉換,將流量發往172.17.0.2:8080或者172.17.0.3:8080也就是server1或server2容器。

在bridge模式下除了使用默認的橋接網絡bridge外,咱們也能夠自定義橋接網絡並讓容器鏈接上來。

建立自定義的bridge network。

> docker network create --driver bridge demo-net                                                  
0eb15c4cfa0138e9d2234b58bef032b26cbb0635c855a7645c63cf1ad0127a73
> docker network ls
NETWORK ID NAME DRIVER SCOPE 366865106dfe bridge bridge local 0eb15c4cfa01 demo
-net bridge local c9f4882bd348 host host local ddbca9f4bc4a none null local

檢視下demo-net的網絡信息能夠看到IP等,當前還沒容器連上因此Containers中是空的。

> docker network inspect demo-net
[
    {
        "Name": "demo-net",
        "Id": "0eb15c4cfa0138e9d2234b58bef032b26cbb0635c855a7645c63cf1ad0127a73",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {}, "Options": {},
        "Labels": {}
    }
]

建立兩個測試容器連上咱們自定義的網絡demo-net。Docker中在建立容器時使用 --network 來指定容器的網絡。

> docker run -dit --name demoC1 --network demo-net alpine ash                                     
a0efcb9108c9ea55e6eb54d8c01f6ce687fad7fd02796e17d13e7a476c79c1c7
> docker run -dit --name demoC2 --network demo-net alpine ash
2f49f11c443bd5c466070467c6b7788be6ab3deaba0dc87c6f615b3615b20ca5
> docker network inspect demo-net                                                                 [
    ... ..."Containers": {
            "2f49f11c443bd5c466070467c6b7788be6ab3deaba0dc87c6f615b3615b20ca5": {
                "Name": "demoC2",
                "EndpointID": "4a121a78efe9849ebf37667c595b7740650850a7f0324342b39c17d0e4ea766a",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            "a0efcb9108c9ea55e6eb54d8c01f6ce687fad7fd02796e17d13e7a476c79c1c7": {
                "Name": "demoC1",
                "EndpointID": "889ffd05fcf772ad5d07fa068ed91ac5f5f1f547f7f9935dc41a45d4fc3eeef4",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
     ... ...

這是再檢查demo-net時可看到Containers中多出了「demoC1」和「demoC2」。接下來咱們測試下這兩個容器的通訊,值得注意的是此次咱們並不是使用IP地址而是使用容器的名稱。

> docker container attach demoC1                                                                  
/ # ping -c 2 demoC2 PING demoC2 (172.18.0.3): 56 data bytes 64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.100 ms 64 bytes from 172.18.0.3: seq=1 ttl=64 time=0.078 ms --- demoC2 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.078/0.089/0.100 ms

若是沒有在容器建立時經過 --network 指定網絡,可使用Docker命令 docker network connect <network> <container> 將容器加入網絡,使用命令 docker network disconnect <network> <container> 將容器移除該網絡。

> docker run -dit --name demoC3 alpine ash                                                        
a9188e57c0ffb84dae344b8ef8d6953229ba8404498434d8a2bcfa3258881137
> docker network connect demo-net demoC3 # 將demoC3加入網絡demo-net > docker container inspect demoC3    # 檢視容器demoC3,可看到連上了兩個網絡,默認的bridge和後加的demo-net ... ... "Networks": { "bridge": { "IPAMConfig": null, "Links": null, "Aliases": null, "NetworkID": "bc461594e771ba255f2feade7acdc8d44c92698266966e3e85c75e908e1dcc13", "EndpointID": "1463e6457be98038f50749ee0ad7fd0989ed5a810ed27f04597831efe66cad3d", "Gateway": "172.17.0.1", "IPAddress": "172.17.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:ac:11:00:02", "DriverOpts": null }, "demo-net": { "IPAMConfig": {}, "Links": null, "Aliases": [ "a9188e57c0ff" ], "NetworkID": "0eb15c4cfa0138e9d2234b58bef032b26cbb0635c855a7645c63cf1ad0127a73", "EndpointID": "2c175f500d410134284cb848b0bfcdd2cbf005a675de8eb1a1f4e56acc0c7231", "Gateway": "172.18.0.1", "IPAddress": "172.18.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:ac:12:00:02", "DriverOpts": {} }
... ...

用戶自定義的網絡和默認網橋網絡bridge存在如下區別。

  1. 若是容器使用默認的網橋bridge,那麼容器之間通訊只能夠經過IP地址,或者咱們爲其指定--link參數。而後--link用法Docker官方已經代表爲過期並不建議使用了,用法可參考這裏。而用戶定義的網橋自帶DNS解析,因此容器間除了能夠經過IP還能夠經過容器名來相互訪問。
  2. 使用自定義的網橋能夠作到更好的網絡隔離,咱們能夠將容器經過--network指定網橋將須要通訊的容器放在一個網絡下,有點相似分組。不須要通訊的容器嫁接在不一樣網橋上,避免發生沒必要要的鏈接而產生沒法預知的問題。
  3. 使用自定義的網橋,能夠在容器運行的時候使其從網橋上斷開或鏈接上一個咱們定義的網橋。而默認的bridge網橋想要作到這一點必須先停掉容器。
  4. 使用自定義網橋能夠更加靈活的配置,針對不一樣的容器使用不用的配置。而且修改默認bridge網橋的配置時須要將Docker重啓。

bridge模式下容器鏈接

瞭解了bridge模式的工做原理後咱們試着將ASP.NET Core應用容器化部署並通訊。實驗經過兩個簡單的ASP.NET Core Web應用(一個爲MVC項目一個爲WebApi項目),MVC經過HttpClient訪問WebApi來獲取數據並在頁面上顯示出來。

咱們設定兩個web應用的運行端口都爲80端口。有兩種方法能夠修改默認端口,第一中是修改代碼,在Program.cs中增長 UseUrls 方法。

1 public static IHostBuilder CreateHostBuilder(string[] args) =>
2             Host.CreateDefaultBuilder(args)
3                 .ConfigureWebHostDefaults(webBuilder =>
4                 {
5                     webBuilder.UseUrls("http://*:80")  #設置啓動的端口 6                               .UseStartup<Startup>();
7                 });

另一種是設置環境變量,這個環境變量咱們能夠在項目的Dockerfile中增長環境變量 ASPNETCORE_URLS 並賦值,也可在docker啓動容器時指定環境變量

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_URLS http://+:80
ENTRYPOINT ["dotnet", "ASP.NET Core3.x MVC Demo.dll"]

咱們的MVC項目使用HttpClient來向WebApi項目發送請求,我採用的是從配置中設置WebApi的uri。如下是在Startup.cs中ConfigureServices方法中註冊HttpClient時的代碼。

1 //從配置中讀取WebApi的地址
2 services.AddHttpClient<DemoApiHttpClient>(client =>
3 {
4     client.BaseAddress = new Uri(Configuration["DemoApiUri"]);
5 });

因爲咱們將要將兩個Web應用的容器加入到自定義bridge網絡中,這樣咱們就可使用容器名來做爲地址進行請求了,因此在配置中WebApi的地址是這麼寫的。

"DemoApiUri": "http://webapidemo:80"

接下來咱們來構建這兩個應用的鏡像,構建鏡像的方法就不在本文贅述了,可參考上一篇文章。關於本項目代碼我上傳在GitHub上:https://github.com/Xuhy0826/ASP.NET-Core3.x-Demo

打開powershell並cd到解決方案根目錄下,執行build命令,建立webapi項目的鏡像。

> docker build -f "${pwd}\ASP.NET Core3.x WebApi Demo\Dockerfile" --force-rm -t mark826/demo.aspnetcore/webapi:v520 .

接着build mvc項目的鏡像。

> docker build -f "${pwd}\ASP.NET Core3.x MVC Demo\Dockerfile" --force-rm -t mark826/demo.aspnetcore/mvc:v520 .

接着使用這兩個鏡像建立容器,使用咱們建立的「demo-net」網絡。而且將mvc應用的容器的80端口映射到主機的8081端口上。

> docker run -dt --network demo-net --name webapidemo mark826/demo.aspnetcore/webapi:v520
> docker run -dt --network demo-net  -p:8081:80 --name mvcdemo mark826/demo.aspnetcore/mvc:v520
> docker ps
CONTAINER ID   IMAGE                               COMMAND                  PORTS                  NAMES
703d48ac965a   mark826/demo.aspnetcore/mvc:v520    "dotnet 'ASP.NET Cor…"   0.0.0.0:8081->80/tcp   mvcdemo
bd81fe720e8f   mark826/demo.aspnetcore/webapi:v520 "dotnet 'ASP.NET Cor…"   80/tcp                 webapidemo

容器啓動成功後咱們在瀏覽器上打開mvc地址。以下圖,能夠看到mvc項目已從webapi中獲取了數據。

咱們查看一下mvc容器的日誌,注意下面加粗的日誌段,打出了mvc請求的地址爲「http://webapidemo/api/employees」,訪問的是容器名稱而非IP。

> docker logs mvcdemo
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /app
info: System.Net.Http.HttpClient.DemoApiHttpClient.LogicalHandler[100]
      Start processing HTTP request GET http://webapidemo/api/employees
info: System.Net.Http.HttpClient.DemoApiHttpClient.ClientHandler[100] Sending HTTP request GET http://webapidemo/api/employees
info: System.Net.Http.HttpClient.DemoApiHttpClient.ClientHandler[101]
      Received HTTP response after 556.1971ms - OK
info: System.Net.Http.HttpClient.DemoApiHttpClient.LogicalHandler[101]
      End processing HTTP request after 563.7564ms - OK

host模式

若是容器使用的網絡模式是host模式,那麼這個容器將會和Docker宿主共享同一個network namespace,容器不會再獲得一個新的IP地址。這種狀況下若是咱們將容器的應用綁定80端口的話,即可以經過主機的ip地址和80端口來訪問應用。而且咱們在啓動容器時不須要再使用 -p 標識來進行端口映射了,反而若是使用了端口映射,Docker會發出警告。

在host模式下性能是較好的,這比較適合須要同時使用多個端口而不須要NAT的場景。可是遺憾的是這種模式只能運行在Linux系統下,並不支持Windows系統和Mac系統的Docker。

咱們在Linux主機上使用nginx的官方鏡像來測試一下host模式。

~$ docker run --rm -d --network host --name my_nginx nginx

容器啓動成功後試着訪問下主機IP的80端口,即可以看到nginx的默認頁

查看下主機上的80端口占用狀況和容器的網絡信息。

:~$ sudo netstat -tulpn | grep :80
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN    6556/nginx: master
~$ sudo docker inspect my_nginx
... ...
"Networks": {
                "host": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "df91d31f09927ac3a31dd3388dca9c288f5ccf9501a1865c075687c732a40302",
                    "EndpointID": "42202ab20aece70befc0ceae4570f10caa4e2394ac82e87dbd090684af6784fa",
                    "Gateway": "",
                    "IPAddress": "",
                    "IPPrefixLen": 0,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "",
                    "DriverOpts": null
                }
            }

參考文獻:

[1] https://docs.docker.com/network/bridge/

[2] https://docs.docker.com/network/network-tutorial-standalone/

[3] http://www.javashuo.com/article/p-ndsxsert-vc.html

[4] https://juejin.im/post/5e7181ebe51d4526fb5dfb00

[5] http://www.javashuo.com/article/p-ktgntuaj-mt.html

[6] https://docs.docker.com/network/network-tutorial-host/

[7] https://docs.docker.com/network/host/

相關文章
相關標籤/搜索