Docker Hub
Docker Hub 在国内用不了已经有一段时间了,由于我们在实验室自建了透明代理,所以大部分时候对我们而言其实没啥影响。对于不在内网环境下或偶尔帮助他人调试的时候,也还有国内镜像站凑合,但好景不长,今年 6 月开始国内 Docker Hub 镜像站相继停止服务,这下不得不想点办法了。
自建 Proxy
考虑到我在海外还有一台闲置的小鸡,每个月可用流量也不少,正好可以利用起来。 最简单的方法就是参照这篇文章利用 Nginx 做反向代理,从而实现加速的效果。事实上最开始我们也是这么操作的,令人惊喜的是,速度还不错,接近 9MB/s。
可惜好景又不长,很快实验室的同学和我反馈镜像拉取异常,复现后发现面向auth.docker.io
的请求受到了阻断,而在此之前,这个阻断是不存在的。对于 Docker Hub 来说,在拉取镜像前需要先进行鉴权(即使是公开镜像),既然无法鉴权,自然也就无法顺利下载镜像。
这个auth.docker.io
的域名是哪来的,可不可以替换?结合其他博主的抓包结果,还真有办法。但首先,我想先复现一遍抓包结果。
Linux 抓包的准备
怎么抓?Docker 跑在 Linux 上,那么最简单的办法就是通过给 Docker 配置代理,指向 Windows 下的抓包软件 Fiddler 监听的端口即可。
安装 Fiddler
Fiddler 有两个版本,经典 Classic 和新版 Everywhere,这里我们用 Everywhere。 这是一个付费软件,那怎么能顺利用上就需要自己想办法了,这里留一个传送门。
Fiddler 使用
安装完成后,打开设置面板,这里有两步操作:
安装并导出 CA 证书备用,用于解密 HTTPS 流量
允许接收局域网内的流量
需要注意的是,导出证书需要选择 PEM 格式(即纯文本),这是在 Linux 下的通用格式,这点我曾经在小例会上提过。
同样的 X.509 证书可能存在不同的编码格式,但他们可以相互转换:
- PEM - Privacy Enhanced Mail
- 文本类型 Base64 编码 BEGIN 开头 END 结尾
- UNIX 常见
- DER - Distinguished Encoding Rules
- 二进制类型
- Windows 常见
编码格式是局限的,但扩展名是丰富多彩的:
- PEM 后缀:通常用于公钥
- CRT 后缀:通常用于公钥
- KEY 后缀:通常用于私钥
- PIZZA 后缀:你高兴就好
在 UNIX 上通常使用 PEM 编码,即文本格式。但是具体证书的后缀用什么,其实开心就好,无所谓的。
在 Linux 安装 CA 证书
在安装 Fiddler 的 Windows 电脑上安装证书只能解密来自本机的流量,还需要在安装 Docker 的 Linux 上也安装证书。
我这里是 Ubuntu,具体的安装过程如下:
- 将证书复制到
/usr/local/share/ca-certificates
目录下 - 执行
update-ca-certificates
搞定!记得重启一下 Docker,否则 Pull 的时候依旧会报证书错误。
如果提示证书加载失败,记得看看自己导入的证书文件能否使用文本编辑器打开,别不小心导成了 DER 格式的证书文件了,这是 Fiddler 的默认导出。
给 Docker 配置代理
Docker 的 Proxy 有两种,一种是给 Daemon(守护进程)配置,一种是给 CLI 配置。
显然,我们这里需要给守护进程配置代理。
按照官方文档的提示,我们创建/etc/docker/daemon.json
。
如果 Docker 使用的是 rootless 模式,这个路径的位置会有所不同,具体参见:Daemon | Docker Docs,但正常情况下,你应该和我一样。
需要注意的是,如果进行过配置镜像源等操作,这个文件你应该创建过,把代理相关配置加进去的时候,注意符合 JSON 格式。
除了使用daemon.json
,还有其他载入代理信息的方法,但daemon.json
是最被推荐的,如果你需要使用其他方法,记得去看文档。
接着我们填入代理相关信息:
{
"proxies": {
"http-proxy": "http://proxy.example.com:3128",
"https-proxy": "https://proxy.example.com:3129",
"no-proxy": "*.test.example.com,.example.org,127.0.0.0/8"
}
}
需要注意的是,这里的http-proxy
和https-proxy
都需要使用http://192.168.181.1:8866
(根据自己的进行修改)即 HTTP 协议。
如何验证配置被正确加载了?输入docker info
,如果找到自己配置的 Proxy 相关内容,就说明配置成功了。
顺便一提,如果你需要使用 SSH 隧道之类的操作来给 Docker 提供一个通畅的网络环境,也需要通过这种方式来进行。
开始抓包
随意 Pull 一个镜像,如docker pull mysql
,如果一切顺利,你就能在 Fiddler 的界面看到相关请求了。
果然遇事不决先抓包,一抓,万事万物都清晰了。
首先,Docker 会访问https://registry-1.docker.io/v2/
并得到一个 401 状态码,在www-authenticate
头中包含了无法访问的 URL:https://auth.docker.io/token
,在正常情况下,Docker 会接着访问https://auth.docker.io/token
并得到相关 Token,最终拿着 Token 去访问镜像仓库https://registry-1.docker.io/v2/
。只是这还没结束,请求最终会被 307 重定向到https://production.cloudflare.docker.com
,这才是最终产生流量的地方。
那让我们再看看如果使用最开始反向代理的方式会发生什么呢?
可以看到,一切几乎照旧,唯一不同的是镜像的获取不是通过 307 将客户端重定向到 CloudFlare 的服务,而是直接通过代理下载,这也是之前通过之前博主对 Nginx 的配置实现的。
那么现在需要解决的问题就很简单了,在原来反向代理的基础上加上对auth
部分的代理就好了,同时替换第一次 401 中无法访问的 URL 为代理后的地址即可。
对策
server {
listen 443 ssl;
server_name docker.cqut.cc;
#access_log /var/log/nginx/host.access.log main;
ssl_certificate /ssl/cqut.cc.crt;
ssl_certificate_key /ssl/cqut.cc.key;
location / {
proxy_pass https://registry-1.docker.io;
proxy_set_header Host registry-1.docker.io;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forward-Proto $scheme;
# 替换认证Header
more_set_headers 'www-authenticate: Bearer realm="https://docker.cqut.cc/auth",service="registry.docker.io"';
# 关闭缓存
proxy_buffering off;
proxy_set_header Authorization $http_authorization;
proxy_pass_header Authorization;
proxy_intercept_errors on;
recursive_error_pages on;
error_page 301 302 307 = @handle_redirect;
}
location /auth {
proxy_pass https://auth.docker.io/token;
proxy_set_header Host auth.docker.io;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forward-Proto $scheme;
}
location @handle_redirect {
resolver 1.1.1.1;
set $saved_redirect_location '$upstream_http_location';
proxy_pass $saved_redirect_location;
}
}
需要注意的是,more_set_headers
指令用到了headers-more-nginx-module
模块,这部分默认是没有被编译进 Nginx 的,也没有被放到 Nginx 官方源中。需要自行编译或看看自己操作系统的发行版是否提供这个包,包名为:libnginx-mod-http-headers-more-filter
或被包含在nginx-extra
中,如果 Nginx 版本太新导致无法被依赖或者找不到这个包,就需要自行编译了。
自行编译可以使用 Nginx 最新的dynamic module
,动态模块技术,相对方便,增减模块无需重新编译整个 Nginx。只需要下载 Nginx 对应版本源码和headers-more-nginx-module
源码,将编译好的 so 文件放到 Nginx 的 moudles 文件夹中,并在 Nginx 配置中增加这一部分就可以了,具体可以参考官方仓库。
搞定!再抓个包看看效果。
舒服了。
使用
- 官方镜像:
docker pull docker.cqut.cc/library/mysql
- 普通镜像:
docker pull docker.cqut.cc/xhofe/alist
参考
- 最初的实现:Nginx proxy manager 反向代理 docker hub - monkey6 - 博客园
- 解决无法认证:使用 Caddy 反向代理 dockerhub 需要几步? - 竹林里有冰的博客
- 其他方式:无障碍访问 Docker Hub 的各种方法(自建 registry、Cloudflare 加速、Nginx 反代、代理 Docker 网络) | 绅士喵
- https://askubuntu.com/questions/73287/how-do-i-install-a-root-certificate
- 国内的 Docker Hub 镜像加速器,由国内教育机构与各大云服务商提供的镜像加速服务 | Dockerized 实践 https://github.com/y0ngb1n/dockerized