systemd tmpfiles相關的服務

語言: CN / TW / HK

前言

前些日子在排查一個httpd session的問題,發現虛機儘管重啟了,但是Firefox瀏覽器並不logout,機器重啟後,Firefox的傳送的請求,httpd後臺依然會處理。

一般來講,httpd請求會為每一個client維護一個session,session相關的資訊存放位置

  • CentOS:/tmp/systemd-private-5257097ec71448b0aac566695b533a84-httpd.service-dMlTTI/sessions 這種類似的目錄
CentOS 版本:
---------------
[root@node-a tmp]# tree systemd-private-5257097ec71448b0aac566695b533a84-httpd.service-dMlTTI
systemd-private-5257097ec71448b0aac566695b533a84-httpd.service-dMlTTI
└── tmp
    └── sessions
        └── 6d474c39c1606792c92f72973c0f135dc07b6682
2 directories, 1 file

注意為什麼CentOS版本的session存放在一個systemd-priviate-開頭的奇怪目錄下。原因是寫在httpd的systemd啟動指令碼中:

[root@node-a system]# cat httpd.service 
[Unit]
Description=The Apache HTTP Server
After=network.target remote-fs.target nss-lookup.target
Documentation=man:httpd(8)
Documentation=man:apachectl(8)

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/httpd
ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND
ExecReload=/usr/sbin/httpd $OPTIONS -k graceful
ExecStop=/bin/kill -WINCH ${MAINPID}
# We want systemd to give httpd some time to finish gracefully, but still want
# it to kill httpd after TimeoutStopSec if something went wrong during the
# graceful stop. Normally, Systemd sends SIGTERM signal right after the
# ExecStop, which would kill httpd. We are sending useless SIGCONT here to give
# httpd time to finish.
KillSignal=SIGCONT
PrivateTmp=true

[Install]
WantedBy=multi-user.target

注意上面的PrivateTmp=true,這個選項是一個systemd選項,表示該服務會有一個獨立的/tmp目錄作為自己的/tmp目錄,對於httpd而言:

drwx------  3 root root    4096 Mar 18 13:22 systemd-private-5257097ec71448b0aac566695b533a84-httpd.service-dMlTTI
drwx------  3 root root    4096 Mar 18 12:44 systemd-private-5257097ec71448b0aac566695b533a84-ntpd.service-YDlnOF

很多服務都有類似的行為,比如上面的ntpd的service,毫不意外,ntpd的service指令碼中也有:

[Unit]
Description=Network Time Service
After=syslog.target ntpdate.service sntp.service

[Service]
Type=forking
EnvironmentFile=-/etc/sysconfig/ntpd
ExecStart=/usr/sbin/ntpd -u ntp:ntp $OPTIONS
PrivateTmp=true

[Install]
WantedBy=multi-user.target

很奇怪的現象是,CentOS的session檔案,有以下行為模式:

  • reboot -f時,sessoin並不會被清理 。reboot -f,然後重建叢集,發現很多很奇怪的請求到來,並且被後端正常處理。
  • reboot的時候,本次的session檔案會被清理,但是歷史垃圾的session資訊不會被清理。

systemd-tmpfiles 相關的服務

Linux作業系統,會有一些臨時存放檔案的區域,最典型的就應該是/tmp/目錄下,該目錄存放的檔案,一般為臨時存放區,不重要,可損失的檔案,正式因為這個區域不那麼嚴肅,所以很多程序或者人,會堆放一些檔案在該目錄下,如果沒有任何服務負責管理,很可能會累積非常多的垃圾檔案。

Linux的systemd提供了tmpfiles相關的服務:

[root@node-a system]# systemctl status systemd-tmpfiles-*
● systemd-tmpfiles-clean.service - Cleanup of Temporary Directories
   Loaded: loaded (/usr/lib/systemd/system/systemd-tmpfiles-clean.service; static; vendor preset: disabled)
   Active: inactive (dead) since Fri 2022-03-18 13:00:01 CST; 1h 14min ago
     Docs: man:tmpfiles.d(5)
           man:systemd-tmpfiles(8)
  Process: 12340 ExecStart=/usr/bin/systemd-tmpfiles --clean (code=exited, status=0/SUCCESS)
 Main PID: 12340 (code=exited, status=0/SUCCESS)

Mar 18 13:00:01 node-a systemd[1]: Starting Cleanup of Temporary Directories...
Mar 18 13:00:01 node-a systemd[1]: Started Cleanup of Temporary Directories.

● systemd-tmpfiles-clean.timer - Daily Cleanup of Temporary Directories
   Loaded: loaded (/usr/lib/systemd/system/systemd-tmpfiles-clean.timer; static; vendor preset: disabled)
   Active: active (waiting) since Fri 2022-03-18 12:44:40 CST; 1h 29min ago
     Docs: man:tmpfiles.d(5)
           man:systemd-tmpfiles(8)

Mar 18 12:44:40 node-a systemd[1]: Started Daily Cleanup of Temporary Directories.

● systemd-tmpfiles-setup-dev.service - Create Static Device Nodes in /dev
   Loaded: loaded (/usr/lib/systemd/system/systemd-tmpfiles-setup-dev.service; static; vendor preset: disabled)
   Active: active (exited) since Fri 2022-03-18 12:44:35 CST; 1h 29min ago
     Docs: man:tmpfiles.d(5)
           man:systemd-tmpfiles(8)
  Process: 556 ExecStart=/usr/bin/systemd-tmpfiles --prefix=/dev --create --boot (code=exited, status=0/SUCCESS)
 Main PID: 556 (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/systemd-tmpfiles-setup-dev.service

Mar 18 12:44:35 node-a systemd[1]: Starting Create Static Device Nodes in /dev...
Mar 18 12:44:35 node-a systemd[1]: Started Create Static Device Nodes in /dev.

● systemd-tmpfiles-setup.service - Create Volatile Files and Directories
   Loaded: loaded (/usr/lib/systemd/system/systemd-tmpfiles-setup.service; static; vendor preset: disabled)
   Active: active (exited) since Fri 2022-03-18 12:44:38 CST; 1h 29min ago
     Docs: man:tmpfiles.d(5)
           man:systemd-tmpfiles(8)
  Process: 805 ExecStart=/usr/bin/systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev (code=exited, status=0/SUCCESS)
 Main PID: 805 (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/systemd-tmpfiles-setup.service

Mar 18 12:44:38 node-a systemd[1]: Starting Create Volatile Files and Directories...
Mar 18 12:44:38 node-a systemd[1]: Started Create Volatile Files and Directories.
[root@node-a system]# vim systemd-tmpfiles-clean.service

清理工作一般分成兩類:

  • 開機啟動時的清理
    • systemd-tmpfiles-setup.service
    • systemd-tmpfiles-setup-dev.service
  • Linux正常執行期間的清理
    • systemd-tmpfiles-clean.timer

開機清理,這個是比較容易理解,開機的時候,可能所有服務都沒有啟動,上一輪機器執行階段,可能產生了不少垃圾檔案,或者沒來得及清理的檔案(比如異常掉電或者reboot -f),需要對具體的目錄做一些清理動作。

另外一個就是長時間執行狀態下,也要有服務負責清理,因為Linux伺服器一般不喜歡關機,我們也有很多機器線上執行時間1000天以上,完全指望開機清理,可能勢必垃圾檔案堆積成山。

我們一起看下sytemd-tmpfiles-setup.service配置檔案的內容:

[Unit]
Description=Create Volatile Files and Directories
Documentation=man:tmpfiles.d(5) man:systemd-tmpfiles(8)
DefaultDependencies=no
Conflicts=shutdown.target
After=systemd-readahead-collect.service systemd-readahead-replay.service local-fs.target systemd-sysusers.service
Before=sysinit.target shutdown.target
RefuseManualStop=yes

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev

該檔案可以看到,主要是呼叫一個名稱為/usr/bin/systemd-tmpfiles的可執行檔案,傳了一些引數。sytemd-tmpfiles有一些引數值得我們注意:

  • create:

    • If this option is passed, all files and directories marked with f, F, w, d, D, v, p, L, c, b, m in the configuration files are created
                 or written to. Files and directories marked with z, Z, t, T, a, and A have their ownership, access mode and security labels set.
  • remove

    • If this option is passed, the contents of directories marked with D or R, and files or directories themselves marked with r or R are removed.
  • clean

    • If this option is passed, all files and directories with an age parameter configured will be cleaned up.
  • boot

    • Also execute lines with an exclamation mark. (帶感嘆號的行, --boot才會執行)

boot最好理解,正常執行期間和重啟剛開機是又分別的,有些目錄,開機階段可以粗暴刪除,但是正常執行期間不能太粗暴,因為你可能沒有辦法確定,該檔案或者目錄是否還在被使用。因此後面配置檔案會有區分的手段,劇透一下就是 action後面加感嘆號,就是隻有boot階段才應該執行,正常執行期間,本條配置可以忽略。

systemd-tmpfiles 的可配置性

我們講了很多了,但是到底如何告知systemd那些檔案可以被清理,可被清理的檔案又遵循什麼樣的策略呢?這個地方就到了配置檔案部分了。

配置檔案的名稱必須符合 package.conf package-part.conf格式。 當需要明確的將某部分(part)配置提取出來,以方便使用者專門針對這部分進行修改的時候, 應該使用第二種命名格式。

對於不同目錄下的同名配置檔案,僅以優先順序最高的目錄中的那一個為準。具體說來就是: /etc/tmpfiles.d 的優先順序最高、 /run/tmpfiles.d 的優先順序居中、 /usr/lib/tmpfiles.d 的優先順序最低。 軟體包應該將自帶的配置檔案安裝在 /usr/lib/tmpfiles.d 目錄中, 而 /etc/tmpfiles.d 目錄僅供系統管理員使用。 所有的配置檔案,無論其位於哪個目錄中,都統一按照檔名的字典順序處理。 如果在多個配置檔案中設定了同一個路徑(檔案或目錄),那麼僅以檔名最靠前(字典順序)的那一個為準, 其他針對同一個路徑的配置項將會作為警告資訊記錄到錯誤日誌中。 如果有兩行的路徑互為前後綴,那麼始終是先建立字首行、再建立字尾行, 如果還需要刪除,那麼順序正好相反,始終是先刪除字尾行、再刪除字首行。 所有帶有shell風格萬用字元的行,都在所有不帶萬用字元的行之後處理。如果有多個操作符應用於同一個檔案(例如 ACL, xattr, 檔案屬性調整),那麼將始終按固定的順序操作。除上述清空之外,對於其他情況, 檔案與目錄總是按照它們在配置檔案中出現的順序處理。

[root@node-a system]# cd /usr/lib/tmpfiles.d/
[root@node-a tmpfiles.d]# ll
total 132
-rw-r--r--. 1 root root   29 Mar 16 04:33 ceph-common.conf
-rw-r--r--. 1 root root   35 Apr  1  2020 cryptsetup.conf
-rw-r--r--. 1 root root   26 Mar 16 05:02 ctdb.conf
-rw-r--r--. 1 root root   67 Dec 11 08:32 elasticsearch.conf
-rw-r--r--. 1 root root  464 Nov 17  2020 etc.conf
-rw-r--r--. 1 root root   77 Nov 16  2020 httpd.conf
-rw-r--r--. 1 root root   39 Nov 17  2020 initscripts.conf
-rw-r--r--. 1 root root   75 Dec 15  2020 iscsi.conf
-rw-r--r--. 1 root root 1181 Nov 17  2020 legacy.conf
-rw-r--r--. 1 root root   34 Apr  1  2020 libselinux.conf
-r--r--r--. 1 root root   61 Dec 16  2020 lvm2.conf
-rw-r--r--. 1 root root   34 Oct  1  2020 mdadm.conf
-rw-r--r--. 1 root root   35 Jan 22  2021 net-snmp.conf
-rw-r--r--. 1 root root   27 Sep 30  2020 nscd.conf
-rw-r--r--. 1 root root  104 Sep 30  2020 nss-pam-ldapd.conf
-rw-r--r--. 1 root root   85 Oct  1  2020 openldap.conf
-rw-r--r--. 1 root root  110 Apr  1  2020 pam.conf
-rw-r--r--. 1 root root   15 Apr 10  2017 proftpd.conf
-rw-r--r--. 1 root root   16 Nov 17  2020 python.conf
-rw-r--r--. 1 root root   42 Nov 17  2020 resource-agents.conf
-rw-r--r--. 1 root root   87 Apr  1  2020 rpcbind.conf
-rw-r--r--. 1 root root   22 Oct  1  2020 rpm.conf
-rw-r--r--. 1 root root   60 Mar 16 05:02 samba.conf
-rw-r--r--. 1 root root  228 Nov 17  2020 sap.conf
-rw-r--r--. 1 root root   72 Oct  1  2020 screen.conf
-rw-r--r--. 1 root root  137 Nov 17  2020 selinux-policy.conf
-rw-r--r--. 1 root root  305 Jan 27  2021 sudo.conf
-rw-r--r--. 1 root root 1662 Nov 17  2020 systemd.conf
-rw-r--r--. 1 root root  496 Nov 17  2020 systemd-nologin.conf
-rw-r--r--. 1 root root  638 Nov 17  2020 tmp.conf
-rw-r--r--. 1 root root   56 Mar 22  2019 tuned.conf
-rw-r--r--. 1 root root  563 Nov 17  2020 var.conf
-rw-r--r--. 1 root root  623 Nov 17  2020 x11.conf
[root@node-a tmpfiles.d]# 
[root@node-a tmpfiles.d]# cd /etc/tmpfiles.d/
[root@node-a tmpfiles.d]# ll
total 0
[root@node-a tmpfiles.d]#

主要的配置檔案都被存放在/etc/tmpfiles.d和 /usr/lib/tmpfiles.d/下。其中/usr/lib/tmpfiles.d下可以看到有形形色色的配置檔案。Ubuntu版本的產品比較神器,可以自動清理/tmp/sessions下的檔案,甚至整個/tmp/sessions都被刪除,我們看下配置檔案:

root@CVM01:/usr/lib/tmpfiles.d# cat tmp.conf 
D /tmp 1777 root root -
#q /var/tmp 1777 root root 30d

# Exclude namespace mountpoints created with PrivateTmp=yes
x /tmp/systemd-private-%b-*
X /tmp/systemd-private-%b-*/tmp
x /var/tmp/systemd-private-%b-*
X /var/tmp/systemd-private-%b-*/tmp

這裡除了我們比較熟悉的路徑意外,有寫D x X之類的奇奇怪怪的字母,這些何意?

systemd-tmpfiles 配置語法

tmpfiles.d 配置檔案定義了一套臨時檔案管理機制: 建立 檔案、目錄、管道、裝置節點, 調整訪問模式、所有者、屬性、限額、內容, 刪除過期檔案。 主要用於管理易變的臨時檔案與目錄,例如 /run , /tmp , /var/tmp , /sys , /proc , 以及 /var 下面的某些目錄

配置檔案的格式是每行對應一個路徑,包含如下欄位: 型別, 路徑, 許可權, 屬主, 屬組, 壽命, 引數

#Type Path        Mode User Group Age Argument
d     /run/user   0755 root root  10d -
L     /tmp/foobar -    -    -     -   /dev/null

欄位值可以用引號界定,並可以包含C風格的轉義字元

然後就到了奇奇怪怪的字母部分了,因為它支援的Type太多了,我就一一羅列的,否則也記不住,我重點介紹常用的,如果需要了解細節的,可以閱讀 金步國的文章

  • d 建立指定的目錄並賦於指定的User/Group與許可權。如果指定的目錄已經存在,那麼僅調整User/Group與許可權。 如果指定了”壽命”欄位,那麼該目錄中的內容將遵守基於時間的清理策略。
  • D 與 d 類似, 但是如果使用了 --remove 選項,那麼將會清空目錄中的所有內容。

注意此處, 我們看下Ubuntu的tmp.conf配置檔案裡面的一行:

D /tmp 1777 root root -

這一行毀天滅地,如果有印象的話,systemd-tmpfiles-setup.service執行的是:

ExecStart=/usr/bin/systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev

這會將/tmp/下的所有東西一掃而空。但是Ubuntu版本有些人會說,沒有,啟動之後/tmp/目錄下還有檔案。這些檔案是重啟之後創建出來的。因此我們就明白了為什麼Ubuntu下,/tmp/sessions會被清理,因為實時上不僅它會被清理,整個/tmp/都會被清理。

  • x 根據”壽命”欄位清理過期檔案時, 忽略指定的路徑及該路徑下的所有內容。 可以在”路徑”欄位中使用shell風格的萬用字元。 注意, 這個保護措施對 rR 無效。
  • R 在根據”壽命”欄位清理過期檔案時, 僅忽略指定的路徑自身而不包括該路徑下的其他內容。 可以在”路徑”欄位中使用shell風格的萬用字元。 注意, 這個保護措施對 rR 無效。

這兩個是額外的保護,即按照時間線來清理的時候,免死金牌,不被清理。但是這個保護措施對後面提到的r和R無效

  • r 若指定的檔案或目錄存在, 則刪除它。 不可用於非空目錄。 可以在”路徑”欄位中使用shell風格的萬用字元。 不追蹤軟連線。
  • R 若指定的檔案或目錄存在,則遞迴的刪除它。 可用於非空目錄。 可以在”路徑”欄位中使用shell風格的萬用字元。 不追蹤軟連線。

這兩個刪除指令,R可遞迴,r不可。支援萬用字元。我們欣賞下Ubuntu 20.04版本里的systemd-tmp.conf

# Exclude namespace mountpoints created with PrivateTmp=yes
x /tmp/systemd-private-%b-*
X /tmp/systemd-private-%b-*/tmp
x /var/tmp/systemd-private-%b-*
X /var/tmp/systemd-private-%b-*/tmp

# Remove top-level private temporary directories on each boot
R! /tmp/systemd-private-*
R! /var/tmp/systemd-private-*

上面赦免不用說了,正常清理過程中,不要按照時間清理/tmp/systemd-private/打頭的檔案。但是後面R!表示機器重啟期間,要將上述檔案徹底刪除,防止留下垃圾。

使用了感嘆號(!)標記的行,僅可在系統啟動過程中執行, 而不能用於執行中的系統(會破壞系統的正常執行)。 未使用感嘆號(!)標記的行, 可以在任意時間安全的執行(例如升級軟體包的時候)。 systemd-tmpfiles 僅在明確使用了 --boot 選項的時候 才會執行使用了感嘆號(!)標記的行。

分析CentOS版本的行為

# Clear tmp directories separately, to make them easier to override
v /tmp 1777 root root 10d
v /var/tmp 1777 root root 30d

# Exclude namespace mountpoints created with PrivateTmp=yes
x /tmp/systemd-private-%b-*
X /tmp/systemd-private-%b-*/tmp
x /var/tmp/systemd-private-%b-*
X /var/tmp/systemd-private-%b-*/tmp

啥也不說了。根本就不清理 /tmp/system-private-檔案。

如何除錯systemd-tmpfiles

env SYSTEMD_LOG_LEVEL=debug systemd-tmpfiles --clean

這種方式比較詳細,會把中間的判斷過程也打印出來,如果清理行為不理解的話,可以用這個方法,看下為什麼行為和自己理解的不一樣。

[root@node-a tmpfiles.d]# env SYSTEMD_LOG_LEVEL=debug systemd-tmpfiles --clean
Reading config file "/usr/lib/tmpfiles.d/ceph-common.conf".
Reading config file "/usr/lib/tmpfiles.d/cryptsetup.conf".
Reading config file "/usr/lib/tmpfiles.d/ctdb.conf".
Reading config file "/usr/lib/tmpfiles.d/elasticsearch.conf".
Reading config file "/usr/lib/tmpfiles.d/etc.conf".
Reading config file "/usr/lib/tmpfiles.d/httpd.conf".
Reading config file "/usr/lib/tmpfiles.d/initscripts.conf".
Reading config file "/usr/lib/tmpfiles.d/iscsi.conf".
Reading config file "/run/tmpfiles.d/kmod.conf".
Ignoring entry c! "/dev/fuse" because --boot is not specified.
Ignoring entry c! "/dev/cuse" because --boot is not specified.
Ignoring entry c! "/dev/btrfs-control" because --boot is not specified.
Ignoring entry c! "/dev/loop-control" because --boot is not specified.
Ignoring entry c! "/dev/net/tun" because --boot is not specified.
Ignoring entry c! "/dev/ppp" because --boot is not specified.
Ignoring entry c! "/dev/uinput" because --boot is not specified.
Ignoring entry c! "/dev/mapper/control" because --boot is not specified.
Ignoring entry c! "/dev/uhid" because --boot is not specified.
Ignoring entry c! "/dev/vfio/vfio" because --boot is not specified.
Ignoring entry c! "/dev/vhci" because --boot is not specified.
Ignoring entry c! "/dev/vhost-net" because --boot is not specified.
....

如果帶上引數 –boot 和–remove,模擬的基本就是開機啟動時的清理邏輯:

env SYSTEMD_LOG_LEVEL=debug systemd-tmpfiles --clean

參考文獻: