Total Pageviews

Sunday, 7 October 2018

用Systemd管理log

管理coredump

注:debian testing中systemd(204)的coredumpctl目前不可用,打包时候选择了--disable-coredump,原因不明。本节实验在fedora20上完成(with systemd-208)。

开启core dump

#ulimit -c
输入以上命令看看返回值,如果是非0,则表示core dump功能已开启,如果是0,需要手动开启。
手动开启(例:无限制存储、64MB)
#ulimit -c unlimited
#ulimit -c 67108864
注:以上设置只在当前终端下生效,全局设置可以在/etc/profile.d/目录新建配置文件写入配置信息。

获取core dump文件

systemd下使用coredumpctl工具获取的core dump是来自它的journal,即在磁盘目录上是找不到存储文件的(注:215有变化,见下面)。
查看所有的core dump记录:
# systemd-coredumpctl
# systemd-coredumpctl list '要匹配的字符串'
需要将core dump导出为文件,只需指定--output=FILE。另外coredumpctl可以根据PID、进程名称、执行路径等对code dump进行筛选,更多选项请man coredumpctl
注:systemd-215开始,可以配置为自动将coredumps存储在磁盘上(/var/lib/systemd/coredump,可能会经过压缩)。这个选项是默认开启的,配置文件在/etc/systemd/coredum.conf
若要配置为在指定目录获取core dump文件,而不通过systemd,只需配置kernel.core_pattern参数即可。
/sysctl/kernel.txt#core_pattern:
If the first character of the pattern is a '|', the kernel will treat the rest of the pattern as a command to run. The core dump will be written to the standard input of that program instead of to a file.
这个文件的默认值在systemd 下,是一个以'|' 字符(其实就是个管道)开头的字符串,一般类似:
| /usr/lib/systemd/systemd-coredump [参数](参数请在上文链接中中搜索'corename format specifiers')
若我们期望在指定目录(例如当前目录)立刻产生转储文件,那么只需要修改core_pattern 为如下:
#sysctl -w 'kernel.core_pattern=%t-%e-%p-%c.core'
在Fedora上,kernel.core_pattern=core,即默认可在程序目录下看到core.*文件。
关于core dump的过滤,systemd并没有提供更多的途径,依然是通过内核存储掩码来配置(coredump_filter),具体请查看上文的/sysctl/kernel.txt

本节参考链接


日志的记录,分类,过滤,分发和监控

Systemd Journal 特点

systemd 自带日志服务 journald,该日志服务的设计初衷是克服现有的 syslog 服务的缺点。比如:
  • syslog 不安全,消息的内容无法验证。每一个本地进程都可以声称自己是 Apache PID 4711,而 syslog 也就相信并保存到磁盘上。
  • 数据没有严格的格式,非常随意。自动化的日志分析器需要分析人类语言字符串来识别消息。一方面此类分析困难低效;此外日志格式的变化会导致分析代码需要更新甚至重写。
Systemd Journal 用二进制格式保存所有日志信息,用户使用 journalctl 命令来查看日志信息。无需自己编写复杂脆弱的字符串分析处理程序。
Systemd Journal 的优点如下:
  • 简单性:代码少,依赖少,抽象开销最小。
  • 零维护:日志是除错和监控系统的核心功能,因此它自己不能再产生问题。举例说,自动管理磁盘空间,避免由于日志的不断产生而将磁盘空间耗尽。
  • 移植性:日志 文件应该在所有类型的 Linux 系统上可用,无论它使用的何种 CPU 或者字节序。
  • 性能:添加和浏览 日志 非常快。
  • 最小资源占用:日志 数据文件需要较小。
  • 统一化:各种不同的日志存储技术应该统一起来,将所有的可记录事件保存在同一个数据存储中。所以日志内容的全局上下文都会被保存并且可供日后查询。例如一条固件记录后通常会跟随一条内核记录,最终还会有一条用户态记录。重要的是当保存到硬盘上时这三者之间的关系不会丢失。Syslog 将不同的信息保存到不同的文件中,分析的时候很难确定哪些条目是相关的。
  • 扩展性:日志的适用范围很广,从嵌入式设备到超级计算机集群都可以满足需求。
  • 安全性:日志 文件是可以验证的,让无法检测的修改不再可能。

日志记录

Messages coming in via /dev/log, via the native protocol, via STDOUT/STDERR of all services and via the kernel are received in the journal daemon.
Journal通过以下三种方式收集日志:
  • 程序使用libc库 syslog()输出的日志
  • 内核使用printk()函数打印的日志
  • 任何服务进程输出到STDOUT/STDERR的所有内容
syslog()
用法例1(C):
#include <syslog.h>

void syslog(int priority, const char *message, ... /* argument */);
#include <syslog.h>

int main(int argc, char *argv[]) {
        syslog(LOG_NOTICE, "Hello World");
        return 0;
}
用法例2(Python): - syslog
#!/usr/bin/evn python

import syslog
syslog.syslog('Hello World')
编写简单的service单元文件调用log.py。
或用journalctl命令查看。
查看生成的日记格式(JSON)(journalctl -o json-pretty -f):
{
        "__CURSOR" : "s=0e4330b5048f4ee09c213caf4c99f294;i=d1f;b=49cbe7a3d....",
        "__REALTIME_TIMESTAMP" : "1406216993978340",
        "__MONOTONIC_TIMESTAMP" : "24171673254",
        "_BOOT_ID" : "49cbe7a3d9f646beb526340832a145e6",
        "PRIORITY" : "6",
        "_UID" : "0",
        "_GID" : "0",
        "_SYSTEMD_SLICE" : "system.slice",
        "_MACHINE_ID" : "0de206a66f9cf937239d126753c62e2f",
        "_HOSTNAME" : "debian",
        "_CAP_EFFECTIVE" : "1fffffffff",
        "_TRANSPORT" : "syslog",
        "MESSAGE" : "Hello World",
        "SYSLOG_FACILITY" : "1",
        "SYSLOG_IDENTIFIER" : "log.py",
        "_PID" : "7643",
        "_COMM" : "python",
        "_EXE" : "/usr/bin/python2.7",
        "_CMDLINE" : "python /root/log.py",
        "_SYSTEMD_CGROUP" : "/system.slice/python-syslog.service",
        "_SYSTEMD_UNIT" : "python-syslog.service",
        "_SOURCE_REALTIME_TIMESTAMP" : "1406216993976092"
}
prntf()
如上文提到的,journal可以捕获服务进程往STDOUT/STDERR输出的所有内容,即例如C语言中的print函数打印的内容,Python中print打印的内容,以及Shell脚本中echo打印的内容等等都可以被journal捕获到,加入服务进程日志。
例:
#include <stdio.h>

int main(int argc, char *argv[]) {
        printf("Hello World\n");
        return 0;
}
如一般的printf打印内容,日志等级为LOG_INFO(可以通过service单元文件中的SyslogLevel=更改默认日志等级)。如何指定日志等级,可在每行打印内容前加上"",N为需要指定的日志等级。如下:
#include<stdio.h>

#define PREFIX_NOTICE "<5>"

int main(void){

  printf(PREFIX_NOTICE "Hello World\n");
  fprintf(stderr, "<3>Hello  Error\n");

  return 0;

}
其他例子:
#!/usr/bin/env python
print '<5>Hello World'
#!/bin/bash
echo "<5>Hello World"
Native Messages
systemd提供了原生的C语言库(systemd/sd-journal.h),用于向系统journal提交日志。 详情请查看systemd/sd-journal.h
例(未实验,例子与实验结果摘录自Lennart的blog):
#include <systemd/sd-journal.h>

int main(int argc, char *argv[]) {
        sd_journal_print(LOG_NOTICE, "Hello World");
        return 0;
}
相比上文使用print()或者syslog()提交的日志,使用sd_journal_print可以直观的指定LOG等级、日志内容等等,另外输出的日志会包含执行代码的位置信息,例如执行到的函数,代码文件位置,代码具体行数,方便开发人员调试。
除了可以包含代码信息,也可以向提交的日志中加入自定义段包含更多自定义信息,配合journalctl工具,可以更方便的过滤日志。
例:
#include <systemd/sd-journal.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
        sd_journal_send("MESSAGE=Hello World!",
                        "MESSAGE_ID=52fb62f99e2c49d89cfbf9d6de5e3555",
                        "PRIORITY=5",
                        "HOME=%s", getenv("HOME"),
                        "TERM=%s", getenv("TERM"),
                        "PAGE_SIZE=%li", sysconf(_SC_PAGESIZE),
                        "N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN),
                        NULL);
        return 0;
}
使用journalctl,通过指定自定义条目,快速过滤日志。
extension for systemd
除了systemd提供的一套C语言库,目前其他个别语言也衍生了systemd的相关扩展,其中就包含了和journald相关的扩展。
from systemd import journal
journal.send('Hello world')
journal.send('Hello, again, world', FIELD2='Greetings!', FIELD3='Guten tag')
var journald = require('journald').Log;
journald.log({
MESSAGE: 'Hello world',
ARG1: '',
ARG2: '',
ARG3: ''
});
<?php
sd_journal_send('MESSAGE=Hello world.');
sd_journal_send('MESSAGE=Hello, again, world.', 'FIELD2=Greetings!', 'FIELD3=Guten tag.');
sd_journal_send('ARBITRARY=anything', 'FIELD3=Greetings!');
local fmt = require('string').format
local Journal = require('systemd-journal')
j = Journal:new()
j:print(1, "Hello World From Print")
j:send({PRIORITY=1, MESSAGE="Hello World From Send", VERSION=process.version})
本小节参考链接

日志存储

systemd中日志不再以文本格式保存, Journal用二进制格式保存所有日志信息,用户使用 journalctl 命令来查看日志信息。
默认情况下(当 Storage= 在文件 /etc/systemd/journald.conf 中被设置为 auto(注:debian中默认该选项进行了注释,默认为auto,但是需要手动创建存储目录)),日志记录将被写入 /var/log/journal/。该目录是 systemd 软件包的一部分。若被删除,systemd 不会自动创建它,直到下次升级软件包时重建该目录。如果该目录缺失,systemd 会将日志记录写入 /run/systemd/journal。这意味着,系统重启后日志将丢失。
手动创建日志储存目录:
# mkdir -p /var/log/journal
重启之后,可在/var/log/journal/$MACHINE_ID/目录发现存储的日志文件,\$MACHINE_ID为系统本机的ID,详细介绍请参考Machine ID一节。
注:journal会自动管理磁盘空间,避免由于日志的不断产生而将磁盘空间耗尽,默认日志存储限制为所在文件系统容量的10%。 可在journald.conf文件中通过配置SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=, RuntimeMaxUse=, RuntimeKeepFree=, RuntimeMaxFileSize= 等选项来设置日志占的占的磁盘空间比率、systemd-journal必须给系统空出的文件系统容量等等,具体请参考本节参考链接4。

日志过滤和分发

过滤
systemd journal的工具journalctl提供了强大的日志过滤功能,可以按日志等级、进程ID、UID、GID、单元、执行路径、设备路径、时间区间、主机名、程序名等过滤,设置将这些选项进行配合使用过滤日志,详情请参考本节参考链接3与journalctl 用户手册。
使用示例:
显示本次启动后的所有日志:
# journalctl -b
显示本地启动后,且等级为err的日志:
#journalctl -b -p err
动态跟踪最新日志:
# journalctl -f
显示特地时间区间内的日志:
#journalctl --since=yesterday
#journalctl --since=2012-10-15 --until="2011-10-16 23:59:59"
显示某磁盘设备相关的日志:
#journalctl /dev/sda
显示特定程序的所有消息:
# journalctl /usr/lib/systemd/systemd
显示特定进程的所有消息:
# journalctl _PID=1
显示指定单元的消息(+时间区间):
# journalctl -u httpd
# journalctl -u httpd --since=00:00 --until=9:30
显示与指定用户相关的日志:
# journalctl _UID=70
# journalctl _UID=70 _UID=71
http://0pointer.de/blog/projects/journalctl.html #And now, it becomes magic!
分发
systemd journal本身未提供日志分发功能。
  • 常见的解决方案,做好单机日志的存储配置之后,通过rsync、btsync等工具收集同步日志至某中心节点进行分析处理。
  • 另外在systemd-193添加了systemd-journal-gatewayd.service,服务器开启此服务之后,将监听本地19531端口,其他机器可通过HTTP或JSON协议访问服务器得到后者日志,详细介绍(登录验证等)请查看systemd-journal-gatewayd.service
  • 注:systemd-212引入了 systemd-journal-remote >systemd-journal-remote is a command to receive serialized journal events and store them to the journal.
日志监控和报警
journal没有监控和报警方面的特性与功能,可以通过日志工具的过滤再配合脚本或程序做监控与报警。
本节参考链接
  1. 浅析 Linux 初始化 init 系统,第 3 部分: Systemd#日志服务
  2. Log and Service Status
  3. Using the Journal
  4. journald.conf — Journal service configuration file
  5. Writing syslog Daemons Which Cooperate Nicely With systemd
  6. systemd Journal译文

Journal与Rsyslog(syslog)

systemd 提供了 socket /run/systemd/journal/syslog,以兼容传统日志服务。所有系统信息都会被传入。要使传统日志服务工作,需要让syslog daemon绑定该 socket, 而非 /dev/log,见systemd v38 released
debian testing下默认安装rsyslog用于代替syslog,
/etc/systemd/system/syslog.service:注意ExecStart 选项
[Unit]
Description=System Logging Service
Requires=syslog.socket

[Service]
Type=notify
ExecStart=/usr/sbin/rsyslogd -n
StandardOutput=null

[Install]
WantedBy=multi-user.target
Alias=syslog.service
/lib/systemd/system/rsyslog.service:注意Alias选项
[Unit]
Description=System Logging Service
Requires=syslog.socket

[Service]
Type=notify
ExecStart=/usr/sbin/rsyslogd -n
StandardOutput=null

[Install]
WantedBy=multi-user.target
Alias=syslog.service
而默认情况下,journald.conf中ForwardToSyslog=默认开启,journal的日志会转发给syslog daemon,通过套接字(/run/systemd/journal/syslog)。
/lib/systemd/system/syslog.socket
[Unit]
Description=Syslog Socket
Documentation=man:systemd.special(7)
Documentation=http://www.freedesktop.org/wiki/Software/systemd/syslog
DefaultDependencies=no
Before=sockets.target syslog.target shutdown.target

# Don't allow logging until the very end
Conflicts=shutdown.target

# Pull in syslog.target to tell people that /dev/log is now accessible
Wants=syslog.target

[Socket]
ListenDatagram=/run/systemd/journal/syslog
SocketMode=0666
PassCredentials=yes
PassSecurity=yes
ReceiveBuffer=8M
注:关于进程向journal提交日志的接口请参考 详细介绍--日志管理--日志的记录..一节,另外可关注服务单元的标准输出StandardOutput=,可选择将 进程标准输出连接到inherit, null, tty, syslog, kmsg, journal, syslog+console, kmsg+console, journal+console 还是 socket,默认为journal,即systemd的日志系统。
Messages coming in via /dev/log, via the native protocol, via STDOUT/STDERR of all services and via the kernel are received in the journal daemon.

Audit record generation for started services

介绍

We hooked systemd up to the Linux auditing subsystem: as first init system at all systemd now generates auditing records for all services it starts/stops, including their failure status.
systemd下服务单元的启动、停止以及进入失败状态都会生成Audit记录,日志记录在/var/log/audit/audit.log文件中。

实例

安装auditd:
# aptitude install -y auditd
默认的auditd.conf与auditd.rulues下(实验环境debian stable:sysv,debian testing:systemd,两者默认auditd.conf差别只是num_logs项,不影响;两者默认auditd.rulues相同),设置auditd开启自动启动,我们看看开启自动启动的那些服务信息是否会被记录到audit.log中。
(sysv与systemd下的系统中,都安装了nginx服务,并随机启动)
可发现systemd下的auditd.log中记录了systemd下service单元启动的信息,sysv下见不到sysv管理的服务的启动信息。
我们手动restart或停止systemd下的nginx.service,见图 systemd-auditd-nginx-log,同样发现有相关信息,从左侧的Type,可区分哪些记录是启动,哪些记录是停止。

本节参考链接

Systemd-analyze

systemd-analyze 是一个内置的启动性能分析工具,它除了可以分析和直观的展现系统启动时候每项服务的耗时,也可以图表的方式直观得绘出启动阶段各服务的依赖关系,方便系统管理员优化系统启动,理解系统服务依赖关系等,具体用法请参考用户手册,非常简单。
注:systemd-209,systemd-analyze增加远程控制功能(over SSH)。

本节参考链接

-----------------------------

systemd实战

systemctl对服务的操作

设置开机启动

对于那些支持Systemd的软件,安装的时候,会自动在/usr/lib/systemd/system目录添加一个配置文件(一般为.service后缀)。
配置文件的后缀名,就是该Unit的种类,比如sshd.socket。如果省略,Systemd默认后缀名为.service,所以nginx会被理解成nginx.service
如果你想让该软件开机启动,就执行下面的命令(以nginx.service为例):
sudo systemctl enable nginx
Bash
上面的命令相当于在/etc/systemd/system/目录添加一个符号链接nginx.service,指向/usr/lib/systemd/system/nginx.service
这是因为开机时,Systemd只执行/etc/systemd/system/目录里面的配置文件。这也意味着,如果把修改后的配置文件放在该目录,就可以达到覆盖原始配置的效果。
但要注意,符号链接可能不是直接在/etc/systemd/system/目录下,而是在该目录下的multi-user.target.wants/目录中。

启动服务

设置开机启动以后,软件并不会立即启动,必须等到下一次开机。如果想现在就运行该软件,那么要执行以下命令:
sudo systemctl start nginx
Bash
为了查看nginx服务是否真的启动了,可以查看一下它的状态:
sudo systemctl status nginx
Bash
输出结果:
● nginx.service - nginx - high performance web server
   Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled)
   Active: active (running) since 二 2019-03-26 03:06:39 CST; 1 weeks 1 days ago
     Docs: http://nginx.org/en/docs/
 Main PID: 4084 (nginx)
    Tasks: 3
   Memory: 34.0M
   CGroup: /system.slice/nginx.service
           ├─ 4084 nginx: master process /usr/local/openresty/nginx/sbin/nginx -c /usr/lo...
           ├─25383 nginx: worker process
           └─25384 nginx: cache manager process

Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.
  • Loaded行:配置文件的位置,表示是否设为开机启动,enabled表示已经设置为开机自启动。
  • Active行:表示是否处于激活状态(即是否正在运行)
  • Docs行:该软件的文档,有可能是一个网址,有可能是man(意思是让你用man命令查看)
  • Main PID行:主进程PID
  • Tasks行:表示一共有几个子进程
  • Memory行:表示该服务占用的内存大小
  • CGroup块:应用的所有子进程
  • 日志块:应用的日志

停止服务

终止正在运行的服务,需要执行systemctl stop命令:
sudo systemctl stop nginx
Bash
有时候,该命令可能没有响应,服务停不下来。这时候就不得不”杀进程”了,向正在运行的进程发出kill信号:
sudo systemctl kill nginx
Bash
当然,我们自己用ps -ef | grep nginx找到pid,然后自己用kill -9 pid也是可以的

重启服务

sudo systemctl restart nginx
Bash

读懂配置文件

一个服务怎么启动,完全由它的配置文件决定。下面就来看,配置文件有些什么内容。
前面说过,配置文件主要放在/usr/lib/systemd/system目录,也可能在/etc/systemd/system目录。找到配置文件以后,使用文本编辑器打开即可。
systemctl cat命令可以用来查看配置文件,下面以sshd.service文件为例,它的作用是启动一个SSH服务器,供其他用户以SSH方式登录。
 xiebruce@centos-linux1 > ~ > systemctl cat sshd.service
# /usr/lib/systemd/system/sshd.service
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service
Wants=sshd-keygen.service

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target
Bash
可以看到,配置文件分成几个区块,每个区块包含若干条键值对。下面依次解释每个区块的内容。

[Unit]区块:启动顺序与依赖关系

  • Description:当前服务的简单描述
  • Documentation:文档位置,有可能是一个网址,有可能是man,意思是让你用man命令去查。
  • After:表示如果network.target或sshd-keygen.service需要启动,那么sshd.service应该在它们之后启动。
    相应地,还有一个Before字段,定义sshd.service应该在哪些服务之前启动。
    注意:After和Before字段只涉及启动顺序,不涉及依赖关系。
    举例来说,某Web应用需要mysql数据库储存数据。在配置文件中,它只定义要在 mysql之后启动,而没有定义依赖mysql。上线后,由于某种原因,mysql需要重新启动,在停止服务期间,该Web应用就会无法建立数据库连接。
设置依赖关系,需要使用Wants字段和Requires字段。
– Wants:表示sshd.service与sshd-keygen.service之间存在”弱依赖”关系,即如果”sshd-keygen.service”启动失败或停止运行,不影响sshd.service继续执行。
Requires字段则表示”强依赖”关系(由于sshd没有强依赖,所以在这里并没有写Requies字段),即如果该服务启动失败或异常退出,那么sshd.service也必须退出。
注意,Wants字段与Requires字段只涉及依赖关系,与启动顺序无关,默认情况下是同时启动的。

[Service] 区块:启动行为

Service区块定义如何启动当前服务。

启动命令

许多软件都有自己的环境参数文件,该文件可以用EnvironmentFile字段读取。
EnvironmentFile字段:指定当前服务的环境参数文件。该文件内部的key=value键值对,可以用$key的形式,在当前配置文件中获取。
上面的例子中,sshd的环境参数文件是/etc/sysconfig/sshd
ExecStart:定义启动进程时执行的命令,配置文件里面最重要的字段是ExecStart。
上面的例子中,启动sshd,执行的命令是/usr/sbin/sshd -D $OPTIONS,其中的变量$OPTIONS就来自EnvironmentFile字段指定的环境参数文件。
与之作用相似的,还有如下这些字段。
  • ExecReload字段:重启服务时执行的命令
  • ExecStop字段:停止服务时执行的命令
  • ExecStartPre字段:启动服务之前执行的命令
  • ExecStartPost字段:启动服务之后执行的命令
  • ExecStopPost字段:停止服务之后执行的命令
请看下面的例子。
[Service]
ExecStart=/bin/echo execstart1
ExecStart=
ExecStart=/bin/echo execstart2
ExecStartPost=/bin/echo post1
ExecStartPost=/bin/echo post2
Bash
上面这个配置文件,第二行ExecStart设为空值,等于取消了第一行的设置,运行结果如下。
execstart2
post1
post2
所有的启动设置之前,都可以加上一个连词号(-),表示”抑制错误”,即发生错误的时候,不影响其他命令的执行。比如,EnvironmentFile=-/etc/sysconfig/sshd(注意等号后面的那个连词号),就表示即使/etc/sysconfig/sshd文件不存在,也不会抛出错误。

启动类型

Type字段定义启动类型。它可以设置的值如下:
  • simple(默认值):ExecStart字段启动的进程为主进程
  • forking:ExecStart字段将以fork()方式启动,此时父进程将会退出,子进程将成为主进程
  • oneshot:类似于simple,但只执行一次,Systemd会等它执行完,才启动其他服务
  • dbus:类似于simple,但会等待D-Bus信号后启动
  • notify:类似于simple,启动结束后会发出通知信号,然后Systemd再启动其他服务
  • idle:类似于simple,但是要等到其他任务都执行完,才会启动该服务。一种使用场合是为让该服务的输出,不与其他服务的输出相混合
下面是一个oneshot的例子,笔记本电脑启动时,要把触摸板关掉,配置文件可以这样写。
[Unit]
Description=Switch-off Touchpad

[Service]
Type=oneshot
ExecStart=/usr/bin/touchpad-off

[Install]
WantedBy=multi-user.target
Ini
上面的配置文件,启动类型设为oneshot,就表明这个服务只要运行一次就够了,不需要长期运行,但是你有没有发现,停止了之后,无法用systemctl开启呀,所以我们可以再改成以下这样:
[Unit]
Description=Switch-off Touchpad

[Service]
Type=oneshot
ExecStart=/usr/bin/touchpad-off start
ExecStop=/usr/bin/touchpad-off stop
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
Ini
可以看到,上面配置文件中添加了ExecStop和RemainAfterExit。
当执行start后,就关闭了触控板(有人可能会觉得奇怪,start不是开启触摸板么?怎么会关闭呢?但你要看清楚,被执行的命令是touchpad-off,start表示执行touchpad-off这个命令,当然就是关闭触控板)
RemainAfterExit字段的值可以为true或false,也可以用yes和no。
这个字段可能比较多人不懂,它设为yes的意思是“程序虽然退出了,但仍然让systemctl status命令查询到的状态为active”,为什么要这样呢?因为只有这样才可以执行stop操作,假如你本身就是stop状态,你是没法执行systemctl stop操作的(实际上你去执行它是不会报错的,但按原则来说,只有状态为active的服务才能stop)。
有人会觉得,既然执行之后就自己退出了,对程序来说实际上已经stop了,为什么还要stop呢?很明显,本例的stop并不是指“/usr/bin/touchpad-off”这个服务的stop,而是指“/usr/bin/touchpad-off start”的“反操作”而已。
RemainAfterExit这个配置主要是提供给一些并非常驻内存的程序使用。

重启行为

Service区块有一些字段,定义了重启行为。
KillMode字段:定义Systemd如何停止sshd服务。
上面这个例子中,将KillMode设为process,表示只停止主进程,不停止任何sshd 子进程,即子进程打开的 SSH session 仍然保持连接。这个设置不太常见,但对sshd很重要,否则你停止服务的时候,会连自己打开的SSH session一起杀掉(这样你就再也无法远程连接去启动它了,所以不能这么做,sshd这点比较特殊)。
KillMode字段可以设置的值如下:
  • control-group(默认值):当前控制组里面的所有子进程,都会被杀掉
  • process:只杀主进程
  • mixed:主进程将收到SIGTERM信号,子进程收到SIGKILL信号
  • none:没有进程会被杀掉,只是执行服务的stop命令。
接下来是Restart字段。
Restart字段:定义了sshd退出后,Systemd的重启方式。
上面的例子中,Restart设为on-failure,表示任何意外的失败,就将重启sshd。如果sshd正常停止(比如执行systemctl stop命令),它就不会重启。
Restart字段可以设置的值如下。
  • no(默认值):退出后不会重启
  • on-success:只有正常退出时(退出状态码为0),才会重启
  • on-failure:非正常退出时(退出状态码非0),包括被信号终止和超时,才会重启
  • on-abnormal:只有被信号终止和超时,才会重启
  • on-abort:只有在收到没有捕捉到的信号终止时,才会重启
  • on-watchdog:超时退出,才会重启
  • always:不管是什么退出原因,总是重启
对于守护进程,推荐设为on-failure。对于那些允许发生错误退出的服务,可以设为on-abnormal
RestartSec字段:表示Systemd重启服务之前,需要等待的秒数。上面的例子设为等待42秒。

[Install]区块

Install区块,定义如何安装这个配置文件,即怎样做到开机启动。
WantedBy字段:表示该服务所在的Target。
Target的含义是服务组,表示一组服务。WantedBy=multi-user.target指的是,sshd所在的Target是multi-user.target。
这个设置非常重要,因为执行systemctl enable sshd.service命令时,sshd.service的一个符号链接,就会放在/etc/systemd/system目录下面的multi-user.target.wants子目录之中。
Systemd有默认的启动Target,使用以下命令查看:
 xiebruce@centos-linux1 > ~ > systemctl get-default
multi-user.target
Bash
一般来说,常用的Target有两个:一个是multi-user.target,表示多用户命令行状态;另一个是graphical.target,表示图形用户状态,它依赖于multi-user.target。官方文档有一张非常清晰的Target依赖关系图

Target的配置文件

 xiebruce@centos-linux1 > ~ > systemctl cat multi-user.target
# /lib/systemd/system/multi-user.target
#  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.

[Unit]
Description=Multi-User System
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.service rescue.target
After=basic.target rescue.service rescue.target
AllowIsolate=yes
Bash
注意,Target 配置文件里面没有启动命令。
上面输出结果中,主要字段含义如下。
  • Requires字段:要求basic.target一起运行。
  • Conflicts字段:冲突字段。如果rescue.service或rescue.target正在运行,multi-user.target就不能运行,反之亦然。
  • After:表示multi-user.target在basic.target 、 rescue.service、 rescue.target之后启动,如果它们有启动的话。
  • AllowIsolate:允许使用systemctl isolate命令切换到multi-user.target。

修改配置文件后重启

修改配置文件以后,需要重新加载配置文件,然后重新启动相关服务。
# 重新加载配置文件
sudo systemctl daemon-reload

# 重启相关服务
sudo systemctl restart foobar