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// 获取镜像信息 grpci, 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, 创建containerr, 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独享一个bucketnamespace, err := namespaces.NamespaceRequired(ctx)if err != nil {return images.Image{}, err}if err := view(ctx, s.db, func(tx *bolt.Tx) error {// 使用namespce,获取镜像所在的bucketbkt := 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 = nameif 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 []stringif 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) {// 获取namespacenamespace, err := namespaces.NamespaceRequired(ctx)if err != nil {return containers.Container{}, err}// 验证containerif 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对应的bucketbkt, err := createContainersBucket(tx, namespace)if err != nil {return err}// 创建container对应的bucketcbkt, 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.CreatedAtif 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.Containerif 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.hqwc.cn/news/235569.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Vulnhub 靶场】【DriftingBlues: 9 (final)】【简单】【20210509】

1、环境介绍 靶场介绍:https://www.vulnhub.com/entry/driftingblues-9-final,695/ 靶场下载:https://download.vulnhub.com/driftingblues/driftingblues9.ova 靶场难度:简单 发布日期:2021年05月09日 文件大小:738 …

数字图像处理(实践篇)一 将图像中的指定目标用bBox框起来吧!

目录 一 实现方法 二 涉及的OpenCV函数 三 代码 四 效果图 一 实现方法 ①利用OTSU方法将前景与背景分割。 ②使用连通区域分析可以将具有相同像素值且位置相邻的前景像素点组成的图像区域识别。 ③画bbox。 ④显示结果。 二 涉及的OpenCV函数 ① OpenCV提供了cv2.th…

MySQL组合索引,最左匹配原则失效

说明:在SQL优化时,建立组合索引,我们需要知道最左匹配失效的情况,本文通过实例介绍最左匹配原则失效; 建立组合索引 如下,是一张大表,有1000万条数据; 对表中password、sex和email…

FFmpeg架构全面分析

一、简介 它的官网为:https://ffmpeg.org/,由Fabrice Bellard(法国著名程序员Born in 1972)于2000年发起创建的开源项目。该人是个牛人,在很多领域都有很大的贡献。 FFmpeg是多媒体领域的万能工具。只要涉及音视频领…

virtualbox安装时报错:无法访问你试图使用的功能所在的网络位位置。旧版本的msi文件误删问题解决。

错误如下图所示: 无法访问你试图使用的功能所在的网络位位置。单击“确定”重试,或在下面的框中输入包含安装程序包“xxxxxxxxxxxxx.msi”的文件夹路径。 今天在帮同学安装时发生这个问题,然后各种网站搜索教程,最后也是花了将近…

【EI会议征稿通知】第七届结构工程与工业建筑国际学术会议(ICSEIA 2024)

第七届结构工程与工业建筑国际学术会议(ICSEIA 2024) 2024 7th International Conference on Structural Engineering and Industrial Architecture 随着城市化进程的不断深入,建筑领域的需求也在优化、调整。结构工程的发展依旧受到重视&am…

visionOS空间计算实战开发教程Day 10 照片墙

本例选择了《天空之城》的25张照片,组成5x5的照片墙)。首先我们在setupContentEntity方法中构建了一个纹理数组,将这25张照片添加到数组images中。其中封装了setup方法,借助于visionOS对沉浸式空间的支持,我们创建了三…

【玩转 EdgeOne】| 腾讯云下一代边缘加速CDN EdgeOne 是安全加速界的未来吗?

目录 前言边缘加速与安全加固边缘计算与CDN的融合EdgeOne优秀的安全特性EdgeOne卓越的性能表现灵活的配置和管理生态系统的支持与发展技术创新与未来展望EdgeOne试用结束语 前言 在当下互联网的迅猛发展的时刻,云计算和边缘计算技术的快速发展为网络加速领域带来了…

python中的条件用语

文章目录 ifelse语句elif语句条件嵌套range函数while循环for循环辗转相除法break语句continue语句循环中的else语句 if else语句 elif语句 条件嵌套 range函数 注: 1.不包含end的值 while循环 for循环 注: 在Python中,print(i, end)的意思是…

获取焦点后,样式异常的处理方法

问题 在使用monaco-editor 设置代码提示未正常显示,提示框出现,看不到内容,如图 看不到内容,有两种情况: 情况一:没有得到数据,所以没有展示; 情况二:得到了数据&#x…

收藏!7个小众宝藏的开发者学习类网站

1、simplilearn 地址:https://www.simplilearn.com/ simplilearn是全球排名第一的在线学习网站,它的课程由世界知名大学、顶级企业和领先的行业机构通过实时在线课程设计和提供,其中包括顶级行业从业者、广受欢迎的培训师和全球领导者。 2、…

hutool的bug之 DateUtil.endOfDay(DateUtil.date())

hutool 工具类DateUtil 使用时谨慎 DateUtil.endOfDay 得到的时间保存到数据时会增加一秒 首先比较下时间的long值: 这样就很明显的看出来,hutool工具类的date是毫秒位多了.999,保存到mysql 的时候,MySQL数据库对于毫秒大于500的数据进行…