如何在Docker容器使用代理时用别名访问其他容器

当Docker容器内设置了HTTP/HTTPS代理时,所有通过别名访问的其他容器的请求也会通过代理转发,导致无法通过Docker内的路由表正确解析目标容器的IP。今天遇到这个问题时尝试了多种方法,最后以一种不是非常优雅的方式解决了问题,但至少是能用了。

遇到问题的Docker网络环境如下:

  • Docker Network名称为0x1c-net
  • 0x1c-net上挂载了两个服务,分别为:
    • caddy-gateway:Caddy网关,将HTTP请求按URL路径反向代理到指定的内网服务
    • 0x1c.site.placeholder:一个简单的静态网站,以nginx作为WebServer在容器的80端口上发布
  • caddy-gateway在配置文件中将指定路径的请求转发到0x1c.site.placeholder:80
  • ~/.docker/config.json中设置了HTTP_PROXYHTTPS_PROXY,代理服务器连接到0x1c-net网络外的一个本机端口。

在配置好Caddyfile并启动相关容器后,访问指定URL没有得到相应。查看代理服务器的日之后发现如下报错:


accepted http://0x1c.site.placeholder/

[Warning] [383945001] v2ray.com/core/proxy/http: failed to read response from 0x1c.site.placeholder > io: read/write on closed pipe

可以看出代理拦截了发往0x1c.site.placeholder这个并不存在的域名的请求,并尝试通过代理服务器访问,从而导致请求失败。

问题的解决思路很简单:让Docker内网容器间的请求绕过代理直接访问。但问题在于如何禁用容器间的请求代理。

我首先尝试修改代理服务器的路由配置,让所有包含0x1c.site字符串的请求都用直连模式访问。但因为并不了解的原因没能成功。转念一想,如果用户不能随意修改代理配置,或者代理服务器不支持路由配置,那么这种办法就更不可行了。

于是我想到正确的方法应该是设置~/.docker/config.json中的NO_PROXY环境变量,从Docker客户端层面绕开代理访问。

由于容器IP是会随Docker网络环境变化的,因此不可用于NO_PROXY。在Docker官方文档上也没有找到任何固定的表示内网端点的IP规则或环境变量。我尝试在NO_PROXY上填写形如0x1c.site.*的规则,结果根本不生效。查阅资料后发现,NO_PROXY只能支持对子域名级别的通配符,形如.foo.com

纠结了很久,我突然想到既然Docker规则限定死了,那为什么不在我自己可以控制的范围内进行变通,让容器别名去匹配子域名的格式呢?💡

于是尝试了如下解决方法:

  1. 修改网站容器的别名为placeholder.0x1c.site,并规定以后其他提供网站内容的容器别名都必须遵循*.0x1c.site的格式。同理,所有API后台接口服务都遵循*.0x1c.api的别名格式。
  2. NO_PROXY中添加规则.0x1c.site,.0x1c.api
  3. 修改Caddyfile中的反向代理配置,将特定路径的请求指向placeholder.0x1c.site:80
  4. 重启网关和网站容器

实验成功!在通过网关访问指定的URL时,代理服务器的日志中不会再出现相关请求的记录,说明这些请求没有被发送给代理服务器。

至此,问题解决。但仍旧存在几个不完美的地方:

  • 容器别名的命名必须严格按照规则执行,对于多人协作的项目来说增加了学习成本。
  • 每增加一种“域名规则”时,都必须修改Docker客户端配置中的NO_PROXY环境变量。这项工作很可能被遗漏,排查起来也不太方便。

希望能找到更好的办法来解决这个问题。


Docker

2019-09-10 10:54 +0000