docker 实践十:docker 网络管理

戏子无情 提交于 2019-12-26 08:24:54

本篇是关于 docker 网络管理的内容,同时也包含了 docker 网络的高级应用。

注:环境为 CentOS7,docker 19.03。

docker 网络基础

docker 网络模型

在 docker 1.7版本中,官方就开始将 docker 网络部分的代码抽出并单独创建独立的网络库,那就是 libnetwork。之后,在 docker 1.9版本中,有推出一套 docker network 命令来管理主机的网络。

docker 官方整合了网络驱动并使之标准化,并使用 CNM(Container Network Model)来定义构建容器虚拟化网络的模型,提供了可以用于开发多种网咯驱动的标准化接口和组件。

libnetwork 中使用 CNM 来完成网络功能的提供,CNM中主要有 sandbox(沙盒)、endpoint(端点)和 network(网络)三种组件。同时内置六种类型驱动。

来介绍下 CNM 的三个核心组件:

  • 沙盒:一个沙盒包含了一个容器网络栈的信息。沙盒可以对容器的接口、路由和 DNS 设置等进行管理。沙盒的实现可以是 Linux network namespace、FreeBSD Jail 或者类似的机制。一个沙盒可以有多个端点和多个网络。
  • 端点:一个端点可以加入一个沙盒和一个网络。端点的实现可以是veth pair、Open vSwitch 内部端口或者相似的设备。一个端点只可以属于一个网络并且只属于一个沙盒。
  • 网络:一个网络是一组可以直接相互连通的端点。网络的实现可以是 Linux bridge、VLAN 等。一个网络可以包含多个端点。

libnetwork 内置的六种驱动:

  • bridge 驱动:bridge 是 Docker 默认设置,使用这个驱动的时候,libnetwork 将创建出来的 docker 容器连接到 docker 网桥上(Docker0)。bridge 模式一般情况下已经可以满足容器最基本的使用需求。但由于与外部通信使用 NAT,增加了通信的复杂性,不适合在复杂场景下使用。
  • host 驱动:使用 host 驱动,libnetwork 容器和宿主机共用同一个 network namespace,使用宿主机的网卡、IP和端口等信息(其他资源是隔离的)。host 模式很好地解决了容器与外界通信地地址转换问题,可以直接使用宿主机地IP进行通信,不存在虚拟化网路带来地性能损耗。但也降低了和主机之间网络层地隔离性,引起网络资源地竞争与冲突。一般私用与容器集群规模不大地场景。
  • overlay 驱动:overlay 驱动采用IETF标准地 VXLAN 方式,并且是 VXLAN 中被普遍认为最适合大规模地云计算虚拟化环境地 SDN controller 模式。使用时,需要一个额外地配置存储服务,如 Consul,etcd,ZooKeeper。在启动 docker daemon 时还要额外添加参数来指定所有使用地配置存储服务地址。
  • remote 驱动:remote 驱动提供可插件化地方式,调用用户自行实现地网络驱动插件。用户只要根据 libnetwork 提供地协议标准,实现其所要求地各个接口并向 docker daemon 注册。
  • null 驱动:null 驱动提供了 network namespace 和自带地 loopback 网卡,如果用户不进行特定地配置就无法使用 docker 网络。它给用户最大地自由度来自定义容器地网络环境。
  • macvlan 驱动:macvlan 驱动和其他驱动相比是最新的一种驱动。它本身是 linxu kernel的模块,本质上是一种网卡虚拟化技术。其功能是允许在同一个物理网卡上虚拟出多个网卡,通过不同的MAC地址在数据链路层进行网络数据的转发,一块网卡上配置多个 MAC 地址(即多个 interface)

mavlan 驱动地注意事项:

  • 由于IP地址耗尽或“VLAN传播”,很容易无意中损坏您的网络,在这种情况下,您的网络中存在大量不合适的MAC地址。
  • 您的网络设备需要能够处理“混杂模式”,其中一个物理接口可以分配多个MAC地址。
  • 如果您的应用程序可以使用桥接器(在单个Docker主机上)或覆盖层(跨多个Docker主机进行通信),那么从长远来看,这些解决方案可能会更好。

而 docker 默认 bridge 驱动的网络环境拓扑图如下

docker network 命令解析

docker network 子命令用于 docker 中网络的操作,它主要包含以下命令:

  • create: 创建⼀个⽹络;
  • connect: 将容器接⼊到⽹络;
  • disconnect: 把容器从⽹络上断开;
  • inspect: 查看⽹络的详细信息。
  • ls: 列出所有的⽹络;
  • prune: 清理⽆⽤的⽹络资源;
  • rm: 删除⼀个⽹络。

1.创建网络

创建网络使用命令 docker network create [OPTIONS] NETWORK,⽀持参数包括:

  • --attachable[=false]: ⽀持⼿动容器挂载;
  • --aux-address=map[]: 辅助的IP地址;
  • --config-from="": 从某个⽹络复制配置数据;·-config-only[=false]: 启⽤仅可配置模式;
  • -d,--driver="bridge": ⽹络驱动类型, 如bridge或overlay;
  • --gateway=[]: ⽹关地址;
  • --ingress[=false]: 创建⼀个Swarm可路由的⽹状⽹络⽤于负载均衡, 可将对某个服务的请求⾃动转发给⼀个合适的副本;
  • --internal[=false]: 内部模式, 禁⽌外部对所创建⽹络的访问;
  • --ip-range=[]: 指定分配IP地址范围;
  • --ipam-driver="default": IP地址管理的插件类型;
  • --ipam-opt=map[]: IP地址管理插件的选项;
  • --ipv6[=false]: ⽀持IPv6地址;
  • --label value: 为⽹络添加元标签信息;
  • -o,--opt=map[]: ⽹络驱动所⽀持的选项;
  • --scope="": 指定⽹络范围;
  • --subnet=[]: ⽹络地址段, CIDR格式, 如172.17.0.0/16。
# docker network create nettest
e128e4369a3366a246c0204c5e6d9a05922a7429988ee224025932e2aa1ab1ae

2.列出网络

命令 docker network ls [OPTIONS] 用来列出 docker 的所有网络,支持的选项有:

  • -f,--filter="": 指定输出过滤器, 如driver=bridge;
  • --format="": 给定⼀个golang模板字符串, 对输出结果进⾏格式化;
  • --no-trunc[=false]: 不截断地输出内容;
  • -q,--quiet[=false]: 安静模式, 只打印⽹络的ID。
# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
e08e2741bb1a        bridge              bridge              local
1e7a510c218d        docker_gwbridge     bridge              local
ff8cf4a7552a        host                host                local
sr8ng3ogxxa8        ingress             overlay             swarm
e128e4369a33        nettest             bridge              local
8514568883d2        none                null                local

docker daemon 启动时,默认有 null,host 和 bridge 三种类型的驱动。linux 上会创建一个名为 docker0 的网卡,对应 docker 中的 bridge 驱动,另外,ingress 是 overlay 驱动,使用命令 docker swarm 创建集群时就会生成该驱动。

3.接入网络

docker network connect [OPTIONS] NETWOKR CONTAINER 命令将一个容器连接到一个已存在的网络上,同一个网络上的容器可以互通,容器和网络为多对多关系。也可以在 docker run 命令时指定 -net 参数指定容器连接的网络。支持的参数为:

  • --alias=[]: 为容器添加⼀个别名, 此别名仅在所添加⽹络上可见;
  • --ip="": 指定IP地址, 需要注意不能跟已接⼊的容器地址冲突;
  • --ip6="": 指定IPv6地址;
  • --link value: 添加链接到另外⼀个容器;
  • --link-local-ip=[]: 为容器添加⼀个链接地址。
# docker run -itd --name node1 --network nettest busybox sh                     
a9ec9af4fac7074031d3f23068600c1a8151771611aa57914ab77df67f5914cd
# docker run -itd --name node2  busybox sh                   
# docker network connect nettest node2
# docker exec -it node2 sh
# ping node1
PING node1 (172.19.0.2): 56 data bytes
64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.120 ms

4.断开网络

与 connect 对应的命令时 docker network disconnect [OPTIONS] NETWORK CONTAINER,⽀持参数包括 -f, -force: 强制把容器从⽹络上移除。

# docker network disconnect nettest node2

5.查看⽹络信息

docker network inspect [OPTIONS] NETWORK [NETWORK...] 命令⽤于查看⼀个⽹络的具体信息(JSON格式) , 包括接⼊的容器、 ⽹络配置信息等。⽀持参数包括:

  • -f,--format="": 给定⼀个Golang模板字符串, 对输出结果进⾏格式化, 如只查看地址配置可以⽤-f'{{.IPAM.Config}}';
  • -v,--verbose[=false]: 输出调试信息。

6.清理⽆⽤⽹络

docker network prune [OPTIONS] [flags] 命令⽤于清理已经没有容器使⽤的⽹络。⽀持参数包括:

  • --filter="": 指定选择过滤器;
  • -f,--force: 强制清理资源。

7.删除⽹络

docker network rm NETWORK [NETWORK...] 命令⽤于删除指定的⽹络。 当⽹络上没有容器连接上时, 才会成功删除。

docker 网络高级应用

玩转 network namespace

docker 网络高级应用就涉及到 Linux network namespace。ip 是 linux 系统下强大的网络配置工具,我们可以使用 ip 命令来配置管理 network namespace。

1.使用 ip netns 命令操作 network namespace

ip netns 命令用来操作 network namespace,使用方法如下:
创建一个 network namespace

# ip netns add nstest

列出系统中所有的 network namespace

# ip netns list
nstest

删除一个 network namespace

# ip netns delete nstest

在一个 network namespace 中执行命令

# ip netns exec nstest ip addr
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

2.使用ip命令为 network namespace配置网卡

使用 ip netns add 命令创建一个 network namespace 后,就生成一个独立的网络空间,它默认只有一个 lo 设备,这样我们可以根据需求添加网卡、配置IP、设置路由规则等。

之前创建的 lo 设备,现在来启动它

# ip netns exec nstest ip link set dev lo up

在主机上创建两张虚拟网卡 veth-a 和 veth-b

ip link add veth-a type veth peer name veth-b

将 veth-b 设备添加到 nstest 这个 network namespace 中,veth-a 留在主机中

# ip link set veth-b netns nstest

现在 nstest 中就存在两块网卡 lo 和 veth-b 了

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
24: veth-b@if25: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 32:fe:32:98:1f:15 brd ff:ff:ff:ff:ff:ff link-netnsid 0

然后为网卡分配IP并启动网卡

# ip addr add 10.0.0.1/24 dev veth-a
# ip link set dev veth-a ip
# ip link set dev veth-a up

在 nstest 中配置 veth-a 并启动

# ip netns exec nstest ip addr add 10.0.0.2/24 dev veth-b
# ip netns exec nstest ip link set dev veth-b up
# ip netns exec nstest ip addr
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
24: veth-b@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 32:fe:32:98:1f:15 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.0.0.2/24 scope global veth-b
       valid_lft forever preferred_lft forever
    inet6 fe80::30fe:32ff:fe98:1f15/64 scope link 
       valid_lft forever preferred_lft forever

给两张网卡配置了 IP 后,就在各自的 network namespace 中生成一条路由,使用命令 ip route 命令可以查看

# ip route
...
10.0.0.0/24 dev veth-a proto kernel scope link src 10.0.0.1 
# ip netns exec nstest ip route
10.0.0.0/24 dev veth-b proto kernel scope link src 10.0.0.2 

3.将两个 network namespace 连接起来

我们再扩展之前的配置,利用 veth pair 设备连接两个 network namespace,结构如下图:

# 创建两个 network namespace
# ip netns add ns1
# ip netns add ns2
# 创建 veth pair 设备 veth-a,veth-b
# ip link add veth-a type veth peer name veth-b
# 将网卡放在两个 network namespace 中
# ip link set veth-a netns ns1
# ip link set veth-b netns ns2 
# 启动网卡
# ip netns exec ns1 ip link set dev veth-a up
# ip netns exec ns2 ip link set dev veth-b up 
# 分配ip
# ip netns exec ns1 ip addr add 10.0.0.1/24 dev veth-a
# ip netns exec ns2 ip addr add 10.0.0.2/24 dev veth-b  
# ip netns exec ns1 ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.155 ms

4.使用 ip 命令配置 docker 容器网络

ip 命令不能直接管理到 docker 容器所在的网络,但经过相应的处理可以达到我们的目的。使用 ip netns list 查看到的 network namespace 保存在目录 /var/run/netns 下,使用 docker 创建的容器的同时也会创建一个 network namespace。

# docker run -itd --name test1 centos /bin/bash
# docker inspect test1 --format "{{.State.Pid}}"
14404
# 查询到 docker 容器对应的 pid 之后,我们就可以使用 pid 查看对应的 network namespace
# ll /proc/14404/ns/net 
lrwxrwxrwx 1 root root 0 Sep  8 23:02 /proc/14404/ns/net -> net:[4026532749]

每个 network namespace 中的进程有不同的 net:[] 号码分配,这些号码代表不同的 network namespace,拥有相同 net:[] 号码的进程属于同一个 network namespace。只要将 docker 创建的 network namespace 的文件链接到 /var/run/netns 目录下,就可以使用 ip netns 命令进行操作了。

# mkdir -p /var/run/netns
# ln -s /proc/14404/ns/net /var/run/netns/test1
# ip netns list
test1 (id: 4)
ns2 (id: 3)
ns1 (id: 2)
# ip netns exec test1 ip addr
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
25: eth0@if26: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

在没有特权模式下(--privileged)下不能直接在容器内部操作网络,所以这种方法是比较好的一个方法了,同时 linux 还有一个命令,能更简便的操作 docker 网络 nsenter。

# nsenter --target 14404 --mount --uts --ipc --net --pid

pipework 原理解析

除了 docker 本身的网络模型外,我们可以自定义 docker 网络,比如将 docker 容器网络配置到本地主机网络中,这样就可以将容器当作应用的节点使用,实现容器与容器,容器与主机的通信了。

# 启动一个名为 test1 的 docker 容器
# docker run -itd --name test1 --net=none ubuntu /bin/bash
# 创建一个供容器连接的网桥 br0
# brctl addbr br0
# ip link set br0 up
# 将主机 ens33 桥接到 br0 上,并把 ens33 的 ip 配置在 br0 上。
# ip addr add 192.168.10.100/24 dev br0; \
     ip addr del 192.168.10.10/24 dev ens33; \
     brctl addif br0 ens33; \
     ip route del default; \
     ip route add default via 192.168.10.254 dev br0

# pid=$(docker inspect --format '{{ .State.Pid }}' test1)
# mkdir -p /var/run/netns
# ln -s /proc/$pid/ns/net /var/run/netns/$pid

# ip link add veth-a type veth peer name veth-b
# brctl addif br0 veth-a
# ip link set veth-a up
# ip link set veth-b netns $pid
# ip netns exec $pid ip link set dev veth-b name ens33
# ip netns exec $pid ip link set ens33 up
# ip netns exec $pid ip addr add 192.168.10.101/24 dev ens33
# ip netns exec $pid ip route add default via 192.168.10.254

使用 pipework

以上的命令封装成 shell 脚本的话就是工具 pipework 了。

配置Linux网桥连接容器并配置容器IP地址

先下载 pipework

# git clone https://github.com/jpetazzo/pipework
# 如果没有 git 命令,可以使用 docker 来实现
# docker run -it --rm -v ./pipework:/gitdata alpine/git clone https://github.com/jpetazzo/pipework /gitdata/
# cp ~/pipework/pipework /usr/local/sbin/

使用命令完成之前的操作

pipework br0 test1 192.168.10.102/24@192.168.10.2

这行命令执行的操作如下:

  • 查看主机使用存在br0的网桥,不存在就创建;
  • 向test1中加入一块名为eth1的网卡,并配置IP地址为192.168.10.102/24;
  • 若test1中已经有默认路由,则删掉,把192.168.10.102设为默认路由的网关;
  • 将test1容器连接到之前创建的网桥br0上。

使用macvlan设备将容器连接到本地网络

macvlan设备是从网卡上虚拟出的一块新网卡,它和主网卡分别有不同的MAC地址,可以配置独立的IP地址。使用pipework配置macvlan命令如下:

# pipework ens33 test1 192.168.10.104/24@192.168.10.2

操作过程如下:

  • 从主机的ens33上创建一块macvlan设备,将macvlan设备放入到test1中并命名为eth1;
  • 为test1中新添加的网卡设备配置IP地址为192.168.10.104/24;
  • 若test1中已经有默认路由,则删掉,把192.168.10.2设为默认路由的网关

也可以使用ip命令创建macvlan设备:

# 用命令在ens33上创建macvlan设备
# ip link add link ens33 dev ens33m mtu 1500 type macvlan mode bridge
# 将创建的macvlan设备放入Docker容器中,并重命名为eth1
# ip link set ens33m netns $NSPID
# ip netns exec $NSPID ip link set ens33m name eth1

从ens33上创建出的macvlan设备放在test1后,test1容器就可以和本地网络中的其他主机通信了。但是,如果在test1所在的主机上却不能访问test1,因为进出macvlan设备的流量被主网卡ens33隔离了,主机不能通过ens33访问macvlan。要解决这个问题,需要在ens33在再创建一个macvlan设备,将ens33的IP地址转移到这个macvlan设备上

# ip addr del 192.168.10.10/24 dev ens33; \
> ip link add link ens33 dev ens33m type macvlan mode bridge; \
> ip link set ens33m up; \
> ip addr add 192.168.10.10/24 dev ens33m; \
> route add default gw 192.168.10.2

使用DHCP获取容器IP

命令如下

pipework ens33 test1 dhcp

DHCP服务除了要求主机环境存在DHCP服务器外,Docker主机上还必须安装有DHCP客户端(udhcp,dhclient,dhcpcd)。

使用Open vSwitch

Open vSwitch 是一个开源的虚拟交换机,相比于 Linux Bridge,Open vSwitch 支持 VLAN,Qos 等功能,同时还提供对 OpenFlow 协议的支持,可以很好的于SDN(软件定义网络)体系融合。因此,利用 Open vSwitch 能很好的扩展 docker 网络。当 pipework 目前也只支持 Open vSwitch 的简单功能。

# pipework ovsbr0 test1 192.168.20.100/24

设置网卡MAC地址

pipework 除了支持给网卡配置IP外,还可以指定网卡的MAC地址。具体用法是再IP参数后面再加一个MAC地址的参数

# pipework br0 test1 dhcp fa:de:b0:99:52:1c
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!