Docker提供了非常方便的根据镜像创建容器的方法,默认从镜像创建的容器都采用连接到 docker0 网桥的NAT模式,也就是如果需要访问虚拟容器提供的服务,需要在物理服务器上使用端口镜像方式,将物理服务器端口映射到虚拟容器的对应端口。

这样的模式主要有以下限制:

  • 每个虚拟容器的服务需要通过端口映射才能被外部访问 - 对于某些需要直接访问服务器端口(如ssh登录),需要给不同虚拟机映射不同端口(物理服务器端口不能重合),管理有些麻烦
  • 同一个物理服务器运行相同服务的多个容器,需要创建一个提供负载均衡的虚拟容器,这样才能实现 “物理端口 => 负载均衡端口 => 负载均衡到各个虚拟容器端口”
  • 虚拟容器的IP地址不固定,要解决容器IP地址的动态解析 (dnsmasq服务部署文档中介绍了dnsmasq可以实现的动态IP分配和DNS解析)

采用NAT模式的容器,可以在一个物理服务器上实现一个单元化部署 - 实现 “firewall + loadbalance + web + cache + db” - 如果做好数据库同步和缓存一致性,应该能够实现分布式应用部署(将每个物理服务器视为一个工作单元,内部自给自足,外部模块化水平扩容)

参考 Docker Network Configuration 可以使用脚本命令方式来实现给虚拟容器分配静态IP地址和路由

按照前述 CentOS 7安装docker 方式安装和配置好物理服务器 docker0 虚拟交换网络,并制作了 自定义Docker镜像 ,就可以设置静态IP地址的虚拟容器。以下案例创建一个名字为 plone 的虚拟容器,展示如何通过脚本命令创建虚拟容器的网络。

使用 --net=none 参数创建虚拟容器:

docker run -h plone --name="plone" -t -i --net=none docker-repo.huatai.me/centos:centos7_base_apps /bin/bash

检查运行的容器id

docker ps

可以看到输出

eb02f6afb946        docker-repo.huatai.me/centos:centos7_base_apps   "/bin/bash"         5 minutes ago       Up 5 minutes                            plone

获得了该虚拟容器的 Container ID 是 eb02f6afb946 ,执行以下命令

mkdir -p /var/run/netns
ln -s /proc/$pid/ns/net /var/run/netns/$pid

ip link add A type veth peer name B
brctl addif docker0 A
ip link set A up
ip link set B netns $pid
ip netns exec $pid ip link set dev B name eth0
ip netns exec $pid ip link set eth0 up
ip netns exec $pid ip addr add 192.168.1.41/24 dev eth0
ip netns exec $pid ip route add default via 192.168.1.1

以上命令完成后在容器中就可以看到 eth0 设备,并且可以通过设备A连接外网。

汇总以上命令步骤,整理脚本 docker_addnet.sh 如下:

# 使用格式:
# docker_addnet.sh 容器名字 IP
# 注意:网桥接口默认为 veth_容器名字
# 注意:网关地址设置为固定的 192.168.1.1

if [ $# != 2 ]; then
    echo -e "ERROR! 使用参数如下: docker_addnet.sh 容器名字 IP"
    exit 1
fi

container_netmask=24
container_gw=192.168.1.1

container_name=$1
bridge_if=veth_`echo ${container_name} | cut -c 1-10`
container_ip=$2/${container_netmask}

container_id=`docker ps | grep $1 | awk '{print \$1}'`


mkdir -p /var/run/netns
ln -s /proc/$pid/ns/net /var/run/netns/$pid

ip link add A type veth peer name B
ip link set A name $bridge_if
brctl addif docker0 $bridge_if
ip link set $bridge_if up
ip link set B netns $pid
ip netns exec $pid ip link set dev B name eth0
ip netns exec $pid ip link set eth0 up
ip netns exec $pid ip addr add $container_ip dev eth0
ip netns exec $pid ip route add default via $container_gw

使用方法:

./docker_addnet.sh plone 192.168.1.41

使用 ip link set X name Y 命令时,设备命名 Y 是有长度限制的,过长的设备命名会报类似错误 Error: argument "veth_docker_repo" is wrong: "name" too long (这里 veth_docker_repo 就是过长的设备命名)。经过验证,设备名字的长度最长可以是 15 个字符,所以脚本里面加了 veth_ 前缀以后,主机名建议在10个字符,否则就要在脚本中做截取。