提到 registry v2 ,主要改进是支持并行 pull 镜像,镜像层 id 变成唯一的,解决同一个 tag 可能对应多个镜像的问题等等。如果还不太了解,可以且听我细细道来。
他是基于内容进行寻址( Content-addressable)算法算出来的一串 hash 值。简单的说就是内容不同,得出了的 digest 值是不同的,但是内容相同的话,得出的 digest 值是一定相同的。我们的每个镜像层 id 就是根据每个镜像层的内容得出来的 digest 的。
所以你在改动镜像层以后生成的 digest 就不同了,而不动的话,这个 digest 还是不变的,那么这个 digest id 是什么时候生成的呢?我们在本地构建镜像时生成的镜像层 id 每次都是不一样的,这个 digest 是我们在 push 镜像时生成的。
为了验证内容相同, push 到 registry 得到的 digest 相同,我做了个小实验,用如下 Dockerfile 注释掉第三行和不注释构建了两次镜像,再 push 到 registry
如果是 v1 的话, push 上去得到的层 id 肯定是不一样的,但是 v2 里面,注释第三行得到了 5 个镜像层,不注释掉第三行得到了 6 个镜像层,并且第一次的 5 个层都包含在第二次 6 个里面。所以得出结论这个 digest 确实是根据内容生成的。
镜像 id 也是生成了一个 digest 值,镜像 id 是根据_manifest 这个文件,也就是镜像层 id 和镜像名字等一些其他信息生成的 digest 。我们在每次向 v2 push 镜像时候,最后都会返回给 docker client 一个 digest 值,这个值就代表了镜像的 digest id 。这个 id 的作用就是可以指定唯一的镜像了。类似 tag 使用。
因为我们知道 v1 时候用 tag 有个弊病就是多次构建的镜像可以使用同一个 tag ,导致我们用 tag 标识镜像的时候可能并不是我们想要的,而用了 digest 就不会出现这种问题。
我们在写 Dockerfile 的时候引用镜像就可以这么用:
FROM localhost:5000/test@sha256:ac81211548c0d228e10daaf76f6e0024e5f91879c8a7e105e777d6f964270449
像使用 tag 一样,用本地 docker 查看镜像 digests 时候可以使用: docker images --digests ,
当然,目前来说你看到的都是<none>,我们需要从 registry 上 pull 下来,使用
docker pull localhost:5000/test@sha256:ac81211548c0d228e10daaf76f6e0024e5f91879c8a7e105e777d6f964270449
这部分最好可以对着 registry 的文件夹结构来看。简单的画了个草图。
是 v2 的文件夹层级关系
这个是目录下具体的样子,可以先看一会儿,可以看到 registry 下面有两个文件夹 blobs 和 repositories ,
blobs 下面存储了 registry 的所有基本信息元素,包括镜像层 digest 和镜像 digest ,之后在通过某种方式将调用这里的信息。
blobs 文件夹下面先分了一个等级,是 digest 的前两位字符组成的文件夹为了筛选 digest ,避免了这个文件夹太大,查看时都难。这样方便定位。每个 blob 里都有一个 data 文件,存储相关信息。
repositories 下面存储的是各个镜像名字命名的文件夹,进入一个镜像文件夹后,可以看到三个子文件夹,_layers, _manifests, _uploads ,_layers 这个文件夹是跟这个镜像有关的所有镜像层,进入其中一个镜像层文件夹,下面只有一个文件--link 命名的文件,里面的内容就是一个 digest 。
这就跟 blobs 下面的镜像层建立起了联系,在 repositories 这个文件夹下,都是通过 link 文件与 blobs 文件夹下的文件建立的联系。
_upload 这个文件夹,平时点进去是空的,这个文件夹主要作用是上传用的。我们上传镜像层的时候,先上传到这个文件夹下,等完成传输以后,在将这个文件夹下的内容移动到 blobs 下面,然后将原来的文件删除。
_manifest 这个文件夹非常重要,是镜像的相关信息。他下面有两个子文件夹, revisions 和 tags , revisions 这个文件夹下是这个镜像的所有可用的镜像 digest ,里面的 link 文件指向了镜像的 digest 。我们去 blobs 里面找相应的 id 对应的文件,查看文件下面的 data ,我们发现这个 data 文件里面存储的信息,和我们通过 registry v2 rest api 请求 manifest 信息相同~在看_manifest/tags/。下面就是这个镜像的不同 tag 了。又分了 current 和 index 分别表示当前 tag 对应的 digest 和此 tag 下的所有镜像 digest 。
先看一下官方给的图
可以看到, v2 和 v1 相比,访问形式完全变了,去掉了 index server ,加上了一个 auth server 。
访问顺序是这样的
我们通过 docker client 让 docker deamon 先访问 registry , registry 如果不需要身份验证,则直接返回结果,若需要验证,返回 401 并在 header 附带一些信息,
daemon 根据信息访问 auth server 。 auth server 判断通过了验证,并返回给 daemon 一串 token 。
daemon 带着这串 token 再去访问 registry 则可以获得到信息, pull , push , api 都走的这套流程。
接下来贴下我的配置文件 config.yml ,配置了选项 auth/token 表示要 token 验证。这个流程确实需要好好读一下,跟她的加密方式有很大关系
version: 0.1
log:
fields:
service: registry
storage:
cache:
blobdescriptor: redis
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
secret: randomstringsecret
tls:
certificate: /root/sslkeys/domain.crt
key: /root/sslkeys/domain.key
auth:
token:
realm: https://registry.tenxcloud.com:5001/auth
service: test123.tenxcloud.com:5000
issuer: qwertyui
rootcertbundle: /root/sslkeys/domain.crt
当然,我是通过容器启动的 v2 ,把这个 config volume 进去的
我的启动命令: docker run -d -p 5000:5000 --restart=always --name registry -v pwd
/sslkeys:/root/sslkeys -v pwd
/config.yml:/etc/docker/registry/config.yml -v pwd
/data:/var/lib/registry registry:2.1.1
auth/token 下的 4 个子选项都是必须配置的, realm 表示我的 auth server 地址, service 表示的 registry 的地址, issuer 是一串标示符,随便写一下, auth server 加密的时候也需要配置同样的字符串。 rootcertbundle 配置一个秘钥,对 token 进行加密。(我这里复用了我的 tls token )
当我们第一次未带 token 访问 registry 时会返回 401 并附带如下信息: Www-Authenticate: Bearer realm="test123.tenxcloud.com:5000 ",service="test123.tenxcloud.com:5000 "scope="repository:husseingalal/hello:push", realm 和 service 就是前面说的, scope 表示的是我要做的操作, repository 代表镜像,接着是镜像名字,然后是 pull 或者是 push 或者二者都有
知道了各个参数的功能,就可以搭建我们的 auth server 了,我从 github 下找到了一个项目: https://github.com/cesanta/docker_auth
用 go 语言写的,其中的账号密码存储方式有:写入文件, ldap 和 google 账号的。这个 server 实现了动态加载配置文件,配置文件有变化这个 server 会进行安全的重启,所以可以对文件动态添加账号密码。当然也可以自己写身份验证,添加数据库等方式的,只需要继承 Authenticator 这个 interface 就可以。添加起来很容易。身份验证后有权限控制 acl ,并且我们也可以自定义权限控制,继承 Authorizer 这个 interface 即可。
前两项通过后就是生成 token (这一部分这个项目已经封装好了,不用改动),
v2 的 token 采用的 JWT 加密方式, JWT 分了三个部分, header , payload,signature , header 里面带的信息是 token 加密方式,一般都是 base64 , payload 里带的都是需要的基本信息, user,权限,过期时间,还有前面说的 issure 等等,
signature 是 header+payload 后用秘钥进行加密,这个秘钥就是配置在 registry 里的 rootcertbundle 对应的秘钥。
三部分通过 . 连接, 因为有秘钥加密,保证了 token 信息不会被窜改,这种加密方式保证了将权限验证和 registry 分离也 是安全的, 将 token 发送回 daemon 后 daemon 就可以带着 token 去正常访问 registry 了,如果是 rest api ,直接将其写成 Bearer token 就可以请求了。