起因

最近部门搭建的harbor镜像仓库因为更改ip地址,导致使用 docker pull 命令时,发生错误,如下:

# docker pull 10.1.27.89:9000/vappserver/test-image:1.0
Error response from daemon: 
Head "http://10.1.27.89:9000/v2/vappserver/test-image/manifests/1.0": 
Get "http://10.1.27.240:9000/service/token?account=admin&scope=repository%3Avappserver%2Ftest-image%3Apull&service=harbor-registry": 
dial tcp 10.1.27.240:9000: connect: no route to host

仔细看错误提示,我们访问的是“10.1.27.89:9000”,缘何却访问了“http://10.1.27.240:9000/service/token”?而这里的 “10.1.27.240”刚好就是harbor的旧ip地址。

我们接下来希望借助于网络抓包,来:

  • 了解docker pull命令背后的执行过程
  • docker客户端和harbor仓库之间的通讯协议究竟是什么(http协议吗?)
  • 为什么docker pull命令访问了旧的harbor仓库ip?

工具准备

  • 已知harbor部署在 10.1.27.89:9000端口
  • 我们在 10.1.27.90 主机上执行docker pull命令,并安装rpcapd工具来抓包;
  • 在windows环境使用wireshark连接10.1.27.90上的rpcapd端口监听网络通讯;

rpcapd和wireshark的准备参考另文:https://segmentfault.com/a/1190000044448055

抓取数据包

在wireshark上打开对 rpcap://10.1.27.90:2002/ens33网络接口的监听,设置过滤条件:ip.addr==10.1.27.89,在10.1.27.90主机上执行:docker pull 10.1.27.89:9000/vappserver/test-image:1.0

我们看到如起因中描述的命令错误提示

再简单看一下抓取的数据

通讯过程 1-尝试HTTPS连接

我们看到从 数据包序列 51-60 是一个完整的TCP请求 从10.1.27.90:43192端口->10.1.27.89:9000,从TCP3次握手,到客户端发起Client Hello的HTTPS连接请求,到服务端返回一个400 BadRequest (Http格式的错误响应),以及服务端主动断开此连接。

这里面,docker客户端第一次尝试以 HTTPS方式连接 harbor仓库。虽然我们在 /etc/docker/daemon.json中已经将 harbor仓库地址配置为insecure-registries,但docker客户端仍然会首先尝试使用 HTTPS方式进行连接。

{
    "registry-mirrors": [
        "https://hub-mirror.c.163.com",
        "https://docker.mirrors.ustc.edu.cn"
    ],
    "insecure-registries": [
        "10.1.27.89:9000"
    ]
}

通讯过程 2-登录拦截

从数据包序列 61-70 是另一个完整的HTTP请求过程。我们右键选择“追踪流 - Http Stream”,查看http请求的数据。


我们看到请求地址是:http://10.1.27.89:9000/v2/ 并没有携带请求参数。
服务端给出了 401 Unauthorized的应答。
服务端给出的 认证方法Www-Authenticate: Bearer realm="http://10.1.27.240:9000/service/token",service="harbor-registry"
这里的关键是 应答给出的地址是 harbor仓库的旧ip地址(10.1.27.240:9000)
然后docker再去连接 旧ip地址就会发现无法连接了!

docker pull完整的通讯过程

修复好harbor仓库的ip地址后,我们再发起一次docker pull操作,看一下整个请求流程

除了第1个HTTPS连接尝试、第2个/v2/请求需要身份认证之外,后续还有5个HTTP请求。

通讯过程 2-登录拦截(新)

在harbor仓库的 hostname属性,我们配置为“myserver”,仍然发起:docker pull 10.1.27.89:9000/vappserver/test-image:1.0的命令。
我们看到发起的请求地址为:http://10.1.27.89:9000/v2/,这个地址和 harbor配置中的 hostname无关。
harbor主机的应答为:Bearer realm="http://myserver:9000/service/token",service="harbor-registry"
这里的登录验证地址就是我们配置的 hostname 的值。

harbor为何不使用相对地址,由发起客户端根据当前请求地址来决定登录校验服务地址呢?
这里我想到的唯一作用就是 将harbor镜像服务 和 认证服务可以剥离,分离部署到不同的域名中。(好像只有这个用处了-_- )

通讯过程 3-登录认证

注意:发起认证的地址就是 前面返回的URLhttp://myserver:9000/service/token
通过Authorization: Basic YWRtaW46Sxxx请求头传递登录帐号/密码
服务端则返回认证结果 token串。

另外,注意在追踪流的对话框,点击右下角的 上下箭头就可以直接查看下一个请求数据了。

通讯过程 4-请求镜像元数据


这里我们注意几点:

  • 请求地址重新回到了 镜像地址(10.1.27.89,这里harbor配置的hostname已经不再使用了)
  • 身份认证信息通过携带Authorization: Bearer eyJhbGciOiJSUzI传递
  • harbor通过Docker-Content-Digest: sha256:b14c1b2982c44fda73返回镜像的元数据
  • 不知是否有用:Etag: "sha256:b14c1b2982c44f"、X-Request-Id: 08a74bd7

通讯过程 5-请求layers元数据

不确定这里是否查询的是 镜像的所属图层的元数据?

通讯过程 6-请求图层数据

不确定这里是否查询的是 镜像的所属图层的元数据或者是数据?
但从请求地址看和前一个请求有差别:

  • 上一个请求:GET /v2/vappserver/test-image/manifests/sha256:b14c1b2982
  • 当前请求:GET /v2/vappserver/test-image/blobs/sha256:85770059ae

通讯过程 7-请求图层具体数据

当前请求返回的似乎是 图层的具体数据。

另外,我们重新看第6 和第7请求数据包的顺序,会发现这两个数据包是穿插的,也就是说,这是一个并发请求。

回顾小结

结合对harbor仓库配置文件的分析 (harbor.yml)、以及./prepare命令、以及prepare命令生成的各配置文件(harbor/common/config)的对比、以及harbor前端nginx代理的配置,我们发现

  • harbor的登录认证服务可以独立配置,所以登录地址是由登录拦截器返回,并且由客户端根据返回的地址进行登录。所以,在harbor中需要配置登录服务地址。
  • 登录服务地址有两种配置方法。
  • 其一:配置参数“external_url: http://xxx:yyy”,并且该项配置优先级最高
  • 其二:配置参数“hostname”、以及“port”,如果没有配置external_url,则该项生效。最终访问地址为:“http://hostname:port
  • 在特殊场景下,只能配置external_url。例如:harbor的内网端口为 9000, 但是域名映射后访问地址的端口为 80,那么这时候,我们只能通过external_url来配置。否则因为port属性同时作为 harbor的前端ngxin代理的服务端口,又被拿来当作登录认证服务的端口,这样就无法配置了。
  • 即使我们将 hostname配置为域名,那也不意味着使用docker pull命令时,我们必须通过域名方式访问harbor服务。同样的内网ip、或者公网ip、域名都可以访问。hostname的唯一作用就是配置认证服务地址。

sswhsz
168 声望4 粉丝