前言
K8s集群和Node宿主机之间的监控覆盖默认是断层的;
需要借助OpenTelemetry实现IasS层(主机)+PasS(K8s)+SasS(微服务层) 日志和监控数据,实现可观测性;
可观测平台可以实现故障的快速定位;
故障分析
Pod因内存不足OOM,一般由以下2种原因导致
原因1:Kubelet干的
Kubelet会定期检查节点的资源,当资源不足时,根据优先级驱逐一些pod。
Pod中的运行进程占用空间超出了Pod设置的Limit限制,导致该Pod被Kubelet终止,此时Pod的Status为OOMKilled;
Pod的OOMKilled状态可以借助Prometheus进行监控;
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
namespace: mem-example
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
limits:
memory: "200Mi"
requests:
memory: "100Mi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]
现象如下
[root@master /]# kubectl delete -f test-oom.yaml pod "memory-demo" deleted [root@master /]# kubectl apply -f test-oom.yaml pod/memory-demo created [root@master /]# kubectl get pod -n mem-example -o wide -w NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES memory-demo 0/1 ContainerCreating 0 4s <none> node1 <none> <none> memory-demo 1/1 Running 0 18s 10.244.166.150 node1 <none> <none> memory-demo 0/1 OOMKilled 0 19s 10.244.166.150 node1 <none> <none>
原因2:Linux内核干的
如果1个Pod分配内存的速度太快,以至于Kubelet没有在默认的检查窗口(默认10s)中发现它;
Pod内存使用试图超过可分配内存,加上硬驱逐阈值的总和,那么Linux内核的OOM killer将介入并强行终止Pod容器中的1个或N个进程。
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
namespace: mem-example
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
limits:
memory: "3Gi"
requests:
memory: "3Gi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "500M", "--vm-hang", "1"]
此时Pod会突然完全消失,Prometheus也发现不了;
只能采用监控系统+日志系统对该现象进行监控报警,需要通过日志系统收集Node节点的日志进行佐证;
stress --vm 4 --vm-bytes 730M --vm-keep
查看K8s node的/var/log/messages日志可发现Pod中运行进程被该node宿主机的Linux内核Kill掉的日志;
1.监控结合日志系统
监控+日志结合起来监控Pod进程被Linux系统内核Kill导致的OOM事件;
2.missing-container-metrics
也可以在每个Node上运行missing-container-metrics
Kubernetes 默认情况下使用 cAdvisor 来收集容器的各项指标,足以满足大多数人的需求,但还是有所欠缺,比如缺少对以下几个指标的收集:
OOM kill
容器重启的次数
容器的退出码
missing-container-metrics 这个项目弥补了 cAdvisor 的缺陷,新增了以上几个指标,集群管理员可以利用这些指标迅速定位某些故障。例如,假设某个容器有多个子进程,其中某个子进程被 OOM kill,但容器还在运行,如果不对 OOM kill 进行监控,管理员很难对故障进行定位。
3.OpenTelemetry
通过可观测平台定位PodOom故障发生原因;
使用Top命令
我们平时会部署一些应用到Linux服务器,所以经常需要了解服务器的运行状态;
Top命令是帮助我们了解服务器当前的CPU、内存、进程状态的实用工具;
在node宿主机使用top命令查看被Pod管理的容器进程的内存占用情况;
1.top快速入门
(base) [root@docker /]# top top - 09:57:23 up 137 days, 25 min, 2 users, load average: 0.05, 0.03, 0.05 Tasks: 148 total, 1 running, 147 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 32779568 total, 31119584 free, 636676 used, 1023308 buff/cache KiB Swap: 0 total, 0 free, 0 used. 31707740 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 8753 root 20 0 822868 47888 8040 S 0.7 0.1 11:52.10 python3.8 1 root 20 0 191028 4040 2604 S 0.0 0.0 0:46.16 systemd 2 root 20 0 0 0 0 S 0.0 0.0 0:00.26 kthreadd 4 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0H 5 root 20 0 0 0 0 S 0.0 0.0 0:01.76 kworker/u16:0 6 root 20 0 0 0 0 S 0.0 0.0 0:00.01 ksoftirqd/0 7 root rt 0 0 0 0 S 0.0 0.0 0:00.06 migration/0 8 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_bh 9 root 20 0 0 0 0 S 0.0 0.0 3:13.03 rcu_sched 10 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 lru-add-drain 11 root rt 0 0 0 0 S 0.0 0.0 0:46.57 watchdog/0 12 root rt 0 0 0 0 S 0.0 0.0 0:41.06 watchdog/1 13 root rt 0 0 0 0 S 0.0 0.0 0:00.05 migration/1 14 root 20 0 0 0 0 S 0.0 0.0 0:00.02 ksoftirqd/1 16 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/1:0H 17 root rt 0 0 0 0 S 0.0 0.0 0:39.13 watchdog/2 18 root rt 0 0 0 0 S 0.0 0.0 0:00.07 migration/2 19 root 20 0 0 0 0 S 0.0 0.0 0:00.01 ksoftirqd/2 21 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/2:0H 22 root rt 0 0 0 0 S 0.0 0.0 0:40.14 watchdog/3
2.系统状态概览
第一行是对当前Linux系统情况的整体概况;
top - 09:57:23 up 137 days, 25 min, 2 users, load average: 0.05, 0.03, 0.05
top:当前处在Top命令模式,系统时间
up:Linux操作系统运行了多久
users:当前活跃用户
load average:5分钟、10分钟、15分钟之内的CPU负载
2.1.load average
Linux系统中的Load是对当前CPU工作量的度量,简单的说是进程队列的长度。
Load Average 就是一个时间段 (1 分钟、5分钟、15分钟) 内CPU的平均 Load 。
2.2.如何衡量cpu load
假设1台电脑只有1个CPU,所有进程中的运算任务,都必须由这1个CPU来完成。
那么,我们不妨把这个CPU想象成1座单向通行大桥,桥上只有1根车道,所有车辆都必须从这1根车道上通过。
2.2.1.系统负荷为0
意味着大桥上1辆车也没有。
2.2.2.系统负荷为0.5
意味着大桥的一半,被车流占用了。
2.2.3.系统负荷为1.0
意味着大桥的全部,被车流占满了,但是直到此时该大桥还是能顺畅通行的,没有堵车;
2.2.4.系统负荷为1.7
大桥在通车的时候, 不光桥上的车流会影响通车的效率, 后面排队等着还没有上桥的车也增加道路的拥堵,;
如果把等待进入大桥的车,也算到负载中去, 那么Load就会 > 1.0.
例:系统负荷为1.7,意味着车辆太多了,大桥已经被占满了(100%),后面等待上桥的车辆为大桥上车辆的70%。
2.2.5.系统负荷高会造成什么影响?
以此类推,系统负荷2.0,意味着等待上桥的车辆与桥面的车辆一样多;
系统负荷3.0,意味着等待上桥的车辆是桥面车辆的2倍。
总之,当cpu load大于1时,后面的车辆(进程)就必须等待了,系统负荷越大,过桥的时间就越久。
2.3.cpu load和cpu使用率
CPU Load 低 CPU利用率低 :CPU资源良好,系统运行正常
CPU Load 低 CPU利用率高:确定程序是否有问题,少量进程消耗大量COP计算资源
CPU Load 高 CPU利用率低:程序中IO操作比较多,导致系统出现IO瓶颈;
CPU Load 高 CPU利用率高:CPU资源不足
3.进程状态概览
Tasks: 148 total, 1 running, 147 sleeping, 0 stopped, 0 zombie
Tasks:当前系统中运行的进程总数
Running:当前系统中处在running 状态的进程个数
Sleeping:当前系统中处在sleeping状态的进程个数
Stopped:当前系统中处在Stopped状态的进程个数
Zombie:当前系统中处在Zombie状态的进程个数,子进程比父进程先结束,父进程无法获取到子进程的Exit状态,该进程称为僵尸进程。
4.CPU状态概览
%Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
CPU分为用户态和内核态
us:CPU在用户态花费的时间 ,应该<60%
sy:CPU在内核态花费的时间,sy+us<80%
id:CPU在空闲状态花费的时间
wa: 进程执行IO操作占用CPU的时间,<30%,不同功能的服务器,阈值不一,邮件服务器的wa很高;
5.内存使用概览
以CentOS-7.6为例
[root@node1 ~]# cat /etc/centos-release CentOS Linux release 7.6.1810 (Core) [root@node1 ~]# cat /proc/version Linux version 3.10.0-957.el7.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) ) #1 SMP Thu Nov 8 23:39:32 UTC 2018 [root@node1 ~]#
讲解Linux的内存
KiB Mem : 32779568 total, 31119584 free, 636676 used, 1023308 buff/cache KiB Swap: 0 total, 0 free, 0 used. 31707740 avail Mem
5.1.buff/cache占用内存的意义
Linux系统设计哲学之一是一切皆文件,所以该系统对IO性能要求比较高;
buffer:用于存放从内存即将输出到磁盘的数据
cache:用于存放从磁盘读取到内存中,待今后使用的数据
Linux借内存空间,造buff/cache,是为了提升Linux系统的IO性能,使Linux用起来更加流畅;
当进程可使用的内存空间严重不足时,Linux会把借用的buff/cache内存空间让进程占用,虽然内存空间还回来了,但此时Linux会变得卡顿起来。
5.2.如何查看进程使用的内存空间大小
[root@node1 ~]# free -h total used free shared buff/cache available Mem: 7.1G 2.0G 2.8G 397M 2.3G 4.3G Swap: 0B 0B 0B [root@node1 ~]#
- total:total=used(进程使用的内存)+buff/cache(提升Linux系统的IO性能花销的内存)+free(空闲的)
- used:正在运行的进程使用的内存(used= total – free – buff/cache)
- free: 未使用的内存 (free= total – used – buff/cache)
- shared:多个进程共享的内存
- buffers:内存保留用于内核操作一个进程队列请求
- cache:在 RAM 中保存最近使用的文件的页面缓存的大小
- buff/cache:Buffers + Cache
- available:在不使用Swap分区的前提下,预计有多少内存可用于将程序启动为进程,
available = free + buffer/cache
(注:只是大概的计算方法)
6.进程详细状态
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 8753 root 20 0 822868 47888 8040 S 0.7 0.1 11:52.10 python3.8
PID:进程编号
USER:执行进程的用户
PR(Priority):进程优先级,由OS决定的;(PR越低越优先被CPU调度),越低越优先
NI(Nice):进程的Nice值,用户可以设置进程的执行优先权,root(-20到19),普通user 0-19
VIRT(Virtual memory usage ):进程需要的虚拟内存空间大小
RES(Resident Memory Usage):进程当前实际占用的物理内存大小,但不包括swap out
SHR(Shared Memory):除了自身进程的共享内存,也包括其他进程的共享内存
S(Status):进程状态D(Dead)R(运行) T(跟踪/停止)Z(僵尸)
%CPU:当前进程占用CPU时间的百分比
%MEM:当前进程占用内存空间的百分比
TIME+:当前进程使用CPU时间的总和
COMMAND:命令行
6.1.虚拟内存和物理内存的关系
虚拟内存和物理内存通过Page Table关联起来。
虚拟内存空间中着色的部分,分别被映射到物理内存空间对应相同着色的部分。
然而虚拟内存空间中的灰色部分,在物理内存空间中没有与之对应的部分,也就是说灰色部分没有被映射到物理内存空间中。
如此设计是本着“按需映射”的指导思想。
因为虚拟内存空间很大,虚拟内存中很多部分,在1次程序运行过程中根本不需要访问;
所以也就没有必要,将虚拟内存空间中的这些不需要被访问的部分映射到物理内存空间上。
6.2.虚拟内存(Virtual Memory)
VIRT=Swap分区+Resident Memory
虚拟内存是1个假象的内存空间,在程序运行过程中虚拟内存空间中需要被访问的部分会被映射到物理内存空间中。
虚拟内存空间大只能表示1个程序运行过程中可访问的内存空间比较大,不代表占用实际物理内存的空间。
6.3.驻留内存(Resident Memory)
RES=Code+Data
驻留内存,是指那些被映射到虚拟内存空间中的物理内存。
上图中,在系统物理内存空间中被着色的部分都是驻留内存。
比如,A1、A2、A3和A4是进程A的驻留内存,B1、B2和B3是进程B的驻留内存。
驻留内存就是进程实实在在占用的物理内存,一般我们所讲的进程占用了多少内存,其实就是说的占用了多少驻留内存而不是多少虚拟内存。
因为进程的虚拟内存大,并不意味着进程运行过程成所占用的物理内存大。
6.4.共享内存(Shared Memory)
多个进程之间通过共享内存的方式相互通信也会出现了共享内存。
7.常用快捷键
- shift+e:切换内存单位显示格式 (可重复按键切换)
- z:换是否彩色显示(可复按键换
- m: 切换内存使用率显示格式(可重复按键切换)
- e:切换底部各个进程详细中单位的显示模式 (可重复按键切换)
- b:切换高亮选中 (可重复按键切换)
- W:把当前配置保存到文件中,下次启动top会使用当前的配置
- h:进入帮助菜单(进入菜单后,可按ESC或q退出帮助菜单)
- q:退出 top命令
8.字段排序
top底部的进程列表信息是可以选择指定列进行排序的
- 按f进入字段选择界面
- 按上下键选择要进行排序的字段/ space键选择该字段是否显示
- 按下s键激活这个选择
- 按q键退出排序字段选择界面
Node宿主机内存不足监控方案
Pod是K8s中的逻辑单位,当Pod被调度在Node宿主机之后,Pod本质就是运行在Node宿主机的1-n个容器进程;
ps -ef | grep "app-workspace-373-1675214627414" | grep -v grep
我们可以通过容器进程的名称来识别这些容器进程,之前属于哪1个Pod;
[root@byg612sv036 ~]# docker ps | grep "app-workspace-373-1675214627414" | grep -v grep 040582751cbc registry.docker.aimaster.lenovo.com:20443/library/373/workspace "jupyter lab --port …" 8 minutes ago Up 8 minutes k8s_app-workspace-373-1675214627414-podgroup-pfhh-pod_app-workspace-373-1675214627414-podgroup-pfhh-d42q6_aimaster-user-namespace-373_4881e655-9c3e-41a0-81d7-187ce8bcbafc_0 1d331fcc018b registry.access.redhat.com/rhel7/pod-infrastructure:latest "/usr/bin/pod" 8 minutes ago Up 8 minutes k8s_POD_app-workspace-373-1675214627414-podgroup-pfhh-d42q6_aimaster-user-namespace-373_4881e655-9c3e-41a0-81d7-187ce8bcbafc_0 [root@byg612sv036 ~]# ps -ef | grep "app-workspace-373-1675214627414" | grep -v grep root 43434 43404 0 09:58 pts/0 00:00:04 /usr/bin/python /usr/local/bin/jupyter-lab --port 8888 --allow-root --config='/dfs/share-read-only/jupyter_notebook_config.py' --LabApp.base_url='/app-workspace-373-1675214627414' --NotebookApp.token='6745c8z8'
1./proc/pid
Linux一切皆文件, 每1个进程的详细信息,都记录在/proc/pid目录下;
- /proc/pid/cmdline 进程启动命令
- /proc/pid/cwd 链接到进程当前工作目录
- /proc/pid/environ 进程环境变量列表
- /proc/pid/exe 链接到进程的执行命令文件
- /proc/pid/fd 包含进程相关的所有的文件描述符
- /proc/pid/maps 与进程相关的内存映射信息
- /proc/pid/mem 指代进程持有的内存,不可读
- /proc/pid/root 链接到进程的根目录
- /proc/pid/stat 进程的状态
- /proc/pid/statm 进程使用的内存的状态
- /proc/pid/status 进程状态信息,比stat/statm更具可读性
- /proc/self 链接到当前正在运行的进程
[centos@docker 8751]$ ps -ef | grep python root 867 1 0 2022 ? 00:20:30 /usr/bin/python2 -Es /usr/sbin/tuned -l -P root 8751 1 0 Feb22 ? 00:00:00 python3.8 manage.py runserver 0.0.0.0:8001 root 8753 8751 0 Feb22 ? 00:30:33 /usr/local/miniconda3/bin/python3.8 manage.py runserver 0.0.0.0:8001 centos 12048 12007 0 09:50 pts/0 00:00:00 grep --color=auto python [centos@docker 8751]$ pwd /proc/8751 [centos@docker 8751]$ ls ls: cannot read symbolic link cwd: Permission denied ls: cannot read symbolic link root: Permission denied ls: cannot read symbolic link exe: Permission denied attr cmdline environ io mem ns pagemap sched stack task autogroup comm exe limits mountinfo numa_maps patch_state schedstat stat timers auxv coredump_filter fd loginuid mounts oom_adj personality sessionid statm uid_map cgroup cpuset fdinfo map_files mountstats oom_score projid_map setgroups status wchan clear_refs cwd gid_map maps net oom_score_adj root smaps syscall [centos@docker 8751]$ '
2.cgroups
cgroups,其名称源自控制组群(control groups)的简写,是Linux内核的一个功能;
用来限制、控制与分离一个进程组的资源(如CPU、内存、磁盘输入输出等)。
[root@node1 43359]# ps -ef | grep stress root 43359 43338 0 16:09 ? 00:00:00 stress --vm 1 --vm-bytes 150M --vm-hang 1 root 43372 43359 5 16:09 ? 00:01:10 stress --vm 1 --vm-bytes 150M --vm-hang 1 root 62876 26220 0 16:28 pts/2 00:00:00 grep --color=auto stress [root@node1 43359]# docker inspect --format '{{.Config.Hostname}}' $(cat /proc/43359/cgroup|awk -F 'docker-' '{print $2}' |cut -c1-12| head -n 1) memory-demo [root@node1 43359]#
3.监控Node宿主机上的容器进程
每1个Pod运行到Node宿主机上之后,Pod管理的容器,都会在Node上被运行为容器进程,以下称为容器进程;
最终问题本质:监控Node宿主机的OS上的一部分进程(容器进程)是否被Node宿主机的系统内核OOM Killed ?
使用process-exporter监控容器进程。
基于以上问题,结合node_exporter的--collector.textfile.directory=/data/textfile启动参数,在Node宿主机上额外部署1个Shell/Golang监控插件;
使用该监控插件的前提是Pod在CI/CD阶段规范Pod中运行进程的进程名称;
该监控插件的功能大致如下:
- ps -ef | grep "规范的Pod进程名称" , 保存Pod进程在Node的OS上使用PID的记录(Node|Pname|PID)到Pod进程记录表;
- 实时监控K8s node的/var/log/messages|grep "规范的Pod进程名 out of memory", 一旦监控到发生OOM的进程的PID,在Pod进程记录表中 ,立即把Pod进程和Node信息记录到collector.textfile.directory的指标文件中。
- 最终通过node_exporter上报到PrometheusServer。