一、背景說明
運行環境信息:Kubernetes + docker,應用程序:Java
問題描述
1、首先從 Kubernetes 事件中心告警信息如下,該告警集群常規告警事件(其實從下面這些常規告警信息是無法判斷是什么故障問題)
2、最初懷疑是 docker 服務有問題,切換至節點上查看 docker & kubelet 日志,如下:
kubelet 無法初始化線程,需要增加所處運行用戶的進程限制,大致意思就是需要調整 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、于是查看系統日志,如下(本來想追蹤當前時間的系統日志,但當時系統反應超級慢,但是當時的系統 load 是很低并沒有很高,而且 CPU & MEM 利用率也不高,具體在此是系統為什么反應慢,后面再分析 “問題一”):
再執行查看系統命令,提示無法創建進程
?
$?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、嘗試在該節點新建 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
?
二、故障分析
根據以上的故障問題初步分析,第一反應是 ulimi -u 值太小,已經被 hit(觸及到,突破該參數的上限),于是查看各用戶的 ulimi ?-u,官方描述就是 max user ?processes(該參數的值上限應該小于 user.max_pid_namespace 的值,該參數是內核初始化分配的)。
監控信息
查看用戶的 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 的配置,如下:
根據以下配置判斷,并沒有超出設定的范圍。
?
#?默認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
?
查看節點運行的進程:
從監控信息可以看到在故障最高使用 457 個進程。
查看系統中的進程狀態,如下:
雖然說有個 Z 狀的進程,但是也不影響當前系統運行。
查看系統 create 的線程數,如下:
從下監控圖表,當時最大線程數是 32616。
分析過程
1、從以上監控信息分析,故障時間區間,系統運行的線程略高 31616,但是該值卻沒有超過當前用戶的 ulimit -u 的值,初步排除該線索。
2、根據系統拋出的錯誤提示,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、于是根據該線索,翻閱 linux 內核文檔,搜索 PID 相關字段,其中找到如下相關的 PID 參數:
kernel.core_uses_pid = 1
?
參數大致意思是為系統 coredump 文件命名,實際生成的名字為 “core.PID”,則排除該參數引起的問題。
kernel.ns_last_pid = 23068
參數大致意思是,記錄當前系統最后分配的 PID identifiy,當 kernel fork 執行下一個 task 時,kernel 將從此 pid 分配 identify。
kernel.pid_max = 32768
參數大致意思是,kernel 允許當前系統分配的最大 PID identify,如果 kernel ?在 fork 時 hit 到這個值時,kernel 會 wrap back 到內核定義的 minimum PID ?identify,意思就是不能分配大于該參數設定的值+1,該參數邊界范圍是全局的,屬于系統全局邊界。
通過該參數的闡述,大致問題定位到了,在 linux 中其實 thread & process 的創建都會被該參數束縛,因為無論是線程還是進程結構體基本上一樣的,都需要 PID 來標識。
user.max_pid_namespaces = 253093
?
參數大致意思是,在當前所屬用戶 namespace 下允許該用戶創建的最大的 PID,意思應該是最大進程吧,等同于參數 ulimit ?-u 的值,由內核初始化而定義的,具體算法應該是(init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads/2)。
kernel.cad_pid = 1
?
參數大致意思是,向系統發送 reboot 信號,特別針對于 ctrl+alt+del,對于該參數不需要理解太多,用不到。
4、查看系統內核參數 kernel.pid_max,如下:
?
關于該參數的初始值是如何計算的,下面會分析的。
?
$?sysctl?-a?|?grep?pid_max kernel.pid_max?=?32768
?
5、返回系統中,需要定位是哪個應用系統 create 如此之多的線程,如下(推薦安裝監控系統,用于記錄監控數據信息):
通常網上的教程都是盲目的調整對應的內核參數值,個人認為運維所有操作都是被動的,不具備根治問題,需要從源頭解決問題,最好是拋給研發,在應用系統初始化,create 適當的線程量。
具體如何優化內核參數,下面來分析。
參數分析
相關內核參數詳細說明,及如何調整,及相互關系,及計算方式,參數邊界,如下說明:
kernel.pid_max
概念就不詳述了,參考上文(大致意思就是,系統最大可分配的 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 的值,但是初始創建的 PID identify 是 RESERVD_PIDS + 1,也就是等于 301,小于等于 300 是系統內核保留值(可能是特殊使用吧)
那么 PID_MAX_DEFAULT 的值是如何計算的及初始化時是如何定義的及默認值、最大值,及 LIMIT 的值的呢?具體 PID_MAX_DEFAULT 代碼如下:
?
/* ?*?This?controls?the?default?maximum?pid?allocated?to?a?process ?*?大致意思就是,如果在編譯內核時指定或者為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,如果條件不成立,則判斷long的類型通常應該是操作系統版本,如果大于4字節,取值范圍大約就是4?million,精確計算就是4,194,304,如果條件還不成立則只能取值最被設置的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 位系統 PID_MAX_LIMIT 的邊界值為 2 的 22 次方,精確計算就是 210241024*1024 等于 1,073,741,824,10 億多。而 32BIT 的操作系統默認就是 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
該參數大致意思是,系統內核 fork() 允許創建的最大線程數,在內核初始化時已經設定了此值,但是即使設定了該值,但是線程結構只能占用可用 RAM page 的一部分,約 1/8(注意是可用內存,即 Available memory ?page), 如果超出此值 1/8 則 threads-max 的值會減少
內核初始化時,默認指定最小值為 MIN_THREADS = 20,MAX_THREADS 的最大邊界值是由 FUTEX_TID_MASK 值而約束,但是在內核初始化時,kernel.threads-max 的值是根據系統實際的物理內存計算出來的,如下代碼:
?
/* ?*?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 該參數一般不需要手動更改,因為在內核根據現在有的內存已經算好了,不建議修改
那么 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
這個參數大致意思是,允許系統進程最大分配的內存 MAP 區域,一般應用程序占用少于 1000 個 map,但是個別程序,特別針對于被 malloc 分配,可能會大量消耗,每個 allocation 會占用一到二個 map,默認值為 65530。
通過設定此值可以限制進程使用 VMA(虛擬內存區域) 的數量。虛擬內存區域是一個連續的虛擬地址空間區域。在進程的生命周期中,每當程序嘗試在內存中映射文件,鏈接到共享內存段,或者分配堆空間的時候,這些區域將被創建。調優這個值將限制進程可擁有 VMA 的數量。限制一個進程擁有 VMA 的總數可能導致應用程序出錯,因為當進程達到了 VMA 上線但又只能釋放少量的內存給其他的內核進程使用時,操作系統會拋出內存不足的錯誤。如果你的操作系統在 NORMAL 區域僅占用少量的內存,那么調低這個值可以幫助釋放內存給內核用參數大致作用就是這樣的。
可以總結一下什么情況下,適當的增加:
壓力測試,壓測應用程序最大 create 的線程數量;
高并發的應用系統,單進程并發非常高。
配置建議
參數邊界
參數名稱 | 范圍邊界 |
---|---|
kernel.pid_max | 系統全局限制 |
kernel.threads-max | 系統全局限制 |
vm.max_map_count | 進程級別限制 |
/etc/security/limits.conf | 用戶級別限制 |
總結建議
kernel.pid_max 約束整個系統最大 create 的線程與進程數量,無論是線程還是進程,都不能 hit 到此設定的值,錯誤有二種(create 接近拋出 Resource temporarily unavailable,create 大于拋出 No more processes...);可以根據實際應用場景及應用平臺修改此值,比如 Kubernetes 平臺,一個節點可能運行上百 Container instance,或者是高并發,多線程的應用。
kernel.threads-max 只針對事個系統所有用戶的最大他 create 的線程數量,就大于系統所有用戶設定的 ulimit ?-u 的值,最好 ulimit -u ?精確的計算一下(不推薦手動修改該參數,該參數是由在內核初始化系統算出來的結果,如果將其放大可以會造成內存溢出,一般系統默認值不會被 hit 到)。
vm.max_map_count 是針對系統單個進程允許被分配的 VMA 區域,如果在壓測時,會有二種情況拋出(線程不夠 11=no more threads allowed,資源不夠 12 = out of ?mem.)但是此值了不能設置的太大,會造成內存開銷,回收慢;此值的調整,需要根據實際壓測結果而定(常指可以被 create 多少個線程達到飽和)。
limits.conf 針對用戶級別的,在設置此值時,需要考慮到上面二個全局參數的值,用戶的 total 值(不管是 nproc ?還是 nofile)不能大于與之對應的 kernel.pid_max & kernel.threads-max & ?fs.file-max。
Linux 通常不會對單個 CPU 的 create 線程數做上限,過于復雜,個人認為內存不好精確計算吧。
審核編輯:湯梓紅
?
?
評論