1.Docker仓库
1.1 Docker Hub
docker仓库用于存放docker镜像,可以分为公用和私有两种。Docker Hub是全球公用的仓库,因服务器在国外,国内基本不可以;一般需要配置阿里、腾讯等加速器。公司内部而言,可以搭建私有的Docker仓库(如Harbor)。
说明:使用Docker Hub下载镜像时,不需要登录;但是推送、删除、修改时需要登录。
登录与注销涉及如下命令:
#登录时携带用户名、密码、仓库地址信息
docker login --username test --password test@123 192.168.0.22:8000
docker login --username seong --password 3er4#ER$ 192.168.0.22:8000
#注销时需要仓库信息
docker logout 192.168.0.22:8000
注册成功后,在/root/.docker/config.json文件中记录仓库登录成功信息:
{"auths": {"192.168.0.22:8000": {"auth": "c2Vvbmc6M2VyNCNFUiQ="}}
}
注销命令执行时,将删除文件中对应信息(可以重复注销)。
1.2 常见的本地仓库:Registory和Harbor
Docker提供了registry镜像,可以较为方便地搭建仓库。Harbor提供了对用户更为友好的UI界面,推荐使用Harbor。需要注意: Harbor通过docker-compose管理,因此安装Harbor是需要先安装/docker-compose,详细的安装流程请参考服务器环境搭建-2 Docker与Harbor。
1.3 配置docker仓库
docker的配置文件路径为/etc/docker/daemon.json,可以通过registry-mirrors
属性添加docker仓库。改动配置文件后,需要重启docker(执行service docker restart)生效。如可以配置腾讯的docker仓库镜像:
{"registry-mirrors": ["https://mirror.ccs.tencentyun.com"]
}
默认情况下,docker使用https协议与仓库通信。将上述搭建的Harbor仓库配置为docker的仓库地址时会报错: http: server gave HTTP response to HTTPS client
.
可以通过在insecure-registries属性中添加白名单地址来规避。如Harbor仓库的地址为:“http://192.168.0.22:8000”, 可以配置为:
{"registry-mirrors": ["https://mirror.ccs.tencentyun.com"], "insecure-registries": ["192.168.0.22:8000"]
}
除了在daemon.json配置文件中修改外,还可以再docker的service文件(/usr/lib/systemd/system/docker.service)中进行配置:
[Service]
Type=notify
ExecStart=/usr/bin/dockerd --insecure-registry=192.168.0.22:8000 -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutStartSec=0
RestartSec=2
Restart=always
ExecStart表示进程启动命令,通过–insecure-registry参数设置允许通过http访问的仓库地址。
说明:修改docker.service文件后,需要执行systemctl daemon-reload使得配置文件生效;然后执行service docker restart使用新的service文件重启docker.
2. Docker Registry v2 API介绍和案例分析
Docker Registry v2 API是Docker引擎与Docker Registry进行交互时的接口规范。Docker引擎进行镜像的下拉、删除、推送和修改时需要调用Docker Registry提供的接口。
Docker对容器和镜像进行了分层设计,API也以分层为基础。Docker镜像数据包括:数据(blob)和元数据(manifest)两个部分,前者是实际的二进制数据,后者是镜像的描述数据,V2 API为这两部分分别提供了接口。
本文介绍推送和下拉镜像相关的API,其他API请参考: Distribution Registry / Reference Overview / HTTP API V2。
2.1 API接口版本协商
2.1.1 API接口版本协商
url: GET /v2/
参数:无
响应: 返回200 OK表示请求成功; 返回401未经授权.
2.1.2 案例:
GET /v2/ HTTP/1.1
Host: 192.168.0.22:8000
User-Agent: docker/24.0.4 go/go1.20.5 git-commit/4ffc614 kernel/3.10.0-1160.92.1.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/24.0.4 \(linux\))
Accept-Encoding: gzip
Connection: closeHTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json; charset=utf-8
Docker-Distribution-Api-Version: registry/2.0
X-Content-Type-Options: nosniff
Date: Mon, 05 Feb 2024 01:48:16 GMT
Connection: close{}
说明:Docker引擎与仓库交互前,会使用该接口进行协议确认。收到 200 OK表示仓库支持V2版本API,即Docker引擎可继续使用V2 API与仓库交互。
2.2 推送镜像
2.2.1 推送镜像的 manifests
url: PUT /v2/<name>/manifests/<reference>
参数:
[1] name
: 镜像名称
[2] reference
: 标签/摘要
响应: 返回 201 Created表示已成功推送。
2.2.2 推送镜像的 blobs
url: PUT /v2//blobs/uploads/?digest=
参数:
[1] name
: 镜像仓库名称
[2] uuid: 唯一ID标识
[3] digest: blob 的摘要
响应: 返回 201Created表示已成功推送。
2.2.3 案例:
执行如下命令将从Docker Hub仓库下载的hello-world镜像,上传到本地仓库192.168.0.22:8000上:
docker pull hello-worlddocker tag hello-world 192.168.0.22:8000/hello-worlddocker push 192.168.0.22:8000/hello-world
在本地通过docker history命令可以看出hello-world包含两个镜像层, 其中一层镜像ID为d2c94e258dcb:
[root@VM-4-6-centos ~]# docker history hello-world
IMAGE CREATED CREATED BY SIZE COMMENT
d2c94e258dcb 9 months ago CMD ["/hello"] 0B buildkit.dockerfile.v0
<missing> 9 months ago COPY hello / # buildkit 13.3kB buildkit.dockerfile.v0
同时在环境上抓包,如下所示:
整个流程可以分为两个部分:上传各个镜像层的blobs数据和上传整个镜像的manifests数据;
[1] 上传各个镜像层的blobs数据
由于hello-world包含两个镜像层c1ec31e和d2c94e,因此分别进行镜像层数据的推送。在每次推送过程会经过校验和传输两个阶段: (1)通过HEAD请求携带镜像的ID信息查询镜像层在仓库是否存在,收到200表示已存在,不需要上传,收到404表示不存在,(2)上传镜像层数据。
上传完成后,还会再次通过HEAD请求校验是否上传完成。
[2] 上传整个镜像的manifests数据
上述上传hello-world:latest镜像元数据的抓包信息如下:
PUT /v2/hello-world/manifests/latest HTTP/1.1
Host: 192.168.0.22:8000
User-Agent: docker/24.0.4 go/go1.20.5 git-commit/4ffc614 kernel/3.10.0-1160.92.1.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/24.0.4 \(linux\))
Content-Length: 524
Content-Type: application/vnd.docker.distribution.manifest.v2+json
Accept-Encoding: gzip
Connection: close{"schemaVersion": 2,"mediaType": "application/vnd.docker.distribution.manifest.v2+json","config": {"mediaType": "application/vnd.docker.container.image.v1+json","size": 581,"digest": "sha256:d2c94e258dcb3c5ac2798d32e1249e42ef01cba4841c2234249495f87264ac5a"},"layers": [{"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip","size": 2459,"digest": "sha256:c1ec31eb59444d78df06a974d155e597c894ab4cda84f08294145e845394988e"}]
}HTTP/1.1 201 Created
Docker-Content-Digest: sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7
Docker-Distribution-Api-Version: registry/2.0
Location: http://192.168.0.22:8000/v2/hello-world/manifests/sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7
X-Content-Type-Options: nosniff
Date: Mon, 05 Feb 2024 01:48:16 GMT
Content-Length: 0
Connection: close
进一步测试将hello-world打包成beta版本,然后推送到本地192.168.0.22:8000仓库:
docker tag hello-world 192.168.0.22:8000/hello-world:beta2
docker push 192.168.0.22:8000/hello-world:beta2
同时在环境上抓包:
通过HEAD请求携带镜像的ID信息查询镜像层在仓库是否存在,收到200表示已存在,不需要上传镜像层数据,只需上传镜像的manifests即可。
由此也可以看出,Docker仓库将镜像层与镜像的元数据是分开存储的。镜像层可以被不同的镜像重复使用。
2.3 拉取镜像
2.3.1 获取镜像的manifests(元数据)
url: GET /v2/<name>/manifests/<reference>
参数:
[1] name
: 镜像名称
[2] reference
: 标签/摘要
响应: 包含manifest信息的JSON 对象。
2.3.2 获取镜像的blobs(二进制数据)
url: GET /v2/<name>/blobs/<digest>
参数:
[1] name
: 镜像名称
[2] digest
: blob 的摘要
响应: 返回镜像的 二进制数据
2.3.3 案例
将本地的hello-world:latest镜像删除,然后执行 docker pull 192.168.0.22:8000/hello-world从仓库拉重新拉取hello-world:latest镜像时,具体执行过程如下:
[1] 通过HEAD请求获取hello-world:latest版本镜像的摘要sha256,如果返回404表示镜像不存在
HEAD /v2/hello-world/manifests/latest HTTP/1.1
Host: 192.168.0.22:8000
User-Agent: docker/24.0.4 go/go1.20.5 git-commit/4ffc614 kernel/3.10.0-1160.92.1.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/24.0.4 \(linux\))
Accept: application/vnd.oci.image.manifest.v1+json
Accept: application/vnd.docker.distribution.manifest.v2+json
Accept: application/vnd.docker.distribution.manifest.list.v2+json
Accept: application/vnd.oci.image.index.v1+json
Accept: application/vnd.docker.distribution.manifest.v1+prettyjws
Accept: application/json
Connection: closeHTTP/1.1 200 OK
Content-Length: 524
Content-Type: application/vnd.docker.distribution.manifest.v2+json
Docker-Content-Digest: sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7
Docker-Distribution-Api-Version: registry/2.0
Etag: "sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7"
X-Content-Type-Options: nosniff
Date: Mon, 05 Feb 2024 03:43:16 GMT
Connection: close
[2] 得到sha256信息为d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7,
然后将得到的摘要作为参数调用GET方法获取镜像的元数据:
GET /v2/hello-world/manifests/sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7 HTTP/1.1
Host: 192.168.0.22:8000
User-Agent: docker/24.0.4 go/go1.20.5 git-commit/4ffc614 kernel/3.10.0-1160.92.1.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/24.0.4 \(linux\))
Accept: application/json
Accept: application/vnd.oci.image.manifest.v1+json
Accept: application/vnd.docker.distribution.manifest.v2+json
Accept: application/vnd.docker.distribution.manifest.list.v2+json
Accept: application/vnd.oci.image.index.v1+json
Accept: application/vnd.docker.distribution.manifest.v1+prettyjws
Accept-Encoding: gzip
Connection: closeHTTP/1.1 200 OK
Content-Length: 524
Content-Type: application/vnd.docker.distribution.manifest.v2+json
Docker-Content-Digest: sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7
Docker-Distribution-Api-Version: registry/2.0
Etag: "sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7"
X-Content-Type-Options: nosniff
Date: Mon, 05 Feb 2024 03:43:16 GMT
Connection: close{"schemaVersion": 2,"mediaType": "application/vnd.docker.distribution.manifest.v2+json","config": {"mediaType": "application/vnd.docker.container.image.v1+json","size": 581,"digest": "sha256:d2c94e258dcb3c5ac2798d32e1249e42ef01cba4841c2234249495f87264ac5a"},"layers": [{"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip","size": 2459,"digest": "sha256:c1ec31eb59444d78df06a974d155e597c894ab4cda84f08294145e845394988e"}]
}
[3] 从镜像的 manifests信息得知: hello-world:latest镜像由上下(d2c94e258dcb…)和(c1ec31eb5944…)两层组成。
Docker引擎判断本地是否有对应的镜像层,如果没有,会依次向仓库发送/v2//blobs/请求获取镜像层数据。此时本地环境不存在这两个镜像层,因此分别发送了两次请求:
请求c1ec31eb5944层镜像:
GET /v2/hello-world/blobs/sha256:c1ec31eb59444d78df06a974d155e597c894ab4cda84f08294145e845394988e HTTP/1.1
Host: 192.168.0.22:8000
User-Agent: docker/24.0.4 go/go1.20.5 git-commit/4ffc614 kernel/3.10.0-1160.92.1.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/24.0.4 \(linux\))
Accept-Encoding: gzip
Connection: close.....数据....
请求d2c94e258dcb层镜像:
GET /v2/hello-world/blobs/sha256:d2c94e258dcb3c5ac2798d32e1249e42ef01cba4841c2234249495f87264ac5a HTTP/1.1
Host: 192.168.0.22:8000
User-Agent: docker/24.0.4 go/go1.20.5 git-commit/4ffc614 kernel/3.10.0-1160.92.1.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/24.0.4 \(linux\))
Accept-Encoding: gzip
Connection: close.....数据....
上述流程的抓包信息如下:
此时,再次执行docker pull 192.168.0.22:8000/hello-world指令,抓包信息如下:
此时Docker引擎判断本地已有对应的镜像,无需再从仓库下载。
3.总结
Docker仓库的搭建、配置、使用较为简单,上手操作就能掌握,重点在于理解分层的概念。从上述镜像的推送和拉取过程可以看出,Docker容器的镜像以层为颗粒度,从而减少了不必要的数据存储和传输。后续在介绍Docker容器中也会提到分层的概念,说明分层对降低计算机资源占用率的重要意义。