OCI 定义了容器运行时标准,runC 是 Docker 按照开放容器格式标准(OCF, Open Container Format)制定的一种具体实现。
runC 是从 Docker 的 libcontainer 中迁移而来的,实现了容器启停、资源隔离等功能。Docker 默认提供了 docker-runc 实现,事实上,通过 containerd 的封装,可以在 Docker Daemon 启动的时候指定 runc 的实现。
从Docker 1.11之后,Docker Daemon被分成了多个模块以适应OCI标准。拆分之后,结构分成了以下几个部分。
从图中可以看出,docker 对容器的管理和操作基本都是通过 containerd 完成的。 那么,containerd 是什么呢?
Containerd 是一个工业级标准的容器运行时,它强调简单性、健壮性和可移植性。Containerd 可以在宿主机中管理完整的容器生命周期:容器镜像的传输和存储、容器的执行和管理、存储和网络等。
详细点说,Containerd 负责干下面这些事情:
runC
运行容器(与 runC
等容器运行时交互)注意:Containerd 被设计成嵌入到一个更大的系统中,而不是直接由开发人员或终端用户使用。
内存映射,其实就是将虚拟内存地址映射到物理内存地址。为了完成内存映射,内核为每个进程都维护了一张页表,记录虚拟地址与物理地址的映射关系。
当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常
为决页表项过多的问题,Linux 提供了两种机制,也就是多级页表和大页(HugePage)
分配
对小块内存(小于 128K),C 标准库使用 brk() 来分配,也就是通过移动堆顶的位置来分配内存。这些内存释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用。
brk() 方式的缓存,可以减少缺页异常的发生,提高内存访问效率。不过,由于这些内存没有归还系统,在内存工作繁忙时,频繁的内存分配和释放会造成内存碎片。
而大块内存(大于 128K),则直接使用内存映射 mmap() 来分配,也就是在文件映射段找一块空闲内存分配出去。
mmap() 方式分配的内存,会在释放时直接归还系统,所以每次 mmap 都会发生缺页异常。在内存工作繁忙时,频繁的内存分配会导致大量的缺页异常,使内核的管理负担增大。这也是 malloc 只对大块内存使用 mmap 的原因。
在内核空间,Linux 则通过 slab 分配器来管理小内存。
回收内存
回收缓存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的内存页面;
回收不常访问的内存,把不常用的内存通过交换分区直接写到磁盘中;
杀死进程,内存紧张时系统还会通过 OOM(Out of Memory),直接杀掉占用大量内存的进程。
直接内存回收,尽可能地满足新内存请求,进而回收一部分内存
kswapd0内核线程定时回收内存
kswapd0 定期扫描内存的使用情况,并根据剩余内存落在这三个阈值的空间位置,进行内存的回收操作。
剩余内存小于页最小阈值,说明进程可用内存都耗尽了,只有内核才可以分配内存。 剩余内存落在页最小阈值和页低阈值中间,说明内存压力比较大,剩余内存不多了。这时 kswapd0 会执行内存回收,直到剩余内存大于高阈值为止。 剩余内存落在页低阈值和页高阈值中间,说明内存有一定压力,但还可以满足新内存请求。 剩余内存大于页高阈值,说明剩余内存比较多,没有内存压力。
通过内核选项 /proc/sys/vm/min_free_kbytes来设置页低阈值,而其他两个阈值,都是根据页最小阈值计算生成的pages_low = pages_min*5/4, pages_high = pages_min*3/2
NUMA (Non-UniformMemory Access) 架构下的内存回收。
/proc/sys/vm/zone_reclaim_mode 来调整numa node的内存回收策略
默认的 0 ,也就是刚刚提到的模式,表示既可以从其他 Node 寻找空闲内存,也可以从本地回收内存。 1、2、4 都表示只回收本地内存,2 表示可以回写脏数据回收内存,4 表示可以用Swap 方式回收内存。
Buffers 是内核缓冲区用到的内存,对应的是 /proc/meminfo 中的 Buffers 值。 Cache 是内核页缓存和 Slab 用到的内存,对应的是 /proc/meminfo 中的 Cached 与SReclaimable 之和。
Buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。 Cached 是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。
SReclaimable 是 Slab 的一部分。Slab 包括两部分,其中的可回收部分,用SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录。
Buffer 是对磁盘数据的缓存,而 Cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。
所谓缓存命中率,是指直接通过缓存获取数据的请求次数,占所有数据请求次数的百分比。命中率越高,表示使用缓存带来的收益越高,应用程序的性能也就越好。
堆内存由应用程序自己来分配和管理。除非程序退出,这些堆内存并不会被系统自动释放,而是需要应用程序明确调用库函数 free() 来释放它们。如果应用程序没有正确释放堆内存,就会造成内存泄漏。
内存泄漏的危害非常大,这些忘记释放的内存,不仅应用程序自己不能访问,系统也不能把它们再次分配给其他应用。
属于可回收内存的缓存和缓冲区,是文件页
被应用程序修改过,并且暂时还没写入磁盘的数据就是脏页
应用程序动态分配的堆内存被称为匿名页
对文件页的回收,当然就是直接回收缓存,或者把脏页写回磁盘后再回收。 而对匿名页的回收,其实就是通过 Swap 机制,把它们写入磁盘后再释放内存。
Swap 说白了就是把一块磁盘空间或者一个本地文件(以下讲解以磁盘为例),当成内存来使用。它包括换出和换入两个过程。
/proc/sys/vm/swappiness 选项,用来调整使用 Swap 的积极程度。
swappiness 的范围是 0-100,数值越大,越积极使用 Swap,也就是更倾向于回收匿名页;数值越小,越消极使用 Swap,也就是更倾向于回收文件页。
在内存资源紧张时,Linux 会通过 Swap ,把不常访问的匿名页换出到磁盘中,下次访问的时候再从磁盘换入到内存中来。你可以设置 /proc/sys/vm/min_free_kbytes,来调整系统定期回收内存的阈值;也可以设置/proc/sys/vm/swappiness,来调整文件页和匿名页的回收倾向。
内存性能指标
├── 系统内存指标
│ ├── 已用内存
│ ├── 剩余内存
│ ├── 可用内存
│ ├── 缺页异常
│ │ ├── 主缺页异常
│ │ └── 次缺页异常
│ ├── 缓存/缓冲区
│ └── Slabs
├── 进程内存指标
│ ├── 虚拟内存(VSS)
│ ├── 常驻内存(RSS)
│ ├── 按比例分配共享内存后的物理内存(PSS)
│ ├── 独占内存(USS)
│ ├── 共享内存
│ ├── SWAP内存
│ └── 缺页异常
│ │ ├── 主缺页异常
│ │ └── 次缺页异常
└── SWAP
├── 已用空间
├── 剩余空间
├── 换入速度
└── 换出速度
系统的已用内存、剩余内存、共享内存、可用内存、缓存和缓冲区的用量
进程内存使用情况,比如进程的虚拟内存、常驻内存、共享内存以及 Swap 内存等。
缺页异常
Swap 的使用情况,比如 Swap 的已用空间、剩余空间、换入速度和换出速度等。
案例
在缓存和缓冲区的原理篇中,
我们通过 proc 文件系统,找到了内存指标的来源; 并通过 vmstat,动态观察了内存的变化情况。与 free 相比,vmstat 除了可以动态查看内存变化,还可以区分缓存和缓冲区、Swap 换入和换出的内存大小。
为了弄清楚缓存的命中情况,我们又用了 cachestat,查看整个系统缓存的读写命中情况,并用 cachetop 来观察每个进程缓存的读写命中情况。
在内存泄漏的案例中,我们用 vmstat,发现了内存使用在不断增长,又用memleak,确认发生了内存泄漏。通过 memleak 给出的内存分配栈,我们找到了内存泄漏的可疑位置。
在 Swap 的案例中,我们用 sar 发现了缓冲区和 Swap 升高的问题。通过cachetop,我们找到了缓冲区升高的根源;通过对比剩余内存跟 /proc/zoneinfo 的内存阈,我们发现 Swap 升高是内存回收导致的。案例最后,我们还通过 /proc 文件系统,找出了 Swap 所影响的进程。
内存指标 | 性能工具 |
---|---|
系统已用、可用、剩余内存 | free vmstat sar /proc/meminfo |
进程虚拟内存、常驻内存、共享内存 | ps top |
进程内存分布 | pmap |
进程Swap换出内存 | top /proc/pid/status |
进程缺页异常 | top |
系统换页情况 | sar free vmstat |
缓存/缓冲区用量 | sar cachestat |
缓存/缓冲区命中率 | cachetop free |
SWAP已用空间和剩余空间 | sar |
Swap换入换出 | vmstat memleak |
内存泄漏检测 | valgrind |
指定文件的缓存大小 | pcstat |
根据工具查指标
性能工具 | 内存指标 |
---|---|
free /proc/meminfo |
系统已用、可用、剩余内存以及缓存和缓冲区的使用量 |
top ps |
进程虚拟、常驻、共享内存以及缺页异常 |
vmstat | 系统剩余内存、缓存、缓冲区、换入、换出 |
sar | 系统内存换页情况、内存使用率、缓存和缓冲区用量以及Swap使用情况 |
cachestat | 系统缓存和缓冲区的命中率 |
cachetop | 进程缓存和缓冲区的命中率 |
slabtop | 系统Slab缓存使用情况 |
/proc/pid/status | 进程Swap内存等 |
/proc/pid/smaps | 进程地址空间和内存状态 |
pmap | 进程内存错误检查器,用来检测内存初始化、 |
valgrind | 泄漏、越界访问等各种内存错误 |
memleak | 内存泄漏检测 |
pcstat | 查看指定文件的缓存情况 |
快速分析内存的性能瓶颈
分析流程图
显示Linux系统中空闲的、已用的物理内存及swap内存,及被内核使用的buffer
常用命令
free
free -m
free -g
指标含义
total:总计物理内存的大小。
used:已使用多大。
free:可用有多少。
Shared:多个进程共享的内存总额。
Buffers/cached:磁盘缓存的大小。
available
提供了整个操作系统缓存的读写命中情况。 需要安装 bcc 软件包 。
常用命令
cachestat 1 3
TOTAL MISSES HITS DIRTIES BUFFERS_MB CACHED_MB
2 0 2 1 17 279
2 0 2 1 17 279
2 0 2 1 17 279
指标含义
TOTAL 表示总的 I/O 次数
MISSES 表示缓存未命中的次数
HITS 表示缓存命中的次数
DIRTIES 表示新增到缓存中的脏页数
BUFFERS_MB 表示 Buffers 的大小,以 MB 为单位
CACHED_MB 表示 Cache 的大小,以 MB 为单位
提供了每个进程的缓存命中情况。 需要安装 bcc 软件包 。
cachetop
11:58:50 Buffers MB: 258 / Cached MB: 347 / Sort: HITS / Order: ascending
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
13029 root python 1 0 0 100.0% 0.0%
指标含义
默认按照缓存的命中次数(HITS)排序,
HITS 表示缓存命中的次数
MISSES 表示缓存未命中的次数
DIRTIES 表示新增到缓存中的脏页数
READ_HIT 表示读的缓存命中率
WRITE_HIT 表示写的缓存命中率
以实时的方式显示内核“slab”缓冲区的细节信息
常用命令
slabtop
slabtop -d 1 # 1秒钟刷新一次数据
slabtop -s c # 按照cache size排序
用于显示一个或多个进程的内存状态
常用命令
pmap $PID
名称解释
Address: 内存开始地址
Kbytes: 占用内存的字节数(KB)
RSS: 保留内存的字节数(KB)
Dirty: 脏页的字节数(包括共享和私有的)(KB)
Mode: 内存的权限:read、write、execute、shared、private (写时复制)
Mapping: 占用内存的文件、或[anon](分配的内存)、或[stack](堆栈)
Offset: 文件偏移
Device: 设备名 (major:minor)
valgrind是一个专业的程序优化和内存调试工具集。
常用的工具包括
memcheck
,cachegrind
,callgrind
,massif
,helgrind
和drd
。
安装
yum -y install make gcc autoconf automake
wget https://sourceware.org/pub/valgrind/valgrind-3.15.0.tar.bz2
tar -xjvf valgrind-3.15.0.tar.bz2
cd valgrind-3.15.0
./autogen.sh
./configure
make
make install
valgrind --version
常用命令
valgrind # 使用Memcheck(默认)工具显示program对内存使用情况的诊断
valgrind --leak-check=full --show-leak-kinds=all # 使用Memcheck详细输出program的所有可能的内存泄漏
valgrind --tool=cachegrind # 使用Cachegrind工具来分析和记录program的CPU缓存操作
valgrind --tool=massif --stacks=yes # 使用Massif工具来分析和记录program的堆内存和堆栈使用情况
memleak跟踪内存分配和回收请求,收集每次分配的调用栈。 属于bcc套件
/usr/share/bcc/tools/memleak -a -p ${pid}
可以查看某个文件是否被缓存 https://github.com/tobert/pcstat
pcstat /bin/ls
参考 极客时间专栏《Linux 性能优化实战》
平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数
CPU上下文(context switch)切换就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
所以,根据任务的不同,CPU 的上下文切换就可以分为几个不同的场景,也就是进程上下文切换、线程上下文切换以及中断上下文切换。
从用户态到内核态的转变,需要通过系统调用来完成。比如,当我们查看文件内容时,就需要多次系统调用来完成:首先调用 open() 打开文件,然后调用 read() 读取文件内容,并调用 write() 将内容写到标准输出,最后再调用 close() 关闭文件。
CPU 寄存器里原来用户态的指令位置,需要先保存起来。接着,为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。而系统调用结束后,CPU 寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以,一次系统调用的过程,其实是发生了两次 CPU 上下文切换。
系统调用过程通常称为特权模式切换,而不是上下文切换。但实际上,系统调用过程中,CPU 的上下文切换还是无法避免的。
监控supervisor管理的进程状态,在进程异常退出的时候给出报警。
将进程的状态信息数据发送给Prometheus,通过Prometheus进行报警。
上一篇文章利用 Supervisor 的 Event & Listener 监控进程并报警 已经完全满足需求了,那为啥还要这种方式么?我想有两个点吧,1. 内部环境中存在Prometheus监控报警体系,将报警统一到平台中,方便管理。2.通过平台可以看到进程的趋势图,比如进程的cpu,内存。。不过这个方案也有个缺点,就是报警不及时。有人人说了,node_exporter也可以监控supervisor进程呀。为啥非要自己开发,我只能说,方案千千万,适合自己最好。
监控supervisor管理的进程状态,在进程异常退出的时候给出报警。
利用 Supervisor 的 Event & Listener 功能进行订阅异常退出事件,并进行报警处理。
Supervisor 官方对其 Event 机制的描述是:一个进程的监控/通知框架。
该机制主要通过一个 event listener 订阅 event 通知实现。当被 Supervisor 管理的进程有特定行为的时候,supervisor 就会自动发出对应类型的 event。即使没有配置 listener,这些 event 也是会发的;如果配置了 listener 并监听该类型的 event,那么这个 listener 就会接收到该 event。 event listener 需要自己实现,并像 program 一样,作为 superviosr 的子进程运行。
一直想做个关于资源巡检的功能,其需求就是通过邮件的形式来查看linux资源的使用情况,超出一定的阈值时高亮显示出来。也有人说啦,这个需求通过监控zabbix
, prometheus
都能做呀,何必自己重复造轮子做这些啊。我就是瞎折腾呗,只能说巡检报告是一总主动探测系统资源的一种手段,一般公司监控,外部都不能直接访问的,需要拨通vpn才可以,有些情况我们是无法连接到监控平台,比如放假游玩,不想打开电脑…这些情况下通过每天的巡检报告可以随时的了解系统资源的情况。
本次使用全手工的方式以kubeadm形式部署kubernetes的ha集群,ha方式选择node节点代理apiserver的方式。
本次我们将实际利用Kubeadm工具来更新集群,这边沿用之前分享的使用kubeadm安装Kubernetes v15.4 ha集群,部署的HA丛集来进行v1.15.4
更新至 v1.16.0
。