容器学习--进程、内存、容器存储、网络_/proc/net/dev能映射到容器内吗-程序员宅基地

技术标签: 读书笔记  linux  namespace  docker  

  • 来源:https://time.geekbang.org/column/intro/365

容器

  • 镜像:就是一个特殊的文件系统,它提供了容器中程序执行需要的所有文件。
  • 容器所有的进程调度,内存访问,文件的读写都直接跑在宿主机的内核之上
  • 容器是什么:Namespace和Cgroups,它们可以让程序在一个资源可控的独立(隔离)环境中运行,这个就是容器。
  • Namespace:
    • Linux在创建容器的时候,就会创建出一个PID Namespace,PID其实就是进程的编号。这个PID Namespace,就是指每建立出一个Namespace,就会单独对进程进行PID编号,每个Namespace的PID编号从1开始
    • Namespace其实就是一种隔离机制,主要目的是隔离运行在同一个宿主机上的容器,让这些容器之间不能访问彼此的资源。隔离的作用:
      • 第一可以充分利用系统的资源,也就是说在同一台宿主机上可以运行多个用户的容器;
      • 第二保证了安全性,因为不同用户之间不能访问对方的资源
    • 文件系统隔离 Mount Namespace
    • 网络隔离 Network Namespace
  • Cgroups:对指定进程的各种计算机资源的限制,比如限制CPU的使用率,内存使用量,IO设备的流量等等。
    • Cgroups通过不同的子系统限制不同的资源,每个子系统限制一种资源。每个子系统限制资源的方式都是类似的,就是把相关的一组进程分配到一个控制组里,然后通过树结构进行管理,每个控制组都设有自己的资源控制参数。
    • CPU子系统:一个控制组(一组进程,可以理解为一个容器里的所有进程)可使用的最大CPU。
    • memory子系统:一个控制组最大的内存容量
    • pids子系统:限制一个控制组里最多可以运行多少进程。
    • cpuset子系统:限制一个进程组里的进程可以在那几个物理CPU上运行

进程

  • init进程。1号进程,它最基本的功能都是创建出 Linux 系统中其他所有的进程,并且管理这些进程。
# CentOS Linux release 8.2.2004 (Core)
$ ls -la /sbin/init
lrwxrwxrwx 1 root root 22 Jul 21  2020 /sbin/init -> ../lib/systemd/systemd
  • 信号:Linux收到的一个通知.Ctrl+C SIGINT(2)

  • 进程收到信号之后的处理:

    • 忽略(Ignore)。SIGKILL和SIGSTOP这两个信号,进程是不能忽略的。
    • 捕获(Catch)。用户进程可以注册自己针对这个信号的handler。SIGKILL和SIGSTOP这两个信号也不能捕获,只能执行系统的缺省行为。
    • 缺省行为(Default)。
  • 特权信号【SIGKILL和SIGSTOP】就是 Linux 为 kernel 和超级用户去删除任意进程所保留的,不能被忽略也不能被捕获。那么进程一旦收到 SIGKILL,就要退出。

  • 容器里 1 号进程对信号处理的两个要点,这也是这一讲里我想让你记住的两句话:

    • 在容器中,1 号进程永远不会响应 SIGKILL 和 SIGSTOP 这两个特权信号;
    • 对于其他的信号,如果用户自己注册了 handler,1 号进程可以响应。
  • 进程限制:内核进程数限制:/proc/sys/kernel/pid_max

# 容器进程限制
/sys/fs/cgroup/pids/docker/2cfdc5833a8d07f1739978239b6d7d647b67e5b3e6cee1739239f30a210c1aee
echo 1002 > pids.max # 进程数限制
  • 每一个 Linux 进程在退出的时候都会进入一个僵尸状态(EXIT_ZOMBIE);

  • 僵尸进程一定需要父进程调用 wait() 或者 waitpid() 系统调用来清理,这也是容器中 init 进程必须具备的一个功能。

  • Containerd 在停止容器的时候,就会向容器的 init 进程发送一个 SIGTERM 信号。我们会发现,在 init 进程退出之后,容器内的其他进程也都立刻退出了。不过不同的是,init 进程收到的是 SIGTERM 信号,而其他进程收到的是 SIGKILL 信号

  • cpu限制

    • cpu监控含义

    • 每个进程的 CPU Usage 只包含用户态(us 或 ni)和内核态(sy)两部分,其他的系统 CPU 开销并不包含在进程的CPU使用中,而CPU Cgroup只是对进程的 CPU 使用做了限制。

    • cpu.cfs_quota_us(一个调度周期里这个控制组被允许的运行时间)除以 cpu.cfs_period_us(用于设置调度周期)得到的这个值决定了CPU Cgroup每个控制组中 CPU 使用的上限值。

    • cpu.shares 参数,正是这个值决定了CPU Cgroup子系统下控制组可用CPU的相对比例,当系统上 CPU 完全被占满的时候,这个比例才会在各个控制组间起效。

    • Kubernetes中,Limit CPU 就是容器所在Cgroup控制组中的CPU上限值,Request CPU的值就是控制组中的cpu.shares的值。

  • CPU利用率计算

    • Linux里获取CPU使用率的工具,比如top,都是通过读取proc文件系统下的stat文件来得到CPU使用了多少ticks。而这里的ticks,是Linux操作系统里的一个时间单位,可以理解成类似秒,毫秒的概念。由于 /proc/stat 文件是整个节点全局的状态文件,不属于任何一个Namespace,因此在容器中无法通过读取 /proc/stat 文件来获取单个容器的 CPU 使用率。
    • 对于top命令来说,它只能显示整个节点中各项CPU的使用率,不能显示单个容器的各项 CPU 的使用率
    • 单个容器 CPU 使用率 =((utime_2 – utime_1) + (stime_2 – stime_1)) * 100.0 / (HZ * et * 1 )。
      • utime 是表示进程的用户态部分在Linux调度中获得CPU的ticks,stime是表示进程的内核态部分在Linux调度中获得CPU的ticks。
      • ((utime_2 – utime_1) + (stime_2 – stime_1)) 是瞬时进程总的 CPU ticks
      • HZ:Linux固定频率ticks,默认100
      • et 是我们刚才说的那个“瞬时”的时间,也就是得到utime_1和utime_2这两个值的时间间隔。
      • 1 CPU数
      • 也可可简化为进程的CPU使用率 =(进程的ticks/单个CPU总ticks)*100.0
  • load average:Linux进程调度器中可运行队列(Running Queue)的进程平均数和休眠队列(Sleeping Queue)里的一段时间的TASK_UNINTERRUPTIBLE状态下的进程平均数之和。

    • 即Load Average= 可运行队列进程平均数 + 休眠队列中不可打断的进程平均数
    • 如果只考虑CPU的资源,load Average等于单位时间内正在运行的进程加上可运行队列的进程
      • 第一,不论计算机CPU是空闲还是满负载,Load Average都是Linux进程调度器中可运行队列(Running Queue)里的一段时间的平均进程数目。
      • 第二,计算机上的CPU还有空闲的情况下,CPU Usage 可以直接反映到"load average"上,什么是 CPU 还有空闲呢?具体来说就是可运行队列中的进程数目小于CPU个数,这种情况下,单位时间进程 CPU Usage 相加的平均值应该就是"load average"的值。
      • 第三,计算机上的CPU满负载的情况下,计算机上的CPU已经是满负载了,同时还有更多的进程在排队需要CPU资源。这时"load average"就不能和CPU Usage等同了。
    • TASK_UNINTERRUPTIBLE 是Linux进程状态的一种,是进程为等待某个系统资源而进入了睡眠的状态,并且这种睡眠的状态是不能被信号打断的。
    • 当进程处于TASK_UNINTERRUPTIBLE状态时[D]时,此时资源(磁盘I/O、信号量等)处于竞争状态,如果很多进程处于这个等待状态,这会在应用程序的最终性能上体现出来,也就是说用户会发觉应用的性能下降了。
    • Cgroups 更多的是以进程为单位进行隔离,而D状态进程是内核中系统全局资源引入的,所以Cgroups影响不了它。 所以我们可以做的是,在生产环境中监控容器的宿主机节点里D状态的进程数量,然后对D状态进程数目异常的节点进行分析,比如磁盘硬件出现问题引起D状态进程数目增加,这时就需要更换硬盘。
    • 如果打个比方来说明 Load Average 的统计原理。你可以想象每个 CPU 就是一条道路,每个进程都是一辆车,怎么科学统计道路的平均负载呢?就是看单位时间通过的车辆,一条道上的车越多,那么这条道路的负载也就越高。此外,Linux 计算系统负载的时候,还额外做了个补丁把 TASK_UNINTERRUPTIBLE 状态的进程也考虑了,这个就像道路中要把红绿灯情况也考虑进去。一旦有了红灯,汽车就要停下来排队,那么即使道路很空,但是红灯多了,汽车也要排队等待,也开不快。
    • Linux状态:
      • R(TASK_RUNNING):可执行队列中的进程状态,包含正在运行的和准备运行的。其他教科书上所说的READY状态也包含在R状态里。
      • S(TASK_INTERRUPTIBLE):可中断的睡眠状态,可以被信号和wake_up()唤醒的,当信号到来时,进程会被设置为可运行。
      • D(TASK_UNINTERRUPTIBLE):不可中断睡眠状态,只能被wake_up()唤醒。kill对其无效。
    • TASK_UNINTERRUPTIBLE的意义
      • TASK_UNINTERRUPTIBLE存在的意义就在于,内核的某些处理流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程(这个插入的流程可能只存在于内核态,也可能延伸到用户态),于是原有的流程就被中断了。在对某些硬件进行操作时(比如进程调用read系统调用对某个设备文件进行读操作,而read系统调用最终执行到对应设备驱动的代码,并与对应的物理设备进行交互),可能需要TASK_UNINTERRUPTIBLE状态对进程进行保护,以避免进程与设备交互的过程被打断,造成设备陷入不可控的状态。
      • 通常情况下TASK_UNINTERRUPTIBLE状态是非常短暂的,通过ps命令基本上不可能捕捉到。进程又是为什么会被置于 uninterruptible sleep 状态呢?处于 uninterruptible sleep 状态的进程通常是在等待 IO,比如磁盘 IO,网络 IO,其他外设 IO,如果进程正在等待的 IO 在较长的时间内都没有响应,很有可能有 IO 出了问题,可能是外设本身出了故障,也可能是比如挂载的远程文件系统NFS等已经不可访问了,那么就很会不幸地被 ps 看到进程状态位已经变成D。
      • 正是因为得不到 IO 的相应,进程才进入了 uninterruptible sleep 状态,所以要想使进程从 uninterruptible sleep 状态恢复,就得使进程等待的 IO 恢复,比如如果是因为从远程挂载的 NFS 卷不可访问导致进程进入 D状态的,那么可以通过恢复该 NFS 卷的连接来使进程的 IO 请求得到满足,除此之外,要想干掉处在 D 状态进程就只能重启整个 Linux 系统了。如果为了想要杀掉 D 状态的进程,而去杀掉它的父进程(通常是shell,在shell下允许某进程,然后某进程转入D状态),就会出现这样的状态:他们的父进程被杀掉了,但是他们的父进程 PID 都变成了1,也就是 init 进程,D状态的进程会变成僵尸进程。

内存

  • 内存限制 /sys/fs/cgroup/memory
    • memory.limit_in_bytes:控制组里所有进程可使用的内存最大数
    • memory.oom_control:当控制组中的进程内存达到了上限值时,这个参数能够决定会不会触发OOM Killer,默认会触发。
    • memory.usage_in_bytes:当前控制组里所有进程实际使用的内存
  • OOM:用系统总的可用页面数,去乘以OOM校准值oom_score_adj,再加上进程已经使用的物理页面数,计算出来的值越大,那么这个进程被OOM Kill的几率也就越大。
  • 内存类型:比如内核需要分配内存给页表,内核栈,还有slab,也就是内核各种数据结构的Cache Pool;用户态进程里的堆内存和栈的内存,共享库的内存,还有文件读写的Page Cache。
    • RSS:进程真正申请到物理页面的内存大小。对于进程来说,RSS内存包含了进程的代码段内存,栈内存,堆内存,共享库的内存, 这些内存是进程运行所必须的。通过malloc/memset得到的内存,就是属于堆内存。
    • page cache:磁盘上读写到的页面放入内存,这部分内存就是page cache。
  • Memory Cgroup控制组里RSS内存和Page Cache内存的和,正好是memory.usage_in_bytes的值。
  • 容器里 /sys/fs/cgroup/memory/memory.stat rss查看实际使用的内存
  • swap:容器
    • 在linux中,swappiness的取值范围在0到100,值为100的时候系统平等回收匿名内存和Page Cache内存;一般缺省值为60,就是优先回收Page Cache;即使swappiness为0,也不能完全禁止Swap分区的使用,就是说在内存紧张的时候,也会使用Swap来回收匿名内存。
    • 在容器中,当memory.swappiness=0的时候,对匿名页的回收是始终禁止的,也就是始终都不会使用Swap空间
    [1217562.233709] Memory cgroup out of memory: Killed process 3017715 (mem_alloc) 
    total-vm:2060328kB, anon-rss:497240kB, file-rss:1008kB, shmem-rss:0kB, UID:0
    [1217562.296557] oom_reaper: reaped process 3017715 (mem_alloc), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
    
    • 我们还是可以在宿主机上打开swap空间,同时在其他容器对应的Memory Cgroups控制组里,把memory.swappiness设为0,让容器不使用swap,满足memory.limit_in_bytes来限制内存的使用。

容器存储

  • 容器文件系统:减少相同镜像文件在同一个节点上的数据冗余,可以节省磁盘空间,也可以减少镜像文件下载占用的网络资源。
  • 作为容器文件系统,UnionFS通过多个目录挂载的方式工作。OverlayFS就是UnionFS的一种实现,是目前主流Linux发行版中缺省使用的容器文件系统。
  • OverlayFs也是把多个目录合并挂载,被挂载的目录分为两类:lowerdir和upperdir
    • lowerdir允许有多个目录,在被挂载后,这些目录里的文件都是不会被修改或者删除的,也就是只读的
    • upperdir只有一个,不过这个目录是可读写的,挂载点目录中的所有文件修改都会在upperdir中反映出来。
  • 容器的镜像文件中各层正好作为OverlayFS的lowerdir的目录,然后加上一个空的upperdir一起挂载好后,就组成了容器的文件系统。
#!/bin/bash

umount ./merged
rm upper lower merged work -r

mkdir upper lower merged work
echo "I'm from lower!" > lower/in_lower.txt
echo "I'm from upper!" > upper/in_upper.txt
# `in_both` is in both directories
echo "I'm from lower!" > lower/in_both.txt
echo "I'm from upper!" > upper/in_both.txt

sudo mount -t overlay overlay \
 -o lowerdir=./lower,upperdir=./upper,workdir=./work \
 ./merged
  • overlayfs

  • “merged” ,它是挂载点(mount point)目录,也是用户看到的目录,用户的实际文件操作在这里进行。

  • “work/”,这个目录没有在这个图里,它只是一个存放临时文件的目录,OverlayFS中如果有文件修改,就会在中间过程中临时存放文件到这里。

  • upper的in_both.txt会覆盖lower的in_both.txt

  • 在merged/中操作

    • 当创建文件时,这个文件出现在upper
    • 第二种是删除文件,如果我们删除"in_upper.txt",那么这个文件会在upper/目录中消失。如果删除"in_lower.txt", 在 lower/目录里的"in_lower.txt"文件不会有变化,只是在upper/目录中增加了一个特殊文件来告诉OverlayFS,"in_lower.txt’这个文件不能出现在merged/里了,这就表示它已经被删除了。
    • 还有一种操作是修改文件,类似如果修改"in_lower.txt",那么就会在upper/目录中新建一个"in_lower.txt"文件,包含更新的内容,而在lower/中的原来的实际文件"in_lower.txt"不会改变。
  • 磁盘限制容量:采用文件系统提供的quota特性。

    • XFS Quota:可以为Linux系统里的一个用户(user),一个用户组(group)或者一个项目(project)来限制它们使用文件系统的额度(quota),也就是限制它们可以写入文件系统的文件总量。
    • 要使用XFS Quota特性,,必须在文件系统挂载的时候加上对应的 Quota 选项,比如我们目前需要配置 Project Quota,那么这个挂载参数就是"pquota"
    fdisk /dev/sdb # 生成磁盘/dev/sdb1
    mkfs.xfs /dev/sdb1 # 初始化文件系统
    mkdir /mnt/sdb1 # 创建挂载点
    mount -o pquota /dev/sdb1 /mnt/sdb1 # 挂载文件系统 一定要加上pquota标签
    cat /proc/mounts|grep prj
    # /dev/sdb1 /mnt/sdb1 xfs rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,prjquota 0 0
    mkdir /mnt/sdb1/xfs_prjquota # 创建限制容量文件夹
    xfs_quota -x -c 'project -s -p /mnt/sdb1/xfs_prjquota 101' /mnt/sdb1 # 给文件夹打上101的Project ID标记
    xfs_quota -x -c 'limit -p bhard=10m 101' /mnt/sdb1 # 限制容量
    dd if=/dev/zero of=/mnt/sdb1/xfs_prjquota/test.file bs=1024 count=20000 # 写入20M测试,只成功了10M
    # -rw-r--r--. 1 root root 10M Jun 22 09:18 test.file
    
    • docker对宿主机为xfs文件系统限制容量,ext4不行
    docker run --storage-opt size=10M -it centos bash
    
  • 磁盘性能:衡量磁盘性能的两个常见的指标IOPS和吞吐量(Throughput)

    • blkio Cgroup:/sys/fs/cgroup/blkio/
    • Direct I/O:用户写磁盘文件,就会通过Linux内核的文件系统层(fileseytem) -> 块设备层(block layer) -> 磁盘驱动 -> 磁盘硬件,这样一路下去写入磁盘
    • Buffered I/O:用户写磁盘文件,用户进程只需要把文件系统写到内存中(Page Cache)就返回了,而Linux 内核自己有线程会把内存数据再写入到磁盘中。
    • Cgroup v1的blkio控制子系统,只能用来限制Direct I/O的容器的进程读写IOPS和吞吐量,对Buffered I/O无效。这是因为Buffered I/O会把数据先写到内存Page Cache中,然后由内核线程把数据写入磁盘,而Cgroup v1 blkio的子系统独立于memory子系统,无法统计到由Page Cache刷入到磁盘的数据流。
    • Cgroup v2从架构上允许一个控制组里有多个子系统协同运行,这样在一个控制组里只要有io和memory子系统,就可以对Buffered I/O做磁盘读写的限速。
  • dirty page:/proc/sys/vm

    • dirty_writeback_centisecs: 这个参数的值是个时间值,以百分之一秒为单位,缺省值是500,也就是5秒钟。它表示每5秒钟会唤醒内核的flush线程来处理dirty pages。
    • dirty_expire_centisecs:这个参数的值也是一个时间值,以百分之一秒为单位,缺省值是3000,也就是30秒钟。它定义了dirty page在内存中存放的最长时间,如果一个dirty page超过这里定义的时间,那么内核的flush线程也会把这个页面写入磁盘。
    • 当dirty pages数量超过dirty_background_ratio对应的内存量的时候,内核flush线程就会开始把dirty pages写入磁盘 ;
    • 当dirty pages数量超过dirty_ratio对应的内存量,这时候程序写文件的函数调用write()就会被阻塞住,直到这次调用的dirty pages全部写入到磁盘。
    • 根据ftrace的结果,我们发现写数据到Page Cache的时候,需要不断地去释放原有的页面,这个时间开销是最大的。造成容器中Buffered I/O write()不稳定的原因,正是容器在限制内存之后,Page Cache的数量较小并且不断申请释放。

网络

  • Network Namespace可以隔离网络设备
    • 网络设备,指lo eth0等网络设备。ip link
    • IPv4和IPv6协议栈。IP层及以上的TCP和UDP协议栈也是每个Namespace独立工作的。所以IP、TCP、UDP的很多协议,它们的相关参数也是每个Namespace独立的
    • IP路由表。ip route
    • 防火墙规则。iptables
    • 网络状态信息
  • 通过系统调用clone或者unshare()这两个函数来建立新的Network Namespace。
    • clone:在新进程创建的时候,伴随新进程建立,同时也建立新的Network Namespace。通过clone()带上CLONE_NETWORK flag来实现
    • unshare:通过unshare来直接改变当前进程的Network Namespace
  • 容器里Network Namespace的网络参数并不是完全从宿主机Host Namespace里继承来的,也不是完全在新的Network Namespace建立的时候重新开始的。
  • 容器中的/proc/sys/是只读mount的,那么在容器里是不能修改/proc/sys/net的任何参数的。可以在创建容器的时候设置参数
# docker run -d --name net_para --sysctl net.ipv4.tcp_keepalive_time=600 centos:8.1.1911 sleep 3600
7efed88a44d64400ff5a6d38fdcc73f2a74a7bdc3dbc7161060f2f7d0be170d1
# docker exec net_para cat /proc/sys/net/ipv4/tcp_keepalive_time
600
  • 工具
    • ip netns 直接对Network Namespace做相关操作,需要在/var/run/netns/有命名文件指向一个Network Namespace
    • unshare 用来建立一个新的Network Namespace
    • lsns 用于查看当前宿主机上所有的Namespace
    • nsenter 可以进入到任何Namespace中执行命令
  • 容器网络通讯:第一步,让数据包从容器Network Namespace发送到Host Network Namespace上,通过配置一对veth虚拟网络设备的方法实现;第二步,数据包到了Host Network Namespace之后,还需要让它从宿主机eth0发送出去,通过bridge+nat的方式完成。
  • veth
docker run -d --name if-test --network none centos:8.1.1911 sleep 36000
docker exec -it if-test ip addr

pid=$(ps -ef | grep "sleep 36000" | grep -v grep | awk '{print $2}')
echo $pid
# 在"/var/run/netns/"的目录下建立一个符号链接,指向这个容器的 Network Namespace。
# 完成这步操作之后,在后面的"ip netns"操作里,就可以用 pid 的值作为这个容器的 Network Namesapce 的标识了。
mkdir -p /var/run/netns
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.1.1
 
# In the host, set veth_host up
ip link set veth_host up

ip addr add 172.17.1.1/16 dev veth_host
# ip addr delete 172.17.1.1/16 dev veth_host 
ip link set veth_host master docker0
iptables -P FORWARD ACCEPT

# 抓包
# 容器eth0
ip netns exec $pid tcpdump -i eth0 host 39.106.233.176 -nn
# veth_host:
tcpdump -i veth_host host 39.106.233.176 -nn
# docker0
tcpdump -i docker0 host 39.106.233.176 -nn
# host eth0:
tcpdump -i eth0 host 39.106.233.176 -nn
./netperf -H 192.168.0.194 -t TCP_RR 
  • veth实现
    • veth发送数据的函数是veth_xmit(),它里面的主要操作就是找到veth peer设备,然后触发peer设备去接收数据包。veth_container这个接口调用了veth_xmit()来发送数据包,最后就是触发了它的peer设备veth_host去调用netif_rx() 来接收数据包。
    • 而 netif_rx()是一个网络设备驱动里面标准的接收数据包的函数,netif_rx() 里面会为这个数据包raise一个 softirq。
    • 所以,根据veth这个虚拟网络设备的实现方式,我们可以看到它必然会带来额外的开销,这样就会增加数据包的网络延时。
  • macvlan和ipvlan
    • 无论是macvlan还是ipvlan,它们都是在一个物理的网络接口上再配置几个虚拟的网络接口。在这些虚拟的网络接口上,都可以配置独立的IP,并且这些IP可以属于不同的Namespace。
    • 对于macvlan,每个虚拟网络接口都有自己独立的mac地址;而ipvlan的虚拟网络接口是和物理网络接口共享同一个mac地址。
    • 对于延时敏感的应用程序,我们可以考虑使用ipvlan/macvlan网络接口的容器。
    docker run --init --name lat-test-1 --network none -d registry/latency-test:v1 sleep 36000
    
    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
    
  • 网络重传
    • 快速重传的基本定义是:如果发送端收到3个重复的ACK,那么发送端就可以立刻重新发送ACK对应的下一个数据包,而不用等待发送超时。
    • Linux 系统上还会看到发送端收到一个重复的ACK就快速重传的,这是因为Linux下对SACK做了一个特别的判断之后,就可以立刻重传数据包。
    • RPS和RSS的作用类似,都是把数据包分散到更多的CPU上进行处理,使得系统有更强的网络包处理能力。它们的区别是RSS工作在网卡的硬件层,而RPS工作在Linux内核的软件层。
    • 在把数据包分散到各个CPU时,RPS保证了同一个数据流是在一个CPU上的,这样就可以有效减少包的乱序。那么我们可以把 RPS 的这个特性配置到veth网络接口上,来减少数据包乱序的几率。
    • RSS(Receive Side Scaling),是一项网卡的新特性,俗称多队列。具备多个RSS队列的网卡,可以将不同的网络流分成不同的队列,再分别将这些队列分配到多个CPU核心上进行处理,从而将负荷分散,充分利用多核CPU的能力。

容器安全

  • Privileged的容器也就是允许容器中的进程可以执行所有的特权操作。因为"privileged"包含了所有的Linux capabilities, 这样"privileged"就可以轻易获取宿主机上的所有资源,这会对宿主机的安全产生威胁。所以,我们要根据容器中进程需要的最少特权来赋予 capabilities。
  • Linux capabilities就是把Linux root用户原来所有的特权做了细化,可以更加细粒度地给进程赋予不同权限。对于Linux中的每一个特权操作都有一个对应的capability,对于一个capability,有的对应一个特权操作,有的可以对应很多个特权操作。
  • User Namespace隔离了一台Linux节点上的User ID(uid)和Group ID(gid),它给Namespace中的uid/gid的值与宿主机上的uid/gid值建立了一个映射关系。经过User Namespace的隔离,我们在Namespace中看到的进程的uid/gid,就和宿主机Namespace 中看到的uid和gid不一样了。好处如下:
    • 第一,它把容器中root用户(uid 0)映射成宿主机上的普通用户。
    • 第二,对于用户在容器中自己定义普通用户uid的情况,我们只要为每个容器在节点上分配一个uid范围,就不会出现在宿主机上uid冲突的问题了。
  • 为了减少安全风险,业界都是建议在容器中以非root用户来运行进程。不过在没有User Namespace的情况下,在容器中使用非root用户,对于容器云平台来说,对uid的管理会比较麻烦。

工具使用

  • perf
    • perf stat 计数,用来查看每种event发生的次数;
    • perf record 获取采样数据
    • perf list:perf支持的event
      • Hardware event 来自处理器中的一个 PMU(Performance Monitoring Unit),这些event数目不多,都是底层处理器相关的行为,perf中会命名几个通用的事件,比如 cpu-cycles,执行完成的instructions,Cache相关的cache-misses。
      • Software event 是定义在 Linux 内核代码中的几个特定的事件,比较典型的有进程上下文切换(内核态到用户态的转换)事件context-switches、发生缺页中断的事件 page-faults 等。
      • Tracepoints event 内核中很多关键函数里都有Tracepoints。它的实现方式和Software event类似,都是在内核函数中注册了event。
    • 计数(count):统计某个 event 在一段时间里发生了多少次。
    • 采样(sample):
      • 第一种是按照event的数目(period),比如每发生10000次cycles event就记录一次IP、进程等信息,perf record中的-c参数可以指定每发生多少次,就做一次记录。
      • 第二种是定义一个频率(frequency),perf record中的-F参数就是指定频率的,比如perf record -e cycles -F 99 – sleep 1 ,就是指采样每秒钟做99次。
    • 常规使用
    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
    • Perf通过系统调用perf_event_open()来完成对perf event的计数或者采样。不过Docker使用seccomp(seccomp 是一种技术,它通过控制系统调用的方式来保障 Linux 安全)会默认禁止perf_event_open()。
    • 在用Docker启动容器的时候,我们需要在seccomp的profile里,允许perf_event_open()这个系统调用在容器中使用。在我们的例子中,启动container的命令里,已经加了这个参数允许了,参数是"–security-opt seccomp=unconfined"。
    docker run -itd --name perf-test2 --security-opt seccomp=unconfined centos  bash
    # 宿主机
    echo -1 > /proc/sys/kernel/perf_event_paranoid
    
  • ftrace(function tracer):用来跟踪内核中的函数的。
    • ftrace的操作都可以在tracefs这个虚拟文件系统中完成,对于CentOS,这个tracefs的挂载点在/sys/kernel/debug/tracing下
    # tracefs /sys/kernel/debug/tracing tracefs rw,relatime 0 0
    cat /proc/mounts | grep tracefs 
    
    • 通过对/sys/kernel/debug/tracing下某个文件的echo操作,我们可以向内核的ftrace系统发送命令,然后cat某个文件得到ftrace的返回结果
    • function tracer
    # nop
    cat current_tracer
    
    # hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
    cat available_tracers
    
    echo function > current_tracer
    # function
    cat current_tracer
    cat trace | more
    
    # set_ftrace_filter 只列出想看到的内核函数,或者通过 set_ftrace_pid 只列出想看到的进程。
    echo nop > current_tracer
    echo do_mount > set_ftrace_filter # 过滤函数
    echo function > current_tracer
    echo 1 > options/func_stack_trace # 展示完整的函数调用栈
    
    # function_graph tracer 查看每个函数的执行时间
    echo '!do_mount ' >> set_ftrace_filter ### 先把之前的do_mount filter给去掉。
    echo kfree_skb > set_graph_function  ### 设置kfree_skb()
    echo nop > current_tracer ### 暂时把current_tracer设置为nop, 这样可以清空trace
    echo function_graph > current_tracer ### 把current_tracer设置为function_graph
    
    • 在ftrace实现过程里,最重要的一个环节是利用gcc编译器的特性,为每个内核函数二进制码中预留了5个字节,这样内核函数就可以调用调试需要的函数,从而实现了ftrace的功能。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_31220203/article/details/118279600

智能推荐

JWT(Json Web Token)实现无状态登录_无状态token登录-程序员宅基地

文章浏览阅读685次。1.1.什么是有状态?有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。缺点是什么?服务端保存大量数据,增加服务端压力 服务端保存用户状态,无法进行水平扩展 客户端请求依赖服务.._无状态token登录

SDUT OJ逆置正整数-程序员宅基地

文章浏览阅读293次。SDUT OnlineJudge#include<iostream>using namespace std;int main(){int a,b,c,d;cin>>a;b=a%10;c=a/10%10;d=a/100%10;int key[3];key[0]=b;key[1]=c;key[2]=d;for(int i = 0;i<3;i++){ if(key[i]!=0) { cout<<key[i.

年终奖盲区_年终奖盲区表-程序员宅基地

文章浏览阅读2.2k次。年终奖采用的平均每月的收入来评定缴税级数的,速算扣除数也按照月份计算出来,但是最终减去的也是一个月的速算扣除数。为什么这么做呢,这样的收的税更多啊,年终也是一个月的收入,凭什么减去12*速算扣除数了?这个霸道(不要脸)的说法,我们只能合理避免的这些跨级的区域了,那具体是那些区域呢?可以参考下面的表格:年终奖一列标红的一对便是盲区的上下线,发放年终奖的数额一定一定要避免这个区域,不然公司多花了钱..._年终奖盲区表

matlab 提取struct结构体中某个字段所有变量的值_matlab读取struct类型数据中的值-程序员宅基地

文章浏览阅读7.5k次,点赞5次,收藏19次。matlab结构体struct字段变量值提取_matlab读取struct类型数据中的值

Android fragment的用法_android reader fragment-程序员宅基地

文章浏览阅读4.8k次。1,什么情况下使用fragment通常用来作为一个activity的用户界面的一部分例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输_android reader fragment

FFT of waveIn audio signals-程序员宅基地

文章浏览阅读2.8k次。FFT of waveIn audio signalsBy Aqiruse An article on using the Fast Fourier Transform on audio signals. IntroductionThe Fast Fourier Transform (FFT) allows users to view the spectrum content of _fft of wavein audio signals

随便推点

Awesome Mac:收集的非常全面好用的Mac应用程序、软件以及工具_awesomemac-程序员宅基地

文章浏览阅读5.9k次。https://jaywcjlove.github.io/awesome-mac/ 这个仓库主要是收集非常好用的Mac应用程序、软件以及工具,主要面向开发者和设计师。有这个想法是因为我最近发了一篇较为火爆的涨粉儿微信公众号文章《工具武装的前端开发工程师》,于是建了这么一个仓库,持续更新作为补充,搜集更多好用的软件工具。请Star、Pull Request或者使劲搓它 issu_awesomemac

java前端技术---jquery基础详解_简介java中jquery技术-程序员宅基地

文章浏览阅读616次。一.jquery简介 jQuery是一个快速的,简洁的javaScript库,使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互 jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax_简介java中jquery技术

Ant Design Table换滚动条的样式_ant design ::-webkit-scrollbar-corner-程序员宅基地

文章浏览阅读1.6w次,点赞5次,收藏19次。我修改的是表格的固定列滚动而产生的滚动条引用Table的组件的css文件中加入下面的样式:.ant-table-body{ &amp;amp;::-webkit-scrollbar { height: 5px; } &amp;amp;::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box..._ant design ::-webkit-scrollbar-corner

javaWeb毕设分享 健身俱乐部会员管理系统【源码+论文】-程序员宅基地

文章浏览阅读269次。基于JSP的健身俱乐部会员管理系统项目分享:见文末!

论文开题报告怎么写?_开题报告研究难点-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。同学们,是不是又到了一年一度写开题报告的时候呀?是不是还在为不知道论文的开题报告怎么写而苦恼?Take it easy!我带着倾尽我所有开题报告写作经验总结出来的最强保姆级开题报告解说来啦,一定让你脱胎换骨,顺利拿下开题报告这个高塔,你确定还不赶快点赞收藏学起来吗?_开题报告研究难点

原生JS 与 VUE获取父级、子级、兄弟节点的方法 及一些DOM对象的获取_获取子节点的路径 vue-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏17次。原生先获取对象var a = document.getElementById("dom");vue先添加ref <div class="" ref="divBox">获取对象let a = this.$refs.divBox获取父、子、兄弟节点方法var b = a.childNodes; 获取a的全部子节点 var c = a.parentNode; 获取a的父节点var d = a.nextSbiling; 获取a的下一个兄弟节点 var e = a.previ_获取子节点的路径 vue