Docker高级网络实践

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介: Docker中libnetwork提供的4种驱动,它们各有千秋,但实际上每一种方式都有一定的局限性。

Docker中libnetwork提供的4种驱动,它们各有千秋,但实际上每一种方式都有一定的局限性。假设需要运营一个数据中心的网络,我们有许多宿主机,每台宿主机上运行了数百个甚至上千个Docker容器,使用4种网络驱动的具体情况如下。

❏ 使用host驱动可以让容器与宿主机共用同一个网络栈,这么做看似解决了网络问题,可实际上并未使用network namespace的隔离,缺乏安全性。

❏ 使用Docker默认的bridge驱动,容器没有对外IP,只能通过NAT来实现对外通信。这种方式不能解决跨主机容器间直接通信的问题,难以满足复杂场景下的需求。

❏ 使用overlay驱动,可以用于支持跨主机的网络通信,但必须要配合Swarm进行配置和使用才能实现跨主机的网络通信。

❏ 使用null驱动实际上不进行任何网络设置。

玩转Linux network namespace

ip是Linux系统下一个强大的网络配置工具,它不仅可以替代一些传统的网络管理工具,如ifconfig、route等

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

ip netns命令是用来操作network namespace的指令

  • 创建一个名为nstest的network namespace .
  • 列出系统中已存在的network namespace:

删除一个network namespace:

在network namespace中执行一条命令:

root@foobar-server:/home/vagrant# ip netns exec 23531 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    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
12: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether e6:91:07:39:fd:59 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.95/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::e491:7ff:fe39:fd59/64 scope link
       valid_lft forever preferred_lft forever

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

使用ip netns add命令创建了一个network namespace后,就拥有了一个独立的网络空间,可以根据需求来配置该网络空间,如添加网卡、配置IP、设置路由规则等.


使用ip命令创建一个network namespace时,会默认创建一个回环设备(loopback interface: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这个network namespace就有了两块网卡lo和veth-b

ip netns exec nstest ip link
lo: <LOOPBACK, UP, LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
veth-b: <BROADCAST, MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 72:01:ad:c5:67:84 brd ff:ff:ff:ff:ff:ff

为网卡分配IP并启动网卡

# 在主机上为veth-a配置IP并启动
 ip addr add 10.0.0.1/24 dev veth-a
ip link set dev veth-a up
# 为nstest中的veth-b配置IP并启动
 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后,会在各自的network namespace中生成一条路由,用ip route 查看

# 在主机中
 ip route
...
10.0.0.0/24 dev veth-a  proto kernel  scope link  src 10.0.0.1
# 在nstest network namespace中
 ip netns exec nstest ip route
10.0.0.0/24 dev veth-b  proto kernel  scope link  src 10.0.0.2

这两条路由表明的意义是目的地址为10.0.0.0/24网络的IP包分别从veth-a和veth-b发出。

测试一下连通性

从主机的veth-a网卡ping nstest network namespace的veth-b网卡

ping 10.0.0.2

从nstest network namespace的veth-b网卡ping主机的veth-a网卡

ping 10.0.0.1

3.将两个network namespace连接起来

创建相互隔离的networknamespace,然后通过网卡、网桥等虚拟设备将它们连接起来,组成想要的拓扑网络。

# 创建两个network namespace ns1, ns2
 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
# 将网卡分别放到两个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_req=1 ttl=64 time=0.054 ms
...

通过veth pair设备连接起来的两个network namespace就好像直接通过网线连接起来的两台机器。

如果有更多network namespace需要连接,那就有必要引入虚拟网桥了,就如同Docker的网络一样。

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

Docker正是使用Linux namespaces技术进行资源隔离的,网络也是如此隔离的。当用默认网络模式(bridge模式)启动一个Docker容器时,一定是在主机上新创建了一个Linux network namespace。


配置Docker容器的网络

启动一个名为con4的Docker容器

docker run -itd --name con4 ubuntu:14.04 /bin/bash

解决ip netns list 无法查看docker 创建的netns.

ip netns list命令在/var/run/netns目录下查找network namespace。由于Docker创建的network namespace并不在此目录下创建任何项。


不同network namespace环境中/proc/$PID/ns目录下都有不同的netns. 这几个网络是相互隔离起来的。

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

# 用docker inspect查看从con4容器的PID
 docker inspect ——format '{{ .State.Pid }}' con4
31203
# 若不存在/var/run/netns目录,则创建
 mkdir -p /var/run/netns
# 在/var/run/netns目录下创建软链接,指向test1容器的network namespace
 ln -s /proc/31203/ns/net /var/run/netns/con4
# 测试是否成功
 ip netns list
ns1
ns2
con4
 ip netns exec con4 ip link
1: lo: <LOOPBACK, UP, LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
52: eth0: <BROADCAST, UP, LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen
    1000
    link/ether 02:42:ac:11:00:04 brd ff:ff:ff:ff:ff:ff

pipework原理解析

Docker现有的网络模式比较简单,扩展性和灵活性都不能满足很多复杂应用场景的需求。很多时候用户都需要自定义Docker容器的网络,而非使用Docker默认创建的IP和NAT规则。


使用Docker容器来安装部署Cloud Foundry时,为了方便Cloud Foundry各节点之间以及各节点和本地主机之间的通信,一个简单的做法就是将Docker容器网络配置到本地主机网络的网段中。

1.将Docker容器配置到本地网络环境中

如果想要使Docker容器和容器主机处于同一个网络,那么容器和主机应该处在一个二层网络中。能想到的场景就是把两台机器连在同一个交换机上,或者连在不同的级联交换机上。


在虚拟场景下,虚拟网桥可以将容器连在一个二层网络中,只要将主机的网卡桥接到虚拟网桥中,就能将容器和主机的网络连接起来。构建完拓扑结构后,只需再给Docker容器分配一个本地局域网IP就大功告成了。


网络拓扑如图


本地网络为10.10.103.0/24


网关为10.10.103.254


有一台IP地址为10.10.103.91/24的主机(网卡为eth0)

6ba7fabdbb814d81b33cf81788b19efb.png

# 启动一个名为test1的Docker容器
 docker run -itd --name test1 --net=none ubuntu /bin/bash
# 创建一个供容器连接的网桥br0
 brctl addbr br0
 ip link set br0 up
# 将主机eth0桥接到br0上,并把eth0的IP配置在br0上。由于是远程操作,会导致网络断开,因此这里放在
一条命令中执行
 ip addr add 10.0.2.15/24 dev br0; \
      sudo ip addr del 10.0.2.15/24 dev eth0; \
    sudo brctl addif br0 eth0; \
    sudo ip route del default; \
    sudo ip route add default via 10.0.2.254 dev br0
# 找到test1的PID,保存到pid中
$ pid=$(sudo docker inspect --format='{{ .State.Pid }}' test1)
# 将容器的network namespace添加到/var/run/netns目录下
 mkdir -p /var/run/netns
 ln -s /proc/$pid/ns/net /var/run/netns/$pid
# 创建用于连接网桥和Docker容器的网卡设备
# 将veth-a连接到br0网桥中
 ip link add veth-a type veth peer name veth-b
 brctl addif br0 veth-a
 ip link set veth-a up
# 将veth-b放到test1的netwrok namespace中,重命名为eth0,并为其配置IP和默认路由
 ip link set veth-b netns $pid
 ip netns exec $pid ip link set dev veth-b name eth0
 ip netns exec $pid ip link set eth0 up
 ip netns exec $pid ip addr add 10.0.2.95/24 dev eth0
 ip netns exec $pid ip route add default via 10.0.2.254

现在test1容器可以很方便地实现与本地主机相互访问,并且test1容器可以通过本地网络的网关10.10.103.254访问外部网络。

2. 使用pipework配置docker 网络

配置Docker容器的网络是相当烦琐的。如果需要经常自定义Docker网络,可考虑提炼上述过程,编写成shell脚本,方便操作。


这个过程由Docker公司工程师Jerome Petazzoni在GitHub上发布的名为pipework的工具可以实现。pipework号称是容器的SDN解决方案,可以在复杂场景下将容器连接起来。它既支持普通的LXC容器,也支持Docker容器。


下面是启动一个容器不使用docker任何网络模型,通过pipework + iptables 命令去实现宿主机和容器、容器和容器、容器和外部的通信

 docker run -it --rm --net none --name test1 ubuntu:14.04 bash

用了很多ip命令来配置test1容器的IP地址和网关,用pipework工具则可以很方便地完成配置,具体示例如下:

# 下载pipework
 git clone https://github.com/jpetazzo/pipework
# 将pipework脚本放入PATH环境变量所指定的目录下,如/usr/local/bin/
cp ~/pipework/pipework /usr/local/bin/
# 完成test1的配置
 ./pipework br0 test1 192.168.1.10/24@192.168.1.254

这一行配置命令执行的操作如下:

❏ 查看主机中是否存在br0网桥,不存在就创建;

❏ 向test1容器中加入一块名为eth1的网卡,并配置IP地址为192.168.1.10/24;

❏ 若test1中已经有默认路由,则删掉,把192.168.1.254设为默认路由的网关;

❏ 将test1容器连接到之前创建的网桥br0上。

这是容器能访问外部网络吗?

答案是不能,没有相应的 NAT 地址伪装规则,而这个 NAT 地址伪装规则在默认情况下会由 Docker 自动添加。在 Docker 宿主机上添加相应的 iptables 规则。

iptables -t nat -A POSTROUTING -s 192.168.0.0/16 -j MASQUERADE

这条命令是加入 NAT 表 (-t nat) 中的POSTROUTING 链 (-A POSTROUTING),将源地址为 192.168.0.0/16 的数据包进行地址转换,转变为 NAT 转换节点的地址。这里指的是服务器的IP地址。

换句话说,当从一个 IP 地址为 192.168.0.0/16 的 Docker 容器发送到外部网络的数据包时,这条规则将会允许这些数据包通过该主机网络接口并自动更改来源地址为该主机所拥有的 IP 地址。这通常用于特定场景下的网络地址转换和隐藏,比如在 Docker 网络中使用 NAT 的情况下将容器内的私有 IP 地址变为公网 IP,以使其能够与外部网络通信。


具体地,通过 -j MASQUERADE 指定了一种地址转换方式,即源 IP 地址 NAT,也称 Source NAT 。此操作将更改数据包的源地址为该接口的公有 IP,这使得返回流量能够回到原始 IP。

3、使用网络命令配置docker 网络

假如不使用这个pipework,自己实现这个过程呢?这种逆向功能有助于深入理解docker 容器的网络


下面是启动一个容器不使用docker任何网络模型,通过命令去实现宿主机和容器、容器和容器、容器和外部的通信.


需要掌握的网络命令

  • ip
  • brctl
  • route
  • iptables

宿主机上的操作:

  • 创建网桥并设置为ip地址(网关的地址)
$ sudo brctl addbr br0
$ sudo ip addr add 192.168.1.254/24 dev br0
$ sudo ip link set dev br0 up
  • 创建虚拟的veth pair, 并将一端和br0连接

来创建一个 veth 对 veth_host、veth_container,并将 veth_host连接到网桥 br0 上

$ sudo ip link add veth_host type veth peer name veth_container # 创建veth pair
$ sudo brctl addif br0 veth_host # 将veth_host 插入br0
$ sudo ip link set veth_host up # 启动veth_host设备

ip -d link show 查看所有网络接口详细信息

brctl show 查看网桥信息

$ ip -d link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT \
group default
 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state \
UNKNOWN mode DEFAULT group default qlen 1000
 link/ether 08:00:27:98:a7:ad brd ff:ff:ff:ff:ff:ff promiscuity 0
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state \
DOWN mode DEFAULT group default
 link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff promiscuity 0
 bridge
6: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode \
DEFAULT group default
 link/ether ee:7d:7e:f7:6f:18 brd ff:ff:ff:ff:ff:ff promiscuity 0
 bridge
8: veth_host: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master br0 \
state UP mode DEFAULT group default qlen 1000
 link/ether ee:7d:7e:f7:6f:18 brd ff:ff:ff:ff:ff:ff promiscuity 1
 veth
$ brctl show
bridge name bridge id STP enabled interfaces
br0 8000.ee7d7ef76f18 no foo
docker0 8000.000000000000 not

容器里面的操作:

ip netns 默认查询network ns的地址是/var/run/netns。 因此建一个软连接指向docker 容器的ns

$ cd /var/run
$ sudo ln -s /var/run/docker/netns netns
$ sudo ip netns
c785553b22a1
$ NID=$(sudo ip netns)

宿主机可以看到容器的ns了。容器的操作都是在宿主机上切换ns进入容器的ns进行设定。

713fdbfaf2514c9b9c43dcea128ad410.png

  • 增加一个网络接口
$ sudo ip link set veth_conntainer netns $NID # 将刚才创建的veth pair中的veth_conntainer虚拟设备放入容器的ns
$ sudo ip netns exec $NID ip link set dev veth_conntainer  name eth1 #  进入ns 并将"bar"的接口重命名为 "eth1。  这是模拟docker引擎处理虚拟网络接口的过程。
$ sudo ip netns exec $NID ip link set eth1 address 12:34:56:78:9a:bc # 为eth1 添加ip地址
$ sudo ip netns exec $NID ip link set eth1 up
  • 设置路由信息
$ sudo ip netns exec $NID ip addr add 192.168.1.1/24 dev eth1 # 为eth1 设置ip地址
$ sudo ip netns exec $NID ip route add default via 192.168.1.254 #  为eth1 设置gateway

如果你想访问容器外部网络,就要添加如下的 IP NAT 地址伪装规则。

$ sudo iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -j MASQUERADE

pipework源代码

# IFACE保存传进去的第一个参数,即br0
IFNAME=$1
# 判断主机中是否存在br0,并判断其类型
if [ -d /sys/class/net/$IFNAME/bridge ]
    then
      IFTYPE=bridge
      BRTYPE=linux
...
# 若主机中不存在br0,则从名称头部“br”判断它为Linux网桥,并创建br0
case "$IFNAME" in
br*)
    IFTYPE=bridge
    BRTYPE=linux
    ;;
...
[ $IFTYPE = bridge ] && [ ! -d /sys/class/net/$IFNAME ] && {
[ $BRTYPE = linux ] && {
    (ip link add dev $IFNAME type bridge > /dev/null 2>&1) || (brctl addbr $IFNAME)
    ip link set $IFNAME up
}
 ..
# CONTAINER_IFNAME中保存了需要添加到Docker容器中网卡的名称,默认为eth1
# 可以使用-i参数自定义,如pipework br0-i eth2 ...
if [ "$2" = "-i" ]; then
    CONTAINER_IFNAME=$3
    shift 2
fi
...
CONTAINER_IFNAME=${CONTAINER_IFNAME:-eth1}
# IPADDR保存要配置的IP地址,GATEWAY保存网关信息
# IP地址一定要指定子网掩码。网关使用“@”符号接在IP地址后,也可以不指定
IPADDR=$3
...
if echo $IPADDR | grep -q @
then
    GATEWAY=$(echo $IPADDR | cut -d@ -f2)
    IPADDR=$(echo $IPADDR | cut -d@ -f1)
else
    GATEWAY=
fi
# GUESTNAME保存要配置的容器的名称,即test1
# 通过Docker容器名称test1找到容器的PID,并最终保存到NSPID中
GUESTNAME=$2
...
DOCKERPID=$(docker inspect ——format='{{ .State.Pid }}' $GUESTNAME
...
if [ $DOCKERPID ]; then
    NSPID=$DOCKERPID
# 将Docker容器的network namespace软链接到/var/run/netns目录下
ln -s /proc/$NSPID/ns/net /var/run/netns/$NSPID
# 创建veth pair设备,名称分别为LOCAL_IFNAME和GUEST_IFNAME,并将LOCAL_IFNAME放到之前创建的br0网桥上
LOCAL_IFNAME="v${CONTAINER_IFNAME}pl${NSPID}"
GUEST_IFNAME="v${CONTAINER_IFNAME}pg${NSPID}"
ip link add name $LOCAL_IFNAME mtu $MTU type veth peer name $GUEST_IFNAME mtu $MTU
brctl addif $IFNAME $LOCAL_IFNAME
ip link set $LOCAL_IFNAME up
# 将GUEST_IFNAME放入Docker容器中,并重命名为eth1
ip link set $GUEST_IFNAME netns $NSPID
ip netns exec $NSPID ip link set $GUEST_IFNAME name $CONTAINER_IFNAME
# 给Docker容器新增加的网卡配置IP地址和网关
ip netns exec $NSPID ip addr add $IPADDR dev $CONTAINER_IFNAME
[ "$GATEWAY" ] && {
ip netns exec $NSPID ip route delete default >/dev/null 2>&1 && true
}
ip netns exec $NSPID ip link set $CONTAINER_IFNAME up
[ "$GATEWAY" ] && {
ip netns exec $NSPID ip route get $GATEWAY >/dev/null 2>&1 || \
ip netns exec $NSPID ip route add $GATEWAY/32 dev $CONTAINER_IFNAME
ip netns exec $NSPID ip route replace default via $GATEWAY
}

pipework跨主机通信

  • weave

你想要为你在跨越多个数据中心的、规模从单台到数千台的主机上运行的容器创建一个网 络。该网络具备自动 IP 地址分配功能,并且集成基于 DNS 的服务发现。

  • flannel覆盖网络
  • 在多台Docker主机中使用Docker Network
  • calico

参考

[图灵程序设计丛书].Docker经典实例.pdf

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
目录
相关文章
|
1天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的卷积神经网络:从理论到实践
【10月更文挑战第35天】在人工智能的浪潮中,深度学习技术以其强大的数据处理能力成为科技界的宠儿。其中,卷积神经网络(CNN)作为深度学习的一个重要分支,在图像识别和视频分析等领域展现出了惊人的潜力。本文将深入浅出地介绍CNN的工作原理,并结合实际代码示例,带领读者从零开始构建一个简单的CNN模型,探索其在图像分类任务中的应用。通过本文,读者不仅能够理解CNN背后的数学原理,还能学会如何利用现代深度学习框架实现自己的CNN模型。
|
1天前
|
Cloud Native 持续交付 Docker
Docker容器化技术:从入门到实践
Docker容器化技术:从入门到实践
|
1天前
|
数据采集 网络协议 算法
移动端弱网优化专题(十四):携程APP移动网络优化实践(弱网识别篇)
本文从方案设计、代码开发到技术落地,详尽的分享了携程在移动端弱网识别方面的实践经验,如果你也有类似需求,这篇文章会是一个不错的实操指南。
10 1
|
7天前
|
数据采集 存储 XML
Python实现网络爬虫自动化:从基础到实践
本文将介绍如何使用Python编写网络爬虫,从最基础的请求与解析,到自动化爬取并处理复杂数据。我们将通过实例展示如何抓取网页内容、解析数据、处理图片文件等常用爬虫任务。
|
15天前
|
Docker 容器
docker swarm启动服务并连接到网络
【10月更文挑战第16天】
19 5
|
15天前
|
调度 Docker 容器
docker swarm创建覆盖网络
【10月更文挑战第16天】
13 5
|
16天前
|
负载均衡 网络协议 关系型数据库
docker swarm 使用网络启动服务
【10月更文挑战第15天】
18 4
|
1天前
|
Docker 容器
【赵渝强老师】Docker的None网络模式
Docker容器在网络方面实现了逻辑隔离,提供了四种网络模式:bridge、container、host和none。其中,none模式下容器具有独立的网络命名空间,但不包含任何网络配置,仅能通过Local Loopback网卡(localhost或127.0.0.1)进行通信。适用于不希望容器接收任何网络流量或运行无需网络连接的特殊服务。
|
1天前
|
Docker 容器
【赵渝强老师】Docker的Host网络模式
Docker容器在网络环境中是隔离的,可通过配置不同网络模式(如bridge、container、host和none)实现容器间或与宿主机的网络通信。其中,host模式使容器与宿主机共享同一网络命名空间,提高性能但牺牲了网络隔离性。
|
1天前
|
Kubernetes Docker 容器
【赵渝强老师】Docker的Container网络模式
Docker容器在网络环境中彼此隔离,但可通过配置不同网络模式实现容器间通信。其中,container模式使容器共享同一网络命名空间,通过localhost或127.0.0.1互相访问,提高传输效率。本文介绍了container模式的特点及具体示例。
下一篇
无影云桌面