Containerd Container管理功能解析

news2024/11/23 21:04:51

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.coloradmin.cn/o/1258888.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

01 项目架构

关于我 曾经就职于蚂蚁金服,多年的后端开发经验,对微服务、架构这块研究颇深,同时也是一名热衷于技术分享、拥抱开源技术的博主。 个人技术公众号:码猿技术专栏个人博客:www.java-family.cn 前期一直在更新《Spring…

什么是美颜sdk?视频直播美颜sdk技术深度剖析

美颜sdk可以通过实时处理图像,提升主播或用户在视频直播中的外观。通过美颜sdk接口调用可以轻松实现美颜效果。美颜sdk的核心目标是在保持图像真实性的同时,为用户创造出最理想的美化效果。 一、美颜sdk的技术实现 1.面部识别技术:美颜sdk…

虚拟直播在文旅行业火爆发展!背后的“生意经”你抓住了吗?

自疫情结束以来,文化和旅游行业恢复势头强劲,各大旅游景点消费活跃度持续攀升。在这种情况下,“直播文旅”模式的深度融合对文旅行业的客流导入起到了极大的带动作用。 不过,当前的文旅直播也出现了一些问题&#xf…

流媒体播放器EasyPlayer播放H.265与H.264时进度条样式异常该如何解决?

H5无插件流媒体播放器EasyPlayer属于一款高效、精炼、稳定且免费的流媒体播放器,可支持多种流媒体协议播放,可支持H.264与H.265编码格式,性能稳定、播放流畅,能支持WebSocket-FLV、HTTP-FLV,HLS(m3u8&#…

北京 | 竹云与南方电网携手荣获“IDC 2023未来企业奖未来连接领导者”

11月22日-23日,2023第八届IDC中国数字化转型年度盛典在北京召开。本次大会以“竞放数字力量”为主题,汇聚超过1000位来自不同行业的大咖与伙伴共同参与此次盛会,从全球化视角出发,围绕本土化落地人工智能(大模型&#…

pytest系列——pytest_collection_modifyitems钩子函数修改测试用例执行顺序

前言 pytest默认执行用例是根据项目下的文件名称按ascii码去收集运行的;文件中的用例是从上往下按顺序执行的。 pytest_collection_modifyitems 这个函数顾名思义就是收集测试用例、改变用例的执行顺序的。 【严格意义上来说,我们在用例设计原则上用例…

NFTScan | 11.20~11.26 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期:2023.11.20~ 2023.11.26 NFT Hot News 01/ OKX Ordinals 市场 API 完成升级 11 月 21 日,OKX Ordinals 市场 API 现已完成升级,新增支持按币种单价查询、排序&…

TDA4VM EVM开发板调试笔记

文章目录 1. 前言2. 官网资料导读3. 安装 Linux SDK4. 制作SD 启动卡5. 验证启动1. 前言 TDA4作为一般经典的车规级SOC芯片,基于它的低阶智驾方案目前成为各家智驾方案公司的量产首选,这也使得基于TDA4的开发需求陡增,开发和使用TDA4既要熟悉Linux驱应用开发,还要熟悉传统…

uniapp基础-教程之HBuilderX配置篇-01

uniapp教程之HBuilderX配置篇-01 为什么要做这个教程的梳理,主要用于自己学习和总结,利于增加自己的积累和记忆。首先下载HBuilderX,并保证你的软件在C盘进行运行,最好使用英文或者拼音,这个操作是为了保证软件的稳定…

瑞芯微RK3588核心板在网络录像机产品中的应用与优势-迅为电子

你是否曾经想过,网络录像机是如何工作的?为什么它可以在没有电脑的情况下进行录像和监控?答案是,它依赖于RK3588核心板。那么,RK3588核心板到底在网络录像机产品中扮演着什么样的角色呢?它又具有哪些优势呢…

力扣:239. 滑动窗口最大值

题目&#xff1a; 给定一个数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回滑动窗口中的最大值。 提示&#xff1a; 1 < nums.length < 10^5-10^4 < n…

使用UI Automation库用于UI自动化测试

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

NX二次开发UF_CURVE_ask_spline_data 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_ask_spline_data Defined in: uf_curve.h int UF_CURVE_ask_spline_data(tag_t spline_tag, UF_CURVE_spline_p_t spline_data ) overview 概述 Reads the spline data a…

FreeRTOS-任务管理

目录 任务管理 创建任务 创建任务示例1&#xff1a;创建两个同等级的任务 创建任务示例2&#xff1a;使用任务参数 删除任务 删除任务示例&#xff1a;删除任务 挂起任务 任务优先级 优先级实验&#xff1a;修改优先级 Tick 延时函数 延时示例 空闲任务 钩子函数…

创意二维码案例:意大利艺术家的最新二维码艺术展!

意大利艺术家——米开朗基罗皮斯特莱托&#xff08;Michelangelo Pistoletto&#xff09;的个人艺术展“二维码‘说’”&#xff08;QR CODE POSSESSION&#xff09;正在北京798艺术区的常青艺术画廊展出&#xff0c;这是一次别出心裁的创意艺术展&#xff01; 主要体现在3个方…

案例036:基于微信小程序的在线课堂系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

2019年3月28日 Go生态洞察:Go 2018调查结果分析

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

数据结构与算法编程题29

先序遍历二叉树&#xff08;非递归&#xff09;栈 #define _CRT_SECURE_NO_WARNINGS#include <iostream> using namespace std;typedef char ElemType; #define ERROR 0 #define OK 1 #define Maxsize 100 #define STR_SIZE 1024typedef struct BiTNode {ElemType data;B…

BetaFlight模块设计之三十七:SBUS

BetaFlight模块设计之三十七&#xff1a;SBUS 1. 源由2. sbus启动&动态任务3. 主要函数3.1 sbus初始化3.2 sbusFrameStatus更新3.3 rxFrameTimeUs3.4 sbusDataReceive接收数据 4. 辅助函数4.1 sbusChannelsDecode 5. 参考资料 1. 源由 接着BetaFlight模块设计之三十六&…

西北大学计算机844考研-23年计网计算题详细解析

西北大学计算机844考研-23年计网计算题详细解析 1.计算无传输差错状态下停止—等待ARQ协议效率,电磁波传播速率为2*10^8m/s&#xff0c;链路长为2000m&#xff0c;帧长度为1000比特&#xff0c;计算传输速率10kbps及10Mbps时的协议效率&#xff08;即信道利用率&#xff09; …