前言
在 Docker 常用指令详解 一文中介绍了 Docker 的常用指令, 在构建容器的时候使用了 --net anyesu_net
这个选项, 意思是让容器使用自定义的网络 anyesu_net , 本文就 Docker 下的网络模式做一个简单介绍。
四种网络模式的选择
- bridge
这是 Docker 默认使用的模式, Docker Daemon 启动时默认会创建 Docker0 这个网桥, 网段为 172.17.0.0/16 , 宿主机 IP 为 172.17.0.1 , 作为这个虚拟子网的 网关 。
当然, 也可以新建一个名为 anyesu_net 网段为 172.18.0.0/16 的网桥:
docker network create --subnet=172.18.0.0/16 anyesu_net
启动 容器 时指定 --net anyesu_net
即可。
- host
容器 共享 宿主机 的网络 ( IP 和 端口 ) 。使用 Docker 有相当一部分目的是为了隔离 宿主机 和 容器 , 使用 host 模式就违背了这一点, 不是很好。另外有很多 镜像 如 tomcat 默认监听 8080 端口的, 使用 host 模式后开多个 容器 就会造成端口冲突, 而不得不修改 tomcat 的监听端口。
- none
这种模式下, 创建的 容器 拥有自己的 Network Namespace, 但是没有任何网络配置, 所以默认是没有网络的, 可以自己对 容器 的 网卡、IP 进行配置, 适合用来配置比默认设置更加复杂的网络环境。
- container
类似于 host 模式, 不过这种模式是共享已存在的 容器 使用的网络。
给容器分配固定 IP
默认应该是按 容器 创建或启动顺序依次分配的, 所以 容器 重启后 IP 就可能会变化, 这对于一些需要定向访问 容器 的功能来说就比较麻烦了。一种解决办法是使用 link 来链接 容器, 原理就是动态配置 hosts , 不过这种方式启动顺序有依赖关系, 因此本人不习惯使用。还有一种方法, 是启动 容器 的时候使用自定义网络, 如 anyesu_net , 并指定 --ip
选项来固定 IP 。
当然, 网上还有很多教程借助 pipework 、nsenter 等工具实现固定 IP 的功能, 甚至可以分配到 宿主机 所在物理网段上的 IP 。不过, 步骤都比较复杂, 有兴趣的小伙伴可以参考下面的几篇文章自己尝试哦。
- Docker为容器分配指定物理网段的静态IP
- Docker 配置固定IP及桥接的实现方法
- 分配局域网固定ip
- 为Docker容器指定自定义网段的固定IP/静态IP地址
- Docker 使用物理网络IP地址 及四种网络模式
关于容器中 hosts 文件的修改
启动 容器 的时候指定 --link
或 --add-host
选项修改 hosts 文件内容, 但都是追加内容而无法覆盖已有内容, 比如我要重设 localhost 使其指向 宿主机 的 IP 172.17.0.1
而不是默认的 127.0.0.1
( 可能也就我吃饱了撑着要这么做吧 ) 就不能用这种方法了。
了解到 容器 的 hosts 文件是 容器 启动时先在 宿主机 上动态创建后再挂载到 容器 上的 ( 源文件位于 宿主机 的 /var/lib/docker/containers/[容器id] 目录下 ) , 因此, 容器 重启之后还会重新创建, 即之前所做的修改都没了。于是想到在容器的 启动命令 中动态修改, 但是 hosts 文件是不允许直接修改的, 于是采用下面的办法:
# 拷贝hosts内容
cp /etc/hosts /etc/hosts.tmp
# 替换字符串
sed -i 's$127.0.0.1$172.17.0.1$' /etc/hosts.tmp
# 覆盖hosts
cat /etc/hosts.tmp > /etc/hosts
# CMD参数中 使用 sh -c "... && ... && ..." 的方式来运行多个指令
个人觉得这也不是一种好办法, 就换了一种更简单粗暴的方法: -v /etc/hosts:/etc/hosts
, 使用 宿主机 文件映射 容器 的 hosts 文件 ( 或者新建一个 hosts 文件专门给 容器 用而不影响 宿主机 的 hosts ) 达到覆盖的目的, 再配合 容器 固定 IP 配置, 多个 容器 共用一套 hosts 文件, 将所有 容器 的主机名和 IP 配置进去, 容器 之间也可以通过主机名或子网 IP 直接访问。
相关文章:
Docker 下使用 dubbo
公司项目中使用了 dubbo 来搭建分布式系统, 本地开发环境统一使用 localhost 来连接 ZooKeeper , 绑定不同端口实现 伪分布式 。后来就在测试环境上使用 Jenkins + Svn + Maven + Docker 的方式完成 一键构建 , 将每个服务部署在单独的容器之中, 使用默认的 bridge 网络模式。
遇到的问题
1. 访问宿主机上的 ZooKeeper
简单的方法是在项目配置文件中使用环境变量或主机名 ZkServer 作为注册中心地址, 相应的对 容器 进行环境变量和 hosts 的配置即可。不过,作为一个懒人, 项目中已经写好了使用 localhost 就不想改成 ZkServer 了, 方法也简单, 就是使用上面的方法替换 localhost 的 IP 为 172.17.0.1 , 即 宿主机的 IP。
2. 服务提供者注册 IP 问题
容器 内的服务默认注册到 zookeeper 上的地址是一个 IP ( 如 172.17.0.2
) , 是一个内网地址, 对于 宿主机 和 运行在其上的所有容器 之外的其他机器来说是不可访问的。如果要使服务对其他机器可用的话就要另辟蹊径了, 主要方法有:
- 容器使用 host 网络模式或设置为宿主机物理网段上的 IP
- 对宿主机和服务消费者之间的网络设置路由规则, 使消费者可访问容器内网 IP
- 修改 dubbo 源码来指定服务注册 IP
上面的方法实践起来有点复杂, 也不是很可靠, 还得另寻方法。
查了下 dubbo 的源码, 在 com.alibaba.dubbo.config.ServiceConfig 类的 doExportUrlsFor1Protocol 方法中, 如果配置中没有指定 host 属性或者使用了回环地址则调用 InetAddress.getLocalHost().getHostAddress() 来获取本机可用 IP。InetAddress.getLocalHost 这个方法在 Windows 下应该是取所有网卡中第一个可用的 IP, 在 Linux 下应该是取主机名对应的 IP ( 先去 /etc/hosts 中找, 找不到再去 dns 服务器找, 需要注意这有可能会得到一个错误的 IP) , 因此, 在 Linux 下针对上面的问题只需修改 hosts 文件, 指定主机名对应的 IP 为 宿主机 的局域网 IP 。另外测试了下, 设置的 host 在 Linux 下只用于提供给服务注册中心而不用于端口绑定, 也就是说可以设置任意 IP ; 而在 Windows 下设置了非本机 IP 会无法绑定端口。
hosts 文件改完还有一个问题:tomcat 默认 shutdown 命令绑定的端口是 localhost:8005, 由于 localhost 已经不再是容器的 IP , 因此端口绑定会失败。简单处理就是修改 tomcat/conf/server.xml 文件中 Server 节点的属性, 添加属性 address="127.0.0.1"
, 如下所示:
<Server address="127.0.0.1" port="8005" shutdown="SHUTDOWN">
总结下最终的方案步骤:
- 创建自定义网络 ( 172.18.0.0/16 网段 )
- 容器 指定 hostname 、自定义网络、固定 IP
-
宿主机 ( 局域网 IP :
192.168.1.100
)新建文件 /etc/hosts2 挂载到容器的 /etc/hosts 上 (-v /etc/hosts2:/etc/hosts
) , 内容如下, 所有容器共用此 hosts 文件
172.18.0.1 localhost
192.168.1.100 docker_host1
192.168.1.100 docker_host2
...
-
宿主机 到 容器 的端口映射
-p 20880:20880
最终结构图如下所示:
2017-12-25 追加
目前 dubbo 自身已提供环境变量的方式注册 IP 和端口,详见 官方示例
- 当前 2.5.9 版本有 bug , 监听端口与注册端口不一致会导致找不到对应服务
2.5.x 分支中配置优先级:
系统环境变量 > java 命令参数 ( -Dxxx=yyy
) > 配置文件 host 属性 > /etc/hosts 中 hostname-ip 映射关系 > 默认连通注册中心地址的网卡地址 > 第一个可用的网卡地址