你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

Maven 仓库进阶

2021-11-23 8:28:36

Maven仓库进阶

什么是仓库

仓库用来集中存储Docker镜像,支持镜像分发和更新。目前世界上最大最知名的公共仓库是Docker官方发布的Docker Hub,上面有超过15000个镜像。国内比较知名的Docker仓库社区有Docker Pool、阿里云等。
为了满足容灾需求,Docker仓库后端通常采用分布式存储。比较常用的分布式存储平台包括亚马逊S3、微软Azure和华为UDS等。

仓库的组成

人们习惯称呼一个镜像集群社区(Hub)为仓库,如上面提到的Docker Hub、Docker Pool等,事实上在这类社区里面,又包含着很多具有特定功能的仓库(repository),这些仓库由一个或多个名称相同而版本不同的镜像组成。如Docker Hub上的Ubuntu、Redis都是具有具体功能的仓库,而且它们包含着不同的镜像版本。
仓库的名字通常由两部分组成,中间以斜线分开。斜线之前是用户名,斜线之后是镜像名。如tom/ubuntu表示属于用户tom的Ubuntu镜像。这也是社区上区分公有仓库和私有仓库的方法。
简单来说,仓库包括镜像存储系统和账户管理系统。前者通过Docker仓库注册服务实现;而一个有使用价值的仓库都会有完善的账户管理系统。
使用者注册成为用户之后,可以通过login命令登录到仓库服务。

docker login -u <user name> -p <password> -e <email> <registry domain>

为安全起见,对于以上命令行输出,请在login命令行回车后输入用户名、密码和邮箱。回车之后,注册的用户将会得到成功登录的提示:
Login Succeeded

仓库镜像

仓库下面包含着一组镜像,镜像之间用标签区分。一个完善的镜像路径通常由服务器地址、仓库名称和标签组成。

registry.hub.docker.com/official/ubuntu:14.04

它代表Docker Hub上的Ubuntu官方镜像,发行版本14.04。
通过Docker命令,可以从本地对仓库里的镜像进行上传、下载和查询等操作
上传镜像
可通过push命令上传镜像

docker push localhost:5000/official/ubuntu:14.04

上面是向本地私有仓库上传镜像。如果不写服务器地址,则默认上传到官方Docker Hub。(http://hub.docker.com)。需要指出的是,对于有账户管理系统的Docker服务器如Docker Hub,上传镜像之前需要先登录。而一般用户上传镜像的目标是自己的仓库。
下载镜像
通过pull命令可下载镜像

docker pull ubuntu:14.04

上面的示例统一会在省略地址的情况下,从Docker Hub上下载镜像。这里Ubuntu前面没有带用户名,表示下载的是Docker官方镜像。如果下载对象不带标签,则会把Ubuntu仓库下的官方镜像全部下载。
从上传/下载的过程信息中可以看到,一个镜像在操作中会被逐层上传/下载。不同镜像层的上传和下载操作能并发进行。因此,后一个下载动作不需要等到上一个动作的完成。
查询镜像
下面通过search命令实现查询操作

docker search localhost:5000/ubuntu

输入查询名称后,返回的结果包含了仓库中Ubuntu共有镜像。下载和查询不需要登录远程服务器。

仓库服务

Docker仓库和仓库注册服务经常被混为一谈。事实上服务是用来搭建仓库的,通常运行在容器里面。
Docker Registry是构建仓库的核心,用于实现开源Docker镜像的分发。Docker Registry源码发布在:https://github.com/docker/distribution。Docker先后设计并发布了两个Registry开源项目,这里只介绍2.0版本Registry。

Registry功能和架构

从使用来说,Registry旨在实现镜像的创建、存储、分发和更新等功能。

  • 镜像存储:镜像数据存储在Registry后端,与本地镜像存储方式类似,它也分隔成了多个镜像层,放置在各自的目录中,保存成tar包格式。除了镜像数据,Registry还保存了清单文件(mainfest)和镜像签名文件(signature)等。
  • 镜像创建、分发和更新:本地用户和Registry之间通过Registry API传输镜像。Registry API即一系列HTTP/HTTPS请求,用来发送用户请求到Registry,并接受Registry的响应。在请求和响应中则包含了镜像数据交互。
    从设计角度看,Registry有如下特点:
  • 快速上传和下载镜像;
  • 设计方案新颖且高性能;
  • 部署方便;
  • 有详细完整的Registry API(V2)说明文档;
  • 后端支持多种分布式云存储方案(如s3、azure)和本地文件系统等,接口以插件方式存在,易于配置;
  • 清单文件作为元数据完整地记录镜像信息;
  • 以Webhook方式实现的通知系统
  • 有可配置地认证模块
  • 有健康检查模块
  • 正在努力让用于管理镜像的清单和摘要文件格式更加清晰,以及更清楚地为镜像打标签。
  • 拥有完善镜像缓存机制,镜像下载更加快捷

Registry API

API描述
Registry API遵循REST设计标准,用于Registry和Docker Engine之间的通信,实现Registry镜像分发,是Docker Registry的重要组成部分。
Registry API语句由方法、路径和实体组成。它负责接收engine的访问请求,实现对Registry后端实体的操作,写入或获取数据及状态。这里先对Registry API进行简单描述,并给出使用演示。

方法路径实体描述
GET/v2/Base检查Registry是否工作正常
GET/v2/<name>/tags/listTags根据仓库名称获取仓库下的所有镜像名称
GET/v2/<name>/mainifests/<reference>Manifest获取镜像清单内容。Reference表示镜像标签或摘要
PUT/v2/<name>/mainifests/<reference>Manifest创建清单或更新清单内容
DELETE/v2/<name>/mainifests/<reference>Manifest根据填入的摘要名删除指定清单文件
GET/v2/<name>/blobs/<digest>Blob根据摘要获取镜像块数据内容。如果用HEAD方法则获取块数据信息
POST/v2/<name>/blobs/uploads/Intiate Blob Upload开始上传块数据。成功上传之后会生成并返回这个块数据的位置。如果参数中含有摘要,则意味完成一次块数据的上传
GET/v2/<name>/blobs/uploads/<uuid>Blob Upload根据镜像的uuid获取上传状态。这样做的目的是为了在恢复续传的时候获取到当前状态
PATCH/v2/<name>/blobs/uploads/<uuid>Blob Upload上传chunk数据
PUT/v2/<name>/blobs/uploads/<uuid>Blob Upload上传最后一笔chunk数据后,完成上传
DELETE/v2/<name>/blobs/uploads/<uuid>Blob Upload取消一个上传进程,释放进程资源。如果没有调用,未完成的上传将会做超时处理

API传输的对象主要包括镜像layer的块数据和表单
表单
Manifest是JSON格式的文件,记录镜像的元数据信息,并兼容V1版本镜像信息。

描述
Name镜像仓库名称
Tag镜像标签
fsLayers镜像层列表,包括tarsum校验码
Signature用于校验Manifest内容的签名

Manifest的内容形式如下

{
	"name": <name>,
	"tag": <tag>,
	"fsLayers": [
		{
			"blobSum": <tarsum>
		},
		...
	]
	"histiry": [...],
	"signatures": [...]
}

可以看到,Manifest里面包含了一段标签信息,用关键字signatures标签,所指的是一个签名镜像的表单;用户凭标签信息校验Manifest数据。给Manifest签名有两种方式:一是用Docker官方提供的libtrust函数库生成私钥,二是用工具链生成X509证书。签名信息内容形式如下:

"signatures": [
	{
		"header": {
			"jwk": {
				"crv": "P-256",
				"kid": "OD6I:6DRK....:LSF4",
				"kty": "EC",
				"x": "3gAwX...kg4A",
				"y": "t72ge...B010":
			},
			"alg": "ES256"
		},
		"signature": "XRE.......VFg",
		"protected": "eyJ.......ifQ"
	}
]

内容摘要
Registry API采用内容寻址存储(CAS)方式,针对固定内容存储。数据单元的唯一ID与元数据一起构成所访问数据的实际有效地址,从而确保内容的可靠性。
Content Digest用于内容寻址,是依据哈希算法生成的一串校验码,是数据内容的唯一标识。为了通信的安全,digest具有不透明性。digest在Registry API的作用过程如下:
1)用户通过Registry API请求获取数据
2)Registry返回数据的同时,在响应头信息里面返回digest
3)用户收到digest,然后用它校验获取数据的完整性和一致性。
一条digest由算法名称和一段十六进制数据(把哈希校验码通过十六进制进行编码)组成,其语法标识如下:

digest:= algorithm ":" hex algorithm:= /[A-Fa-f0-9_+.-]+/ hex:= /[A-Fa-f0-9]+/

生成的digest内容形式如下

sha256:c1ff613e8ba25833d2e1940da0940c3824f03f802c449f3d1815a66b7f8c0e9d

当Registry返回的结果是manifest或者layer的二进制数据时,Registry将生成digest,digest字段包含在Docker-Content-Digest响应头信息里,会一并返回给用户。对于Manifest,digest的哈希校验对象是不带签名字段的Manifest内容;而对于layer数据,digest的哈希校验对象是真个数据内容。
digest在用户获取Registry数据的请求中十分重要,它保证了通信的安全性,以及数据传输的完整性和一致性。理论上,用户收到digest之后如果不作校验,传输过程也会继续,但这显然存在安全隐患。
Registry API的使用
在使用Registry时,首先要检查Registry工作是否正常,命令如下:

curl -X GET http://localhost:5000/v2/

上述命令可能返回如下状态:

  • 200 OK:Registry工作正常,并且用户可以访问操作。
  • 401 Unauthorized:Registry工作正常,但是用户没有访问权限,要想访问需要先做授权认证。
  • 404 Not Found:Registry并不存在。
    然后,通过如下命令查找镜像:
curl -X GET http://localhost:5000/v2/foo/bar/tags/list
{"name":"foo/bar","tags":["2.0","1.0"]}

在上面的命令中输入查询路径,将会返回JSON格式的结果,列出了仓库里面包含的所有镜像标签。
最后,通过如下命令获取Manifest。

curl -X GET http://localhost:5000/v2/foo/bar/manifests/1.0
{
	"name": "foo/bar",
	"tag": "1.0",
	"architecture": "amd64",
	"fsLayers": []
		<清单内容(...)>
}

在请求中输入指定仓库和标签,即可返回整个清单内容。

Registry API传输过程分析

用户对Registry的访问,包括镜像上传、下载、查询和删除等操作,都是通过Registry发送一系列API请求来实现的。下面重点介绍Registry API在镜像上传和下载过程中的应用。
镜像下载(pulling)
下载的顺序是先下载元数据Manifest,再下载镜像layers。下载请求的格式是GET方法购买跟随下载路径,并附上Host和Authorization头信息。

GET /v2/<name>/manifests/<reference>

如果请求返回404Not Found结果,则表明Manifest不存在,下载结束。
Manifest成功下载之后,用户通过Docker Engine对镜像签名进行校验。校验通过后,用户找到blob的tarsum digest,通过它下载layers blob。API请求如下:

GET /v2/<name>/blobs/<tarsum>

Registry提供了HTTP缓存功能(分别用Redis和inmemory方式是实现)以加快下载速度。另外下载请求的header里面Range项是偏移量值,表示分段范围。
镜像上传
和下载顺序相反,上传时是先上传镜像layers,后上传Mainfest。
1)初始化上传
用户上传镜像的时候,首先要向Registry发送一个API请求初始化上传任务。

POST /v2/<name>/blobs/uploads/

URL中间的参数<name>表示上传的layer链接到哪个镜像。
Registry成功请求之后,返回202Accepted状态响应,以及一系列响应头信息。

202 Accepted
Location: /v2/<name>/blobs/uploads/<uuid>
Range: bytes=0-<offset>
Content-Length: 0
Docker-Upload-UUID: <uuid>

其中,Location字段指定了上传的URL地址,接下来的上传过程都会指向这个地址,<uuid>经过了base64格式编码,内容对于用户是不透明的,防止uuid被修改或者伪造,在持续上传layer的过程中,用户会用到Docker-Upload-UUID字段。这是为了把本地上传状态和远端关联起来,作为判断上传状态的条件。
开始上传之前,用户会检查layer是否已经在仓库里存在。检查的动作通过发送一个API请求来实现:

HEAD /v2/<name>/blobs/<digest>

Registry存放着layer数据的校验码。如果Registry根据digest参数找到了对应的layer校验码,则认为layer已经存在于Registry。接下来返回响应信息给用户:

200 OK
Content-Length: <length of blob>
Docker-Content: <digest>

用户从"200 OK"的状态响应中得知,layer数据已经存在于Registry,不会再继续执行上传动作。
2)上传过程
Registry支持两种模式上传layer:整体上传模式和分段上传模式。

  • 整体上传模式
    layer数据作为一个整体,一次性上传到Registry仓库,从而降低了上传过程的复杂性。
PUT /v2/<name>/blobs/uploads/<uuid>?digest=<tarsum>[&digest=sha256:<hex diest>]
Content-Length: <size of layer>
Content-Type: application/octet-stream

<Layer Binary Data>

请求命令行必须包含digest参数,表示layer数据的哈希校验码;头信息包含表示数据长度和数据类型的字段。
Registry接收到请求之后,会验证上传来的layer数据,并把验证结果通过响应返回给用户。假如上传失败,Registry会发送错误的响应给用户,并向响应头信息加上Location字段,指明失败对象的路径,使用用户可以重新上传。

  • 分段上传模式
    在该模式下,用户会把一笔layer数据分割成几段,每次请求一段。请求头信息Content-Length和Content-Range字段表明了数据段的长度和偏移范围。完整的请求格式
PATCH /v2/<name>/blobs/uploads/<uuid>
Content-Length: <size of chunk>
Content-Range: <start of range>-<end of range>
Content-Type: application/octet-stream

<layer Chunk Binary Data>

如何分割layer数据没有强制要求,不过Registry必须依照上传顺序接收数据。Registry可以规定上传数据的长度范围,如果用户上传的数据长度超出了范围,就会返回如下错误信息:

416 Requested Range Not Satisfiable
Location: /v2/<name>/blobs/uploads/<uuid>
Range: 0-<last valid range>
Content-Length: 0
Docker-Upload-UUID: <uuid>

1)请求头信息中Content-Range字段格式不符合Registry要求;
2)数据段没有按顺序上传给Registry
用户收到错误之后,应该从last valid range参数指定的位置继续上传数据。Registry成功接收数据后返回的信息如下:

202 Accepted
Location: /v2/<name>/blobs/uploads/<uuid>
Range: bytes=0-<offset>
Content-Length: 0
Docker-Upload-UUID: <uuid>

用户上传最后一个数据段的时候,发送的请求会与上面有所不同

PUT /v2/<name>/blob/uploads/<uuid>?digest=<tarsum>[&digest=sha256:<hex digest>]
Content-Length: <size of chunk>
Content-Range: <start of range>-<end of range>
Content-Type: application/octet-stream

<Last Layer Chunk Binary Data>

如果用户不发送这条请求,Registry会认为layer还没有上传完。如果layer数据段已经存在于Registry,用户需要把layer的digest发送PUT请求到Registry,请求Body置空,通过这条请求就可表示完成了一次上传。
Registry从<tarsum>获取到校验码后,会校验上传的layer数据。如果成功返回则响应如下:

201 Created
Location: /v2/<name>/blobs/<tarsum>
Content-Length: 0
Docker-Content-Digest: <digest>
  • 201Created状态响应表示layer在仓库中成功建立
  • Location字段表示layer在仓库的URL地址
  • Docker-Content-Digest字段包含了layer的完整性校验码

用户通常会忽略Docker-Content-Digest信息,但为了安全起见,建议用它校验本次上传layer数据的完整性。
在两种上传模式中,如果用户想知道某时刻的上传进度,只要发送一个查询请求

GET /v2/<name>/blobs/uploads/<uuid>
Host: <registry host>

如果上传已经完成,状态返回值是202 Accepted,反之返回204 No Content。
3)取消上传
如果镜像数据在上传途中因故要取消,用户可发送DELETE请求,Registry接收到请求之后会丢弃之前的上传数据,且不会记录镜像的<uuid>

DELETE /v2/<name>/blobs/uploads/<uuid>

上传如果没有完成,Registry将对这次过程作超时处理。若用户端遇到严重的问题无法正确上传,但是HTTP请求还在工作,则可以主动发送取消命令。
4)上传Manifest
全部layer数据上传完成之后,开始上传Manifest。

PUT /v2/<name>/manifests/<reference>
{
	"name": <name>,
	"tag": <tag>,
	"fsLayers": [
		{
			"blobSum": <tarsum>
		},
		...
	],
	"history": <v1 images>,
	"signature": <JWS>,
	...
}

请求URL中的<reference>表示这次上传的是指定的镜像<tag>或是<digest>,其中的<name>和<digest>要和整个上传过程保持一致,否则Registry会返回错误。如果Manifest里面带入的layer digest在Registry中没有记录,Registry将返回错误BLOB_UNKNOWN,认为layer无法识别。

鉴权机制

鉴权机制是Registry V2 版本之后新增加的功能,目的是校验用户请求权限。和Github类似,Registry的内部仓库分为公有和私有,私有仓库只允许特定用户访问,这种校验和限制访问权限的认为由Docker Engine、Registry和Auth Server协作完成。
外来的Registry HTTP请求由不同的客户端发起,Registry只有验证到请求由哪个用户发起,这次请求访问的资源是否对这个用户开放之后,才会允许请求继续。鉴权认证功能保证了Registry访问安全可控。
Auth Server根据Oauth 2.0协议生成token供Registry认证用户请求。token依照JSON Web Token(JWT)规范,在内容里包含Registry用到的域关键字如typ、alg和kid等。
为使能鉴权功能,Registry要在Auth接口上配置相关选项,其格式如下:

auth:
	token:
		realm: "https://127.0.0.1:5001/auth"
		service: "Docker registry"
		issuer: "My Auth Server"
		rootcerbundle: "/path/to/server.pem"

Auth Server由Registry开发者部署搭建,Registry完全信任Auth Server;Registry和Auth Server之间以证书(open SSL)作为凭据认证token,Auth Server生成和保持pem证书和密钥key,Registry导入Server发放的证书;Server把签过用户名的token返回给用户,用户用这个token去Registry作鉴权认证。
鉴权是针对一次完整的push或pull过程的,Docker Engine在push或pull过程中会调用一系列Registry API。鉴权之后产生token,每一次API访问都带有含认证信息的token,Registry就是通过校验token来接收API访问的。
在上传过程中,首先,用户发起上传请求,请求通过Docker Engine,依照Registry API协议发送给Registry。Engine试图赋给HTTP请求一个鉴权的token;如果没有token,daemon会试图更新一个新的token。
请求中包含了一系列的REST API方法因为push和pull的行为包含了这样一组方法,所以在Registry作为HTTP server运行的时候,就会通过方法注册的回调机制来判断方法所对应的后面的资源是不是和从token解析出来的参数匹配。
第二步,如果用户请求没有做过认证并且请求中不带token,则Registry将会返回401Unauthorized状态,并告诉用户端Auth Server地址,要求用户去认证。
这里的realm、service、scope等都是遵循Oauth 2.0协议的关键字。scope的内容格式是<resource>:<path>:<action>。以上面返回的信息为例,用户访问Registry的前提是从Auth Server获得token,且包含push samalba/my-app这个仓库资源的权限。
第三步,用户带着Registry返回的信息以及用户证书去访问Auth Server(地址包含在返回信息中)申请token。

GET /v2/token/?service=registry.docker.com&scope=repository:samalba/my-app:push&account=jlhawn HTTP/1.1
Host: <registry address>

第四步,Auth Server后端账户系统记录着用户名和密码,获取到当前访问用户的名字和密码后,依照JWT格式生成带有鉴权信息的token返回给用户。
密码以密文方式保证安全性,解密通过bcrypt算法,实现平台通常提供了相应的函数库。Auth Server可以通过灵活设计接口来提供一个或者多个认证源,包括基本的本地认证,以及第三方账户认证。
此时,Auth Server生成token,作为响应返回:

HTTP/1.1 200 OK
Content-Type: application/json
("token": "<token content>")

token 包含头信息、Claims Set和签名这三部分信息。

  • 头信息
{
	"type": "JWT",
	"alg": "ES256",
	"kid": "PYYO:....:Z7Q6"
}

其中,typ表示token类型,alg表示签名算法,kid表示密钥key的ID,密钥用来给token签名。

  • Claims Set
{
	"iss": "auth.server.com",
	"sub": "jlhawn",
	"aud": "my.registry.io",
	"exp": 1415387315,
	"nbf": 1415387315,
	"iat": 1415387315
	"jti": "tYJCO1....Fws",
	"access": [
		{
			"type": "repository",
			"name": "samalba/my-app",
			"actions": [
				"push"
			]
		}
	]
}

其中iss表示Auth Server域名;sub表示用户名;aud表示校验token方的域名;exp、nfb和iat表示token的有效期;jti表示token的唯一标识符;access数组标识访问对象的实体。

  • 签名
    签名使用密钥对头信息和Claims Set实体进行base64编码,然后合并编码结果生成token。

第五步,用户带着获取的token再次访问Registry,头信息包含关键字Authorization:

Authorization: Bearer <token content>

第六步,Registry校验token,校验对象包括:

  • Auth Server域名得到Registry认可;
  • token标示符是唯一的;
  • token在有效期;
  • token中访问对象信息和用户访问匹配。
    校验成功后用户上传获得鉴权,开始上传。

部署私有仓库

运行私有服务

搭建私有仓库的前提是部署Docker Pricate Registry。Registry的运行命令如下:

docker run -d --hostname localhost --name registry-v2 \
-v /opt/data/distribution:/var/lib/registry/docker/registry/v2 \
-p 5000:5000 registry

上面运行一个名为registry-v2的服务时,命令中的-v选项会把本地某个目录mount到容器内镜像存储目录,方便开发者查看和管理本地镜像数据。
这里为了演示,让Registry向外暴露了端口5000,这样一来,任何可以访问这台主机的用户都可以向Registry上传和下载镜像。比如,可用上面介绍Registry时演示的针对私有仓库的push/pull命令完成操作:

docker push localhost:5000/official/ubuntu:14.94

push后面指明了服务主机名或域名localhost,5000代表对外端口,后面跟随的是具体仓库的名称。这样访问该主机的用户就可用上传或下载镜像了。

构建反向代理

在实际使用中,暴露主机端口的方法是不安全的,如果Reigstry没有配置访问代理,任何用户够可以直接通过端口访问的。因此,设计时需要为其加上HTTPS反向代理。该方法会用代理服务器来接收用户HTTPS请求,然后将请求转发给内部网络上的Registry服务器,并将Registry响应结果返回给用户。请求经过代理服务的方式保证了对内部服务器的安全访问。
加上反向代理之后私有仓库会分发证书给用户,用户通过HTTPS协议才能够访问想要访问的主机,提升了用户访问的可靠性和安全性。Registry作为后端不会对外暴露端口,一定程度上保护了Reigstry不受外部非法访问攻击,唯一可以访问的端口是HTTPS协议规定443或是80端口。
在很多设计中,前端的HTTPS代理会采用Transport Layer Security(TLS)安全模式或者Nginx反向代理技术,这里以Nginx为例分析前端反向代理的部署方法。
开源社区上提供了一些在Docker Registry上用Nginx实现反向代理的方法。这里以ipendns组织下的nginx-auth-proxy开源项目为例,实现Docker Registry的反向代理功能。
在实现反向代理功能时,先要生成服务器的一对密钥和根证书,下面看看如何用openssl命令生成自签名证书。
首先生成私钥文件 server.key:

openssl genrsa -out server.key 2048

在这里插入图片描述
在生成根证书文件service.pem

openssl req -newkey rsa:2048 -nodes -keyout server.key \
-x509 -days 3650 -out server.pem -subj \
"/C=CN/ST=state/L=city/O=Your Company Name/OU=localhost"

在这里插入图片描述
下面将pem编码格式的证书转换成crt拓展名证书,放到系统证书目录(用户访问Registry之前需要作这一步)

cat server.pem | sudo tee -a /etc/ssl/certs/server.crt

在这里插入图片描述
生成用户信息文件

htpasswd -c passwd root

在这里插入图片描述
编辑default.conf

vi default.conf
server {
	listen       443;
	server_name  localhost;

	ssl on;
	ssl_certificate /etc/ssl/certs/docker-registry;
	ssl_certificate_key /etc/ssl/private/docker-registry;

	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header Authorization "";
	proxy_set_header Accept-Encoding "";
	proxy_set_header X-Forwarded-By $server_addr:$server_port;
	proxy_set_header X-Forwarded-For $remote_addr;

	location / {
		root   /usr/share/nginx/html;
		index  index.html index.htm;
	}

	location /v2 {
		auth_basic off;
		proxy_pass http://registry:5000;
	}

	location /auth {
		auth_basic "Restricted";
		auth_basic_user_file passwd;
		proxy_pass http://registry:5000;
	}

	error_page   500 502 503 504  /50x.html;
	location = /50x.html {
		root   /usr/share/nginx/html;
	}
}

Nginx把生成好的证书和用户信息通过Dockerfile加载到镜像中:

ADD server.crt /etc/ssl/certs/docker-registry
ADD server.key /etc/ssl/private/docker-registry
ADD passwd /etc/nginx/conf.d/passwd
ADD default.conf /etc/nginx/conf.d/default.conf

在这里插入图片描述
运行nginx

docker run -d --hostname my.docker.io --name nginx --link registry-v2:registry -p 443:443 nginx:my

在这里插入图片描述
在这里插入图片描述

Index及仓库高级功能

Index的作用和组成

按照上一节介绍的方法,搭建起来的私有仓库只具备镜像存储和安全访问功能,而不具备账户管理功能。要实现完整的解决方案,Docker开发者需要设计Docker Index用户管理系统。
Index主要包括以下几项作用:

  • 管理Docker Private Hub注册用户,认证用户访问权限;
  • 保持记录和更新用户信息,以及token等校验信息;
  • Docker元数据存储;
  • 记录用户操作镜像的历史数据;
  • 提供操作界面Web UI,用户可以方便地访问和更新资源。
    Index主要由几个子模块组成,包括控制单元、鉴权模块、数据库、健康检查模块和日志系统等。

控制单元

控制单元负责搭建HTTPS服务,运行在Index上层。它和周边模块通信,实现用户扩展功能。模块对上层提供信息交互的接口,把上层用户的请求封装给下层接收模块做处理,再把接收的返回信息传回给上层应用。
模块构建了一个HTTP Server,上面封装了TLS,以实现HTTPS方式安全访问。此外还设计了一个Application添加到Server,成为一个独立运行的Web App。对于查询、删除镜像和设置仓库属性等用户功能,Registry没有直接提供API接口,需要自己实现,把实现这几项功能的代码注册成为App回调函数,用户发送HTTP请求时,App从数据库返回结果给用户。
要让Index工作,需要预先配置若干环境变量,如鉴权地址、端口参数和数据库参数等。控制单元把这些参数从配置文件读出解析,根据环境变量配置工作模式或是传递给周边单元。
有些用户操作信息需要被记录在数据库中,如某个镜像被上传。下载及关注的次数等,但这些无法直接从Registry获取,因此,需要Index控制单元通过接收和处理外部请求或通知来获取,实现步骤如下:
1)控制单元中实现一系列上述功能的Handler处理函数,注册到Index HTTP Server
2)Registry处理一个用户上传或下载镜像的请求后,会通过内部的Broadcast模块通知远端的Index接收该事件,其中包含用户名、操作行为、操作对象、镜像名和操作状态等信息。
3)控制单元对收到的事件信息进行判断,若判断出是一次成功的上传或下载操作,就把需要记录的信息封装并发送给数据库接口。
4)对于关注镜像的操作,控制单元内部会实现一个Rest API。收到用户的关注请求后,API就把关注成功的响应返回给数据库接口

鉴权模块

鉴权模块是Index内部负责对Registry API请求提供鉴权token的模块。
Auth Server是Index中一个单独定义的路径,以HTTP Handler机制实现。
Server收到用户请求后,实现提取和解析用户名、密码、用户操作行为和用户访问对象等信息一致,然后确认用户是否有访问对象的权限。认证通过之后,Server根据JWT规范补全token信息,并生成签名校验码。最后,Server对token进行base64编码,把编码后的token字段返回给用户。
对于第三方用户的请求,Index Auth Server会把用户请求转发到第三方Server,获取认证结构后保存第三方token到数据库以避免重复认证,最后用上面介绍的方法生成Registry识别的token返回给用户。