Containerd Container管理功能解析

Containerd Container管理功能解析

container是containerd的一个核心功能,用于创建和管理容器的基本信息。
本篇containerd版本为v1.7.9
更多文章访问 https://www.cyisme.top

本文从ctr c create命令出发,分析containerd的容器及镜像管理相关功能。
流程图

ctr命令

ctr container相关命令的实现在cmd/ctr/commands/containers目录中。

// 查看文件 cmd/ctr/commands/containers/containers.go
var createCommand = cli.Command{
    // 省略内容...
	Action: func(context *cli.Context) error {
		// 省略内容...
		client, ctx, cancel, err := commands.NewClient(context)
		if err != nil {
			return err
		}
		defer cancel()
		_, err = run.NewContainer(ctx, client, context)
		if err != nil {
			return err
		}
		return nil
	},
}
// 查看文件`cmd/ctr/commands/run/run_unix.go`
func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
    // 省略内容...
	if config {
		cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label"))))
		opts = append(opts, oci.WithSpecFromFile(context.String("config")))
	} else {
		// 省略内容...
		if context.Bool("rootfs") {
            // 是否以指定的本地文件系统运行,而不是镜像
			rootfs, err := filepath.Abs(ref)
			if err != nil {
				return nil, err
			}
			opts = append(opts, oci.WithRootFSPath(rootfs))
			cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label"))))
		} else {
			snapshotter := context.String("snapshotter")
			var image containerd.Image
            // 获取镜像信息 grpc
			i, err := client.ImageService().Get(ctx, ref)
			if err != nil {
				return nil, err
			}
            // 是否指定了平台, 未指定则使用默认平台(client.platform)
			if ps := context.String("platform"); ps != "" {
				platform, err := platforms.Parse(ps)
				if err != nil {
					return nil, err
				}
				image = containerd.NewImageWithPlatform(client, i, platforms.Only(platform))
			} else {
				image = containerd.NewImage(client, i)
			}
            // 目标镜像是否已经解压,未解压则解压
            // 解压相关解析可以看我的《Containerd Snapshots功能解析》这篇文章
			unpacked, err := image.IsUnpacked(ctx, snapshotter)
			if err != nil {
				return nil, err
			}
			if !unpacked {
				if err := image.Unpack(ctx, snapshotter); err != nil {
					return nil, err
				}
			}
            //省略生成配置代码...
			
		}
		// 省略生成配置代码...
        // 容器是否为特权容器,有直接访问宿主机的权限
		privileged := context.Bool("privileged")
		privilegedWithoutHostDevices := context.Bool("privileged-without-host-devices")
		if privilegedWithoutHostDevices && !privileged {
			return nil, fmt.Errorf("can't use 'privileged-without-host-devices' without 'privileged' specified")
		}
		if privileged {
			if privilegedWithoutHostDevices {
				opts = append(opts, oci.WithPrivileged)
			} else {
				opts = append(opts, oci.WithPrivileged, oci.WithAllDevicesAllowed, oci.WithHostDevices)
			}
		}
		// 省略生成配置代码...
        // rootfsPropagation 用于控制容器文件系统的挂载传播行为
        // 响容器内部文件系统和主机文件系统之间的挂载关系
        // 当容器内的文件系统发生变更时,这些变更如何传播到主机文件系统。
		rootfsPropagation := context.String("rootfs-propagation")
		if rootfsPropagation != "" {
			opts = append(opts, func(_ gocontext.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
				if s.Linux != nil {
					s.Linux.RootfsPropagation = rootfsPropagation
				} else {
					s.Linux = &specs.Linux{
						RootfsPropagation: rootfsPropagation,
					}
				}

				return nil
			})
		}
		// 省略生成配置代码...
	}
    // 省略生成配置代码...
	// oci.WithImageConfig (WithUsername, WithUserID) depends on access to rootfs for resolving via
	// the /etc/{passwd,group} files. So cOpts needs to have precedence over opts.
	return client.NewContainer(ctx, id, cOpts...)
}
// client.go
func (c *Client) NewContainer(ctx context.Context, id string, opts ...NewContainerOpts) (Container, error) {
	// 调用grpc, 创建container
	r, err := c.ContainerService().Create(ctx, container)
	if err != nil {
		return nil, err
	}
	return containerFromRecord(c, r), nil
}

containerd grpc

主要涉及到imagecontainer两个服务。

image服务

// 获取镜像信息 grpc
i, err := client.ImageService().Get(ctx, ref)
if err != nil {
    return nil, err
}

containerd中的image服务相关实现,在 services/images/local.go 中。

这里解析Get和Delete两个方法, Create和Update可以看 《Containerd Snapshots功能解析》 这篇文章。

// image service 结构体定义
type local struct {
    // bbolt 数据库
	store     images.Store
    // 用于删除时的清理操作
	gc        gcScheduler
    // 用于发布事件
	publisher events.Publisher
    // 这里用于发出弃用警告
	warnings  warning.Service
}
// 获取镜像
func (l *local) Get(ctx context.Context, req *imagesapi.GetImageRequest, _ ...grpc.CallOption) (*imagesapi.GetImageResponse, error) {
    // 从数据库中获取镜像信息
	image, err := l.store.Get(ctx, req.Name)
	if err != nil {
		return nil, errdefs.ToGRPC(err)
	}
    // 省略代码...
}
// 删除镜像
func (l *local) Delete(ctx context.Context, req *imagesapi.DeleteImageRequest, _ ...grpc.CallOption) (*ptypes.Empty, error) {
	log.G(ctx).WithField("name", req.Name).Debugf("delete image")

	if err := l.store.Delete(ctx, req.Name); err != nil {
		return nil, errdefs.ToGRPC(err)
	}
    // 省略代码...
    // 清理操作
	if req.Sync {
		if _, err := l.gc.ScheduleAndWait(ctx); err != nil {
			return nil, err
		}
	}
	return &ptypes.Empty{}, nil
}

镜像管理的接口逻辑比较简单,最终都是对bbolt数据库的操作。

这里仅用Get方法举例说明, 其他操作均是调用bbolt数据库的相关方法。

// 镜像存储结构体定义
type imageStore struct {
	db *DB
}
func (s *imageStore) Get(ctx context.Context, name string) (images.Image, error) {
	var image images.Image
    // 获取namespace
    // namespace是containerd中的一个概念,用于隔离不同的用户或者应用
    // 一个namespace独享一个bucket
	namespace, err := namespaces.NamespaceRequired(ctx)
	if err != nil {
		return images.Image{}, err
	}

	if err := view(ctx, s.db, func(tx *bolt.Tx) error {
        // 使用namespce,获取镜像所在的bucket
		bkt := getImagesBucket(tx, namespace)
		if bkt == nil {
			return fmt.Errorf("image %q: %w", name, errdefs.ErrNotFound)
		}
        // 获取镜像信息
		ibkt := bkt.Bucket([]byte(name))
		if ibkt == nil {
			return fmt.Errorf("image %q: %w", name, errdefs.ErrNotFound)
		}

		image.Name = name
		if err := readImage(&image, ibkt); err != nil {
			return fmt.Errorf("image %q: %w", name, err)
		}

		return nil
	}); err != nil {
		return images.Image{}, err
	}

	return image, nil
}

snapshot服务

镜像的解压操作会涉及到snapshot服务,相关解析可以看 《Containerd Snapshots功能解析》 这篇文章。

if !unpacked {
    if err := image.Unpack(ctx, snapshotter); err != nil {
        return nil, err
    }
}

container服务

// 调用grpc, 创建container
r, err := c.ContainerService().Create(ctx, container)
if err != nil {
    return nil, err
}

containerd中的image服务相关实现,在 services/containers/local.go 中。

这里仅解析Create和Update方法, 其余方法逻辑比较简单。

// container service 结构体定义
type local struct {
    // 用于存储container的基础信息
    // containers.Store是定义的interface, 限制了容器相关的操作范围,仅增删查改,并做了业务逻辑处理
	containers.Store
    // 用于存储container的元数据
    // metadata.DB是基础的数据存储模块。
	db        *metadata.DB
    // 实际运行时,实现containers.Store接口的结构内部也有一个db对象,实际上和上述的db是同一个对象。
	publisher events.Publisher
}
// 创建container
func (l *local) Create(ctx context.Context, req *api.CreateContainerRequest, _ ...grpc.CallOption) (*api.CreateContainerResponse, error) {
	var resp api.CreateContainerResponse

    // 在bbolt数据库中创建container记录
	if err := l.withStoreUpdate(ctx, func(ctx context.Context) error {
		container := containerFromProto(req.Container)

		created, err := l.Store.Create(ctx, container)
		if err != nil {
			return err
		}

		resp.Container = containerToProto(&created)

		return nil
	}); err != nil {
		return &resp, errdefs.ToGRPC(err)
	}
	// 省略代码...
	return &resp, nil
}
// 更新container
func (l *local) Update(ctx context.Context, req *api.UpdateContainerRequest, _ ...grpc.CallOption) (*api.UpdateContainerResponse, error) {
    // 省略代码...
	if err := l.withStoreUpdate(ctx, func(ctx context.Context) error {
        // 获取需要更新的字段
		var fieldpaths []string
		if req.UpdateMask != nil && len(req.UpdateMask.Paths) > 0 {
			fieldpaths = append(fieldpaths, req.UpdateMask.Paths...)
		}

		updated, err := l.Store.Update(ctx, container, fieldpaths...)
		if err != nil {
			return err
		}

		resp.Container = containerToProto(&updated)
		return nil
	})
    // 省略代码...
}

实际调用的是containers.Store接口的方法, 接口实现在 metadata/containers.go。

// 创建container
// metadata/containers.go
func (s *containerStore) Create(ctx context.Context, container containers.Container) (containers.Container, error) {
    // 获取namespace
	namespace, err := namespaces.NamespaceRequired(ctx)
	if err != nil {
		return containers.Container{}, err
	}
    // 验证container
	if err := validateContainer(&container); err != nil {
		return containers.Container{}, fmt.Errorf("create container failed validation: %w", err)
	}
    // 在bbolt数据库中创建container记录
	if err := update(ctx, s.db, func(tx *bolt.Tx) error {
        // 获取namespace对应的bucket
		bkt, err := createContainersBucket(tx, namespace)
		if err != nil {
			return err
		}
        // 创建container对应的bucket
		cbkt, err := bkt.CreateBucket([]byte(container.ID))
		if err != nil {
			if err == bolt.ErrBucketExists {
				err = fmt.Errorf("container %q: %w", container.ID, errdefs.ErrAlreadyExists)
			}
			return err
		}
        // 更新container时间相关信息
		container.CreatedAt = time.Now().UTC()
		container.UpdatedAt = container.CreatedAt
		if err := writeContainer(cbkt, &container); err != nil {
			return fmt.Errorf("failed to write container %q: %w", container.ID, err)
		}

		return nil
	}); err != nil {
		return containers.Container{}, err
	}

	return container, nil
}
func (s *containerStore) Update(ctx context.Context, container containers.Container, fieldpaths ...string) (containers.Container, error) {
	// 省略获取namespace代码...
	var updated containers.Container
	if err := update(ctx, s.db, func(tx *bolt.Tx) error {
		// 省略获取bucket、container代码...
		if len(fieldpaths) == 0 {
			// only allow updates to these field on full replace.
			fieldpaths = []string{"labels", "spec", "extensions", "image", "snapshotkey"}

            // 这里是container的一些基础信息,不允许更新
            // Snapshotter改变会导致container的快照信息丢失, 相当于从新创建
            // Runtime改变会导致container的运行时信息丢失, 相当于从新创建
			if updated.Snapshotter != container.Snapshotter {
				return fmt.Errorf("container.Snapshotter field is immutable: %w", errdefs.ErrInvalidArgument)
			}

			if updated.Runtime.Name != container.Runtime.Name {
				return fmt.Errorf("container.Runtime.Name field is immutable: %w", errdefs.ErrInvalidArgument)
			}
		}

		// 省略更新字段更新代码...
		return nil
	}); err != nil {
		return containers.Container{}, err
	}

	return updated, nil
}

总结

containerd的容器管理功能,主要是通过grpc调用containerd的image和container服务实现的。

container创建时会获取镜像快照信息(解压/挂载),命令行参数会转换成oci配置,最终生成container的基础信息。

container的创建动作仅生成数据,用于后续的container start(task)操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/195332.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[英语学习][3][Word Power Made Easy]的精读与翻译优化

[序言] 这次翻译校验, 难度有点大, 原版中英翻译已出现了严重地偏差. 昨晚11点开始阅读如下段落, 花费了1个小时也没有理解原作者的核心表达, 索性睡觉了. 今早学习完朗文单词之后, 9点半开始继续揣摩. 竟然弄到了中午11点30, 终于明白原作者要表达的意思了. 废话不多说&#x…

Selenium 学习(0.14)——软件测试之测试用例设计方法——因果图法2【基本步骤及案例】

1、因果图法的基本步骤 2、案例分析 1)分析原因和结果 2)关联原因和结果 投入1元5角或2元,按下“可乐”,送出“可乐”【暂时忽略找零】 投入2元,按下“可乐”或“雪碧”。找零5角,送出“可乐”或“雪…

大量用户弃用5G网络,四大运营商血亏? 喜闻乐见

家人们,老百姓讨厌5G网络,不是一天两天的事情了,有人认为5G网络是个坑,我们就应该永远用4G网络才对,国家为何要折腾这事?肯定是闲的。 正是这种思维的蔓延,导致了大量用户弃用5G网络。这事对四大…

搭建Angular并引入NG-ZORRO组件库

作者:baekpcyyy🐟 1.安装node.js 注:安装 16.0 或更高版本的 Node.js node官网:https://nodejs.org/en 2.进入angular官网 https://angular.cn/guide/setup-local 新建一个文件夹 vsc打开 打开终端 1.首先安装angular手脚架…

高级/进阶”算法和数据结构书籍推荐

“高级/进阶”算法和数据结构书籍推荐《高级算法和数据结构》 高级算法和数据结构 为什么要选择本书 谈及为什么需要花时间学算法,我至少可以列举出三个很好的理由。 (1)性能:选择正确的算法可以显著提升应用程序的速度。仅就搜索来说,用二…

Python基础语法之学习input()函数

Python基础语法之学习input函数 前言一、代码二、效果 前言 一、代码 # 默认是字符串类型 number input("请输入一个数字:") print("输入的数字是",number)二、效果 没有人可以阻止你成为自己想成为的人,只有你自己才能放弃梦想。…

WinMerge使用教程,WinMerge下载

一、下载 官方下载 WinMerge - You will see the difference… 官方地址:https://winmerge.org/ 阿里云盘下载 文件内容对比工具WinMerge2.16.25.25 https://www.alipan.com/s/r7MzudB235x 点击链接保存,或者复制本段内容,打开「阿里云盘…

机器视觉:塑造未来的智能视界

🎥 屿小夏 : 个人主页 🔥个人专栏 : IT杂谈 🌄 莫道桑榆晚,为霞尚满天! 文章目录 📑 前言🌤️ 机器视觉技术的实现☁️ 图像采集☁️ 图像处理☁️ 数据建模☁️应用展示…

94.STM32外部中断

目录 1.什么是 NVIC? 2.NVIC寄存器 3.中断优先级 4.NVIC的配置 设置中断分组​编辑 配置某一个中断的优先级 5.什么是EXTI 6.EXTI和NVIC之间的关系 7.SYSCFG 的介绍 1.什么是 NVIC? NVIC是一种中断控制器,主要用于处理 ARM Cort…

java--子类中访问其他成员的特点

1.在子类方法中访问其他成员(成员变量、成员方法),是依照就近原则的。 ①先子类局部范围找。 ②然后子类成员范围找。 ③然后父类成员范围找,如果父类范围还没有找到则报错。 2.如果父类中,出现了重名的成员,会优先使用子类的…

NV040C语音芯片:让自助ATM机使用更加安全快捷

近年来,移动支付方式的兴起、银行加强线上化服务、数字人民币项目推进等因素的影响,人们使用ATM机的频率呈现小幅度的下降趋势。然而,自助ATM机并未从我们的视野中消失,它们仍然在金融领域发挥着重要的作用。未来,ATM机…

Rust内存布局

题图忘了来自哪里.. 整型,浮点型,struct,vec!,enum 本文是对 Rust内存布局 的学习与记录 struct A { a: i64, b: u64,}struct B { a: i32, b: u64,}struct C { a: i64, b: u64, c: i32,}struct D { a: i32, b: u64, c: i32, d: u64,}fn main(…

关于网站的favicon.ico图标的设置需要注意的几点

01-必须在网页的head标签中放上下面的说明&#xff1a; <link rel"shortcut icon" href"/favicon.ico">否则&#xff0c;浏览器虽然能读到图标&#xff0c;但是不会把图标显示在标签上。 02-浏览器会默认到网站根目录中读取文件favicon.ico&#x…

学习笔记-瑞吉外卖项目实战(一)

软件开发整体介绍 软件开发流程 角色分工 软件环境 瑞吉外卖项目介绍 项目介绍 产品原型介绍 技术选型 功能架构 角色 开发环境搭建 数据 创建database reggie&#xff0c;在里面创建表&#xff1a; maven 创建springboot项目并导入相关依赖坐标&#xff1a; 我们可以在项目…

webGL网页游戏的开发步骤

开发基于 WebGL 的网页游戏涉及多个步骤&#xff0c;包括游戏概念的设计、图形资源的创建、编码和调试等。以下是一个一般性的步骤指南&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 确定游戏概念&a…

【办公软件】XML格式文件怎么转Excel表格文件?

首先我们有一个XML格式的文件&#xff0c;例如&#xff1a; 其中的数据是这样的&#xff1a; 我们现在在路径中选中这个XML格式的文件&#xff0c;然后鼠标右键&#xff0c;选择用WPS表格打开后&#xff0c;可见XML格式的文件已经自动转换成了Excel表啦。。。。

CH02_交给子类

Template Method模式 组成模板的方法被定义在父类中&#xff0c;由于这些方法是抽象方法&#xff0c;所以只查看父类的代码是无法知道这些方法最终会进行何种具体处理的。唯一能知道的就是父类如何调用这些方法。 类图 说明 AbstractClass&#xff08;抽象类&#xff09; Abs…

JAVAEE初阶 多线程基础(四)

join的知识补充,线程的状态和线程安全 一.多线程完成运算操作二.多线程代码的变换2.1 转换成串行执行 三.join的参数四.获取线程的引用4.1用this方法获取实例4.2 用currentThread获取实例 五.线程的状态六.线程安全 一.多线程完成运算操作 可以发现,多线程并行比单线程的速度快…

Java核心知识点整理大全19-笔记

目录 14.1.5.2. MemStore 刷盘 全局内存控制 MemStore 达到上限 RegionServer 的 Hlog 数量达到上限 手工触发 关闭 RegionServer 触发 Region 使用 HLOG 恢复完数据后触发 14.1.6.HBase vs Cassandra 15. MongoDB 15.1.1. 概念 15.1.2. 特点 16. Cassandra 16.1.1…

MySQL-02-InnoDB存储引擎

实际的业务系统开发中&#xff0c;使用MySQL数据库&#xff0c;我们使用最多的当然是支持事务并发的InnoDB存储引擎的这种表结构&#xff0c;下面我们介绍下InnoDB存储引擎相关的知识点。 1-Innodb体系架构 InnoDB存储引擎有多个内存块&#xff0c;可以认为这些内存块组成了一…