提升效率和管理的 systemd

背景

最近工作频繁和 Linux 打交道,发现一个挺有用的配置:systemd,多次使用到 systemd 的场景:

  1. jenkins 启动节点的服务,避免手动执行 nohup、手动重连
  2. gitlab-runner 的服务,gitlab-runner install 时会创建一个 systemd 服务
  3. 配置 python http 服务,启动一个服务,让其他人可以访问某个目录下的 html 静态文件

systemd 功能

systemd(System Daemon)是 Linux 的系统和服务管理器,用于控制系统启动和后台服务。

  • 统一管理(systemctl start|stop|enable|status):所有服务用同样的命令
  • 开机自启(WantedBy=multi-user.target
  • 自动重启崩溃的服务(Restart=always
  • 日志集成:通过 journalctl 统一查看日志
  • 依赖控制(After=Requires=):可以定义服务启动顺序
  • 资源限制: 可以限制 CPU、内存等资源

实践场景

Jenkins Agent 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[Unit]
Description=Jenkins Agent Service
After=network.target

[Service]
Type=simple
User=magnolia
WorkingDirectory=/home/magnolia/jenkins_home
ExecStart=/usr/bin/java -jar /home/magnolia/jenkins_home/agent.jar \
-url http://127.0.0.1:8081/ \
-secret @/home/magnolia/jenkins_home/secret-file \
-name linux \
-webSocket \
-workDir /home/magnolia/jenkins_home/
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

在用户目录,创建一个 service 文件,软连接到 /etc/systemd/system/ (分离操作系统文件和本地配置)

1
sudo ln -s  /home/magnolia/jenkins_home/jenkins-agent.service /etc/systemd/system/jenkins-agent.service
1
2
3
4
5
sudo systemctl stop jenkins-agent
sudo systemctl daemon-reload
sudo systemctl start jenkins-agent
sudo systemctl status jenkins-agent
sudo journalctl -u jenkins-agent -f

GitLab Runner 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=GitLab Runner
ConditionFileIsExecutable=/usr/local/bin/gitlab-runner
After=network.target
[Service]
StartLimitInterval=5
StartLimitBurst=10
ExecStart=/usr/local/bin/gitlab-runner "run" "--config" "/etc/gitlab-runner/config.toml" "--working-directory" "/home/tools/gitlab-runner" "--service" "gitlab-runner" "--user" "gitlab-runner"
Restart=always
RestartSec=120
EnvironmentFile=-/etc/sysconfig/gitlab-runner
[Install]
WantedBy=multi-user.target

Python HTTP 静态文件服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=Python HTTP Static File Server
After=network.target

[Service]
Type=simple
User=autotest
WorkingDirectory=/home/autotest/work/auto-doc/doc/build/
ExecStart=/usr/bin/python3.6 -m http.server 8000 --bind 0.0.0.0
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Flask 服务

一个用于启动 flask 服务的 sh 脚本方式,start.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
# shellcheck disable=SC1090
source ~/tools/miniforge3/etc/profile.d/conda.sh
conda activate vltenvLinux
which python
python --version
flask --version
gunicorn --version
export $(grep -v '^#' .env | xargs)

TIMESTAMP=$(date '+%Y_%m_%d_%H_%M_%S')
ENABLE_SCHEDULER=true gunicorn -w 4 -b 0.0.0.0:5001 app:app \
--access-logfile "logs/gunicorn_access_${TIMESTAMP}.log" \
--error-logfile "logs/gunicorn_error_${TIMESTAMP}.log" \
--log-level info \
--timeout 120 \
--keep-alive 5 \
--max-requests 1000 \
--max-requests-jitter 100 \
--preload

转为 systemd 管理

step1,创建 systemd 服务文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# /home/autotest/vlt/vlt_track
(vltenvLinux) [autotest@dgvxl2905 vlt_track]$ cat vlt_track_app.service
[Unit]
Description=VLT TRACK Flask Application with Gunicorn
After=network.target

[Service]
Type=notify
User=autotest
WorkingDirectory=/home/autotest/vlt/vlt_track

Environment="PATH=/home/autotest/tools/miniforge3/envs/vltenvLinux/bin:/usr/local/bin:/usr/bin:/bin"
EnvironmentFile=/home/autotest/vlt/vlt_track/.env
Environment="ENABLE_SCHEDULER=true"

ExecStart=/home/autotest/tools/miniforge3/envs/vltenvLinux/bin/gunicorn \
-w 4 \
-b 0.0.0.0:5000 \
app:app \
--access-logfile /home/autotest/vlt/vlt_track/logs/gunicorn_access.log \
--error-logfile /home/autotest/vlt/vlt_track/logs/gunicorn_error.log \
--log-level info \
--timeout 120 \
--keep-alive 5 \
--max-requests 1000 \
--max-requests-jitter 100 \
--preload

# 优雅重启(用于日志轮转)
ExecReload=/bin/kill -USR1 $MAINPID

Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
1
2
mkdir -p /home/autotest/vlt/vlt_track/logs
sudo ln -s /home/autotest/vlt/vlt_track/vlt_track_app.service /etc/systemd/system/vlt_track_app.service

step2,创建 logrotate 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(base) [root@dgvxl2905 vlt_track]# cd /etc/logrotate.d/
(base) [root@dgvxl2905 logrotate.d]# cat vlt_track
/home/autotest/vlt/vlt_track/logs/gunicorn_*.log {
su autotest autotest
daily
rotate 30
compress
delaycompress
notifempty
missingok
dateext
dateformat _%Y%m%d
create 0644 autotest autotest
sharedscripts
postrotate
/bin/systemctl reload vlt_track_app.service > /dev/null 2>&1 || true
endscript
}

测试 logrotate 配置

1
2
3
4
5
6
7
8
9
10
11
(base) [root@dgvxl2905 logrotate.d]# logrotate -d /etc/logrotate.d/vlt_track
reading config file /etc/logrotate.d/vlt_track
Allocating hash table for state file, size 15360 B

Handling 1 logs

rotating pattern: /home/autotest/vlt/vlt_track/logs/gunicorn_*.log after 1 days (30 rotations)
empty log files are not rotated, old logs are removed
switching euid to 5991 and egid to 5991
considering log /home/autotest/vlt/vlt_track/logs/gunicorn_access_2025_08_06_11_21_11.log
log does not need rotating (log has been already rotated)considering log /home/autotest/vlt/vlt_track/logs/gunicorn_access_2025_08_06_11_48_12.log

step3,启动服务

1
2
3
systemctl daemon-reload
systemctl start vlt_track_app
systemctl status vlt_track_app
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(base) [root@dgvxl2905 vlt_track]# systemctl status vlt_track_app
● vlt_track_app.service - VLT TRACK Flask Application with Gunicorn
Loaded: loaded (/home/autotest/vlt/vlt_track/vlt_track_app.service; linked; vendor preset: disabled)
Active: active (running) since Fri 2025-10-17 10:17:41 CST; 2s ago
Main PID: 6208 (gunicorn)
Status: "Gunicorn arbiter booted"
Tasks: 8
Memory: 54.3M
CGroup: /system.slice/vlt_track_app.service
├─6208 /home/autotest/tools/miniforge3/envs/vltenvLinux/bin/python /home/autotest/tools/miniforge3/envs/vltenvLinux/bin/gunicorn -w 4 -b 0.0.0.0:...
├─6213 /home/autotest/tools/miniforge3/envs/vltenvLinux/bin/python /home/autotest/tools/miniforge3/envs/vltenvLinux/bin/gunicorn -w 4 -b 0.0.0.0:...
├─6228 /home/autotest/tools/miniforge3/envs/vltenvLinux/bin/python /home/autotest/tools/miniforge3/envs/vltenvLinux/bin/gunicorn -w 4 -b 0.0.0.0:...
├─6229 /home/autotest/tools/miniforge3/envs/vltenvLinux/bin/python /home/autotest/tools/miniforge3/envs/vltenvLinux/bin/gunicorn -w 4 -b 0.0.0.0:...
└─6230 /home/autotest/tools/miniforge3/envs/vltenvLinux/bin/python /home/autotest/tools/miniforge3/envs/vltenvLinux/bin/gunicorn -w 4 -b 0.0.0.0:...

Oct 17 10:17:40 dgvxl2905 systemd[1]: Starting VLT TRACK Flask Application with Gunicorn...
Oct 17 10:17:41 dgvxl2905 gunicorn[6208]: 2025-10-17 10:17:41.336 | INFO | utils.api_scanner:start_scheduler:297 - API扫描-定时任务已启动
Oct 17 10:17:41 dgvxl2905 gunicorn[6208]: 2025-10-17 10:17:41.336 | INFO | utils.api_scanner:start_scheduler:298 - API扫描-定时任务时间: 1点0分
Oct 17 10:17:41 dgvxl2905 gunicorn[6208]: 2025-10-17 10:17:41.336 | INFO | utils.api_scanner:init_api_scanner_scheduler:564 - API扫描-定时任务已…PID: 6208)
Oct 17 10:17:41 dgvxl2905 gunicorn[6208]: 2025-10-17 10:17:41.338 | INFO | utils.statistics_collector:start_scheduler:37 - 统计数据-定时任务已启…PID: 6208)
Oct 17 10:17:41 dgvxl2905 gunicorn[6208]: 2025-10-17 10:17:41.338 | INFO | utils.statistics_collector:init_statistics_collector_scheduler:70 - …(PID: 6208)
Oct 17 10:17:41 dgvxl2905 systemd[1]: Started VLT TRACK Flask Application with Gunicorn.
Hint: Some lines were ellipsized, use -l to show in full.

Timer

场景:每天凌晨2点自动清理 /tmp 目录中的旧文件。

step1:创建要执行的脚本

1
2
3
4
5
6
7
8
(base) magnolia@Magnolia:~/tools/systemd_tasks$ sudo vi cleanup-tmp.sh
(base) magnolia@Magnolia:~/tools/systemd_tasks$ cat cleanup-tmp.sh
#!/bin/bash
# 删除 /tmp 目录下 7 天前的文件
echo "$(date): 开始清理临时文件..."
find /tmp -type f -mtime +7 -delete
echo "$(date): 清理完成!"
(base) magnolia@Magnolia:~/tools/systemd_tasks$ sudo chmod +x cleanup-tmp.sh

step2:创建 Service 文件(定义要做什么)

1
2
3
4
5
6
7
8
(base) magnolia@Magnolia:~/tools/systemd_tasks$ sudo vi cleanup-tmp.service
(base) magnolia@Magnolia:~/tools/systemd_tasks$ cat cleanup-tmp.service
[Unit]
Description=清理临时文件

[Service]
Type=oneshot
ExecStart=/home/magnolia/tools/systemd_tasks/cleanup-tmp.sh

step3:创建 Timer 文件(定义什么时候执行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(base) magnolia@Magnolia:~/tools/systemd_tasks$ sudo vi cleanup-tmp.timer
(base) magnolia@Magnolia:~/tools/systemd_tasks$ cat cleanup-tmp.timer
[Unit]
Description=每天凌晨2点清理临时文件

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true # 如果错过了执行时间(比如关机了),开机后会立即执行一次

[Install]
WantedBy=timers.target

(base) magnolia@Magnolia:~/tools/systemd_tasks$ ls -l
total 12
-rw-r--r-- 1 root root 103 Oct 8 10:37 cleanup-tmp.service
-rwxr-xr-x 1 root root 166 Oct 8 10:31 cleanup-tmp.sh
-rw-r--r-- 1 root root 139 Oct 8 10:40 cleanup-tmp.timer

step4:软连接到 /etc/systemd/system/

在用户目录集中管理自定义配置,便于维护,systemd 通过软连接获取

1
2
3
4
5
6
(base) magnolia@Magnolia:/etc/systemd/system$ sudo ln -s /home/magnolia/tools/systemd_tasks/cleanup-tmp.service  /etc/systemd/system/cleanup-tmp.service
(base) magnolia@Magnolia:/etc/systemd/system$ sudo ln -s /home/magnolia/tools/systemd_tasks/cleanup-tmp.timer /etc/systemd/system/cleanup-tmp.timer
(base) magnolia@Magnolia:/etc/systemd/system$ ls -l
total 48
lrwxrwxrwx 1 root root 54 Oct 8 10:54 cleanup-tmp.service -> /home/magnolia/tools/systemd_tasks/cleanup-tmp.service
lrwxrwxrwx 1 root root 52 Oct 8 10:56 cleanup-tmp.timer -> /home/magnolia/tools/systemd_tasks/cleanup-tmp.timer

step5:启用和管理 Timer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(base) magnolia@Magnolia:/etc/systemd/system$ sudo systemctl daemon-reload
(base) magnolia@Magnolia:/etc/systemd/system$ sudo systemctl enable cleanup-tmp.timer
Created symlink /etc/systemd/system/timers.target.wants/cleanup-tmp.timer → /home/magnolia/tools/systemd_tasks/cleanup-tmp.timer.
(base) magnolia@Magnolia:/etc/systemd/system$ sudo systemctl start cleanup-tmp.timer
(base) magnolia@Magnolia:/etc/systemd/system$ sudo systemctl status cleanup-tmp.timer
● cleanup-tmp.timer - 每天凌晨2点清理临时文件
Loaded: loaded (/etc/systemd/system/cleanup-tmp.timer; enabled; preset: enabled)
Active: active (waiting) since Wed 2025-10-08 10:57:27 CST; 4s ago
Trigger: Thu 2025-10-09 02:00:00 CST; 15h left
Triggers: ● cleanup-tmp.service

Oct 08 10:57:27 Magnolia systemd[1]: Started cleanup-tmp.timer - 每天凌晨2点清理临时文件.
(base) magnolia@Magnolia:/etc/systemd/system$ systemctl list-timers
NEXT LEFT LAST PASSED UNIT ACTIVATES
Wed 2025-10-08 15:26:32 CST 4h 28min Wed 2025-10-08 02:40:01 CST 1h 12min ago motd-news.timer motd-news.service
Wed 2025-10-08 16:23:34 CST 5h 25min Fri 2025-10-03 10:54:44 CST 18h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Thu 2025-10-09 00:00:00 CST 13h Wed 2025-10-08 00:00:07 CST 1h 19min ago dpkg-db-backup.timer dpkg-db-backup.service
Thu 2025-10-09 00:00:00 CST 13h Wed 2025-10-08 00:00:07 CST 1h 19min ago logrotate.timer logrotate.service
Thu 2025-10-09 01:59:50 CST 15h Wed 2025-10-08 08:38:33 CST 1h 11min ago apt-daily.timer apt-daily.service
Thu 2025-10-09 02:00:00 CST 15h - - cleanup-tmp.timer cleanup-tmp.service
Thu 2025-10-09 06:47:26 CST 19h Wed 2025-10-08 07:07:45 CST 1h 11min ago apt-daily-upgrade.timer apt-daily-upgrade.service
Thu 2025-10-09 07:51:38 CST 20h Wed 2025-10-08 02:38:48 CST 1h 13min ago man-db.timer man-db.service
Sun 2025-10-12 03:10:17 CST 3 days Sun 2025-10-05 03:25:10 CST 11h ago e2scrub_all.timer e2scrub_all.service

9 timers listed.
Pass --all to see loaded but inactive timers, too.
(base) magnolia@Magnolia:/etc/systemd/system$ sudo journalctl -u cleanup-tmp.timer -f
Oct 08 10:57:27 Magnolia systemd[1]: Started cleanup-tmp.timer - 每天凌晨2点清理临时文件.