编程技术文章分享与教程

网站首页 > 技术文章 正文

镜像仓库registry命令行启动,垃圾回收和服务监听我全都要

hmc789 2024-11-20 16:28:45 技术文章 1 ℃

简要:本文主要是对镜像结构及Registry API使用进行了总结,对registry启动GC和Serve命令做出了解析。

当我们使用Docker命令时,Push和Pull镜像操作会将镜像从哪里推送和拉取,镜像的结构是什么,为什么不能简单的用云盘这样的存储直接存储镜像?看完这篇文章,就能有个大概的了解了。

一、API V2

Docker Registry HTTP API是镜像到镜像仓库的协议,它与管理docker镜像和启用分布式的镜像仓库实例相互交互。而我们正在用的正是Docker Registry HTTP API V2。

虽然V1版本仍可用,但是和最新版本有整体架构上的问题,主要是因为V2改变了镜像的格式,详见docker/docker#8093

新的镜像manifest简化了镜像定义和提升了安全性。而API V2正是工作在新的manifest基础上,提高性能、减少带宽使用和减少后端故障的可能性。

这个特性是在release v1.3.0版本开始实现,只不过我们现在提到的V2是指Docker Manifest V2 schema 2,主要实现两点:

  • 允许多平台镜像,特定平台版本的镜像元数据
  • 支持将Docker引擎指向可寻址的内容镜像,通过支持镜像配置被Hash成镜像ID的镜像模型

1.1、媒体类型

  • application/vnd.docker.distribution.manifest.v1+json: schema1 (existing manifest format)
  • application/vnd.docker.distribution.manifest.v2+json: New image manifest format (schemaVersion = 2)
  • application/vnd.docker.distribution.manifest.list.v2+json: Manifest list, aka “fat manifest”
  • application/vnd.docker.container.image.v1+json: Container config JSON
  • application/vnd.docker.image.rootfs.diff.tar.gzip: “Layer”, as a gzipped tar
  • application/vnd.docker.image.rootfs.foreign.diff.tar.gzip: “Layer”, as a gzipped tar that should never be pushed
  • application/vnd.docker.plugin.v1+json: Plugin config JSON

1.2、元数据(manifest)列表

举个栗子

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
  "manifests": [
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "size": 7143,
      "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
      "platform": {
        "architecture": "ppc64le",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "size": 7682,
      "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
      "platform": {
        "architecture": "amd64",
        "os": "linux",
        "features": [
          "sse4"
        ]
      }
    }
  ]
}
  • schemaVersion(int):镜像元数据的架构版本,现在是2
  • mediaType(string):元数据列表的MIME类型,应该被设置为application/vnd.docker.distribution.manifest.list.v2+json
  • manifests(array):包括多平台的元数据列表 mediaType:对象的MIME类型,通常是application/vnd.docker.distribution.manifest.v2+json,但如果支持 Schema 1,也能支持application/vnd.docker.distribution.manifest.v1+json size:对象大小,客户端在验证之前可以比较大小,如果大小不符,则内容不能被信任 digest:对象的摘要 platform:描述了元数据中镜像的运行平台

1.3、镜像元数据(manifest)

举个栗子

{
    "schemaVersion": 2,
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "config": {
        "mediaType": "application/vnd.docker.container.image.v1+json",
        "size": 7023,
        "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
    },
    "layers": [
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 32654,
            "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
        },
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 16724,
            "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b"
        },
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 73109,
            "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
        }
    ]
}
  • schemaVersion:元数据版本
  • mediaType:元数据列表的MIME类型,应该被设置为application/vnd.docker.distribution.manifest.v2+json
  • config:通过摘要引用容器的配置对象,是runtime用来设置容器的Blob。 mediaType:指定对象的MIME类型,应该被设置为application/vnd.docker.container.image.v1+json size:对象的大小 digest:对象的摘要
  • layer:图层列表从base镜像开始(与Schmea 1相反) mediaType:指定对象的MIME类型,通常被设置为application/vnd.docker.image.rootfs.diff.tar.gzip,如果设置为application/vnd.docker.image.rootfs.foreign.diff.tar.gzip,会从远程拉取 size:对象大小 digest:对象摘要 urls:提供可以从中获取内容的 URL 列表。 必须根据 digest 和 size 验证内容。 此字段是可选的且不常见。

1.4、小结

到这里,我们能大概知道了docker的API升级了,现在在用的API V2,底层用的元数据是Manifest V2 schema 2,了解了MIME类型在镜像中的区别和应用。

二、启动流程

在cmd/registry/main.go文件中有启动registry的命令

func main() {
	registry.RootCmd.Execute()
}

在RootCmd命令下有两个子命令:

  • ServeCmd:服务监听
  • GCCmd:垃圾回收

2.1、GC

2.1.1、背景知识

docker镜像是分层的,registry在存储镜像时,将docker镜像分成2个部分:

  • 镜像元数据(manifest):存储在docker/registry/v2/repositories目录中,在这里能看到registry上的目录、项目中的镜像、镜像到layer的索引信息
  • blobs:存储在docker/registry/v2/blobs目录中,在这里按照00-ff分目录存储了所有镜像的layer。

例如有2个镜像使用了同一个基础镜像,那么在registry上存储时,blobs只有一份数据,而镜像的元数据的部分索引会指向相同的layer

举个栗子:

初始状态,A、B两个镜像,都是基于layer b所做的镜像,A引用a、b;B引用b、c

A -----> b <----- B
|                 |
|---->a     c<----|

之后删除镜像B

A -----> b        B
|                 
|---->a     c

此时layer c实际上没有在用了,但是registry在删除镜像B时,只是会删除B的元数据,并不会主动删除layer c,所以需要GC。

2.1.2、GC过程

registry的GC使用“标记-清理”法

1、标记:registry扫描元数据,元数据能索引到的blob标记为不能删除

2、清理:registry扫描所有的blobs,如果blobs没有被标记,则删除

2.2、Serve

服务启动需要配置文件,这部分就不提了,配置内容包括日志、存储、验证方式、消息通知、监控、监听地址等。

// setup context
ctx := dcontext.WithVersion(dcontext.Background(), version.Version)

// 1、从参数或环境变量解析配置文件
config, err := resolveConfiguration(args)
if err != nil {
	fmt.Fprintf(os.Stderr, "configuration error: %v\n", err)
	cmd.Usage()
	os.Exit(1)
}

if config.HTTP.Debug.Addr != "" {
	go func(addr string) {
		logrus.Infof("debug server listening %v", addr)
		if err := http.ListenAndServe(addr, nil); err != nil {
			logrus.Fatalf("error listening on debug interface: %v", err)
		}
	}(config.HTTP.Debug.Addr)
}
// 2、通过配置文件注册registry
registry, err := NewRegistry(ctx, config)
if err != nil {
	logrus.Fatalln(err)
}
// 3、如果有监控,则启动prometheus
if config.HTTP.Debug.Prometheus.Enabled {
	path := config.HTTP.Debug.Prometheus.Path
	if path == "" {
		path = "/metrics"
	}
	logrus.Info("providing prometheus metrics on ", path)
	http.Handle(path, metrics.Handler())
}
// 4、启动registry
if err = registry.ListenAndServe(); err != nil {
	logrus.Fatalln(err)
}

启动是初始化Registry实例

registry, err := NewRegistry(ctx, config)

2.2.1、初始化实例

func NewRegistry(ctx context.Context, config *configuration.Configuration) (*Registry, error) {
	var err error
	// 1、注册日志
	ctx, err = configureLogging(ctx, config)
	if err != nil {
		return nil, fmt.Errorf("error configuring logger: %v", err)
	}

	configureBugsnag(config)

	uuid.Loggerf = dcontext.GetLogger(ctx).Warnf
	
	// 2、生成app实例
	app := handlers.NewApp(ctx, config)
	// 3、注册健康检查:对象存储、本地文件、http、tcp
	app.RegisterHealthChecks()
	// 4、配置reporter
	handler := configureReporting(app)
	// 5、注册接口 /
	handler = alive("/", handler)
	// 6、注册健康检查接口
	handler = health.Handler(handler)
	// 7、中间件recover
	handler = panicHandler(handler)
	if !config.Log.AccessLog.Disabled {
		handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler)
	}

	server := &http.Server{
		Handler: handler,
	}

	return &Registry{
		app:    app,
		config: config,
		server: server,
	}, nil
}

健康检查配置是在配置文件中health中进行配置。

而初始化app实例这部分代码可以自己了解,里面主要做了注册API接口、账号的鉴权等

Tags:

标签列表
最新留言