16 容器运行注意事项

0.1. 问题描述

在Go语言中,Go scheduler的P数量非常重要,因为它会极大地影响Go在运行时的表现。在目前的Go语言中,P的数量默认是系统的CPU核数

在容器化的环境中,Go程序所获取的CPU核数是错误的,它所获取的是宿主机的CPU核数。

即使容器和宿主机的CPU核数是共享的,但在集群中会针对每个Pod分配指定的核数,因此实际上需要的是Pod的核数,而不是宿主机的CPU核数。

0.2. 会造成什么后果

Go运行时调度模型,要求M必须与P进行绑定,然后才能不断地在M上循环寻找可运行的G来执行相应的任务。

注意,M必须与P进行绑定,其绑定的这个P,要求必须是空闲状态。

在容器化的部署环境中,P的数量由于被“错误”设置,因此拥有大量空闲的P。可以这样理解,只要有足够多的M,那么P就可以都被绑定。

这时又产生了另外一个问题,M的数量是会不断增加。在程序运行过程中,由于产生了网络I/O阻塞,导致M会随着程序的不断执行而不断增加。最终导致Go程序的延迟加大,程序响应缓慢。

0.3. 解决方法

产生这个问题的本质原因是Go程序没有正确地获得所期望的CPU核数(应当获取具体分配给Pod的配额),因此解决方案有两种:

  1. 结合部署情况,主动设置正确的GOMAXPROCS核数
  2. 通过cgroup信息,读取容器内的正确GOMAXPROCS核数

目前,Go尚没有非常完美的办法来解决这个问题,因此这里推荐使用Uber公司推出的uber-go/maxprocs开源库,它会在Go程序运行时根据cgroup的挂载信息来修改GOMAXPROCS核数,并基于一定规则选择一个最合适的数值。

使用方式如下:

package automaxprocs_test

// Importing automaxprocs automatically adjusts GOMAXPROCS.
import _ "go.uber.org/automaxprocs"

// To render a whole-file example, we need a package-level declaration.
var _ = ""

func Example() {}

只需在Go程序启动时进行引用即可,如果有特殊的需求,那么主动设置GOMAXPROCS也是可以的。

上次修改: 15 July 2020