如何使用Journalctl查看并操作Systemd日志

内容简介

作为最具吸引力的优势,systemd拥有强大的处理与系统日志记录功能。在使用其它工具时,日志往往被分散在整套系统当中,由不同的守护进程及进程负责处理,这意味着我们很难跨越多种应用程序对其内容进行解读。

相比之下,systemd尝试提供一套集中化管理方案,从而统一打理全部内核及用户级进程的日志信息。这套系统能够收集并管理日志内容,而这也就是我们所熟知的journal。

Journal的实现归功于journald守护进程,其负责处理由内核、initrd以及服务等产生的信息。在今天的教程中,我们将探讨如何使用journalctl工具,并在其帮助下访问并操作journal内部的数据。

总体思路

Systemd journal的深层驱动力在于以集中方式管理对来自任意来源的日志信息。由于大部分引导进程都是由systemd进程处理的,因此我们有理由以标准化方式实现日志的收集与访问。其中jornald守护进程会收集全部来源的数据并将其以二进制格式加以存储,从而轻松实现动态操作。

这种作法能够实现多种收益。通过单一工具与数据交互,管理员能够以动态方式显示日志数据。另外,我们也可以轻松查看历史引导数据,或者将日志条目同其它相关服务加以结合,从而 完成通信问题调试。

将日志数据以二进制形式存储还意味着这些数据可根据需求随时以二进制输出格式显示。例如,大家可以通过标准syslog格式查看日志以实现日常管理,并在需要使用图形服务时将各条目作为JSON对象交由图形化服务处理。由于数据不会以纯文本形式被写入磁盘,因此我们无需进行任何格式转换。

大家可以将systemd journal与现有syslog方案配合使用,也可利用其替代现有syslog功能,具体取决于实际需求。尽管systemd journal足以涵盖大部分管理工作需求,但其同时也能够补充现有日志记录机制。例如,大家可以建立一套集中式syslog服务器,从而对来自多台服务器的数据进行编译;或者,我们也能够利用systemd journal将来自多项服务的日志汇总在单一系统当中。

设置系统时间

使用二进制journal的一大好处在于,它能够以UTC或者本地时间显示日志记录。在默认情况下,systemd会以本地时间显示结果。

有鉴于此,在我们开始使用journal之前,首先要确保时区得到正确设置。Systemd套件中还提供一款timedatectl工具,专门用于解决此类问题。
首先,利用list-timezones选项查看可用时区:

timedatectl list-timezones

结果将列出系统上可用的全部时区。而后选择与服务器所在地相匹配的项目,并使用set-timezone选项加以设置:

sudo timedatectl set-timezone zone

为了确保我们的设备使用正确的时间,可单独使用timedatectl命令或者添加status选项。显示结果如下:

timedatectl status

Local time: Thu 2015-02-05 14:08:06 EST
Universal time: Thu 2015-02-05 19:08:06 UTC
    RTC time: Thu 2015-02-05 19:08:06
   Time zone: America/New_York (EST, -0500)
 NTP enabled: no
NTP synchronized: no
RTC in local TZ: no
  DST active: n/a

第一行所示应为正确时间。

基础日志查看

要查看journald守护进程收集到的日志,可使用journalctl命令。

在单独使用时,系统中的每个journal条目都会被显示在单一pager中供我们浏览。条目时间越早,排列越靠前:

journalctl

-- Logs begin at Tue 2015-02-03 21:48:52 UTC, end at Tue 2015-02-03 22:29:38 UTC. --
Feb 03 21:48:52 localhost.localdomain systemd-journal[243]: Runtime journal is using 6.2M (max allowed 49.
Feb 03 21:48:52 localhost.localdomain systemd-journal[243]: Runtime journal is using 6.2M (max allowed 49.
Feb 03 21:48:52 localhost.localdomain systemd-journald[139]: Received SIGTERM from PID 1 (systemd).
Feb 03 21:48:52 localhost.localdomain kernel: audit: type=1404 audit(1423000132.274:2): enforcing=1 old_en
Feb 03 21:48:52 localhost.localdomain kernel: SELinux: 2048 avtab hash slots, 104131 rules.
Feb 03 21:48:52 localhost.localdomain kernel: SELinux: 2048 avtab hash slots, 104131 rules.
Feb 03 21:48:52 localhost.localdomain kernel: input: ImExPS/2 Generic Explorer Mouse as /devices/platform/
Feb 03 21:48:52 localhost.localdomain kernel: SELinux:  8 users, 102 roles, 4976 types, 294 bools, 1 sens,
Feb 03 21:48:52 localhost.localdomain kernel: SELinux:  83 classes, 104131 rules

. . .

大家可以一页页进行翻看,不过如果系统运行时间较长,那么systemd中的日志也将成千上万,这也证明了journal数据库中可观的数据量。

其格式与标准的syslog日志非常相似。然而,其收集数据的来源较syslog要丰富得多。其中包含有来自先前引导进程、内核、initrd以及应用程序标准错误与输出的日志。这一切都可在journal中查看到。

大家可能还注意到,全部时间戳都以本地时间为准。由于已经为系统正确设置了本地时间,所以显示的时间戳也都准确无误。

如果大家希望以UTC显示时间戳,则可使用–utc标记:

journalctl --utc

按时间进行journal过滤

浏览大量数据当然有其作用,但信息量过于庞大则会让我们很难甚至根本不可能找到真正重要的内容。因此,journalctl提供了极为关键的过滤选项。

  • 显示当前引导进程下的日志

其中最常用的就是-b标记了,其将显示全部最近一次重新引导后收集到的journal条目。

journalctl -b

通过这种方式,我们能够识别并管理源自当前环境下的信息。
如果不使用这项功能,而且显示的引导数量超过一天,那么journalctl会在在系统关闭处插入说明:

. . .

-- Reboot --

. . .

这种方式能够帮助我们有效区分来自不同引导会话的信息。

  • 过往引导记录

大家通常只需要查看当前引导环境下的信息,但有时候查看过往引导记录也非常必要。Journal能够保存大量过往引导信息,从而允许journalctl轻松显示相关内容。

有些版本会在默认情况下保存过往引导信息,而有些则默认禁用这项功能。要启用此功能,可以使用以下功能以创建用于存储journal信息的目录:

sudo mkdir -p /var/log/journal

或者直接编辑journal配置文件:

sudo nano /etc/systemd/journald.conf

在[Journal]区段下将Storage=选项设定为“persistent”以启用持久记录:

/etc/systemd/journald.conf

. . .
[Journal]
Storage=persistent

当启用保存过往引导信息功能后,journalctl会提供额外命令以帮助大家将各引导记录作为独立单元操作。要查看Journald中已经记录的引导信息,可使用–list-boots选项:

journalctl --list-boots

-2 caf0524a1d394ce0bdbcff75b94444fe Tue 2015-02-03 21:48:52 UTC—Tue 2015-02-03 22:17:00 UTC
-1 13883d180dc0420db0abcb5fa26d6198 Tue 2015-02-03 22:17:03 UTC—Tue 2015-02-03 22:19:08 UTC
 0 bed718b17a73415fade0e4e7f4bea609 Tue 2015-02-03 22:19:12 UTC—Tue 2015-02-03 23:01:01 UTC

这里每次引导都将显示为一行。第一列可用于在journalctl中引用该次引导。如果大家需要更为准确的引用方式,则可在第二列中找到引导ID。末尾记录的两次时间为当次引导的开始与结束时间。

要显示这些引导中的具体信息,则可使用第一或者第二列提供的信息。

例如,要查看上次引导的journal记录,则可使用-1相对指针配合-b标记:

journalctl -b -1

另外,也可以使用引导ID:

journalctl -b caf0524a1d394ce0bdbcff75b94444fe
  • 时间窗

按照引导环境查看日志条目当然非常重要,但我们往往还需要使用与系统引导无关的时间窗作为浏览基准。这种情况在长期运行的服务器当中较为常见。

大家可以利用–since与–until选项设定时间段,二者分别负责说明给定时间之前与之后的记录。

时间值可以多种格式输出。对于绝对时间值,大家可以使用以下格式:

YYYY-MM-DD HH:MM:SS

例如,我们可以通过以下命令查看全部2015年1月10日下午5:15之后的条目:

journalctl --since "2015-01-10 17:15:00"

如果以上格式中的某些组成部分未进行填写,系统会直接进行默认填充。例如,如果日期部分未填写,则会直接显示当前日期。如果时间部分未填写,则缺省使用“00:00:00”(午夜)。第二字段亦可留空,默认值为“00”:

journalctl --since "2015-01-10" --until "2015-01-11 03:00"

另外,journal还能够理解部分相对值及命名简写。例如,大家可以使用“yesterday”、“today”、“tomorrow”或者“now”等表达。另外,我们也可以使用“-”或者“+”设定相对值,或者使用“ago”之前的表达。

获取昨天数据的命令如下:

journalctl –since yesterday

要获得早9:00到一小时前这段时间内的报告,可使用以下命令:

journalctl --since 09:00 --until "1 hour ago"

如大家所见,时间窗的过滤机制非常灵活且易用。

  • 按信息类型过滤

现在我们要探讨如何利用感兴趣的服务或者组件类型实现过滤。Systemd journal同样提供多种方式供大家选择。

  • 按单元

最常用的此类过滤方式当数按单元过滤了。我们可以使用-u选项实现这一效果。

例如,要查看系统上全部来自Nginx单元的日志,可使用以下命令:

journalctl -u nginx.service

一般来讲,我们可能需要同时按单元与时间进行信息过滤。例如,检查今天某项服务的运行状态:

journalctl -u nginx.service --since today

我们还可以充分发挥journal查看多种单元信息的优势。例如,如果我们的Nginx进程接入某个PHP-FPM单元以处理动态内容,则可将这两个单元合并并获取按时间排序的查询结果:

journalctl -u nginx.service -u php-fpm.service --since today

这种能力对于不同程序间交互及系统调试显然非常重要。

  • 按进程、用户或者群组ID

由于某些服务当中包含多个子进程,因此如果我们希望通过进程ID实现查询,也可以使用相关过滤机制。

这里需要指定_PID字段。例如,如果PID为8088,则可输入:

journalctl _PID=8088

有时候我们可能希望显示全部来自特定用户或者群组的日志条目,这就需要使用_UID或者_GID。例如,如果大家的Web服务器运行在www-data用户下,则可这样找到该用户ID:

id -u www-data

33

接下来,我们可以使用该ID返回过滤后的journal结果:

journalctl _UID=33 --since today

Systemd journal拥有多种可实现过滤功能的字段。其中一些来自被记录的进程,有些则由journald用于自系统中收集特定时间段内的日志。

之前提到的_PID属于后一种。Journal会自动记录并检索进程PID,以备日后过滤之用。大家可以查看当前全部可用journal字段:

man systemd.journal-fields

下面来看针对这些字段的过滤机制。-F选项可用于显示特定journal字段内的全部可用值。

例如,要查看systemd journal拥有条目的群组ID,可使用以下命令:

journalctl -F _GID

32
99
102
133
81
84
100
0
124
87

其将显示全部journal已经存储至群组ID字段内的值,并可用于未来的过滤需求。

  • 按组件路径

我们也可以提供路径位置以实现过滤。

如果该路径指向某个可执行文件,则journalctl会显示与该可执行文件相关的全部条目。例如,要找到与bash可执行文件相关的条目:

journalctl /usr/bin/bash

一般来讲,如果某个单元可用于该可执行文件,那么此方法会更为明确且能够提供更好的相关信息(与子进程相关的条目等)。但有时候,这种作法则无法奏效。

  • 显示内核信息

内核信息通常存在于dmesg输出结果中,journal同样可对其进行检索。要只显示此类信息,可添加-k或者–dmesg标记:

journalctl -k

默认情况下,其会显示当前引导环境下的全部内核信息。大家也可以使用常规的引导选择标记对此前的引导记录进行查询。例如,要查询五次之前引导环境的信息:

journalctl -k -b -5
  • 按优先级

管理员们可能感兴趣的另一种过滤机制为信息优先级。尽管以更为详尽的方式查看日志也很有必要,不过在理解现有信息时,低优先级日志往往会分散我们的注意力并导致理解混乱。

大家可以使用journalctl配合-p选项显示特定优先级的信息,从而过滤掉优先级较低的信息。

例如,只显示错误级别或者更高的日志条目:

journalctl -p err -b

这将只显示被标记为错误、严重、警告或者紧急级别的信息。Journal的这种实现方式与标准syslog信息在级别上是一致的。大家可以使用优先级名称或者其相关量化值。以下各数字为由最高到最低优先级:

0: emerg
1: alert
2: crit
3: err
4: warning
5: notice
6: info
7: debug

以上为可在-p选项中使用的数字或者名称。选定某一优先级会显示等级与之等同以及更高的信息。

修改journal显示内容

到这里,过滤部分已经介绍完毕。我们也可以使用多种方式对输出结果进行修改,从而调整journalctl的显示内容。

  • 截断或者扩大输出结果

我们可以缩小或者扩大输出结果,从而调整journalctl的显示方式。

在默认情况下,journalctl会在pager内显示各条目,并通过右箭头键访问其信息。

如果大家希望截断输出内容,向其中插入省略号以代表被移除的信息,则可使用–no-full选项:

journalctl --no-full

. . .

Feb 04 20:54:13 journalme sshd[937]: Failed password for root from 83.234.207.60...h2
Feb 04 20:54:13 journalme sshd[937]: Connection closed by 83.234.207.60 [preauth]
Feb 04 20:54:13 journalme sshd[937]: PAM 2 more authentication failures; logname...ot

大家也可以要求其显示全部信息,无论其是否包含不可输出的字符。具体方式为添加-a标记:

journalctl -a
  • 标准输出结果

默认情况下,journalctl会在pager内显示输出结果以便于查阅。如果大家希望利用文本操作工具对数据进行处理,则可能需要使用标准格式。在这种情况下,我们需要使用–no-pager选项:

journalclt --no-pager

这样相关结果即可根据需要被重新定向至磁盘上的文件或者处理工具当中。

  • 输出格式

如果大家需要对journal条目进行处理,则可能需要使用更易使用的格式以简化数据解析工作。幸运的是,journal能够以多种格式进行显示,只须添加-o选项加格式说明即可。

例如,我们可以将journal条目输出为JSON格式:

journalctl -b -u nginx -o json

{ "__CURSOR" : "s=13a21661cf4948289c63075db6c25c00;i=116f1;b=81b58db8fd9046ab9f847ddb82a2fa2d;m=19f0daa;t=50e33c33587ae;x=e307daadb4858635", "__REALTIME_TIMESTAMP" : "1422990364739502", "__MONOTONIC_TIMESTAMP" : "27200938", "_BOOT_ID" : "81b58db8fd9046ab9f847ddb82a2fa2d", "PRIORITY" : "6", "_UID" : "0", "_GID" : "0", "_CAP_EFFECTIVE" : "3fffffffff", "_MACHINE_ID" : "752737531a9d1a9c1e3cb52a4ab967ee", "_HOSTNAME" : "desktop", "SYSLOG_FACILITY" : "3", "CODE_FILE" : "src/core/unit.c", "CODE_LINE" : "1402", "CODE_FUNCTION" : "unit_status_log_starting_stopping_reloading", "SYSLOG_IDENTIFIER" : "systemd", "MESSAGE_ID" : "7d4958e842da4a758f6c1cdc7b36dcc5", "_TRANSPORT" : "journal", "_PID" : "1", "_COMM" : "systemd", "_EXE" : "/usr/lib/systemd/systemd", "_CMDLINE" : "/usr/lib/systemd/systemd", "_SYSTEMD_CGROUP" : "/", "UNIT" : "nginx.service", "MESSAGE" : "Starting A high performance web server and a reverse proxy server...", "_SOURCE_REALTIME_TIMESTAMP" : "1422990364737973" }

. . .

这种方式对于工具解析非常重要。大家也可以使用json-pretty格式以更好地处理数据结构:

journalctl -b -u nginx -o json-pretty

{
"__CURSOR" : "s=13a21661cf4948289c63075db6c25c00;i=116f1;b=81b58db8fd9046ab9f847ddb82a2fa2d;m=19f0daa;t=50e33c33587ae;x=e307daadb4858635",
"__REALTIME_TIMESTAMP" : "1422990364739502",
"__MONOTONIC_TIMESTAMP" : "27200938",
"_BOOT_ID" : "81b58db8fd9046ab9f847ddb82a2fa2d",
"PRIORITY" : "6",
"_UID" : "0",
"_GID" : "0",
"_CAP_EFFECTIVE" : "3fffffffff",
"_MACHINE_ID" : "752737531a9d1a9c1e3cb52a4ab967ee",
"_HOSTNAME" : "desktop",
"SYSLOG_FACILITY" : "3",
"CODE_FILE" : "src/core/unit.c",
"CODE_LINE" : "1402",
"CODE_FUNCTION" : "unit_status_log_starting_stopping_reloading",
"SYSLOG_IDENTIFIER" : "systemd",
"MESSAGE_ID" : "7d4958e842da4a758f6c1cdc7b36dcc5",
"_TRANSPORT" : "journal",
"_PID" : "1",
"_COMM" : "systemd",
"_EXE" : "/usr/lib/systemd/systemd",
"_CMDLINE" : "/usr/lib/systemd/systemd",
"_SYSTEMD_CGROUP" : "/",
"UNIT" : "nginx.service",
"MESSAGE" : "Starting A high performance web server and a reverse proxy server...",
"_SOURCE_REALTIME_TIMESTAMP" : "1422990364737973"
}

. . .

以下为可用于显示的各类格式:

cat: 只显示信息字段本身。
export: 适合传输或备份的二进制格式。
json: 标准JSON,每行一个条目。
json-pretty: JSON格式,适合人类阅读习惯。
json-sse: JSON格式,经过打包以兼容server-sent事件。
short: 默认syslog类输出格式。
short-iso: 默认格式,强调显示ISO 8601挂钟时间戳。
short-monotonic: 默认格式,提供普通时间戳。
short-precise: 默认格式,提供微秒级精度。
verbose: 显示该条目的全部可用journal字段,包括通常被内部隐藏的字段。

这些选项允许大家以最适合需求的格式显示journal条目。

活动进程监控

Journalctl命令还能够帮助管理员以类似于tail的方式监控活动或近期进程。这项功能内置于journalctl当中,允许大家在无需借助其它工具的前提下实现访问。

  • 显示近期日志

要显示特定数量的记录,大家可以使用-n选项,具体方式为tail -n。

默认情况下,其会显示最近十条记录:

journalctl -n

大家可以在-n之后指定要查看的条目数量:

journalctl -n 20
  • 追踪日志

要主动追踪当前正在编写的日志,大家可以使用-f标记。方式同样为tail -f:

journalctl -f

Journal维护

存储这么多数据当然会带来巨大压力,因此我们还需要了解如何清理部分陈旧日志以释放存储空间。

  • 了解现有磁盘使用量

大家可以利用–disk-usage标记查看journal的当前磁盘使用量:

journalctl --disk-usage

Journals take up 8.0M on disk.
  • 删除旧有日志

如果大家打算对journal记录进行清理,则可使用两种不同方式(适用于systemd 218及更高版本)。

如果使用–vacuum-size选项,则可硬性指定日志的总体体积,意味着其会不断删除旧有记录直到所占容量符合要求:

sudo journalctl --vacuum-size=1G

另一种方式则是使用–vacuum-time选项。任何早于这一时间点的条目都将被删除。

例如,去年之后的条目才能保留:

sudo journalctl --vacuum-time=1years
  • 限定Journal扩展

大家可以配置自己的服务器以限定journal所能占用的最高容量。要实现这一点,我们需要编辑/etc/systemd/journald.conf文件。

以下条目可用于限定journal体积的膨胀速度:

SystemMaxUse=: 指定journal所能使用的最高持久存储容量。
SystemKeepFree=: 指定journal在添加新条目时需要保留的剩余空间。
SystemMaxFileSize=: 控制单一journal文件大小,符合要求方可被转为持久存储。
RuntimeMaxUse=: 指定易失性存储中的最大可用磁盘容量(/run文件系统之内)。
RuntimeKeepFree=: 指定向易失性存储内写入数据时为其它应用保留的空间量(/run文件系统之内)。
RuntimeMaxFileSize=: 指定单一journal文件可占用的最大易失性存储容量(/run文件系统之内)。

通过设置上述值,大家可以控制journald对服务器空间的消耗及保留方式。

总结

到这里,systemd journal对系统及应用数据的收集与管理机制就介绍完毕了。其出色的灵活性源自将广泛的元数据自动记录至集中化日志之内。另外,journalctl命令则显著简化了journal的使用方式,从而让更多管理员得以利用它完成面向不同应用组件的分析与相关调试工作。

使用 systemd 限制系统资源的使用

简单介绍

在基于 Linux-3.x 内核版本的很多发行版都提供了 Systemd 来管理系统和服务. 同时也将 cgroup 功能加到了 slice, scope 和 service 三个单元中, 详见 sec-Default_Cgroup_Hierarchies. 基于这些特性我们可以很方便的通过 systemd 来限制服务或者进程对系统资源的使用, 这在单主机多服务的场景下会很有用. 下面则以 MySQL 服务为例介绍如何使用 systemd 限制资源的使用, 其它服务的限制和此等同.

示例使用

以 Centos7 系统为例, 从 redhat-resource-control 的文档来看, 官方建议通过 service 来实现资源的限制, 所以这里我们增加可以带端口参数(如果单台主机有多个 MySQL 实例的话)启动的 mysql 服务:

# cat /usr/lib/systemd/system/mysql@.service                                   
[Unit]
Description=MySQL Server node%i

[Service]
Type=forking
Environment="PORT_ARGS=%I"
PermissionsStartOnly=true
ExecStart=/usr/local/mysqlnode/bin/node ${PORT_ARGS} start
ExecStop=/usr/local/mysqlnode/bin/node ${PORT_ARGS} stop

[Install]
WantedBy=multi-user.target

上述服务以 fork 方式启动服务, @ 符号之后的端口号即为相应的端口参数, 启动后查看对应服务状态:

# systemctl start mysql@3327 
# systemctl status mysql@3327
● mysql@3327.service - MySQL Server node3327
   Loaded: loaded (/usr/lib/systemd/system/mysql@.service; disabled; vendor preset: disabled)
   Active: active (running) since Fri 2018-12-07 21:31:32 CST; 4s ago
  Process: 58084 ExecStart=/usr/local/mysqlnode/bin/node ${PORT_ARGS} start (code=exited, status=0/SUCCESS)
 Main PID: 58159 (mysqld_safe)
   CGroup: /system.slice/system-mysql.slice/mysql@3327.service
           ├─58159 /bin/sh /opt/Percona-Server-5.6.38-rel83.0-Linux.x86_64.ssl101//bin/mysqld_safe --defaults-file=/export/mysql/node3327/my.node.cnf
           ├─59489 /opt/Percona-Server-5.6.38-rel83.0-Linux.x86_64.ssl101/bin/mysqld --defaults-file=/export/mysql/node3327/my.node.cnf --basedir=/opt/Percona-Server-5.6.38-rel83.0-Linux.x86_64.ssl101 --datadir=/export/mysql/node3327/...
           └─59490 logger -t mysqld-3327 -p daemon.error

通过 systemd 增加 cgroup 限制,可以一次设置单项属性值, 也可以一次设置多项:

# systemctl set-property mysql@3327.service MemoryLimit=5G       # 5G 内存
# systemctl set-property mysql@3327.service CPUQuota=150%        # 150% cpu 使用率
# systemctl set-property mysql@3327.service BlockIOWeight=1000   # IO 权重

# systemctl status mysql@3327
● mysql@3327.service - MySQL Server node3327
   Loaded: loaded (/usr/lib/systemd/system/mysql@.service; disabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/mysql@3327.service.d
           └─50-MemoryLimit.conf
   Active: active (running) since Fri 2018-12-07 21:31:32 CST; 1min 18s ago
  Process: 58084 ExecStart=/usr/local/mysqlnode/bin/node ${PORT_ARGS} start (code=exited, status=0/SUCCESS)
 Main PID: 58159 (mysqld_safe)
   Memory: 1.3M (limit: 5G)            # 内存限制 5G
   CGroup: /system.slice/system-mysql.slice/mysql@3327.service
           ├─58159 /bin/sh /opt/Percona-Server-5.6.38-rel83.0-Linux.x86_64.ssl101//bin/mysqld_safe --defaults-file=/export/mysql/node3327/my.node.cnf
           ├─59489 /opt/Percona-Server-5.6.38-rel83.0-Linux.x86_64.ssl101/bin/mysqld --defaults-file=/export/mysql/node3327/my.node.cnf --basedir=/opt/Percona-Server-5.6.38-rel83.0-Linux.x86_64.ssl101 --datadir=/export/mysql/node3327/...
           └─59490 logger -t mysqld-3327 -p daemon.error

更多属性参见 man systemd.resource-control, 不过一些参数没有对应的属性, 需要手动单独设置, 比如设置单独某个服务的 memory + swap 限制, 可以使用以下命令将限制的字节数写到对应的参数文件中:

echo xxxxxx > memory.memsw.limit_in_bytes

查看 MySQL 进程 cgroup 信息, 可以看到 memory, blkid, cpuacct 三个条目对应的信息:

# cat /proc/59489/cgroup 
11:memory:/system.slice/system-mysql.slice/mysql@3327.service
10:devices:/system.slice/system-mysql.slice
9:cpuset:/
8:blkio:/system.slice/system-mysql.slice/mysql@3327.service
7:perf_event:/
6:hugetlb:/
5:freezer:/
4:cpuacct,cpu:/system.slice/system-mysql.slice
3:pids:/system.slice/system-mysql.slice
2:net_prio,net_cls:/
1:name=systemd:/system.slice/system-mysql.slice/mysql@3327.service

查看进程相关的限制值:

# cat /sys/fs/cgroup/memory/system.slice/system-mysql.slice/mysql@3327.service/memory.limit_in_bytes 
5368709120

# cat /sys/fs/cgroup/blkio/system.slice/system-mysql.slice/mysql@3327.service/blkio.weight
1000

# cat /sys/fs/cgroup/cpu/system.slice/system-mysql.slice/mysql@3327.service/cpu.cfs_quota_us 
150000

简单验证

这里仅以不同的 CPUQuota 为例说明, 如下所示可以看到, MySQL 进程在不同 CPUQuota 限制下的不同表现, benchyou 的压测结果也相差较大.

限制进程 CPUQuota 为 150% 的情况下:

# top -p 59489
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                           
 59489 mysql     20   0 3412052 1.468g   8784 S 149.5  2.3   7:08.22 mysqld

# benchyou --oltp-tables-count=256 --read-threads=30 --update-threads 8 --write-threads 6 --delete-threads=5 --mysql-table-engine=innodb ..
time            thds              tps     wtps    rtps    rio    rio/op   wio    wio/op    rMB     rKB/op    wMB     wKB/op   cpu/op  freeMB  cacheMB   w-rsp(ms)  r-rsp(ms)    total-number
[13s]        [r:30,w:6,u:8,d:5]  3323     304     3019    0      0.00     0      0.00      0.00    0.00      0.00    0.00     0.00    0       0         62.56      9.92         40765

time            thds              tps     wtps    rtps    rio    rio/op   wio    wio/op    rMB     rKB/op    wMB     wKB/op   cpu/op  freeMB  cacheMB   w-rsp(ms)  r-rsp(ms)    total-number
[14s]        [r:30,w:6,u:8,d:5]  3319     325     2994    0      0.00     0      0.00      0.00    0.00      0.00    0.00     0.00    0       0         57.21      9.99         44084

限制进程 CPUQuota 为 400% 情况下:

# top -p 59489
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                           
 59489 mysql     20   0 3416472 1.474g   8796 S 400.0  2.4   7:47.42 mysqld 


# benchyou --oltp-tables-count=256 --read-threads=30 --update-threads 8 --write-threads 6 --delete-threads=5 --mysql-table-engine=innodb ..
time            thds              tps     wtps    rtps    rio    rio/op   wio    wio/op    rMB     rKB/op    wMB     wKB/op   cpu/op  freeMB  cacheMB   w-rsp(ms)  r-rsp(ms)    total-number
[10s]        [r:30,w:6,u:8,d:5]  10547    967     9580    0      0.00     0      0.00      0.00    0.00      0.00    0.00     0.00    0       0         19.49      3.12         96270

time            thds              tps     wtps    rtps    rio    rio/op   wio    wio/op    rMB     rKB/op    wMB     wKB/op   cpu/op  freeMB  cacheMB   w-rsp(ms)  r-rsp(ms)    total-number
[11s]        [r:30,w:6,u:8,d:5]  10829    1036    9793    0      0.00     0      0.00      0.00    0.00      0.00    0.00     0.00    0       0         18.23      3.04         107099

会话级别限制

如果没有以 service 启动, 可以按照 pid 查看会话级别的信息:

# systemctl status 303   # 303 为 mysql 进程 id
● session-31208.scope - Session 31208 of user root

session-31208.scope 即为会话级别的信息, 一个会话可能包含多个 MySQL 进程, 使用上述的 systemctl set-property session-31208.scope XXXX 即可对整个会话的进程进行相关的限制. 当然使用这种方式即是对多个进程总体使用情况的限制.

其它限制

如果要单独限制 1 个进程 id, 则需要借助 libcgroup 提供的工具, 具体的使用同 Centos6 的设置, 不过 Centos7 已经弃用了 libgcgroup-tools, 不再建议使用, 如果要对单独进程进行限制, 最好使用 service 进行启动. 另外从 systemd 提供的 ControlGroupInterface 来看, 其提供的控制选项还不够精细, 比如没有 memsw, cpuset 等设置的属性, 这种情况下如果有限制的需求, 还需要靠和以前一样的 libcgroup-tool 方式来设置, 比如以下设置, 限制 pid 为 59489 的进程仅使用 cpu 0 ~ 5, mem 可以是 cpu node0 或 node1, cgclassify 没有指定 --sticky 选项的话则 tasks 会包含指定进程的子进程信息:

# cgcreate -g  cpuset:mysql@3327.service
# cgset -r cpuset.cpus=0-5 mysql@3327.service
# cgset -r cpuset.mems=0-1 mysql@3327.service
# cgclassify -g cpuset:mysql@3327.service 59489

当然也可以使用红帽知识库的方式在启动进程的时候就限制相应的 cpuset 等选项, 详见 redhat-1445073

centos7 配置 uwsgi 系统服务(systemd)

背景生产环境中采用nginx + uwsgi + django 来部署web服务,这里需要实现uwsgi的启动和停止,简单的处理方式可以直接在命令行中启动和kill掉uwsgi服务,但为了更安全、方便的管理uwsgi服务,配置uwsgi到systemd服务中,同时实现开启自启的功能;
另,鉴于supervisor不支持python3,没采用supervisor来管理uwsgi服务;

具体配置方法如下:

step1. 创建配置文件

/etc/systemd/system/server_uwsgi.service

step2. 填入以下内容

[Unit]
Description=HTTP Interface Server
After=syslog.target

[Service]
KillSignal=SIGQUIT
ExecStart=/usr/bin/uwsgi --ini /path/uwsgi.ini
Restart=always
Type=notify
NotifyAccess=all
StandardError=syslog

[Install]
WantedBy=multi-user.target

step3. 将该服务加入到systemd中

systemctl enable /etc/systemd/system/server_uwsgi.service

然后就可以通过systemctl来控制服务的启停

systemctl stop server_uwsgi.service 关闭uwsgi服务
systemctl start server_uwsgi.service 开启uwsgi服务
systemctl restart server_uwsgi.service 重启uwsgi服务

注意事项:

如果uwsgi配置文件中配置了 daemonize=/path/uwsgi.log (uwsgi服务以守护进程运行)
会导致sytemctl启动时多次重启而导致启动失败
需改为 logto=/path/uwsgi.log

首页关于归档友链 NGINX在SYSTEMD中开机自启动失败

最近遇到一个玄学问题,我在 Arch 中使用 Systemd 管理 Nginx 的自启动服务。当时在某次我服务器所在的腾讯云母机半夜宕机自动拉起后,我发现 Nginx 没有正确启动,我没有在意,直接手动拉起了 Nginx 。但是在上次,将内核切换到长期支持版后我发现 Nginx 仍然没有在开机后自启动,systemctl status nginx显示 timeout,但是随后使用systemctl start nginx启动 Nginx 没有遇到任何错误。

$ sudo systemctl status nginx
* nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled)
Active: failed (Result: timeout) since Sat 2018-08-04 16:26:14 CST; 10s ago

Aug 04 16:24:44 isthnew systemd[1]: Starting A high performance web server and a reverse proxy server...
Aug 04 16:26:14 isthnew systemd[1]: nginx.service: Start operation timed out. Terminating.
Aug 04 16:26:14 isthnew systemd[1]: nginx.service: Failed with result 'timeout'.
Aug 04 16:26:14 isthnew systemd[1]: Failed to start A high performance web server and a reverse proxy server.

我检查了我的 nginx.service

[Unit]
Description=A high performance web server and a reverse proxy server
After=network.target network-online.target nss-lookup.target

[Service]
Type=forking
PIDFile=/usr/local/nginx/logs/nginx.pid
PrivateDevices=yes
SyslogLevel=err

ExecStart=/usr/local/nginx/sbin/nginx -g 'pid /usr/local/nginx/logs/nginx.pid; error_log stderr;'
ExecReload=/usr/local/nginx/sbin/nginx -s reload
KillMode=mixed

[Install]
WantedBy=multi-user.target

感觉上是没有问题的,因为这个配置我在另外的服务器上也在使用。重启之后马上 ssh 登录服务器,检查 Systemd 启动 Nginx 的状态,Systemd 一直在等待 Nginx 启动,直到1min30s后提示 Nginx 启动失败。Systemd 只记录到了启动超时的日志,Nginx 的日志里面并没有任何东西。

我尝试过 Google 出的办法,例如将 Type = forking 改为 Type = simple,或者给 Nginx 加上参数 daemon on; 依然不工作。之后我甚至怀疑是部分服务或组建还没有启动的时候 Nginx 就开始启动了,所以启动不正常,但是 After=network.target network-online.target nss-lookup.target 是 Nginx 官方文档中建议的参数,我甚至在将 php-fpm.service 放在了里面也没有效果。

后面我发现手动启动 Nginx 之后 Systemd 中会提示无法读取它的 PID

nginx.service: Failed to read PID from file /usr/local/nginx/logs/nginx.pid: Invalid argument

在 https://bugs.launchpad.net/ubuntu/+source/nginx/+bug/1581864 中发现有人记录了这个错误,在单核服务器中 Systemd 与 Nginx 会产生竞争。解决方法就是让 Nginx 等一下 Systemd

mkdir /etc/systemd/system/nginx.service.d

printf "[Service]nExecStartPost=/bin/sleep 0.1n" > /etc/systemd/system/nginx.service.d/override.conf

systemctl daemon-reload

无法读取PID的错误是解决了,但是依然自启动超时,所以暂时是未解之迷了。

Linux系统/run/systemd空间不足问题解决

之前说Debian8直接升级到Debian9,发现升级完成之后遇到一个问题,就是升级到Debian9之后,在/run/systemd出现可用空间不足问题,连个Nginx都不能启动,于是花了点时间解决了下。

报错详情:

Failed to reload daemon: Refusing to reload, not enough space available on /run/systemd. Currently, 10.5M are free, but a safety buffer of 16.0M is enforced.
Processing triggers for systemd (232-25+deb9u3) ...
Failed to reload daemon: Refusing to reload, not enough space available on /run/systemd. Currently, 10.5M are free, but a safety buffer of 16.0M is enforced.

df -h一下,发现这个目录确实空间不多:

root@elsenow-virmach-128:~# df -h
Filesystem      Size  Used Avail Use% Mounted on
udev             47M     0   47M   0% /dev
tmpfs            12M  880K   11M   8% /run
/dev/vda1       9.7G  1.9G  7.4G  21% /
tmpfs            58M     0   58M   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs            58M     0   58M   0% /sys/fs/cgroup
tmpfs            12M     0   12M   0% /run/user/0

很明显,是提示/run/systemd空间不足,我们只需要想办法给这个目录增加空间就行,一开始我还以为会很麻烦,没想到最后也就一行配置的事情,不多说,直接上代码:

vim /etc/fstab

tmpfs /run tmpfs nosuid,noexec,size=18M,nr_inodes=4096 0 0

也就是到/etc/fstab增加一行就行了。增加之后,保存,重启,问题就解决了。

Ubuntu 18.04 rc.local systemd设置

ubuntu18.04不再使用initd管理系统,改用systemd。

然而systemd很难用,改变太大,跟之前的完全不同。

使用systemd设置开机启动
为了像以前一样,在/etc/rc.local中设置开机启动程序,需要以下几步:

1、systemd默认读取/etc/systemd/system下的配置文件,该目录下的文件会链接/lib/systemd/system/下的文件。一般系统安装完/lib/systemd/system/下会有rc-local.service文件,即我们需要的配置文件。
链接过来:

ln -fs /lib/systemd/system/rc-local.service /etc/systemd/system/rc-local.service  
cd /etc/systemd/system/  
cat rc-local.service  

rc-local.service内容

#  SPDX-License-Identifier: LGPL-2.1+  
#  
#  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  
Documentation=man:systemd-rc-local-generator(8)  
ConditionFileIsExecutable=/etc/rc.local  
After=network.target  

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

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

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

2) [Service] 区块:启动行为,如何启动,启动类型。

3) [Install] 区块,定义如何安装这个配置文件,即怎样做到开机启动。

2、创建/etc/rc.local文件

touch /etc/rc.local  

3、赋可执行权限

chmod 755 /etc/rc.local  

4、编辑rc.local,添加需要开机启动的任务

#!/bin/bash  

echo "test rc " > /var/test.log  

5、执行reboot重启系统,然后查看test.log

systemd 的网络管理

0. 简介

systemd 是 freedesktop 的项目,官网 https://www.freedesktop.org/wiki/Software/systemd/ ,项目源码在 github 上发布,可以在 https://github.com/systemd/systemd 查看所有版本更新、 Bug Fix 和版本对应的文档等。
systemd-networkd 是 systemd 默认提供的网络管理服务,可以完全管理以太网,对于无线网卡,还需要其他服务支持,比如管理 Wi-Fi 的 wpa_supplicant@.service ,管理 PPP 的 ppp@.service 。
管理网卡前,应该确保各网卡的驱动都已经正常加载,systemd 的 systemd-modules-load.service 负责在系统启动时静态加载内核模块。它会从以下路径搜索可用的配置文件:

/etc/modules-load.d/*.conf
/run/modules-load.d/*.conf
/usr/lib/modules-load.d/*.conf

配置文件的内容就是一个内核模块名称的列表,可以用井号 # 或者分号 ; 注释单个模块。

1. 基本配置

我的系统中,systemd 的版本是 216 ,有两个以太网卡和一个 Wi-Fi 网卡,先查看一下网卡列表,再查看 systemd-networkd.service 的状态:

~# networkctl list
IDX LINK             TYPE               OPERATIONAL SETUP     
1 lo               loopback           carrier     unmanaged 
2 wlp1s0           wlan               off         unmanaged 
3 enp0s20f6        ether              routable    configured
4 enp0s20f7        ether              no-carrier  configured
4 links listed.
~# systemctl status systemd-networkd 
a—? systemd-networkd.service - Network Service
Loaded: loaded (/lib/systemd/system/systemd-networkd.service; enabled)
Active: active (running) since Fri 2018-04-27 17:49:22 UTC; 24min ago
    Docs: man:systemd-networkd.service(8)
Main PID: 260 (systemd-network)
Status: "Processing requests..."
CGroup: /system.slice/systemd-networkd.service
        a””a”€260 /lib/systemd/systemd-networkd

Apr 27 17:49:21 quark systemd[1]: Starting Network Service...
Apr 27 17:49:22 quark systemd-networkd[260]: lo              : gained carrier
Apr 27 17:49:22 quark systemd[1]: Started Network Service.
Apr 27 17:49:22 quark systemd-networkd[260]: eth1            : renamed to enp0s20f7
Apr 27 17:49:22 quark systemd-networkd[260]: eth0            : renamed to enp0s20f6
Apr 27 17:49:23 quark systemd-networkd[260]: enp0s20f6       : gained carrier
Apr 27 17:49:23 quark systemd-networkd[260]: enp0s20f7       : gained carrier
Apr 27 17:49:25 quark systemd-networkd[260]: enp0s20f6       : lost carrier
Apr 27 17:49:26 quark systemd-networkd[260]: enp0s20f7       : lost carrier

sytemd-network.service 的配置文件可以位于 /usr/lib/systemd/network/ 或者 /etc/systemd/network/ 目录下,后者具有最高优先级。配置文件有三种类型:

  • .network 文件,设置网卡的 IP 等各项属性
  • .netdev 文件,新建一个虚拟网卡
  • .link 文件,每当一个网卡出现时,udev 都会查找与它同名的 .link 文件

这几类文件都遵循下面的规则:

  • 各选项的值都支持星号 * 通配符
  • 当 [Match] 段内的条件都匹配时,后面的配置项才会被激活
  • 如果 [Match] 段为空,表示后面的配置项在任何情况下都可用
  • 无论配置文件在哪个目录,都会统一安装字典顺序进行加载
  • 同名文件可以相互替换

如果要给 enp0s20f6 配置一个静态 IP ,可以在 /etc/systemd/network/ 目录下新建一个 eth0.network 文件,内容如下:

[Match]
Name=enp0s20f6  # 匹配名为 enp0s20f6 的网卡
[Network]
DHCP=none    # 关闭 DHCP 客户端,
Address=192.168.5.242/24  # 设置 IP 和子网掩码
Gateway=192.168.5.50   # 设置网关,这项设置会将该网卡添加到缺省路由
DNS=8.8.8.8  # 设置 DNS 

如果要使用 DHCP 自动获取 IP ,也将 DHCP 设为如下值:

  • v4 ,只接受 ipv4 的 IP
  • v6 ,只接受 ipv6 的 IP
  • both ,同时接受 ipv4 和 ipv6 格式的 IP

启动 DHCP 客户端后,Gateway 和 DNS 也会自动获取,有时我们不希望这样,可以在配置文件中添加一个 [DHCP] 段,做如下设置:

[DHCP]
UseDNS=false # 不使用 DHCP 分配的 DNS ,默认值是 true
UseRoutes=false # 不会将本网卡设为缺省路由,默认值是 true 

在这里会出现一个 Bug ,就是 UseRoutes 设置无效,高版本中已经解决,解决方案在 https://github.com/systemd/systemd/pull/3075 。

2. Wi-Fi 配置

Wi-Fi 连接还是使用 wpa_supplicant 程序,systemd 提供了 wpa_supplicant@.service 管理整个流程,然后再用 systemd-networkd.service 配置 IP 等。
wpa_supplicant@.service 的内容:

[Unit]
Description=WPA supplicant daemon (interface-specific version)
Requires=sys-subsystem-net-devices-%i.device
After=sys-subsystem-net-devices-%i.device

# NetworkManager users will probably want the dbus version instead.

[Service]
Type=simple
ExecStart=/usr/sbin/wpa_supplicant -c/etc/wpa_supplicant/wpa_supplicant-%I.conf -i%I

[Install]
Alias=multi-user.target.wants/wpa_supplicant@%i.service

这是 service 模板,启动时在 @ 符号之后加入 Wi-Fi 网卡的名称使其实例实例化,这个名词还会关联到 wpa_supplicant 的配置文件,我们先在 /etc/ 下新建一个 wpa_supplicant 目录,然后新建一个 wpa_supplicant-wlp1s0.conf 文件,内容如下:

ctrl_interface=/var/run/wpa_supplicant
ctrl_interface_group=wheel
update_config=1
eapol_version=1
ap_scan=1
fast_reauth=1

用 wpa_passphrase 添加无线路由器配置:

~# sudo wpa_passphrase [essid] [password] >> /etc/wpa_supplicant/wpa_supplicant-wlp1s0.conf
在 /etc/systemd/network/ 目录下添加配置文件 wireless.network ,内容如下:
[Match]
Name=wlp1s0
[Network]
DHCP=yes

最后重启各项服务:

~# systemctl restart systemd-networkd
~# systemctl restart wpa_supplicant@wlp1s0
~# systemctl restart systemd-resolved

3. 3G/4G 配置

依然使用 pppd 进行 3G/4G 拨号,systemd 提供了 ppp@.service 服务管理 pppd 。首先在 /etc/ppp/peers/ 下建好 ppp 拨号文件 me909s ,然后启动服务:

~# systemctl start ppp@me909s 

4. 关闭 IPv6

向内核参数加入 ipv6.disable=1 可以完全关闭 IPv6 功能。此外,只加入 ipv6.disable_ipv6=1 内核参数可以保留 IPv6 功能,但不会向网卡分配 IPv6 地址,做法是:

~# echo 1 > /proc/sys/net/ipv6/conf/enp0s20f6/disable_ipv6

5. ssh server

目前流行的启动 ssh server 的方式是调用 sshd.socket :

~# systemctl start sshd.socket

旧有的 sshd.service 模式会在后台保持一个 sshd 的守护进程,每当有 ssh 连接要建立时,就创建一个新进程,比较适合 SSH 下有大量流量的系统;
新的 sshd.socket 方式也是在每次要建立新的ssh连接时生成一个守护进程的实例,不过监听端口则是交给了 systemd 来完成,意味着没有 ssh 连接的时候,也不会有 sshd 守护进程运行,大部分情况下,使用 sshd.socket 服务更为合适。这也与 MacOS 下的行为相一致,默认只监听端口,有连接时才创建进程。
另外,通过使用 .socket 文件来管理需要监听端口的服务,可以直接通过 systemctl 来查看一些网络相关的信息,如监听的端口、目前已经接受的连接数、目前正连接的连接数等。

centos7的服务治理-systemd

经常用到的高频命令小结

- 所有服务unit放在这里
ll /usr/lib/systemd/system

- 默认启动级别
[root@n1 ~]# ll /etc/systemd/system/default.target 
lrwxrwxrwx 1 root root 41 Mar  4 09:02 /etc/systemd/system/default.target -> /usr/lib/systemd/system/multi-user.target

- 开机启动的服务
ll /etc/systemd/system/multi-user.target.wants/

- 哪些服务开机会启动
[root@n1 ~]# systemctl list-unit-files --type service |grep enable
autovt@.service                               enabled 
crond.service                                 enabled 
docker.service                                enabled 
getty@.service                                enabled 
ntpdate.service                               enabled 
rsyslog.service                               enabled 
sshd.service                                  enabled 
sysstat.service                               enabled 

- 目前哪些服务处于active状态
[root@n1 ~]# systemctl list-units --type service |grep active
crond.service                      loaded active running Command Scheduler
dbus.service                       loaded active running D-Bus System Message Bus
docker.service                     loaded active running Docker Application Container Engine
getty@tty1.service                 loaded active running Getty on tty1

- 查看某个服务是否开机自启
systemctl is-enabled httpd.service 或
systemctl status httpd.service

systemd 有很多unit,其中service.unit是管理系统服务的.

未分类

unit的类型

/usr/lib/systemd/system

未分类

查看管理系统服务的相关的, 即 .service后缀的

[root@n1 system]# ls /usr/lib/systemd/system/*.service|more
/usr/lib/systemd/system/arp-ethers.service
/usr/lib/systemd/system/auditd.service
/usr/lib/systemd/system/autovt@.service
/usr/lib/systemd/system/blk-availability.service
/usr/lib/systemd/system/brandbot.service
...

特点:
    不需要可执行权限,其内容也不能执行
    仅是systemd的调用的配置文件<img

src=”http://devops.webres.wang/wp-content/uploads/2018/03/3-20.png” alt=”” width=”388″ height=”443″ class=”alignnone size-full wp-image-19103″ />

centos6 chkconfig VS 使用systemd的service unit治理centos7服务

未分类

ls /usr/lib/systemd/system/*.service|more 下以service开头的文件,都是和系统服务有关的, 其下还有别的后缀的服务.是别的unit的配置文件.

未分类

serivice unit配置

未分类

target unit: 的含义是服务组,表示一组服务

将很多服务放在一坨, 开启启动时候加载这个文件.

启动级别

/usr/lib/systemd/system
/etc/systemd/system

runlevel0.target -> poweroff.target
runlevel2.target -> multi-user.target
runlevel3.target -> multi-user.target
runlevel4.target -> multi-user.target
runlevel5.target -> graphical.target
runlevel6.target -> reboot.target

注意: centos7里,运行级别234没区别.

查看在运行的服务

我们看ACTIVE就ok了,SUB这一列可以不用理会

[root@n1 system]# systemctl list-units --type=service
UNIT                               LOAD   ACTIVE SUB     DESCRIPTION
crond.service                      loaded active running Command Scheduler
dbus.service                       loaded active running D-Bus System Message Bus
docker.service                     loaded active running Docker Application Container Engine
getty@tty1.service                 loaded active running Getty on tty1
httpd.service                      loaded active running The Apache HTTP Server

查看开机是否会启动

[root@n1 system]# systemctl list-unit-files --type=service
UNIT FILE                                     STATE   
arp-ethers.service                            disabled
auditd.service                                disabled
autovt@.service                               enabled #开机会启动
blk-availability.service                      disabled#开机不会启动
brandbot.service                              static  #这类不用管
conntrackd.service                            disabled

systemd管理服务入门http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html

设置开机自动启动细节

[root@n1 ~]# yum install psmisc -y
[root@n1 ~]# pstree
systemd─┬─agetty
        ├─crond
        ├─dbus-daemon
        ├─dockerd─┬─docker-containe─┬─docker-containe─┬─mysqld───20*[{mysqld}]
        │         │                 │                 └─8*[{docker-containe}]
        │         │                 └─9*[{docker-containe}]
        │         ├─docker-proxy───3*[{docker-proxy}]
        │         └─11*[{dockerd}]
        ├─httpd───8*[httpd]
        ├─lvmetad
        ├─polkitd───5*[{polkitd}]
        ├─rsyslogd───2*[{rsyslogd}]
        ├─sshd───sshd───bash───pstree
        ├─systemd-journal
        ├─systemd-logind
        └─systemd-udevd
[root@n1 system]# systemctl enable httpd
Created symlink from /etc/systemd/system/multi-user.target.wants/httpd.service to /usr/lib/systemd/system/httpd.service.

相当于执行

ln -s /usr/lib/systemd/system/httpd.service /etc/systemd/system/multi-user.target.wants/httpd.service

disable相当于删除

[root@n1 system]# systemctl disable httpd
Removed symlink /etc/systemd/system/multi-user.target.wants/httpd.service.
[root@n1 system]# ll /etc/systemd/system/multi-user.target.wants
total 0
lrwxrwxrwx. 1 root root 37 Dec 27 04:44 crond.service -> /usr/lib/systemd/system/crond.service
lrwxrwxrwx  1 root root 38 Dec 26 21:05 docker.service -> /usr/lib/systemd/system/docker.service
lrwxrwxrwx  1 root root 37 Mar  4 03:39 httpd.service -> /usr/lib/systemd/system/httpd.service
lrwxrwxrwx. 1 root root 39 Dec 26 21:00 ntpdate.service -> /usr/lib/systemd/system/ntpdate.service
lrwxrwxrwx. 1 root root 40 Dec 27 04:44 remote-fs.target -> /usr/lib/systemd/system/remote-fs.target
lrwxrwxrwx. 1 root root 39 Dec 27 04:44 rsyslog.service -> /usr/lib/systemd/system/rsyslog.service
lrwxrwxrwx. 1 root root 36 Dec 27 04:44 sshd.service -> /usr/lib/systemd/system/sshd.service
lrwxrwxrwx. 1 root root 39 Dec 27 05:00 sysstat.service -> /usr/lib/systemd/system/sysstat.service

设置启动级别的命令

- 设置默认启动级别
[root@n1 ~]# systemctl set-default graphical.target 
Removed symlink /etc/systemd/system/default.target.
Created symlink from /etc/systemd/system/default.target to /usr/lib/systemd/system/graphical.target.

- 查看默认的启动级别
[root@n1 ~]# systemctl get-default 
graphical.target

- 手动修改启动级别(先将原来的rm了)
[root@n1 ~]# ln -sv /usr/lib/systemd/system/multi-user.target /etc/systemd/system/default.target
ln: failed to create symbolic link ‘/etc/systemd/system/default.target’: File exists
[root@n1 ~]# rm -rf /etc/systemd/system/default.target
[root@n1 ~]# ln -sv /usr/lib/systemd/system/multi-user.target /etc/systemd/system/default.target
‘/etc/systemd/system/default.target’ -> ‘/usr/lib/systemd/system/multi-user.target’
[root@n1 ~]# systemctl get-default 
multi-user.target

- 查看服务状态

[root@n1 ~]# systemctl is-enabled httpd.service 
disabled

systemd 编写服务管理脚本

我们运行 linux 服务器的主要目的是通过运行程序提供服务,比如 mysql、web server等。因此管理 linux 服务器主要工作就是配置并管理上面运行的各种服务程序。在 linux 系统中服务程序的管理主要由 init 系统负责。如同笔者在《初识 systemd》一文中的介绍,linux 的 init 系统已经从最初的 sysvinit 进化到了如今的 systemd。本文主要介绍在 systemd 环境中如何编写运行服务的配置文件。

unit(单元)的配置文件

Unit 是 systemd 进行任务管理的基本单位,我们在前文中已经介绍过,service 类型的 unit 代表一个后台服务进程。接下来我们就详细的介绍如何配置 service 类型的 unit。下面我们先来看一个简单的服务配置:

[Unit]
Description=Prometheus Server
Documentation=https://prometheus.io/docs/introduction/overview/
After=network.target

[Service]
User=prometheus
Restart=on-failure
WorkingDirectory=/usr/local/share/prometheus/
ExecStart=/usr/local/share/prometheus/prometheus 
          -config.file=/usr/local/share/prometheus/prometheus.yml

[Install]
WantedBy=multi-user.target

这是笔者主机上 prometheus 服务的配置文件。把上面的内容保存到文件 /lib/systemd/system/prometheus.service 中,然后就可以使用 systemctl 命令管理 prometheus 服务了。注意,服务类型的配置文件名称必须以 .service 结尾。
查看上面配置信息的详细内容,我们会发现整个配置的内容分为三个部分:
[Unit] unit 本身的说明,以及与其它有依赖关系的服务的设置,包括在什么服务之后才启动此 unit 之类的设置。
[Service] 不同的 unit 类型就得要使用相对应的设置项目,比如 timer 类型的 unit 应该是 [Timer],socket 类型的 unit 应该是 [Socket]。服务类型的 unit 就是 [Service],这个项目内主要在规范服务启动的脚本、环境配置文件文件名、重新启动的方式等等。
[Install] 这个部分主要设置把该 unit 安装到哪个 target 。

服务类型 unit 的详细配置

配置文件分为三个部分,每个部分中都可以提供详细的配置信息。为了精确的控制服务的运行方式,我们需要了解这些详细的配置选项,并最终让服务以我们期望的方式运行。

[Unit] 部分
Description 关于该 unit 的简易说明。
Documentation 文档相关的内容,如 Documentation=https://prometheus.io/docs/introduction/overview/
Documentation=man:sshd(8)
Documentation=file:/etc/ssh/sshd_config
After 说明本 unit 是在哪个服务启动之后才启动的意思。仅是说明服务启动的顺序而已,并没有强制要求 。
Before 与 After 的意义相反,在指定的服务启动前最好启动本个服务的意思。仅是说明服务启动的顺序而已,并没有强制要求 。
Requires 本 unit 需要在哪个服务启动后才能够启动!就是设置服务间的依赖性。如果在此项设置的前导服务没有启动成功,那么本 unit 就不会被启动!
Wants 与 Requires 刚好相反,规范的是这个 unit 之后还要启动什么服务,如果这 Wants 后面接的服务如果没有启动成功,其实不会影响到这个 unit 本身!
Conflicts 这个项目后面接的服务如果有启动,那么本 unit 就不能启动!如果本 unit 启动了,则指定的服务就不能启动。

[Service] 部分
Type
说明这个服务的启动方式,会影响到 ExecStart,主要有下面几种类型:
simple:默认值,这个服务主要由 ExecStart 设置的程序来启动,启动后常驻于内存中。
forking:由 ExecStart 指定的启动的程序通过 spawns 产生子进程提供服务,然后父进程退出。
oneshot:与 simple 类似,不过这个程序在工作完毕后就结束了,不会常驻在内存中。
dbus:与 simple 类似,但这个服务必须要在取得一个 D-Bus 的名称后,才会继续运行!因此设置这个项目时,通常也要设置 BusName= 才行。
idle:与 simple 类似,意思是,要执行这个服务必须要所有的工作都顺利执行完毕后才会执行。这类的服务通常是开机到最后才执行即可的服务。
notify:与 simple 类似,但这个服务必须要收到一个 sd_notify() 函数发送的消息后,才会继续运行。

ExecStart
就是实际执行此服务的程序。接受 “命令 参数 参数…” 的格式,不能接受 <, >, >>, |, & 等特殊字符,很多的 bash 语法也不支持。所以,要使用这些特殊的字符时,最好直接写入到脚本里面去!

ExecStartPreExecStartPost 分别在服务启动前后,执行额外的命令。

ExecStop 用来实现 systemctl stop 命令,关闭服务。
ExecReload 用来实现 systemctl reload 命令,重新加载服务的配置信息。

Restart 当设置为 Restart=1 时,如果服务终止,就会自动重启此服务。
RestartSec 与 Restart 配合使用,在服务终止多长时间之后才重新启动它。默认是 100ms。

KillMode
可以是 process, control-group, none 中的一种,如果是 process 则服务终止时,只会终止主要的程序(ExecStart接的后面那串指令),如果是 control-group 时,则由此 daemon 所产生的其他 control-group 的程序,也都会被关闭。如果是 none 的话,则没有程序会被关闭。

TimeoutSec
若这个服务在启动或者是关闭时,因为某些缘故导致无法顺利 “正常启动或正常结束” 的情况下,则我们要等多久才进入 “强制结束” 的状态!

RemainAfterExit
当设置为 RemainAfterExit=1 时,则当这个服务所属的所有程序都终止之后,此服务会再尝试启动。这对于 Type=oneshot 的服务很有帮助!

环境变量的设置对很多程序来说都是十分重要的,下面的配置则可以以不同的方式为服务程序设置环境变量:
Environment 用来设置环境变量,可以使用多次:

[Service]
# Client Env Vars
Environment=ETCD_CA_FILE=/path/to/CA.pem
Environment=ETCD_CERT_FILE=/path/to/server.crt

EnvironmentFile 通过文件的方式设置环境变量,可以把下面的内容保存到文件 testenv 中:

AAA_IPV4_ANCHOR_0=X.X.X.X
BBB_IPV4_PRIVATE_0=X.X.X.X
CCC_HOSTNAME=test.example.com

然后这样设置:

[Service]
EnvironmentFile=/testenv

接下来就可以在 ExecStart 配置中使用在文件中设置的环境变量,如:

ExecStart=/xxx --abc=xx${AAA_IPV4_ANCHOR_0}yy

[Install] 部分
WantedBy 这个设置后面接的大部分是 *.target unit。意思是,这个 unit 本身是附挂在哪个 target unit 下面。
Also 当目前这个 unit 被 enable 时,Also 后面接的 unit 也要 enable 的意思。
Alias 当 systemctl enable 相关的服务时,则此服务会进行链接文件的创建!

Timer 类型 unit 的详细配置

Timer 类型的 unit 主要用来执行定时任务,并有可能取代 cron 服务。由于 timer 类型的 unit 经常与服务类型的 unit 一起使用,所以本文也附带介绍一下 timer unit 的配置。与服务类型的 unit 不同,timer unit 配置文件中的主要部分是 [Timer],下面是其主要的配置项:
OnActiveSec 当 timers.target 启动后多久才执行这个 unit。
OnBootSec 当开机后多久才执行这个 unit。
OnStartupSec 当 systemd 第一次启动后多久才执行这个 unit。
OnUnitActiveSec 这个 timer 配置文件所管理的那个 unit 服务在最后一次启动后,隔多久后再执行一次。
OnUnitInactiveSec 这个 timer 配置文件所管理的那个 unit 服务在最后一次停止后,隔多久后再执行一次。
Unit 一般不需要设置,基本上我们设置都是 服务名称.server + 服务名称.timer。如果你的服务名称和 timer 名称不相同,就需要在 .timer 文件中通过 Unit 项指定服务的名称。
OnCalendar 使用实际时间(非循环时间)的方式来启动服务。
Persistent 当使用 OnCalendar 的设置时,指定该功能要不要持续执行。

通过上面的介绍,相信大家对 systemd 服务类型和 timer 类型的 unit 配置已经有了基本的理解,下面让就让我们配置两个实际的例子。

配置 redis 服务

在 ubuntu 上我们一般会手动编译并安装 redis。在安装完成后需要把 redis 配置为 systemd 管理的服务,下面介绍具体的配置过程。

添加 redis 配置文件

首先手动创建 /etc/redis 目录并添加配置文件:

$ sudo mkdir /etc/redis

并把代码目录中的配置文件 redis.conf 拷贝到 /etc/redis 目录中:

$ sudo cp /tmp/redis-4.0.0/redis.conf /etc/redis/

然后修改配置文件 /etc/redis/redis.conf 中的 supervised 为 systemd:
supervised systemd

未分类

接着继续在配置文件 /etc/redis/redis.conf 中配置工作目录,把 dir ./ 修改为:
dir /var/lib/redis

未分类

配置由 systemd 管理 redis 服务

创建 /etc/systemd/system/redis.service 文件

$ sudo vim /etc/systemd/system/redis.service

编辑其内容如下:

[Unit]
Description=Redis In-Memory Data Store
After=network.target

[Service]
User=redis
Group=redis
ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
ExecStop=/usr/local/bin/redis-cli shutdown
Restart=always

[Install]
WantedBy=multi-user.target

启动服务并配置为开机启动:

$ sudo systemctl start redis
$ sudo systemctl enable redis
$ sudo systemctl status redis

未分类

通过脚本定时备份文件

备份文件的 bash 脚本:

#!/bin/bash
mydate()
{
        date "+%Y%m%d%H%M%S"
}
backupdate=$(mydate)
tar -zcf /tmp/backup.${backupdate}.tar.gz /home/nick/learn

把上面的代码保存到文件 /usr/local/bin/backupdir.sh,并添加可执行权限:

$ sudo chmod +x /usr/local/bin/backupdir.sh

然后创建 service unit 配置文件:

[Unit]
Description=nick backup learn dir service

[Service]
User=nick
Group=nick
Type=simple
ExecStart=/usr/local/bin/backupdir.sh

[Install]
WantedBy=multi-user.target

把上面的 unit 配置保存到文件 /etc/systemd/system/nickbak.service。
然后执行下面的命令测试服务的执行情况:

$ sudo systemctl daemon-reload
$ sudo systemctl start nickbak.service

这样的备份任务只会在执行 sudo systemctl start nickbak.service 时执行一次。下面我们通过 timer unit 把它配置为定时执行。
创建 timer unit 配置文件:

[Unit]
Description=nick backup learn dir timer

[Timer]
OnCalendar=*:0/15
Persistent=true
Unit=nickbak.service

[Install]
WantedBy=multi-user.target

把上面的 unit 配置保存到文件 /etc/systemd/system/nickbak.timer。配置中 OnCalendar=*:0/15 表示每 15 分钟执行一次 nickbak.service 服务。

执行下面的命令把 nickbak.timer 设置为开机启动,并启动 nickbak.timer:

$ sudo systemctl daemon-reload
$ sudo systemctl enable nickbak.timer
$ sudo systemctl start nickbak.timer

现在来看看 nickbak.timer 的状态:

$ sudo systemctl status nickbak.timer

未分类

从现在开始 nickbak.timer 会每隔 15 分钟执行一次 nickbak.service 服务。

总结

systemd 提供了服务管理(其实是 unit 管理)的方方面面,我们需要做的就是写好服务 unit 的配置文件,然后利用 systemd 来管理我们的服务。这是一个看似简单实则繁琐的任务(很多的配置项其实需要我们在实践中不断的调整并优化)。希望本文对大家来说是个简单的入门。

systemd 下开机启动的优化,删除无用的systemd服务

一般情况下,常规用途的 Linux 发行版在开机启动时拉起各种相关服务进程,包括许多你可能无需使用的服务,例如蓝牙bluetooth、Avahi、 调制解调管理器ModemManager、ppp-dns(LCTT 译注:此处作者笔误 ppp-dns 应该为 pppd-dns) 等服务进程,这些都是什么东西?用于哪里,有何功能?

Systemd 提供了许多很好的工具用于查看系统启动情况,也可以控制在系统启动时运行什么。在这篇文章中,我将说明在 Systemd 类发行版中如何关闭一些令人讨厌的进程。

查看开机启动项

在过去,你能很容易通过查看 /etc/init.d 了解到哪些服务进程会在引导时启动。Systemd 以不同的方式展现,你可以使用如下命令罗列允许开机启动的服务进程。

$ systemctl list-unit-files --type=service | grep enabled
accounts-daemon.service                    enabled
anacron-resume.service                     enabled
anacron.service                            enabled
bluetooth.service                          enabled
brltty.service                             enabled
[...]

在此列表顶部,对我来说,蓝牙服务是冗余项,因为在该电脑上我不需要使用蓝牙功能,故无需运行此服务。下面的命令将停止该服务进程,并且使其开机不启动。

$ sudo systemctl stop bluetooth.service
$ sudo systemctl disable bluetooth.service

你可以通过下面命令确定是否操作成功。

$ systemctl status bluetooth.service
 bluetooth.service - Bluetooth service
  Loaded: loaded (/lib/systemd/system/bluetooth.service; disabled; vendor preset: enabled)
  Active: inactive (dead)
    Docs: man:bluetoothd(8)

停用的服务进程仍然能够被另外一个服务进程启动。如果你真的想在任何情况下系统启动时都不启动该进程,无需卸载该它,只需要把它掩盖起来就可以阻止该进程在任何情况下开机启动。

$ sudo systemctl mask bluetooth.service
 Created symlink from /etc/systemd/system/bluetooth.service to /dev/null.

一旦你对禁用该进程启动而没有出现负面作用感到满意,你也可以选择卸载该程序。

通过执行命令可以获得如下服务列表:

$ systemctl list-unit-files --type=service                       
UNIT FILE                                  STATE   
accounts-daemon.service                    enabled
acpid.service                              disabled
alsa-restore.service                       static    
alsa-utils.service                         masked

你不能启用或禁用静态服务,因为静态服务被其他的进程所依赖,并不意味着它们自己运行。

哪些服务能够禁止?

如何知道你需要哪些服务,而哪些又是可以安全地禁用的呢?它总是依赖于你的个性化需求。

这里举例了几个服务进程的作用。许多服务进程都是发行版特定的,所以你应该看看你的发行版文档(比如通过 google 或 StackOverflow)。

  • accounts-daemon.service 是一个潜在的安全风险。它是 AccountsService 的一部分,AccountsService 允许程序获得或操作用户账户信息。我不认为有好的理由能使我允许这样的后台操作,所以我选择掩盖mask该服务进程。
  • avahi-daemon.service 用于零配置网络发现,使电脑超容易发现网络中打印机或其他的主机,我总是禁用它,别漏掉它。
  • brltty.service 提供布莱叶盲文设备支持,例如布莱叶盲文显示器。
  • debug-shell.service 开放了一个巨大的安全漏洞(该服务提供了一个无密码的 root shell ,用于帮助 调试 systemd 问题),除非你正在使用该服务,否则永远不要启动服务。
  • ModemManager.service 该服务是一个被 dbus 激活的守护进程,用于提供移动宽频broadband(2G/3G/4G)接口,如果你没有该接口,无论是内置接口,还是通过如蓝牙配对的电话,以及 USB 适配器,那么你也无需该服务。
  • pppd-dns.service 是一个计算机发展的遗物,如果你使用拨号接入互联网的话,保留它,否则你不需要它。
  • rtkit-daemon.service 听起来很可怕,听起来像是 rootkit。 但是你需要该服务,因为它是一个实时内核调度器real-time kernel scheduler。
  • whoopsie.service 是 Ubuntu 错误报告服务。它用于收集 Ubuntu 系统崩溃报告,并发送报告到 https://daisy.ubuntu.com 。 你可以放心地禁止其启动,或者永久的卸载它。
  • wpa_supplicant.service 仅在你使用 Wi-Fi 连接时需要。

系统启动时发生了什么?

Systemd 提供了一些命令帮助调试系统开机启动问题。该命令会重演你的系统启动的所有消息。

$ journalctl -b

-- Logs begin at Mon 2016-05-09 06:18:11 PDT,
end at Mon 2016-05-09 10:17:01 PDT. --
May 16 06:18:11 studio systemd-journal[289]:
Runtime journal (/run/log/journal/) is currently using 8.0M.
Maximum allowed usage is set to 157.2M.
Leaving at least 235.9M free (of currently available 1.5G of space).
Enforced usage limit is thus 157.2M.
[...]

通过命令 journalctl -b -1 可以复审前一次启动,journalctl -b -2 可以复审倒数第 2 次启动,以此类推。

该命令会打印出大量的信息,你可能并不关注所有信息,只是关注其中问题相关部分。为此,系统提供了几个过滤器,用于帮助你锁定目标。让我们以进程号为 1 的进程为例,该进程是所有其它进程的父进程。

$ journalctl _PID=1

May 08 06:18:17 studio systemd[1]: Starting LSB: Raise network interfaces....
May 08 06:18:17 studio systemd[1]: Started LSB: Raise network interfaces..
May 08 06:18:17 studio systemd[1]: Reached target System Initialization.
May 08 06:18:17 studio systemd[1]: Started CUPS Scheduler.
May 08 06:18:17 studio systemd[1]: Listening on D-Bus System Message Bus Socket
May 08 06:18:17 studio systemd[1]: Listening on CUPS Scheduler.
[...]

这些打印消息显示了什么被启动,或者是正在尝试启动。

一个最有用的命令工具之一 systemd-analyze blame,用于帮助查看哪个服务进程启动耗时最长。

$ systemd-analyze blame
         8.708s gpu-manager.service
         8.002s NetworkManager-wait-online.service
         5.791s mysql.service
         2.975s dev-sda3.device
         1.810s alsa-restore.service
         1.806s systemd-logind.service
         1.803s irqbalance.service
         1.800s lm-sensors.service
         1.800s grub-common.service

这个特定的例子没有出现任何异常,但是如果存在系统启动瓶颈,则该命令将能发现它。