1. 程式人生 > 其它 >Docker網路配置⚽

Docker網路配置⚽

Docker網路配置

文章作者: Escape  文章連結: https://www.escapelife.site/posts/ddc27dcd.html

紙上得來終覺淺,絕知此事要躬行。

當你開始大規模使用 Docker 時,你會發現需要了解很多關於網路的知識。無論你是在單主機上進行部署,還是在叢集上部署,你總得和網路打交道。Docker 網路有四種模式:網橋模式,主機模式,容器模式和無網路模式。

Docker網路配置

1. 網路模式

Docker 預設提供了四種網路模式,供容器啟動的時候選擇,分別是 bridgenonecontainer 和 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"}]
Docker網路配置
  • [1] 網橋模式(bridge)

該模式是 Docker 預設的網路模式,此模式會為每一個容器分配 Network Namespace 、設定 IP 等,並將一個主機上的 Docker 容器連線到一個虛擬網橋上。Docker 在安裝時會建立一個名為 docker0 的網橋(bridge),建立的容器都會預設掛到 docker0 上面。

  bash
# 指定使用的網路模式
$ 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
Docker網路配置
  • [2] 主機模式(none)

在這種模式下,Docker 容器擁有自己的 Network Namespace,但是,並不為 Docker 容器進行任何網路配置。也就是說,這個 Docker 容器沒有網絡卡、IP、路由等資訊。這種網路模式下容器只有 lo 迴環網路,沒有其他網絡卡,所以沒有辦法聯網,封閉的網路能很好的保證容器的安全性。

  bash
# 指定使用的網路模式
$ docker run -it --rm --network=none busybox
Docker網路配置
  • [3] 容器模式(container)

這個模式指定新建立的容器和已經存在的一個容器共享一個Network Namespace,而不是和宿主機共享。新建立的容器不會建立自己的網絡卡,配置自己的 IP,而是和一個指定的容器共享 IP、埠範圍等。同樣,兩個容器除了網路方面,其他的如檔案系統、程序列表等還是隔離的。兩個容器的程序可以通過 lo 網絡卡裝置通訊。

  bash
# 啟動依賴的容器
$ docker run -d --name=net_container_1 busybox

# 指定使用的網路模式
$ docker run -it --network=container:net_container_1 busybox
Docker網路配置
  • [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 地址與外界通訊,容器內部的服務埠也可以使用宿主機的埠,不需要進行 NAThost 網路模式最大的優勢就是網路效能比較好,但是 host 上已經使用的埠就不能再用了,網路的隔離性不好。

  bash
# 指定使用的網路模式
$ docker run -it --rm --network=host busybox
Docker網路配置

2. 外部訪問

瞭解外部主機訪問容器內應用程式的基本方式和方法

我們在容器中運行了一些網路應用程式的時候,如果需要讓外部網路能夠訪問到該應用就需要對埠進行映射了。通常,我們都是通過如下三種方式進行設定的。Mac 系統的隨機對映埠是 32768~32900Linux 類作業系統的隨機對映埠是 49000~49900

  • 通過 -p 將容器的指定埠對映到宿主機的指定埠上
  • 通過 -P 將容器的指定埠對映到宿主機的隨機埠上
  bash
# -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 來多個容器互相連線
    • 高階用法,後續補充哈
  bash
# 引數格式: --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 檔案立刻得到更新。

  bash
# 在容器中使用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 的配置並重啟,即可。

  bash
{
  "dns" : [
    "114.114.114.114",
    "8.8.8.8"
  ]
}

如果只是針對於不同的容器使用不同的配置,則可以在啟動時 docker run 時候通過引數的指定來達到同樣的效果。

  • --hostname=HOSTNAME
    • 設定容器的主機名
    • 會寫入容器內的 /etc/hostname 和 /etc/hosts 中
  • --dns=IP_ADDRESS
    • 新增 DNS 伺服器
    • 會寫入容器內的 /etc/resolv.conf 中
  bash
# 舉個栗子
$ 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 的地址。

  bash
$ 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,所以不建議再生產環境中使用。有興趣的話,可以閱讀官方文件加以理解。

Docker網路配置
  • [2] 容器訪問控制

容器的訪問控制,主要通過 Linux 上的 iptables 防火牆來進行管理和實現。iptables 是 Linux 上預設的防火牆軟體,在大部分發行版中都自帶。

容器要想訪問外部網路,需要本地系統的轉發支援。如果在啟動 Docker 服務的時候設定 --ip-forward=true, 系統就會自動設定系統的 ip_forward 引數為 1

  bash
# 容器訪問外部網路
$ sysctl net.ipv4.ip_forward

# 開啟轉發機制
$ sysctl -w net.ipv4.ip_forward=1

容器之間訪問,需要兩方面的支援。容器的網路拓撲是否已經互聯。預設情況下所有容器都會被連線到 docker0 網橋上。本地系統的防火牆軟體,iptables 是否允許通過。

當啟動 Docker 服務的時候,預設會新增一條轉發策略到本地主機 iptables 的 FORWARD 鏈上。--icc=true的值決定策略是 ACCEPT 還是 DROP,預設開啟。當然,如果手動指定 --iptables=false 則不會新增 iptables 規則。

  bash
$ 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 指令

  bash
# 舉個栗子
$ 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 的好處是它能動態從網絡卡獲取地址。

  bash
# 檢視主機的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 引數,來限制指定地址訪問。

  bash
$ 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 作為所有容器的預設閘道器。

  bash
$ sudo brctl show
bridge name    bridge id        STP enabled    interfaces
docker0        8000.0242f38734a3    no
  • [5] 自定義網橋

除了預設的 docker0 網橋,使用者也可以指定網橋來連線各個容器。在啟動 Docker 服務的時候,使用 -b BRIDGE 或--bridge=BRIDGE 來指定使用的網橋。

  bash
# 先停止服務並刪除舊的網橋
$ 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 上。

  bash
{
  "bridge": "bridge0",
}

6. 總結一下

這裡說下,對應網路相關的一些實用庫!

通過上面知識理解,相信我們都對應 docker 的網路有了一定深度的理解了。如果我們不考慮防火牆的話,使用還是比較方便,但是要設定防火牆的話就比較費勁了。

幸好,我們這裡提供了兩個方便我們配置 docker 網路的 github 專案,通過對其的瞭解和使用,可以方便我們管理和配置複雜的網路設定。

  • pipework
    • 一個 shell 指令碼
    • 可以幫助使用者在比較複雜的場景中完成容器的連線
  • playground
    • 一個 Python 庫
    • 提供完整的容器網路拓撲管理,路由、NAT防火牆等

7. 參考連結

送人玫瑰,手有餘香!