5.我在B站学云原生之Docker容器网络介绍与实践
全栈工程师修炼指南
2022年02月09日 17:54
收录于文集
共39篇

本章目录:

0x04 Docker 网络管理

  • 1.容器网络原理

  • 2.容器网络实践

  • 3.高级网络配置


0x04 Docker 网络管理

描述: 大量互联网服务包括多个服务组件往往需要多个容器之间进行网络通信相互配合,Docker目前提供了映射容器端口与宿主主机和容器互联机制来为容器提网络服务;并且采用Linux系统知道的网络系统来实现对网络服务的支持使之能提供稳定支持以及快速的高性能转发;

1.容器网络原理

描述: 当 Docker 启动时会自动在主机上创建一个 docker0 虚拟网桥,实际上它是 Linux 的一个 bridge (网桥),可以理解为一个软件交换机,它会在挂载到它的网口之间进行转发。 同时,Docker 随机分配一个本地未占用的私有网段(在 RFC1918 中定义)中的一个地址给 docker0 接口

  • 比如典型的 172.17.42.1,掩码为 255.255.0.0

Docker 就创建了在主机和所有容器之间一个虚拟共享网络 当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包), 这对接口

  • 一端在容器内即 eth0;

  • 另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)

通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。

例如, 当在一台未经过特殊网络配置的centos 或 ubuntu机器上安装完docker之后, 在宿主机上通过ifconfig命令可以看到多了一块名为docker0的网卡;不难想到docker0就不只是一个简单的网卡设备了而是一个网桥。

代码块
Shell
自动换行
复制代码
$ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.18.0.1  netmask 255.255.0.0  broadcast 172.18.255.255
$route -n
172.18.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0

#下图即为Docker默认网络模式(bridge模式)下的网络环境拓扑图,创建了docker0网桥,并以eth pair连接各容器的网络,容器中的数据通过docker0网桥转发到eth0网卡上。
#网桥(等同于交换机)

#在Linux中,可以使用brctl命令查看和管理网桥(需要安装bridge-utils软件包),比如查看本机上的Linux网桥以及其上的端口:
$yum install bridge-utils
$brctl show
bridge name     bridge id               STP enabled     interfaces
br-63791a62ad5a   8000.024258a66d6e       no        veth4ce6e0e
                                                    vethe5abf0f
br-6cde78afe495   8000.02420a2496c6       no      veth93e8dc0
                                                  vethfda7b14
docker0   8000.024216e63d3c       no

#docker0网桥是在Docker daemon启动时自动创建的,其IP默认为172.18.0.1/16,之后创建的Docker容器都会在docker0子网的范围内选取一个未占用的IP使用,并连接到docker0网桥上。

#除了使用docker0网桥外,还可以使用自己创建的网桥,比如创建一个名为br0的网桥,配置IP:
brctl  addbr br0
ifconfig  br0 18.18.0.1

#在Docker容器和外界通信的过程中,还涉及了数据包在多个网卡间的转发(如从docker0网卡转发到宿主机ens160网卡)
#需要内核将ip-forward功能打开即将ip_forward系统参数设1
echo 1 > /proc/sys/net/ipv4/ip_forward
复制成功

网络驱动模型

在Docker在1.9版本中network子命令和跨主机网络支持,为了标准化网络的驱动开发步骤和支持多种网络驱动,Docker公司在libnetwork中使用了CNM(Container Network Model)定义了构建容器虚拟化网络的模型。 libnetwork和Docker daemon及各个网络驱动的关系图: Docker daemon通过调用libnetwork对外提供的API完成网络的创建和管理等功能,libnetwrok中则使用了CNM来完成网络功能的提供;

CNM中主要有沙盒(sandbox)、端点(endpoint)、网络(network)3种组件:

  • (1)Sandbox:包含了一个容器网络栈的信息,实现对容器的接口、路由和DNS设置等进行管理;

  • (2)Endpoint:它可以加入一个沙盒和一个网络,且一个端点只可以属于一个网络并且只属于一个沙盒;

  • (3)Network:可以直接互相联通的端点(包含多个端点),网络的实现可以是Linux bridge、VLAN等

libnetwork共有5种内置驱动:bridge驱动、host驱动、overlay驱动、remote驱动、null驱动。

  • (1)bridge驱动: Docker默认设置驱动,它可以将创建出来的Docker容器连接到Docker网桥常规方法能满足容器基础需求,然后在复杂场景下使用又诸多限制(使用NAT转发)

  • (2)host驱动:它将不为Docker容器创建网络协议栈(不会创建独立的network namespace),即容器于宿主机共用一个network namespace并使用宿主机的网卡/IP端口等等信息,该驱动适用于对于容器集群规模不大的场景;

  • (3)overlay驱动: 采用IETE标准的VXLAN方式,并且是VXLAN中被普遍认为最适合大规模的云计算虚拟化环境的SDN controller模式,使用时候需要额外的配置存储服务(例如Consul、etcd和zookeeper)

  • (4)remote驱动: 该驱动并未做真正的网络服务实现而是调用了用户自行实现的网络驱动插件,使libnetwork实现了驱动的可插件化,更好地满足了用户的多种需求

  • (5)null驱动: 容器拥有自己的网络命名空间,但并不为Docker容器进行任何网络配置;容器除了network namespace自带的loopback网卡名,没有其他任何网卡、IP、路由等信息(需要用户对齐配置)

libnetwork官方示例:

2.容器网络实践

(1)端口映射实现访问容器 通过-P或者-p来指定端口,

  • 使用-P时候:会进行选择 49000 ~ 49900 端口随机分配映射;

  • 使用-p时候:会让你您设置固定与容器映射的端口;

支持的格式:

  • hostPort:containerPort < 映射到本地指定端口以及容器端口

  • ip:hostPort:containerPort < 映射到本地指定地址以及本地指定端口和容器端口

  • ip::containerPort < 映射本地指定IP的任意端口和容器端口 (注意此处是:😃

代码块
Shell
自动换行
复制代码
#随机开启端口
$sudo docker run -d -P centos python app.py 

#查看转发的端口
$docker ps -l   

#查看详细的日志信息
$sudo docker logs -f [NAMES] 

#1.映射所有接口地址 [将本地的5000端口映射到容器的5000端口]
$sudo docker run -d -p 5000:5000 centos python app.py 
#2.映射指定地址指定端口
$sudo docker run -d -p 127.0.0.1:5000:5000 centos python app.py 
#3.映射指定地址到的任意端口 还能使用tcp标记来指定udp端口
$sudo docker run -d -p 127.0.0.1::5000/tcp centos python app.py 

#查看端口映射配置
$sudo docker port [NAMES] 5000 #容器Port
复制成功

(2)实现容器间通信 容器的链接(Linking)系统是除了端口映射外的另一种可以与容器中应用进行交换的方式;它会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息;

Docker两种方式为容器公开连接信息:

  • 1.环境变量

  • 2.更新/etc/hosts

代码块
Shell
自动换行
复制代码
#首先创建一个新的数据库容器,启动db容器的时候并没有使用-p与-P标记,避免了暴露数据库端口到外部网络上
$sudo docker run -d --name db tranining/postgres

#使db容器与web容器建立互联关系;
#--link name:alias  其中namd是链接的容器的名称,alias是这个链接的别名.
$ sudo docker run -d -P --name web --link db:db training/webapp python app.py

#使用 docker ps 来查看容器的连接
$ docker ps 
CONTAINER ID IMAGE COMMAND CREATED STAT
US PORTS NAMES
349169744e49 training/postgres:latest su postgres -c '/usr About a minute ago Up About a minute 5432/tcp db, web/db'
#web/db这表示web 容器链接到 db 容器,web 容器将被允许访问 db 容器的信息。


#使用env命令来查看web容器的环境变量
$sudo docker run --rm --name web1 --link db:db training/webapp env
#起点汇总DB_开头的环境变量是提供web容器连接到db使用,前缀采用大写的链接别名
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


#同时Docker还添加host信息到父容器的/etc/hosts文件
$sudo docker run -it --rm --link db:db training/webapp /bin/bash
cat /etc/hosts
复制成功

实例演示:

代码块
Shell
自动换行
复制代码
$docker pull nginx
$docker run -d -p 80:80 --name web nginx
$docker container ls -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
16207d8f2291        nginx               "nginx -g 'daemon of…"   19 seconds ago      Up 18 seconds       0.0.0.0:80->80/tcp   web
#容器有自己的内部网络和IP地址,使用docker inspect + 容器ID 可以获取所有的变量值;
$docker inspect web
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.2",
"MacAddress": "02:42:ac:12:00:02",

#然后打开80端口进行访问采用logs打印访问
$docker logs -f web
218.x.x.xx - - [08/May/2019:15:00:11 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0" "-"

#进行link关联容器使之能进行正常通信
[root@izwz9biz2m4sd3bb3k38pgz ~]$docker run -d --name web1 --link web:nginx_web alpine cat /etc/hosts
ebe1df8c1fb00462b127d36201d558a9f62507c81faea1ce6c4bf4b5ea6075e3

[root@izwz9biz2m4sd3bb3k38pgz ~]$docker run -d --name web2 --link web:nginx_web alpine env
baa9dfe5f64519eb5ccbd122fc191e0f40118a4ee28385a818f7ffe6e2e03639

[root@izwz9biz2m4sd3bb3k38pgz ~]$docker start -i web1
172.18.0.2      nginx_web 16207d8f2291 web  #成功添加web
172.18.0.3      ebe1df8c1fb0

[root@izwz9biz2m4sd3bb3k38pgz ~]$docker start -i web2
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=baa9dfe5f645
NGINX_WEB_PORT=tcp://172.18.0.2:80  #web2也成功添加web
NGINX_WEB_PORT_80_TCP=tcp://172.18.0.2:80
NGINX_WEB_PORT_80_TCP_ADDR=172.18.0.2
NGINX_WEB_PORT_80_TCP_PORT=80
NGINX_WEB_PORT_80_TCP_PROTO=tcp
NGINX_WEB_NAME=/web2/nginx_web
NGINX_WEB_ENV_NGINX_VERSION=1.15.12-1~stretch
NGINX_WEB_ENV_NJS_VERSION=1.15.12.0.3.1-1~stretch
复制成功

(3)libnetwork库官方示例 执行流程:

代码块
Shell
自动换行
复制代码
#docker内置的默认默认网卡是是无法使用docker network rm进行删除;
$docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
f26c3ed5f7b0        bridge              bridge              local
fd3572aceb38        host                host                local
01da6464c812        none                null                local

#Step 1.创建名为backend、frontend两个网络
$docker network create backend
6cde78afe495a310bb27c5e1a50074b20e204bfa72e71bcaf6a4c37feb300b93
$docker network create frontend
63791a62ad5a5fe4aeb5926616b2ea1d65b490bb9fb718824b7cb1c408ae50c1
$docker network create -d bridge test-net
#-d:参数指定 Docker 网络类型,有 bridge、overlay/none。

#Step 2. 将c1与c2的容器加入到backend网络中,将c3容器加入到frontend网络中
$docker run -itd --name c1 --net backend alpine
729f2abef71ceaf831999d66264d05f78674d9cd2c235f84481a14b366698adb
$docker run -itd --name c2 --net backend alpine
26d47af2d39a1b00f767c60a68cd5f61f1cf5f48652cdcbcb0216968a3185f5e
$docker run -itd --name c3 --net frontend alpine
9cb94f7c66955ba5a95c90d08ce314da0e477f6eddbcea0329309ec36ca5a711


#Step 3. 分别进入c1和c3容器使用ping命令测试其与c2的连通性,因为c1和c2都在backend网络中,所以两者可以连通。但是因为c3和c2不在一个网络中,所以两个容器之间不能连通:
# 使用docker inspect c3 查看IP信息:
C1: 172.19.0.2
C2: 172.19.0.3
C3: "IPAddress": "172.20.0.2",

# 进入c1容器ping c2通、ping c3不通。其它两个容器就不进入演示了,大家自己可以试一下:
# docker exec -it c1 sh
# ip addr
18: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.2/16 brd 172.19.255.255 scope global eth0
# ping c2
PING c2 (172.19.0.3): 56 data bytes
64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.065 ms
64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.050 ms
/ # ping 172.20.0.2
PING 172.20.0.2 (172.20.0.2): 56 data bytes


# Step 4. 将c2连接加入到front网络中,使用exec进入c2中查看网卡信息,测试c2与c3的连通性后,可以发现两者已经连通
$docker network connect frontend c2
$docker exec -it c2 sh
/ $ ip addr
20: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:13:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.3/16 brd 172.19.255.255 scope global eth0
24: eth1@if25: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:14:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.20.0.3/16 brd 172.20.255.255 scope global eth1

/ $ ping c3
PING c3 (172.20.0.2): 56 data bytes
64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.100 ms
复制成功

(4) 跨主机实现互通 描述: 此外我们还可以通过通过添加路由route的方式进行跨主机实现互通,但是通常情况下我们不会如此操作,只在特殊环境中使用测试。

比如:Docker1: 172.18.0.1/24 <--> Gateways <--> 192.168.1.99

总结说明:

  • 用户可以链接多个子容器到父容器中比如连接多个web到db容器上;

  • 学习额外的机制比如SDN(软件定义网络)或者NFV(网络功能虚拟化)的相关技术

3.高级网络配置

描述: 容器互联 随着 Docker 网络的完善,强烈建议大家将容器加入自定义的 Docker 网络来连接多个容器,而不是使用 --link 参数

如何自定义配置容器的主机名和 DNS 呢? 答:Docker 会默认用主机上的 /etc/resolv.conf 来配置容器, 所以可以直接在宿主机上的resolv.conf文件中进行更改,也可以在/etc/docker/daemon.json 文件中增加DNS键值对是一个数组;

代码块
Shell
自动换行
复制代码
#新建网络
docker network create -d bridge my-net
#分别启动两个容器(然后进入互ping查看)
$docker run -it --rm --name busybox1 --network my-net busybox sh
$docker run -it --rm --name busybox1 --network my-net busybox sh


#秘诀就是 Docker 利用虚拟文件来挂载容器的 3 个相关配置文件,在容器中使用 mount 命令可以看到挂载信息
#可以让宿主主机 DNS 信息发生更新后,所有 Docker 容器的 DNS 配置通过 /etc/resolv.conf 文件立刻得到更新。
/dev/mapper/cl-root on /etc/resolv.conf type xfs (rw,relatime,attr2,inode64,noquota)
/dev/mapper/cl-root on /etc/hostname type xfs (rw,relatime,attr2,inode64,noquota)
/dev/mapper/cl-root on /etc/hosts type xfs (rw,relatime,attr2,inode64,noquota)
#Docker 1.2.0 开始支持在运行中的容器里编辑 /etc/hosts, /etc/hostname 和 /etc/resolv.conf 文件。
#但是这些修改是临时的,只在运行的容器中保留,容器终止或重启后并不会被保存下来,也不会被 docker commit 提交。

#宿主机更改后立即生效(容器重新进入的时候)
$docker start -i 81fa
sh-4.2# cat /etc/resolv.conf
# Generated by NetworkManager
nameserver 192.168.10.254
nameserver 61.128.128.68
nameserver 114.114.114.114
复制成功

容器访问控制 容器的访问控制,主要通过 Linux 上的 iptables 防火墙来进行管理和实现. 容器访问外部网络,需要本地系统的转发支持。在Linux 系统中,检查转发是否打开。

代码块
Shell
自动换行
复制代码
$sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

#如果在启动 Docker 服务的时候设定 --ip-forward=true, Docker 就会自动设定系统的 ip_forward 参数为 1。
$sysctl -w net.ipv4.ip_forward=1
复制成功

容器之间访问需要两方面的支持:

  • 容器的网络拓扑是否已经互联。默认情况下,所有容器都会被连接到 docker0 网桥上。

  • 本地系统的防火墙软件 -- iptables 是否允许通过。 动容器(docker run)时使用 --link=CONTAINER_NAME:ALIAS 选项,就会在宿主机上添加了 iptables 规则。

代码块
Shell
自动换行
复制代码
$ sudo iptables -nL
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
ACCEPT     tcp  --  172.17.0.2           172.17.0.3           tcp spt:80
ACCEPT     tcp  --  172.17.0.3           172.17.0.2           tcp dpt:80
DROP       all  --  0.0.0.0/0            0.0.0.0/0
复制成功

默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。

  • 容器访问外部实现:容器所有到外部网络的连接,源地址都会被 NAT 成本地系统的 IP 地址

  • 外部访问容器实现:可以在 docker run 时候通过 -p 或 -P 参数来启用。

不管用那种办法,其实也是在本地的 iptable 的 nat 表中添加相应的规则

代码块
Shell
自动换行
复制代码
$sudo iptables -t nat -nL
#其中,上述规则将所有源地址在 172.17.0.0/16 网段,目标地址为其他网段(外部网络)的流量动态伪装为从系统网卡发出。
#MASQUERADE 跟传统 SNAT 的好处是它能动态从网卡获取地址。
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16       !172.17.0.0/16


#使用端口映射的时候
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
复制成功

docker 网桥 Docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。 Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信,它还给出了 MTU(接口允许接收的最大传输单元),通常是 1500 Bytes,或宿主主机网络路由上支持的默认值,值都可以在服务启动的时候进行配置。

代码块
Shell
自动换行
复制代码
#由于目前 Docker 网桥是 Linux 网桥,用户可以使用 brctl show 来查看网桥和端口连接信息。
$brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242f0960e9f       no              veth4fe5b74

#每次创建一个新容器的时候,Docker 从可用的地址段中选择一个空闲的 IP 地址分配给容器的 eth0 端口。使用本地主机上 docker0 接口的 IP 作为所有容器的默认网关。
$ sudo docker run -i -t --rm base /bin/bash
$ip addr show eth0
$ip route
default via 172.17.42.1 dev eth0
172.17.0.0/16 dev eth0  proto kernel  scope link  src 172.17.0.3

#自定义网桥:
#用户也可以指定网桥来连接各个容器,在启动 Docker 服务的时候,使用 -b BRIDGE或--bridge=BRIDGE 来指定使用的网桥。
1. 如果服务已经运行,那需要先停止服务,并删除旧的网桥。
$ sudo systemctl stop docker
$ sudo ip link set dev docker0 down #将网卡处于非工作状态
$ sudo brctl delbr docker0   #删除默认网桥
$ sudo brctl addbr bridge0   #然后创建一个网桥 bridge0。
$ sudo ip addr add 192.168.5.1/24 dev bridge0
$ sudo ip link set dev bridge0 up  #查看确认网桥创建并启动。
复制成功

注:brctl 命令在 Debian、Ubuntu /Centsos采用相同的包中可以使用 sudo apt-get install bridge-utils 来安装。

实践案例: 创建一个点到点连接,点到点链路不需要子网和子网掩码。 默认情况下,Docker 会将所有容器连接到由 docker0 提供的虚拟子网中,用户有时候需要两个容器之间可以直连通信,而不用通过主机网桥进行桥接。 解决办法很简单:创建一对 peer 接口,分别放到两个容器中,配置成点到点链路类型即可

代码块
Shell
自动换行
复制代码
$docker run -it --rm --net=none --name demo1 alpine sh
39ca23ce4417

$docker run -it --rm --net=none --name demo2 alpine sh
d4b1311349c1

#找到进程号,然后创建网络命名空间的跟踪文件。
$docker inspect -f '{{.State.Pid}}' d4b
6742
$docker inspect -f '{{.State.Pid}}' 39c
6807

sudo mkdir -p /var/run/netns
sudo ln -s /proc/6742/ns/net /var/run/netns/6742
sudo ln -s /proc/6807/ns/net /var/run/netns/6807

#创建一对A B peer 接口,然后配置路由
$ sudo ip link add A type veth peer name B

$ sudo ip link set A netns 6742
$ sudo ip netns exec 6742 ip addr add 10.1.1.1/32 dev A
$ sudo ip netns exec 6742 ip link set A up
$ sudo ip netns exec 6742 ip route add 10.1.1.2/32 dev A

$ sudo ip link set B netns 6807
$ sudo ip netns exec 6807 ip addr add 10.1.1.2/32 dev B
$ sudo ip netns exec 6807 ip link set B up
$ sudo ip netns exec 6807 ip route add 10.1.1.1/32 dev B

#现在这 2 个容器就可以相互 ping 通,并成功建立连接。点到点链路不需要子网和子网掩码。
容器$ ip addr
复制成功

至此本节完毕,敬请期待下一小节内容。

Docker容器技术入门实践系列历史已发布文章(点击即可进入):

1.我在B站学云原生之Docker容器技术基础知识介绍​

2.我在B站学云原生之Docker容器环境安装实践​

3.我在B站学云原生之Docker容器三大核心概念介绍​

4.我在B站学云原生之Docker容器数据持久化介绍与实践​


欢迎各位志同道合的朋友一起学习交流,如文章有误请在下方留下您宝贵的经验知识,个人邮箱地址【master#weiyigeek.top】或者 个人公众号【WeiyiGeek】联系我。


更多文章来源于【WeiyiGeek Blog - 为了能到远方,脚下的每一步都不能少】 个人博客。

博客地址: https://weiyigeek.top 

衡量人生的社会价值的标准是个体对社会和他人所作的贡献。

专栏书写不易,如果您觉得这个专栏还不错的,请给这篇专栏【点个赞、投个币、收个藏、关个注,转个发】,这将对我的肯定,谢谢!。