开机自启

在16.04版本以及之前版本都只需要在"/etc/rc.local"文件的"exit 0;"前加要执行的命令即可。服务器是18.04,etc目录下根本没有rc.local文件,ubuntu做了改动。

ubuntu-16.10 开始不再使用initd管理系统,改用systemd

systemd is now used for user sessions. System sessions had already been provided by systemd in previous Ubuntu releases.

systemd 的使用方法,发现改动有点大, 包括用 systemctl 命令来替换了 servicechkconfig 的功能。

比如以前启动 mysql 服务用:

1
sudo service mysql start

现在用:

1
sudo systemctl start mysqld.service

设置方法

systemd 默认读取 /etc/systemd/system 下的配置文件,该目录下的文件会链接/lib/systemd/system/下的文件。
执行 ls /lib/systemd/system 你可以看到有很多启动脚本,其中就有我们需要的 rc.local.service

打开脚本内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#  This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.

# This unit gets pulled automatically into multi-user.target by
# systemd-rc-local-generator if /etc/rc.local is executable.
[Unit]
Description=/etc/rc.local Compatibility
ConditionFileIsExecutable=/etc/rc.local
After=network.target

[Service]
Type=forking
ExecStart=/etc/rc.local start
TimeoutSec=0
RemainAfterExit=yes

一般正常的启动文件主要分成三部分

  • [Unit] 段: 启动顺序与依赖关系

  • [Service] 段: 启动行为,如何启动,启动类型

  • [Install] 段: 定义如何安装这个配置文件,即怎样做到开机启动

可以看出,/etc/rc.local 的启动顺序是在网络后面,但是显然它少了 Install 段,也就没有定义如何做到开机启动,所以显然这样配置是无效的。 因此我们就需要在后面帮他加上[Install] 段:

1
2
3
[Install]  
WantedBy=multi-user.target
Alias=rc-local.service

这里需要注意一下,ubuntu-18.04 默认是没有 /etc/rc.local 这个文件的,需要自己创建

1
sudo vim /etc/rc.local 

写入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
# 这里写入你要执行的命令;或者脚本(16.04之前版本只需要操作这里就ok了)
exit 0

然后把你需要启动脚本写入 /etc/rc.local ,我们不妨写一些测试的脚本放在里面,以便验证脚本是否生效.

1
echo "this is a test" > /root/test.log

做完这一步,还需要最后一步 前面我们说 systemd 默认读取 /etc/systemd/system 下的配置文件, 所以还需要在 /etc/systemd/system 目录下创建软链接

1
ln -s /lib/systemd/system/rc.local.service /etc/systemd/system/ 

rc.local加上权限

1
sudo chmod +x /etc/rc.local

启用服务

1
sudo systemctl enable rc-local

启动服务并检查状态

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
sudo systemctl start rc-local.service   # 启动服务
sudo systemctl status rc-local.service # 查看状态

sudo systemctl restart rc-local.service # 重启服务
sudo systemctl stop rc-local.service # 停止服务
sudo systemctl enable rc-local # 设置开机启动
########################系统服务管理################################
systemctl enable <unit>
systemctl disable <unit> # 取消<unit>开机自启动

systemctl start <unit> # 启动<unit>
systemctl restart <unit> # 重启<unit>
systemctl stop <unit> # 停止<unit>
systemctl status <unit> # 查看<unit>的状态
systemctl reload <unit> # 重新加载<unit>的配置文件而不关闭服务

systemctl is-active <unit> # 查看<unit>是否为激活状态
systemctl is-enabled <unit> # 查看<unit>是否设置了开机自启
systemctl is-failed <unit> # 查看<unit>是否加载失败
########################电源管理################################
systemctl reboot # 重启
systemctl poweroff # 关机
systemclt hibernate # 休眠
systemctl suspend # 待机

接下来,重启系统,然后看看/root/test.log文件是否存在就知道开机脚本是否生效了。检查test.log文件

1
cat /root/test.log  

编写Systemd Service

Systemd Service 位于 /etc/systemd/system(供系统管理员和用户使用),/usr/lib/systemd/system(供发行版打包者使用),我们一般使用前者即可。

Systemd 服务的内容主要分为三个部分,控制单元(unit)的定义、服务(service)的定义、以及安装部分。

定义控制单元 [Unit]

在 Systemd 中,所有引导过程中 Systemd 要控制的东西都是一个单元。基本的用法如下:

  • Description:对本service的描述。
  • Documentation:文档地址
  • BindsTo:与Requires类似,它指定的 Unit 如果退出,会导致当前 Unit 停止运行
  • Before, After:定义启动顺序,Before=xxx.service,代表本服务在xxx.service启动之前启动。After=xxx.service,代表本服务在xxx之后启动。
  • Requires: 这个单元启动了,那么它“需要”的单元也会被启动; 它“需要”的单元被停止了,它自己也活不了。但是请注意,这个设定并不能控制某单元与它“需要”的单元的启动顺序(启动顺序是另外控制的),即 Systemd 不是先启动 Requires 再启动本单元,而是在本单元被激活时,并行启动两者。于是会产生争分夺秒的问题,如果 Requires 先启动成功,那么皆大欢喜; 如果 Requires 启动得慢,那本单元就会失败(Systemd 没有自动重试)。所以为了系统的健壮性,不建议使用这个标记,而建议使用 Wants 标记。可以使用多个 Requires。
  • RequiresOverridable:跟 Requires 很像。但是如果这条服务是由用户手动启动的,那么 RequiresOverridable 后面的服务即使启动不成功也不报错。跟 Requires 比增加了一定容错性,但是你要确定你的服务是有等待功能的。另外,如果不由用户手动启动而是随系统开机启动,那么依然会有 Requires 面临的问题。
  • Requisite:强势版本的 Requires。要是这里需要的服务启动不成功,那本单元文件不管能不能检测等不能等待都立刻就会失败。
  • Wants:推荐使用。本单元启动了,它“想要”的单元也会被启动。但是启动不成功,对本单元没有影响。
  • Conflicts:一个单元的启动会停止与它“冲突”的单元,反之亦然。
  • Condition...:当前 Unit 运行必须满足的条件,否则不会运行
  • Assert...:当前 Unit 运行必须满足的条件,否则会报启动失败

看一个实际的例子:

1
2
3
4
[Unit]
Description=Protect ARP list
Wants=network-online.target
After=network.target

其中network.target代表有网路,network-online.target代表一个连通着的网络。

定义服务本体 [service]

  • 在定义完了 Systemd 用来识别服务的单元后,我们来定义服务本体。基本的用法如下:

  • Type:服务的类型,各种类型的区别如下所示

    • simple:默认,这是最简单的服务类型。意思就是说启动的程序就是主体程序,这个程序要是退出那么一切皆休。
    • forking:标准 Unix Daemon 使用的启动方式。启动程序后会调用 fork() 函数,把必要的通信频道都设置好之后父进程退出,留下守护精灵的子进程。
    • oneshot:适用于那些被一次性执行的任务或者命令,它运行完成后便了无痕迹。因为这类服务运行完就没有任何痕迹,我们经常会需要使用 RemainAfterExit=yes。意思是说,即使没有进程存在,Systemd 也认为该服务启动成功了。同时只有这种类型支持多条命令,命令之间用;分割,如需换行可以用\
    • dbus:这个程序启动时需要获取一块 DBus 空间,所以需要和 BusName= 一起用。只有它成功获得了 DBus 空间,依赖它的程序才会被启动。
    • notify:类似于simple,启动结束后会发出通知信号,然后 Systemd 再启动其他服务
    • idle:类似于simple,但是要等到其他任务都执行完,才会启动该服务。一种使用场合是为让该服务的输出,不与其他服务的输出相混
  • ExecStart:在输入的命令是start时候执行的命令,这里的命令启动的程序必须使用绝对路径,比如你必须用/sbin/arp而不能简单的以环境变量直接使用arp

  • ExecStop:在输入的命令是stop时候执行的命令,要求同上。

  • ExecReload:这个不是必需,如果不写则你的service就不支持restart命令。ExecStart和ExecStop是必须要有的。

  • ExecStartPost字段:启动服务之后执行的命令

  • ExecStopPost字段:停止服务之后执行的命令

  • ExecStartPre字段:启动服务之前执行的命令

  • EnvironmentFile 字段:指定当前服务的环境参数文件。该文件内部的 key=value 键值对,可以对 $key 的形式,在当前配置文件中获取。

  • Environment:指定环境变量

  • TimeoutSec:定义 Systemd 停止当前服务之前等待的秒数

  • RestartSec:自动重启当前服务间隔的秒数

  • Restart:定义何种情况 Systemd 会自动重启当前服务,可能的值包括

    • always(总是重启)
    • on-success
    • on-failure
    • on-abnormal
    • on-abort
    • on-watchdog

看一个实际的例子:

1
2
3
4
5
6
[Service]
Type=forking
RemainAfterExit=yes
ExecStart=/sbin/arp -f /etc/ip-mac
ExecReload=/sbin/arp -f /etc/ip-mac
ExecStop=/sbin/arp -d -a

这里在start和restart的时候会读取并添加/etc/ip-mac文件中的ARP条目到ARP表中,而stop时清空ARP表。

安装服务 [install]

  • 服务编写完之后还需要被systemd装载,定义安装单元各个字段如下:
  • WantedBy:设置服务被谁装载,一般设置为multi-user.target,它的值是一个或多个 Target,当前 Unit 激活时(enable)符号链接会放入/etc/systemd/system目录下面以 Target 名 + .wants后缀构成的子目录中
  • RequiredBy:它的值是一个或多个 Target,当前 Unit 激活时,符号链接会放入/etc/systemd/system目录下面以 Target 名 + .required后缀构成的子目录中
  • Alias:为service设置一个别名,可以使用多个名字来操作服务。
  • Also:在安装这个服务时候还需要的其他服务

完整配置实例

组合上面的三个模块,我们可以得到一个完整的 Systemd Service 配置实例:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=Protect ARP list
Wants=network-online.target
After=network.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/arp -f /etc/ip-mac
ExecReload=/sbin/arp -f /etc/ip-mac
ExecStop=/sbin/arp -d -a
[Install]
WantedBy=multi-user.target

Systemd Service 是一种替代/etc/init.d/下脚本的更好方式,它可以灵活的控制你什么时候要启动服务,一般情况下也不会造成系统无法启动进入紧急模式。所以如果想设置一些开机启动的东西,可以试着写 Systemd Service。当然了,前提是你使用的Linux发行版是支持它的才行。

参考

Systemd 入门教程:实战篇