qemu 编译

qemu的编译是比较简单的: https://www.qemu.org/download/#source

qemu 的编译逻辑是:

如果你编译环境上有相关功能的库和开发包的支持,则编译出来的qemu就支持该功能,如果没有就自动不支持;所以,你基本不需要明确开启指定功能的选项,但是,如果你想明确禁止支持某功能,可以明确通过选项来禁止

 

我的环境下,安装了下面这么多包,enable了很多支持

 

pid namespace

The namespace init process
The first process created in a new namespace (i.e., the process
created using clone(2) with the CLONE_NEWPID flag, or the first child
created by a process after a call to unshare(2) using the
CLONE_NEWPID flag) has the PID 1, and is the “init” process for the
namespace (see init(1)). A child process that is orphaned within the
namespace will be reparented to this process rather than init(1)
(unless one of the ancestors of the child in the same PID namespace
employed the prctl(2) PR_SET_CHILD_SUBREAPER command to mark itself
as the reaper of orphaned descendant processes
).

cgroup 审计策略

内存子系统

场景:

当你把一个已经占用了1GB内存的进程转移到一个新的cgroup后,新的cgroup看到的该进程使用的内存大小会是多少呢?

memory.max_usage_in_bytes  和 memory.usage_in_bytes 都不会将这1GB计算在内的,只有该进程继续申请内存才会计算新申请的内存。

同样,如果把一个占用内存很多的进程从cgroup中移出来,memory.max_usage_in_bytes  和 memory.usage_in_bytes 应该也不会被更新

Process Number Controller

  1.  pids.current 可以大于 pids.max
    1. 当cgroup中进程数量达到最大时,cgroup并不“阻止”从其它cgroup中移动一个进程到该cgroup中;只是说不允许在该cgroup中fork、clone新的进程或线程
  2. pids.current包含所有子孙cgroup中的进程(和线程)
  3. 当cgroup对进程数量不限制时,pids.max 不是0(零)也不是-1,而是字符串max😓

docker 之磁盘限额

docker-ce-17.07.0 版本开始支持overlay2的磁盘配额,该版本 8月29号release了,现在可以使用edge版本:

https://download.docker.com/linux/centos/7/x86_64/edge/Packages/docker-ce-17.07.0.ce-1.el7.centos.x86_64.rpm

 

overlay2的配额限制是有条件的:

  1. 基于xfs文件系统(如果不开启配额的话,ext4上也能支持,如果内核版本够高的话,btrfs也能支持)
  2. xfs文件系统挂载时使用pquota选项(pquota和gquota 不能同时出现)
  3. xfs 格式化时需要ftype=1,用于enable d_type

 

xfs的pquota是个啥东西: https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Storage_Administration_Guide/xfsquota.html

 

XFS quotas are enabled at mount time, with specific mount options. Each mount option can also be specified as noenforce; this will allow usage reporting without enforcing any limits. Valid quota mount options are:
  • uquota/uqnoenforce – User quotas
  • gquota/gqnoenforce – Group quotas
  • pquota/pqnoenforce – Project quota

注意:

pquota和gquota 不能同时出现

  • uquota: 强制限制,绝对不能超限
  • uqnoenforce:可以超限,但是会贴罚单

 

如果是kickstart 安装的系统,用如下命令:


mkfs.xfs 还有很多选项,专门研究存储的话,还是要了解的

参考: http://foxhound.blog.51cto.com/1167932/1841487

docker-ce-17.07.0 关于xfs上的overlay2存储驱动的磁盘限额,使用效果如下:

注意:

  1. –storage-opt时, 使用的是size,而不是overlay2.size;关键是多处文档描述的都是overlay2.size=1g,而实际上却不识别overlay2.size,识别的却是size
  2. 做磁盘限额的时候,基础镜像的大小显然没有计算在内; 不做磁盘限额的时候,显示的是整个overlay文件系统的使用情况
  3. 据说daemon.json 中也是可以定义存储选项的,但是测试发现不管使用overlay2.size还是size都不报错,也都不生效(但是源码上确实都是overlay2.size呢)
  4. docker info中不显示默认的存储配额
  5. 关于存储选项: overlay2.override_kernel_check, 文档 要求必须设置为true,实际情况是:
    1. 如果设置为true,docker会帮忙检查内核版本够不够高,不够高就直接报错了
    2. 如果设置为false,docker依然会帮忙检查内核版本够不够高,不够包就记录一个警告日志

阿里云提供的centos7镜像的文件系统类型似乎都是ext4的

并非所有的D状态的进程都是kill不掉的:

docker 的磁盘限额是可以通过xfs_quota 查看到的(kernel内核版本要求4.6以上才能查看到,小于4.6的内核版本上不是不支持限额,只是通过xfs_quota 看不到而已)

 

关于mlock

mlock可以锁住一部分内存,锁住干啥?

这里的锁住的真实含义是不让交换到swap上去。

用途:

  1. real-time Application
    1. 对于实时应用来讲,就是要反应迅速,所以,事件发生后才发现内存中的数据被交换到swap上去了,那就太耽误事了
  2. 出于安全考虑
    1. 安全信息只要落地了(哪怕是被swap出来的)就容易泄露,所以,敏感信息经常mlock
  3. 就是不想swap(管我呢)

限制:

ulimit可以限制允许mlock的内存大小

查看:

cat /proc/$pid/status |grep VmLck

 

参考:

docker之username space

usernamespace在docker中的用法:

  1. docker 中可以通过dockerd的 –userns-remap=$username 选项来指定用户映射,从文档上来看,该选项只能使用一次,也就是说,只能指定一个用户
  2. docker 参考的用户映射关系文件为 /etc/subuid  和 /etc/subgid ,文件格式大约为:

    其中:
    bozo: 是–userns-remap 指定的用户名
    100000:65536   以为着该用户可以映射到的容器外部的uid的范围为 100000 ~ (100000 + 65536)
  3. 目前来看,不同容器中相同用户映射到容器外面的uid是相同的
  4. docker文档 要求需要映射的用户必须在容器外面是真实存在的,至少在 /etc/passwd 中是需要存在的; 从uid映射来看,/etc/passwd 中定义的uid基本是被无视的
  5. 通过–userns-remap伪造的容器内部的root用户,虽然在容器外部就是个普通用户,但是在容器内部确实是有很大的特权的
  6. docker create的 –user选项和usernamespace没有关系,只是说用哪个用户启动容器内部的进程而已
  7. 参考: https://github.com/docker/labs/tree/master/security/userns

从上面分析来看,userns-remap使用起来是比较麻烦的、且功能太有限; 我们期望不需要太多配置的情况下,不同容器中所有进程、子进程的用户都能自动映射到外部单独的uid,可能实现这个确实比较麻烦(参考下面原理部分)

 

username space原理:

  1. User Namespace是Linux 3.8新增的一种namespace
  2. clone系统调用CLONE_NEWUSER
  3. /proc/$pid/uid_map  (对于支持user namespace的内核,都存在该文件,不明确设置就为空)
  4. 当我们通过CLONE_NEWUSER去clone一个进程(或线程)的话,进程的后续行为都会参考 /proc/$pid/uid_map 来决定用户的身份,那么该uid_map 文件必然要在clone之后才能写入了;为了避免我们clone出来的那个进程很快执行到用户代码而实现提权,则往往在clone后执行的是可控的代码,在准备工作完成后在execv()去执行用户的进程(注意: execv并不改变进程号,或者说不产生新的进程,而是在当前进程空间来执行用户的代码),用户代码中很可能会继续clone,那么后续的clone如果没有CLONE_NEWUSER的话,能否参考到上面的/proc/$pid/uid_map 呢?或者说,只参考该pid namespace中init进程下面的uid_map ? (稍后继续研究)
  5. http://man7.org/linux/man-pages/man7/user_namespaces.7.html

看个例子:

 

实际问题:

在没有使用–userns-map的情况下,很多容器都使用相同的镜像(所以,相同用户名的uid基本都一样),每个容器都可以通过容器中的sshd进行登录,sshd登录后都会有相应的limits限制,以单个用户最大进程数量为例,一般来讲,root都是没有限制的,其他用户都是有限制的,对于mysql用户,通常会产生很多线程,从容器的角度来看,该用户名下的进程数量确实不多,甚至整个容器都没有几个进程,这时候,启动mysql时却是失败的;原因在于:

  1. 启动进程这事儿归根结底还是内核干的
  2. ulimit资源限制最终是从进程属性上生效的,而不是直接从配置文件上生效的
  3. 容器外部和容器内部看到的uid是相同的
  4. 其他容器已经在没有ulimit限制(或者配额足够)的情况下启动了很多mysql进程(或线程)了
  5. 最后,在ulimit限制比较小的情况下启动mysql进程(或线程)自然就会失败的
  6. 所以,从用户级别的资源限制方面来讲,容器之间的相互影响是比较难以控制的
  7. docker本来不是让作为虚拟机使用的,所以,类似问题是不会被docker官方在意(和觉察)的,docker中的–userns-map 仅仅考虑了通常情况下单个容器中只有单个(被docker-containerd启动)的进程的场景
  8. 解决办法: 给用户足够的资源
  9. 我们可以限制单个容器的进程数量:
    1. https://segmentfault.com/a/1190000007468509   https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt
    2. 通过docker create 的 –pids-limit 选项进行配置
  10. dockerd 启动选项 –default-ulimit 可以设置默认的ulimit
  11. 目前,我们生产环境使用的docker容器都是通过docker exec 启动进程的,docker exec 进去后,ulimit如下:

     

问题:

  1. 如何从容器级别限制进程数量?

参考资料:

关于ulimit的问题

  1. ulimit 有软限制和硬限制
    1. 软限制起实际限制作用,但不能超过而硬限制(除非有root权限)【所以,ulimit默认显示的是软限制的值】
    2. 普通用户可以在硬限制范围内,更改自己的软限制
    3. 普通用户都可以缩小硬限制,但不能扩大硬限制。而root缩小扩大都可以
    4. 修改或查看限制可以用 ulimit 命令
  2. 我们一般都知道的文件为: /etc/security/limits.conf
  3. 我们很少关注目录: /etc/security/limits.d/     该目录下有时候也会存在相关配置,使得你怎么修改limits.conf 都不生效
  4. 我们经常的做法是修改limits.conf ,但是正确的做法应该是在limits.d/ 中添加一个文件,文件命名也有讲究,如:
    默认存在一个 90-nproc.conf,我们可以添加 91-nproc.conf 来覆盖前者
  5. sshd 会参考limits相关配置,并且是在ssh登录时参考的,不是在启动sshd时候参考的,所以,修改完limits相关配置,不需要重启sshd
  6. /etc/security/limits* 属于pam_limits 模块所属文件,如果必要的话,从pam中去掉该模块或者卸载该软件包也就不会受到这些限制了
  7. limits 的特点是超过限制就禁止启动或杀掉
  8. 每个进程都有一个 /proc/$pid/limits

有些限制是系统级别的,有些限制是进程级别的:

  1. open files: 单个进程打开文件最大数量限制
    1. 基本上也就是最大文件描述符的限制了,一些进程会据此for循环来关闭所有的文件描述符;所以,没有必要unlimited
  2. max user processes: 系统级别的
    1. 以uid为参考,如果当前已经达到或超过该数值限制,则禁止继续启动

参考: