failed to fetch
今天处理了一个case,某个公司自己搭的镜像仓库,使用我们的PaaS平台。然后出现镜像无法下载的问题。
我自己测试了一下,果然报错了401:
crictlpull--creds用户名:密码镜像名称:镜像tag\nFATA[0000]pullingimage:rpcerror:code=Unknowndesc=failedtopullandunpackimage"xxx":failedtoresolvereference"xxx":\nunexpectedstatuscode[manifestsxxx]:401Unauthorized
401错误,第一个感觉应该是用户名和密码不正确,我让客户自己通过docker登录测试一下,结果,成功了!这不是见鬼了嘛!
查看一下containerd日志发现报错。
failedtoauthorize:failedtofetchoauthtoken:authorizationserverdidnotincludeatokenintheresponse
错误很明显,就是从oauth认证的返回里面没有token。自己模仿oauth通过curl验证一下发现服务端返回很奇怪
{"token":"eyJ0eXAixxx"}
这个并非是oauth认证的返回,而且普通的Basic认证。而containerd的代码里面是这样写的。
//当密码不为空的时候\nifto.Secret!=""{\n\t\n//优先走oauth认证\n\t\tresp,err:=auth.FetchTokenWithOAuth(ctx,ah.client,ah.header,"containerd-client",to)\n\t\tiferr!=nil{\n\t\t\tvarerrStatusremoteerrors.ErrUnexpectedStatus\n\t\t\tiferrors.As(err,&errStatus){\n\t\t\t//如果哦oauth返回405、404、401的时候改用basic认证\n\t\t\t\tif(errStatus.StatusCode==405&&to.Username!="")||errStatus.StatusCode==404||errStatus.StatusCode==401{\n\t\t\t\t\t//走普通basic认证\nresp,err:=auth.FetchToken(ctx,ah.client,ah.header,to)\n\t\t\t\t\n\t\treturnresp.AccessToken,nil\n\t}
可以看到containerd优先走oauth认证的,只有在oauth认证失败的时候,再走basic认证。那么这就和上面的auth服务返回对不上了。当containerd开始通过oauth认证的时候,authserver却返回了一个basic认证的token。每当遇到这种不匹配的时候,我们应该去看一下官网,标准的镜像认证流程。
标准的镜像认证总共分为6步:
1、客户端尝试请求下载镜像
2、返回401,以及返回authsevice的地址
3、携带用户名、密码、scope(仓库和动作(pull、push)),请求authservice获取token
4、authservice返回token
5、使用token再次请求registry下载镜像
6、镜像下载成功
那么出现文件就在第三步,请求镜像仓库的时候,镜像仓库本身不支持oauth认证,此时,按照dockerregistry的规范,此时应该返回401。如下:
在docker1.10之前只支持basic认证,之后版本既支持basic也支持oauth,但如果认证失败需要返回401。可见,containerd本身的实现是没有问题的,出问题的是客户的镜像仓库的authservice。
经过客户沟通后,修改authsevice,当接收到POST发送oauth请求的立刻返回401,然后containerd会自动退避到basic认证,这样整个流程就可以顺利进行了。我们在实现社区服务的时候,一定要遵守规范,所谓的规范就是一种编程接口,不同服务之间必须遵守相同的规范才能正常交互。