进程
strace
Copy # ps -ef | grep c-init-sig
root 15857 14391 0 06:23 pts/0 00:00:00 docker run -it registry/fwd_sig:v1 /c-init-sig
root 15909 15879 0 06:23 pts/0 00:00:00 /c-init-sig
root 15959 15909 0 06:23 pts/0 00:00:00 /c-init-sig
root 16046 14607 0 06:23 pts/3 00:00:00 grep --color=auto c-init-sig
# 15909 是容器内的 init 进程
# strace -p 15909
strace: Process 15909 attached
restart_syscall( <... resuming interrupted read .. . > ) = ? ERESTART_RESTARTBLOCK ( Interrupted by signal )
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=0, si_uid= 0 } ---
write(1, "received SIGTERM\n" , 17 ) = 17
exit_group(0 ) = ?
+++ exited with 0 +++
# 15959 是容器内由 init 进程创建的其他进程
# strace -p 15959
strace: Process 15959 attached
restart_syscall( <... resuming interrupted read .. . > ) = ?
+++ killed by SIGKILL +++
在每个 Linux 发行版都有这个工具,安装方式:
Ubuntu:apt install linux-tools-common
对于定位 CPU Usage 异常,查找高 CPU 使用率情况下的热点函数,perf
显然是最有力的工具。在 CPU Usage 增高的节点上找到具体的引起 CPU 增高的函数,然后就可以有针对性地聚焦到那个函数做分析。
通常分为三个步骤:
抓取数据(pref record
),命令运行结束后会在磁盘的当前目录留下 perf.data
文件,记录了所有采样得到的信息。
数据读取(pref script
和 pref report
),把 perf.data
转化成分析脚本,然后用 FlameGraph 工具来读取脚本,生成火焰图。
Copy # record
pref record -a -g -p < pi d >
## -a 获取所有 CPU Core 上函数运行情况
# record target cpu
perf record -C 32 -g -- sleep 10
## -C 指定只抓取 CPU32 的执行指令
## -g 表示 call-graph enable,也就是记录函数调用关系
## sleep 10 为了让 pref 抓取 10秒钟的数据
# perf record -a -g -- sleep 60
# perf script > out.perf
# git clone --depth 1 https://github.com/brendangregg/FlameGraph.git
# FlameGraph/stackcollapse-perf.pl out.perf > out.folded
# FlameGraph/flamegraph.pl out.folded > out.sv
第一次上手使用时,可以先运行一下 perf list
命令,会看到列出了大量的 event,event 是 perf 工作的基础 ,主要有两种:
Copy # perf list
…
branch-instructions OR branches [Hardware event]
branch-misses [Hardware event]
alignment-faults [Software event]
bpf-output [Software event]
…
block:block_bio_bounce [Tracepoint event]
block:block_bio_complete [Tracepoint event]
Hardware event 来自处理器中的一个 PMU(Performance Monitoring Unit),这些 event 数目不多,都是底层处理器相关的行为,perf 中会命名几个通用的事件,比如 cpu-cycles
,执行完成的 instructions,Cache 相关的 cache-misses
。运行一下 perf stat
,可以看到在这段时间里这些 Hardware event 发生的数目。
Copy # perf stat
Performance counter stats for 'system wide' :
58667.77 msec cpu-clock # 63.203 CPUs utilized
258666 context-switches # 0.004 M/sec
2554 cpu-migrations # 0.044 K/sec
30763 page-faults # 0.524 K/sec
21275365299 cycles # 0.363 GHz
24827718023 instructions # 1.17 insn per cycle
5402114113 branches # 92.080 M/sec
59862316 branch-misses # 1.11% of all branches
0.928237838 seconds time elapsed
Software event 是定义在 Linux 内核代码中的几个特定的事件,比较典型的有进程上下文切换(内核态到用户态的转换)事件 context-switches
、发生缺页中断的事件 page-faults
等。
Tracepoints event 在 perf list 中数量众多,因为内核中很多关键函数里都有 Tracepoints。它的实现方式和 Software event 类似,都是在内核函数中注册了 event。这些 Tracepoints 不仅是用在 perf 中,它已经是 Linux 内核 tracing 的标准接口了,ftrace,ebpf 等工具都会用到它。
pref 使用这些 event 的方式有两种,计数和采样。
计数,统计某个 event 在一段时间里发生了多少次(pref stats
)
采样,按照一定规则抽样(pref record
在不加 -e 指定 event 的时候,缺省的 event 是 Hardware event cycles )
Copy # perf stat -e page-faults -- sleep 1
## -e 指定要查看的 event
Performance counter stats for 'sleep 1' :
49 page-faults
1.001583032 seconds time elapsed
0.001556000 seconds user
0.000000000 seconds sys
Perf 对 event 的采样有两种模式:
按照 event 的数目(period),比如每发生 10000 次 cycles event 就记录一次 IP、进程等信息,-c
参数可以指定每发生多少次,就做一次记录。
定义一个频率(frequency), -F
参数就是指定频率的。
Copy # perf record -e cycles -c 10000 -- sleep 1
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.024 MB perf.data ( 191 samples) ]
# perf record -e cycles -F 99 -- sleep 1
## 指采样每秒钟做 99 次。
最理想的情况是直接在宿主机上运行 pref 命令,容器中使用 pref 的注意事项:
perf 是和 Linux kernel 一起发布的,版本最好是和 Linux kernel 一样,不然可能无法正常工作,可以直接从源代码编译出静态链接的 perf
。
系统调用权限问题,perf
通过系统调用 perf_event_open()
来完成对 event 的计数或者采样。Docker 使用 seccomp 默认禁止这个系统调用,可以在启动容器的命令里,加上参数"--security-opt seccomp=unconfined
" 来解决。
需要允许容器在没有 SYS_ADMIN
这个 capability 的情况下,也可以让 perf 访问 event。在宿主机上设置 echo -1 > /proc/sys/kernel/perf_event_paranoid
,这样普通的容器里也能执行 perf 。
ftrace
ftrace(function tracer)的操作都可以在 tracefs 这个虚拟文件系统中完成,对于 CentOS,这个 tracefs 的挂载点在 /sys/kernel/debug/tracing
。
Copy # cat /proc/mounts | grep tracefs
tracefs /sys/kernel/debug/tracing tracefs rw,relatime 0 0
# ls /sys/kernel/debug/tracing
available_events dyn_ftrace_total_info kprobe_events saved_cmdlines_size set_graph_notrace trace_clock tracing_on
available_filter_functions enabled_functions kprobe_profile saved_tgids snapshot trace_marker tracing_thresh
available_tracers error_log max_graph_depth set_event stack_max_size trace_marker_raw uprobe_events
buffer_percent events options set_event_pid stack_trace trace_options uprobe_profile
buffer_size_kb free_buffer per_cpu set_ftrace_filter stack_trace_filter trace_pipe
buffer_total_size_kb function_profile_enabled printk_formats set_ftrace_notrace synthetic_events trace_stat
current_tracer hwlat_detector README set_ftrace_pid timestamp_mode tracing_cpumask
dynamic_events instances saved_cmdlines set_graph_function trace tracing_max_latency
通过 cat trace
命令可以查看 ftrace 的输出结果,每一行的输出就是当前内核中被调用到的内核函数。在缺省 状态下 ftrace 的 tracer 是 nop
,也就是什么都不做。通过向挂载点内的文件执行echo
命令,来向内核的 ftrace 系统发送命令,通过 cat
文件的方式查看 ftrace 的设置结果。
Copy # cat current_tracer
nop
# cat available_tracers
hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
# echo function > current_tracer
# echo nop > current_tracer ### 暂时把 current_tracer 设置为 nop, 这样可以清空 trace
# cat current_tracer
function
利用 ftrace 里的 filter 参数做筛选:
通过 set_ftrace_filter
只列出想看到的内核函数,
通过 set_ftrace_pid
只列出想看到的进程。
如果想要看更加完整的函数调用栈,可以打开 ftrace 中的 func_stack_trace
选项。
Copy # echo do_mount > set_ftrace_filter
# echo '!do_mount ' >> set_ftrace_filter ### 把 do_mount filter 给去掉
# mount -t tmpfs tmpfs /tmp/fs
# cat trace
# tracer: function
#
# entries-in-buffer/entries-written: 3/3 #P:12
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
mount-20889 [005] .... 2159455.499195: do_mount < -ksys_mount
mount-21048 [000] .... 2162013.660835: do_mount < -ksys_mount
mount-21048 [000] .... 2162013.660841: < stack trace >
= > do_mount
= > ksys_mount
= > __x64_sys_mount
= > do_syscall_64
= > entry_SYSCALL_64_after_hwframe
通过 function tracer 可以帮我们判断内核中函数是否被调用到,以及函数被调用的整个路径 也就是调用栈。
如果通过 perf
发现了一个内核函数的调用频率比较高,就可以通过 ftrace
工具继续深入,这样就能大概知道这个函数是在什么情况下被调用到的。
如果还想知道,某个函数在内核中大致花费了多少时间,把 ftrace 的 tracer 设置为 function_graph
,通过这个办法查看内核函数的调用时间。
Copy # echo function_graph > current_tracer
# echo 1 > tracing_on
# umount /tmp/fs
# mount -t tmpfs tmpfs /tmp/fs
# cat trace
# tracer: function_graph
#
# CPU DURATION FUNCTION CALLS
# | | | | | | |
0 ) ! 175.411 us | do_mount () ;
通过 function_graph
tracer,还可以看到每个函数里所有子函数的调用以及时间,这对理解和分析内核行为都是很有帮助的。
Copy # cat trace | more
# tracer: function_graph
#
# CPU DURATION FUNCTION CALLS
# | | | | | | |
0 ) | kfree_skb () {
0 ) | skb_release_all () {
0 ) | skb_release_head_state () {
0 ) | nf_conntrack_destroy () {
0 ) | destroy_conntrack [nf_conntrack ] () {
0 ) 0.205 us | nf_ct_remove_expectations [nf_conntrack ] () ;
0 ) | nf_ct_del_from_dying_or_unconfirmed_list [nf_conntrack ] () {
0 ) 0.282 us | _raw_spin_lock () ;
0 ) 0.679 us | }
0 ) 0.193 us | __local_bh_enable_ip () ;
0 ) | nf_conntrack_free [nf_conntrack ] () {
0 ) | nf_ct_ext_destroy [nf_conntrack ] () {
0 ) 0.177 us | nf_nat_cleanup_conntrack [nf_nat ] () ;
0 ) 1.377 us | }
0 ) | kfree_call_rcu () {
0 ) | __call_rcu () {
0 ) 0.383 us | rcu_segcblist_enqueue () ;
0 ) 1.111 us | }
0 ) 1.535 us | }
0 ) 0.446 us | kmem_cache_free () ;
0 ) 4.294 us | }
0 ) 6.922 us | }
0 ) 7.665 us | }
0 ) 8.105 us | }
0 ) | skb_release_data () {
0 ) | skb_free_head () {
0 ) 0.470 us | page_frag_free () ;
0 ) 0.922 us | }
0 ) 1.355 us | }
0 ) + 10.192 us | }
0 ) | kfree_skbmem () {
0 ) 0.669 us | kmem_cache_free () ;
0 ) 1.046 us | }
0 ) + 13.707 us | }
磁盘
fio
磁盘性能测试工具。
Copy # fio -direct=1 -iodepth=64 -rw=read -ioengine=libaio -bs=4k -size=10G -numjobs=1 -name=./fio.test
# -direct=1 表示采用非 buffered I/O 文件读写方式,避免文件读写过程中内存缓冲对性能的影响
# -iodepth=64 同时发起 64 个 I/O 请求
# -ioengine-libaio 表示文件读写采用异步 I/O(Async I/O) 的方式,进程可以发起多个 I/O 请求,并且不用阻塞地等待 I/O 的完成,等 I/O 完成之后,进程会收到通知。
# -rw=read 表示读文件测试
# -bs=4k 表示每次读 4KB 大小数块
# -size=10G 表示总共读 10GB 的数据
# -numjobs=1 表示只有一个进程/线程在运行
iostat
查看实际的磁盘写入速度。
xfs_quota
目录静态容量大小限制。
Copy # check if project quota feature enabled
cat /proc/mounts | grep prjquota
# make directory
mkdir -p /tmp/xfs_prjquota
# set project id
xfs_quota -x -c 'project -s -p /tmp/xfs_prjquota 101' /
# set quota
xfs_quota -x -c 'limit -p bhard=10m 101' /
# write data
dd if=/dev/zero of=/tmp/xfs_prjquota/test.file bs= 1024 count= 20000
# check file size
ls -l /tmp/xfs_prjquota/test.file
网络
namespace
Copy # list network namespace
lsns -t net
# list all namespace
lsns
# enter namesapce
nsenter -t < PI D > -n < comman d >
nsenter --target $( docker inspect -f {.State.Pid} ) --net
ip
Copy docker run --init --name lat-test-1 --network none -d registry/latency-test:v1 sleep 36000
# ---Set veth---
pid = $( ps -ef | grep "sleep 36000" | grep -v grep | awk '{print $2}' )
echo $pid
# Get network namespace id by pid then link pid and network namesapce id
ln -s /proc/ $pid /ns/net /var/run/netns/ $pid
# Create a pair of veth interfaces
ip link add name veth_host type veth peer name veth_container
# Put one of them in the new net ns
ip link set veth_container netns $pid
# In the container, setup veth_container
ip netns exec $pid ip link set veth_container name eth0
ip netns exec $pid ip addr add 172.17.1.2/16 dev eth0
ip netns exec $pid ip link set eth0 up
ip netns exec $pid ip route add default via 172.17.0.1
# In the host, set veth_host up
ip link set veth_host up
# set veth_host into docker0 bridge
ip link set veth_host master docker0
# ---Set ipvlan---
pid1 = $( docker inspect lat-test-1 | grep -i Pid | head -n 1 | awk '{print $2}' | awk -F "," '{print $1}' )
echo $pid1
ln -s /proc/ $pid1 /ns/net /var/run/netns/ $pid1
ip link add link eth0 ipvt1 type ipvlan mode l2
ip link set dev ipvt1 netns $pid1
ip netns exec $pid1 ip link set ipvt1 name eth0
ip netns exec $pid1 ip addr add 172.17.3.2/16 dev eth0
ip netns exec $pid1 ip link set eth0 up
tcpdump
Copy # container eth0
ip netns exec $pid tcpdump -i eth0 host < target ip add r > -nn
# veth_host
tcpdump -i veth_host host < target ip add r > -nn
# docker0
tcpdump -i docker0 host < target ip add r > -nn
# host eth0
tcpdump -i eth0 host < target ip add r > -nn
衡量网络性能的工具,它可以提供单向吞吐量和端到端延迟的测试。
TCP_RR 是 netperf 里专门用来测试网络延时的,缺省每次运行 10 秒钟。运行以后,计算平均每秒钟 TCP request/response 的次数,这个次数越高,就说明延时越小。
Copy # ./netperf -H 192.168.0.194 -t TCP_RR
MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 192.168.0.194 () port 0 AF_INET : first burst 0
Local /Remote
Socket Size Request Resp. Elapsed Trans.
Send Recv Size Size Time Rate
bytes Bytes bytes bytes secs. per sec
16384 131072 1 1 10.00 2504.92
16384 131072
# ./netperf -H 192.168.0.194 -t TCP_RR
MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 192.168.0.194 () port 0 AF_INET : first burst 0
Local /Remote
Socket Size Request Resp. Elapsed Trans.
Send Recv Size Size Time Rate
bytes Bytes bytes bytes secs. per sec
16384 131072 1 1 10.00 2410.14
16384 131072
# ./netperf -H 192.168.0.194 -t TCP_RR
MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 192.168.0.194 () port 0 AF_INET : first burst 0
Local /Remote
Socket Size Request Resp. Elapsed Trans.
Send Recv Size Size Time Rate
bytes Bytes bytes bytes secs. per sec
16384 131072 1 1 10.00 2422.81
16384 131072
iperf3
从 iperf3 的输出 "Retr
" 列里,可以看到有多少重传的数据包。
Copy # iperf3 -c 192.168.147.51
Connecting to host 192.168.147.51, port 5201
[ 5] local 192.168.225.12 port 51700 connected to 192.168.147.51 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 1001 MBytes 8.40 Gbits/sec 162 192 KBytes
…
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 9.85 GBytes 8.46 Gbits/sec 162 sender
[ 5] 0.00-10.04 sec 9.85 GBytes 8.42 Gbits/sec receiver
iperf Done.
ipvsadm
查看节点上 IPVS 规则的数目。
Copy # ipvsadm -L -n | wc -l
79004
安全
配置 Linux capabilities 的工具。
Copy # remove CAP_NET_ADMIN linux capabilities
sudo /usr/sbin/capsh --keep=1 --user=root --drop=cap_net_admin -- -c './iptables -L;sleep 100'
Chain INPUT (policy ACCEPT )
target prot opt source destination
Chain FORWARD (policy ACCEPT )
target prot opt source destination
Chain OUTPUT (policy ACCEPT )
target prot opt source destination
iptables: Permission denied (you must be root ).
# get process id
ps -ef | grep sleep
root 22603 22275 0 19:44 pts/1 00:00:00 sudo /usr/sbin/capsh --keep=1 --user=root --drop=cap_net_admin -- -c ./iptables -L ; sleep 100
root 22604 22603 0 19:44 pts/1 00:00:00 /bin/bash -c ./iptables -L ; sleep 100
# check process linux capabilities
cat /proc/22604/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000003fffffefff
CapEff: 0000003fffffefff # Effective capability sets
CapBnd: 0000003fffffefff
CapAmb: 0000000000000000
getcap
查看文件的 capabilities。
setcap
给文件设置 capabilities。
Copy setcap -r $( which ping )