- Created by 郭强, last modified on Apr 09, 2024
You are viewing an old version of this page. View the current version.
Compare with Current View Page History
« Previous Version 5 Next »
基本介绍
众所周知,GOMAXPROCS
是 Golang
提供的非常重要的一个环境变量设定。通过设定 GOMAXPROCS
,用户可以调整 Runtime Scheduler
中 Processor
(简称P
)的数量。由于每个系统线程,必须要绑定 P
才能真正地进行执行。所以 P
的数量会很大程度上影响 Golang Runtime
的并发表现。GOMAXPROCS
的默认值是 CPU
核数,而以 Docker
为代表的容器虚拟化技术,会通过 cgroup
等技术对 CPU
资源进行隔离。以 Kubernetes
为代表的基于容器虚拟化实现的资源管理系统,也支持这样的特性。这类技术对 CPU
的隔离限制,会影响到 Golang
中的 GOMAXPROCS
,进而影响到 Golang Runtime
的并发表现。
更多关于调度器的介绍请参考:Goroutine调度器
理论上来讲,过多的调度器P
设置以及过少的CPU
资源限制,将会增大调度器内部的上下文切换以及资源竞争成本。如何优雅地让Golang
程序资源设置与外部cgroup
资源限制匹配是本文讨论的主题。
Golang资源设置
Golang
引擎已经在运行时提供了两个比较重要的资源设置参数,通过环境变量来设置/改变:
GOMAXPROCS
:设置调度器使用的Processor
数量,进而控制并发数量。GOMEMLIMIT
:设置进程可用的最大内存大小,进而影响GC
的表现。在Go 1.19
版本后支持。需要注意,如果是在内存泄漏的程序中,该设置会延迟OOM
的出现,但不会阻止OOM
的出现。
测试过程
测试环境
使用的Kubernetes
集群,每台Node
节点32
核128G
内存。
我们通过给容器设置环境变量,来实现对容器中Golang
程的资源设置:
env: - name: GOMEMLIMIT valueFrom: resourceFieldRef: resource: limits.memory - name: GOMAXPROCS valueFrom: resourceFieldRef: resource: limits.cpu
不限制资源
不传递环境变量
我们先来看看不设置资源limits
也不传递环境变量的时候Golang
程序使用的资源情况:
apiVersion: v1 kind: Pod metadata: name: gomaxprocs-test spec: containers: - name: main image: golang:1.22 imagePullPolicy: IfNotPresent args: - bash - -c - | cat <<EOF > /tmp/code.go package main import ( "fmt" "os" "runtime" "runtime/debug" ) func main() { fmt.Printf("GOMAXPROCS(env): %s\n", os.Getenv("GOMAXPROCS")) fmt.Printf("GOMEMLIMIT(env): %s\n", os.Getenv("GOMEMLIMIT")) fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0)) fmt.Printf("GOMEMLIMIT: %d\n", debug.SetMemoryLimit(-1)) } EOF go run /tmp/code.go
运行后,我们查看终端输出结果:
$ kubectl logs pod/gomaxprocs-test GOMAXPROCS(env): GOMEMLIMIT(env): GOMAXPROCS: 32 GOMEMLIMIT: 9223372036854775807
可以看到,Golang
程序使用的是当前运行Node
的CPU
并且内存没有任何的限制。 其中默认的内存值9223372036854775807
表示int64
类型的最大值。
传递环境变量
我们再来看看不设置资源limits
但传递环境变量的时候Golang
程序使用的资源情况:
apiVersion: v1 kind: Pod metadata: name: gomaxprocs-test spec: containers: - name: main image: golang:1.22 imagePullPolicy: IfNotPresent args: - bash - -c - | cat <<EOF > /tmp/code.go package main import ( "fmt" "os" "runtime" "runtime/debug" ) func main() { fmt.Printf("GOMAXPROCS(env): %s\n", os.Getenv("GOMAXPROCS")) fmt.Printf("GOMEMLIMIT(env): %s\n", os.Getenv("GOMEMLIMIT")) fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0)) fmt.Printf("GOMEMLIMIT: %d\n", debug.SetMemoryLimit(-1)) } EOF go run /tmp/code.go resources: env: - name: GOMEMLIMIT valueFrom: resourceFieldRef: resource: limits.memory - name: GOMAXPROCS valueFrom: resourceFieldRef: resource: limits.cpu
运行后,我们查看终端输出结果:
$ kubectl logs pod/gomaxprocs-test GOMAXPROCS(env): 32 GOMEMLIMIT(env): 127719542784 GOMAXPROCS: 32 GOMEMLIMIT: 127719542784
可以看到,Golang
程序使用的是当前运行Node
的CPU
并且内存没有任何的限制。在没有设置资源limits
的情况下,环境变量中的limits.cpu
及limits.memory
传递的是当前Node
节点的CPU
和内存值。
设置资源限制
不传递环境变量
如果只设置资源limits
但是不传递环境变量会怎么样呢?我们来试试看。
apiVersion: v1 kind: Pod metadata: name: gomaxprocs-test spec: containers: - name: main image: golang:1.22 imagePullPolicy: IfNotPresent args: - bash - -c - | cat <<EOF > /tmp/code.go package main import ( "fmt" "os" "runtime" "runtime/debug" ) func main() { fmt.Printf("GOMAXPROCS(env): %s\n", os.Getenv("GOMAXPROCS")) fmt.Printf("GOMEMLIMIT(env): %s\n", os.Getenv("GOMEMLIMIT")) fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0)) fmt.Printf("GOMEMLIMIT: %d\n", debug.SetMemoryLimit(-1)) } EOF go run /tmp/code.go resources: limits: cpu: 200m memory: 512M requests: cpu: 100m memory: 128M
运行后,我们查看终端输出结果:
$ kubectl logs pod/gomaxprocs-test GOMAXPROCS(env): GOMEMLIMIT(env): GOMAXPROCS: 32 GOMEMLIMIT: 9223372036854775807
可以看到,同上面没有设置资源限制一样,Golang
程序仍然使用的是默认资源设置,并不能感知cgroup
的资源限制。
传递环境变量
我们仍然需要环境变量来通知容器中的Golang
程序cgroup
资源的限制。
apiVersion: v1 kind: Pod metadata: name: gomaxprocs-test spec: containers: - name: main image: golang:1.22 imagePullPolicy: IfNotPresent args: - bash - -c - | cat <<EOF > /tmp/code.go package main import ( "fmt" "os" "runtime" "runtime/debug" ) func main() { fmt.Printf("GOMAXPROCS(env): %s\n", os.Getenv("GOMAXPROCS")) fmt.Printf("GOMEMLIMIT(env): %s\n", os.Getenv("GOMEMLIMIT")) fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0)) fmt.Printf("GOMEMLIMIT: %d\n", debug.SetMemoryLimit(-1)) } EOF go run /tmp/code.go resources: limits: cpu: 200m memory: 512M requests: cpu: 100m memory: 128M env: - name: GOMEMLIMIT valueFrom: resourceFieldRef: resource: limits.memory - name: GOMAXPROCS valueFrom: resourceFieldRef: resource: limits.cpu
运行后,我们查看终端输出结果:
$ kubectl logs pod/gomaxprocs-test GOMAXPROCS(env): 1 GOMEMLIMIT(env): 512000000 GOMAXPROCS: 1 GOMEMLIMIT: 512000000
可以看到,Golang
程序的CPU
及内存受限制给定的limits
限制。并且CPU
的限制使用的是limits
中CPU
核心数的向上取整设置,例如给定的CPU limits
为100m
时,容器Golang
程序获取到的GOMAXPROCS
变量是1
。
一些总结
容器中的Golang
程序并不能动态感知外部cgroup
的资源限制,需要通过环境变量的方式来同步外部cgroup
对资源的限制到容器中的Golang
程序中。此外,也有一些第三方开源组件可以使得Golang
程序动态感知cgroup
的资源限制:https://github.com/uber-go/automaxprocs 其原理也是通过程序动态读取cgroup
配置来实现的。
参考资料
- Goroutine调度器
- https://gaocegege.com/Blog/maxprocs-cpu
- https://blog.howardjohn.info/posts/gomaxprocs/
- https://www.ardanlabs.com/blog/2024/02/kubernetes-cpu-limits-go.html
- No labels