幾個內(nèi)核參數(shù)引起的 K8s 集群 Java 血案

?本文轉(zhuǎn)自博客園,原文:https://www.cnblogs.com/apink/p/15728381.html,版權(quán)歸原作者所有。歡迎投稿,投稿請?zhí)砑游⑿藕糜眩?strong style="color: rgb(50, 108, 229);">cloud-native-yang
背景說明
運行環(huán)境信息:Kubernetes + docker,應(yīng)用程序:Java
問題描述
1、首先從 Kubernetes 事件中心告警信息如下,該告警集群常規(guī)告警事件(其實從下面這些常規(guī)告警信息是無法判斷是什么故障問題)

2、最初懷疑是 docker 服務(wù)有問題,切換至節(jié)點上查看 docker & kubelet 日志,如下:
?kubelet 無法初始化線程,需要增加所處運行用戶的進程限制,大致意思就是需要調(diào)整 ulimit -u(具體分析如下先描述問題)
$ journalctl -u "kubelet" --no-pager --follow
-- Logs begin at Wed 2019-12-25 11:30:13 CST. --
Dec 22 14:21:51 PROD-BE-K8S-WN8 kubelet[3124]: encoding/js(*decodeState).unmarshal(0xc000204580, 0xcafe00, 0xc00048f440, 0xc0002045a8, 0x0)
Dec 22 14:21:51 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/encoding/json/decode.go:180 +0x1ea
Dec 22 14:21:51 PROD-BE-K8S-WN8 kubelet[3124]: encodijson.Unmarshal(0xc00025e000, 0x9d38, 0xfe00, 0xcafe00, 0xc00048f440, 0x0, 0x0)
Dec 22 14:21:51 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/encoding/json/decode.go:107 +0x112
Dec 22 14:21:51 PROD-BE-K8S-WN8 kubelet[3124]: github.com/go-openaspec.Swagger20Schema(0xc000439680, 0x0, 0x0)
Dec 22 14:21:51 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/vendor/github.com/openapi/spec/spec.go:82 +0xb8
Dec 22 14:21:51 PROD-BE-K8S-WN8 kubelet[3124]: github.com/go-openapi/spec.MustLoadSwagger20Schema(...)
Dec 22 14:21:51 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/vendor/github.com/openapi/spec/spec.go:66
Dec 22 14:21:51 PROD-BE-K8S-WN8 kubelet[3124]: github.com/go-openapi/spec.init.4()
Dec 22 14:21:51 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/vendor/github.com/openapi/spec/spec.go:38 +0x57
Dec 22 14:22:06 PROD-BE-KWN8 kubelet[3124]: runtime: failed to create new OS thread (have 15 already; errno=11)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: runtime: may need to increase max user processes (ulimiu)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: fatal error: newosproc
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: goroutine 1 [running, locked to thread]:
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: runtime.throw(0xcbf07e, 0x9)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/runtipanic.go:1116 +0x72 fp=0xc00099fe20 sp=0xc00099fdf0 pc=0x4376d2
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: runtime.newosproc(0xc000600800)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/runtios_linux.go:161 +0x1c5 fp=0xc00099fe80 sp=0xc00099fe20 pc=0x433be5
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: runtime.newm1(0xc000600800)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/runtiproc.go:1843 +0xdd fp=0xc00099fec0 sp=0xc00099fe80 pc=0x43dcbd
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: runtime.newm(0xcf1010, 0x0, 0xffffffffffffffff)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/runtiproc.go:1822 +0x9b fp=0xc00099fef8 sp=0xc00099fec0 pc=0x43db3b
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: runtime.startTemplateThread()
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/runtiproc.go:1863 +0xb2 fp=0xc00099ff28 sp=0xc00099fef8 pc=0x43ddb2
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: runtime.LockOSThread()
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/runtiproc.go:3845 +0x6b fp=0xc00099ff48 sp=0xc00099ff28 pc=0x44300b
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: main.init.0()
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/plugins/cilium-ccilium-cni.go:66 +0x30 fp=0xc00099ff58 sp=0xc00099ff48 pc=0xb2fa50
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: runtime.doInit(0x11c73a0)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/runtiproc.go:5652 +0x8a fp=0xc00099ff88 sp=0xc00099ff58 pc=0x44720a
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: runtime.main()
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/runtiproc.go:191 +0x1c5 fp=0xc00099ffe0 sp=0xc00099ff88 pc=0x439e85
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: runtime.goexit()
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/runtiasm_amd64.s:1374 +0x1 fp=0xc00099ffe8 sp=0xc00099ffe0 pc=0x46fc81
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: goroutine 11 [chan receive]:
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: k8s.io/klog/v2.(*loggingT).flushDaemon(0x121fc40)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/vendor/k8s.io/klog/klog.go:1131 +0x8b
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: created by k8s.io/klog/v2.init.0
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/vendor/k8s.io/klog/klog.go:416 +0xd8
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: goroutine 12 [select]:
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: (*pipe).Read(0xc000422780, 0xc00034b000, 0x1000, 0x1000, 0xba4480, 0x1, 0xc00034b000)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/io/pipe.go:57 +0xe7
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: (*PipeReader).Read(0xc00000e380, 0xc00034b000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/io/pipe.go:134 +0x4c
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: bufio.(*Scanner).Scan(0xc00052ef38, 0x0)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/bufio/scan.go:214 +0xa9
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: github.com/sirupsen/logr(*Entry).writerScanner(0xc00016e1c0, 0xc00000e380, 0xc000516300)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:59 +0xb4
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: created by github.com/sirupsen/logrus.(*Entry).WriterLevel
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:51 +0x1b7
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: goroutine 13 [select]:
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: (*pipe).Read(0xc0004227e0, 0xc000180000, 0x1000, 0x1000, 0xba4480, 0x1, 0xc000180000)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/io/pipe.go:57 +0xe7
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: (*PipeReader).Read(0xc00000e390, 0xc000180000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/io/pipe.go:134 +0x4c
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: bufio.(*Scanner).Scan(0xc00020af38, 0x0)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/bufio/scan.go:214 +0xa9
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: github.com/sirupsen/logr(*Entry).writerScanner(0xc00016e1c0, 0xc00000e390, 0xc000516320)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:59 +0xb4
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: created by github.com/sirupsen/logrus.(*Entry).WriterLevel
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:51 +0x1b7
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: goroutine 14 [select]:
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: (*pipe).Read(0xc000422840, 0xc0004c2000, 0x1000, 0x1000, 0xba4480, 0x1, 0xc0004c2000)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/io/pipe.go:57 +0xe7
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: (*PipeReader).Read(0xc00000e3a0, 0xc0004c2000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/io/pipe.go:134 +0x4c
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: bufio.(*Scanner).Scan(0xc00052af38, 0x0)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/bufio/scan.go:214 +0xa9
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: github.com/sirupsen/logr(*Entry).writerScanner(0xc00016e1c0, 0xc00000e3a0, 0xc000516340)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:59 +0xb4
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: created by github.com/sirupsen/logrus.(*Entry).WriterLevel
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:51 +0x1b7
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: goroutine 15 [select]:
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: (*pipe).Read(0xc0004228a0, 0xc000532000, 0x1000, 0x1000, 0xba4480, 0x1, 0xc000532000)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/io/pipe.go:57 +0xe7
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: (*PipeReader).Read(0xc00000e3b0, 0xc000532000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/io/pipe.go:134 +0x4c
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: bufio.(*Scanner).Scan(0xc00052ff38, 0x0)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /usr/local/go/src/bufio/scan.go:214 +0xa9
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: github.com/sirupsen/logr(*Entry).writerScanner(0xc00016e1c0, 0xc00000e3b0, 0xc000516360)
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:59 +0xb4
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: created by github.com/sirupsen/logrus.(*Entry).WriterLevel
Dec 22 14:22:06 PROD-BE-K8S-WN8 kubelet[3124]: /go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:51 +0x1b7
3、于是查看系統(tǒng)日志,如下(本來想追蹤當前時間的系統(tǒng)日志,但當時系統(tǒng)反應(yīng)超級慢,但是當時的系統(tǒng) load 是很低并沒有很高,而且 CPU & MEM 利用率也不高,具體在此是系統(tǒng)為什么反應(yīng)慢,后面再分析 “問題一”):
?再執(zhí)行查看系統(tǒng)命令,提示無法創(chuàng)建進程
$ dmesg -TL
-bash: fork: retry: No child processes
[Fri Sep 17 18:25:53 2021] Linux version 5.11.1-1.el7.elrepo.x86_64 (mockbuild@Build64R7) (gcc (GC9.3.1 20200408 (Red Hat 9.3.1-2), GNU ld version 2.32-16.el7) #1 SMP Mon Feb 22 17:30:33 EST 2021
[Fri Sep 17 18:25:53 2021] Command line: BOOT_IMAGE=/boot/vmlinuz-5.11.1-1.el7.elrepo.x86_root=UUID=8770013a-4455-4a77-b023-04d04fa388c8 ro crashkernel=auto spectre_v2=retpoline net.ifnamesconsole=tty0 console=ttyS0,115200n8 noibrs
[Fri Sep 17 18:25:53 2021] x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
[Fri Sep 17 18:25:53 2021] x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'
[Fri Sep 17 18:25:53 2021] x86/fpu: Supporting XSAVE feature 0x004: 'AVX registers'
[Fri Sep 17 18:25:53 2021] x86/fpu: Supporting XSAVE feature 0x008: 'MPX bounds registers'
[Fri Sep 17 18:25:53 2021] x86/fpu: Supporting XSAVE feature 0x010: 'MPX CSR'
[Fri Sep 17 18:25:53 2021] x86/fpu: Supporting XSAVE feature 0x020: 'AVX-512 opmask'
[Fri Sep 17 18:25:53 2021] x86/fpu: Supporting XSAVE feature 0x040: 'AVX-512 Hi256'
4、嘗試在該節(jié)點新建 Container,如下:
?提示初始化線程失敗,資源不夠
$ docker run -it --rm tomcat bash
runtime/cgo: runtime/cgo: pthread_create failed: Resource temporarily unavailable
pthread_create failed: Resource temporarily unavailable
SIGABRT: abort
PC=0x7f34d16023d7 m=3 sigcode=18446744073709551610
goroutine 0 [idle]:
runtime: unknown pc 0x7f34d16023d7
stack: frame={sp:0x7f34cebb8988, fp:0x0} stack=[0x7f34ce3b92a8,0x7f34cebb8ea8)
00007f34cebb8888: 000055f2b345a7bf <runtime.(*mheap).scavengeLocked+559> 00007f34cebb88c0
00007f34cebb8898: 000055f2b3450e0e <runtime.(*mTreap).end+78> 0000000000000000
故障分析
根據(jù)以上的故障問題初步分析,第一反應(yīng)是 ulimi -u 值太小,已經(jīng)被 hit(觸及到,突破該參數(shù)的上限),于是查看各用戶的 ulimi -u,官方描述就是 max user processes(該參數(shù)的值上限應(yīng)該小于 user.max_pid_namespace 的值,該參數(shù)是內(nèi)核初始化分配的)。
監(jiān)控信息
查看用戶的 max processes 的上限,如下:
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 249047
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 65535
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 249047
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
因為 ulimit 是針對于每用戶而言的,具體還要驗證每個用戶的 limit 的配置,如下:
?根據(jù)以下配置判斷,并沒有超出設(shè)定的范圍。
# 默認limits.conf的配置
# End of file
root soft nofile 65535
root hard nofile 65535
* soft nofile 65535
* hard nofile 65535
# limits.d/20.nproc.conf的配置,如下
# Default limit for number of user's processes to prevent
# accidental fork bombs.
# See rhbz #432903 for reasoning.
* soft nproc 65536
root soft nproc unlimited
查看節(jié)點運行的進程:
?從監(jiān)控信息可以看到在故障最高使用 457 個進程。

查看系統(tǒng)中的進程狀態(tài),如下:
?雖然說有個 Z 狀的進程,但是也不影響當前系統(tǒng)運行。

查看系統(tǒng) create 的線程數(shù),如下:
?從下監(jiān)控圖表,當時最大線程數(shù)是 32616。

分析過程
1、從以上監(jiān)控信息分析,故障時間區(qū)間,系統(tǒng)運行的線程略高 31616,但是該值卻沒有超過當前用戶的 ulimit -u 的值,初步排除該線索。
2、根據(jù)系統(tǒng)拋出的錯誤提示,Google 一把 fork: Resource temporarily unavailable,找到了一個貼子:https://github.com/awslabs/amazon-eks-ami/issues/239 [1],在整個帖子看到一條這樣的提示:
One possible cause is running out of process IDs. Check you don't have 40.000 defunct processes or similar on nodes with problems
3、于是根據(jù)該線索,翻閱 linux 內(nèi)核文檔,搜索 PID 相關(guān)字段,其中找到如下相關(guān)的 PID 參數(shù):
kernel.core_uses_pid = 1
?
引用官方文檔 https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#core-uses-pid
參數(shù)大致意思是為系統(tǒng) coredump 文件命名,實際生成的名字為 “core.PID”,則排除該參數(shù)引起的問題。
kernel.ns_last_pid = 23068
?
引用官方文檔 https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#ns-last-pid
參數(shù)大致意思是,記錄當前系統(tǒng)最后分配的 PID identifiy,當 kernel fork 執(zhí)行下一個 task 時,kernel 將從此 pid 分配 identify。
kernel.pid_max = 32768
?
引用官方文檔 https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#pid-max
參數(shù)大致意思是,kernel 允許當前系統(tǒng)分配的最大 PID identify,如果 kernel 在 fork 時 hit 到這個值時,kernel 會 wrap back 到內(nèi)核定義的 minimum PID identify,意思就是不能分配大于該參數(shù)設(shè)定的值+1,該參數(shù)邊界范圍是全局的,屬于系統(tǒng)全局邊界。
通過該參數(shù)的闡述,大致問題定位到了,在 linux 中其實 thread & process 的創(chuàng)建都會被該參數(shù)束縛,因為無論是線程還是進程結(jié)構(gòu)體基本上一樣的,都需要 PID 來標識。
user.max_pid_namespaces = 253093
?
引用官方文檔 https://www.kernel.org/doc/html/latest/admin-guide/sysctl/user.html#max-pid-namespaces
參數(shù)大致意思是,在當前所屬用戶 namespace 下允許該用戶創(chuàng)建的最大的 PID,意思應(yīng)該是最大進程吧,等同于參數(shù) ulimit -u 的值,由內(nèi)核初始化而定義的,具體算法應(yīng)該是(init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads/2)。
kernel.cad_pid = 1
?
引用官方文檔 https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#cad-pid
參數(shù)大致意思是,向系統(tǒng)發(fā)送 reboot 信號,特別針對于 ctrl+alt+del,對于該參數(shù)不需要理解太多,用不到。
4、查看系統(tǒng)內(nèi)核參數(shù) kernel.pid_max,如下:
?關(guān)于該參數(shù)的初始值是如何計算的,下面會分析的。
$ sysctl -a | grep pid_max
kernel.pid_max = 32768
5、返回系統(tǒng)中,需要定位是哪個應(yīng)用系統(tǒng) create 如此之多的線程,如下(推薦安裝監(jiān)控系統(tǒng),用于記錄監(jiān)控數(shù)據(jù)信息):

通常網(wǎng)上的教程都是盲目的調(diào)整對應(yīng)的內(nèi)核參數(shù)值,個人認為運維所有操作都是被動的,不具備根治問題,需要從源頭解決問題,最好是拋給研發(fā),在應(yīng)用系統(tǒng)初始化,create 適當?shù)木€程量。
具體如何優(yōu)化內(nèi)核參數(shù),下面來分析。
參數(shù)分析
相關(guān)內(nèi)核參數(shù)詳細說明,及如何調(diào)整,及相互關(guān)系,及計算方式,參數(shù)邊界,如下說明:
kernel.pid_max
概念就不詳述了,參考上文(大致意思就是,系統(tǒng)最大可分配的 PID identify,理解有點抽象,嚴格意義是最大標識,每個進程的標識符,當然也代表最大進程吧)。
話不多說,分析源代碼,如有誤,請指出。
int pid_max = PID_MAX_DEFAULT;
#define RESERVED_PIDS 300
int pid_max_min = RESERVED_PIDS + 1;
int pid_max_max = PID_MAX_LIMIT;
代碼地址:https://github.com/torvalds/linux/blob/v5.11-rc1/kernel/pid.c
上面代碼表示,pid_max 默認賦值等于 PID_MAX_DEFAULT 的值,但是初始創(chuàng)建的 PID identify 是 RESERVD_PIDS + 1,也就是等于 301,小于等于 300 是系統(tǒng)內(nèi)核保留值(可能是特殊使用吧)
那么 PID_MAX_DEFAULT 的值是如何計算的及初始化時是如何定義的及默認值、最大值,及 LIMIT 的值的呢?具體 PID_MAX_DEFAULT 代碼如下:
/*
* This controls the default maximum pid allocated to a process
* 大致意思就是,如果在編譯內(nèi)核時指定或者為CONFIG_BASE_SMALl賦值了,那么默認值就是4096,反而就是32768
*/
#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)
/*
* A maximum of 4 million PIDs should be enough for a while.
* [NOTE: PID/TIDs are limited to 2^30 ~= 1 billion, see FUTEX_TID_MASK.]
* 如果CONFIG_BASE_SMALL被賦值了,則最大值就是32768,如果條件不成立,則判斷l(xiāng)ong的類型通常應(yīng)該是操作系統(tǒng)版本,如果大于4字節(jié),取值范圍大約就是4 million,精確計算就是4,194,304,如果條件還不成立則只能取值最被設(shè)置的PID_MAX_DEFAULT的值
*/
#define PID_MAX_LIMIT (CONFIG_BASE_SMALL ? PAGE_SIZE * 8 : \
(sizeof(long) > 4 ? 4 * 1024 * 1024 : PID_MAX_DEFAULT))
代碼地址:linux/threads.h at v5.11-rc1 · torvalds/linux · GitHub[2]
但是翻閱 man proc 的官方文檔,明確說明:如果 OS 為 64 位系統(tǒng) PID_MAX_LIMIT 的邊界值為 2 的 22 次方,精確計算就是 210241024*1024 等于 1,073,741,824,10 億多。而 32BIT 的操作系統(tǒng)默認就是 32768
如何查看 CONFIG_BASE_SMALL 的值,如下:
$ cat /boot/config-5.11.1-1.el7.elrepo.x86_64 | grep CONFIG_BASE_SMALL
CONFIG_BASE_SMALL=0
0 代表未被賦值。
kernel.threads-max
參考文檔:Documentation for /proc/sys/kernel/ — The Linux Kernel documentation[3]
該參數(shù)大致意思是,系統(tǒng)內(nèi)核 fork() 允許創(chuàng)建的最大線程數(shù),在內(nèi)核初始化時已經(jīng)設(shè)定了此值,但是即使設(shè)定了該值,但是線程結(jié)構(gòu)只能占用可用 RAM page 的一部分,約 1/8(注意是可用內(nèi)存,即 Available memory page), 如果超出此值 1/8 則 threads-max 的值會減少
內(nèi)核初始化時,默認指定最小值為 MIN_THREADS = 20,MAX_THREADS 的最大邊界值是由 FUTEX_TID_MASK 值而約束,但是在內(nèi)核初始化時,kernel.threads-max 的值是根據(jù)系統(tǒng)實際的物理內(nèi)存計算出來的,如下代碼:
/*
* set_max_threads
*/
static void set_max_threads(unsigned int max_threads_suggested)
{
u64 threads;
unsigned long nr_pages = totalram_pages();
/*
* The number of threads shall be limited such that the thread
* structures may only consume a small part of the available memory.
*/
if (fls64(nr_pages) + fls64(PAGE_SIZE) > 64)
threads = MAX_THREADS;
else
threads = div64_u64((u64) nr_pages * (u64) PAGE_SIZE,
(u64) THREAD_SIZE * 8UL);
if (threads > max_threads_suggested)
threads = max_threads_suggested;
max_threads = clamp_t(u64, threads, MIN_THREADS, MAX_THREADS);
}
代碼地址:linux/fork.c at v5.16-rc1 · torvalds/linux · GitHub[4]
kernel.threads-max 該參數(shù)一般不需要手動更改,因為在內(nèi)核根據(jù)現(xiàn)在有的內(nèi)存已經(jīng)算好了,不建議修改
那么 kernel.threads-max 由 FUTEX_TID_MASK 常量所約束,那它的具體值是多少呢,代碼如下:
#define FUTEX_TID_MASK 0x3fffffff
代碼地址:linux/futex.h at v5.16-rc1 · torvalds/linux · GitHub[5]
vm.max_map_count
參考文檔:Documentation for /proc/sys/vm/ — The Linux Kernel documentation[6]
這個參數(shù)大致意思是,允許系統(tǒng)進程最大分配的內(nèi)存 MAP 區(qū)域,一般應(yīng)用程序占用少于 1000 個 map,但是個別程序,特別針對于被 malloc 分配,可能會大量消耗,每個 allocation 會占用一到二個 map,默認值為 65530。
通過設(shè)定此值可以限制進程使用 VMA(虛擬內(nèi)存區(qū)域) 的數(shù)量。虛擬內(nèi)存區(qū)域是一個連續(xù)的虛擬地址空間區(qū)域。在進程的生命周期中,每當程序嘗試在內(nèi)存中映射文件,鏈接到共享內(nèi)存段,或者分配堆空間的時候,這些區(qū)域?qū)⒈粍?chuàng)建。調(diào)優(yōu)這個值將限制進程可擁有 VMA 的數(shù)量。限制一個進程擁有 VMA 的總數(shù)可能導(dǎo)致應(yīng)用程序出錯,因為當進程達到了 VMA 上線但又只能釋放少量的內(nèi)存給其他的內(nèi)核進程使用時,操作系統(tǒng)會拋出內(nèi)存不足的錯誤。如果你的操作系統(tǒng)在 NORMAL 區(qū)域僅占用少量的內(nèi)存,那么調(diào)低這個值可以幫助釋放內(nèi)存給內(nèi)核用參數(shù)大致作用就是這樣的。
可以總結(jié)一下什么情況下,適當?shù)脑黾樱?/p>
壓力測試,壓測應(yīng)用程序最大 create 的線程數(shù)量; 高并發(fā)的應(yīng)用系統(tǒng),單進程并發(fā)非常高。
參考文檔:
http://www.kegel.com/c10k.html#limits.threads https://listman.redhat.com/archives/phil-list/2003-August/msg00005.html https://bugzilla.redhat.com/show_bug.cgi?id=1459891
配置建議
參數(shù)邊界
| 參數(shù)名稱 | 范圍邊界 |
|---|---|
| kernel.pid_max | 系統(tǒng)全局限制 |
| kernel.threads-max | 系統(tǒng)全局限制 |
| vm.max_map_count | 進程級別限制 |
| /etc/security/limits.conf | 用戶級別限制 |
總結(jié)建議
kernel.pid_max 約束整個系統(tǒng)最大 create 的線程與進程數(shù)量,無論是線程還是進程,都不能 hit 到此設(shè)定的值,錯誤有二種(create 接近拋出 Resource temporarily unavailable,create 大于拋出 No more processes...);可以根據(jù)實際應(yīng)用場景及應(yīng)用平臺修改此值,比如 Kubernetes 平臺,一個節(jié)點可能運行上百 Container instance,或者是高并發(fā),多線程的應(yīng)用。 kernel.threads-max 只針對事個系統(tǒng)所有用戶的最大他 create 的線程數(shù)量,就大于系統(tǒng)所有用戶設(shè)定的 ulimit -u 的值,最好 ulimit -u 精確的計算一下(不推薦手動修改該參數(shù),該參數(shù)是由在內(nèi)核初始化系統(tǒng)算出來的結(jié)果,如果將其放大可以會造成內(nèi)存溢出,一般系統(tǒng)默認值不會被 hit 到)。 vm.max_map_count 是針對系統(tǒng)單個進程允許被分配的 VMA 區(qū)域,如果在壓測時,會有二種情況拋出(線程不夠 11=no more threads allowed,資源不夠 12 = out of mem.)但是此值了不能設(shè)置的太大,會造成內(nèi)存開銷,回收慢;此值的調(diào)整,需要根據(jù)實際壓測結(jié)果而定(常指可以被 create 多少個線程達到飽和)。 limits.conf 針對用戶級別的,在設(shè)置此值時,需要考慮到上面二個全局參數(shù)的值,用戶的 total 值(不管是 nproc 還是 nofile)不能大于與之對應(yīng)的 kernel.pid_max & kernel.threads-max & fs.file-max。 Linux 通常不會對單個 CPU 的 create 線程數(shù)做上限,過于復(fù)雜,個人認為內(nèi)存不好精確計算吧。
引用鏈接
https://github.com/awslabs/amazon-eks-ami/issues/239 : https://github.com/awslabs/amazon-eks-ami/issues/239
[2]linux/threads.h at v5.11-rc1 · torvalds/linux · GitHub: https://github.com/torvalds/linux/blob/v5.11-rc1/include/linux/threads.h
[3]Documentation for /proc/sys/kernel/ — The Linux Kernel documentation: https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#threads-max
[4]linux/fork.c at v5.16-rc1 · torvalds/linux · GitHub: https://github.com/torvalds/linux/blob/v5.16-rc1/kernel/fork.c
[5]linux/futex.h at v5.16-rc1 · torvalds/linux · GitHub: https://github.com/torvalds/linux/blob/v5.16-rc1/include/uapi/linux/futex.h
[6]Documentation for /proc/sys/vm/ — The Linux Kernel documentation: https://www.kernel.org/doc/html/latest/admin-guide/sysctl/vm.html#max-map-count


你可能還喜歡
點擊下方圖片即可閱讀
2022-10-27
2022-10-24
2022-10-20
2022-10-19

云原生是一種信仰 ??
關(guān)注公眾號
后臺回復(fù)?k8s?獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!


點擊 "閱讀原文" 獲取更好的閱讀體驗!
發(fā)現(xiàn)朋友圈變“安靜”了嗎?

