0%

使用Systemd进行自动部署

作为实验室项目服务器的运 (bei) 维 (guo) 工 (xia) ,我长期经受着各种各样dirty工作的鞭打:频繁bug导致的重新部署、机房莫名奇妙的断电、nohup运行不优雅…

终于,在又一次因机房断电重启服务后我决定做点什么来减轻一下自己的压力。于是我注意到了Systemd。

什么是Systemd?

早期Linux使用SysVinit来做系统初始化,然而其具有着串行启动、功能单一诸多局限性,于是Systemd应运而生。它为系统的启动和管理提供了一套完整的解决方案。

Systemd可以管理所有的系统资源,不同资源统称为Unit。Unit共12种,包括

  • service: 系统服务
  • target: 多个unit构成的组
  • device: 硬件设备
  • mount: 文件系统的挂载点
  • automount: 自动挂载点
  • path: 文件或路径
  • scope: 不是由Systemd启动的外部进程
  • slice: 进程组
  • snapshot: Systemd快照,可以切回某个快照
  • socket: 进程间通信的socket
  • swap: swap文件
  • timer: 定时器

Systemd包括了一系列命令,本次使用到了systemctljournalctl,前者为Systemd的主命令,后者用于操作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
# systemctl常用命令
# 启动指定的服务
systemctl start foo.service

# 关闭指定的服务
systemctl stop foo.service

# 重启指定的服务
systemctl restart foo.service

# 重载指定的服务(支持时)
systemctl reload foo.service

# 查看指定服务的状态
systemctl status foo.service

# 显示指定unit的所有底层参数
systemctl show foo.service

# 查看指定服务的描述
systemctl cat foo.service

# 在下次启动时或满足其他触发条件时设置服务为启用
systemctl enable foo.service

# 在下次启动时或满足其他触发条件时设置服务为禁用
systemctl disable foo.service

# 列出可启动或停止的服务列表
systemctl list-unit-files --type=service

# 服务配置变更时使用
systemctl daemon-reload
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
# journalctl常用命令
# 查看所有日志
journalctl

# 查看指定时间的日志
journalctl --since "20 min ago"
journalctl --since="2012-10-30 18:17:16"
journalctl --since yesterday
journalctl --since "2015-01-10" --until "2015-01-11 03:00"
journalctl --since 09:00 --until "1 hour ago"

# 查看实时滚动的日志
journalctl -f

# 查看指定Unit的日志
journalctl -u foo.service

# 显示日志占据的硬盘空间
journalctl --disk-usage

# 指定日志文件占据的最大空间
journalctl --vacuum-size=500M

# 指定日志文件的最长保存时间
journalctl --vacuum-time=2d

service编写

服务器上需要配置的是一个前后端分离的web项目,二者独立更新,因此为二者分别编写service是更加灵活的选择。

前端服务的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# service-pattern-frontend.service
[Unit]
Description=Service Pattern System Frontend # 服务描述
After=network.target

[Service]
TimeoutStartSec=150 # Start的最长等待时间
WorkingDirectory=/home/cbs/workspace/servicesysclient # 工作目录
ExecStartPre=/usr/local/bin/npm run build # Start前先执行build
ExecStart=/home/cbs/.yarn/bin/serve -s build -l 6052 # 启动监听
ExecStop=/bin/kill -INT $MAINPID # 使用SIGINT结束服务器的监听
RestartSec=15 # 重启前等待时间
Restart=on-failure # 重启策略

[Install]
WantedBy=multi-user.target

后端服务的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# service-pattern-backend.service
[Unit]
Description=Service Pattern System Backend # 服务描述
After=network.target

[Service]
WorkingDirectory=/home/cbs/workspace/servicesysserver # 工作目录
ExecStart=/home/cbs/anaconda3/bin/python manage.py runserver 0.0.0.0:6051 # 启动Django
ExecStop=/bin/kill -TERM $MAINPID # 使用SIGTERM结束服务器的监听
RestartSec=15 # 重启前等待时间
Restart=on-failure # 重启策略

[Install]
WantedBy=multi-user.target

简要介绍一下这两个service文件。首先看二者的相同部分

1
2
3
4
5
6
7
8
9
# foo.service
[Unit]
...
After=network.target

...

[Install]
WantedBy=multi-user.target

[Unit]中的After=network.target表示当前服务需要在启动network.target之后才能启动。不过,由于项目中的前后端都跑在私有地址上,再通过端口映射暴露到外网,根据NetWorkTarget,不添加这一设置也是可行的,后面可以找时间再测试一下。

[Install]中定义了WantedBy=multi-user.target,这一配置是服务实现开机自启动的关键。当执行

1
systemctl enable foo.service

时,foo.service将在/etc/systemd/system/multi-user.target.wants下创建一个符号链接。在终端环境下,multi-user.target是默认的启动target,这个组里的所有服务都将开机启动。

配置流程

以前端的service-pattern-frontend.service为例,记录一下完整的配置流程。

首先创建.service文件

1
touch service-pattern-frontend.service

并填入上面的配置内容。

接着执行

1
sudo cp service-pattern-frontend.service /etc/systemd/system

将文件拷入保存service的目录中。

此时输入systemctl cat service-pattern-frontend.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(base) cbs@ubuntu:/$ systemctl cat service-pattern-frontend.service 
# /etc/systemd/system/service-pattern-frontend.service
[Unit]
Description=Service Pattern System Frontend
After=network.target

[Service]
TimeoutStartSec=150
WorkingDirectory=/home/cbs/workspace/servicesysclient
ExecStartPre=/usr/local/bin/npm run build
ExecStart=/home/cbs/.yarn/bin/serve -s build -l 6052
ExecStop=/bin/kill -INT $MAINPID
RestartSec=15
Restart=on-failure

[Install]
WantedBy=multi-user.target

已经可以看到配置文件的内容。

之后执行

1
sudo systemctl start service-pattern-frontend.service

启动服务。

npm run build的时间较长,因此在配置中额外设置了TimeoutStartSec,延长了等待时间。若未设置这一字段,Systemd将采用在/etc/systemd/system.conf中定义的DefaultTimeoutStartSec=90s。采用默认值可能使得npm run build来不及完成。

启动成功后,输入systemctl status service-pattern-frontend.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(base) cbs@ubuntu:/$ systemctl status service-pattern-frontend.service
● service-pattern-frontend.service - Service Pattern System Frontend
Loaded: loaded (/etc/systemd/system/service-pattern-frontend.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2021-02-26 01:26:48 CST; 14h ago
Process: 26222 ExecStartPre=/usr/local/bin/npm run build (code=exited, status=0/SUCCESS)
Main PID: 26422 (node)
Tasks: 11 (limit: 4915)
CGroup: /system.slice/service-pattern-frontend.service
└─26422 node /home/cbs/.yarn/bin/serve -s build -l 6052

Feb 26 01:26:48 ubuntu npm[26222]: You can also analyze the project dependencies: https://goo.gl/LeUzfb
Feb 26 01:26:48 ubuntu npm[26222]: The project was built assuming it is hosted at /.
Feb 26 01:26:48 ubuntu npm[26222]: You can control this with the homepage field in your package.json.
Feb 26 01:26:48 ubuntu npm[26222]: The build folder is ready to be deployed.
Feb 26 01:26:48 ubuntu npm[26222]: You may serve it with a static server:
Feb 26 01:26:48 ubuntu npm[26222]: serve -s build
Feb 26 01:26:48 ubuntu npm[26222]: Find out more about deployment here:
Feb 26 01:26:48 ubuntu npm[26222]: bit.ly/CRA-deploy
Feb 26 01:26:48 ubuntu systemd[1]: Started Service Pattern System Frontend.
Feb 26 01:26:48 ubuntu serve[26422]: INFO: Accepting connections at http://localhost:6052

可以看到前端服务已经启动成功。

使用journalctl -f -u service-pattern-frontend.service可以查看启动日志

1
2
3
4
5
6
7
8
9
10
11
12
(base) cbs@ubuntu:/$ journalctl -f -u service-pattern-frontend.service
-- Logs begin at Fri 2020-07-31 12:30:45 CST. --
Feb 26 01:26:48 ubuntu npm[26222]: You can also analyze the project dependencies: https://goo.gl/LeUzfb
Feb 26 01:26:48 ubuntu npm[26222]: The project was built assuming it is hosted at /.
Feb 26 01:26:48 ubuntu npm[26222]: You can control this with the homepage field in your package.json.
Feb 26 01:26:48 ubuntu npm[26222]: The build folder is ready to be deployed.
Feb 26 01:26:48 ubuntu npm[26222]: You may serve it with a static server:
Feb 26 01:26:48 ubuntu npm[26222]: serve -s build
Feb 26 01:26:48 ubuntu npm[26222]: Find out more about deployment here:
Feb 26 01:26:48 ubuntu npm[26222]: bit.ly/CRA-deploy
Feb 26 01:26:48 ubuntu systemd[1]: Started Service Pattern System Frontend.
Feb 26 01:26:48 ubuntu serve[26422]: INFO: Accepting connections at http://localhost:6052

最后,使用

1
sudo systemctl enable service-pattern-frontend.service

设置开机自启动,配置完毕。

若对service文件有任何修改,记得使用

1
sudo systemctl daemon-reload

重新载入配置文件。

由于后端服务有一功能会有大量std输出,曾经出现过40多G的nohup.out,因此每隔一段时间需要执行一下

1
sudo journalctl --vacuum-size=BYTES

清理日志文件。

之后进行代码更新时,只需要重启一次服务即可,不用再kill,serve二连。日志也有了统一管理的工具,不需要再去nohup.out里一行行找。更重要的是,不用再担心哪天机房断电我又被叫去重启服务了,好耶!

Reference