分享

关于Docker 资源管理及我们踩过的那些坑

yr123 发表于 2015-9-10 21:05:09 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 0 26108
本帖最后由 yr123 于 2015-9-10 21:06 编辑
问题导读
1.Docker解决什么问题?
2.Docker如何实现隔离?
3.Docker如何实现资源管控?
4.Docker容器的资源使用情况怎么监控?



Docker解决什么问题?
它解决了两大问题:
  • 容器级别的高性能虚拟化,提供了资源的隔离和管控;
  • DockerHub + Aufs(当然还有其他驱动)提供的镜像存储和变更管理。

通过解决这两个核心问题,Docker给了我们很大的想象力。我们可以在Docker提供的这两个基础功能上,包装出更多更有趣的产品,同时这些生态环境中出现的项目,也可以大大解放运维哥哥。

Docker如何实现隔离?
Docker的资源隔离使用了Linux Kernel中的Namespaces功能。Namespaces主要是通过Linux的clone内核调用来使用。根据传入的不同参数组合来启用对应的Namespace隔离。
下面是目前Linux Kernel支持的Namespaces参数列表(2.* 是指Linux Kernel 版本):
  • 挂载点的隔离:CLONE_NEWNS (2.4.19+ );
  • 主机名的隔离:CLONE_NEWUTS (2.6.19+ );
  • 进程间通讯的隔离:CLONE_NEWIPC (2.6.19+ );
  • 进程ID的隔离:CLONE_NEWPID (2.6.24+ );
  • 网络栈的隔离:CLONE_NEWNET (2.6.24+ );
  • 用户和组的隔离:CLONE_NEWUSER (3.8+ )。

通过上面的列表看来,越新的Kernel对Namespaces的支持越好。不过这也预示着还在使用CentOS 5(Kernel版本为2.6.18)及以下版本的服务器来说可以宣判死刑了。考虑到,CentOS 6的Kernel主要还是在2.6.32,所以支持还不错。

1. Namespace是怎么展现的?
我们可以通过/proc文件系统来找到一些Namespace的蛛丝马迹。
在任何一个/proc/${PID}/ns文件夹中我们都可以发现以下这么几个文件:
  • ipc
  • mnt
  • net
  • pid
  • user
  • uts

这几个文件对应了clone内核调用中所提供的Namespaces。如果我们在clone内核调用中不传入所有的CLONE_NEW*参数,那么未传入的Namespace会从父进程继承。这种设计的灵活性在于,它给了我们一种让不同的Container共享同一个Namespace的能力。比如说,让两个Container共享宿主机的网络。

2. 如何操作Namespaces?
对于Namespaces的操作,主要是3个内核调用:
  • clone
  • setns
  • unshareclone

之前已经提到了,专门用来创建Namespace的。setns用来给调用该函数的进程替换到指定的Namespace上,它需要一个fd参数,而fd就是对应进程中/proc/${PID}/ns/xxx中对应文件的描述符。unshare有点类似clone,不过它负责给调用它的进程创建一个指定的Namespace。当然,除了内核调用之外,我们还有几个命令行工具来简单地操作Namespace:
  • ip netns
  • unshare

其中最有意思的就是ip netns了,我们可以用它来创建一个只有网络隔离功能的Container。

3. 通过Namespaces可以做什么?
既然我们了解了Namespaces以及如何控制它,接下来我们就来看看如何通过对Namespaces的操控来Hack一下Docker的Container。
注意,前方高能,直接上代码:
#!/bin/bash

PID=`docker inspect -f '{{.State.Pid}}' $1`
ID=`docker inspect -f '{{.Id}}' $1`
ETHNAME=$2

mkdir -p /var/run/netns
ln -s /proc/${PID}/ns/net /var/run/netns/${ID}

ip link add dev ${ETHNAME}.0 type vet peer name ${ETHNAME}.1
ip link set dev ${ETHNAME}.1 netns ${ID}
ip link set dev ${ETHNAME}.0 up
ip netns exec ${ID} ifconfig ${ETHNAME}.1 $3 up

rm -rf /var/run/netns/${ID}
上面这个脚本主要是通过ip netns来操作Net Namespace,从而给一个Docker的Container增加一块网卡,并配置好IP地址。
用法为:
network.sh  docker-test veth0 192.168.1.10/24
当命令执行完毕之后,在宿主机上可以看到一个veth0.0的网卡,剩下的可以根据个人喜好把网卡加入Linux Bridge还是OpenVSwitch。该脚本的关键在于:
ip link set dev ${ETHNAME}.1 netns ${ID}
这行用来向一个网络的Namespace中加入一块网卡。而下面这句则用来在一个指定的网络Namespace中执行一条命令:
ip netns exec ${ID} ...

Docker如何实现资源管控?
如果只是使用Namespaces来隔离的话,我们会遇到以下的一些问题:
  • 不同Container之间抢夺CPU资源;
  • 不同Container之间抢夺IO资源;
  • Container的磁盘资源使用过多,把硬盘写满了导致其他Contianer出现问题。

Docker最开始的理想中包含了对groups封装,以方便管理员更容易地操作Container,避免陷入groups的复杂操作。想法很好,但当我们真正运维Docker时,我们还是无法逃脱cgroups的魔爪。
注:以下针对groups子系统的描述使用的是lxc-cgroup中对state-object参数的命名方式。

1. Docker的cgroups模型
Docker对cgroups的操作根据驱动的不同使用了不同的路径:
  • libcontainer: /sys/fs/cgroups/<subsystem>/docker/<containerID>
  • Linux Container: /sys/fs/cgroups/<subsystem>/lxc/<containerID>


2. CPU和内存的资源管控
cgroups中对CPU的资源管控主要通过两种方式来设置:
  • cpu.shares
  • cpuset.cpuscpu

子系统中的shares文件用来设置一个CPU使用优先级的相对比例值。
通过对这个值的修改可以标识出不同Container在使用CPU时的优先级。越大的优先级越高。
cpuset子系统中的cpus可以指定一个Container只使用指定的CPU Core
对于内存的管控,cgoups可以通过以下两个参数来设置:
  • memory.memsw.limit_in_bytes
  • memory.limit_in_bytes

memory子系统中的limit_in_bytes和memsw.limit_in_bytes用来设置Container能使用的最大内存和swap。对于swap来讲,可能很多人忽略了它的重要性。swap的存在很大程度上减少或者说减缓了OOM(Out of Memory)的触发。从而给了运维人员在业务进程被OOM杀掉之前介入处理的可能性。
对于OOM来说,它的触发机制为:
  • 内核申请分配内存;
  • 内核发现物理内存中没有可用内存可分配,则把冷数据换出到swap中;
  • 内核发现swap已满,无法换出数据;
  • 触发OOM。而OOM被触发只有在于,它会扫描所有进程并统计它们使用的内存,并把内存占用最多的进程杀掉。

在统计进程使用的内存时,OOM会把子进程使用的内存累加到父进程。也就是说它会杀掉父进程而不是那个占用内存最多的子进程。当我们知道这个特性之后,想想看如果服务器中运行了php-fpm,然后OOM被触发了……那么有人会问了,有没有swap最终都会触发OOM啊!答案是没错,OOM确实都会触发。
但当你使用swap时,你的业务处理的性能就会变得很慢,这时运维可以通过监控系统发现问题并介入。但没有swap的话,你的运维人员还没来得及介入,业务进程已经被OOM杀掉了。
这也是我们遇到的最大坑之一。所以,请记住(重要的事情说三遍):
为了业务系统的稳定性Contaier一定要设置swap!
为了业务系统的稳定性Contaier一定要设置swap!
为了业务系统的稳定性Contaier一定要设置swap!

如果发现/sys/fs/cgroups/memory/docker/CONTAINER_ID/memsw.limit_in_bytes文件不存在,可以增加一个Kernel的启动参数来启用这个功能:
cgroups_enable=memory swapaccount=1

3. 磁盘IO的资源管控
在Container使用磁盘IO时,就跟Container使用CPU时是一样的。如果没有管控,那么大家就开始相互抢夺磁盘IO资源了。
对于磁盘IO资源的管控,cgroups提供了两种方法:
  • 基于权重的IO调度;
  • 对IOPS和吞吐量进行精细的限制。

如果要使用基于权重的IO调度的话,需要启用CFQ调度算法,这里我就不详解了。主要讲一下针对IOPS和吞吐量限制的方法:
  • blkio.throttle.read_bps_device
  • blkio.throttle.write_bps_device
  • blkio.throttle.read_iops_device
  • blkio.throttle.write_iops_device

光看名字就能知道blkio子系统中不同文件所要达到的目的了。
在这里我主要讲一下如何设置。对于throttle.read_bps_device这个文件,我们需要写入的格式为:
<major>:<minor> <limit>

major和minor是磁盘的两个编号,我们可以通过/proc/partitions文件中获得对应磁盘的major和minor编号。

4. Docker的磁盘容量的管控
默认的Docker Image的驱动使用的是Aufs。使用Aufs可以很方便快捷地创建一个Container的底层文件系统。但Aufs在Container磁盘容量管控中做得并不好。在磁盘容量管控上,为大家分享两种方法:
  • 通过LVM方式创建一个卷,通过—volume参数来让Container访问;
  • 使用btrfs驱动,通过对sub volume设置quota参数来限制磁盘容量。

对于第一种方法来说,有个前提条件是,Container中运行的应用程序所进行的磁盘操作要落到“—volume”参数指定的文件夹中。否则就会占用Aufs所在盘的容量。这种方式适用于Container中运行的程序是自己可控的。
第二种方法相对简单一些,Docker的btrfs驱动是使用btrfs所提供的sub volume功能来实现的。一个Container会对应一个subvolume,针对这个subvolume启用quota并设置quota参数即可限制Container的磁盘容量:
btrfs qgroup limit -e 100G /var/lib/docker/btrfs/subvolumes/<CONTAINER_ID>

对于btrfs来说还有很多很好地特性,比如说可以在线加入一块新的块设备来扩充整个文件系统的大小等等。有兴趣的同学可以自行研究一下。
不过btrfs也有自己的问题,对于老版本的Kernel来说,其支持的功能和稳定性并不适合生产环境。如果Kernel比较新,那么可以考虑部署在生产环境,毕竟已经有Facebook这样的大公司在生产环境上测试btrfs了。

Docker容器的资源使用情况怎么监控?
对于Docker的Container所使用的资源进行监控也是运维同学最关心的内容。
cgroups提供了很多接口来让我们看到Container对资源的使用情况。
  • cpuacct.usage:Container占用的CPU时间,单位为ns
  • memory.usage_in_bytes:Container占用的内存容量
  • memory.memsw.usage_in_bytes:Container占用内存和swap的总容量
  • blkio.throttle.io_serviced:Container在各个块设备上的IO操作数
  • blkio.throttle.io_service_bytes:Container在各个块设备商的IO吞吐量

对于网络带宽的监控可以通过/sys/class/net/<ethname>/statistics/文件夹下的文件来获取。
Out Of Docker最后提供2个和Docker类似的项目,他们都有一个统一的特点:
用Bash实现
  • Bocker
  • Dockerlite


如何一起愉快地发展
“高效运维”公众号(如下二维码)值得您的关注,作为高效运维系列微信群的唯一官方公众号,每周发表多篇干货满满的
原创好文:来自于系列群的讨论精华、运维讲坛线上精彩分享及群友原创。“高效运维”也是互联网专栏《高效运维最佳实践》及运维2.0官方公众号。
提示:目前高效运维两个微信主群仅有少量珍贵席位,如您愿意,可添加萧田国个人微信号 xiaotianguo8 为好友,进行申请;或申请加入我们技术交流群(技术讨论为主,没有主群那么多规矩,更热闹)。

没找到任何评论,期待你打破沉寂

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条