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

智能推荐

class和struct的区别-程序员宅基地

文章浏览阅读101次。4.class可以有⽆参的构造函数,struct不可以,必须是有参的构造函数,⽽且在有参的构造函数必须初始。2.Struct适⽤于作为经常使⽤的⼀些数据组合成的新类型,表示诸如点、矩形等主要⽤来存储数据的轻量。1.Class⽐较适合⼤的和复杂的数据,表现抽象和多级别的对象层次时。2.class允许继承、被继承,struct不允许,只能继承接⼝。3.Struct有性能优势,Class有⾯向对象的扩展优势。3.class可以初始化变量,struct不可以。1.class是引⽤类型,struct是值类型。

android使用json后闪退,应用闪退问题:从json信息的解析开始就会闪退-程序员宅基地

文章浏览阅读586次。想实现的功能是点击顶部按钮之后按关键字进行搜索,已经可以从服务器收到反馈的json信息,但从json信息的解析开始就会闪退,加载listview也不知道行不行public abstract class loadlistview{public ListView plv;public String js;public int listlength;public int listvisit;public..._rton转json为什么会闪退

如何使用wordnet词典,得到英文句子的同义句_get_synonyms wordnet-程序员宅基地

文章浏览阅读219次。如何使用wordnet词典,得到英文句子的同义句_get_synonyms wordnet

系统项目报表导出功能开发_积木报表 多线程-程序员宅基地

文章浏览阅读521次。系统项目报表导出 导出任务队列表 + 定时扫描 + 多线程_积木报表 多线程

ajax 如何从服务器上获取数据?_ajax 获取http数据-程序员宅基地

文章浏览阅读1.1k次,点赞9次,收藏9次。使用AJAX技术的好处之一是它能够提供更好的用户体验,因为它允许在不重新加载整个页面的情况下更新网页的某一部分。另外,AJAX还使得开发人员能够创建更复杂、更动态的Web应用程序,因为它们可以在后台与服务器进行通信,而不需要打断用户的浏览体验。在Web开发中,AJAX(Asynchronous JavaScript and XML)是一种常用的技术,用于在不重新加载整个页面的情况下,从服务器获取数据并更新网页的某一部分。使用AJAX,你可以创建异步请求,从而提供更快的响应和更好的用户体验。_ajax 获取http数据

Linux图形终端与字符终端-程序员宅基地

文章浏览阅读2.8k次。登录退出、修改密码、关机重启_字符终端

随便推点

Python与Arduino绘制超声波雷达扫描_超声波扫描建模 python库-程序员宅基地

文章浏览阅读3.8k次,点赞3次,收藏51次。前段时间看到一位发烧友制作的超声波雷达扫描神器,用到了Arduino和Processing,可惜啊,我不会Processing更看不懂人家的程序,咋办呢?嘿嘿,所以我就换了个思路解决,因为我会一点Python啊,那就动手吧!在做这个案例之前先要搞明白一个问题:怎么将Arduino通过超声波检测到的距离反馈到Python端?这个嘛,我首先想到了串行通信接口。没错!就是串口。只要Arduino将数据发送给COM口,然后Python能从COM口读取到这个数据就可以啦!我先写了一个测试程序试了一下,OK!搞定_超声波扫描建模 python库

凯撒加密方法介绍及实例说明-程序员宅基地

文章浏览阅读4.2k次。端—端加密指信息由发送端自动加密,并且由TCP/IP进行数据包封装,然后作为不可阅读和不可识别的数据穿过互联网,当这些信息到达目的地,将被自动重组、解密,而成为可读的数据。不可逆加密算法的特征是加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,只有重新输入明文,并再次经过同样不可逆的加密算法处理,得到相同的加密密文并被系统重新识别后,才能真正解密。2.使用时,加密者查找明文字母表中需要加密的消息中的每一个字母所在位置,并且写下密文字母表中对应的字母。_凯撒加密

工控协议--cip--协议解析基本记录_cip协议embedded_service_error-程序员宅基地

文章浏览阅读5.7k次。CIP报文解析常用到的几个字段:普通类型服务类型:[0x00], CIP对象:[0x02 Message Router], ioi segments:[XX]PCCC(带cmd和func)服务类型:[0x00], CIP对象:[0x02 Message Router], cmd:[0x101], fnc:[0x101]..._cip协议embedded_service_error

如何在vs2019及以后版本(如vs2022)上添加 添加ActiveX控件中的MFC类_vs添加mfc库-程序员宅基地

文章浏览阅读2.4k次,点赞9次,收藏13次。有时候我们在MFC项目开发过程中,需要用到一些微软已经提供的功能,如VC++使用EXCEL功能,这时候我们就能直接通过VS2019到如EXCEL.EXE方式,生成对应的OLE头文件,然后直接使用功能,那么,我们上篇文章中介绍了vs2017及以前的版本如何来添加。但由于微软某些方面考虑,这种方式已被放弃。从上图中可以看出,这一功能,在从vs2017版本15.9开始,后续版本已经删除了此功能。那么我们如果仍需要此功能,我们如何在新版本中添加呢。_vs添加mfc库

frame_size (1536) was not respected for a non-last frame_frame_size (1024) was not respected for a non-last-程序员宅基地

文章浏览阅读785次。用ac3编码,执行编码函数时报错入如下:[ac3 @ 0x7fed7800f200] frame_size (1536) was not respected for anon-last frame (avcodec_encode_audio2)用ac3编码时每次送入编码器的音频采样数应该是1536个采样,不然就会报上述错误。这个数字并非刻意固定,而是跟ac3内部的编码算法原理相关。全网找不到,国内音视频之路还有很长的路,音视频人一起加油吧~......_frame_size (1024) was not respected for a non-last frame

Android移动应用开发入门_在安卓移动应用开发中要在活动类文件中声迷你一个复选框变量-程序员宅基地

文章浏览阅读230次,点赞2次,收藏2次。创建Android应用程序一个项目里面可以有很多模块,而每一个模块就对应了一个应用程序。项目结构介绍_在安卓移动应用开发中要在活动类文件中声迷你一个复选框变量

推荐文章

热门文章

相关标签