Docker 的网络管理

date
Mar 1, 2022
slug
hpukshan
status
Published
tags
Docker
summary
type
Post
 

前言

在实践中,大家常使用 docker 来部署一个或多个容器服务,存在外部访问容器端口,存在容器之间的网络互通,也存在禁止容器之间互访。
所以主机和 docker 之间大部分情况下存在端口映射,但容器的 IP 是如何分配的,如何设置 DNS 访问自定义域名。
更甚者哦公司安全部找到你说
安全部:你的服务器 xxx 上是不是部署了 aaa,有个漏洞需要修复,我们通过端口扫描发现的。 我:我通过 firewall-cmd 只开放了 22\80\443 端口,我那个端口没有开放啊。
所以我们要搞懂几个问题:
  • docker 是怎么访问互联网的?
  • 互联网怎么访问 docker 的?
  • docker 之间是怎么互相访问的?
  • docker 之间又是怎么隔离的?

网络类型

我们知道 docker 容器是独立互不影响的,它的其网络也是,其底层主要就是依赖 Linux 的网络命名空间来实现网络的隔离。每个容器都有着独属自己的网络命名空间,每个网络命名空间都提供了一个完全独立的网络协议栈,包括网络设备接口、IPV4 和 IPV6 协议栈、IP 路由表、防火墙规则、端口、sockets 等,这样便使得每个容器的网络是相互隔离互不影响的。而网络命名空间的通信也就是容器间的通信主要是借助虚拟网络设备(特别是 veth)实现的。
docker 容器有四种网络模式,用过 vmware workstation 虚拟机的同学应该很熟悉这个:
  • bridge: 网桥,如上,为默认网络模式
  • host:容器和宿主机共享网络,即不给容器分配独立的网络命名空间,和宿主机处于同一个网络栈里,使用的也是宿主机的网络。
  • none:给容器分配独立的网络命名空间,但是不进行网络配置,需要自行手动配置(如何配置:可阅读 ip netns )
  • container:和另一个容器一起共享它的网络命名空间。
# 查看已有网络
[root@gitlab ~]# docker network ls
NETWORK ID     NAME                  DRIVER    SCOPE
2413b995d637   bridge                bridge    local
8a381c1db11c   docker-spa_default    bridge    local
50d928024d5d   docker-yapi_default   bridge    local
bd957bc79faf   docker_cfg_default    bridge    local
e993792c3a67   host                  host      local
a8e4cad26df8   none                  null      local
查看网络详细信息:docker network inspect 网络NMAE_or_ID
notion image
docker network inspect bridge 效果
在使用 docker run 时可增加选项,--dns=114.114.114.114可指定 DNS,--network=xxxx可指定网络选项有:
  • –network=bridge,默认模式可省略
  • –network=host
  • –network=none
  • –network=container:容器 NAME_or_ID

bridge

当 Docker 进程启动时,默认情况(bridge 模式)下会在主机上创建一个名为 docker0 的虚拟网桥,此主机上启动的 Docker 容器会连接到这个虚拟网桥上,创建一个容器就会多出来一个以 veth 开头的网卡,所以有默认地址 172.17.0.0/16 的地址。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中,最低层是宿主机上的 eth0 网卡。
notion image
docker 网络拓扑图
容器里能访问互联网就是从 veth 网卡到 docker0 转换,最后经过 NAT 转换到我们的宿主机网卡上的,同样同属一个 docker0 网桥下的容器能直接互相访问。
使用ifconfig查看所有网卡信息,下面只查看 docker0 的信息
[root@gitlab ~]# ifconfig docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:9ff:fe04:d5b5  prefixlen 64  scopeid 0x20<link>
        ether 02:42:09:04:d5:b5  txqueuelen 0  (Ethernet)
        RX packets 2178871  bytes 3509792555 (3.2 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2909305  bytes 1065495039 (1016.1 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
可以简单的理解,bridge 模式就是从 docker0 子网中分配一个 IP 给容器使用,并设置 docker0 的 IP 地址为容器的默认网关。在主机上创建一对虚拟网卡 veth pair 设备,Docker 将 veth pair 设备的一端放在新创建的容器中,并命名为 eth0(容器的网卡),另一端放在主机中,以 vethxxx 这样类似的名字命名,并将这个网络设备加入到 docker0 网桥中。可以通过 brctl show 命令查看:
[root@gitlab ~]# brctl show
bridge name	        bridge id		    STP enabled	interfaces
br-50d928024d5d		8000.024268b73f02	no		    veth3e58549
											                        veth67a038f
br-8a381c1db11c		8000.02420511324c	no		    veth5d38c4b
br-bd957bc79faf		8000.0242242c5b68	no		    vethf87df3b
docker0		        8000.02420904d5b5	no		    veth42f8218
												                      vethc8f2db5
											                        vethf9a83ac
virbr0		        8000.525400d6be0e	yes		    virbr0-nic
在 bridge 模式下,连在同一网桥上的容器可以相互通信。也也可以禁止它们之间通信,方法是在 DOCKER_OPTS 变量中设置–icc=false,这样只有使用 –link 才能使两个容器通信。
互联网访问容器需要暴露容器的端口,创建 docker 容器时使用 -p 选项表示随机或指定一个端口映射到内部容器开放的网络端口,并且在一个指定端口上只可以绑定一个容器,支持的格式有:
ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort
省略 ip 表示绑定所有网卡,省略 hostPort 表示随机指定宿主机端口,containerPort 后可加 tcp 或 udp 表示协议。
查看容器中的端口映射docker port 容器NAME_or_ID,其实就是docker ps中 ports 那一列的内容
[root@gitlab ~]# docker port gitlab
22/tcp -> 0.0.0.0:9022
22/tcp -> :::9022
80/tcp -> 0.0.0.0:9000
80/tcp -> :::9000
增加端口映射后可以看到主机上 iptable 表增加了一条规则,可iptables -t nat -vnL查看 但 firewall-cmd 方式是看不到的,具体原因待补充:)
针对已创建的 bridge 型容器,可在/etc/docker/daemon.json添加"iptables": false重启 docker 后生效,这是外部也不能访问容器了。

host

host 模式下的容器,在网络方面与宿主机之间没有隔离,不用做端口映射,外界通过访问主机 ip 加对应端口访问到容器中的服务。该模式下最大的好处就是网络性能最高,坏处就是牺牲一些灵活性,比如要考虑端口冲突问题。
host 的另一个用途是让容器可以直接配置 host 网路,比如某些跨 host 的网络解决方案,其本身也是以容器方式运行的,这些方案需要对网络进行配置,比如管理 iptables、nginx 等,方便应用和数据分离。

none

该模式下的容器除了 lo,没有其他任何网卡。封闭意味着隔离,一些对安全性要求高并且不需要联网的应用可以使用 none 网络。比如某个容器的唯一用途是生成随机密码,就可以放到 none 网络中避免密码被窃取。

自定义网络

在实践中使用最多的模式还是默认的 bridge,所以我们常基于此模式自定义一些网络,自定义网络主要是对docker network的使用。
Usage:  docker network COMMAND

Manage networks

Commands:
  create      创建一个网络
  connect     将容器连接到网络
  disconnect  断开容器与网络的连接
  inspect     查看网络的详细信息
  ls          列出所有网络
  prune       清理无用的网络
  rm          删除一个网络
1、创建网络
docker network create [OPTIONS] NETWORK
该命令用于创建一个新的网络,默认是 bridge 类型。
 
选项
含义
–subnet strings
网络地址段。CIDR 格式,如:192.168.1.0/24
–gateway strings
网关地址
–ip-range strings
指定分配的 ip 地址范围
–internal
禁止外部访问创建的网络
–scope string
指定网络范围
d, –driver string
网络驱动类型,有 bridge、ipvlan、macvlan、overlay
–config-from string
从某个网络复制配置数据
–config-only
创建仅可配置网络
–attachable
支持手动容器挂载
–ipv6
支持 ipv6 地址
 
例:创建名为 test 的网络,网络网段为 192.168.1.0/24,网关为 192.168.1.1(不指定也可以默认为该网段的第一个 ip),并且创建一个使用 test 的容器 xzzz
# 创建网络
➜  docker network create --subnet 192.168.1.0/24 --gateway 192.168.1.1 test
16592db9541aed35cba693384b0f2e69b2aa5f35d04baa9e46b887b1b0302d26

# 查看创建的网络
➜  docker network ls
NETWORK ID     NAME                  DRIVER    SCOPE
16592db9541a   test                  bridge    local

# 使用test创建一个容器xzzz
➜  docker run -it --network=test --ip=192.168.1.10 xzzz centos
默认网络并不支持指定容器的 ip 地址,仅用户自定义的网络可以指定。
2、连接网络
docker network connect [OPTIONS] NETWORK CONTAINER
将一个容器连接到一个网络,处于同一网络的容器是互通的,且一个容器可以同时连接到多个网络。
在相同网络中的容器可以通过 ip 相互访问,但是跨网络则不行。那如果需要不同网络中的容器能够相互访问,则需要将某一个容器加入某一个网络中,那么这个容器就可以访问都该网络中的容器了。
选项
含义
–ip string
指定 ipv4 地址(例如 172.30.100.104)
–ip6 string
指定 ipv6 地址(例如 2001:db8::33)
–alias strings
为容器添加一个别名,此别名只在所连接的网络上可见
–link list
添加链接到其它容器
–link-local-ip strings
为容器添加本地链接地址
# 创建一个容器d1
docker run -itd --name d1 centos
docker run -itd --name d2 centos

# 连接网络
docker network connect test d1
docker network connect --ip 192.168.1.5 test d2
3、断开网络
docker network disconnect [OPTIONS] NETWORK CONTAINER

其他

常用的其他网络命令

下面是一个跟 Docker 网络相关的命令列表。其中有些命令选项只有在 Docker 服务启动的时候才能配置,而且不能马上生效。
  • b BRIDGE 或 –bridge=BRIDGE 指定容器挂载的网桥
  • –bip=CIDR 定制 docker0 的掩码
  • H SOCKET… 或 –host=SOCKET… Docker 服务端接收命令的通道
  • –icc=true|false 是否支持容器之间进行通信
  • –ip-forward=true|false 请看下文容器之间的通信
  • –iptables=true|false 是否允许 Docker 添加 iptables 规则
  • –mtu=BYTES 容器网络中的 MTU
下面 2 个命令选项既可以在启动服务时指定,也可以在启动容器时指定。在 Docker 服务启动的时候指定则会成为默认值,后面执行 docker run 时可以覆盖设置的默认值。
  • –dns=IP_ADDRESS… 使用指定的 DNS 服务器
  • –dns-search=DOMAIN… 指定 DNS 搜索域
最后这些选项只有在 docker run 执行时使用,因为它是针对容器的特性内容。
  • h HOSTNAME 或 –hostname=HOSTNAME 配置容器主机名
  • –link=CONTAINER_NAME:ALIAS 添加到另一个容器的连接
  • –net=bridge|none|container:NAME_or_ID|host 配置容器的桥接模式
  • p SPEC 或 –publish=SPEC 映射容器端口到宿主主机
  • P or –publish-all=true|false 映射容器所有端口到宿主主机

最佳实践

1、创建自定义网络 myNet,在宿主机上搭建 DNS 解析 2、创建容器,禁用 iptables 并连接 myNet,指定 DNS 3、各容器之间通过自定义域名进行互联

参考资料:

© 刘德华 2020 - 2023