Docker容器非常輕量,系統開銷非常少,比VMware或者VirtualBox用起來友善,部署起來也非常容易。官方推薦我們通過端口映射的方式把Docker容器的服務提供給主控端或者區域網路其他容器使用。一般過程是:
1、Docker程序通過監聽主控端的某個端口,将該端口的資料包發送給Docker容器
2、主控端可以打開防火牆讓區域網路其他裝置通過通路主控端的端口進而通路docker的端口
這裡以CDNS為例,CDNS是一個用于避免DNS污染的程式,通過CDNS可以把你的計算機變成一個抗污染的DNS伺服器提供給區域網路使用。Docker鏡像下載下傳位址:https://hub.docker.com/r/alexzhuo/cdns/
原理是在Docker容器中啟動CDNS,監聽53端口,Docker容器的IP位址為172.12.0.2,主控端把5053端口映射到Docker容器上,通路主控端的127.0.0.1:5053就相當于通路Docker的53端口,是以Docker的啟動方法是:
[plain] view plain copy
- sudo docker run -itd -p 0.0.0.0:5053:53/udp --name=CureDNS alexzhuo/cdns cdns -c /etc/cdns.config.json
這樣我們使用dig工具通過5053端口查詢DNS就是無污染的DNS了,過程如下: [plain] view plain copy
- al[email protected]:~$ dig www.facebook.com @127.0.0.1 -p 5053
- ; <<>> DiG 9.10.3-P4-Ubuntu <<>> www.facebook.com @127.0.0.1 -p 5053
- ;; global options: +cmd
- ;; Got answer:
- ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9522
- ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 2, ADDITIONAL: 5
- ;; OPT PSEUDOSECTION:
- ; EDNS: version: 0, flags:; udp: 4096
- ;; QUESTION SECTION:
- ;www.facebook.com. IN A
- ;; ANSWER SECTION:
- www.facebook.com. 1550 IN CNAME star-mini.c10r.facebook.com.
- star-mini.c10r.facebook.com. 30 IN A 31.13.95.36
- ;; AUTHORITY SECTION:
- c10r.facebook.com. 2010 IN NS a.ns.c10r.facebook.com.
- c10r.facebook.com. 2010 IN NS b.ns.c10r.facebook.com.
- ;; ADDITIONAL SECTION:
- a.ns.c10r.facebook.com. 2439 IN A 69.171.239.11
- a.ns.c10r.facebook.com. 2439 IN AAAA 2a03:2880:fffe:b:face:b00c:0:99
- b.ns.c10r.facebook.com. 3351 IN A 69.171.255.11
- b.ns.c10r.facebook.com. 1253 IN AAAA 2a03:2880:ffff:b:face:b00c:0:99
- ;; Query time: 47 msec
- ;; SERVER: 127.0.0.1#5053(127.0.0.1)
- ;; WHEN: Mon Apr 10 16:21:46 CST 2017
- ;; MSG SIZE rcvd: 213
這裡假設我們的主控端IP是192.168.12.107
如果現在出現另外一台區域網路計算機,IP位址為192.168.12.113,它想把主控端當成DNS伺服器,那麼我們就需要在192.168.12.113這台計算機上通路192.168.12.107:5053來查詢DNS,dig指令如下
[plain] view plain copy
- dig www.facebook.com @192.168.12.107 -p 5053
這樣做顯然是很不友善的,我們現在希望不經過主控端這一套NAT和代理,想要直接在區域網路内的任意一台計算機上直接通過IP通路Docker容器,讓Docker容器完整的暴露在區域網路裡而不是僅單單暴露一個53端口。那麼應該如何做呢?
首先通過觀察發現,Docker的預設啟動方式中,會産生一塊虛拟網卡,在這裡我們可以了解這塊網卡連接配接着一個虛拟交換機,然後每個Docker容器又會擁有自己單獨的網卡和IP,而且所有Docker容器也連接配接在這個虛拟交換機的下面。我們可以在主控端上通過ifconfig指令看到這個虛拟網卡。
[plain] view plain copy
- [email protected]:~$ ifconfig
- docker0 Link encap:以太網 硬體位址 02:42:cd:21:5c:81
- inet 位址:172.17.0.1 廣播:0.0.0.0 掩碼:255.255.0.0
- inet6 位址: fe80::42:cdff:fe21:5c81/64 Scope:Link
- UP BROADCAST RUNNING MULTICAST MTU:1500 躍點數:1
- 接收資料包:2892 錯誤:0 丢棄:0 過載:0 幀數:0
- 發送資料包:3517 錯誤:0 丢棄:0 過載:0 載波:0
- 碰撞:0 發送隊列長度:0
- 接收位元組:187022 (187.0 KB) 發送位元組:4771886 (4.7 MB)
- lo Link encap:本地環回
- inet 位址:127.0.0.1 掩碼:255.0.0.0
- inet6 位址: ::1/128 Scope:Host
- UP LOOPBACK RUNNING MTU:65536 躍點數:1
- 接收資料包:9993 錯誤:0 丢棄:0 過載:0 幀數:0
- 發送資料包:9993 錯誤:0 丢棄:0 過載:0 載波:0
- 碰撞:0 發送隊列長度:1
- 接收位元組:934304 (934.3 KB) 發送位元組:934304 (934.3 KB)
- wlp3s0 Link encap:以太網 硬體位址 74:e5:43:b0:dd:b0
- inet 位址:192.168.12.107 廣播:192.168.12.255 掩碼:255.255.255.0
- inet6 位址: fe80::8adf:28f7:5ec:3a5d/64 Scope:Link
- UP BROADCAST RUNNING MULTICAST MTU:1500 躍點數:1
- 接收資料包:69760 錯誤:0 丢棄:0 過載:0 幀數:0
- 發送資料包:64718 錯誤:0 丢棄:0 過載:0 載波:0
- 碰撞:0 發送隊列長度:1000
- 接收位元組:41517057 (41.5 MB) 發送位元組:9971762 (9.9 MB)
上面的docker0這塊網卡就是虛拟網卡,它的IP位址是172.17.0.1,它和其他的docker容器都連接配接在一個虛拟交換機上,網段為172.17.0.0/16,下面我們登入到Docker容器裡面,檢視一下容器的網卡和IP [plain] view plain copy
- # ifconfig
- eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:02
- inet addr:172.17.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
- inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
- UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
- RX packets:3449 errors:0 dropped:0 overruns:0 frame:0
- TX packets:2811 errors:0 dropped:0 overruns:0 carrier:0
- collisions:0 txqueuelen:0
- RX bytes:4763490 (4.7 MB) TX bytes:219998 (219.9 KB)
- 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:1
- RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
可以看到這個容器的IP位址為172.17.0.2,現在我們到主控端裡看看ping 172.17.0.2能不能ping通。
答案當然是能ping通,能ping通的原因就是我們的主控端裡知道目标位址為172.17.0.1/16的路由資訊,不信我們可以檢視一下
[plain] view plain copy
- [email protected]:~$ ip route
- default via 192.168.12.1 dev wlp3s0 proto static metric 600
- 169.254.0.0/16 dev docker0 scope link metric 1000
- 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
- 192.168.12.0/24 dev wlp3s0 proto kernel scope link src 192.168.12.107 metric 600
從上面可以看出來,172.17.0.0/16這個網段的資料包可以通過docker0這塊網卡發送出去。也就是說,目前主控端有兩個IP,一個是192.168.12.107,用于連接配接實體的區域網路,一個是172.17.0.1,用來和Docker容器通信,從這可以看出主控端和路由器的作用是一緻的。而Docker容器隻有一個IP就是172.17.0.2。如果docker容器想要通路外網,那麼它就會把資料包發送到網關172.17.0.1,然後由主控端做NAT發送到192.168.12.1/24這個網段的網關上。
不光主控端可以ping通容器,而且由于在docker容器中預設路由(網關)是172.17.0.1,是以docker容器不光可以ping主機的172.17.0.1,還能ping通主機的另一個IP:192.168.12.107
此時我們的網絡拓撲其實就變成了192.168.12.0/24這個網段裡有個主控端,為了友善了解,我們把這個主控端看成一個路由器,路由器下面是172.17.0.1/16這個網段。我們把Docker容器看成實實在在的機器裝置,連接配接在主控端這個路由器的下面。這樣Docker的拓撲結構就非常清晰了。我們可以發現這個拓撲結構其實非常的簡單。就像家裡上網的路由器一樣。打個比方:我家裡有兩個路由器,一個路由器通過PPPOE撥接上網公網,内網位址為192.168.12.1,另一個路由器連接配接在第一個路由器上面,WAN口IP是192.168.12.107,LAN口位址是172.17.0.1,我們的Docker容器看成一個個的電腦接在第二個路由器LAN上面,是以Docker容器的IP為172.17.0.2。
第二個路由器(主控端)通過NAT讓我們的電腦們(Docker容器)可以通路網際網路。電腦們(Docker容器們)可以互相ping通,也能ping通全部兩個路由器。第二個路由器可以ping通電腦們,但是第一個路由器ping不同電腦們。如果還是不了解拓撲結構,可以自己在家裡買兩個路由器一前一後放上試試。
現在問題來了,如果有一個電腦連接配接在第一個路由器的下面,和第二個路由器(主控端)平級,其IP為192.168.12.113,現在它想ping通172.17.0.2這個Docker容器,發現是ping不通的。同樣第一台路由器自己也是ping不通Docker容器的
原因很簡單,這台新計算機隻能ping通同網段的裝置,比如路由器2,因為他們同屬于192.168.12.1/24這個網段。而172.17.0.2/16這個網段它并不知道怎麼路由過去,隻能把目标位址為172.17.0.1/16的資料包發給路由器一,可惜就連第一個路由器也不知道怎麼個路由法。是以我們就ping不通了。
是以現在問題就很好解決了,我們隻需要告訴這台新電腦或者第一個路由器到172.17.0.2/16這個網段的路徑就可以了。
于是我們可以在新電腦或者路由器一中這樣寫
[plain] view plain copy
- route add -net 172.17.0.1/16 gw 192.168.12.107
或者是 [plain] view plain copy
- ip route add 172.17.0.1/16 via 192.168.12.107
普通路由器可以像這樣設定
現在新電腦通路172.17.0.2的資料包就會先被發送到主控端(第二個路由器)上,然後主控端再轉發到Docker容器上,我們就把Docker容器暴露到區域網路裡了。
但此時你會發現你在新計算機上還是ping不通,這是為什麼呢。因為路由器二(主控端)對它的内網機器也就是Docker容器們全部開啟了NAT,源IP為172.17.0.2/16的資料包不會出現在主控端以外的網絡中,因為他們被NAT了。這個NAT是Docker程序預設自動幫我們實作的,我們先看一下
[plain] view plain copy
- [email protected]:~$ sudo iptables -t nat -L -n
- Chain PREROUTING (policy ACCEPT)
- target prot opt source destination
- DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
- Chain INPUT (policy ACCEPT)
- target prot opt source destination
- Chain OUTPUT (policy ACCEPT)
- target prot opt source destination
- DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
- Chain POSTROUTING (policy ACCEPT)
- target prot opt source destination
- MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
- MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:53
- MASQUERADE udp -- 172.17.0.2 172.17.0.2 udp dpt:53
- 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 127.0.0.1 tcp dpt:5053 to:172.17.0.2:53
- DNAT udp -- 0.0.0.0/0 127.0.0.1 udp dpt:5053 to:172.17.0.2:53
注意那句MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0會導緻所有172.17.0.0/16的資料包都不能到達docker以外的網絡,是以我們要關掉這個NAT,關掉很容易,我們隻需删掉這一條iptables規則就可以了。然後源IP為172.17.0.2的資料包就可以出現在192.168.12.1/24的網絡中了。 [plain] view plain copy
- sudo iptables -t nat -D POSTROUTING 3
但是把NAT關掉了以後,雖然内網可以互ping了,但是Docker容器可能上不去網呀。第一個路由器如果自動NAT 了172.17.0.2還好,但要是沒有人給Docker容器做NAT,Docker容器就不能上網了,那我們的CDNS也就沒法用了。那麼如何既保證Docker容器通路外網的資料包被NAT,又保證内網通信不被NAT呢?隻要稍微修改一下iptables規則就好了,如下
[plain] view plain copy
- sudo iptables -t nat -A POSTROUTING -s 172.17.0.2 ! -d 192.168.12.1/24 -j MASQUERADE
上面的iptables規則通過對内外網流量的分離實作差別的NAT對待,就可以既保證Docker容器正常上網,也可以被内網其他主機通路了。
可能會有這麼一種情況,上面所說的第一台路由器不是什麼智能路由器,或者你沒有權限在那個路由器上配置路由條目,讓目标為172.17.0.0/16的資料包通過路由器進行路由。同時你的區域網路其他電腦是XP系統的,也沒法手動配置路由規則,這該怎麼辦呢?
現在以要通路Docker容器的區域網路主機為Windows XP系統為例,雖然WinXP不能手動配置路由規則,但是我們可以配置網關,隻要我們把網關設定為192.168.0.107也就是我們的主控端,目标位址為172.17.0.0/16的IP包就會發送到主控端上,而主控端不同于第一個路由器,它是知道如何路由這些IP包的。于是我們就可以在WinXP上ping通Docker容器了