淺談 Docker 網絡:單節點單容器


1.Docker 網絡模型

Docker 在 1.7 版本中將容器網絡部分代碼抽離出來做爲 Docker 的網絡庫,叫 libnetwork。libnetwork 中使用 CNM(Container Network Model) 來定義容器虛擬化網絡的模型。CNM 包含三個核心組件和五種內置驅動,其示意圖以下所示:
 
CNM 的三個核心組件:
  • sandbox:沙盒,沙盒包含了容器網絡棧的信息,它能夠對容器的接口、路由和 DNS 設置等進行管理。一個沙盒能夠有多個 endpoint 和 network,它的實現機制通常是 Liunx network space。
  • endpoint:端點,一個端點能夠加入 sandbox 和 network 中,它能夠經過 veth pair、Open vSwitch 或其它網絡設備實現,一個端點只能夠屬於一個網絡而且只屬於一個沙盒。
  • network:網絡,網絡是一組能夠直接互相連通的端點,它能夠經過 Liunx Bridge、VLAN 等實現。
 
CNM 的五種內置驅動:
  • bridge driver:Docker 默認的驅動類型,使用該驅動時 libnetwork 將建立的容器鏈接到 Docker 網橋上。它能夠知足大部分容器網絡應用場景,可是在容器訪問外網時須要作 NAT, 增長了通訊的複雜度。
  • host driver:使用該驅動類型時 libnetwork 將不會爲容器建立 network namespace,容器和宿主機共用 network namespace。
  • overlay driver:該驅動類型使用標準的 VXLAN 方式進行通訊,使用該驅動須要額外配置存儲服務,如 etcd 等。
  • remote driver:該驅動並未作真正的網絡實現,它經過調用用戶自行實現的網絡驅動插件,使 libnetwork 實現驅動的插件化。
  • null driver:該驅動會爲容器建立 network space,可是不對容器進行任何網絡配置,即建立的容器只有 loopback 地址,沒有其它網卡、路由和 IP 等信息。
 
Docker 內置了五種驅動,可是實際上常常使用的仍是 bridge driver,後面僅以 bridge driver 爲例按部就班介紹 Docker 容器網絡。

2.單容器網絡

根據上節說明,這裏構建一個單容器的網絡拓撲圖:
 
查看 docker 網絡:
[root@lianhua ~]$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
1a779d0e62d5        bridge              bridge              local
f0ae6387e721        host                host                local
6d565e9acb10        none                null                local
能夠看到,Docker 自帶三種網絡 bridge, host 和 none,它們對應的 driver 分別是 bridge,host 和 null。
 
建立 container:
[root@lianhua ~]$ docker run -it --name demo0 httpd
 
以交互模式運行 container,在不指定 network 狀況下 Docker 默認 container 使用的網絡爲 bridge。經過 inspect 查看 bridge 的屬性:
[root@lianhua ~]$ docker inspect bridge
[
    {
        "Name": "bridge",
        "Id": "1a779d0e62d5a309e1e942862b76d69d4ba9ed9be9c7bcdc051e8de89b0cc3ee",
        "Created": "2020-08-26T00:06:03.910196776+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "7be09e54b24c45100769e131b46259c519710785ccfb68afaa904a1114add9a1": {
                "Name": "demo0",
                "EndpointID": "98399b3c0560aac4ca63de9f79659176562406ac02d917c667852b9a863296bb",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]
 
Docker 爲 bridge 網絡驅動添加了一個網橋 docker0,docker0 上分配的子網是 172.17.0.0/16,而且 ip 172.17.0.1 被做爲子網的網關配置在 docker0 上。這裏須要注意的是,網橋 docker0 是二層設備,至關於交換機,它的做用是爲連在其上的設備轉發數據幀,其上配置的 ip 可認爲是網橋內部有一個專門用來配置 IP 信息的網卡接口,該接口做爲 container 的默認網關地址。一樣的,咱們看到 container 分到的 ip 地址爲 172.17.0.2。
 
那麼 container 是怎麼鏈接到網橋 docker0 的呢?這裏執行 brctl show 查看連在 docker0 的接口:
[root@lianhua ~]$ brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02426c5d38db       no              veth559f8be
 
連在 docker0 上的接口是以 veth 開頭的接口設備,它是 veth-pair。能夠把它想象成網線的兩端,一端連在宿主機上,另外一端連在 container 內,從而實現 container 到網橋 docker0 的訪問。
 
基於上面分析,咱們能夠畫出 container demo0 的網絡示意圖以下:

3.單容器訪問外網

進入 container 中分別 ping 網關,宿主機網口 ip 和外網地址:
[root@lianhua ~]$ docker exec -it demo0 /bin/bash
bash-4.2$ ping 172.17.0.1 -c 3
PING 172.17.0.1 (172.17.0.1) 56(84) bytes of data.
64 bytes from 172.17.0.1: icmp_seq=1 ttl=64 time=0.049 ms
64 bytes from 172.17.0.1: icmp_seq=2 ttl=64 time=0.047 ms
64 bytes from 172.17.0.1: icmp_seq=3 ttl=64 time=0.053 ms
--- 172.17.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 0.047/0.049/0.053/0.008 ms
 
bash-4.2$ ping 192.168.0.69 -c 3
PING 192.168.0.69 (192.168.0.69) 56(84) bytes of data.
64 bytes from 192.168.0.69: icmp_seq=1 ttl=64 time=0.040 ms
64 bytes from 192.168.0.69: icmp_seq=2 ttl=64 time=0.055 ms
64 bytes from 192.168.0.69: icmp_seq=3 ttl=64 time=0.052 ms
--- 192.168.0.69 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.040/0.049/0.055/0.006 ms
 
bash-4.2$ ping 10.57.***.*** -c 3
PING 10.57.***.*** (10.57.***.***) 56(84) bytes of data.
64 bytes from 10.57.***.***: icmp_seq=1 ttl=42 time=1.81 ms
64 bytes from 10.57.***.***: icmp_seq=2 ttl=42 time=1.59 ms
64 bytes from 10.57.***.***: icmp_seq=3 ttl=42 time=1.71 ms
--- 10.57.***.*** ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 1.593/1.705/1.813/0.095 ms
(這裏的宿主機是虛擬機,它的網口 ip 是一個私網地址)
 
能夠看到建立好的 container 能夠直接訪問外網。直接訪問網橋是由於網橋地址在同一網段這是 work 的,直接訪問宿主機網口是怎麼回事呢?宿主機網口 ip 和 container ip 並不在一個網段,咱們查看路由表尋找答案:
[root@lianhua ~]$ iptables-save
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
 
在路由表裏咱們看到規則 -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE,這條規則的意思是網橋 docker0 若是收到來自 172.17.0.0/16 網段的外出包,把它交給 MASQUERADE 處理。而 MASQUERADE 的處理方式是將包的源地址替換成宿主機的 ip 地址,其實是在 container ip 和宿主機網口 ip 之間作了 NAT 轉換,訪問外網是經過宿主機網口 ip 訪問的。在網橋 docker0,宿主機網口,外網接口上抓包看結果是否是這樣:
# 容器中 ping 外網
bash-4.2$ ping 10.57.***.*** -c 3
PING 10.57.***.*** (10.57.***.***) 56(84) bytes of data.
64 bytes from 10.57.***.***: icmp_seq=1 ttl=42 time=1.67 ms
64 bytes from 10.57.***.***: icmp_seq=2 ttl=42 time=1.59 ms
64 bytes from 10.57.***.***: icmp_seq=3 ttl=42 time=1.64 ms
 
--- 10.57.***.*** ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 1.594/1.637/1.673/0.057 ms
 
# 宿主機(虛擬機)上抓包
[root@lianhua ~]$ tcpdump -i eth0 -n icmp -vv
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
22:03:18.733722 IP (tos 0x0, ttl 63, id 25663, offset 0, flags [DF], proto ICMP (1), length 84)
    192.168.0.69 > 10.57.***.***: ICMP echo request, id 15, seq 1, length 64
22:03:18.735322 IP (tos 0x48, ttl 43, id 58922, offset 0, flags [none], proto ICMP (1), length 84)
    10.57.***.*** > 192.168.0.69: ICMP echo reply, id 15, seq 1, length 64
22:03:19.735287 IP (tos 0x0, ttl 63, id 25731, offset 0, flags [DF], proto ICMP (1), length 84)
    192.168.0.69 > 10.57.***.***: ICMP echo request, id 15, seq 2, length 64
22:03:19.736786 IP (tos 0x48, ttl 43, id 59208, offset 0, flags [none], proto ICMP (1), length 84)
    10.57.***.*** > 192.168.0.69: ICMP echo reply, id 15, seq 2, length 64
22:03:20.736261 IP (tos 0x0, ttl 63, id 26101, offset 0, flags [DF], proto ICMP (1), length 84)
    192.168.0.69 > 10.57.***.***: ICMP echo request, id 15, seq 3, length 64
22:03:20.737811 IP (tos 0x48, ttl 43, id 59632, offset 0, flags [none], proto ICMP (1), length 84)
    10.57.***.*** > 192.168.0.69: ICMP echo reply, id 15, seq 3, length 64
 
# 外網 host 上抓包
[root@controller-2 admin(admin)]# tcpdump -i eth0 -n icmp -vv
tcpdump: listening on vlan9, link-type EN10MB (Ethernet), capture size 262144 bytes
22:03:18.772846 IP (tos 0x48, ttl 42, id 25663, offset 0, flags [DF], proto ICMP (1), length 84)
    10.183.**.*** > 10.57.***.***: ICMP echo request, id 15, seq 1, length 64
22:03:18.772890 IP (tos 0x48, ttl 64, id 58922, offset 0, flags [none], proto ICMP (1), length 84)
    10.57.***.*** > 10.183.**.***: ICMP echo reply, id 15, seq 1, length 64
22:03:19.774331 IP (tos 0x48, ttl 42, id 25731, offset 0, flags [DF], proto ICMP (1), length 84)
    10.183.**.*** > 10.57.***.***: ICMP echo request, id 15, seq 2, length 64
22:03:19.774358 IP (tos 0x48, ttl 64, id 59208, offset 0, flags [none], proto ICMP (1), length 84)
    10.57.***.*** > 10.183.**.***: ICMP echo reply, id 15, seq 2, length 64
22:03:20.775339 IP (tos 0x48, ttl 42, id 26101, offset 0, flags [DF], proto ICMP (1), length 84)
    10.183.**.*** > 10.57.***.***: ICMP echo request, id 15, seq 3, length 64
22:03:20.775390 IP (tos 0x48, ttl 64, id 59632, offset 0, flags [none], proto ICMP (1), length 84)
    10.57.***.*** > 10.183.**.***: ICMP echo reply, id 15, seq 3, length 64

# 10.183.**.*** 是虛擬機所在 host 的網口 ip 地址
在宿主機上 ping 包的源地址被改成宿主機的 ip 地址 192.168.0.69,確實作了 NAT 轉換。
 
訪問流程以下圖所示:

4.外網訪問單容器

外網直接訪問容器是訪問不了的,容器的 ip 是從 docker subnet 分配的私有地址,對於外網來講是不可見的。爲了使外網能夠訪問容器,須要在容器和宿主機間創建端口映射。
 
建立容器:
$ docker run -d -p 80 --name demo1 httpd
6070389f1362ef4ad6c6264077c4a47ffe8d9b2700c48e03afcb8afa5e92356c
$ docker ps
CONTAINER ID   IMAGE     COMMAND              CREATED             STATUS              PORTS                   NAMES
6070389f1362   httpd     "httpd-foreground"   44 seconds ago      Up 32 seconds       0.0.0.0:32768->80/tcp   demo1
 
容器 demo1 的 80 端口被映射到宿主機的 32768 端口,外網可經過宿主機 ip + 端口的方式訪問容器:
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWNgroup 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
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:42:ac:11:00:44 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.68/16 brd 172.17.255.255 scope global ens3
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe11:44/64 scope link
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:b6:5f:a7:1b brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/24 brd 172.18.0.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:b6ff:fe5f:a71b/64 scope link
       valid_lft forever preferred_lft forever
5: veth0541dca@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    link/ether 2e:31:fe:95:a3:b8 brd ff:ff:ff:ff:ff:ff link-netnsid0
    inet6 fe80::2c31:feff:fe95:a3b8/64 scope link
       valid_lft forever preferred_lft forever

$ curl 172.17.0.68:32768
<html><body><h1>It works!</h1></body></html>
 
Docker 在作端口映射時,會啓一個 docker-proxy 進程實現宿主機流量到容器 80 端口流量的轉發:
$ ps -elf | grep docker-proxy | grep -v grep
root /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port32768 -container-ip 172.18.0.2 -container-port 80
 
查看路由表:
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 32768 -j DNAT --to-destination 172.18.0.2:80
該規則定義了,訪問非 docker0 接口且目的端口爲 32768 的數據包將被做目的地址 NAT 轉換,且轉換的目的 ip 是容器的 ip。
 
一樣的,畫出外網訪問容器的流程以下所示:
相關文章
相關標籤/搜索