惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
Martin Fowler
Martin Fowler
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
C
Cisco Blogs
Last Week in AI
Last Week in AI
T
The Blog of Author Tim Ferriss
The GitHub Blog
The GitHub Blog
T
Tenable Blog
A
Arctic Wolf
小众软件
小众软件
Google DeepMind News
Google DeepMind News
aimingoo的专栏
aimingoo的专栏
PCI Perspectives
PCI Perspectives
博客园 - 司徒正美
The Last Watchdog
The Last Watchdog
H
Hacker News: Front Page
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Stack Overflow Blog
Stack Overflow Blog
N
News and Events Feed by Topic
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 【当耐特】
S
Security @ Cisco Blogs
P
Proofpoint News Feed
Cloudbric
Cloudbric
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Jina AI
Jina AI
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
月光博客
月光博客
Schneier on Security
Schneier on Security
Hacker News: Ask HN
Hacker News: Ask HN
V
Visual Studio Blog
D
DataBreaches.Net
H
Help Net Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Project Zero
Project Zero
阮一峰的网络日志
阮一峰的网络日志
Cyberwarzone
Cyberwarzone
博客园 - Franky
Y
Y Combinator Blog
Spread Privacy
Spread Privacy
N
News and Events Feed by Topic
The Cloudflare Blog
Simon Willison's Weblog
Simon Willison's Weblog
S
SegmentFault 最新的问题
W
WeLiveSecurity
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
I
Intezer
Hugging Face - Blog
Hugging Face - Blog
Attack and Defense Labs
Attack and Defense Labs

蛮荆

如何获取更多的免费服务器 Kubernetes 调度器队列 - 设计与实现 Kubernetes 调度器 - 核心流程 Kubernetes Networking Model & CNI Kubernetes 控制器管理总结 Kubernetes CronJob 设计与实现 Kubernetes Job 设计与实现 Kubernetes HPA 设计与实现 Kubernetes Deployment 滚动更新实现原理 Kubernetes GC 设计与实现 Kubernetes Pod 驱逐 - 设计与实现 Kubernetes Daemonset 设计与实现 Kubernetes ReplicaSet 设计与实现 Kubernetes EndPoint 设计与实现 Kubernetes Informer 设计与实现 降本增效之应用优化 (三) 日志存储与检索 Kubernetes Pod 设计与实现 - 创建流程 Kubernetes 探针设计与实现 Unix 编程艺术名句摘录 Kubernetes - CRI 概述 Golang 编译速度为什么这么快? Kubernetes Pod 设计与实现 - Pause 容器 Kubernetes - kube-proxy 代理模式工程优化 Kubernetes 应用最佳实践 - 优雅关闭长连接 Kubernetes Service 类型和会话亲和性 Kubernetes 为什么需要 Ingress Kubernetes 架构 - 控制平面和数据平面 降本增效之应用优化 (二) 大报表 Go 语言如何获取 CPU 利用率 降本增效之应用优化 (一) Redis 业务规则引擎演变过程简述 微服务中的熔断算法 漏桶算法和令牌桶算法 jsonparser 为什么比标准库的 encoding/json 快 10 倍 ? zap 高性能设计与实现 HTTP Router 算法演进 fastcache 高性能设计与实现 Web 常见的三个安全问题 ants Code Reading Go 线程安全 map 方案选型 布隆过滤器 死锁、活锁、饥饿、自旋锁 sync.Pool Code Reading Go 内存管理概述 Go netpoll Code Reading goroutine 泄漏与检测 time/Timer Code Reading GMP Scheduler Code Reading Go channel 的 15 条规则和底层实现 为什么 Linux “一切皆文件” context.Context Code Reading runtime/HACKING.md Goland 最佳实践 互联网开发与金庸武学 为什么 Redis 6.0 引入多线程模型? Kubernetes 应用最佳实践 - 金丝雀发布 singleflight Code Reading sync.Map Code Reading sync.Cond Code Reading sync.WaitGroup Code Reading sync.RWMutex Code Reading sync.Mutex Code Reading sync.Once Code Reading Go 无锁编程 sync/atomic Code Reading goroutine 交替打印奇偶数 GODEBUG Go 并发模式 Go 汇编 UUID 通用技术选型 Kubernetes 应用最佳实践 - 水平自动伸缩 Go 高性能 Tips fasthttp 为什么比标准库 net/http 快 10 倍 ? 技术文章配图指南 ChatGPT 初体验 Docker 网络原理概览 iptables 的五表五链 Kubernetes 应用最佳实践 - 亲和性和污点容忍度 Go 的反射与三大定律 Docker 官方提供的最佳实践 Go 语言内置的设计模式 HTTP1 到 HTTP3 的工程优化 Kubernetes 应用最佳实践 - Sidecar 模式 Kubernetes 应用最佳实践 - init 容器和钩子函数 为什么 recover 必须在 defer 中调用? 为什么 defer 的执行顺序和注册顺序不同? Go map 设计与实现 Go 切片扩容底层实现 Go 语言中的零拷贝 Go Delve 云原生和边缘计算简介 Kubernetes Pod 服务质量等级 Kubernetes 应用最佳实践 - 探针 Kubernetes 应用最佳实践 - 资源请求和限制 CDN 原理 Kubernetes 应用最佳实践 - 开篇 缓存策略和模式 Go 内存模型 Kubernetes 核心概念 软件版本语义化
容器中如何正确配置 GOMAXPROCS ?
2023-04-30 · via 蛮荆

2023-04-30 Golang Docker Go 源码分析 读代码

前言

前两天看了一篇文章 GOMAXPROCS 与容器的相处之道,里面提到了 Go 标准库中 runtime.GOMAXPROCS 方法的问题:

因为系统调用 sched_getaffinity 并不感知它对进程的限制,所以运行在 Kubernetes 中的 Go 程序的运行时始终会认为自己可以使用宿主机上的所有 CPU,进而创建了相同数量的 P (处理器), 而当 GOMAXPROCS 被手动地设置为限制后的值后,CPU 密集型场景的性能就得到了很大提高。

Go 标准库在应用层面并没有给出任何解决方案,Uber 公司的技术团队针对这个问题开发并开源了组件 automaxprocs, 对于需要严格限制 P 数量 的业务场景,可以使用该组件在运行时根据容器中 CGroups 的配置动态修改 GOMAXPROCS,避免资源的不合理使用。

内部实现

我们一起探究下 automaxprocs 组件的内部代码实现,笔者选择的版本为 v1.5.1

备注: 如果读者对 CGroups 概念尚不清晰,建议先阅读一下这篇文章 Docker 基础支撑技术概览

示例程序

package main

import (
	_ "go.uber.org/automaxprocs"
	"fmt"
	"runtime"
	"time"
)

func main() {
	for i := 0; i < 10; i++ {
		go func() {
			time.Sleep(time.Millisecond)
		}()
	}

	// 输出 CPU Core 的数量
	// 输出处理器 P 的数量
	fmt.Printf("CPU number = %d, P number = %d\n", runtime.NumCPU(), runtime.GOMAXPROCS(-1))

	time.Sleep(time.Second)
}

宿主机运行

$ go run main.go

# 输出如下
CPU number = 8, P number = 8

笔者的宿主机 CPU 配置为 8 Core,通过输出的信息可以看到处理器的数量并没有被限制。

容器运行

接下来构建一个配置为 –cpus=2 的容器运行上面的代码:

$ go run main.go

# 输出如下
CPU number = 2, P number = 2

通过输出的信息可以看到处理器的数量已经被限制。

接口

automaxprocs 组件同时兼容 CGroupsCGroups2, 通过 queryer 接口来抽象隔离具体的版本实现。

type queryer interface {
	CPUQuota() (float64, bool, error)
}

var (
    _newCgroups2 = cg.NewCGroups2ForCurrentProcess
    _newCgroups  = cg.NewCGroupsForCurrentProcess
)

queryer 接口

CGroups2CGroups 的升级版本,提供了统一的控制系统并增强了资源管理能力,详情可以参考这篇文章 About cgroup v2

# 确认当前系统的 CGroups 版本 

$ stat -fc %T /sys/fs/cgroup/

# CGroups  输出 tmpfs
# CGroups2 输出 cgroup2fs

笔者使用的 Ubuntu 版本为 tmpfs, 所以下面的代码研究以 CGroups 实现为主。

初始化函数

automaxprocs 包内注册了 init 方法,会被其被引用时自动调用。

package automaxprocs

func init() {
	maxprocs.Set(maxprocs.Logger(log.Printf))
}

Set 函数

Set 函数是功能实现的核心代码,它会自动匹配 Linux 容器中的 CPU 资源配额并更改 GOMAXPROCS,对于非 Linux 系统或者没有配置 CPU 资源配置的 Linux 系统, 该方法什么也不做,最后方法返回一个 error 和一个 undo 函数 (用来撤销针对 GOMAXPROCS 所作的修改)。

题外话: 这个返回的 undo 函数小技巧值得学习 (设计模式之状态模式)。

func Set(opts ...Option) (func(), error) {
	cfg := &config{
        // 用来获取 CPU 资源配额的方法
		procs:         iruntime.CPUQuotaToGOMAXPROCS,
		// GOMAXPROCS 最小值为 1
		minGOMAXPROCS: 1,    
	}

	...

	// 默认的 undo 函数
	undoNoop := func() {
        
	}
	
	if max, exists := os.LookupEnv(_maxProcsKey); exists {
        // 如果已经设置了 GOMAXPROCS 环境变量,就以环境变量为准
		// 不做任何操作,直接返回
		return undoNoop, nil
	}

    // 获取 CPU 资源配额
	maxProcs, status, err := cfg.procs(cfg.minGOMAXPROCS)   
    
	...

    // 未设置 CPU 资源配额,直接返回
	if status == iruntime.CPUQuotaUndefined {
		return undoNoop, nil
	}

    // 获取当前 GOMAXPROCS 值
	prev := runtime.GOMAXPROCS(0)
    // undo 函数更新,恢复到未修改前的 GOMAXPROCS 值
	undo := func() {
		runtime.GOMAXPROCS(prev)
	}

    ...

	runtime.GOMAXPROCS(maxProcs)
	
	return undo, nil
}

CPUQuotaToGOMAXPROCS 函数

CPUQuotaToGOMAXPROCS 函数用于获取 Linux 容器中的 CPU 资源配额,具体的实现是由 newQueryer 函数完成的。

func CPUQuotaToGOMAXPROCS(minValue int) (int, CPUQuotaStatus, error) {
	cgroups, err := newQueryer()
    
	...

	quota, defined, err := cgroups.CPUQuota()

	...

	maxProcs := int(math.Floor(quota))
    // 如果 CPU 配额比配置的最小值还要小
    // 就以配置的最小值为准
	if minValue > 0 && maxProcs < minValue {
		return minValue, CPUQuotaMinUsed, nil
	}
	return maxProcs, CPUQuotaUsed, nil
}

newQueryer 函数

newQueryer 函数并没有判断当前系统的 CGroups 版本,而是优先获取 CGroups2 的配置,如果获取不到,再获取 CGroups 的配置。

func newQueryer() (queryer, error) {
	// 优先获取 CGroups2 配置
	cgroups, err := _newCgroups2()
	if err == nil {
		return cgroups, nil
	}
	if errors.Is(err, cg.ErrNotV2) {
        // 其次获取 CGroups 配置
		return _newCgroups()
	}
	return nil, err
}

NewCGroupsForCurrentProcess 函数用来获取 CGroups 版本的配置,具体的实现是 NewCGroups 函数完成的。

func NewCGroupsForCurrentProcess() (CGroups, error) {
	return NewCGroups(_procPathMountInfo, _procPathCGroup)
}

NewCGroups 函数通过读取 /proc/self/mountinfo/proc/self/cgroup 文件获取配置,最后包装成 CGroups 接口对象返回。

// CGroups 接口对象
type CGroups map[string]*CGroup

func NewCGroups(procPathMountInfo, procPathCGroup string) (CGroups, error) {
	...
}

CPUQuota 方法

最后,我们看来看下 CGroups 版本的 CPU 资源配额是如何计算出来的,该功能由 CPUQuota 方法完成。

该方法首先获取 cpu.cfs_quota_uscpu.cfs_period_us 两个字段:

cpu.cfs_quota_us: CPU 周期内最多可使用的时间 cpu.cfs_period_us: CPU 周期时间

例如:如果 cpu.cfs_quota_uscpu.cfs_period_us 的 4 倍,表示允许容器使用 4 个 CPU Core

如果上述两个字段任意一个未设置,方法返回 -1,表示不限制对 CPU 资源的使用,如果两个字段都设置了,使用下面的公式返回资源配额:

cpu.cfs_quota_us / cpu.cfs_period_us

func (cg CGroups) CPUQuota() (float64, bool, error) {
    ...

	cfsQuotaUs, err := cpuCGroup.readInt(_cgroupCPUCFSQuotaUsParam)
    
	...

	cfsPeriodUs, err := cpuCGroup.readInt(_cgroupCPUCFSPeriodUsParam)

	...

	return float64(cfsQuotaUs) / float64(cfsPeriodUs), true, nil
}

小结

uber-go/automaxprocs 组件通过读取运行时环境中的 CGroups 相关配置文件,完成动态修改 GOMAXPROCS 功能。

automaxprocs 调用示意图

Reference

扩展阅读