如何从Docker Registry中导出镜像

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何从Docker Registry中导出镜像相关的知识,希望对你有一定的参考价值。

参考技术A 一、目录结构

Registry的配置文件中可以指定registry的运行目录(实验用本地文件系统作为后端存储),registry会在这个目录中建立相应的目录结构,我在本地启动一个registry服务,然后只push一个centos镜像上去。镜像名称是localhost:5000/library/centos:latest,然后registry在本地创建了如图1所示的目录。

图 1 registry目录

为了显示方便,我只截取了64位ID的前一部分。可以看到,目录大体分为两个:一个是blobs,一个是repositories。blobs中主要存放数据文件,可以看出都是经过sha256计算后的ID。repositories目录中放镜像的描述信息,记录了一个镜像有哪些layer,tag对应的manifest文件,link文件是一个文本文件,内容是一个形如“sha256:cf34a09a90b54c…”的64位ID,这个ID对应在blob中的文件其实就是这个image的manifest文件。

二、Manifest文件

manifest文件描述了一个镜像的元信息,包括了layer的数据ID,layer的配置等,文件格式是json形式的文本文件。

docker镜像可以分为V1和V2,在1.9以后镜像格式有一些变化。为了向前兼容V1版本的docker,docekr
registryV2使用的manifest也对应地分为Schema1和Schema2,两者可以通过官方对于manifest的解释可以参考[1]和[2]。这里实验都是在schema1上做的。

Schema1主要包含如下信息:

name:image的仓库(repository)名,比如localhost:5000/library/centos:latest这个镜像的repository name是library/centos
tag:该镜像的tag
architecture:指该镜像的宿主机的操作系统架构,如“amd64”
fsLayers:该字段是一个数组,数组中的元素分别指明了各层对应的数据文件的sha256ID,数组的第1个就是镜像的最顶层,第2个是次顶层…以此类推,值得注意的是,不同层的fsLayer
ID
有可能一样,是因为有些层是空的,只有一些配置信息。当执行了一个不涉及文件操作的命令,这时候就会形成空fsLayer,空fsLayer计算出来的sha256ID也都是一样的了。镜像的一个layer,是由文件系统(比如新增的文件)fsLayer以及配置信息构成的,layer在docker的代码层面又被称为image,因为任意一个layer都可以作为顶层layer,被docker
image信息引用,从而成为一个image。所以需要区分fsLayer与layer。
history:该字段也是一个数组,是为了兼容v1而设置的,指明了每个layer的配置信息,数组第一项对应的是镜像的最顶层,与fslayer一起构成了一个layer。数组元素是一个json格式的map对象,key为“v1Compatibility”,值为一个字符串,该字符串就是layer的配置信息,可以直接用json.Unmrashal成为一个V1Image结构体(定义可以参考代码github.com/docker/docker/image/image.go
L31)
schemaVersion:该manifest的版本,一个int型,如 1。

三、Tar包形式的镜像

Docker中有个save和load命令。save命令可以将一个docker镜像导出,把这个镜像从最顶层到最底层的所有layer一起导出到一个tar包中,然后就可以随意拷贝、发送这个tar包到别的机器,最后可以用load命令把这个镜像重新加载进docker。

如果我们把一个镜像从registry里拿出来,按照save成的tar包格式来组织,然后使用load命令加载,这样就实现了不通过docker pull命令来下载镜像,可以根据这个原理做第三方镜像下载工具。

所以我们先来看一下镜像的tar包形式是什么样的,我使用save命令导出了centos镜像,解压后目录结构如图2所示:

图 2 镜像tar包解压后的目录

下面对各文件进行解释:

根目录下的repositories文件,描述了这个镜像的名字,tage,还有顶层layer的id

不同的文件夹代表了不同的layer。

json:layer的配置信息,如创建时间,执行命令等。

layer.tar:layer中包含的文件,如果是空layer,layer.tar解压后就是空的。

VERSION: 版本信息。

四、从registry导出镜像

我们对比tar包中的文件和registry中的文件,不难发现其中的对应关系,json、VERSION还有repositories文件都是可以从manifest中导出。

json文件其实就是之前提到的history字段中v1Compatibilitiy,不同的是manifest中的这个字段中有很多转义符,我们需要去掉这些转义符,方法是先Unmarshal成为一个V1Image结构体,然后在json.Marshal转回字符串就好。

layer.tar其实就是blobs中对应的data文件,直接复制出来然后改个名字就可以。

VERSION是manifest中的schemaVersion。

repositories文件内容很简单,格式是“imageName”:“tag”:”topLayerID”,所以按照这个格式从manifest中找到对应的数据填进去就ok。

这些文件都准备好了以后,就可以准备打包成tar包了,直接使用linux中自带的tar命令,这里需要注意的一点是,应该使用“只打包不压缩”的选项。生成tar包后就可以直接使用docker load命令导入了。

我这么做了,是可以成功地导入一个镜像。但是发现存在一个问题:使用上述方式导入的镜像,每个layer的ID和我直接用docker
pull命令下载下的不一样,而且docker
pull得到的ID从未在manifest和registry中的任何地方出现过。而且不管我使用新的机器还是重新pull,得到的ID都是一样的。经过阅读docker的代码我才发现,layerID不是随机生成的,也不是manifest中写道的id,而是算出来的。下面就说一下计算过程。

我们最终需要的layerID在docker源码中叫做StrongID,StrongID是把一个byte数组做Hash后得到的,这个byte数组的生成需要三个对象:v1Compatibility,blobSum(manifest中的fsLayerID),parent(父layer的StrongID),数组生成方法参考image.go中的MakeImageConfig方法(docker
1.9),基本操作就是把一个json对象转成字节数组。因为有parent字段的存在,需要从最底层的layer开始计算,逐步迭代,最终的到top
layer的ID。最后要做的工作就是替换json文件中的id字段和parent字段成为新计算出来的ID即可。同样地,文件夹名也要做相应改变。

增量导入:如果本地已经存在某些layer的情况,我们只用打包新的layer即可,因为导入时候docker会检测这个layer是否存在,而且有parent信息来保证layer之间的关系。

Docker极简教程 高级

1.Registry介绍

Registry 是镜像仓库,我们可以从镜像仓库中拉取一些镜像到本地,也可以提交镜像到仓库。

一些术语:

ENGLISH 中文
host 宿主机
image 镜像
container 容器
registry 仓库
daemon 守护程序
client 客户端

与registry仓库的交互:

查找镜像

docker search whalesay


拖取镜像

docker pull whalesay


推送镜像

docker push myname/whalesay

国内的docker镜像仓库:

  1. daoclou

  2. 时速云

  3. aliyun

2.Registry实战

1.查找镜像


STARS可以理解为点赞数,默认是按照这个排序的。
2.拉取镜像

Docker极简教程 【高级】

3.查看镜像

Docker极简教程 【高级】

可以看到,REPOSITORY是镜像名,TAG是默认的latest,正常情况是版本号,这两个比较重要。

CREATED是创建时间,SIZE是占用空间的大小。

4.运行镜像

Docker极简教程 【高级】

5.标记镜像

使用docker tag命令标记本地镜像,将其归入某一仓库。

docker tag docker/whalesay myhaleasy:tag


Docker极简教程 【高级】

6.上传镜像

可以使用docker push命令上传本地镜像到仓库,但是需要注册登录。

Docker极简教程 【高级】

执行登录命令,输入用户名与密码即可。

docker login


Docker极简教程 【高级】

上传镜像前还需要在Docker官网个人中心创建该镜像。

3.Compose多容器应用

Compose 是一个用户定义和运行多个容器的 Docker 应用程序。在 Compose 中你可以使用 YAML 文件来配置你的应用服务。然后,只需要一个简单的命令,就可以创建并启动你配置的所有服务。

docker-compose 安装

1.Mac/Windows:

安装docker的时候附带安装了。

2.Linux:

curl https://github.com/docker/compose
Linux安装

执行命令

curl -L https://github.com/docker/compose/releases/download/1.9.0/docker-compose-$(uname -s)-$(uname -m) > /usr/local/bin/docker-compose

下载完成后,可以设置该目录的权限为777,意为所有人都可以读写该目录,最后使用 docker-compose --version 检查是否安装成功。

Docker极简教程 【高级】

4.Compose搭建博客网站

接下来我们使用docker-compose搭建一个含nginx+ghost+db的博客网站。

1.准备

首先创建如下目录结构:

ghost
  - ghost
    - Dockfile
    - config.js
  - nginx
      - nginx.conf
      - Dockfile
  - data
  - docker-compose.yml

每个文件的具体内容有:

ghost/ghost/Dockfile

过时的配置(不使用)

FROM ghost
COPY ./config.js /var/lib/ghost/config.js
EXPOSE 2368
CMD ["npm","start","--production"]

最新版的官方镜像, 把配置文件改到/var/lib/ghost/content/ ,然后注释掉了 CMD["npm","start","--production"] 即可。

FROM ghost
COPY ./config.js /var/lib/ghost/content/ 
EXPOSE 2368


ghost/ghost/config.js

var path = require('path'),
    config;

config = {
    production: {
        url: 'http://mytestblog.com',
        mail: {},
        database: {
            client: 'mysql',
            connection: {
                host: 'db',
                user: 'ghost',
                password: 'ghost',
                database: 'ghost',
                port: '3306',
                charset: 'utf8'
            },
            debugfalse
        },
        paths: {
            contentPath: path.join(process.env.GHOST_CONTENT, '/')
        },
        server: {
            host: '0.0.0.0',
            port: '2368'
        }
    }
};

module.exports = config;


ghost/nginx/Dockerfile

FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80


ghost/nginx/nginx.conf

worker_processes 4;
events {worker_connections 1024;}
http {
    server {
        listen 80;
        location / {
            proxy_pass http://ghost-app:2368;
        }
    }
}


ghost/docker-compose.yml

version: '2'

networks:
  ghost:
services:
  ghost-app:
    build: ghost
    networks:
      - ghost
    depends_on:
      - db
    ports:
      - "2368:2368"

  nginx:
    build: nginx
    networks:
      - ghost
    depends_on:
      - ghost-app
    ports:
      - "80:80"

  db:
    image: "mysql:5.7.15"
    networks:
      - ghost
    environment:
      MYSQL_ROOT_PASSWORD: mysqlroot
      MYSQL_USER: ghost
      MYSQL_PASSWORD: ghost
    volumes:
      - $PWD/data:/var/lib/mysql
    ports:
      - "3306:3306"

此处数据库相关的配置要和config.js中的配置(user、password)保持一致。

一旦我们为一个服务指定了一个名字(db),它就可以被其它服务所解析。

nginx 中proxy_pass 设置成 http://ghost-app:2368 也是因为在docker-compose.yml 指定了服务是ghost-app。

2.启动

将所有容器启动,并以daemon的方式后台运行。第一次不需要build,因为会自动创建镜像。

docke-compose up -d
3.查看应用
docker-compose ps


Docker极简教程 【高级】

因为文件内容输入有误,导致启动未成功,修改文件后还需要停止、删除掉容器再重新构建才行。

4.停止(+)

停止所有容器

docker-compose stop
5.删除(+)

删除时需要输入y手动确认

docker-compose rm
6.构建(+)

非首次运行就需要手动创建镜像了

docker-compose build

最后,在ghost目录下再次启动!

Docker极简教程 【高级】

7.测试

访问域名进入搭建的博客网站,可以注册账号发布博客。

关于ghost的使用这里不做过多介绍。

5.docker-compose.yml常用命令

命令 用途
build 本地创建镜像
command 覆盖缺省命令
depends_on 连接容器
ports 暴露端口
volumes
image pull镜像
up 启动服务
stop 停止服务
rm 删除服务中的各个容器
logs 观察各个容器的日志
ps 列出服务相关的容器

6.Docker资源链接

Docker官方英文资源

docker官网:

http://www.docker.com

Docker中文资源

Docker中文网站:

https://www.docker-cn.com/


Docker安装手册:

https://docs.docker-cn.com/engine/installation/

Docker 国内镜像

网易加速器:

http://hub-mirror.c.163.com


官方中国加速器:

https://registry.docker-cn.com


ustc的镜像:

https://docker.mirrors.ustc.edu.cn


daocloud:

https://www.daocloud.io/mirror#accelerator-doc(注册后使用)


关注后端技术精选,每天推送优质好文

以上是关于如何从Docker Registry中导出镜像的主要内容,如果未能解决你的问题,请参考以下文章

如何从docker将image镜像到私有的registry

如何从docker将image镜像到私有的registry

docker镜像导出

快速搭建Docker镜像仓库

如何搭建及使用docker registry

Docker镜像命名解析