docker網路埠對映
阿新 • • 發佈:2019-01-10
對映容器埠到宿主主機的實現
預設情況下,容器可以主動訪問到外部網路的連線,但是外部網路無法訪問到容器。
容器訪問外部實現
容器所有到外部網路的連線,源地址都會被NAT成本地系統的IP地址。這是使用 iptables 的源地址偽裝操作實現的。
檢視主機的 NAT 規則。
$ sudo iptables -t nat -nL
...
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16
...
其中,上述規則將所有源地址在 172.17.0.0/16 網段,目標地址為其他網段(外部網路)的流量動態偽裝為從系統網絡卡發出。MASQUERADE 跟傳統 SNAT 的好處是它能動態從網絡卡獲取地址。
外部訪問容器實現
容器允許外部訪問,可以在 docker run 時候通過 -p 或 -P 引數來啟用。
不管用那種辦法,其實也是在本地的 iptable 的 nat 表中新增相應的規則。
使用 -P 時:
$ iptables -t nat -nL
...
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80
使用 -p 80:80 時:
$ iptables -t nat -nL
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
注意:
這裡的規則映射了 0.0.0.0,意味著將接受主機來自所有介面的流量。使用者可以通過 -p IP:host_port:container_port 或 -p IP::port 來指定允許訪問容器的主機上的 IP、介面等,以制定更嚴格的規則。
如果希望永久繫結到某個固定的 IP 地址,可以在 Docker 配置檔案 /etc/default/docker 中指定 DOCKER_OPTS="--ip=IP_ADDRESS",之後重啟 Docker 服務即可生效。
使用iptables管理docker容器做埠對映
昨天寫了篇文章是關於docker如何繫結靜態的ip,使容器裡面的ip是固定的ip地址…. 另外關於繫結ip地址,我們也是可以在docker run的時候用 docker run -p ip:port:port的方式…. 他其實就是呼叫的iptables的方法…
原文連結是, http://xiaorui.cc/?p=1502 http://xiaorui.cc
docker run -d -p 9000:9000 redis_cluster 9000 ,生成一個外部9000對應容器埠9000的容器….
Python
[email protected]:~# 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.1 172.17.0.1 tcp dpt:9000
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:9000 to:172.17.0.1:9000
通過上面的資訊我們可以確定,他的埠轉換不是在docker的服務端實現的,還是藉助於linux的iptables策略實現的…. 那麼我們就可以自己寫DNAT的命令,讓外部的埠進行轉換… docker建立了一個名為DOKCER的自定義的鏈條Chain … … iptables自定義鏈條的好處就是可以讓防火牆的策略更加的層次化… … 不至於因為構建一大堆的命令後,而看的有些迷糊…. 如果是自己手動建立的埠對映,在我們刪除docker 容器的時候,他不會把這條規則刪除…. 因為我們在docker rm 容器id 進行刪除的時候,他也會把這容器相關聯的對映關係給刪掉。
iptables -t nat -A PREROUTING -p tcp –dport 80 -j DNAT –to 172.17.0.1:80
Python
[email protected]:~# iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N DOCKER
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER -d 172.17.0.1/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 9000 -j ACCEPT
[email protected]:~# iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A PREROUTING -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.1:80
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.1/32 -d 172.17.0.1/32 -p tcp -m tcp --dport 9000 -j MASQUERADE
-A DOCKER -p tcp -m tcp --dport 9000 -j DNAT --to-destination 172.17.0.1:9000
我們才別的節點進行測試… …. 結果是OK的 !
Python
[[email protected] ~ ]$ curl -Iv 192.168.1.110
* Rebuilt URL to: 192.168.1.110/
* Hostname was NOT found in DNS cache
* Trying 192.168.1.110...
* Connected to 192.168.1.110 (192.168.1.110) port 80 (#0)
> HEAD / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: 192.168.1.110
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
* Server nginx/1.0.15 is not blacklisted
< Server: nginx/1.0.15
Server: nginx/1.0.15
< Date: Tue, 19 May 2015 16:16:34 GMT
Date: Tue, 19 May 2015 16:16:34 GMT
< Content-Type: text/html
Content-Type: text/html
< Content-Length: 3698
Content-Length: 3698
< Last-Modified: Tue, 11 Nov 2014 16:27:04 GMT
Last-Modified: Tue, 11 Nov 2014 16:27:04 GMT
< Connection: keep-alive
Connection: keep-alive
< Accept-Ranges: bytes
Accept-Ranges: bytes
<
* Connection #0 to host 192.168.1.110 left intact
Dcoker的網路不怎麼好理解,咱們一說橋接可能更多的是和源伺服器本身一個同等的網路…. 但是docker的橋接bridge和我們以前的vmware virtualbox的nat是差不多的概念…. 這大家要注意下。 在複雜的環境下,總是來來回回的用iptables指令碼有些雜亂,如果有閒心,推薦開發一個公司內部自己的docker管理平臺,裡面可以組織性的管理iptables防火牆…
Docker網路原則入門:EXPOSE,-p,-P,-link
如果你已經構建了一些多容器的應用程式,那麼肯定需要定義一些網路規則來設定容器間的通訊。有多種方式可以實現:可以通過--expose引數在執行時暴露埠,或者在Dockerfile裡使用EXPOSE指令。還可以在Docker run的時候通過-p或者-P引數來發布埠。或者通過--link連結容器。雖然這些方式幾乎都能達到一樣的結果,但是它們還是有細微區別。那麼到底應該使用哪一種呢?
TL;DR
使用-p或者-P來建立特定埠繫結規則最為可靠,EXPOSE可以看做是容器文件化的方式,謹慎使用--link的方式。
在比較這些不同方式之前,我們先分別瞭解細節。
通過EXPOSE或者-expose暴露埠
有兩種方式可以用來暴露埠:要麼用EXPOSE指令在Dockerfile裡定義,要麼在docker run時指定--expose=1234。這兩種方式作用相同,但是,--expose可以接受埠範圍作為引數,比如 --expose=2000-3000。但是,EXPOSE和--expose都不依賴於宿主機器。預設狀態下,這些規則並不會使這些埠可以通過宿主機來訪問。
基於EXPOSE指令的上述限制,Dockerfile的作者一般在包含EXPOSE規則時都只將其作為哪個埠提供哪個服務的提示。使用時,還要依賴於容器的操作人員進一步指定網路規則。和-P引數聯合使用的情況,下文會進一步闡述。不過通過EXPOSE命令文件化埠的方式十分有用。
本質上說,EXPOSE或者--expose只是為其他命令提供所需資訊的元資料,或者只是告訴容器操作人員有哪些已知選擇。
實際上,在執行時暴露埠和通過Dockerfile的指令暴露埠,這兩者沒什麼區別。在這兩種方式啟動的容器裡,通過docker inspect $container_id | $container_name檢視到的網路配置是一樣的:
"NetworkSettings": {
"PortMapping": null,
"Ports": {
"1234/tcp": null
}
},
"Config": {
"ExposedPorts": {
"1234/tcp": {}
}
}
可以看到埠被標示成已暴露,但是沒有定義任何對映。注意這一點,因為我們檢視的是釋出埠。
ProTip:使用執行時標誌--expose是附加的,因此會在Dockerfile的EXPOSE指令定義的埠之外暴露新增的埠。
使用-p釋出特定埠
可以使用-p引數顯式將一個或者一組埠從容器裡繫結到宿主機上,而不僅僅是提供一個埠。注意這裡是小寫的p,不是大寫。因為該配置依賴於宿主機器,所以Dockerfile裡沒有對應的指令,這是執行時才可用的配置。-p引數有幾種不同的格式:
ip:hostPort:containerPort| ip::containerPort | hostPort:containerPort | containerPort
實際中,可以忽略ip或者hostPort,但是必須要指定需要暴露的containerPort。另外,所有這些釋出的規則都預設為tcp。如果需要udp,需要在最後加以指定,比如-p 1234:1234/udp。如果只是用命令docker run -p 8080:3000 my-image執行一個簡單的應用程式,那麼容器裡執行在3000埠的服務在宿主機的8080埠也就可用了。埠不需要一樣,但是在多個容器都暴露埠時,必須注意避免埠衝突。
避免衝突的最佳方法是讓Docker自己分配hostPort。在上述例子裡,可以選擇docker run -p 3000 my_image來執行容器,而不是顯式指定宿主機埠。這時,Docker會幫助選擇一個宿主機埠。執行命令docker port $container_id | $container_name可以檢視Docker選出的埠號。除了埠號,該命令只能顯示容器執行時埠繫結資訊。還可以通過在容器上執行docker inspect檢視詳細的網路資訊,在定義了埠對映時,這樣的資訊就很有用。該資訊在Config、HostConfig和NetworkSettings部分。我們檢視這些資訊來對比不同方式搭建的容器間的網路區別。
ProTip:可以用-p引數定義任意數量的埠對映。
-expose/EXPOSE和-p對比
為了更好得理解兩者之間的區別,我們使用不同的埠設定來執行容器。
執行一個很簡單的應用程式,會在curl它的時候列印‘hello world‘。稱這個映象為no-exposed-ports:
FROM ubuntu:trusty
MAINTAINER Laura Frank <[email protected]>
CMD while true; do echo ‘hello world‘ | nc -l -p 8888; done
實驗時注意使用的是Docker主機,而不是boot2docker。如果使用的是boot2docker,執行本文示例命令前先執行boot2docker ssh。
注意,我們使用-d引數執行該容器,因此容器一直在後臺執行。(埠對映規則只適用於執行著的容器):
$ docker run -d --name no-exposed-ports no-exposed-ports
e18a76da06b3af7708792765745466ed485a69afaedfd7e561cf3645d1aa7149
這兒沒有太多的資訊,只是回顯了容器的ID,提示服務已經成功啟動。和預期結果一樣,執行docker port no-exposed-ports和docker inspect no-exposed-ports時沒顯示什麼資訊,因為我們既沒有定義埠對映規則也沒有釋出任何埠。
因此,如果我們釋出一個埠會發生什麼呢,-p引數和EXPOSE到底有什麼區別呢?
還是使用上文的no-exposed-ports映象,在執行時新增-p引數,但是不新增任何expose規則。在config.ExposedPorts裡重新檢視--expose引數或者EXPOSE指令的結果。
$ docker run -d --name no-exposed-ports-with-p-flag -p 8888:8888 no-exposed-ports
c876e590cfafa734f42a42872881e68479387dc2039b55bceba3a11afd8f17ca
$ docker port no-exposed-ports-with-p-flag
8888/tcp -> 0.0.0.0:8888
太棒了!我們可以看到可用埠。注意預設這是tcp。我們到網路設定裡看看還有什麼資訊:
"Config": {
[...]
"ExposedPorts": {
"8888/tcp": {}
}
},
"HostConfig": {
[...]
"PortBindings": {
"8888/tcp": [
{
"HostIp": "",
"HostPort": "8888"
}
]
}
},
"NetworkSettings": {
[...]
"Ports": {
"8888/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "8888"
}
]
}
}
注意”Config“部分的暴露埠欄位。這和我們使用EXPOSE或者--expose暴露的埠是一致的。Docker會隱式暴露已經發布的埠。已暴露埠和已釋出埠的區別在於已釋出埠在宿主機上可用,而且我們可以在“HostConfig”和“NetworkSettings”兩個部分都能看到已釋出埠的資訊。
所有釋出(-p或者-P)的埠都暴露了,但是並不是所有暴露(EXPOSE或--expose)的埠都會發布。
使用-P和EXPOSE釋出埠
因為EXPOSE通常只是作為記錄機制,也就是告訴使用者哪些埠會提供服務,Docker可以很容易地把Dockerfile裡的EXPOSE指令轉換成特定的埠繫結規則。只需要在執行時加上-P引數,Docker會自動為使用者建立埠對映規則,並且幫助避免埠對映的衝突。
新增如下行到上文使用的Web應用Dockerfile裡:
EXPOSE 1000
EXPOSE 2000
EXPOSE 3000
構建映象,命名為exposed-ports。
docker build -t exposed-ports .
再次用-P引數執行,但是不傳入任何特定的-p規則。可以看到Docker會將EXPOSE指令相關的每個埠對映到宿主機的埠上:
$ docker run -d -P --name exposed-ports-in-dockerfile exposed-ports
63264dae9db85c5d667a37dac77e0da7c8d2d699f49b69ba992485242160ad3a
$ docker port exposed-ports-in-dockerfile
1000/tcp -> 0.0.0.0:49156
2000/tcp -> 0.0.0.0:49157
3000/tcp -> 0.0.0.0:49158
很方便,不是麼?
--link怎麼樣呢?
你可能在多容器應用程式裡使用過執行時引數 --link name:alias來設定容器間關係。雖然--link非常易於使用,幾乎能提供和埠對映規則和環境變數相同的功能。但是最好將--link當做服務發現的機制,而不是網路流量的門戶。
--link引數唯一多做的事情是會使用源容器的主機名和容器ID來更新新建目標容器(使用--link引數建立的容器)的/etc/hosts檔案。
當使用--link引數時,Docker提供了一系列標準的環境變數,如果想知道細節的話可以檢視相應文件。
雖然--link對於需要隔離域的小型專案非常有用,它的功能更像服務發現的工具。如果專案中使用了編排服務,比如Kubernetes或者Fleet,很可能就會使用別的服務發現工具來管理關係。這些編排服務可能會不管理Docker的連結,而是管理服務發現工具裡包含的所有服務,在Panamax專案裡使用的很多遠端部署介面卡正是做這個的。
找到平衡
哪一種網路選擇更為適合,這取決於誰(或者哪個容器)使用Docker執行的服務。需要注意的是一旦映象釋出到Docker Hub之後,你無法知道其他人如何使用該映象,因此要儘可能讓映象更加靈活。如果你只是從Docker Hub裡取得映象,使用-P引數執行容器是最方便迅速的方式,來基於作者的建議建立埠對映規則。記住每一個釋出的埠都是暴露埠,但是反過來是不對的。
![](http://dockone.io/uploads/article/20150623/96cceeeb5e5d3745374b152e06bc6561.jpg)
預設情況下,容器可以主動訪問到外部網路的連線,但是外部網路無法訪問到容器。
容器訪問外部實現
容器所有到外部網路的連線,源地址都會被NAT成本地系統的IP地址。這是使用 iptables 的源地址偽裝操作實現的。
檢視主機的 NAT 規則。
$ sudo iptables -t nat -nL
...
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16
...
其中,上述規則將所有源地址在 172.17.0.0/16 網段,目標地址為其他網段(外部網路)的流量動態偽裝為從系統網絡卡發出。MASQUERADE 跟傳統 SNAT 的好處是它能動態從網絡卡獲取地址。
外部訪問容器實現
容器允許外部訪問,可以在 docker run 時候通過 -p 或 -P 引數來啟用。
不管用那種辦法,其實也是在本地的 iptable 的 nat 表中新增相應的規則。
使用 -P 時:
$ iptables -t nat -nL
...
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80
使用 -p 80:80 時:
$ iptables -t nat -nL
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
注意:
這裡的規則映射了 0.0.0.0,意味著將接受主機來自所有介面的流量。使用者可以通過 -p IP:host_port:container_port 或 -p IP::port 來指定允許訪問容器的主機上的 IP、介面等,以制定更嚴格的規則。
如果希望永久繫結到某個固定的 IP 地址,可以在 Docker 配置檔案 /etc/default/docker 中指定 DOCKER_OPTS="--ip=IP_ADDRESS",之後重啟 Docker 服務即可生效。
使用iptables管理docker容器做埠對映
昨天寫了篇文章是關於docker如何繫結靜態的ip,使容器裡面的ip是固定的ip地址…. 另外關於繫結ip地址,我們也是可以在docker run的時候用 docker run -p ip:port:port的方式…. 他其實就是呼叫的iptables的方法…
原文連結是, http://xiaorui.cc/?p=1502 http://xiaorui.cc
docker run -d -p 9000:9000 redis_cluster 9000 ,生成一個外部9000對應容器埠9000的容器….
Python
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.1 172.17.0.1 tcp dpt:9000
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:9000 to:172.17.0.1:9000
通過上面的資訊我們可以確定,他的埠轉換不是在docker的服務端實現的,還是藉助於linux的iptables策略實現的…. 那麼我們就可以自己寫DNAT的命令,讓外部的埠進行轉換… docker建立了一個名為DOKCER的自定義的鏈條Chain … … iptables自定義鏈條的好處就是可以讓防火牆的策略更加的層次化… … 不至於因為構建一大堆的命令後,而看的有些迷糊…. 如果是自己手動建立的埠對映,在我們刪除docker 容器的時候,他不會把這條規則刪除…. 因為我們在docker rm 容器id 進行刪除的時候,他也會把這容器相關聯的對映關係給刪掉。
iptables -t nat -A PREROUTING -p tcp –dport 80 -j DNAT –to 172.17.0.1:80
Python
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N DOCKER
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER -d 172.17.0.1/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 9000 -j ACCEPT
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A PREROUTING -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.1:80
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.1/32 -d 172.17.0.1/32 -p tcp -m tcp --dport 9000 -j MASQUERADE
-A DOCKER -p tcp -m tcp --dport 9000 -j DNAT --to-destination 172.17.0.1:9000
我們才別的節點進行測試… …. 結果是OK的 !
Python
[[email protected] ~ ]$ curl -Iv 192.168.1.110
* Rebuilt URL to: 192.168.1.110/
* Hostname was NOT found in DNS cache
* Trying 192.168.1.110...
* Connected to 192.168.1.110 (192.168.1.110) port 80 (#0)
> HEAD / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: 192.168.1.110
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
* Server nginx/1.0.15 is not blacklisted
< Server: nginx/1.0.15
Server: nginx/1.0.15
< Date: Tue, 19 May 2015 16:16:34 GMT
Date: Tue, 19 May 2015 16:16:34 GMT
< Content-Type: text/html
Content-Type: text/html
< Content-Length: 3698
Content-Length: 3698
< Last-Modified: Tue, 11 Nov 2014 16:27:04 GMT
Last-Modified: Tue, 11 Nov 2014 16:27:04 GMT
< Connection: keep-alive
Connection: keep-alive
< Accept-Ranges: bytes
Accept-Ranges: bytes
<
* Connection #0 to host 192.168.1.110 left intact
Dcoker的網路不怎麼好理解,咱們一說橋接可能更多的是和源伺服器本身一個同等的網路…. 但是docker的橋接bridge和我們以前的vmware virtualbox的nat是差不多的概念…. 這大家要注意下。 在複雜的環境下,總是來來回回的用iptables指令碼有些雜亂,如果有閒心,推薦開發一個公司內部自己的docker管理平臺,裡面可以組織性的管理iptables防火牆…
Docker網路原則入門:EXPOSE,-p,-P,-link
如果你已經構建了一些多容器的應用程式,那麼肯定需要定義一些網路規則來設定容器間的通訊。有多種方式可以實現:可以通過--expose引數在執行時暴露埠,或者在Dockerfile裡使用EXPOSE指令。還可以在Docker run的時候通過-p或者-P引數來發布埠。或者通過--link連結容器。雖然這些方式幾乎都能達到一樣的結果,但是它們還是有細微區別。那麼到底應該使用哪一種呢?
TL;DR
使用-p或者-P來建立特定埠繫結規則最為可靠,EXPOSE可以看做是容器文件化的方式,謹慎使用--link的方式。
在比較這些不同方式之前,我們先分別瞭解細節。
通過EXPOSE或者-expose暴露埠
有兩種方式可以用來暴露埠:要麼用EXPOSE指令在Dockerfile裡定義,要麼在docker run時指定--expose=1234。這兩種方式作用相同,但是,--expose可以接受埠範圍作為引數,比如 --expose=2000-3000。但是,EXPOSE和--expose都不依賴於宿主機器。預設狀態下,這些規則並不會使這些埠可以通過宿主機來訪問。
基於EXPOSE指令的上述限制,Dockerfile的作者一般在包含EXPOSE規則時都只將其作為哪個埠提供哪個服務的提示。使用時,還要依賴於容器的操作人員進一步指定網路規則。和-P引數聯合使用的情況,下文會進一步闡述。不過通過EXPOSE命令文件化埠的方式十分有用。
本質上說,EXPOSE或者--expose只是為其他命令提供所需資訊的元資料,或者只是告訴容器操作人員有哪些已知選擇。
實際上,在執行時暴露埠和通過Dockerfile的指令暴露埠,這兩者沒什麼區別。在這兩種方式啟動的容器裡,通過docker inspect $container_id | $container_name檢視到的網路配置是一樣的:
"NetworkSettings": {
"PortMapping": null,
"Ports": {
"1234/tcp": null
}
},
"Config": {
"ExposedPorts": {
"1234/tcp": {}
}
}
可以看到埠被標示成已暴露,但是沒有定義任何對映。注意這一點,因為我們檢視的是釋出埠。
ProTip:使用執行時標誌--expose是附加的,因此會在Dockerfile的EXPOSE指令定義的埠之外暴露新增的埠。
使用-p釋出特定埠
可以使用-p引數顯式將一個或者一組埠從容器裡繫結到宿主機上,而不僅僅是提供一個埠。注意這裡是小寫的p,不是大寫。因為該配置依賴於宿主機器,所以Dockerfile裡沒有對應的指令,這是執行時才可用的配置。-p引數有幾種不同的格式:
ip:hostPort:containerPort| ip::containerPort | hostPort:containerPort | containerPort
實際中,可以忽略ip或者hostPort,但是必須要指定需要暴露的containerPort。另外,所有這些釋出的規則都預設為tcp。如果需要udp,需要在最後加以指定,比如-p 1234:1234/udp。如果只是用命令docker run -p 8080:3000 my-image執行一個簡單的應用程式,那麼容器裡執行在3000埠的服務在宿主機的8080埠也就可用了。埠不需要一樣,但是在多個容器都暴露埠時,必須注意避免埠衝突。
避免衝突的最佳方法是讓Docker自己分配hostPort。在上述例子裡,可以選擇docker run -p 3000 my_image來執行容器,而不是顯式指定宿主機埠。這時,Docker會幫助選擇一個宿主機埠。執行命令docker port $container_id | $container_name可以檢視Docker選出的埠號。除了埠號,該命令只能顯示容器執行時埠繫結資訊。還可以通過在容器上執行docker inspect檢視詳細的網路資訊,在定義了埠對映時,這樣的資訊就很有用。該資訊在Config、HostConfig和NetworkSettings部分。我們檢視這些資訊來對比不同方式搭建的容器間的網路區別。
ProTip:可以用-p引數定義任意數量的埠對映。
-expose/EXPOSE和-p對比
為了更好得理解兩者之間的區別,我們使用不同的埠設定來執行容器。
執行一個很簡單的應用程式,會在curl它的時候列印‘hello world‘。稱這個映象為no-exposed-ports:
FROM ubuntu:trusty
MAINTAINER Laura Frank <[email protected]>
CMD while true; do echo ‘hello world‘ | nc -l -p 8888; done
實驗時注意使用的是Docker主機,而不是boot2docker。如果使用的是boot2docker,執行本文示例命令前先執行boot2docker ssh。
注意,我們使用-d引數執行該容器,因此容器一直在後臺執行。(埠對映規則只適用於執行著的容器):
$ docker run -d --name no-exposed-ports no-exposed-ports
e18a76da06b3af7708792765745466ed485a69afaedfd7e561cf3645d1aa7149
這兒沒有太多的資訊,只是回顯了容器的ID,提示服務已經成功啟動。和預期結果一樣,執行docker port no-exposed-ports和docker inspect no-exposed-ports時沒顯示什麼資訊,因為我們既沒有定義埠對映規則也沒有釋出任何埠。
因此,如果我們釋出一個埠會發生什麼呢,-p引數和EXPOSE到底有什麼區別呢?
還是使用上文的no-exposed-ports映象,在執行時新增-p引數,但是不新增任何expose規則。在config.ExposedPorts裡重新檢視--expose引數或者EXPOSE指令的結果。
$ docker run -d --name no-exposed-ports-with-p-flag -p 8888:8888 no-exposed-ports
c876e590cfafa734f42a42872881e68479387dc2039b55bceba3a11afd8f17ca
$ docker port no-exposed-ports-with-p-flag
8888/tcp -> 0.0.0.0:8888
太棒了!我們可以看到可用埠。注意預設這是tcp。我們到網路設定裡看看還有什麼資訊:
"Config": {
[...]
"ExposedPorts": {
"8888/tcp": {}
}
},
"HostConfig": {
[...]
"PortBindings": {
"8888/tcp": [
{
"HostIp": "",
"HostPort": "8888"
}
]
}
},
"NetworkSettings": {
[...]
"Ports": {
"8888/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "8888"
}
]
}
}
注意”Config“部分的暴露埠欄位。這和我們使用EXPOSE或者--expose暴露的埠是一致的。Docker會隱式暴露已經發布的埠。已暴露埠和已釋出埠的區別在於已釋出埠在宿主機上可用,而且我們可以在“HostConfig”和“NetworkSettings”兩個部分都能看到已釋出埠的資訊。
所有釋出(-p或者-P)的埠都暴露了,但是並不是所有暴露(EXPOSE或--expose)的埠都會發布。
使用-P和EXPOSE釋出埠
因為EXPOSE通常只是作為記錄機制,也就是告訴使用者哪些埠會提供服務,Docker可以很容易地把Dockerfile裡的EXPOSE指令轉換成特定的埠繫結規則。只需要在執行時加上-P引數,Docker會自動為使用者建立埠對映規則,並且幫助避免埠對映的衝突。
新增如下行到上文使用的Web應用Dockerfile裡:
EXPOSE 1000
EXPOSE 2000
EXPOSE 3000
構建映象,命名為exposed-ports。
docker build -t exposed-ports .
再次用-P引數執行,但是不傳入任何特定的-p規則。可以看到Docker會將EXPOSE指令相關的每個埠對映到宿主機的埠上:
$ docker run -d -P --name exposed-ports-in-dockerfile exposed-ports
63264dae9db85c5d667a37dac77e0da7c8d2d699f49b69ba992485242160ad3a
$ docker port exposed-ports-in-dockerfile
1000/tcp -> 0.0.0.0:49156
2000/tcp -> 0.0.0.0:49157
3000/tcp -> 0.0.0.0:49158
很方便,不是麼?
--link怎麼樣呢?
你可能在多容器應用程式裡使用過執行時引數 --link name:alias來設定容器間關係。雖然--link非常易於使用,幾乎能提供和埠對映規則和環境變數相同的功能。但是最好將--link當做服務發現的機制,而不是網路流量的門戶。
--link引數唯一多做的事情是會使用源容器的主機名和容器ID來更新新建目標容器(使用--link引數建立的容器)的/etc/hosts檔案。
當使用--link引數時,Docker提供了一系列標準的環境變數,如果想知道細節的話可以檢視相應文件。
雖然--link對於需要隔離域的小型專案非常有用,它的功能更像服務發現的工具。如果專案中使用了編排服務,比如Kubernetes或者Fleet,很可能就會使用別的服務發現工具來管理關係。這些編排服務可能會不管理Docker的連結,而是管理服務發現工具裡包含的所有服務,在Panamax專案裡使用的很多遠端部署介面卡正是做這個的。
找到平衡
哪一種網路選擇更為適合,這取決於誰(或者哪個容器)使用Docker執行的服務。需要注意的是一旦映象釋出到Docker Hub之後,你無法知道其他人如何使用該映象,因此要儘可能讓映象更加靈活。如果你只是從Docker Hub裡取得映象,使用-P引數執行容器是最方便迅速的方式,來基於作者的建議建立埠對映規則。記住每一個釋出的埠都是暴露埠,但是反過來是不對的。
![](http://dockone.io/uploads/article/20150623/96cceeeb5e5d3745374b152e06bc6561.jpg)