首頁 > 軟體

Linux systemd資源控制初步理解分析

2020-06-16 16:51:06

本文記錄一次cgroup子目錄丟失問題,並簡單探索了Linux systemd的資源控制機制。

問題現象

我們希望通過systemd拉起服務並通過cgroup限制其CPU、memory的使用,因此我們新建了一個.service檔案,檔案裡面建立了自己的cgroup目錄,設定了cpu、memory限制,然後通過cgexec拉起我們的服務進程。假設我們的服務叫xx,.service檔案大概是這樣的:

[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資源也就沒有限制住。

分析過程

剛開始的定位過程是很懵逼的,各種紀錄檔檢視沒有發現線索,嘗試復現也沒有成功。正在苦惱沒有方向之際,無意中發現執行了其他服務的systemd的某些操作(stop/start/enable)之後,復現了問題,就這樣盯上了systemd。
 後來發現其實一開始就可以通過檢視進程的cgroup資訊就能很快找到線索:進程cgroup移到了/system.slice/xx.service目錄下:

[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顯然沒有設定過資源限制。

systemd資源控制

systemd通過Unit的組態檔設定資源控制,Unit包括services(上面例子就是一個service unit), slices, scopes, sockets, mount points, 和swap devices六種。systemd底層也是依賴Linux Control Groups (cgroups)來實現資源控制。

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。

CPUWeight=weight, StartupCPUWeight=weight:用於設定cgroup v2的cpu.weight引數。取值範圍1-1000,預設值100。StartupCPUWeight應用於系統啟動階段,CPUWeight應用於正常執行時。這兩個設定取代了舊版本的CPUShares=和StartupCPUShares=。

CPUQuota=:用於設定cgroup v2的cpu.max引數或者cgroup v1的cpu.cfs_quota_us引數。表示可以佔用的CPU時間配額百分比。如:20%表示最大可以使用單個CPU核的20%。可以超過100%,比如200%表示可以使用2個CPU核。

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

本文永久更新連結地址https://www.linuxidc.com/Linux/2018-05/152623.htm


IT145.com E-mail:sddin#qq.com