容器学习--进程、内存、容器存储、网络_/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

智能推荐

python简易爬虫v1.0-程序员宅基地

文章浏览阅读1.8k次,点赞4次,收藏6次。python简易爬虫v1.0作者:William Ma (the_CoderWM)进阶python的首秀,大部分童鞋肯定是做个简单的爬虫吧,众所周知,爬虫需要各种各样的第三方库,例如scrapy, bs4, requests, urllib3等等。此处,我们先从最简单的爬虫开始。首先,我们需要安装两个第三方库:requests和bs4。在cmd中输入以下代码:pip install requestspip install bs4等安装成功后,就可以进入pycharm来写爬虫了。爬

安装flask后vim出现:error detected while processing /home/zww/.vim/ftplugin/python/pyflakes.vim:line 28_freetorn.vim-程序员宅基地

文章浏览阅读2.6k次。解决方法:解决方法可以去github重新下载一个pyflakes.vim。执行如下命令git clone --recursive git://github.com/kevinw/pyflakes-vim.git然后进入git克降目录,./pyflakes-vim/ftplugin,通过如下命令将python目录下的所有文件复制到~/.vim/ftplugin目录下即可。cp -R ...._freetorn.vim

HIT CSAPP大作业:程序人生—Hello‘s P2P-程序员宅基地

文章浏览阅读210次,点赞7次,收藏3次。本文简述了hello.c源程序的预处理、编译、汇编、链接和运行的主要过程,以及hello程序的进程管理、存储管理与I/O管理,通过hello.c这一程序周期的描述,对程序的编译、加载、运行有了初步的了解。_hit csapp

18个顶级人工智能平台-程序员宅基地

文章浏览阅读1w次,点赞2次,收藏27次。来源:机器人小妹  很多时候企业拥有重复,乏味且困难的工作流程,这些流程往往会减慢生产速度并增加运营成本。为了降低生产成本,企业别无选择,只能自动化某些功能以降低生产成本。  通过数字化..._人工智能平台

electron热加载_electron-reloader-程序员宅基地

文章浏览阅读2.2k次。热加载能够在每次保存修改的代码后自动刷新 electron 应用界面,而不必每次去手动操作重新运行,这极大的提升了开发效率。安装 electron 热加载插件热加载虽然很方便,但是不是每个 electron 项目必须的,所以想要舒服的开发 electron 就只能给 electron 项目单独的安装热加载插件[electron-reloader]:// 在项目的根目录下安装 electron-reloader,国内建议使用 cnpm 代替 npmnpm install electron-relo._electron-reloader

android 11.0 去掉recovery模式UI页面的选项_android recovery 删除 部分菜单-程序员宅基地

文章浏览阅读942次。在11.0 进行定制化开发,会根据需要去掉recovery模式的一些选项 就是在device.cpp去掉一些选项就可以了。_android recovery 删除 部分菜单

随便推点

echart省会流向图(物流运输、地图)_java+echart地图+物流跟踪-程序员宅基地

文章浏览阅读2.2k次,点赞2次,收藏6次。继续上次的echart博客,由于省会流向图是从echart画廊中直接取来的。所以直接上代码<!DOCTYPE html><html><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /&_java+echart地图+物流跟踪

Ceph源码解析:读写流程_ceph 发送数据到其他副本的源码-程序员宅基地

文章浏览阅读1.4k次。一、OSD模块简介1.1 消息封装:在OSD上发送和接收信息。cluster_messenger -与其它OSDs和monitors沟通client_messenger -与客户端沟通1.2 消息调度:Dispatcher类,主要负责消息分类1.3 工作队列:1.3.1 OpWQ: 处理ops(从客户端)和sub ops(从其他的OSD)。运行在op_tp线程池。1...._ceph 发送数据到其他副本的源码

进程调度(一)——FIFO算法_进程调度fifo算法代码-程序员宅基地

文章浏览阅读7.9k次,点赞3次,收藏22次。一 定义这是最早出现的置换算法。该算法总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面予以淘汰。该算法实现简单,只需把一个进程已调入内存的页面,按先后次序链接成一个队列,并设置一个指针,称为替换指针,使它总是指向最老的页面。但该算法与进程实际运行的规律不相适应,因为在进程中,有些页面经常被访问,比如,含有全局变量、常用函数、例程等的页面,FIFO 算法并不能保证这些页面不被淘汰。这里,我_进程调度fifo算法代码

mysql rownum写法_mysql应用之类似oracle rownum写法-程序员宅基地

文章浏览阅读133次。rownum是oracle才有的写法,rownum在oracle中可以用于取第一条数据,或者批量写数据时限定批量写的数量等mysql取第一条数据写法SELECT * FROM t order by id LIMIT 1;oracle取第一条数据写法SELECT * FROM t where rownum =1 order by id;ok,上面是mysql和oracle取第一条数据的写法对比,不过..._mysql 替换@rownum的写法

eclipse安装教程_ecjelm-程序员宅基地

文章浏览阅读790次,点赞3次,收藏4次。官网下载下载链接:http://www.eclipse.org/downloads/点击Download下载完成后双击运行我选择第2个,看自己需要(我选择企业级应用,如果只是单纯学习java选第一个就行)进入下一步后选择jre和安装路径修改jvm/jre的时候也可以选择本地的(点后面的文件夹进去),但是我们没有11版本的,所以还是用他的吧选择接受安装中安装过程中如果有其他界面弹出就点accept就行..._ecjelm

Linux常用网络命令_ifconfig 删除vlan-程序员宅基地

文章浏览阅读245次。原文链接:https://linux.cn/article-7801-1.htmlifconfigping &lt;IP地址&gt;:发送ICMP echo消息到某个主机traceroute &lt;IP地址&gt;:用于跟踪IP包的路由路由:netstat -r: 打印路由表route add :添加静态路由路径routed:控制动态路由的BSD守护程序。运行RIP路由协议gat..._ifconfig 删除vlan