1. 程式人生 > 實用技巧 >docker容器網路配置

docker容器網路配置

目錄

docker容器網路配置

Linux核心實現名稱空間的建立

ip netns命令

可以藉助ip netns命令來完成對 Network Namespace 的各種操作。ip netns命令來自於iproute安裝包,一般系統會預設安裝,如果沒有的話,請自行安裝。

注意:ip netns命令修改網路配置時需要 sudo 許可權。

可以通過ip netns命令完成對Network Namespace 的相關操作,可以通過ip netns help檢視命令幫助資訊:

[root@localhost ~]# ip netns help
Usage: ip netns list
       ip netns add NAME
       ip netns set NAME NETNSID
       ip [-all] netns delete [NAME]
       ip netns identify [PID]
       ip netns pids NAME
       ip [-all] netns exec [NAME] cmd ...
       ip netns monitor
       ip netns list-id

預設情況下,Linux系統中是沒有任何 Network Namespace的,所以ip netns list命令不會返回任何資訊。

建立Network Namespace

通過命令建立一個名為ns0的名稱空間:

[root@localhost ~]# ip netns list
[root@localhost ~]# ip netns add ns0
[root@localhost ~]# ip netns list
ns0

對於每個 Network Namespace 來說,它會有自己獨立的網絡卡、路由表、ARP 表、iptables 等和網路相關的資源。所以不能重名。

操作Network Namespace

ip命令提供了ip netns exec子命令可以在對應的 Network Namespace 中執行命令。

檢視新建立 Network Namespace 的網絡卡資訊

[root@localhost ~]# ip netns exec ns0 ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

可以看到,新建立的Network Namespace中會預設建立一個lo迴環網絡卡,此時網絡卡處於關閉狀態。

[root@localhost ~]# ip netns exec ns0 ping 127.0.0.1
connect: Network is unreachable

通過下面的命令啟用lo迴環網絡卡:

[root@localhost ~]# ip netns exec ns0 ip link set lo up
[root@localhost ~]# ip netns exec ns0 ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.024 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.026 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.025 ms
64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.027 ms

轉移裝置

我們可以在不同的 Network Namespace 之間轉移裝置(如veth)。由於一個裝置只能屬於一個 Network Namespace ,所以轉移後在這個 Network Namespace 內就看不到這個裝置了。

其中,veth裝置屬於可轉移裝置,而很多其它裝置(如lo、vxlan、ppp、bridge等)是不可以轉移的。

veth pair

veth pair 全稱是 Virtual Ethernet Pair,是一個成對的埠,所有從這對埠一 端進入的資料包都將從另一端出來,反之也是一樣。
引入veth pair是為了在不同的 Network Namespace 直接進行通訊,利用它可以直接將兩個 Network Namespace 連線起來。

建立veth pair

[root@localhost ~]# ip link add type veth
[root@localhost ~]# ip a

4: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 52:06:58:57:d7:c3 brd ff:ff:ff:ff:ff:ff
5: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 7e:b7:b9:c3:cf:1d brd ff:ff:ff:ff:ff:ff

可以看到,此時系統中新增了一對veth pair,將veth0和veth1兩個虛擬網絡卡連線了起來,此時這對 veth pair 處於”未啟用“狀態。

實現Network Namespace間通訊

利用veth pair實現兩個不同的 Network Namespace 之間的通訊

剛才已經建立了一個名為ns0的 Network Namespace,下面再建立一個資訊Network Namespace,命名為ns1

[root@localhost ~]# ip netns add ns1
[root@localhost ~]# ip netns list
ns1
ns0

然後我們將veth0加入到ns0,將veth1加入到ns1

[root@localhost ~]# ip link set veth0 netns ns0
[root@localhost ~]# ip link set veth1 netns ns1

然後我們分別為這對veth pair配置上ip地址,並啟用它們

[root@localhost ~]# ip netns exec ns0 ip link set lo up
[root@localhost ~]# ip netns exec ns0 ip link set veth0 up
[root@localhost ~]# ip netns exec ns0 ip addr add 10.0.0.1/24 dev veth0
[root@localhost ~]# ip netns exec ns1 ip link set lo up
[root@localhost ~]# ip netns exec ns1 ip link set veth1 up
[root@localhost ~]# ip netns exec ns1 ip addr add 10.0.0.2/24 dev veth1

檢視這對veth pair的狀態

[root@localhost ~]# ip netns exec ns0 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
4: veth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 52:06:58:57:d7:c3 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet 10.0.0.1/24 scope global veth0
       valid_lft forever preferred_lft forever
    inet6 fe80::5006:58ff:fe57:d7c3/64 scope link 
       valid_lft forever preferred_lft forever
       
[root@localhost ~]# ip netns exec ns1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
5: veth1@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 7e:b7:b9:c3:cf:1d brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.0.0.2/24 scope global veth1
       valid_lft forever preferred_lft forever
    inet6 fe80::7cb7:b9ff:fec3:cf1d/64 scope link 
       valid_lft forever preferred_lft forever

從上面可以看出,我們已經成功啟用了這個veth pair,併為每個veth裝置分配了對應的ip地址。我們嘗試在ns1中訪問ns0中的ip地址:

[root@localhost ~]# ip netns exec ns1 ping 10.0.0.1
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.034 ms
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=0.031 ms

可以看到,veth pair成功實現了兩個不同Network Namespace之間的網路互動。

veth裝置重新命名

[root@localhost ~]# ip netns exec ns0 ip link set veth0 down
[root@localhost ~]# ip netns exec ns0 ip link set dev veth0 name eth0
[root@localhost ~]# ip netns exec ns0 ip link set eth0 up
[root@localhost ~]# ip netns exec ns0 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 52:06:58:57:d7:c3 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet 10.0.0.1/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::5006:58ff:fe57:d7c3/64 scope link 
       valid_lft forever preferred_lft forever

四種網路模式配置

bridge模式配置

[root@localhost ~]# docker run -it --name t1 --rm busybox

/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
       
# 在建立容器時新增--network bridge與不加--network選項效果是一致的,因為bridge是預設模式
[root@localhost ~]# docker run -it --name t1 --network bridge --rm busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever



none模式配置

[root@localhost ~]# docker run -it --name ti --network none --rm busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
/ # exit

container模式配置

啟動第一個容器

[root@localhost ~]# docker run -it --name b1 --rm busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

啟動第二個容器

[root@localhost ~]# docker run -it --name b2 --rm busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
12: eth0@if13: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

可以看到名為b2的容器IP地址是10.0.0.3,與第一個容器的IP地址不是一樣的,也就是說並沒有共享網路,此時如果我們將第二個容器的啟動方式改變一下,就可以使名為b2的容器IP與b1容器IP一致,也即共享IP,但不共享檔案系統。

[root@localhost ~]# docker run -it --name b2 --rm --network container:b1 busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

此時我們在b1容器上建立一個目錄

/ # mkdir /tmp/data
/ # ls /tmp/
data

到b2容器上檢查/tmp目錄會發現並沒有這個目錄,因為檔案系統是處於隔離狀態,僅僅是共享了網路而已。

在b2容器上部署一個站點

/ # ls /tmp/data
ls: /tmp/data: No such file or directory
/ # 
/ # 
/ # echo 'hello world' > /tmp/index.html
/ # ls /tmp/
index.html
/ # 
/ # httpd -h /tmp
/ # 
/ # netstat -antl
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0 :::80                   :::*                    LISTEN  

在b1容器上用本地地址去訪問此站點

/ # wget -O - -q 127.0.0.1:80
hello world

由此可見,container模式下的容器間關係就相當於一臺主機上的兩個不同程序

host模式配置

啟動容器時直接指明模式為host

[root@localhost ~]# docker run -it --name b2 --rm --network host busybox 
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000
    link/ether 00:0c:29:f6:6c:bc brd ff:ff:ff:ff:ff:ff
    inet 192.168.32.125/24 brd 192.168.32.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fef6:6cbc/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue 
    link/ether 02:42:7f:03:dc:d0 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:7fff:fe03:dcd0/64 scope link 
       valid_lft forever preferred_lft forever

host直接使用了宿主機的IP

容器的常用操作

檢視容器的主機名

[root@localhost ~]# docker run -it --name t1 --network bridge --rm busybox
/ # hostname
feef68ae039c

在容器啟動時注入主機名

[root@localhost ~]# docker run -it --name t1 --network bridge --hostname www --rm busybox
/ # cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2      www		# 注入主機名時會自動建立主機名到IP的對映關係


/ # cat /etc/resolv.conf 
# Generated by NetworkManager
nameserver 114.114.114.114		 # DNS也會自動配置為宿主機的DNS
nameserver 8.8.8.8


/ # ping www.baidu.com
PING www.baidu.com (14.215.177.38): 56 data bytes
64 bytes from 14.215.177.38: seq=0 ttl=127 time=26.342 ms
64 bytes from 14.215.177.38: seq=1 ttl=127 time=23.439 ms
64 bytes from 14.215.177.38: seq=2 ttl=127 time=23.832 ms

手動指定容器要使用的DNS

[root@localhost ~]# docker run -it --name t1 --network bridge --hostname www.text.com --dns 114.114.114.114 --rm busybox

/ # cat /etc/resolv.conf 
nameserver 114.114.114.114

/ # cat /etc/hosts 
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2      www.text.com www

手動往/etc/hosts檔案中注入主機名到IP地址的對映

[root@localhost ~]# docker run -it --name t1 --network bridge --hostname www --add-host www.a.com:1.1.1.1 --rm busybox 

/ # cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
1.1.1.1 www.a.com
172.17.0.2      www

開放容器埠

執行docker run的時候有個-p選項,可以將容器中的應用埠對映到宿主機中,從而實現讓外部主機可以通過訪問宿主機的某埠來訪問容器內應用的目的。

-p選項能夠使用多次,其所能夠暴露的埠必須是容器確實在監聽的埠。

-p選項的使用格式:

  • -P
    • 將指定的容器埠對映至主機所有地址的一個動態埠
  • -p :
    • 將容器埠對映至指定的主機埠
  • -P ::
    • 將指定的容器埠對映至主機指定的動態埠
  • -p ::
    • 將指定的容器埠對映至主機指定的埠

動態埠指的是隨機埠,具體的對映結果可使用docker port命令檢視。

[root@localhost ~]# docker run --name web --rm -p 80 nginx

以上命令執行後會一直佔用著前端,我們新開一個終端連線來看一下容器的80埠被對映到了宿主機的什麼埠上

[root@localhost ~]# docker port web
80/tcp -> 0.0.0.0:32768

由此可見,容器的80埠被暴露到了宿主機的32769埠上,在宿主機上IP都可以訪問

[root@localhost ~]# curl 127.0.0.1:32768
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

iptables防火牆規則將隨容器的建立自動生成,隨容器的刪除自動刪除規則。

將容器埠對映到指定IP的隨機埠

[root@localhost ~]# docker run --name web --rm -p 192.168.32.125::80 nginx

在另一個終端上檢視埠對映情況

[root@localhost ~]# docker port web
80/tcp -> 192.168.32.125:32768

將容器埠對映到宿主機的指定埠

[root@localhost ~]# docker run --name web --rm -p 80:80 nginx

在另一個終端上檢視埠對映情況

[root@localhost ~]# docker port web
80/tcp -> 0.0.0.0:80

自定義docker0橋的網路屬性資訊

官方文件配置

配置預設網橋網路

自定義docker0橋的網路屬性資訊需要修改/etc/docker/daemon.json配置檔案。下面是一個官方示例:

{
  "bip": "192.168.1.5/24",
  "fixed-cidr": "192.168.1.5/25",
  "fixed-cidr-v6": "2001:db8::/64",
  "mtu": 1500,
  "default-gateway": "10.20.1.1",
  "default-gateway-v6": "2001:db8:abcd::89",
  "dns": ["10.20.1.2","10.20.1.3"]
}

bip 就是bridge ip ,用於指定docker0橋自身的IP地址;其它選項可通過此地址計算得出。

重新啟動Docker才能更改生效。

docker遠端連線

Docker Daemon 的連線方式

  • UNIX 域套接字

預設就是這種方式, 會生成一個 /var/run/docker.sock 檔案, UNIX 域套接字用於本地程序之間的通訊, 這種方式相比於網路套接字效率更高, 但侷限性就是隻能被本地的客戶端訪問。

  • tcp 埠監聽

服務端開啟埠監聽 dockerd -H IP:PORT , 客戶端通過指定IP和埠訪問服務端 docker -H IP:PORT 。通過這種方式, 任何人只要知道了你暴露的ip和埠就能隨意訪問你的docker服務了, 這是一件很危險的事, 因為docker的許可權很高, 不法分子可以從這突破取得服務端宿主機的最高許可權。

官網配置示例

dockerd守護程序的C/S,其預設僅監聽Unix Socket格式的地址(/var/run/docker.sock),如果要使用TCP套接字,則需要修改/etc/docker/daemon.json配置檔案,新增如下內容,然後重啟docker服務:

"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]

在客戶端上向dockerd直接傳遞“-H|--host”選項指定要控制哪臺主機上的docker容器

docker -H 192.168.32.125:2375 ps