@Tmacy
2016-08-23T07:00:10.000000Z
字数 18925
阅读 1385
linux
systemd
以下是systemd系列博客的翻译,博文为systemd的作者Lennart Poettering 所写,本译文仅供学习使用。
附录中有其他学习资料供参考
Unix系统的核心特性之一是在操作系统内不同部分的权限隔离。很多系统服务运行在他们自己的ID下,这样限制了他们能做什么,并影响了他们能从系统中得到资源,以防他们被剥削。
这种权限隔离只提供非常基础保护。然而,在通用系统中服务以这种方式运行仍能获得权限。即使得不到root的权限,至少与普通本地用户一样。处于安全目的,希望对服务进一步限制所能做的事情,并且允许普通用户关闭他们。
一个有力的方式来限制服务是通过MAC技术(强制访问控制),例如SELinux。如果你对保护你的服务很感兴趣,运行SELinux是好主意。systemd 使开发者与系统管理员能使用独立于MAC的外的本地服务限制工具。这样,不管你是否使用SELinux,你都可以对你的服务进行安全限制。
在本文中,我们想专注于一些systemd的安全特性,以及如何在服务中使用。这些特性使用了一些Linux特别的技术,并且已近在Linux的内核支持已久,但从未广泛使用。这些systemd特性已经做的尽可能易用,以便更吸引系统管理员以及上游开发者。
所有的选项在systemd的用户手册有具体描述,参看systemd.exec。
所有这些选项在systemd系统中都支持,无论是否使用SELinux或者其他的MAC。
这些选项的使用成本低廉,即使你认为你的服务不用写入/tmp,并且激活PrivateTmp=yes是没有必要的,由于当今复杂的软件,激活这个选项还是会有好处的。很简单,比如你依赖的不归你控制的库,可能需要临时文件。例如:你觉不知道你的本地安装器启动了哪种NSS模块,以及那种NSS模块需要/tmp。
系统管理员为了保护他们的系统,上游开发者为了安全的封装他们的服务,都会对这些选项感兴趣。我们强烈建议上游开发者在他们的服务中考虑默认使用这些选项。这些用起来很简单并且可以为安全提供保护。
你在systemd中使用的一个简单而强大的配置选项是PrivateNetwork=
...
[Service]
ExecStart=...
PrivateNetwork=yes
...
利用一个简单的设置,一个服务以及其包含的所有进程会从任何网络中完全隔离。这些服务是不可访问网络接口的,他们能见到的只是loopback设备“lo“。但是他们从真是的loopback中隔离出来。这对网络攻击是一个很强大的防御。
警告: 一些服务会选择性的需要联网。当然,没有人会在一个网络服务上使用PrivateNetwork=yes,例如apache。然而对于非网络应用的服务提供网络支持是有必要的。例如:如果一个基于LDAP的用户数据库配置本地系统,利用系统调用做glibc名称查找,例如getpwnam(),可能需要网络请求。这就是说,大部分情况下使用PrivateNetwork=yes并不合适,即使没有网络,系统服务用户的用户ID也需要解析。这就意味着只要需要解析的用户ID在1000以下,使用,PrivateNetwork=yes应该是可以的。
这个特性使用了内核的网络命名空间(network namespaces)。如果可以激活一个新的网络空间,那么只有loopback设备需要配置。
另一个简单而强大的配置是PrivateTmp=
...
[Service]
ExecStart=...
PrivateTmp=yes
...
如果激活这个选项,可以确保服务可独立访问/tmp目录,并且从主机系统的/tmp隔离开来。/tmp在传统上是被所有本地服务和用户所共享的。多年以来这都是一个主要的安全隐患。符号链接攻击以及利用推测/tmp临时文件的Dos漏洞都是很常见的。从主系统中隔离出服务的/tmp,这种漏洞变得么意义了。
Fedora 17中增加一个新的特性,可以在多个服务之间激活这个选项。
警告:一些服务实际上误把/tmp用作IPC sockets以及其他进程件通信的位置,这通常是一个漏洞(因为如果你用它做通信,你需要一个可推测的名称,而一个可推测的名字会让你的代码被Dos和符号链接攻击),/run通常是一个安全的位置,因为它对于非授权进程是不可写的。例如,X11会将通信的socket放在/tmp下(这个是安全的--尽管不是很理想--它在引导早期创建了一个安全的子目录)。那些需要利用/tmp通信的服务只能使用PrivateTmp=。感谢只有很少的服务误用了/tmp。
这个特性使用了内核提供的系统命名空间。如果激活了一个新的文件系统命名空间,是继承了主系统的大部分层次结构,除了/tmp。
使用ReadOnlyDirectories=和InaccessibleDirectories=选项可以指定目录的访问权限,包括读与写:
[Service]
ExecStart=...
InaccessibleDirectories=/home
ReadOnlyDirectories=/var
使用这两行配置使得/home下的所有目录对服务都不可见(目录会显示为空文件并且访问权限是000),并且/var下的目录都是只读的。
***警告*:注意***ReadOnlyDirectories=*当前不是递归应用到指定的子挂载点(在/var下的挂载点依然是可写的)。这会尽快修复。(当前已修复)
这个是基于文件系统命名空间来实现的。
另一个非常强大的安全选项是CapabilityBoundingSet=,允许你在一个相对合理的程度上限制一个服务保持的内核功能
[Service]
ExecStart=...
CapabilityBoundingSet=CAP_CHOWN CAP_KILL
上面的例子表示只为服务保留了 CAP_CHOWN 和 CAP_KILL功能,并且服务和进程无法获取任何其他的功能,即使通过setuid。当前定义的能力可以在capablilities中查询。不幸的是一些定义的功能过于通用的(例如CAP_SYS_ADMIN),然而它们仍然是一个很有用的工具,尤其是对需要root权限的服务。
要明确了解那些功能对服务来说是需要的其实并不容易,并且需要一些测试。为了简化一点,可以把那些不需要的功能放在黑名单中。例如:CAP_SYS_PTRACE是非常强大的安全相关的功能,需要实现调试器,因为它允许反射和控制任何本地进程。一个服务,像Apache,很显然不需要对其他进程的调试器,从上面移去这个功能是更安全的:
[Service]
ExecStart=...
CapabilityBoundingSet=~CAP_SYS_PTRACE
符号~的意义是对这个选项进行反义:所有的功能都可以用,除了这个。
警告:有的服务可能依赖某些功能,禁用后便无法使用该服务。当决定要设置一些功能时,你需要仔细确认是安全的。一个好的建议是与上游开发者进行沟通,你会知道哪些选项对服务来说是必须的。
警告2:Capabilities不是一个魔杖。你可能想与其他的安全选项绑定使用,以便更安全。
为了简单检查哪个进程保留了那些功能,可以使用libcap-ng-utils包里的pscap命令。
使用systemd的CapabilityBoundingSet=选项通常是一个简单有效的方式来替代三所有系统守护进程补丁对它们的功能进行限制。
资源限制可能会用于服务的安全限制。主要来说,资源限制对资源控制很有用,对于访问控制不会很多。然而,其中的两个选项可以有效禁止操作系统的特性:RLIMIT_NPROC 和 RLIMIT_FSIZE会用于禁止fork,并禁止写入文件。
...
[Service]
ExecStart=...
LimitNPROC=1
LimitFSIZE=0
...
注意,只有在有问题的服务放弃特权并且运行在非root用户下,或者通过设置CapabilityBoundingSet=放弃CAP_SYS_RESOURCE功能,的情况下有效。例如。没有这个,一个进程会简单增加资源的上限来避免被限制。
警告:LimitFSIZE=是很残忍的。如果服务尝试写入一个大于0的文件,会直接被SIGXFSZ干掉,除非被终止该进程。创建大小未0的文件是被允许的。
更多信息参考setrlimit(2)。
设备结点对于内核与驱动一个重要的接口。由于驱动一般测试与安全检查相比内核较少,通常是安全攻击的入口。systemd允许你未每个服务独立设置访问设备:
[Service]
ExecStart=...
DeviceAllow=/dev/null rw
这会限制访问/dev/null和这个设备节点,禁止访问其他设备结点。
这个特性是利用设备cgroup控制器实现的。
除了上面提到的简单使用的选项,哈有很多其他的安全相关选项。然而它们经常依赖服务本身的准备,并且主要对上游开发者有用。RootDirectory=选项(用于设置一个服务的chroot()环境)以及User=和Group=是用来指定用户和用户组。这些选项对简单的写守护进程尤其有用,把所有复杂安全问题丢给systemd,与守护进程本身无关。
如果你想知道这些选项为什么不会默认激活:有些选项会破坏传统UNIX的语法,并且为了保持兼容性,我们不会默认激活它们。例如:传统的Unix强制/tmp是一个共享的,进程可以用其作为IPC,我们不能只是因为/tmp被/run取代就从全局上关闭它。
以上是本文全部内容。如果你为发行版的上游工作,请考虑使用以上提到的选项。如果你的服务利用这些选项变的更安全了,不只是帮助你的用户,而且也让世界变的更美好了。
在systemd中最常用的命令之一就是用来显示服务状态的systemctl status
。它通常用来计算并显示进程运行时的信息,以及守护进程在运行时的元数据。
随着Fedora17引入journal,在systemd的系统上可以使用新的提供结构化,索引和可靠日志功能的日志系统。并且,它也提供了兼容经典syslog的实现。我们一开始想使用journal是源自一个特殊的特性,局外人看起来可能很简单,但是没有journal会很难达到这个效果:我们希望systemctl status
输出最近10行日志信息。日志数据是我们了解服务状态的最重要的信息。因此,显示服务的最新的状态是显而易见的。
现在简单来看下:我们同时把journal集成到systemd中,下面是一个输出的例子:
$ systemctl status avahi-daemon.service
avahi-daemon.service - Avahi mDNS/DNS-SD Stack
Loaded: loaded (/usr/lib/systemd/system/avahi-daemon.service; enabled)
Active: active (running) since Fri, 18 May 2012 12:27:37 +0200; 14s ago
Main PID: 8216 (avahi-daemon)
Status: "avahi-daemon 0.6.30 starting up."
CGroup: name=systemd:/system/avahi-daemon.service
├ 8216 avahi-daemon: running [omega.local]
└ 8217 avahi-daemon: chroot helper
May 18 12:27:37 omega avahi-daemon[8216]: Joining mDNS multicast group on interface eth1.IPv4 with address 172.31.0.52.
May 18 12:27:37 omega avahi-daemon[8216]: New relevant interface eth1.IPv4 for mDNS.
May 18 12:27:37 omega avahi-daemon[8216]: Network interface enumeration completed.
May 18 12:27:37 omega avahi-daemon[8216]: Registering new address record for 192.168.122.1 on virbr0.IPv4.
May 18 12:27:37 omega avahi-daemon[8216]: Registering new address record for fd00::e269:95ff:fe87:e282 on eth1.*.
May 18 12:27:37 omega avahi-daemon[8216]: Registering new address record for 172.31.0.52 on eth1.IPv4.
May 18 12:27:37 omega avahi-daemon[8216]: Registering HINFO record with values 'X86_64'/'LINUX'.
May 18 12:27:38 omega avahi-daemon[8216]: Server startup complete. Host name is omega.local. Local service cookie is 3555095952.
May 18 12:27:38 omega avahi-daemon[8216]: Service "omega" (/services/ssh.service) successfully established.
May 18 12:27:38 omega avahi-daemon[8216]: Service "omega" (/services/sftp-ssh.service) successfully established.
这里显示了每个人都喜爱的mDNS/DNS-SD的进程信息以及10行日志信息。
有一些命令行的可选项,可提供你需要的输出。两个最有趣的选项“-f和-n。-f是启动跟踪(tail -f),-n是显示行号(tail -n)
日志数据有三个来源:守护进程的所有日志都来自libc的syslog(),使用本地journal API,以及守护进程输出到STDOUT或STDERR的。简单来说,守护进程生成的日志会按照相同的格式交错着搜集起来。
这个是很简单的特性,但是对每个系统管理员来说十分有用。为什么十五年前我们没有这样干?
我们常听到关于systemd的抱怨是引导过程很难理解,甚至是无法理解。我不同意这个观点,并且相信事实是相反的:与之前的相比较——要理解会发生什么,你不得不去理解一门编程语言Bourne Shell——理解systemd的引导过程总体上简单很多。然而,正如很多抱怨所提到,确有一些不是很舒服的地方:对于经验丰富的UNIX管理员,确实需要学点东西才能切换到systemd上来。作为systemd的开发者,我们有责任降低学习难度,尽可能减少引入奇怪的东西,并且提供很好的文档。
systemd会提供很多文档,例如用户使用手册,wiki以及各种博文。然而,任何文档都不足以让一个软件容易理解。实际上,如果想在整个系统中找到感兴趣的那部分,大量的文档会让读者望而生畏。
我们现在已经在systemd中加了一个新的,优雅的小特性:自解释引导过程。这代表什么?简单来说,我们引导中的每个内容都有文档,并且其文档与其内容紧紧连在一起,所以很容易的就能发现。
进一步说,在systemd中所有的单元(那些封装在引导中的)现在都会包含其文档的引用,文档包括了配置文件和使用方面的手册。想了解一个单元的目的,如何适应引导过程以及如何配置,都可很简单的使用systemctl status
在文档中查到。例如查看systemd-logind.service:
$ systemctl status systemd-logind.service
systemd-logind.service - Login Service
Loaded: loaded (/usr/lib/systemd/system/systemd-logind.service; static)
Active: active (running) since Mon, 25 Jun 2012 22:39:24 +0200; 1 day and 18h ago
Docs: man:systemd-logind.service(7)
man:logind.conf(5)
http://www.freedesktop.org/wiki/Software/systemd/multiseat
Main PID: 562 (systemd-logind)
CGroup: name=systemd:/system/systemd-logind.service
└ 562 /usr/lib/systemd/systemd-logind
Jun 25 22:39:24 epsilon systemd-logind[562]: Watching system buttons on /dev/input/event2 (Power Button)
Jun 25 22:39:24 epsilon systemd-logind[562]: Watching system buttons on /dev/input/event6 (Video Bus)
Jun 25 22:39:24 epsilon systemd-logind[562]: Watching system buttons on /dev/input/event0 (Lid Switch)
Jun 25 22:39:24 epsilon systemd-logind[562]: Watching system buttons on /dev/input/event1 (Sleep Button)
Jun 25 22:39:24 epsilon systemd-logind[562]: Watching system buttons on /dev/input/event7 (ThinkPad Extra Buttons)
Jun 25 22:39:25 epsilon systemd-logind[562]: New session 1 of user gdm.
Jun 25 22:39:25 epsilon systemd-logind[562]: Linked /tmp/.X11-unix/X0 to /run/user/42/X11-display.
Jun 25 22:39:32 epsilon systemd-logind[562]: New session 2 of user lennart.
Jun 25 22:39:32 epsilon systemd-logind[562]: Linked /tmp/.X11-unix/X0 to /run/user/500/X11-display.
Jun 25 22:39:54 epsilon systemd-logind[562]: Removed session 1.
第一眼看去,文档的改变很少。如果你仔细查看,你会发现包含一个新的部分:Docs列举了文档的引用。这个例子中有两个用户手册页面URI和一个网页URL。这个用户手册描述了这个服务的目的和配置,网页URL包含了服务的基本内容。
如果用户使用图形的终端,点击URI可以直接显示该文档。换句话说:从没有这么简单的找到我们引导的特定内容:只是使用systemctl status
就可以获得相关信息,并且点击链接即可找到文档。
过去我已经写了用户手册并且为每个单元添加引用。这意味着使用systemctl status
,你可以很简单的发现核心系统的每个服务。
如果你没有使用图形终端,用户手册中的URI并不是很好用。为了容易阅读引用的手册我们也添加了一个新的命令:
systemctl help systemd-logind.service
这会打开一个用户手册列表,而不要点击任何URI。
URI是引用了http和https的URI,也引用了man和info的手册。
当然这不能让所有的东西自解释,简单来说,用户仍然需要找到关于systemctl status
(甚至是systemctl,这样才能知道都有那些单元)。这些简单的知识会帮助我们了解单元的详细内容。
我们希望这样运行时的内链以及文档可以为我们理解引导带来很大的进步。这样的在服务中引用帮助文档并不是新创造,在Solaris' SMF很早就已经有相似的功能。我们相信这个systemd特性确实在Linux上是全新的,并且会给你最好的文档以及自解释的init系统。
当然,如果你为自己的软件包写单元文件,请考虑包含服务的文档引用以及配置说明。这用起来很简单,只需要在[Unit]的Documentation=中列出URI即可。更多的可以参考systemd.unit。我们包括的链接越详细,管理员的工作越简单。
另外,如果你想找一个大概的系统引导过程的说明,我们有个新的页面可以参考。
systemd有三个主要的用户群:嵌入式/移动端的,桌面的以及服务器的。而嵌入式/移动端的系统有功率限制,并且资源有限,桌面则倾向更强大的机器,但仍然比服务器的资源少。这里有很多针对嵌入式以及服务器的特性,也包括桌面端。其中一个是在软件和硬件上对看门狗的支持。
嵌入式设备经常依赖硬件看门狗,帮助设备在软件停止响应后自动重置(更详细来说,停止信号仍然存活在固定的时间间隔中)。这需要增加可靠性并保证无论任何事情发生都可以让系统重新工作。
这样的功能对桌面没什么意义,然而在高可用的服务器上,看门狗经常使用。
systemd全面支持硬件看门狗(在用户空间中扩展了/dev/watchdog),以及对独立系统服务的监控(软件)看门狗。基本的理念是:如果激活,systemd会经常ping硬件看门狗。如果systemd或内核没有响应,硬件就会自动重置系统。这样systemd和内核就通过硬件被无形的保护起来。为了使链条完整,systemd为独立服务暴漏一个软件看门狗的接口,这样他们也可以重启动。软件看门狗可以在逻辑上独立配置每个服务ping的频率以及要做的动作。我们可以用这些(硬件看门狗监控systemd以及kernel,systemd监控所有其他服务)为系统每个部分提供可靠的看门狗。
为了使用硬件看门狗,必须要在/etc/systmd/system.conf中设置RuntimeWatchdogSec=选项。它默认是0(没有看门狗设备)。设置其为20s,看门狗就激活了。在20s后没有keep-alive的ping,硬件就会被重置。注意systemd会在间隔时间到一半时发送一个ping到硬件,比如每10s。通过这个单一简单的选项,你就可以利用硬件的监控。
注意硬件看门狗设备(/dev/watchdog)是单一用户。这意味着你可以在systemd中启动这个功能,也可以把看门狗进程分离出来
ShutdownWatchdogSec=是另一个可配置的选项。它可以控制在重启过程中看门狗的间隔。默认是10分钟,并且增加对系统重启逻辑的额外可靠性:如果一个正常的关闭与重启无法执行,我们依靠看门狗硬件对意外重置系统来重启。
这就是关于硬件看门狗的逻辑。使用硬件看门狗主要是使用这两个选项。现在我们看下如何为独立的服务增加看门狗。
首先,使用软件看门狗监控,需要在间隔期间发送”我还活着”信号。这个相对来说比较简单。第一,一个守护进程需要读 WATCHDOG_USEC=
环境变量。如果其设置了,它会用ascii字符格式来表示看门狗间隔的微妙数。这个守护进程会在每半间隔处调用sd_notify事件("WATCHDOG=1")。用这个方式的守护进程需要明确支持看门狗功能,通过检查是否设置了环境变量。
为一个服务激活软件看门狗逻辑需要为潜在的失败风险设置WatchdogSec=。查看systemd.service获得详细情况。这会导致服务进程设置WATCHDOG_USEC=,进而一旦没有收到keep-alive信号会导致服务进入一个失败状态,
如果一旦看门狗没有检测到反馈,一个进程就进入了失败状态,这很难可以构建一个可靠的系统。下一步是配置这个服务是否重启以及何时重启,如果继续失败应该如何处理。激活服务在失败时自动重启是设置Restart=on-failure。配置一个服务应该尝试重启多少次是联合设置StartLimitBurst=和StartLimitInterval=。它可以让你配置该服务在一个时间段内重启的频率。如果达到上限,会执行特殊的动作。该动作由StartLimitAction=配置。默认是none,表示没有进一步的动作,服务简单的保持失败的状态,不再尝试重启。另外三个可选的值是reboot,reboot-force和reboot-immediate。reboot尝试一个干净的重启过程,经历一个正常的关闭逻辑。reboot-force更突然,它不会慢慢的关闭那些服务,而是直接杀死所有服务,并卸载所有文件系统并强制重启(比正常reboot快)。最后,reboot-immediate不会尝试关闭进程与卸载文件系统,它会立即强制重启机器。因此reboot-immediate更像通过硬件看门狗来重启系统。详细文档参看systemd.service。
综合所有,我们现在有相当灵活的选项来利用看门狗监管一个服务,以及配置服务挂了后的自动重启,以及执行终极动作。
这里有个例子:
[Unit]
Description=My Little Daemon
Documentation=man:mylittled(8)
[Service]
ExecStart=/usr/bin/mylittled
WatchdogSec=30s
Restart=on-failure
StartLimitInterval=5min
StartLimitBurst=4
StartLimitAction=reboot-force
系统管理器在没有收到ping信号30s后,将会自动重启。如果在5分钟内重启超过4次,系统将会快速重启,文件系统将重新启动。
硬件看门狗支持PID 1,以及软件看门狗支持独立的服务,我们提供所有你需要的看门狗功能。无论你使用嵌入式或移动端的应用,或者你想工作在高可用的服务器上,请尝试一下这个!
(如果你想了解为什么PID 1需要处理/dev/watchdog,以及为什么不应该单独分离一个守护进程,请再次阅读一遍。我们建立了所有的超级监控工具链。硬件看门狗监控systemd,systemd监控独立的服务。我们也相信一个服务无法响应时,应该与其他服务进行一样的错误处理。最后,ping /dev/watchdog是系统中最平凡的事情之一。基本上比ioctl()还要多一点。为了支持这个不是少量代码能处理的。维护PID 1与看门狗守护进程之间的IPC是非常复杂,极易出错,而且资源敏感的)
注意内置硬件看门狗对systemd的支持默认不会与其他软件按看门狗混淆。systemd默认不会使用/dev/watchdog,如果你需要,可以与systemd一起使用额外的看门狗进程。
最后一件事:如果想知道你的硬件是否有看门狗,答案是最近这些年来大部分都有。如果你想确认这个,尝试wdctl工具。
使用一个串行控制台,只需要在内核命令行中使用console=ttyS0
,systemd会自动为你启动一个Getty。
RS232串口在当今PC上越来越流行,它在现代服务器以及嵌入式设备上一直以来都扮演很重要的角色。它提供一个相对稳定而轻量的对设备的访问接口。当网络不通常,或者UI界面无法响应时,它仍然会提供支持。虚拟化系统通常也会模拟一个串行端口。
当然,Linux一直都对串行控制台有很好的支持,在systemd上,我们尝试支持串行控制台更简单的使用。下面的文章我会介绍在systemd上如何使用串行控制台,以及如何处理TTY。
在大部分情况是下,得到一个login的提示符不需要你做额外的操作。systemd检查内核控制台的配置,并且会派生出一个串口接口。这样就可以恰当的配置内核控制台(例如,增加console=ttyS0到内核命令行中)。让我们看下细节:
在systemd中,有两个模板单元负责创建一个文本控制台:
1. getty@.service是负责虚拟终端(virtual terminal——VT)。例如你的VGA屏幕会暴露给有类似/dev/tty1的设备
2. serial-getty@.service是负责其他的终端,包括了串口端口例如:/dev/ttyS0。它与getty@.service有很多不一样的地方:首先$TERM环境变量是设置在vt102(对于大部分串行终端来说是一个好的默认项)而不是linux(只对于VT来说是合适的),并且VT的回滚缓存(只工作在VT上)被略过。
让我们看一下getty@.service如何启动,登陆提示符在虚拟终端中是如何工作的。传统上,Linux机器的init系统在启东时生成一个固定数字的登陆提示符。大部分情况下getty程序有6个实例,前六个是tty1到tty6。
在systemd的世界里,我们更加动态一些:为了更有效,登录提示符在启动时只是守护进程。当你切换到VT时,getty服务会实例化getty@tty2.service,getty@tty5.service等。既然我们不在需要启动getty进程,就会节省一些资源,让启动更快一些。这些对用户都是透明的,如果用户激活一个VT,getty就会立刻启动,用户很难发现getty并不是在一直运行。如果用户登陆进来,输入ps命令,就会注意到getty目前只运行了切换到VT后的这段时间。
默认情况下只会动态派生到VT6(为了接近传统的Linux默认配置)。注意gettys的自动派生只会在没有其他子系统占用VT的情况下。更详细来说,如果一个用户经常使用gnome的快速用户切换,会得到前六个VT的X sessions,因为最低可用的VT是需要给每个session都分配。
自动派生逻辑会特别的处理两个VT:第一个是tty1,如果我们启动图形模式,显示管理器会控制这个VT。如果我们从多用户(文本)模式启动,一个getty会在上面启动——不会按需分配。
第二个,tty6是专门用来接受自动派生的getty的,其他子系统无法访问它,例如X。这是为了保证总有一个接受文本登陆的地方,即使快速切换x会超过5VT。
在串行终端上处理登陆提示与其他的VT不同。默认下,如果不是虚拟终端,systemd会在主内核控制台实例化一个serial-getty@.service。内核控制台是内核输出日志信息的地方,在boot loader中的内核命令行上配置类似console=yytS0的参数。它保证当用户让内核重定向其输出到确定的串口终端,会在启动完成后自动获得一个登陆提示符。systemd会在第一个指定的VM控制台(dev/hvc0,/dev/xvc0/dev/hvsi0)派生一个登录提示符,如果运行在VM里的系统提供这些设备。逻辑的实现是一个叫做systemd-getty-generator的生成器。在引导的早期运行,并且拉取执行环境中必须的服务。
在很多例子中,这个逻辑已经可以满足按需分配了,不需要systemd额外配置什么。然而,有时也是需要配置一个串行getty。例如,如果需要多个登陆提示符或内核控制台重定向到不同的终端。你会想给每个串口都实例化一个serial-getty@.service。
# systemctl enable serial-getty@ttyS2.service
# systemctl start serial-getty@ttyS2.service
这样会保证你得到你所选择的端口,并且正确启动。
有时候,也需要更细致的配置登陆提示符。例如,如果默认波特率不正确,或者其他agetty程序需要改变。在这些例子中只需要简单的拷贝默认模板到/etc/systemd/system中即可:
# cp /usr/lib/systemd/system/serial-getty@.service /etc/systemd/system/serial-getty@ttyS2.service
# vi /etc/systemd/system/serial-getty@ttyS2.service
.... now make your changes to the agetty command line ...
# ln -s /etc/systemd/system/serial-getty@ttyS2.service /etc/systemd/system/getty.target.wants/
# systemctl daemon-reload
# systemctl start serial-getty@ttyS2.service
这创建了一个单元文件指向了串口ttyS2,你也可以指向其他的端口。
说了很多关于串口,VT和登录提示符的内容。我希望这会很有趣。
之前的博文,我介绍了一些日志的功能,以及如何使用systemctl。在这篇文章,我尝试解释一些Journal的用法。
简单解释一下Journal是什么:journal是systemd的一个部分,可以捕获syslog消息,kernal的日志信息,初始化RAM磁盘,早期的引导信息以及服务写入STDOUT/STDERR的信息。journal提供给用户这些信息的索引。它可以是并行的,或取代传统的syslog守护进程,例如rsyslog或人syslog-ng。更多信息参考the initial announcement
journal已经是Fedora的一部分了。现在Fedora 18上已经是一个相对稳定,强大的日志工具。注意在Fedora17和Fedora18上Journal默认配置存储日志是/run/log/journal中的一个小环缓冲器。当然这限制了它的用户粗,但足以显示最近的历史日志。对于Fedora 19,我们计划改为默认启用永久存储的日志。这样,journal文件会被存储在/var/log/journal,文件会变得很大,更方便使用。
我们可以手动激活日志的持续存储:
# mkdir -p /var/log/journal
执行该命令后,最好是重启一下,以便journal获得一些有用的结构数据。你已经使用了journal,那么就不再需要syslog了。那么你可以删除rsyslog。
我们开始学习Journal一些基础内容。访问日志,使用journalctl工具:
# journalctl
如果你使用root来运行,就会看到系统所有的日志,以及系统组件中以相同方式生成的用户登陆信息。输出信息与传统的日志类似的/var/log/messages格式,并且有一点改进:
注意为了简洁,本文不再说明这些输出是如何生成的。
浏览日志的方法已经很好了。但是依然会需要root,即使管理员大部分工作都在非特权用户。默认的journal用户只能看到该用户的日志,除了root或者在adm组中。为了监控系统日志,我们增加adm
# usrmod -a -G adm lennart
重新登陆后,lennart就可以访问全部的系统日志了,包括其他用户的。
$ journalctl
如果不带任何参数,journalctl会显示当前的日志数据。有时候我们希望可以监控日志的变化,就像我们常用的tail -f /var/log/messages
$journalctl -f
它会显示最近10行日志信息,并且实时更新。
当直接使用journalctl而不带其他参数时,会得到全部的日志,从最旧的日志开始。自然这会很多。而比较实用的是要显示当前的引导信息:
journalctl -b
这会显示当前的引导日志。但有时及时这样做信息也太多了,那么我们都关心哪些事件呢:所有ERROR级别的信息以及更高优先级的:
$journalctl -b -p err
如果你重启,-b显示当前的意义就不大了。根据时间来过滤可以:
$journalctl --since=yesterday
这样会显示昨天的所有日志。当然我们也会加入类似-p err或者其他的参数。我们想看一下发生在10月15日到16日的日志,该如何写呢?
$journalctl --since=2012-10-15 --until="2012-10-16 23:59:59"
这样我们找到了想要的东西。但是,我注意到一些在Apache的CGI脚本在早些时候启动了,我们来看下apache的日志:
$journalctl -u httpd --since=00:00 --until=9:30
好的,我找到了。但是好像磁盘/dev/sdc有点问题?让我们看下:
$journalctl /dev/sdc
OMG,一个磁盘错误。恩,让我们在数据丢失前赶快替换磁盘。恩,发现vpnc二进制有个错误?我们来检查一下:
$journalctl /usr/sbin/vpnc
恩,我不是很理解,貌似与dhclient有点关系,让他俩一起输出日志看看:
$journalctl /usr/bin/vpnc /usr/bin/dhclient
恩,搞定了。
systemd内部存储每个日志的入口,以及其隐式的元数据。这些元数据看着像一些环境变量的板块,但实际上更强大:值可以是是二进制,大数值(尽管这是个例外,通常只是包含UTF-8),以及域可以声明多种类型的值(另一个例外,通常他们只有一个)。这些隐式元数据在每个日志信息中都能搜集,无需用户干预。
如下:
$ journalctl -o verbose -n
[...]
Tue, 2012-10-23 23:51:38 CEST [s=ac9e9c423355411d87bf0ba1a9b424e8;i=4301;b=5335e9cf5d954633bb99aefc0ec38c25;m=882ee28d2;t=4ccc0f98326e6;x=f21e8b1b0994d7ee]
PRIORITY=6
SYSLOG_FACILITY=3
_MACHINE_ID=a91663387a90b89f185d4e860000001a
_HOSTNAME=epsilon
_TRANSPORT=syslog
SYSLOG_IDENTIFIER=avahi-daemon
_COMM=avahi-daemon
_EXE=/usr/sbin/avahi-daemon
_SYSTEMD_CGROUP=/system/avahi-daemon.service
_SYSTEMD_UNIT=avahi-daemon.service
_SELINUX_CONTEXT=system_u:system_r:avahi_t:s0
_UID=70
_GID=70
_CMDLINE=avahi-daemon: registering [epsilon.local]
MESSAGE=Joining mDNS multicast group on interface wlan0.IPv4 with address 172.31.0.53.
_BOOT_ID=5335e9cf5d954633bb99aefc0ec38c25
_PID=27937
SYSLOG_PID=27937
_SOURCE_REALTIME_TIMESTAMP=1351029098747042
使用-o verbose我们可以激活显示输出。不像经典的/var/log/messages只显示日志的一小部分,在journal我们可以看到所有日志的细节。但是有意思的是:也会显示用户证书信息,SELinux 位,机器信息等。更多细节参考手册。
现在,可以开启journal数据库来索引数据:
$ journalctl _UID=70
这会显示Linux用户ID为70的所有日志信息。并且很简单的可以联合显示日志:
$ journalctl _UID=70 _UID=71
指定两个相同域的匹配,会导致一个逻辑上的或,联合匹配。所有匹配的日志都会显示,例如所有UID为70和71的都会显示。
$ journalctl _HOSTNAME=epsilon _COMM=avahi-daemon
如果你指定了两个不同域的匹配,就会联合执行一个逻辑与。所有同时匹配了这两个的日志就会显示。例如这个会显示进程名为avahi-daemon以及主机名为epsilon。
但是,这没什么了不起。我们毕竟是电脑高手,我们要更牛逼!
$ journalctl _HOSTNAME=theta _UID=70 + _HOSTNAME=epsilon _COMM=avahi-daemon
+是表明一个或的逻辑,可以夹在两个匹配之间。这一行表示:显示主机theta的UID为70的所有日志,或者主机epsilon中进程名为avahi-daemon的日志。
下面更神奇的来了
上面说的都很牛是吧?但是谁能记住journal的所有的域名字呢?好吧,journal有这个:
$ journalctl -F _SYSTEMD_UNIT
这会显示给我们域_SYSTEMD_UNIT的在数据库里的值,换句话说:这是所有在journal中记录过的systemd服务的名字。这就让匹配变得很简单了。但是等下,显示这些还是需要绑定一些shell的技巧!这会更牛叉!之前你输入的可以得到所有的已知域名称。让我们看下如何过滤出带有SELinux标记的,尝试如下:
$journalctl _SE<TAB>
那么,它会直接自动补全
$ journalctl _SELINUX_CONTEXT=
酷,但是我们如何得知该匹配什么值呢?
$ journalctl _SELINUX_CONTEXT=<TAB><TAB>
kernel system_u:system_r:local_login_t:s0-s0:c0.c1023 system_u:system_r:udev_t:s0-s0:c0.c1023
system_u:system_r:accountsd_t:s0 system_u:system_r:lvm_t:s0 system_u:system_r:virtd_t:s0-s0:c0.c1023
system_u:system_r:avahi_t:s0 system_u:system_r:modemmanager_t:s0-s0:c0.c1023 system_u:system_r:vpnc_t:s0
system_u:system_r:bluetooth_t:s0 system_u:system_r:NetworkManager_t:s0 system_u:system_r:xdm_t:s0-s0:c0.c1023
system_u:system_r:chkpwd_t:s0-s0:c0.c1023 system_u:system_r:policykit_t:s0 unconfined_u:system_r:rpm_t:s0-s0:c0.c1023
system_u:system_r:chronyd_t:s0 system_u:system_r:rtkit_daemon_t:s0 unconfined_u:system_r:unconfined_t:s0-s0:c0.c1023
system_u:system_r:crond_t:s0-s0:c0.c1023 system_u:system_r:syslogd_t:s0 unconfined_u:system_r:useradd_t:s0-s0:c0.c1023
system_u:system_r:devicekit_disk_t:s0 system_u:system_r:system_cronjob_t:s0-s0:c0.c1023 unconfined_u:unconfined_r:unconfined_dbusd_t:s0-s0:c0.c1023
system_u:system_r:dhcpc_t:s0 system_u:system_r:system_dbusd_t:s0-s0:c0.c1023 unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
system_u:system_r:dnsmasq_t:s0-s0:c0.c1023 system_u:system_r:systemd_logind_t:s0
system_u:system_r:init_t:s0 system_u:system_r:systemd_tmpfiles_t:s0
太好了,我们想看一下在PolicyKit的安全标记的所有日志
$ journalctl _SELINUX_CONTEXT=system_u:system_r:policykit_t:s0
wow!这太简单了。我并不需要知道所有关于SELinux的内容。当然其他的标记也是可以使用的。
就这些内容了。还有很多关于journalctl的东西在这里。例如为你生成JSON的输出!你可以匹配内核域,你可以获得简单的/var/log/messages的输出,但是有时间戳!等等。