docker registry

OCI即Open Container Initiative,开放容器计划,是一套有关容器与镜像的标准

想要实现一个容器镜像仓库,就要符合OCI的标准与规定的接口

该服务主要维护blob与manifest两种对象,其中blob记录了镜像文件的实际存储路径,manifest(货单)则是用来记录存在这样一个blob

此外还有一个Manifest List,是一个 Tag 所对应所有 Manifest 的元数据列表。digest是manifest和blob的标识符,然而在不同平台digest并不相同,且digest并不方便阅读,于是增加一个tag做标识

建表语句如下:

CREATE TABLE IF NOT EXISTS blobs (
    digest TEXT PRIMARY KEY NOT NULL,
    size INTEGER NOT NULL,
    storage_path TEXT,
    content_type TEXT,
    created_at TEXT DEFAULT (datetime('now')),
    accessed_at TEXT DEFAULT (datetime('now')),
    CHECK(digest LIKE 'sha256:%')
);

CREATE TABLE IF NOT EXISTS uploads (
    uuid TEXT PRIMARY KEY NOT NULL,
    repository TEXT NOT NULL,
    tmp_path TEXT NOT NULL,
    offset INTEGER NOT NULL DEFAULT 0,
    created_at TEXT DEFAULT (datetime('now')),
    updated_at TEXT DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS manifests (
    digest TEXT PRIMARY KEY NOT NULL,
    repository TEXT NOT NULL,
    tag TEXT,
    media_type TEXT NOT NULL,
    schema_version INTEGER NOT NULL DEFAULT 2,
    config_digest TEXT,
    data TEXT NOT NULL,
    created_at TEXT DEFAULT (datetime('now')),
    updated_at TEXT DEFAULT (datetime('now')),
    FOREIGN KEY(config_digest) REFERENCES blobs(digest),
    CHECK(digest LIKE 'sha256:%')
);

CREATE TABLE IF NOT EXISTS manifest_layers (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    manifest_digest TEXT NOT NULL,
    layer_digest TEXT NOT NULL,
    layer_order INTEGER NOT NULL,
    media_type TEXT,
    size INTEGER,
    FOREIGN KEY (manifest_digest) REFERENCES manifests(digest),
    FOREIGN KEY (layer_digest) REFERENCES blobs(digest),
    UNIQUE(manifest_digest, layer_order)
);

CREATE TABLE IF NOT EXISTS manifest_lists (
    digest TEXT PRIMARY KEY NOT NULL,
    repository TEXT NOT NULL,
    tag TEXT,
    media_type TEXT NOT NULL DEFAULT 'application/vnd.docker.distribution.manifest.list.v2+json',
    schema_version INTEGER NOT NULL DEFAULT 2,
    data TEXT NOT NULL,
    created_at TEXT DEFAULT (datetime('now')),
    updated_at TEXT DEFAULT (datetime('now')),
    CHECK(digest LIKE 'sha256:%')
);

CREATE TABLE IF NOT EXISTS manifest_list_manifests (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    list_digest TEXT NOT NULL,
    manifest_digest TEXT NOT NULL,
    architecture TEXT NOT NULL,
    os TEXT NOT NULL,
    variant TEXT,
    features TEXT,
    os_version TEXT,
    os_features TEXT,
    size INTEGER,
    FOREIGN KEY (list_digest) REFERENCES manifest_lists(digest),
    FOREIGN KEY (manifest_digest) REFERENCES manifests(digest),
    UNIQUE(list_digest, manifest_digest)
);

CREATE INDEX IF NOT EXISTS idx_blobs_digest ON blobs(digest);
CREATE INDEX IF NOT EXISTS idx_manifests_repo_tag ON manifests(repository, tag);
CREATE INDEX IF NOT EXISTS idx_manifest_lists_repo_tag ON manifest_lists(repository, tag);

blobs表以SHA256算出来的digest为主键,在上传完成后才新增条目,uploads表用作中间状态,以uuid为主键,manifests表与镜像一一对应,其中的层信息存在manifest_layers表中。两个manifest_list相关的表,建出来摆着看的

对blob,主要有三种操作,上传,存在性检查和下载

对于上传,流程是先blob再manifest:

  • 上传过程分三步,第一步是一个POST请求,api:/v2/{name}/blobs/uploads/,给定一个仓库,可能选择挂载一个别的仓库下的镜像需要特殊处理。新建一个upload项,返回一个location URL,用uuid标识
  • 第二步是一个PATCH请求,请求体里就是上传的镜像了,这一步是分片进行的,可能包含一个bytes 0-65535样的Content-Range。从数据库获取upload项,继续追加内容
  • 第三步是一个PUT请求,通知上传完成,可能有最后一部分blob内容,完成追加,清理临时文件,记录blob
  • 另外还有一个获取上传状态的GET请求,获知已经传了多少数据以便重传,去查offset字段并返回即可

存在性检查时根据digest检查是否有blob存在,取消上传是删除临时文件和upload记录,很直观
下载,读取blob并返回

然后是manifest相关的四个api,增删读查

增在所有层和blob上传之后,直接验证后写入对应字段即可

删只有未被tag或引用的可以,否则403。引用是指多仓库同镜像不重复存的机制,Rc减1即可不能删

读要获知configuration 和 layer digests,以便拉取blob

查存在性和digest/大小