Docker網路配置⚽
Docker網路配置
紙上得來終覺淺,絕知此事要躬行。
當你開始大規模使用 Docker
時,你會發現需要了解很多關於網路的知識。無論你是在單主機上進行部署,還是在叢集上部署,你總得和網路打交道。Docker
網路有四種模式:網橋模式,主機模式,容器模式和無網路模式。
1. 網路模式
Docker
預設提供了四種網路模式,供容器啟動的時候選擇,分別是 bridge
、none
、container
和 host
Docker
會在主機上新建立一個 docker0
的網橋,其作用就相當於一個虛擬的交換機,所有的容器都是連到這臺交換機上面的。Docker
會從私有網路中選擇一段地址來管理容器,比如 172.17.0.1/16
,這個地址根據你之前的網路情況而有所不同。
bash
# 檢視現有的網路模式
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
2bd191119c33 bridge bridge local
1e714111937b host host local
2e4d5111c51e none null local
# 檢視本機Docker網段
$ docker inspect bridge -f "{{json .IPAM.Config}}"
[{"Subnet":"172.17.0.0/16","Gateway":"172.17.0.1"}]
- [1] 網橋模式(
bridge
)
該模式是 Docker
預設的網路模式,此模式會為每一個容器分配 Network Namespace
、設定 IP
等,並將一個主機上的 Docker
容器連線到一個虛擬網橋上。Docker
在安裝時會建立一個名為 docker0
的網橋(bridge
),建立的容器都會預設掛到 docker0
上面。
# 指定使用的網路模式
$ docker run -it --rm busybox
$ docker run -it --rm --network=bridge busybox
# 使用brctl可以檢視docker0上面有哪些容器
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242de1d30e9 no veth40db10a
veth5998947
veth220960a
bash
# 如果想做隔離,可以使用下面命令來建立一下bridge
$ docker network create -d bridge net_bridge
fb0a51e7e720f0......1c33293c58815e31
# 自定自己建立的bridge網路
$ docker run -it --rm --network=net_bridge busybox
- [2] 主機模式(
none
)
在這種模式下,Docker
容器擁有自己的 Network Namespace
,但是,並不為 Docker
容器進行任何網路配置。也就是說,這個 Docker
容器沒有網絡卡、IP
、路由等資訊。這種網路模式下容器只有 lo
迴環網路,沒有其他網絡卡,所以沒有辦法聯網,封閉的網路能很好的保證容器的安全性。
# 指定使用的網路模式
$ docker run -it --rm --network=none busybox
- [3] 容器模式(
container
)
這個模式指定新建立的容器和已經存在的一個容器共享一個Network Namespace
,而不是和宿主機共享。新建立的容器不會建立自己的網絡卡,配置自己的 IP
,而是和一個指定的容器共享 IP
、埠範圍等。同樣,兩個容器除了網路方面,其他的如檔案系統、程序列表等還是隔離的。兩個容器的程序可以通過 lo
網絡卡裝置通訊。
# 啟動依賴的容器
$ docker run -d --name=net_container_1 busybox
# 指定使用的網路模式
$ docker run -it --network=container:net_container_1 busybox
- [4] 無網路模式(
host
)
眾所周知,Docker
使用了 Linux
的 Namespaces
技術來進行資源隔離。比如 PID Namespace
用來隔離程序,Mount Namespace
用來隔離檔案系統,Network Namespace
用來隔離網路等。一個 Network Namespace
提供了一份獨立的網路環境,包括網絡卡、路由、Iptable
規則等都與其他的 Network Namespace
隔離。
一個 Docker
容器一般會分配一個獨立的 Network Namespace
。但如果啟動容器的時候使用 host
模式,那麼這個容器將不會獲得一個獨立的 Network Namespace
,而是和宿主機共用一個 Network Namespace
。容器將不會虛擬出自己的網絡卡,配置自己的 IP 等,而是使用宿主機的 IP
和埠。
使用 host
模式的容器可以直接使用 host
的 IP
地址與外界通訊,容器內部的服務埠也可以使用宿主機的埠,不需要進行 NAT
。host
網路模式最大的優勢就是網路效能比較好,但是 host
上已經使用的埠就不能再用了,網路的隔離性不好。
# 指定使用的網路模式
$ docker run -it --rm --network=host busybox
2. 外部訪問
瞭解外部主機訪問容器內應用程式的基本方式和方法
我們在容器中運行了一些網路應用程式的時候,如果需要讓外部網路能夠訪問到該應用就需要對埠進行映射了。通常,我們都是通過如下三種方式進行設定的。Mac
系統的隨機對映埠是 32768~32900
,Linux
類作業系統的隨機對映埠是 49000~49900
。
- 通過
-p
將容器的指定埠對映到宿主機的指定埠上 - 通過
-P
將容器的指定埠對映到宿主機的隨機埠上
# -p引數可指定多個
# 1.對映所有網路地址
$ docker run -d -p 5000:5000 --name=myapp sonatype/nexus3
# 2.對映到指定地址的指定埠
$ docker run -d -p 127.0.0.1:5000:5000 --name=myapp sonatype/nexus3
# 3.對映到指定地址的任意埠
$ docker run -d -p 127.0.0.1::5000 --name=myapp sonatype/nexus3
$ docker run -d -p 127.0.0.1:5000:5000/udp --name=myapp sonatype/nexus3
bash
# —P引數不能多個
$ docker run -d -P training/webapp python app.py
bash
# 檢視對映埠配置
$ docker port myapp
8081/tcp -> 127.0.0.1:32769
$ docker port myapp 8081
127.0.0.1:32769
# 檢視容器輸出日誌
$ docker logs -f myapp
3. 容器互聯
瞭解容器之前互信互通的方式和方法
當然,還存在同一宿主機上容器之間應用程式的相互通訊問題,為了解決這個問題,我們就需要讓容器之間進行互通,俗稱容器互聯。通常,我們都是通過如下兩種方式進行設定的。但隨著 Docker
網路的完善,強烈建議大家將容器加入自定義的 Docker
網路來連線多個容器,而不是使用 --link
引數。
- 使用
--link
來連線其他容器- 該引數是通過修改環境變數和
/etc/hosts
檔案實現的
- 該引數是通過修改環境變數和
- 使用
network create
來建立新的網路- 建立獨立的網路,將容器都執行在該網路中就可相互訪問
- 使用
docker compose
來多個容器互相連線- 高階用法,後續補充哈
# 引數格式: --link name:alias
# name: 需要連線的容器名稱
# alias: 設定這個連線的別名
$ docker run -d -P --name myapp --link db:db sonatype/nexus3
# 檢視環境變數env輸出
$ docker run -d -P --name myapp --link db:db sonatype/nexus3 env
. . .
DB_NAME=/web2/db
DB_PORT=tcp://172.17.0.5:5432
DB_PORT_5000_TCP=tcp://172.17.0.5:5432
DB_PORT_5000_TCP_PROTO=tcp
DB_PORT_5000_TCP_PORT=5432
DB_PORT_5000_TCP_ADDR=172.17.0.5
# 在容器中檢視IP地址對映資訊
$ docker exec -it myapp "cat /etc/hosts"
172.17.0.7 aed84ee21bde
172.17.0.5 db
......
bash
# 建立新子網路
$ docker network create -d bridge --subnet=172.30.0.0/16 my_network
# 連線指定網路
$ docker run -d --name=myredis \
--network=my_network \
--ip=172.30.0.10 \
-v /Users/escape/redis:/data \
redis redis-server --requirepass 123456
$ docker run -d --name=mypg \
--network=my_network \
--ip=172.30.0.11 \
-v /Users/escape/pg_data:/var/lib/postgresql/data \
-e POSTGRES_DB=draft \
postgres:10
4. 設定 DNS
掌握 DNS 的設定方式和方法
如何自定義配置容器的主機名和 DNS
呢?祕訣就是 Docker
利用虛擬檔案來掛載容器的 3
個相關配置檔案。這種機制可以讓宿主主機 DNS
資訊發生更新後,所有 Docker
容器的 DNS
配置通過 /etc/resolv.conf
檔案立刻得到更新。
# 在容器中使用mount命令可以看到掛載資訊
/dev/sda1 on /etc/resolv.conf type ext4 (rw,relatime,data=ordered)
/dev/sda1 on /etc/hostname type ext4 (rw,relatime,data=ordered)
/dev/sda1 on /etc/hosts type ext4 (rw,relatime,data=ordered)
......
配置全部容器的 DNS
,也可以在 /etc/docker/daemon.json
檔案中增加以下內容來設定。對應 MacOS
而已,需要在客戶端內進行 DNS
的配置並重啟,即可。
{
"dns" : [
"114.114.114.114",
"8.8.8.8"
]
}
如果只是針對於不同的容器使用不同的配置,則可以在啟動時 docker run
時候通過引數的指定來達到同樣的效果。
-
--hostname=HOSTNAME
- 設定容器的主機名
- 會寫入容器內的
/etc/hostname
和/etc/hosts
中
-
--dns=IP_ADDRESS
- 新增 DNS 伺服器
- 會寫入容器內的
/etc/resolv.conf
中
# 舉個栗子
$ docker run -d -p 5000:5000 --name=myapp \
--hostname=my_app_web \
--dns=8.8.8.8 \
sonatype/nexus3
5. 高階設定
瞭解網路配置的實現原理以及 iptable 規則的指定
- [1] 基礎實現原理
當 Docker
啟動時,會自動在主機上建立一個 docker0
虛擬網橋,實際上是 Linux
的一個 bridge
,可以理解為一個軟體交換機,它會在掛載到它的網口之間進行轉發。此後啟動的容器內的網口也會自動分配一個同一網段 172.17.0.0/16
的地址。
$ ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.42.1 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:f3:87:34:a3 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
當建立一個 Docker
容器的時候,同時會建立了一對 veth pair
介面(當資料包傳送到一個介面時,另外一個介面也可以收到相同的資料包)。這對介面一端在容器內,即 eth0
;另一端在本地並被掛載到 docker0
網橋,名稱以 veth
開頭(例如 vethAQI2QT
)。通過這種方式,主機可以跟容器通訊,容器之間也可以相互通訊。Docker
就建立了在主機和所有容器之間一個虛擬共享網路。
需要注意的是,MacOS
作業系統下 docker
的網路處理方式並不一樣,並沒有上述所說的 docker0
網橋。你可以理解為其實一個假 docker
,所以不建議再生產環境中使用。有興趣的話,可以閱讀官方文件加以理解。
- [2] 容器訪問控制
容器的訪問控制,主要通過 Linux
上的 iptables
防火牆來進行管理和實現。iptables
是 Linux
上預設的防火牆軟體,在大部分發行版中都自帶。
容器要想訪問外部網路,需要本地系統的轉發支援。如果在啟動 Docker
服務的時候設定 --ip-forward=true
, 系統就會自動設定系統的 ip_forward
引數為 1
。
# 容器訪問外部網路
$ sysctl net.ipv4.ip_forward
# 開啟轉發機制
$ sysctl -w net.ipv4.ip_forward=1
容器之間訪問,需要兩方面的支援。容器的網路拓撲是否已經互聯。預設情況下所有容器都會被連線到 docker0
網橋上。本地系統的防火牆軟體,iptables
是否允許通過。
當啟動 Docker
服務的時候,預設會新增一條轉發策略到本地主機 iptables
的 FORWARD
鏈上。--icc=true
的值決定策略是 ACCEPT
還是 DROP
,預設開啟。當然,如果手動指定 --iptables=false
則不會新增 iptables
規則。
$ sudo iptables -t forward -nL
Chain FORWARD (policy DROP)
target prot opt source destination
DOCKER-USER all -- 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-1 all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
......
在啟動 Docker
服務時,可以同時使用 -icc=false --iptables=true
引數來關閉允許相互的網路訪問,並讓 Docker
可以修改系統中的 iptables
規則。使用 --link=CONTAINER_NAME:ALIAS
選項,iptables
會在兩個容器之前新增一條 ACCEPT
規則,允許相互訪問開放的埠。取決於 Dockerfile
中的 EXPOSE
指令
# 舉個栗子
$ docker run -d -p 5000:8081 --name=myapp \
icc=false --iptables=true \
--link=db:app_db \
sonatype/nexus3
- [3] 內外容器訪問
預設情況下,容器可以主動訪問到外部網路的連線,但是外部網路無法訪問到容器。容器所有到外部網路的連線,源地址都會被 NAT
成本地系統的 IP
地址。這是使用 iptables
的源地址偽裝操作實現的。
下述規則將所有源地址在 172.17.0.0/16
網段,目標地址為其他網段(外部網路)的流量動態偽裝為從系統網絡卡發出。MASQUERADE
跟傳統 SNAT
的好處是它能動態從網絡卡獲取地址。
# 檢視主機的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
......
外部訪問容器實現,不管 -p
或 -P
用那種辦法,其實也是在本地的 iptable
的 nat
表中新增相應的規則。規則映射了 0.0.0.0
,意味著將接受主機來自所有介面的流量。可以通過 -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:80 to:172.17.0.2:80
- [4] 設定預設網橋
Docker
服務預設會建立一個 docker0
網橋,它在核心層連通了其他的物理或虛擬網絡卡,這就將所有容器和本地主機都放到同一個物理網路。由於目前 Docker
網橋是 Linux
網橋,使用者可以使用 brctl show 來
檢視網橋和埠連線資訊。
每次建立一個新容器的時候,Docker
從可用的地址段中選擇一個空閒的 IP
地址分配給容器的 eth0
埠。使用本地主機上 docker0
介面的 IP
作為所有容器的預設閘道器。
$ sudo brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242f38734a3 no
- [5] 自定義網橋
除了預設的 docker0
網橋,使用者也可以指定網橋來連線各個容器。在啟動 Docker
服務的時候,使用 -b BRIDGE
或--bridge=BRIDGE
來指定使用的網橋。
# 先停止服務並刪除舊的網橋
$ sudo systemctl stop docker
$ sudo ip link set dev docker0 down
$ sudo brctl delbr docker0
# 建立一個網橋docker1
$ sudo brctl addbr docker1
$ sudo ip addr add 192.168.5.1/24 dev docker1
$ sudo ip link set dev docker1 up
# 檢視確認網橋建立並啟動
$ ip addr show bridge0
在 Docker
配置檔案 /etc/docker/daemon.json
中新增如下內容,即可將 Docker
預設橋接到建立的網橋上。啟動 Docker
服務。新建一個容器,可以看到它已經橋接到了 bridge0
上。
{
"bridge": "bridge0",
}
6. 總結一下
這裡說下,對應網路相關的一些實用庫!
通過上面知識理解,相信我們都對應 docker
的網路有了一定深度的理解了。如果我們不考慮防火牆的話,使用還是比較方便,但是要設定防火牆的話就比較費勁了。
幸好,我們這裡提供了兩個方便我們配置 docker
網路的 github
專案,通過對其的瞭解和使用,可以方便我們管理和配置複雜的網路設定。
-
pipework
- 一個
shell
指令碼 - 可以幫助使用者在比較複雜的場景中完成容器的連線
- 一個
-
playground
- 一個
Python
庫 - 提供完整的容器網路拓撲管理,路由、
NAT
防火牆等
- 一個
7. 參考連結
送人玫瑰,手有餘香!