资源限制

1 CPU 和 内存

body = {
    "Memory": int(self.container["flavor"]["memoryInMB"]["limit"]) * 1024 * 1024,
    "MemorySwap": int(self.container["flavor"]["memoryInMB"]["limit"]) * 1024 * 1024,
    "CpuShares": int(self.container["flavor"]["cpuInCore"]["quota"]) * 1024,
    "CpuQuota": int(self.container["flavor"]["cpuInCore"]["limit"]) * 100000
}
docker update --help

Usage:  docker update [OPTIONS] CONTAINER [CONTAINER...]

Update configuration of one or more containers

Options:
      --blkio-weight uint16        Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)
      --cpu-period int             Limit CPU CFS (Completely Fair Scheduler) period
      --cpu-quota int              Limit CPU CFS (Completely Fair Scheduler) quota
      --cpu-rt-period int          Limit the CPU real-time period in microseconds
      --cpu-rt-runtime int         Limit the CPU real-time runtime in microseconds
  -c, --cpu-shares int             CPU shares (relative weight)
      --cpus decimal               Number of CPUs
      --cpuset-cpus string         CPUs in which to allow execution (0-3, 0,1)
      --cpuset-mems string         MEMs in which to allow execution (0-3, 0,1)
      --kernel-memory bytes        Kernel memory limit
  -m, --memory bytes               Memory limit
      --memory-reservation bytes   Memory soft limit
      --memory-swap bytes          Swap limit equal to memory plus swap: '-1' to enable unlimited swap
      --pids-limit int             Tune container pids limit (set -1 for unlimited)
      --restart string             Restart policy to apply when a container exits

1.1 CPU

--cpu-shares 用来设置 CPU 的权重,当 CPU 资源充足时,设置 CPU 的权重是没有意义的。只有在容器争用 CPU 资源的情况下, CPU 的权重才能让不同的容器分到不同的 CPU 用量。
--cpus=2 和 --cpu-period=100000 --cpu-quota=200000 是同等作用,其实就是限制了可用的 CPU 个数为 2(通过进程消耗的 CPU 时间片来统计出进程占用 CPU 的百分比)

线上的 4c 的容器 CPU 配置

docker update --cpu-shares 4096 --cpu-quota 400000 {container_name}

2 磁盘限速

  • bps 是 byte per second,每秒读写的数据量。

  • iops 是 io per second,每秒 IO 的次数。

  • --device-read-bps,限制读某个设备的 bps。

  • --device-write-bps,限制写某个设备的 bps。

  • --device-read-iops,限制读某个设备的 iops。

  • --device-write-iops,限制写某个设备的 iops。

2.1 Direct I/O 和 Buffered I/O

Linux 的两种文件 I/O 模式了:Direct I/O 和 Buffered I/O

  • Direct I/O 模式,用户进程如果要写磁盘文件,就会通过 Linux 内核的文件系统层(filesystem) -> 块设备层(block layer) -> 磁盘驱动 -> 磁盘硬件

  • Buffered I/O 模式,那么用户进程只是把文件数据写到内存中(Page Cache)就返回了,而 Linux 内核自己有线程会把内存中的数据再写入到磁盘中。

在 Linux 里,由于考虑到性能问题,绝大多数的应用都会使用 Buffered I/O 模式。

经过测试发现 Direct I/O 可以通过 blkio Cgroup 来限制磁盘 I/O,但是 Buffered I/O 不能被限制。

原因就是被 Cgroups v1 的架构限制了。

进程 pid,它可以分别属于 memory Cgroup 和 blkio Cgroup。

但是在 blkio Cgroup 对进程 pid 做磁盘 I/O 做限制的时候,blkio 子系统是不会去关心 pid 用了哪些内存,哪些内存是不是属于 Page Cache,而这些 Page Cache 的页面在刷入磁盘的时候,产生的 I/O 也不会被计算到进程 pid 上面。

就是这个原因,导致了 blkio 在 Cgroups v1 里不能限制 Buffered I/O。

这个 Buffered I/O 限速的问题,在 Cgroup V2 里得到了解决,其实这个问题也是促使 Linux 开发者重新设计 Cgroup V2 的原因之一。

2.2 Cgroup v1 Vs v2

2.2.1 /sys/fs/cgroup/

v1

$ tree -L 1 /sys/fs/cgroup/
/sys/fs/cgroup/
|-- blkio
|-- cpu -> cpu,cpuacct
|-- cpuacct -> cpu,cpuacct
|-- cpu,cpuacct
|-- cpuset
|-- devices
|-- freezer
|-- hugetlb
|-- memory
|-- net_cls -> net_cls,net_prio
|-- net_cls,net_prio
|-- net_prio -> net_cls,net_prio
|-- perf_event
|-- pids
|-- rdma
`-- systemd

16 directories, 0 files

v2

$ tree -L 1 /sys/fs/cgroup/
/sys/fs/cgroup/
|-- cgroup.controllers
|-- cgroup.max.depth
|-- cgroup.max.descendants
|-- cgroup.procs
|-- cgroup.stat
|-- cgroup.subtree_control
|-- cgroup.threads
|-- cpu.pressure
|-- cpuset.cpus.effective
|-- cpuset.mems.effective
|-- cpu.stat
|-- init.scope
|-- io.pressure
|-- io.stat
|-- memory.numa_stat
|-- memory.pressure
|-- memory.stat
|-- system.slice
`-- user.slice

3 directories, 16 files

v1 的 cgroup 为每个控制器都使用独立的树(目录), 每个目录就代表了一个 cgroup subsystem,各个 Subsystem 各自为政,看起来比混乱,难以管理

2.3 系统开启 Cgroup v2

/etc/default/grub 添加 systemd.unified_cgroup_hierarchy=1 参数

GRUB_CMDLINE_LINUX="... systemd.unified_cgroup_hierarchy=1"

更新 grub 并重启

$ grub2-mkconfig -o /boot/grub2/grub.cfg
$ reboot

检查

$ stat -fc %T /sys/fs/cgroup/
如果输出是 cgroup2fs,则表示使用的是cgroup v2。
如果输出是 tmpfs,则表示使用的是cgroup v1。

2.4 Docker 配置

$ docker info | grep Cgroup
 Cgroup Driver: systemd
 Cgroup Version: 2

未开启 Cgroup V2 时,docker info 输出以下信息

Cgroup Driver: cgroupfs Cgroup Version: 1

Cgroup Driver:

  • cgroupfs:Docker 默认的驱动,直接操作文件系统中的 cgroup 目录(如 /sys/fs/cgroup/)。

  • systemd:利用 systemd 作为 cgroup 管理器,将容器进程纳入 systemd 的单元(unit)层级中,实现更精细的资源控制和日志管理。

如果要修改容器的容器默认的 cgroup 父路径(默认是 "/system.slice"),可以添加如下配置:

/etc/docker/daemon.json
{
  "cgroup-parent": "/docker.slice"
}

2.5 测试

2.5.1 创建容器

docker run -d -v /mnt/container/docker_bind/ceshi:/home --device-write-bps /dev/vdb:1M --name test  registry.baidubce.com/scs_test_cce/mysql:v1.0

2.5.2 dd 测试

进入到容器中 /home 目录

# Direct I/O
dd if=/dev/zero of=test.out bs=1M count=1024 oflag=direct
# Buffered I/O
dd if=/dev/zero of=test.out bs=1M count=1024

2.5.3 查看磁盘 io

iostat -kx 1

2.5.4 磁盘 io 限制

$ cat /sys/fs/cgroup/system.slice/docker-a576b0762a93d006841a4a7e20eb7ef7886c1810b2fdd2352a5ba1da96450a65.scope/io.max
253:16 rbps=max wbps=1048576 riops=max wiops=max

返回结果中的 253:16 是磁盘编号, 可以通过 lsblk 获取

$ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT vda 253:0 0 20G 0 disk `-vda1 253:1 0 20G 0 part / vdb 253:16 0 100G 0 disk /mnt

cgroup v1 在通过如下方式获取

  • blkio.throttle.read_bps_device

  • blkio.throttle.read_iops_device

  • blkio.throttle.write_bps_device

  • blkio.throttle.write_iops_device

$ cat /sys/fs/cgroup/blkio/docker/{container_id}/blkio.throttle.write_bps_device

253:16 4000

2.6 Docker API 限制磁盘 IO

Cgroup v1 无法限制 Page Cache 刷盘速度

Cgroup v2 通过 Buffered I/O 写入文件时,会秒级完成写入,但是从 Page Cache 往磁盘写入时会限速

创建容器时的 API 参数

BlkioDeviceReadBps
BlkioDeviceWriteBps

BlkioDeviceReadIOps
BlkioDeviceWriteIOps

在 Docker API 中,BlkioDeviceReadBpsHostConfig 的一个字段,格式为:

{
  "BlkioDeviceReadBps": [
    {
      "Path": "/dev/sda",
      "Rate": 1048576  # 1MB = 1024*1024=1048576 字节/秒
    }
  ]
}

示例

curl -X POST \
  -H "Content-Type: application/json" \
  --unix-socket /var/run/docker.sock \
  "http://v1.41/containers/create?name=my_container" \
  -d '{
    "Image": "nginx",
    "HostConfig": {
      "BlkioDeviceReadBps": [
        {
          "Path": "/dev/sda",
          "Rate": 1048576
        }
      ]
    }
  }'

获取磁盘设备

 [[ -d "/mnt/container" ]] && df -h "/mnt/container" | grep dev | awk '{print $1}'
/dev/vdb

2.7 示例

综合限制磁盘 IOPS 和 IO BPS

docker run -it \
  --device-read-iops /dev/sda:100 \
  --device-write-iops /dev/sda:50 \
  --device-read-bps /dev/sda:1MB \
  --device-write-bps /dev/sda:512KB \
  centos /bin/bash
  • 读 IOPS ≤ 100 次/秒,读带宽 ≤ 1MB/s。

  • 写 IOPS ≤ 50 次/秒,写带宽 ≤ 512KB/s。

Last updated