Docker-Bridge Network 02 容器與外部通訊

本小節介紹bridge network模式下,容器與外部的通訊。html

1.前言2.容器訪問外部2.1 訪問外網2.2 原理2.3 一張圖總結2.4 抓包3.外部訪問容器3.1 建立nginx容器並從外部訪問3.2 原理3.3 一張圖總結3.4 抓包3.5 docker-proxy4.小結linux

1.前言

  上一節介紹了容容器之間的通訊過程。容器中部署了服務,咱們確定要從外部去訪問這個服務,那麼,容器如何與外部進行通訊呢?nginx

2.容器訪問外部

  這一節,咱們仍然使用bbox1容器進行實驗。首先要確保虛擬機Host1能訪問外網。在「準備Docker環境」那一節,介紹了虛擬機如何直接ping通百度。
  要說明一點,外部網絡不必定指互聯網,外部與內部是相對的概念。只要是容器網絡之外的網絡,均可以稱做外部網絡,好比Host1所在的網絡。golang

2.1 訪問外網

[root@docker1 ~]# docker exec -it bbox1 sh
/ # ping www.baidu.com
PING www.baidu.com (180.101.49.11): 56 data bytes
64 bytes from 180.101.49.11: seq=0 ttl=51 time=21.308 ms
64 bytes from 180.101.49.11: seq=1 ttl=51 time=24.613 ms

2.2 原理

  回顧一下當前容器的網絡鏈接狀況:web

  咱們試着分析一下,bbox1發出ping包,根據bbox1的路由表,報文被送到了docker0。那docker0和enp0s3是怎麼鏈接的呢?
  Linux自己就有路由轉發功能,因此其實linux系統自己能夠做爲一個路由器。執行命令cat /proc/sys/net/ipv4/ip_forward,能看到ip_forward=1,說明系統開啓了路由轉發。
  那麼docker0發出數據包後,應該是根據Host的路由表進行轉發。而Host的默認路由是指向enp0s3的,因此enp0s3收到了數據包。
  我我的理解,此時enp0s3上數據包的源IP是bbox1的,目的IP是baidu,應該是可以到達baidu的;可是在baidu發回應答包的時候,它不知道這個私網IP在哪,經過任何一級路由都找不到,因此enp0s3這裏發出報文前就得進行SNAT。
  具體回包過程我不太明白,有明白的同窗麻煩具體解釋一下,感謝。
  說到SNAT,就必須得提到Linux內核的大殺器——IPtables了。
  在虛擬機中執行iptables -t nat -vnL,查看nat表。docker

Chain POSTROUTING (policy ACCEPT 21 packets, 1479 bytes)
 pkts bytes target     prot opt in   out      source            destination         
    3   202 MASQUERADE  all  --  *   !docker0  172.17.0.0/16     0.0.0.0/0 

  docker0發出報文後會通過linux內核的路由轉發,到達enp0s3。而到達enp0s3以前,會通過iptables的POSTROUTING鏈進行SNAT,就是上面這條規則。大意是指把源IP爲172.17.0.0/1六、目的IP任意的報文,進行MASQUERADE(假裝),也就是SNAT,即把源IP和MAC替換,替換成路由選擇以後的enp0s3的IP和MAC。shell

2.3 一張圖總結

  1. bbox1 ping 百度,報文發往docker0;
  2. docker0發出報文,通過內核的路由轉發和SNAT處理,將報文的源IP及MAC換成了enp0s3的IP和MAC,到達enp0s3;
  3. enp0s3發出報文,訪問百度。

2.4 抓包

當前網絡設備及地址以下:編程

設備 IP MAC
容器bbox1 172.17.0.2 02:42:ac:11:00:02
網橋docker0 172.17.0.1 02:42:a8:64:6c:32
虛擬機網卡enp0s3 192.168.0.11 08:00:27:70:b6:ef

在bbox1內部執行ping www.baidu.com微信

  • 在docker0上抓包:
[root@docker1 ~]# tcpdump -nei docker0 icmp
listening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes
12:49:11.669684 02:42:ac:11:00:02 > 02:42:a8:64:6c:32, ethertype IPv4 (0x0800), length 98: 172.17.0.2 > 180.101.49.11: ICMP echo request, id 9728, seq 0, length 64
12:49:11.697374 02:42:a8:64:6c:32 > 02:42:ac:11:00:02, ethertype IPv4 (0x0800), length 98: 180.101.49.11 > 172.17.0.2: ICMP echo reply, id 9728, seq 0, length 64
  • 在enp0s3上抓包:
[root@docker1 ~]# tcpdump -nei enp0s3 icmp
listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
12:49:11.669700 08:00:27:70:b6:ef > 48:0e:ec:3b:b3:41, ethertype IPv4 (0x0800), length 98: 192.168.0.11 > 180.101.49.11: ICMP echo request, id 9728, seq 0, length 64
12:49:11.697340 48:0e:ec:3b:b3:41 > 08:00:27:70:b6:ef, ethertype IPv4 (0x0800), length 98: 180.101.49.11 > 192.168.0.11: ICMP echo reply, id 9728, seq 0, length 64

  從enp0s3的報文能夠看出,源IP和MAC已是enp0s3的了。網絡

3.外部訪問容器

3.1 建立nginx容器並從外部訪問

  建立一個nginx容器,執行docker run -it -d --name=nginx01 -p 8081:80 nginx該命令將容器的80端口映射到主機的8081端口。
  從本虛擬機上curl 127.0.0.1:8081,能返回nginx歡迎信息。從另外一臺虛擬機上curl 192.168.0.11:8081,也能返回nginx歡迎信息。說明外部網絡能訪問nginx容器。

3.2 原理

  使用了IPtables的DNAT功能。
  執行iptables -t nat -vnL查看IPtables規則,能夠發現目的端口8081被替換爲172.17.0.4:80。

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source             destination         
    0     0 RETURN     all  --  docker0 *       0.0.0.0/0          0.0.0.0/0           
    0     0 DNAT       tcp  --  !docker0 *      0.0.0.0/0          0.0.0.0/0          tcp dpt:8081 to:172.17.0.4:80

3.3 一張圖總結

  1. 在Host2裏面curl 192.168.0.11:8081,報文到達Host1的enp0s3;
  2. Host1的enp0s3發出報文後,經由內核的轉發及DNAT處理,將目的IP替換成nginx01的IP和端口;
  3. docker0收到報文後,根據目的mac找到對應端口,送出報文到nginx01。

3.4 抓包

  使用tcpdump,在Host1的網卡及docker0上抓包驗證。

  • Host1的enp0s3
[root@docker1 ~]# tcpdump -nei enp0s3 tcp port 8081
listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
13:25:14.725366 08:00:27:d4:6f:d1 > 08:00:27:70:b6:ef, ethertype IPv4 (0x0800), length 74: 192.168.0.12.48392 > 192.168.0.11.tproxy: Flags [S], seq 1433025043, win 29200, options [mss 1460,sackOK,TS val 4294829465 ecr 0,nop,wscale 7], length 0
13:25:14.725559 08:00:27:70:b6:ef > 08:00:27:d4:6f:d1, ethertype IPv4 (0x0800), length 74: 192.168.0.11.tproxy > 192.168.0.12.48392: Flags [S.], seq 404119170, ack 1433025044, win 28960, options [mss 1460,sackOK,TS val 59419808 ecr 4294829465,nop,wscale 7], length 0
  • Host1的docker0
[root@docker1 ~]# tcpdump -nei docker0 tcp
listening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes
13:26:07.014766 02:42:a8:64:6c:32 > 02:42:ac:11:00:04, ethertype IPv4 (0x0800), length 74: 192.168.0.12.48396 > 172.17.0.4.http: Flags [S], seq 1839700092, win 29200, options [mss 1460,sackOK,TS val 4294881751 ecr 0,nop,wscale 7], length 0
13:26:07.015357 02:42:ac:11:00:04 > 02:42:a8:64:6c:32, ethertype IPv4 (0x0800), length 74: 172.17.0.4.http > 192.168.0.12.48396: Flags [S.], seq 3312305882, ack 1839700093, win 28960, options [mss 1460,sackOK,TS val 59472098 ecr 4294881751,nop,wscale 7], length 0

  能夠看出,docker0上收到的報文,目的IP及MAC已是nginx01的了。

3.5 docker-proxy

  注意:對於外部訪問容器,除了iptables DNAT處理外,還有一種方式docker-proxy。網上不少文章寫得並不全面,只說了docker-proxy。其實經過上面的分析,只經過DNAT就能夠完成外部訪問容器了,那docker-proxy何時起做用呢?

[root@docker1 ~]# ps -ef|grep proxy
root      4073   927  0 13:07 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8081 -container-ip 172.17.0.4 -container-port 80
root      4143  1521  0 13:32 pts/0    00:00:00 grep --color=auto proxy

  查看進程能夠發現,咱們的host上也開啓了docker-proxy,將host的0.0.0.0:8081轉發到容器172.17.0.4:80。
  關於docker-proxy和DNAT什麼時候起做用,有一篇文章分析的很好,分享給你們《docker-proxy存在合理性分析》。

4.小結

  • 容器訪問外部,由iptables SNAT實現
  • 外部訪問容器,由iptables DNAT實現,另外在一些場景下,經過docker-proxy進行轉發

下一節,咱們介紹bridge network的自定義網絡。 點擊此處回到docker系列文章目錄

 

原創文章,轉載請聲明出處!

-----------------------------------------------------------------------------------------------

本人微信公衆號同步更新雲計算、容器、網絡、編程等文章,歡迎參觀!

相關文章
相關標籤/搜索