一、背景說明
運行環(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?1113?CST.?-- Dec?22?1451?PROD-BE-K8S-WN8?kubelet[3124]:?encoding/js(*decodeState).unmarshal(0xc000204580,?0xcafe00,?0xc00048f440,?0xc0002045a8,?0x0) Dec?22?1451?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/encoding/json/decode.go:180?+0x1ea Dec?22?1451?PROD-BE-K8S-WN8?kubelet[3124]:?encodijson.Unmarshal(0xc00025e000,?0x9d38,?0xfe00,?0xcafe00,?0xc00048f440,?0x0,?0x0) Dec?22?1451?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/encoding/json/decode.go:107?+0x112 Dec?22?1451?PROD-BE-K8S-WN8?kubelet[3124]:?github.com/go-openaspec.Swagger20Schema(0xc000439680,?0x0,?0x0) Dec?22?1451?PROD-BE-K8S-WN8?kubelet[3124]:?/go/src/github.com/cilium/cilium/vendor/github.com/openapi/spec/spec.go:82?+0xb8 Dec?22?1451?PROD-BE-K8S-WN8?kubelet[3124]:?github.com/go-openapi/spec.MustLoadSwagger20Schema(...) Dec?22?1451?PROD-BE-K8S-WN8?kubelet[3124]:?/go/src/github.com/cilium/cilium/vendor/github.com/openapi/spec/spec.go:66 Dec?22?1451?PROD-BE-K8S-WN8?kubelet[3124]:?github.com/go-openapi/spec.init.4() Dec?22?1451?PROD-BE-K8S-WN8?kubelet[3124]:?/go/src/github.com/cilium/cilium/vendor/github.com/openapi/spec/spec.go:38?+0x57 Dec?22?1406?PROD-BE-KWN8?kubelet[3124]:?runtime:?failed?to?create?new?OS?thread?(have?15?already;?errno=11) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?runtime:?may?need?to?increase?max?user?processes?(ulimiu) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?fatal?error:?newosproc Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?goroutine?1?[running,?locked?to?thread]: Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?runtime.throw(0xcbf07e,?0x9) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/runtipanic.go:1116?+0x72?fp=0xc00099fe20?sp=0xc00099fdf0?pc=0x4376d2 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?runtime.newosproc(0xc000600800) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/runtios_linux.go:161?+0x1c5?fp=0xc00099fe80?sp=0xc00099fe20?pc=0x433be5 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?runtime.newm1(0xc000600800) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/runtiproc.go:1843?+0xdd?fp=0xc00099fec0?sp=0xc00099fe80?pc=0x43dcbd Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?runtime.newm(0xcf1010,?0x0,?0xffffffffffffffff) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/runtiproc.go:1822?+0x9b?fp=0xc00099fef8?sp=0xc00099fec0?pc=0x43db3b Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?runtime.startTemplateThread() Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/runtiproc.go:1863?+0xb2?fp=0xc00099ff28?sp=0xc00099fef8?pc=0x43ddb2 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?runtime.LockOSThread() Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/runtiproc.go:3845?+0x6b?fp=0xc00099ff48?sp=0xc00099ff28?pc=0x44300b Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?main.init.0() Dec?22?1406?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?1406?PROD-BE-K8S-WN8?kubelet[3124]:?runtime.doInit(0x11c73a0) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/runtiproc.go:5652?+0x8a?fp=0xc00099ff88?sp=0xc00099ff58?pc=0x44720a Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?runtime.main() Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/runtiproc.go:191?+0x1c5?fp=0xc00099ffe0?sp=0xc00099ff88?pc=0x439e85 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?runtime.goexit() Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/runtiasm_amd64.s:1374?+0x1?fp=0xc00099ffe8?sp=0xc00099ffe0?pc=0x46fc81 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?goroutine?11?[chan?receive]: Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?k8s.io/klog/v2.(*loggingT).flushDaemon(0x121fc40) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/go/src/github.com/cilium/cilium/vendor/k8s.io/klog/klog.go:1131?+0x8b Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?created?by?k8s.io/klog/v2.init.0 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/go/src/github.com/cilium/cilium/vendor/k8s.io/klog/klog.go:416?+0xd8 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?goroutine?12?[select]: Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?(*pipe).Read(0xc000422780,?0xc00034b000,?0x1000,?0x1000,?0xba4480,?0x1,?0xc00034b000) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/io/pipe.go:57?+0xe7 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?(*PipeReader).Read(0xc00000e380,?0xc00034b000,?0x1000,?0x1000,?0x0,?0x0,?0x0) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/io/pipe.go:134?+0x4c Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?bufio.(*Scanner).Scan(0xc00052ef38,?0x0) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/bufio/scan.go:214?+0xa9 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?github.com/sirupsen/logr(*Entry).writerScanner(0xc00016e1c0,?0xc00000e380,?0xc000516300) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:59?+0xb4 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?created?by?github.com/sirupsen/logrus.(*Entry).WriterLevel Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:51?+0x1b7 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?goroutine?13?[select]: Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?(*pipe).Read(0xc0004227e0,?0xc000180000,?0x1000,?0x1000,?0xba4480,?0x1,?0xc000180000) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/io/pipe.go:57?+0xe7 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?(*PipeReader).Read(0xc00000e390,?0xc000180000,?0x1000,?0x1000,?0x0,?0x0,?0x0) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/io/pipe.go:134?+0x4c Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?bufio.(*Scanner).Scan(0xc00020af38,?0x0) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/bufio/scan.go:214?+0xa9 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?github.com/sirupsen/logr(*Entry).writerScanner(0xc00016e1c0,?0xc00000e390,?0xc000516320) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:59?+0xb4 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?created?by?github.com/sirupsen/logrus.(*Entry).WriterLevel Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:51?+0x1b7 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?goroutine?14?[select]: Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?(*pipe).Read(0xc000422840,?0xc0004c2000,?0x1000,?0x1000,?0xba4480,?0x1,?0xc0004c2000) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/io/pipe.go:57?+0xe7 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?(*PipeReader).Read(0xc00000e3a0,?0xc0004c2000,?0x1000,?0x1000,?0x0,?0x0,?0x0) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/io/pipe.go:134?+0x4c Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?bufio.(*Scanner).Scan(0xc00052af38,?0x0) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/bufio/scan.go:214?+0xa9 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?github.com/sirupsen/logr(*Entry).writerScanner(0xc00016e1c0,?0xc00000e3a0,?0xc000516340) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:59?+0xb4 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?created?by?github.com/sirupsen/logrus.(*Entry).WriterLevel Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:51?+0x1b7 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?goroutine?15?[select]: Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?(*pipe).Read(0xc0004228a0,?0xc000532000,?0x1000,?0x1000,?0xba4480,?0x1,?0xc000532000) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/io/pipe.go:57?+0xe7 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?(*PipeReader).Read(0xc00000e3b0,?0xc000532000,?0x1000,?0x1000,?0x0,?0x0,?0x0) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/io/pipe.go:134?+0x4c Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?bufio.(*Scanner).Scan(0xc00052ff38,?0x0) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/usr/local/go/src/bufio/scan.go:214?+0xa9 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?github.com/sirupsen/logr(*Entry).writerScanner(0xc00016e1c0,?0xc00000e3b0,?0xc000516360) Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:59?+0xb4 Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?created?by?github.com/sirupsen/logrus.(*Entry).WriterLevel Dec?22?1406?PROD-BE-K8S-WN8?kubelet[3124]:?/go/src/github.com/cilium/cilium/vendor/github.csirupsen/logrus/writer.go:51?+0x1b7
?
3、于是查看系統(tǒng)日志,如下(本來想追蹤當(dāng)前時間的系統(tǒng)日志,但當(dāng)時系統(tǒng)反應(yīng)超級慢,但是當(dā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?1853?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?1733?EST?2021 [Fri?Sep?17?1853?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?1853?2021]?x86/fpu:?Supporting?XSAVE?feature?0x001:?'x87?floating?point?registers' [Fri?Sep?17?1853?2021]?x86/fpu:?Supporting?XSAVE?feature?0x002:?'SSE?registers' [Fri?Sep?17?1853?2021]?x86/fpu:?Supporting?XSAVE?feature?0x004:?'AVX?registers' [Fri?Sep?17?1853?2021]?x86/fpu:?Supporting?XSAVE?feature?0x008:?'MPX?bounds?registers' [Fri?Sep?17?1853?2021]?x86/fpu:?Supporting?XSAVE?feature?0x010:?'MPX?CSR' [Fri?Sep?17?1853?2021]?x86/fpu:?Supporting?XSAVE?feature?0x020:?'AVX-512?opmask' [Fri?Sep?17?1853?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???00007f34cebb88c0 00007f34cebb8898:??000055f2b3450e0e? ??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è)定的范圍。
?
#?默認(rèn)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 狀的進程,但是也不影響當(dāng)前系統(tǒng)運行。
查看系統(tǒng) create 的線程數(shù),如下:
從下監(jiān)控圖表,當(dāng)時最大線程數(shù)是 32616。
分析過程
1、從以上監(jiān)控信息分析,故障時間區(qū)間,系統(tǒng)運行的線程略高 31616,但是該值卻沒有超過當(dāng)前用戶的 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
?
參數(shù)大致意思是為系統(tǒng) coredump 文件命名,實際生成的名字為 “core.PID”,則排除該參數(shù)引起的問題。
kernel.ns_last_pid = 23068
參數(shù)大致意思是,記錄當(dāng)前系統(tǒng)最后分配的 PID identifiy,當(dāng) kernel fork 執(zhí)行下一個 task 時,kernel 將從此 pid 分配 identify。
kernel.pid_max = 32768
參數(shù)大致意思是,kernel 允許當(dāng)前系統(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 來標(biāo)識。
user.max_pid_namespaces = 253093
?
參數(shù)大致意思是,在當(dāng)前所屬用戶 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
?
參數(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ù)值,個人認(rèn)為運維所有操作都是被動的,不具備根治問題,需要從源頭解決問題,最好是拋給研發(fā),在應(yīng)用系統(tǒng)初始化,create 適當(dāng)?shù)木€程量。
具體如何優(yōu)化內(nèi)核參數(shù),下面來分析。
參數(shù)分析
相關(guān)內(nèi)核參數(shù)詳細(xì)說明,及如何調(diào)整,及相互關(guān)系,及計算方式,參數(shù)邊界,如下說明:
kernel.pid_max
概念就不詳述了,參考上文(大致意思就是,系統(tǒng)最大可分配的 PID identify,理解有點抽象,嚴(yán)格意義是最大標(biāo)識,每個進程的標(biāo)識符,當(dāng)然也代表最大進程吧)。
話不多說,分析源代碼,如有誤,請指出。
?
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 默認(rèn)賦值等于 PID_MAX_DEFAULT 的值,但是初始創(chuàng)建的 PID identify 是 RESERVD_PIDS + 1,也就是等于 301,小于等于 300 是系統(tǒng)內(nèi)核保留值(可能是特殊使用吧)
那么 PID_MAX_DEFAULT 的值是如何計算的及初始化時是如何定義的及默認(rèn)值、最大值,及 LIMIT 的值的呢?具體 PID_MAX_DEFAULT 代碼如下:
?
/* ?*?This?controls?the?default?maximum?pid?allocated?to?a?process ?*?大致意思就是,如果在編譯內(nèi)核時指定或者為CONFIG_BASE_SMALl賦值了,那么默認(rèn)值就是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)默認(rèn)就是 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
該參數(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)核初始化時,默認(rèn)指定最小值為 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
這個參數(shù)大致意思是,允許系統(tǒng)進程最大分配的內(nèi)存 MAP 區(qū)域,一般應(yīng)用程序占用少于 1000 個 map,但是個別程序,特別針對于被 malloc 分配,可能會大量消耗,每個 allocation 會占用一到二個 map,默認(rèn)值為 65530。
通過設(shè)定此值可以限制進程使用 VMA(虛擬內(nèi)存區(qū)域) 的數(shù)量。虛擬內(nèi)存區(qū)域是一個連續(xù)的虛擬地址空間區(qū)域。在進程的生命周期中,每當(dāng)程序嘗試在內(nèi)存中映射文件,鏈接到共享內(nèi)存段,或者分配堆空間的時候,這些區(qū)域?qū)⒈粍?chuàng)建。調(diào)優(yōu)這個值將限制進程可擁有 VMA 的數(shù)量。限制一個進程擁有 VMA 的總數(shù)可能導(dǎo)致應(yīng)用程序出錯,因為當(dāng)進程達到了 VMA 上線但又只能釋放少量的內(nèi)存給其他的內(nèi)核進程使用時,操作系統(tǒng)會拋出內(nèi)存不足的錯誤。如果你的操作系統(tǒng)在 NORMAL 區(qū)域僅占用少量的內(nèi)存,那么調(diào)低這個值可以幫助釋放內(nèi)存給內(nèi)核用參數(shù)大致作用就是這樣的。
可以總結(jié)一下什么情況下,適當(dāng)?shù)脑黾樱?/p>
壓力測試,壓測應(yīng)用程序最大 create 的線程數(shù)量;
高并發(fā)的應(yīng)用系統(tǒng),單進程并發(fā)非常高。
配置建議
參數(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)默認(rèn)值不會被 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ù)雜,個人認(rèn)為內(nèi)存不好精確計算吧。
審核編輯:湯梓紅
?
?
評論
查看更多