总结:系列解读系列解读系列解读系列解读系列解读系列解读系列解读系列解读网络模式解读主要是为了隔离,主要是为了资源限制。 联合文件主要用于图像的分层存储和管理。 这是运行时并遵循接口。 一般来说,是基于。 冻结挂起的进程。 配置时间在文件名中以微秒表示。
简介
主要从命名空间、cgroup、协作文件、运行时(runC)和网络方面了解docker。 接下来,我将花时间分别介绍一下他们。
Docker系列--命名空间解读
docker系列--cgroups解读
Docker系列--] unionfs解读
docker系列--runC解读
docker系列--网络模式解读
namesapce主要用于隔离,cgroups主要用于资源限制,联合文件主要用于镜像的分层存储和管理。 runC 是一个遵循 oci 接口的运行时,通常基于 libcontainer。 组网主要是docker独立组网和多主机通信方式。
Cgroup概述什么是cgroup?
Cgroup是对照组的缩写。 Linux 内核提供的一项功能。 它用于限制和隔离进程组对系统资源的使用,即执行资源 QoS。 这些资源主要包括CPU、内存、块I/O和网络带宽。 Cgroups 于 2.6.24 加入内核主线。 现在所有主要发行版都默认启用 Cgroup 功能。
cgroups 提供四个主要功能:
资源限制:cgroups可以限制进程组使用的总资源。 例如,如果您在应用程序运行时设置内存使用限制,超过此分配将导致发出 OOM(内存不足)。
优先级分配(优先级):这实际上相当于通过分配CPU时间片的数量和硬盘IO带宽的大小来控制进程的优先级。
资源统计(记账):cgroups可以统计系统资源使用情况,如CPU时间、内存使用情况。 这个功能非常适合计费。
进程控制:cgroup可以执行挂起和恢复进程组等操作。
Cgroup 的三个组成部分
cgroup 控制组。 cgroup 是一种管理进程组的机制。 一个cgroup包含一组进程,可以将各种Linux子系统参数的配置添加到该cgroup中,将一组进程与一组子系统系统参数关联起来。
子系统子系统。 子系统是一组由资源控制的模块。 下面我们将详细介绍。
hierarchy 层次结构树。 层次结构的功能是将 cgroup 组连接成树状结构。 这样的树就是一个层次结构。 通过这种树状结构,Cgroups可以被继承。 例如,在我的系统上,我限制了通过cgroup1调度的一组任务进程的CPU使用率,并且其中一个调度转储日志的进程也需要限制磁盘IO。 为了保证限制不会影响其他进程,创建继承cgroup1的cgroup2来限制磁盘IO。 这允许 cgroup2 继承 cgroup1 的 CPU 限制并增加磁盘 IO 限制,而不影响 cgroup1 中的其他进程。
cgroups子系统
cgroups中实现的子系统及其功能有:
devices:设备权限控制。
cpuset:分配指定的CPU和内存节点。
cpu:控制CPU使用率。
cpuacct:CPU 使用情况统计信息。
内存:在限制范围内存储使用限制。
Freezer:冻结(挂起)Cgroup 内的进程。
net_cls:与tc(流量控制器)配合使用,限制网络带宽。
net_prio:设置进程的网络流量优先级。
huge_tlb:限制HugeTLB的使用。
perf_event:允许Perf工具基于Cgroup组进行性能监控。
每个子系统目录都包含更详细的配置项。 例如
cpu
控制CPU资源的策略也有两种。 一是完全公平调度器(CFS)策略,它提供了两种资源控制方法:配额和比例分配。 另一种是实时调度(Real-Time Scheduler)策略,它为实时进程分配固定的周期基础。 的执行时间。 配置时间以微秒 (μs) 为单位,并由文件名表示。
cpuset CPU Binding:
除了限制 CPU 使用之外,cgroup 还将任务绑定到特定的 CPU,以便它们可以只运行在CPU上。 在 CPU 上,这是 cpuset 子资源的函数。 除了CPU之外,内存节点也可以被绑定。
在将任务添加到cpuset任务文件之前,用户必须设置cpuset.cpus和cpuset.mems参数。
cpuset.cpus:设置cgroup中任务可用的CPU。 格式是逗号 (,) 分隔的列表。 减号(-)可以表示一个范围。 例如,0-2,7 代表 CPU 核心 0、1、2、7。
cpuset.mems:设置可用于 cgroup 内任务的内存节点。 格式与cpuset.cpus相同。
内存:
memory.limit_bytes:强制限制最大内存使用量。 有 3 个单位:k、m 和 g。 输入- 1 表示无限制。
memory.soft_limit_bytes:软限制。 仅当它小于所需限制中设置的值时,它才有意义。 填写上面的格式。 当总体内存较低时,任务获取的内存会受到软限制,以防止太多进程耗尽内存。 您可以看到,添加内存资源限制并不能消除资源争用。
memory.memsw.limit_bytes:设置最大内存和交换空间内存使用限制。 填写上面的格式。
本节具体介绍了监控和统计相关的参数,包括cadvisor收集的参数。
memory.usage_bytes:报告此cgroup中进程使用的数据。 之前的内存使用总量(以字节为单位)。
memory.max_usage_bytes:报告此cgroup中进程的最大使用量。 内存使用量大。
docker 如何使用 cgroup
创建容器
# 运行一个生成 300 个进程的容器。 docker run cirocosta/stress pid -n 300开始生成 300 个 Block Children[1] 等待 SIGINT# 打开另一个窗口并验证是否有 PIDSdocker statsCONTAINER 300#。MEM USAGE / LIMIT PIDSa730051832 … 21.02MiB / 1.951GiB 300
确保 Docker 已在此容器中放置了一些 cgroup
# 将容器的 ID 设置为 Let's get it。 Docker 使用它。 用于命名主机中事物的 ID,您可以使用此 # 来查找为父 docker 下的容器创建的 cgroup。 cgroupdocker psCONTAINER ID IMAGE COMMAND a730051832e7 cirocosta/stress "pid -n 300" # Include prefix # 系统 cgroup 的挂载点进行搜索。 /sys/fs/cgroup/ -名称“a730051832e7*”/sys/fs/cgroup/cpu,cpuacct/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398 ec660a75a959/sys / fs/cgroup /cpuset/docker/ a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959/sys/fs/cgroup/devices /docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959/sys/fs/cgroup/pids/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a3056 9 398ec660a75a959/sys/fs/cgroup/freezer/docker/a730051832e7d776442b2e969e05766 0ad108a7d6e6a30569398ec660a75a959/sys/fs/cgroup/perf_event/docker/a730051832 e 7d776 442b2e969e057660ad108a7d6e6a30569398ec660a75a959/sys/fs/cgroup/b lkio /docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660 a75a959/sys/fs/cgroup/内存/docker/a730051832e7d776442b2e969e057660ad108a7d 6 30569398ec660a75a959/sys/fs/cgroup/net_cls,net_prio/docker/a730051832e7d776442b 2e969e057660ad108a7d6e6a30569398ec660a75a959/sys/fs/cgroup/hugetlb/docker /a730 051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959 /系统/ fs/cgroup/systemd/docker/a730051832e7d776442b2e969e057660ad108a7d6e6a30569398ec660a75a959# 是的!Docker 创建了一个名为 # 的控制组,其中包含所有子系统下容器的确切 ID。 # 从这个检查中我们可以发现什么? # 我们可以看到设置约束的子系统(PID)。 例如,树 /sys/fs/cgroup/pids/docker/a7300518327d.../sys/fs/cgroup/pids/docker/a73005183。 .. §── cgroup.clone_children §── cgroup.procs §── Notice_on_release §── pids.current §── pids.events§── pids.max└── Tasks# 所以,如果你想知道,目前使用 # 现在您可以检查“pids.current”以了解限制,并检查“pids.max”以了解 # 哪些进程被分配给该控制组 您可以 # 确认该任务。 让我们尝试一下: cat /sys/fs/cgroup/pids/docker/ a730...c660a75a959/tasks 53295371537253735374537553765377(...)# 该容器有 300 个进程,因此会一直持续到第 300 个条目 -# 300 pidscat /sys/fs/cgroup/pids/docker/a730051832e7d7764...9/pids.current300# no max setcat / sys/fs/cgroup/pids/docker/a730051832e7d77.../pids.max max
PS
通常,在k8s安装过程中经常会出现以下错误: :
create kubelet: Misconfiguration: kubelet cgroup driver: "cgroupfs" is different from docker cgroup driver: "systemd"
其实这里的错误信息很明确。 与docker kubelet中指定的cgroup驱动不同。 docker
支持两种驱动模式:systemd 和 cgroupfs。 runc 代码更直观。
Cgroups只能限制CPU使用率,但不能保证CPU使用率。 换句话说,cpuset-cpus 允许您在指定的 CPU 或核心上运行容器,但不保证这些 CPU 的独占占用。 cpu-shares
是一个相对值,只有在 CPU 不足的情况下才能使用。 只有当你使用它时它才起作用。 换句话说,如果有足够的CPU,就会给每个容器足够的CPU。 如果不够,多个容器会按照指定的百分比分配CPU。
说到内存,cgroups可以限制容器使用的最大内存。 使用 -m 参数设置可以使用的最大内存量。
解释代码
单击以了解有关 runc 的 cgroups 代码部分的更多信息。 这里仅提供概述。
首先,容器的创建是通过工厂调用create方法来实现的,而与cgroups相关,工厂根据配置文件cgroup驱动驱动的配置项,调用创建新的CgroupsManager的方法实现。它。 有两种实现方法:systemd 和 cgroupfs:
// SystemdCgroups 是一个可选函数,它配置 LinuxFactory // 返回一个使用 systemd 创建和管理 cgroups.func 的容器。 SystemdCgroups(l *LinuxFactory) error { l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager { return &systemd.Manager{ Cgroups: config, Paths: paths, } } return nil}/ / Cgroupfs 是一个可选函数 // 配置 LinuxFactory 以返回使用本机 cgroups 文件系统实现创建和管理的容器 // cgroups.func Cgroupfs(l *LinuxFactory) error { l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager { return &fs.Manager{ Cgroups: config, Paths: paths, } } return nil}
抽象 cgroup Manager 接口。 界面是:
type Manager Interface { // 将cgroup设置应用到指定pid的进程。 Apply(pid int) error // 返回cgroup集合中的PID GetPids() ([] int, error ) // 返回cgroup集合和所有子-cgroup中的PID GetAllPids() ([] int, error ) // 返回 cgroup set 的统计信息 GetStats() (*Stats, error) // Toggle 根据指定的状态设置冻结器 cgroup。 Freeze(state configs.FreezerState) error // 销毁 cgroup。 set Destroy() error // 选项 func SystemdCgroups() 和 Cgroupfs() 需要以下属性: // Path map[string]string // Cgroups *configs.Cgroup // Path 将 cgroup 子系统映射到其挂载路径。 // Cgroups 为各个子系统指定特定的 cgroup 设置。 // 返回 cgroup 路径,以便将其保存到状态文件并能够稍后恢复该对象。 GetPaths() map[string]string // 按照配置设置 cgroup。 Set(container *configs.Config) error}
在创建容器的过程中,调用了上述接口的方法。 示例:
在container_linux.go中,
func (c *linuxContainer) Set(config configs.Config) error { c.m.Lock() defer c.m.Unlock() status, err := c.currentStatus ( ) if err != nil { return err } ... if err := c.cgroupManager.Set(&config); err != nil { // 设置configs back if err2 := c.cgroupManager.Set(c.config); err2 != nil { logrus.Warnf("Error: Could not revert cgroup configuration due to %v.state.json 与实际配置可能不一致。 ", err2) } return err }...}
接下来我们重点关注 fs 的实现。
在fs中,每个子系统本质上都是一个文件,如上图所示。
看看内存子系统 memory.go。 其他子系统也是如此。
关键方法:
func (s *MemoryGroup) apply(d *cgroupData) (err error) { path, err := d.path("memory") if err != nil && ! cgroups.IsNotFound(err) { return err } else if path == "" { return nil } ifmemoryAssigned(d.config) { if _, err := os.Stat(path) { if err; := os.MkdirAll(path, 0755); err != nil { return err } // 仅启用内核备忘录// 考虑这个 cgroup 是何时由 libcontainer 创建的。 否则,如果用户使用 `cgroupsPath` 加入内核内存尚未初始化的现有 cgroup,则可能会发生错误。 如果错误:= EnableKernelMemoryAccounting(路径);哈! = nil { return err } } } defer func() { if err != nil { os.RemoveAll(path) } }() // 设置内存限制后,我们需要加入内存 cgroup。 设置 cgroup 是否为空。 _, err = d.join("memory") if err != nil && !cgroups.IsNotFound(err) { return err } return nil}
通过 d.path("memory") 查找内存cgroup 的路径
func (raw *cgroupData) 路径(子系统字符串) (字符串,错误){ mnt, err := cgroups.FindCgroupMountpoint(subsystem) // 如果不挂载子系统,则创建路径没有意义。 if err != nil { return "", err } // 对于 cgroup 名称,/path 是绝对的,而不是相对于 init 进程的 cgroup。 if filepath.IsAbs(raw.innerPath) { // 子系统可以作为“cpu,cpuacct”一起安装。 return filepath.Join(raw.root, filepath.Base(mnt), raw.innerPath), nil } // 使用 GetOwnCgroupPath 而不是 GetInitCgroupPath。 这是因为正在创建的进程 // 可能驻留在与主机共享 pid 命名空间的容器内,并且 // /proc/1/cgroup 可能指向: 一个完全不同的 cgroup 世界。 ParentPath, err := cgroups.GetOwnCgroupPath(subsystem) if err != nil { return "", err } return filepath.Join(parentPath, raw.innerPath), nil}
d.join("内存"),将pid写入内存路径
func (raw *cgroupData) join(子系统 string) (string, error) { path, err := raw.path(subsystem) if err != nil { return "", err } if err := os.MkdirAll(path, err != nil { return "", err } if err := cgroups.WriteCgroupProc(path, raw.pid); err != nil { return "", err } 返回路径, nil}
评论前必须登录!
注册