一、Docker的網絡概念
docker受一個github上的issue啟發,引入了容器網絡模型(container network model,CNM),容器網絡模型主要包含了3個概念
-
network:網絡,可以理解為一個Driver,是一個第三方網絡棧,包含多種網絡模式:單主機網絡模式(none、host、bridge,joined container),多主機網絡模式(overlay、macvlan、flannel)
-
sandbox:沙盒,它定義了容器內的虛擬網卡、DNS和路由表,是network namespace的一種實現,是容器的內部網絡棧
-
endpoint:端點,用於連接sandbox和network
我們可以類比傳統網絡模型,將network比作交換機,sandbox比作網卡,endpoint比作接口和網線,另外,docker在創建容器時,先調用控制器創建sandbox對象,再調用容器運行時為容器創建network namespace
二、Docker的網絡模式
這里我們先討論docker的單主機網絡模式,它包括以下4類:host、bridge、none、joined-containe
2.1、Host模式
docker不會為容器創建獨有的network namespace;使用宿主機的默認網絡命名空間,共享一個網絡棧;表現為容器內和宿主機的IP一致;這種模式用於網絡性能較高的場景,但安全隔離性相對差一些。
2.2、Bridge模式
橋接模式,有點類型VM-NAT,dockerd進程啟動時會創建一個docker0網橋,容器內的數據通過這個網卡設備與宿主機進行數據傳輸。
docker0
子網中分配一個 IP 給容器使用,並設置 docker0 的 IP 地址為容器的
默認網關。在主機上創建一對虛擬網卡
veth pair
設備,Docker 將 veth pair 設備的一端放在新創建的容器中,並命名為
eth0
(容器的網卡),另一端放在主機中,以
vethxxx
這樣類似的名字命名,並將這個網絡設備加入到 docker0 網橋中。
bridge
模式是 docker 的默認網絡模式,不寫
–net
參數,就是
bridge
模式。使用
docker run -p
時,docker 實際是在
iptables
做了
DNAT
規則,實現端口轉發功能。可以使用
iptables -vnL
查看。
docker會為容器創建獨有的network namespace,也會為這個命名空間配置好虛擬網卡,路由,DNS,IP地址與iptables規則(也就是sandbox的內容)。
2.3、None模式
none模式可以說是橋接模式的一種特例,docker會為容器創建獨有的network namespace ,但不會為這個命名空間准備虛擬網卡,IP地址,路由等,需要用戶自己配置。
2.4、joined-container 模式
容器共享模式,這種模式是host模式的一種延伸,一組容器共享一個network namespace;對外表現為他們有共同的IP地址,共享一個網絡棧;kubernetes的pod就是使用的這一模式。
關於跨主機的docker網絡通信,包含overlay、macvaln,又包含calico、flannel、weave等方案,不過跨主機的docker網絡管理更多的是交給kubernetes或swarm等編排工具去實現了。
三、Docker網絡對象和網絡模式的關系
回顧docker網絡對象和網絡模式的關系其實就是下面這一張表格,每個容器在network namespace中的占比決定了采用哪種網絡模式
四、Iptables的使用
4.1、iptables語法
[root@localhost ~]# iptables -h iptables v1.4.21 Usage: iptables -[ACD] chain rule-specification [options] iptables -I chain [rulenum] rule-specification [options] iptables -R chain rulenum rule-specification [options] iptables -D chain rulenum [options] iptables -[LS] [chain [rulenum]] [options] iptables -[FZ] [chain] [options] iptables -[NX] chain iptables -E old-chain-name new-chain-name iptables -P chain target [options] iptables -h (print this help information) Commands: Either long or short options are allowed. --append -A chain Append to chain --check -C chain Check for the existence of a rule --delete -D chain Delete matching rule from chain --delete -D chain rulenum Delete rule rulenum (1 = first) from chain --insert -I chain [rulenum] Insert in chain as rulenum (default 1=first) --replace -R chain rulenum Replace rule rulenum (1 = first) in chain --list -L [chain [rulenum]] List the rules in a chain or all chains --list-rules -S [chain [rulenum]] Print the rules in a chain or all chains --flush -F [chain] Delete all rules in chain or all chains --zero -Z [chain [rulenum]] Zero counters in chain or all chains --new -N chain Create a new user-defined chain --delete-chain -X [chain] Delete a user-defined chain --policy -P chain target Change policy on chain to target --rename-chain -E old-chain new-chain Change chain name, (moving any references) Options: --ipv4 -4 Nothing (line is ignored by ip6tables-restore) --ipv6 -6 Error (line is ignored by iptables-restore) [!] --protocol -p proto protocol: by number or name, eg. `tcp' [!] --source -s address[/mask][...] source specification [!] --destination -d address[/mask][...] destination specification [!] --in-interface -i input name[+] network interface name ([+] for wildcard) --jump -j target target for rule (may load target extension) --goto -g chain jump to chain with no return --match -m match extended match (may load extension) --numeric -n numeric output of addresses and ports [!] --out-interface -o output name[+] network interface name ([+] for wildcard) --table -t table table to manipulate (default: `filter') --verbose -v verbose mode --wait -w [seconds] maximum wait to acquire xtables lock before give up --wait-interval -W [usecs] wait time to try to acquire xtables lock default is 1 second --line-numbers print line numbers when listing --exact -x expand numbers (display exact values) [!] --fragment -f match second or further fragments only --modprobe=<command> try to insert modules using this command --set-counters PKTS BYTES set the counter during insert/append [!] --version -V print package version.
4.2、阻止其他主機的ping請求
iptables -A INPUT -p icmp -j REJECT
4.3、開放本機的9501端口
iptables -I INPUT -p tcp --dport 9501 -j ACCEPT iptables -I INPUT -p udp --dport 9501 -j ACCEPT
4.4、禁止本機訪問外部web服務
iptables -A OUTPUT -p tcp --dport 80 -j REJECT
使用 iptables -L
可以查看已設置的規則,iptables -D
可以刪除規則,iptables 命令執行完是即時生效的,但是如果主機重啟,已設置的規則就會丟失,這里可以使用 iptables-save
和 iptables-restore
。iptables-save 將現有規則保存成文件,iptables-restore 從文件中恢復規則。
4.5、docker容器
docker run -d --name redis01 -p 6380:6379 redis
該命令執行后,docker 會在 iptables 自定義鏈 DOCKER 中定義轉發規則,如果此時系統的 net.ipv4.ip_forward
為0,該命令執行完會提示:WARNING: IPv4 forwarding is disabled. Networking will not work,只需打開該配置就行了,無需重啟容器。此時查看 DOCKER 鏈可以看到添加了一條允許所有來源轉發到6379端口的流量,用 redis-cli 也可以順利連上
開發中,經常會遇到容器里面放問宿主機的情況,除了使用 host.docker.internal
之外,還可以配置 extra_hosts 解決,因為 docker0 與 宿主機是相通的,直接用 ifconfig
查看宿主機 en0
網卡的ip地址,配置到 extra_hosts 即可,如:
version: '3' networks: web-network: driver: bridge services: fpm: build: context: ./fpm ports: - '8080:8080' networks: - web-network extra_hosts: - "test.com:192.168.1.100"
五、Docker與Iptables
Docker提供了bridge, host, overlay等多種網絡,同一個Docker宿主機上同時存在多個不同類型的網絡。位於不同網絡中的容器,彼此之間是無法通信的。Docker容器的跨網絡隔離與通信,是借助了iptables的機制。我們知道,iptables的filter表中默認划分為IPNUT, FORWARD和OUTPUT共3個鏈,詳情請參考 iptables及其過濾規則。Docker在FORWARD鏈中,還額外提供了自己的鏈,以實現bridge網絡之間的隔離與通信。
5.1、 Docker在iptables的filter表中的鏈
在2015.12之前,Docker只額外提供了DOCKER鏈。在此之后,直到Docker 17.06.0(2017.6)之前的版本中,Docker提供了如下2個鏈:
DOCKER DOCKER-ISOLATION
在Docker 17.06.0(2017.6)及之后,Docker 18.03.1(2018.4)及之前的版本中,Docker提供了如下3個鏈:
DOCKER DOCKER-ISOLATION DOCKER-USER
查看Docker的iptables如下:
Chain FORWARD (policy ACCEPT) 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 DOCKER all -- 0.0.0.0/0 0.0.0.0/0
在Docker 18.05.0(2018.5)及之后的版本中,提供如下4個chain:
DOCKER DOCKER-ISOLATION-STAGE-1 DOCKER-ISOLATION-STAGE-2 DOCKER-USER
目前,Docker默認對宿主機的iptables設置規則完整一覽:
iptables -N DOCKER iptables -N DOCKER-ISOLATION-STAGE-1 iptables -N DOCKER-ISOLATION-STAGE-2 iptables -N DOCKER-USER iptables -A FORWARD -j DOCKER-USER iptables -A FORWARD -j DOCKER-ISOLATION-STAGE-1 iptables -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT iptables -A FORWARD -o docker0 -j DOCKER iptables -A FORWARD -i docker0 ! -o docker0 -j ACCEPT iptables -A FORWARD -i docker0 -o docker0 -j ACCEPT iptables -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2 iptables -A DOCKER-ISOLATION-STAGE-1 -j RETURN iptables -A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP iptables -A DOCKER-ISOLATION-STAGE-2 -j RETURN iptables -A DOCKER-USER -j RETURN
5.2、Docker的DOCKER鏈
僅處理從宿主機到docker0的IP數據包。
5.3、Docker的DOCKER-ISOLATION鏈
可以看到,為了隔離在不同的bridge網絡之間的容器,Docker提供了兩個DOCKER-ISOLATION階段實現。DOCKER-ISOLATION-STAGE-1鏈過濾源地址是bridge網絡(默認docker0)的IP數據包,匹配的IP數據包再進入DOCKER-ISOLATION-STAGE-2鏈處理,不匹配就返回到父鏈FORWARD。在DOCKER-ISOLATION-STAGE-2鏈中,進一步處理目的地址是bridge網絡的IP數據包,匹配的IP數據包表示該IP數據包是從一個bridge網絡的網橋發出,到另一個bridge網絡的網橋,這樣的IP數據包來自其他bridge網絡,將被直接DROP;不匹配的IP數據包就返回到父鏈FORWARD繼續進行后續處理。
5.4、Docker的DOCKER-USER鏈
Docker啟動時,會加載DOCKER鏈和DOCKER-ISOLATION(現在是DOCKER-ISOLATION-STAGE-1)鏈中的過濾規則,並使之生效。絕對禁止修改這里的過濾規則。
如果用戶要補充Docker的過濾規則,強烈建議追加到DOCKER-USER鏈。DOCKER-USER鏈中的過濾規則,將先於Docker默認創建的規則被加載,從而能夠覆蓋Docker在DOCKER鏈和DOCKER-ISOLATION鏈中的默認過濾規則。例如,Docker啟動后,默認任何外部source IP都被允許轉發,從而能夠從該source IP連接到宿主機上的任何Docker容器實例。如果只允許一個指定的IP訪問容器實例,可以插入路由規則到DOCKER-USER鏈中,從而能夠在DOCKER鏈之前被加載。示例如下:
只允許192.168.1.1訪問容器
iptables -A DOCKER-USER -i docker0 ! -s 192.168.1.1 -j DROP
只允許192.168.1.0/24網段中的IP訪問容器
iptables -A DOCKER-USER -i docker0 ! -s 192.168.1.0/24 -j DROP
只允許192.168.1.1-192.168.1.3網段中的IP訪問容器(需要借助於iprange模塊)
iptables -A DOCKER-USER -m iprange -i docker0 ! --src-range 192.168.1.1-192.168.1.3 -j DROP
5.5、Docker在iptables的nat表中的規則
為了能夠從容器中訪問其他Docker宿主機,Docker需要在iptables的nat表中的POSTROUTING鏈中插入轉發規則,示例如下:
iptables -t nat -A POSTROUTING -s 172.18.0.0/16 -j MASQUERADE
上述配置,還進一步限制了容器實例的IP范圍,這是為了區分Docker宿主機上有多個bridge網絡的情況。
5.6、Docker中禁止修改iptables過濾表
dockerd啟動時,參數--iptables默認為true,表示允許修改iptables路由表。要禁用該功能,可以有兩個選擇:設置啟動參數--iptables=false
修改配置文件/etc/docker/daemon.json,設置"iptables": "false";然后執行systemctl reload docker重新加載