Linux systemd資源控制初探
本文記錄一次cgroup子目錄丟失問題,並簡單探索了Linux systemd的資源控制機制。html
問題現象
咱們但願經過systemd拉起服務並經過cgroup限制其CPU、memory的使用,所以咱們新建了一個.service
文件,文件裏面建立了本身的cgroup目錄,設置了cpu、memory限制,而後經過cgexec拉起咱們的服務進程。假設咱們的服務叫xx,.service
文件大概是這樣的:node
[Unit] Description=xx Server Documentation=xx docs [Service] EnvironmentFile=-/etc/xx ExecStartPre=/usr/bin/mkdir -p /sys/fs/cgroup/memory/xx ExecStartPre=/usr/bin/bash -c "echo 2G > /sys/fs/cgroup/memory/xx/memory.limit_in_bytes" ExecStartPre=/usr/bin/bash -c "echo 2G > /sys/fs/cgroup/memory/xx/memory.memsw.limit_in_bytes" ExecStartPre=/usr/bin/mkdir -p /sys/fs/cgroup/cpu/xx ExecStartPre=/usr/bin/bash -c "echo 100000 > /sys/fs/cgroup/cpu/xx/cpu.cfs_period_us" ExecStartPre=/usr/bin/bash -c "echo 100000 > /sys/fs/cgroup/cpu/xx/cpu.cfs_quota_us" ExecStartPre=/usr/bin/bash -c "echo 1024 > /sys/fs/cgroup/cpu/xx/cpu.shares" ExecStart=/usr/bin/cgexec -g cpu,memory:xx /usr/bin/xx Restart=on-failure KillMode=process LimitNOFILE=102400 LimitNPROC=102400 LimitCORE=infinity [Install] WantedBy=multi-user.target
設置完.service
文件後,將其拷貝到/usr/lib/systemd/system目錄(CentOS 7)下,而後經過systemctl start xx.service啓動,經過systemctl enable xx.service關聯自啓動項。 但在運行好久以後,發現咱們的xx服務內存使用爆了,而後查看咱們本身建立的xx cgroup目錄丟失了,所以對應的CPU、memory資源也就沒有限制住。git
分析過程
剛開始的定位過程是很懵逼的,各類日誌查看沒有發現線索,嘗試復現也沒有成功。正在苦惱沒有方向之際,無心中發現執行了其餘服務的systemd的某些操做(stop/start/enable)以後,復現了問題,就這樣盯上了systemd。 後來發現其實一開始就能夠經過查看進程的cgroup信息就能很快找到線索:進程cgroup移到了/system.slice/xx.service目錄下:github
[root@localhost ~]# cat /proc/214041/cgroup 10:memory:/system.slice/xx.service 4:cpuacct,cpu:/system.slice/xx.service
而/system.slice/xx.service正是systemd爲xx這個服務建立的cgroup目錄。因此問題鎖定爲systemd把xx進程從咱們本身建立的cgroup移動到它默認建立的cgroup裏,可是它默認建立的cgroup顯然沒有設置過資源限制。centos
systemd資源控制
systemd經過Unit的配置文件配置資源控制,Unit包括services(上面例子就是一個service unit), slices, scopes, sockets, mount points, 和swap devices六種。systemd底層也是依賴Linux Control Groups (cgroups)來實現資源控制。bash
cgroup v1和v2
cgroup有兩個版本,新版本的cgroup v2即Unified cgroup(參考cgroup v2)和傳統的cgroup v1(參考cgroup v1),在新版的Linux(4.x)上,v1和v2同時存在,但同一種資源(CPU、內存、IO等)只能用v1或者v2一種cgroup版本進行控制。systemd同時支持這兩個版本,並在設置時爲二者之間作相應的轉換。對於每一個控制器,若是設置了cgroup v2的配置,則忽略全部v1的相關配置。 在systemd配置選項上,cgroup v2相比cgroup v1有以下不同的地方: 1.CPU: CPUWeight=
和StartupCPUWeight=
取代了CPUShares=
和StartupCPUShares=
。cgroup v2沒有"cpuacct"控制器。 2.Memory:MemoryMax=
取代了MemoryLimit=
. MemoryLow=
and MemoryHigh=
只在cgroup v2上支持。 3.IO:BlockIO
前綴取代了IO
前綴。在cgroup v2,Buffered寫入也統計在了cgroup寫IO裏,這是cgroup v1一直存在的問題。網絡
配置選項(新版本systemd)
CPUAccounting=:是否開啓該unit的CPU使用統計,BOOL型,true或者false。dom
CPUWeight=weight, StartupCPUWeight=weight:用於設置cgroup v2的cpu.weight
參數。取值範圍1-1000,默認值100。StartupCPUWeight應用於系統啓動階段,CPUWeight應用於正常運行時。這兩個配置取代了舊版本的CPUShares=
和StartupCPUShares=
。socket
CPUQuota=:用於設置cgroup v2的cpu.max
參數或者cgroup v1的cpu.cfs_quota_us
參數。表示能夠佔用的CPU時間配額百分比。如:20%表示最大可使用單個CPU核的20%。能夠超過100%,好比200%表示可使用2個CPU核。spa
MemoryAccounting=:是否開啓該unit的memory使用統計,BOOL型,true或者false。
MemoryLow=bytes:用於設置cgroup v2的memory.low
參數,不支持cgroup v1。當unit使用的內存低於該值時將被保護,其內存不會被回收。能夠設置不一樣的後綴:K,M,G或者T表示不一樣的單位。
MemoryHigh=bytes:用於設置cgroup v2的memory.high
參數,不支持cgroup v1。內存使用超過該值時,進程將被下降運行時間,並快速回收其佔用的內存。一樣能夠設置不一樣的後綴:K,M,G或者T(單位1024)。也能夠設置爲infinity
表示沒有限制。
MemoryMax=bytes:用於設置cgroup v2的memory.max
參數,若是進程的內存超過該限制,則會觸發out-of-memory將其kill掉。一樣能夠設置不一樣的後綴:K,M,G或者T(單位1024),以及設置爲infinity
。該參數去掉舊版本的MemoryLimit=
。
MemorySwapMax=bytes:用於設置cgroup v2的memory.swap.max"
參數。和MemoryMax相似,不一樣的是用於控制Swap的使用上限。
TasksAccounting=:是否開啓unit的task個數統計,BOOL型,ture或者false。
TasksMax=N:用於設置cgroup的pids.max
參數。控制unit能夠建立的最大tasks個數。
IOAccounting:是否開啓Block IO的統計,BOOL型,true或者false。對應舊版本的BlockIOAccounting=
參數。
IOWeight=weight, StartupIOWeight=weight:設置cgroup v2的io.weight
參數,控制IO的權重。取值範圍0-1000,默認100。該設置取代了舊版本的BlockIOWeight=
和StartupBlockIOWeight=
。
IODeviceWeight=device weight:控制單個設備的IO權重,一樣設置在cgroup v2的io.weight
參數裏,如「/dev/sda 1000」。取值範圍0-1000,默認100。該設置取代了舊版本的BlockIODeviceWeight=
。
IOReadBandwidthMax=device bytes, IOWriteBandwidthMax=device bytes:設置磁盤IO讀寫帶寬上限,對應cgroup v2的io.max
參數。該參數格式爲「path bandwidth」,path爲具體設備名或者文件系統路徑(最終限制的是文件系統對應的設備名)。數值bandwidth支持以K,M,G,T後綴(單位1000)。能夠設置多行以限制對多個設備的IO帶寬。該參數取代了舊版本的BlockIOReadBandwidth=
和BlockIOWriteBandwidth=
。
IOReadIOPSMax=device IOPS, IOWriteIOPSMax=device IOPS:設置磁盤IO讀寫的IOPS上限,對應cgroup v2的io.max
參數。格式和上面帶寬限制的格式同樣同樣的。
IPAccounting=:BOOL型,若是爲true,則開啓ipv4/ipv6的監聽和已鏈接的socket網絡收發包統計。
IPAddressAllow=ADDRESS[/PREFIXLENGTH]…, IPAddressDeny=ADDRESS[/PREFIXLENGTH]…:開啓AF_INET和AF_INET6 sockets的網絡包過濾功能。參數格式爲IPv4或IPv6的地址列表,IP地址後面支持地址匹配前綴(以'/'分隔),如」10.10.10.10/24「。須要注意,該功能僅在開啓「eBPF」模塊的系統上才支持。
DeviceAllow=:用於控制對指定的設備節點的訪問限制。格式爲「設備名 權限」,設備名以"/dev/"開頭或者"char-"、「block-」開頭。權限爲'r','w','m'的組合,分別表明可讀、可寫和能夠經過mknode建立指定的設備節點。對應cgroup的"devices.allow"和"devices.deny"參數。
DevicePolicy=auto|closed|strict:控制設備訪問的策略。strict表示:只容許明確指定的訪問類型;closed表示:此外,還容許訪問包含/dev/null,/dev/zero,/dev/full,/dev/random,/dev/urandom等標準僞設備。auto表示:此外,若是沒有明確的DeviceAllow=
存在,則容許訪問全部設備。auto是默認設置。
Slice=:存放unit的slice目錄,默認爲system.slice。
Delegate=:默認關閉,開啓後將更多的資源控制交給進程本身管理。開啓後unit能夠在單其cgroup下建立和管理其本身的cgroup的私人子層級,systemd將不在維護其cgoup以及將其進程從unit的cgroup裏移走。開啓方法:「Delegate=yes」。因此經過設置Delegate選項,能夠解決上面的問題。
配置選項(舊版本)
這些是舊版本的選項,新版本已經棄用。列出來是由於centos 7裏的systemd是舊版本,因此要使用這些配置。
CPUShares=weight, StartupCPUShares=weight:進程獲取CPU運行時間的權重值,對應cgroup的"cpu.shares"參數,取值範圍2-262144,默認值1024。
MemoryLimit=bytes:進程內存使用上限,對應cgroup的"memory.limit_in_bytes"參數。支持K,M,G,T(單位1024)以及infinity。默認值-1表示不限制。
BlockIOAccounting=:開啓磁盤IO統計選項,同上面的IOAccounting=。
BlockIOWeight=weight, StartupBlockIOWeight=weight:磁盤IO的權重,對應cgroup的"blkio.weight"參數。取值範圍10-1000,默認值500。
BlockIODeviceWeight=device weight:指定磁盤的IO權重,對應cgroup的"blkio.weight_device"參數。取值範圍1-1000,默認值500。
BlockIOReadBandwidth=device bytes, BlockIOWriteBandwidth=device bytes:磁盤IO帶寬的上限配置,對應cgroup的"blkio.throttle.read_bps_device"和 "blkio.throttle.write_bps_device"參數。支持K,M,G,T後綴(單位1000)。
問題解決
回到上面的問題,咱們能夠經過兩種方法解決: 1.在unit配置文件裏添加一個Delegate=yes
的選項,這樣資源控制徹底有用戶本身管理,systemd不會去移動進程到其默認建立的cgroup裏。 2.直接使用systemd的資源控制機制進行資源控制。經過直接使用systemd的資源控制的.service配置文件樣例:
[Unit] Description=xx Server [Service] ExecStart=/usr/bin/xx LimitNOFILE=102400 LimitNPROC=102400 LimitCORE=infinity Restart=on-failure KillMode=process MemoryLimit=1G CPUShares=1024 [Install] WantedBy=multi-user.target
修改完.service文件後,經過systemctl daemon-reload從新導入service文件,經過systemctl restart xx重啓服務。
總結
systemd有本身的資源控制機制,因此用systemd拉起的服務時,不要自做聰明建立本身的cgroup目錄並經過cgexec來拉起進程進行資源控制。
參考
systemd.resource-control systemd for Administrators, Part XVIII Control Group APIs and Delegation