@Tmacy
2016-08-23T06:59:54.000000Z
字数 20232
阅读 1472
linux systemd
以下是systemd系列博客的翻译,博文为systemd的作者Lennart Poettering 所写,本译文仅供学习使用。
附录中有其他学习资料供参考
众所周知,systemd是自Fedora 14后,新的初始化系统(init system)。并且其他的发行版也会采用systemd(例如 OpenSUSE)。
对于管理员,systemd提供了各种新的特性和变化,并且大幅增强管理进程。这个博客是一系列文章的第一部分。我计划在接下来的几个月,每周都会发布文章。每次发布,我都会尝试解释systemd的一个新的特性。这些特性很多都小而简单,所以会有很多读者对此感兴趣。有时,我们也会介绍一点深奥的伟大的新特性提供给大家。
以前,当启动一个Linux 系统,你会看到很多简短的消息刷过你的屏幕。随着我们努力加速和并行化启动进程,这些消息刷过的速度会越来越快,很难读到其内容。如果要显示他们,我们可以使用图形引导启动技术,例如Plymouth。尽管如此,启动是屏幕上的信息是十分重要的,因为它告诉你每个服务在启动过程中的情况,启动成功或者失败(通过绿色的[ok]和红色的[failed])。为了让启动信息在快速的并行启动时更好的显示出来,我们让sysemd在整个启动和运行时期,跟踪和记录每个服务是否启动成功,是否会有返回非零的返回值,是否会超时,或者是否会异常终止(由于段错误或者类似的原因)。你只需要在shell中输入systemctl命令,即可查询到所有服务的状态,包括所有systemd
单元和SysV/LSB服务
[root@lambda] ~# systemctlUNIT LOAD ACTIVE SUB JOB DESCRIPTIONdev-hugepages.automount loaded active running Huge Pages File System Automount Pointdev-mqueue.automount loaded active running POSIX Message Queue File System Automount Pointproc-sys-fs-binfmt_misc.automount loaded active waiting Arbitrary Executable File Formats File System Automount Pointsys-kernel-debug.automount loaded active waiting Debug File System Automount Pointsys-kernel-security.automount loaded active waiting Security File System Automount Pointsys-devices-pc...0000:02:00.0-net-eth0.device loaded active plugged 82573L Gigabit Ethernet Controller[...]sys-devices-virtual-tty-tty9.device loaded active plugged /sys/devices/virtual/tty/tty9-.mount loaded active mounted /boot.mount loaded active mounted /bootdev-hugepages.mount loaded active mounted Huge Pages File Systemdev-mqueue.mount loaded active mounted POSIX Message Queue File Systemhome.mount loaded active mounted /homeproc-sys-fs-binfmt_misc.mount loaded active mounted Arbitrary Executable File Formats File Systemabrtd.service loaded active running ABRT Automated Bug Reporting Toolaccounts-daemon.service loaded active running Accounts Serviceacpid.service loaded active running ACPI Event Daemonatd.service loaded active running Execution Queue Daemonauditd.service loaded active running Security Auditing Serviceavahi-daemon.service loaded active running Avahi mDNS/DNS-SD Stackbluetooth.service loaded active running Bluetooth Managerconsole-kit-daemon.service loaded active running Console Managercpuspeed.service loaded active exited LSB: processor frequency scaling supportcrond.service loaded active running Command Schedulercups.service loaded active running CUPS Printing Servicedbus.service loaded active running D-Bus System Message Busgetty@tty2.service loaded active running Getty on tty2getty@tty3.service loaded active running Getty on tty3getty@tty4.service loaded active running Getty on tty4getty@tty5.service loaded active running Getty on tty5getty@tty6.service loaded active running Getty on tty6haldaemon.service loaded active running Hardware Managerhdapsd@sda.service loaded active running sda shock protection daemonirqbalance.service loaded active running LSB: start and stop irqbalance daemoniscsi.service loaded active exited LSB: Starts and stops login and scanning of iSCSI devices.iscsid.service loaded active exited LSB: Starts and stops login iSCSI daemon.livesys-late.service loaded active exited LSB: Late init script for live image.livesys.service loaded active exited LSB: Init script for live image.lvm2-monitor.service loaded active exited LSB: Monitoring of LVM2 mirrors, snapshots etc. using dmeventd or progress pollingmdmonitor.service loaded active running LSB: Start and stop the MD software RAID monitormodem-manager.service loaded active running Modem Managernetfs.service loaded active exited LSB: Mount and unmount network filesystems.NetworkManager.service loaded active running Network Managerntpd.service loaded maintenance maintenance Network Time Servicepolkitd.service loaded active running Policy Managerprefdm.service loaded active running Display Managerrc-local.service loaded active exited /etc/rc.local Compatibilityrpcbind.service loaded active running RPC Portmapper Servicersyslog.service loaded active running System Logging Servicertkit-daemon.service loaded active running RealtimeKit Scheduling Policy Servicesendmail.service loaded active running LSB: start and stop sendmailsshd@172.31.0.53:22-172.31.0.4:36368.service loaded active running SSH Per-Connection Serversysinit.service loaded active running System Initializationsystemd-logger.service loaded active running systemd Logging Daemonudev-post.service loaded active exited LSB: Moves the generated persistent udev rules to /etc/udev/rules.dudisks.service loaded active running Disk Managerupowerd.service loaded active running Power Managerwpa_supplicant.service loaded active running Wi-Fi Security Serviceavahi-daemon.socket loaded active listening Avahi mDNS/DNS-SD Stack Activation Socketcups.socket loaded active listening CUPS Printing Service Socketsdbus.socket loaded active running dbus.socketrpcbind.socket loaded active listening RPC Portmapper Socketsshd.socket loaded active listening sshd.socketsystemd-initctl.socket loaded active listening systemd /dev/initctl Compatibility Socketsystemd-logger.socket loaded active running systemd Logging Socketsystemd-shutdownd.socket loaded active listening systemd Delayed Shutdown Socketdev-disk-by\x1...x1db22a\x1d870f1adf2732.swap loaded active active /dev/disk/by-uuid/fd626ef7-34a4-4958-b22a-870f1adf2732basic.target loaded active active Basic Systembluetooth.target loaded active active Bluetoothdbus.target loaded active active D-Busgetty.target loaded active active Login Promptsgraphical.target loaded active active Graphical Interfacelocal-fs.target loaded active active Local File Systemsmulti-user.target loaded active active Multi-Usernetwork.target loaded active active Networkremote-fs.target loaded active active Remote File Systemssockets.target loaded active active Socketsswap.target loaded active active Swapsysinit.target loaded active active System InitializationLOAD = Reflects whether the unit definition was properly loaded.ACTIVE = The high-level unit activation state, i.e. generalization of SUB.SUB = The low-level unit activation state, values depend on unit type.JOB = Pending job for the unit.221 units listed. Pass --all to see inactive units, too.[root@lambda] ~#
(我已经精简了部分与本文无关的输出信息。)
ACTIVE一栏显示了一个服务(或者各种systemd单元)的高级状态,会有例如active(运行),inactive(不运行)或者其他几个状态。如果你仔细看,会发现列表中有的项目被红色标记为maintenance。这意味着一个服务运行失败或者遇到一个问题。在这个例子中时ntpd,我们来看看htpd发生了什么事情。使用systemctl status命令:
[root@lambda] ~# systemctl status ntpd.servicentpd.service - Network Time ServiceLoaded: loaded (/etc/systemd/system/ntpd.service)Active: maintenanceMain: 953 (code=exited, status=255)CGroup: name=systemd:/systemd-1/ntpd.service[root@lambda] ~#
这显示了NTP在运行时终止了(当前的进程id为953),并且告诉我们错误状态:进程退出,退出状态代码255
在最新的systemd版本中,我们计划将信息挂在ABRT上,尽快完成这个需求。如果systemctl status 显示了一个服务的崩溃信息,它会立刻指向ABRT的相对应的崩溃日志
使用systemctl和systemctl status完全替代传统的sysV 服务启动信息。
systemctl status 不只是捕捉错误状态的细节,并且除了启动时的错误信息外,也会显示运行期间错误。
这是这周的内容,下周会再回来,提供关于systemd的管理员博文!
在大部分Linux系统上,默认运行的进程数量是巨大的。越来越难以了解哪个进程在做什么以及它属于哪里。有的服务甚至保留一堆工作进程,导致ps命令输出十分混乱,难以辨认。更复杂的情况是,守护进程大量产生任意的第三方进程,例如apache与CGI进程协同工作,或者cron进程处理用户的作业任务。
一个常见的简单补救措施是进程继承树,利用命令ps xaf。然而当父进程结束,子进程被PID 1进程所收养时,这个方法并不可靠,此后所有的继承信息都会丢失。如果一个进程“两次fork”后,会无法找到启动它的那个进程的信息(按照传统的UNIX 守护逻辑,这应该是个特性)。进一步来说,进程可以利用PR_SETNAME或者patching argv[0]来自由的修改它的名称,这样就更难辨认它们。实际上,它们可以很好的利用这一点来和管理员捉迷藏。
在systemd中,我们把每个进程放置在以产生它的服务命名的control group(Cgroups)中。最基本上来说,control groups(cgroups)是一种简单的进程组,按照继承关系排列并且单独标记。当进程大量产生其他子进程时,这些子进程自动被设为父进程的cgroups的成员。对于一个无特权的进程,是不可能离开一个cgroup的。这样,cgroups就可以有效标记进程的所属服务,以及确认进程无法摆脱该标记,无论该进程是否fork或者重命名自己。此外,这可以安全的关掉一个服务和它所产生的进程。
今天我会介绍给大家两个有用的命令,涉及到systemd的服务与进程。第一个是著名的ps,可以显示cgroup的信息和其他进程的细节。
$ps xawf -eo pid,user,cgroup,argsPID USER CGROUP COMMAND2 root - [kthreadd]3 root - \_ [ksoftirqd/0]5 root - \_ [kworker/0:0H]7 root - \_ [rcu_preempt]8 root - \_ [rcu_sched]9 root - \_ [rcu_bh]10 root - \_ [migration/0]11 root - \_ [watchdog/0]12 root - \_ [watchdog/1]13 root - \_ [migration/1]14 root - \_ [ksoftirqd/1][...]1 root 7:devices:/init.scope,6:pid /sbin/init splash186 root 7:devices:/system.slice/sys /usr/lib/systemd/systemd-journald230 root 7:devices:/system.slice/sys /usr/lib/systemd/systemd-udevd487 root 7:devices:/system.slice/hom /sbin/mount.ntfs /dev/sda2 /home/tmacy/win -o rw506 root 7:devices:/system.slice/Mod /usr/bin/ModemManager508 root 7:devices:/system.slice/org /usr/bin/cupsd -l510 dbus 7:devices:/system.slice/dbu /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation520 root 7:devices:/system.slice/sys /usr/lib/systemd/systemd-logind523 root 7:devices:/system.slice/Net /usr/bin/NetworkManager --no-daemon1262 root 7:devices:/system.slice/Net \_ /usr/bin/dhclient -d -q -sf /usr/lib/networkmanager/nm-dhcp-helper -pf /var/run/dhclie527 root 7:devices:/system.slice/cro /usr/bin/crond -n529 avahi 7:devices:/system.slice/ava avahi-daemon: running [tmacy-pc.local]537 avahi 7:devices:/system.slice/ava \_ avahi-daemon: chroot helper
(输出信息被缩减)
在第三列,你会看到cgroup systemd被分配给每个进程。你会发现udev进程在7:devices:/system.slice/syscgroup
我个人建议设置一个shell的别名:psc
alias psc='ps xawf -eo pid,user,cgroup,args'
显示进程的服务信息只需要四个按键!
另一种显示相同内容的方式是systemd-cgls工具。它包括在systemd的工具集中。
$ systemd-cglsControl group /:-.slice├─init.scope│ └─1 /sbin/init splash├─system.slice│ ├─home-tmacy-win.mount│ │ └─487 /sbin/mount.ntfs /dev/sda2 /home/tmacy/win -o rw│ ├─avahi-daemon.service│ │ ├─529 avahi-daemon: running [tmacy-pc.local│ │ └─537 avahi-daemon: chroot helpe│ ├─dbus.service│ │ └─510 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation│ ├─org.cups.cupsd.service│ │ └─508 /usr/bin/cupsd -l│ ├─ModemManager.service│ │ └─506 /usr/bin/ModemManager│ ├─wpa_supplicant.service│ │ └─728 /usr/bin/wpa_supplicant -u│ ├─systemd-journald.service│ │ └─186 /usr/lib/systemd/systemd-journald│ ├─ntpd.service│ │ └─650 /usr/bin/ntpd -g -u ntp:ntp│ ├─udisks2.service│ │ └─907 /usr/lib/udisks2/udisksd --no-debug│ ├─upower.service│ │ └─876 /usr/lib/upower/upowerd│ ├─systemd-logind.service│ │ └─520 /usr/lib/systemd/systemd-logind│ ├─systemd-udevd.service
如你所见,这个命令按照进程所在cgroup和其所在服务来显示。
如果你观察的仔细,会注意到一些进程被分配到cgroup /usr/1.这里我们简单说systemd不只是在cgroups中维护服务,而且维护着用户会话进程。在稍后的文章中我们会仔细讨论这一点。
传统上,Unix和Linux服务(守护进程)是通过SysV的init脚本来启动的。这些是bash脚本,通常在/etc/rc.d/init.d/目录中,通常利用几个标准命令,来调用,例如start stop 或者 restart。启动通常是调用了守护进程的二进制程序,它和fork一个后台进程(更精确称为daemonizes)。Shell脚本一般都很慢,难以读懂,冗长而脆弱。尽管shell脚本比较灵活(毕竟脚本就是代码),但是有的情况很难用脚本去处理,例如打理并发,正确的监控进程或者只是详细的配置执行过程。systemd兼容了这些shell脚本,但是由于以上缺点,还是推荐安装原生systemd服务。而且,与那些为了兼容SysV init脚本的服务相比,systemd服务可以更好的分布式运行。下面介绍如何将一个SysV init脚本转换为原生systemd服务。理想情况下,upstream项目会在他们的压缩包中集成和安装systemd服务。如果你按照教程成功转化了SysV 脚本,最好是提交一个补丁到upstream中。如何准备一个补丁会在稍后介绍,集成在systemd 中的daemon手册包含了很多有用的信息。
那么,从一个ABRT daemon的转化为例。ABRT是所有的Fedora都预装的,是一个自动bug报告工具。是一个收集服务的崩溃信息的工具。
转化的第一步就是去读这个脚本(震惊了吧?)并且在这冗长的脚本中提取有用的信息。几乎所有类似的脚本都包括了模板代,所以init脚本都比较相似,通常都是从一个复制粘贴到另一个中。那么我们一起从这些脚本中提取有用的信息。
这些准备就绪,整个115行的shell脚本的其他内容都是多余的。有些代码是处理同步和串行启动的,或者一些状态信息,或者简单的解析动词(大的case 代码块)
根据以上的分析,我们可以写出自己的systemd服务:
[Unit]Description=Daemon to detect crashing appsAfter=syslog.target[Service]ExecStart=/usr/sbin/abrtdType=forking[Install]WantedBy=multi-user.target
对于文件的解释:[Unit]字段包括了一个服务常规信息。systemd不只是管理系统服务,也包括了设备,挂载点,计时器以及其他系统内容。这些systemd的对象通用内容就是[Unit],其他的systemd对象也会有[Unit]字段。这个例子中我们设置内容如下:我们设置描述,配置守护进程在syslog之后启动。这与shell脚本的LSB的头比较类似。对于Syslog的依赖,我们创建一个After=的依赖,其值就是一个systemd 单元:syslog.target。这是systemd的特殊的目标单元,是进入syslog实现的标准名称。标准名称的详细信息参看:systemd.spcial(7)。注意依赖类型After=只能按照推荐的顺序,但实际上abrtd运行不会导致syslog启动。abrtd实际上即使没有syslog也可以运行良好。然而,如果他们都启动(实际上通常这样),他们的启动顺序就会按照这个依赖而定。
下一个字段[Service]包含了服务本身的信息。它包含了所有应用与服务的信息,而不会用于其他systemd对象(挂载点,设备,计时器,。。。)。这里有两个设置:ExecStart=设置了二进制程序的路径,Type=设置了当服务启动后,如何通知init系统。而传统上Unix守护进程会在fork后返回父进程,并且按照fork时的类型初始化后台守护进程。它告诉systemd等待二进制启动程序的返回,然后会考虑在守护进程启动后,进程是否还在运行。
最后的[Install]字段推荐我们如何安装。例如,在何种环境下和那些服务应该被触发启动。这个例子我们简单的说,这个服务在multi-user.target单元被激活后启动。这是个特殊的单元,之前说过它代表了SysV 的Runlevel 3。WantedBy= 对守护进程的启动几乎没有影响。它只会被systemctl enable命令读取。这个命令是systemd的使能命令,简单保证了一旦mulit-user.target启动,我们的服务会自动的被激活。
现在我们有一个最小systemd服务文件。为了测试,我们拷贝到/etc/systemd/system/abrtd.service,并且执行systemctl daemon-reload。这会让systemd注意到它,并且我们可以用它来启动服务:systemctl start abrtd.service,我们可以用systemctl status abrtd.service来确认服务启动。并且我们可以用systemctl stop abrtd.service开停止服务。最后,我们可以激活它,它会在系统启动时自启动:systemctl enable abrtd.service
以上脚本还有提升空间,我们来作一点升级:
[Unit]Description=ABRT Automated Bug Reporting ToolAfter=syslog.target[Service]Type=dbusBusName=com.redhat.abrtExecStart=/usr/sbin/abrtd -d -s[Install]WantedBy=multi-user.target
我们做的升级是:
我们改进了描述信息。更进一步,我们把服务的类型改为dbus,配置D-Bus服务的总线名称。为什么我们这样做?之前提到经典SysV系列服务在启动后变为守护进程,通常涉及到从任何终端中双forking和分离。然而当守护进程通过脚本被调用时,这会很有用也很必要。但是对于例如systemd这样的进程管理者来说,这不必要而且适得其反。原因是fork得到的守护进程通常与原始进程无关(毕竟守护进程的意图就是摆脱那些关系),这就很难让systemd去分辨服务中fork后的进程的主进程是谁,哪些进程只是辅助进程。但是上述信息最重要的是实现一个高级的进程管理者,例如监控进程,自动回收异常进程,回收垃圾以及退出代号信息等。为了让systemd更容易的分辨守护进程的主进程,我们把服务类型改为dbus。服务类型的语义是在服务初始化的最后为所有在D-Bus总线系统的服务起一个合适的名称。ABRT是其中一个。通过此设置,systemd会产生一个不再fork的ABRT进程(通过-d -s 来切换到守护状态),systemd会让服务完全启动,一旦com.redhat.abrt出现在总线中。被systemd启动的进程就是守护进程的主进程,systemd会很容易判断守护进程何时完全启动并且很容易的监控进程。
就是这样,我们有一个简单的systemd 服务文件,大概十行左右的信息。却SysV init 脚本的115行的信息更丰富。并且即使现在,利用systemd的更多的特性仍然可以有很大的提升。例如我们可以设置Restart=restart-always来告知systemd当服务挂掉后,重启它。或者我们用OOMScoreAdjust=-500来请求内核当OOM killer 打开杀戒时,离开这个进程。或者我们利用CUPSchedulingPolicy=idle来保证abrtd进程只在后台crash dumps,总是允许内核优先被cpu运行
对于更详细的配置信息和选项,可以查看man : systemd.unit(5), systemd.service(5), systemd.exec(5)。或者浏览所有的man手册。
并不是所有的脚本都如此简单的转化为systemd的服务。很高兴的是,大部分是如此的。
关闭一个systemd的守护进程是很简单的么?或者并不是?
当然,只要你的守护进程是单进程,这肯定是简单的事情。你可以输入killall rsyslogd,syslog守护进程就关闭了。然而这样做会有一点不妥,像这样调用可能会杀死所有恰好以其命名的进程,包括那些不幸被人偶然以这样的名字来命名的程序。一个简单的正确方式是读取.pid文件,例如:
kill `cat /var/run/syslogd.pid`
这确实能解决问题,但是真的是我们想要的么?
考虑一个服务,比如Apache,或者cornd,或者通常来产生子进程的atd。用户配置子进程是随意的,例如cron,at作业,或者CGI脚本,甚至全部的应用服务。如果你关闭apache/crond/atd的主程序,并不一定会关闭所有子进程,这取决于这些进程是否想继续运行还是关闭。关闭Apache很大可能导致CGI脚本继续运行,并被init收养,很难追踪了。
使用systemd kill 你可以轻松发送一个信号到这个服务的所有进程。
systemctl kill crond.service
SIGTERM会被送到crond服务的所有进程,不只是主进程。当然,你也可发送其他信号。例如,你想发送一个SIGKILL:
systemctl kill -s SIGKILL crond.service
就这样,服务会被全部关闭,无论它有多少fork,无论试图利用双fork或者fork炸弹来逃避监管。
有时,你只是给一个服务的主进程发送一个指定的信号,可能因为你想通过SIGHUP来重载进程。我们可以这样做:
systemctl kill -s HUP --kill -who=main crond.service
那么,在systemd中关闭服务有什么新奇的地方么?恩,第一次在Linux上正确的做这个操作。先前的方案总会依靠守护进程在被关闭时,协助关闭它自己创建的所有子进程。然而,通常如果你想使用SIGTERM或者SIGKILL并不会有很好的作用。
systemctl stop 与这个有什么不同?kill会直接发送给服务里所有进程一个信号,而stop通过一个正式的关闭方式来关闭服务。比如stop会调用配置中的ExecStop=的命令。通常stop足够用了。kill是更野蛮的版本,比如你不想使用正式的命令,或者当服务莫名挂起。(你可以指定信号的名称,不是用SIG的前缀)
有点奇怪,到目前为止我们从来没有在Linux上正确的杀死服务。systemd也许会帮你第一次实现这个。
在systemd中,关闭一个服务(或者一个单元)有三个层次。
systemctl stop ntpd.service
这个与下面传统命令方式效果一样。大部分SysV系统这样做:
service ntpd stop
实际上,Fedora15上,你使用后面一种方式,会被转换为前一种方式。
systemctl disable ntpd.service
传统上,上面的命令等于:
chkconfig ntpd off
当然,在Fedora15上,后一个命令会转化为systemd的命令。
通常,你想同时stop和disable一个服务,立刻关闭服务实例,并且确保不再会运行,可以这样做:
systemctl disable ntpd.servicesystemctl stop ntpd.service
类似上面的命令,在Fedora上卸载软件包时会使用。
Disable一个服务时永久的改变,重启后生效。
3. 你可以maks一个服务,这很像disable,但是更强大。不只是保证服务不会自动启动,并且保证手动也不可以启动服务。这在systemd中是隐藏的特性,不会经常用到,而且可能会让使用者困惑。像这样:
ln -s /dev/null /etc/systemd/system/htpd.servicesystemctl daemon-reload
通过将/dev/null软链接到一个服务文件上,意味着systemd永远不会启动这个服务并且阻止其运行。单元文件存储在/etc/systemd/system中,与/lib/systemd/system中的文件名称相同。前面的目录是管理员所涉及的,后面的目录是包管理所涉及的。通过在/etc/systemd/system/ntpd.service 安装你的软链接,确保systemd永不会读上游封装的服务文件/lib/systemd/system/ntpd.service
systemd会辨认出软链接到/dev/null,并且认为他们被屏蔽了。如果你尝试手动启动这个服务(systemctl start),会失败并报错。
在SysV系统中类似的做法(正式的)是不存在的。而非正式的做法,例如编辑init脚本并且在开头写exit 0,或者使其不能被执行。然而这些解决方法都有缺点,例如他们会干涉到包管理。
屏蔽(masking)一个服务是永久的,类似disable
现在我们了解了在三个层面上关闭服务。只剩下一个问题:如何再次开启他们?
使用systemctl start来处理systemctl stop。
使用systemctl enable来处理systemctl disable。
删除那个软链接。
就是这些!
作为系统管理员或者开发者,你迟早会遇到chroot()环境。chroot()系统调用简单的迁移了一个进程与其子进程迁移的根目录,这限制了一个进程可以看到的目录层次。chroot 环境的主要作用有两个:
1. 出于安全目的:这种用法中,一个孤立的守护进程chroot到一个私有目录层次,因此暴露在攻击者面前的是私有子目录,而不是完整的操作系统层次。
2. 为了设置或者控制调试,测试,编译,安装或者恢复系统镜像:整个来宾操作系统层次挂载或引导在主操作系统的一个子目录中,这个子目录会被当做/(根目录),并且在里面启动一个shell(或者其他程序)。对于shell,就好像运行在一个与主系统有很大不同的内部系统。例如,可以运行不同的发行版,不同的架构(例如主系统x86_64,来宾是i386)。它无法发现主系统的目录层次。
在经典的System-V的操作系统中,使用chroot环境比较容易。例如,为了测试或者其他原因在基于chroot的来宾系统中启动指定的守护进程,挂载了/proc,/sys和其他API文件系统,然后利用chroot进入chroot环境,然后在里面用/sbin/service来运行SysV init脚本。
在systemd系统中,事情就不在简单了。systemd的其中一个优势是所有的守护进程保证在一个完全干净而独立的上下文中启动,这个上下文环境不会因为用户启动服务而受到干扰。而在sysvinit系统中,一大部分的执行上下文(例如资源限制,环境变量等)是从用户的shell中继承来的。在systemd,用户只是通知了init守护进程,并且init守护进程会在一个理智的,严格定义的崭新上下文中fork出这个守护进程,并且没有继承任何用户上下文。这个强大的特性实际上打破了在chroot环境中调用一个服务的传统做法:实际的守护进程总是从PID1进程中孵化出来,并且继承了chroot的设置,而请求守护进程的客户端是否已经chroot却无关紧要。最重要的是,既然systemd实际上将它的本地通信socket放在/run/systemd,在chroot环境中的进程根本无法与init系统通信。(这可能是件好事,可以利用bind mount来大胆解决这个)
当然在systemd环境中如何恰当的使用chroot()是个开放的问题。我们在这里和你一起,期待这个问题的彻底完全的解答。
让我们来看下第一个案例:在chroot中处于安全目的而锁住一个守护进程。一开始,chroot用于安全工具是没有把握的,毕竟chroot不是单向的。很容易就可以从chroot环境中逃逸,正如用户手册中所指出的那样。只有与几个技术绑定后才能算得上一定程度的安全。由于这个原因,它通常需要应用程序特殊的支持来无干扰的进入chroot环境。最重要的是,它常常需要一个深度理解chroot的服务恰当的建立chroot环境,例如为了让必备服务的所有通信频道在chroot环境中都可用,要知道哪些目录需要从主系统中绑定挂载。综合来看,chroot的安全软件在C写的守护进程中表现最好。开发者很清楚知道(或者应该清楚知道)如何正确安全的chroot,并且在chroot中需要的最小的文件集,文件系统以及目录都是哪些。这些天,大量的守护进程有能力做到这个,不幸的是默认运行在通用Fedora安装程序中的守护进程,只有两个能够做到:Avahi和RealtimeKit。显然,它俩有相同的作者。(验证这点很简单,只需要运行ls -l /proc/*/root)
由上述得知,systemd提供了一种chroot指定守护进程的方法,并且很方便的管理他们。在systemd的服务文件中,有RootDirectory=选项。实例如下:
[Unit]Description=A chroot()ed Service[Service]RootDirectory=/srv/chroot/foobarExecStartPre=/usr/local/bin/setup-foobar-chroot.shExecStart=/usr/bin/foobardRootDirectoryStartOnly=yes
在这个例子中,RootDirectory=配置了在运行由ExecStart=定义的守护进程程序前,在哪个目录里chroot。注意ExecStart=指定的路径是chroot环境中的路径,不是主系统中的路径(例如在主系统中,其路径是/srv/chroot/foobar/usr/bin/foobard)。在守护进程启动之前,一个shell脚本setup-foobar-chroot.sh会被调用,其目的就是要设置chroot的必备环境,例如mount /porc等类似服务所必备的操作。RootDirectoryStartOnly这个选项确保只有在ExecStart=中指定的守护进程能被chroot,而不是ExecStartPre=中需要主系统环境的脚本(更多信息可参考用户手册)。如果你有一个单元文件:/etc/systemd/system/foobar.service。你可能利用systemctl status foobar.service来查看状态。这对管理员来说,chroot的进程与其他的服务进程没什么区别,而SysV却不一样。这不会改变你的监控与控制工具与它的交互。
新的Linux内核支持文件系统的命名空间。这个有点类似chroot,但是更加强大,并不会像chroot那样受到安全问题的困扰。systemd可以在单元文件中提供文件系统命名空间中的部分功能。通常它们可以简单方便地在子目录中设置chroot环境。利用ReadOnlyDirectories=和InaccessibleDirectories=,你可以为服务设置一个文件系统的命名空间监狱。起初,它与你主机系统的文件系统是一样的。通过列出指令中的目录,你可以确认有哪些目录以及在主机系统上的挂载点,可以设置为只读或甚至对守护进程\x0d\x0a完全无法访问。例如:
[Unit]Description=A Service With No Access to /home[Service]ExecStart=/usr/bin/foobardInaccessibleDirectories=/home
这个服务被允许访问主机系统的整个文件系统,除了一个例外:/home。这可以用来保护用户数据。(详情查看用户手册)
文件系统的命名空间实际上是chroot的更好的替代品。最后,Avahi和RealtimeKit应该升级使用namespaces来替代chroot。
写了很多安全方面的实例,现在我们看一下其他实例:设置启动和控制OS镜像,用来条是,测试,编译,安装和恢复。
chroot环境涉及的事情比较简单:它们只是文件系统的虚拟化。通过chroot到一个子目录中,一个进程仍然可以访问到所有系统调用,可以杀死所有进程以及共享所有主机系统运行的东西。例如,如果你在chroot的环境里升级系统,软件包脚本会发送一个SIGTERM信号到PID 1来触发init系统再执行( trigger a reexecution of the init system),这实际上会影响到主机系统。最重要的是,SysV共享了内存,抽象的namespace sockets并且其他的IPC元语也被共享。可能一个用来测试,条是,编译,安装或恢复系统的完全安全的隔离区不是必须的,但一个基本的用来避免对主机系统意外修改的隔离是有很大需求的。你不会知道代码脚本包的执行会对主系统带来什么。
为了处理chroot用于这方面的设置,systemd提供给大家一系列的特性:
第一,systemctl会探测何时会运行在一个chroot中。如果是在chroot中,其大部分的操作会变为空操作,除了systemctl enable和systemctl disable以外。如果一个软件包安装脚本调用这两个命令,服务会在来宾系统中启动。然而一个软件包安装脚本的软件升级部分包括了类似systemctl restart的命令,在chroot环境中将不会有效果。
更重要的是,systemd利用systemd-nspawn来实现chroot(1)的行为:它利用文件系统和PID命名空间来启动一个轻量级容器。它几乎就可用作chroot(1),但隔离区并非更彻底,更安全以及易用。事实上,systemd-nspawn是利用单一的命令,在容器中启动一个完整的systemd或sysvinit系统。既然它虚拟化了PID,init系统在容器中的行为更像PID1。与chroot(1)不一样的是,它自动会帮你挂载/proc,/sys。
这里有个例子,展示了如何利用三行命令在你的Fedora里启动一个Debian系统:
yum install debootstrapdebootstrap --arch=amd64 unstable debian-tree/systemd-nspawn -D debian-tree/
这会启动操作系统目录树,并简单在其中调用shell。如果你想在容器中启动全部系统,使用如下命令:
systemd-nspawn -D debian-tree/ /sbin/init
在快速启动后,你会得到一个shell提示符,一个完整的系统在容器中启动过了。这个容器不能看到外面的所有进程。它会共享网络配置,但是无法修改它(除了在启动中几个无关紧要的EPERM)。像/sys和/proc/sys/这样的目录是可以在容器中看到的,但只会以只读的形式挂载到容器中,防治修改内核以及硬件配置。注意这种保护只会防治对主机系统的意外参数的变化。一个容器中的进程可以手动再挂载文件系统为可读可写,然后为所欲为。
那么 systemd-nspawn有什么厉害的地方呢?
1. 很易用。不需要手动挂载/proc和/sys/到你的chroot环境中。这个工具会自动为你完成,并且当容器退出,内核会自动清理。
2. 隔离区会更完全,来保护主机系统不被容器中的变动所影响。
3. 你可以在容器中启动一个完整的系统,而不是单个的shell进程。
4. 它很小巧并且很容易在安装了systemd的系统中使用,安装过程不复杂。
systemd本身在容器中工作的很好。例如,当关机时并检测到其运行在一个容器中,它会调用exit()而不是reboot()。
注意,systemd-nspawn不是一个完整的容器方案。如果你需要,LXC会更适合你。它使用了类似的内核技术,但是会更加强大,包括了网络的虚拟化等。如你所愿,systemd-nspawn是GNOME3的容器方案:简单易用,只有几个配置选项。LXC OTOH更像KDE,比代码多的多的配置选项。我写systemd-nspawn只为测试,调试,编译,安装和恢复。这是你用其来替代chroot(1)真正的原因所在。
本文要点如下:
systemd的参考资料
1. Systemd与SysVinit的对比图
2. 利用systemd来管理Linux系统
3. systemd-nspawn 快速指南
4. 关于systemd的争议
5. systemd十分钟教程