Systemd 进程管理相关

Systemd 进程管理相关

服务进程管理

本节将从service单元文件的编写(基础选项、起停及重载选项、依赖性处理选项,资源限制选项等),对服务进程基本操做,进程和进程组的查看和如何更好的杀死服务进程 四个方面介绍systemd下服务进程的管理。php

service单元文件

systemd 的单元文件是受 XDG Desktop Entry .desktop 文件启发而产生,而最初起源是 Windows 下的 .ini 文件。 .service 文件是systemd的本地配置文件,相似于 sysvinit 中的 /etc/init.d 里脚本的做用。html

一个.service 文件必定包含三个Sections,分别是[Unit]、[Service]、[Install]。每一个Section有不一样的Key,例如Unit常见 有Description、Documentation、Requires等,Service常见有Type等,Install常见有Alias、 WantedBy=、RequiredBy等,具体查看 man systemd.unit,以及 man systemd.servicenode

systemd单元文件的加载路径(优先级从上到下依次下降):linux

系统模式nginx

/etc/systemd/system/*
/run/systemd/system/*
/usr/lib/systemd/system/*
...

注:debian 下,关注:/lib/systemd/systemd/*目录。通常来讲,本身定义的.service 放在/etc/systemd/system下。git

用户模式github

$XDG_CONFIG_HOME/systemd/user/*
$HOME/.config/systemd/user/*
/etc/systemd/user/*
/run/systemd/user/*
/usr/lib/systemd/user/*
...

先看个例子:web

nginx.serviceapache

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

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=/usr/sbin/nginx -s quit

[Install]
WantedBy=multi-user.target

经过[Unit] section能够知道这是HTTP服务器与反向代理服务器--nginx的单元文件(Description,关于服务的说明),此单元需在某些 target(network.target)以后才可启动(After),关于target的介绍请查看Target一节。api

经过[Server] section,能够知道服务的启动方式为forking,PIDFile文件的路径为/run/nginx.pid(需 与nginx.conf中指定的PID文件路径相同,让systemd追踪服务的主进程,若未指定或不一致将没法启动服务),ExecStartPre用 于指定在服务启动以前须要执行的命令,ExecStart即服务启动调用的命令。ExecReload和ExecStop分别指定服务进程重载和中止的命 令。更多配置选项介绍见下文。

[Install] section 中的WantedBy表示此服务被multi-user.target wants,在对应.wants/子目录中为服务创建相应的连接,当systemd启用此target,本服务也会启动。

基本介绍

service单元文件中的[Unit]和[Install]同其余单元文件相似,其特有的seciton是[Service]。单元文件中空行和 以#和;开始的行会被忽略,因此能够用来写注释。以反斜杠(\)链接在一块儿的行,处理时会将反斜杠替换成空格,因此能够用这个来把太长的行分红多行。若是 文件是空的,或连接到/dev/null,它将不被加载,这样能够用来禁用服务,让它即便手工也没法启动。

[Unit]
  • Description=   :一些描述,显示给用户界面看的,能够是任何字符串,通常是关于服务的说明。

  • Documentation=  :指定参考文档的列表,以空格分开的 URI 形式,如http://, https://, file:, info:, man:,这是有顺序的,最好是先解释这个服务的目的是什么,而后是它是如何配置的,再而后是其它文件,这个选项能够屡次指定,会将多行的合并,若是指定 了一个空的,那么会重置此项,前面的配置再也不起做用。

  • Requires=   :指定此服务依赖的其它服务,若是本服务被激活,那么 Requires 后面的服务也会被激活,反之,若是 Requires 后面的服务被中止或没法启动,则本服务也会中止。这个选项能够指定屡次,那么就要求全部指定的服务都被激活。须要注意的是这个选项不影响启动或中止的顺 序,启动顺序使用单句的 After= 和 Before= 来配置。例如,若是 foo.service 依赖 bar.serivce,可是只配置了 Requires= 而没有 After= 或 Before=,那么 foo.service 启动时会同时激活 foo.service 和 bar.service。一般使用 Wants= 代替 Requires= 是更好的选择,由于系统会更好的处理服务失败的状况。注意,这种依赖关系,也能够在文件以外来处理,即便用 .requires/ 目录,能够参看上面的说明。

  • RequiresOverridable= :相似上面的 Requires= ,不过这种状况下,只要用户明确要求它启动,才会影响到被依赖的服务,否则服务出错什么的,不会影响被依赖服务的启动。

  • Requisite=, RequisiteOverridable=  :分别相似上面的两个,不过若是是这个指定服务没有启动,被依赖的服务会不启动,当即失败。

  • Wants= :相对弱化的 Requires= ,这里列出的服务会被启动,但若是没法启动或没法添加到事务处理,并不影响本服务作为一个总体的启动。这是推荐的两个服务关联的方式。这种依赖也能够配置 文件外,经过 .wants/ 目录添加,具体能够看上面的说明。

  • BindsTo= :和 Requires= 很像,可是这种状况,若是他后面列出的服务中止运行或崩溃之类的,本服务也会同时中止。

  • PartOf=  :又一个相似 Requires= 的选项,可是限制在中止或重启动服务,若是这里列出的服务被中止或重启动,那么本服务也会中止或重启动,注意这个依赖是意向,即本服务中止或重启动,不会影响到这里列出服务的运行状态。

  • Conflicts= :配置一个依赖冲突,若是配置了些项,那么,当一个服务启动时,或中止此处列出的服务,反过来,若是这里列出的服务启动,那 么本服务就会中止,即后启动的才起做用。注意,此设置和 After= 和 Before= 是互相独立的。若是服务 A 和 B 冲突,且在 B 启动的时候同时启动,那么有可能会启动失败(两都都是必需的)或修改以修复它(二者之一或两都都不是必需的),后一种状况,会将不须要的依赖删除,或中止 冲突。

  • Before=, After=  :配置服务间的启动顺序,好比一个 foo.service 包含了一行 Before=bar.service,那么当他们同时启动时,bar.service 会等待 foo.service 启动完成后才启动。注意这个设置和 Requires= 的相互独立的,同时包含 After= 和 Requires= 也是常见的。此选项能够指定一次以上,这时是按顺序所有启动。

  • OnFailure=   :列出一个或更多的服务,当本服务启动状态是 failed 的时候,激活这些服务。

  • PropagatesReloadTo=, ReloadPropagatedFrom=  :这两个是列出一些服务,当其它服务 reload 时同时 reload 这个服务,或者反之。

  • RequiresMountsFor=  :用空格分开的绝对路径列表,是 Requires= 和 After= 添加的依赖中的 mount 文件须要访问的指定的路径。

  • OnFailureIsolate=  :是一个布尔值,若是是真,那么 OnFailure= 后面的服务会进入隔离模式,即全部不是它依赖的服务都会中止。若是只设置一个服务,能够放在 OnFailure= 后,默认值是假。

  • IgnoreOnIsolate=  :一个布尔值.若是是真则当隔离其它服务时本服务不会中止(不明白隔离是什么意思,大概在后面)。默认是假。

  • IgnoreOnSnapshot=  :一个布尔值.若是是真则本服务不包含快照(snapshots)。对 device 和 snapshot 服务默认为真,其它服务默认为假。

  • StopWhenUnneeded=  :一个布尔值。若是是真则当本服务不使用时会中止。 注意,为了尽可能减小 systemd 的工做,默认状况下是不会中止不使用的服务的,除非和其它服务冲突,或用户明确要求中止。若是设置了这个选项,那么若是没有其它活动的服务须要此服务,它 会自动中止。默认值是假。

  • RefuseManualStart=, RefuseManualStop=  :布尔值。若是设为真值,则此服务只能间接的激活或中止。这种状况下,用户直接启动或中止此服务会被拒绝,只有作 为其它的服务依赖关系,由其它服务进行启动或中止才能够。这主要是为了中止用户误操做。默认值是假。

  • AllowIsolate=      :布尔值。若是是真值,则此服务可使用 systemctl isolate 命令进行操做。不然会拒绝此操做。最好的办法是不要动这处选项,除非目标服务的行为相似于 SysV 启动系统中的 runlevels。只是一种预防措施,避免系统没法使用的状态。默认值是假。

  • DefaultDependencies=  :布尔值。若是是真(默认值),一些本服务默认的依赖会隐式的创建,具体是哪些依赖,则于服务的类型 决定。好比,对于普通的服务(.service类型),它会确保在系统基本服务启动后才启动本服务,会在系统关机前确保本服务已关闭。通常来讲,只有早期 开机服务和后期的关机服务,才须要把这个设成假。强烈对大多数普通服务,让这个选项启用便可。若是设成假,也不会禁用全部的隐式依赖,只是禁用那些非必要 的。

  • JobTimeoutSec=  :当一个客户端等待本服务的某个 Job 完成时,所指定的超时时间。若是达到了限制的时间,此 Job 会取消运行,但服务不会更改状态,包括进入“failed”状态。除了设备服务(即.device类型),其它的默认值是0(即没有超时设置)。注意,这 个是独立于特定服务所设置的超时设置的(好比对 .service 类型所设置的 Timeout=),它对服务自己没有影响,但特定服务的设置是有影响的(能用来更改服务状态)。《-这段不明白究竟是什么意思,因此翻译的也是乱七八 糟,真对不起)。

  • ConditionPathExists=, ConditionPathExistsGlob=, ConditionPathIsDirectory=, ConditionPathIsSymbolicLink=, ConditionPathIsMountPoint=, ConditionPathIsReadWrite=, ConditionDirectoryNotEmpty=,ConditionFileNotEmpty=, ConditionFileIsExecutable=, ConditionKernelCommandLine=, ConditionVirtualization=, ConditionSecurity=, ConditionCapability=, ConditionHost=, ConditionACPower=,ConditionNull=  :这是一组相似的东西。检测特定的条件是否是真值,若是不是真值,服务会略过启 动,可是它依赖的服务仍是会正常运行的。这个条件测试失败不会让服务进入失败状态。条件是在服务开始运行时检查的。

  • ConditionPathExists= 是指定在服务启动时检查指定文件的存在状态。若是指定的绝对路径名不存在,这个条件的结果就是失败。若是绝对路径的带有!前缀,则条件反转,即只有路径不存在时服务才启动。

  • ConditionPathExistsGlob= 相似上面的选项,但支持通配符。

  • ConditionPathIsDirectory= 判断指定路径是否是目录。

  • ConditionPathIsSymbolicLink= 判断指定路径是否是连接。

  • ConditionPathIsMountPoint= 判断指定路径是否是一个挂载点。

  • ConditionPathIsReadWrite= 多年指定路径是否可读写(即不是作为只读系统挂载的)

  • ConditionDirectoryNotEmpty= 判断指定目录是否存在且不为空。

  • ConditionFileNotEmpty= 判断指定文件是不是常规文件且不为空(即大小不是0)。

  • ConditionFileIsExecutable= 判断指定文件是不是常规文件且可执行。

  • 相似的,ConditionKernelCommandLine=是判断有没有指定的内核命令行启动参数(或带有!反之),这个参数必须是一个单词或用=分开的两个单词,前一种状况下,会寻找内核参数是否有此单词或是赋值的左边。后一种状况则必须是赋值的左右同时符合。

  • ConditionVirtualization= 是判断是否是在虚拟化环境下执行的服务。这能够是个布尔值以判断是否是任意的虚拟化环境,或者 下列的字符串之一: qemu, kvm, vmware, microsoft, oracle, xen, bochs, chroot, openvz, lxc, lxc-libvirt, systemd-nspawn,以判断是否是特定的虚拟化环境,多重嵌套的虚拟化环境,只判断最后一层。可使用!进行反转判断。

  • ConditionSecurity= 是判断系统是否启用了安全环境,当前仅能识别selinux, apparmor, 和 smack。可使用!进行反转判断。

  • ConditionCapability= 是判断服务管理器绑定的 capability 是否存在。(能够查看其它部分的详细信息。)设置为 capability 的名字,好比 CAP_MKNOD。能够经过在前面加!反转判断。

  • ConditionHost= 是判断主机名 (hostname)或机器ID(machine ID)是否匹配。能够加!反转。

  • ConditionACPower= 是判断机器是否在使用交流电源。若是设成 true,而只有至少链接一个交流电源时结果才为真,反过来,设成 false,则不链接全部交流电源时才为真。

  • ConditionNull= 是一个常量性质的判断条件,它应该是布尔值,若是设成 false ,则条件永远失败,反过来则永远成立。

  • 若是指定多个条件,则全部条件都须要成立(即条件之间是 AND 的关系)。条件前面能够加上 | 符号,这时条件变成一个触发条件,服务定义了触发条件,那么在知足其它非触发条件和这个触发条件的状况下,服务会至少执行一次。同时指定|和!前缀时,先 处理|,后处理!。除了ConditionPathIsSymbolicLink=,其它条件均跟随连接。若是这些条件指定为空,则至关于重置,前面的任 何设置都再也不起做用。

  • SourcePath=  :这个服务生成的配置文件所在的路径,这主要是用在生成工具从外部配置文件的格式转换到本地服务的配置格式中。所以,对通常的服务不要使用此选项。

[Service]

介绍部分常见选项,其他选项请查看本节参考连接2。

  • Type= :

    • simple(默认值):systemd认为该服务将当即启动。服务进程不会fork。若是该服务要启动其余服务,不要使用此类型启动,除非该服务是socket激活型。
    • forking:systemd认为当该服务进程fork,且父进程退出后服务启动成功。对于常规的守护进程(daemon),除非你肯定此启动 方式没法知足需求,使用此类型启动便可。使用此启动类型应同时指定 - PIDFile=,以便systemd可以跟踪服务的主进程。
    • oneshot:这一选项适用于只执行一项任务、随后当即退出的服务。可能须要同时设置 RemainAfterExit=yes 使得 systemd 在服务进程退出以后仍然认为服务处于激活状态。
    • notify:与 Type=simple 相同,但约定服务会在就绪后向 systemd 发送一个信号。这一通知的实现由 libsystemd-daemon.so 提供。
    • dbus:若以此方式启动,当指定的 BusName 出如今DBus系统总线上时,systemd认为服务就绪。
  • PIDFile= :指定PID 文件绝对路径,当Type=forking时,必须设置此项。

  • ExecStart= :服务启动命令,可带参数。!!关于此选项有大量注意事项与细节(例如:命令中不可使用重定向符号,管道,后台运行&以及其余特殊符号等),请查看用户手册。

  • ExecStartPre=, ExecStartPost= :以前ExecStart以前或者以后执行的命令。注意语法格式要求同ExecStart选项。若包含多条此选项,将按顺序串行执行。

  • ExecReload= :重载服务时候触发的命令。

  • ExecStop= :服务中止命令。

  • ExecStopPost= :中止服务以后执行的命令。语法格式要求同ExecStart选项。

  • Restart= :指定服务进程自动重启的条件。

    • no:默认选项,服务不会被systemd自动重启。
    • on-success:当服务进程成功退出后重启(exit code=0,signals SIGHUP, SIGINT, SIGTERM or SIGPIPE, and additionally, 或SuccessExitStatus=选项指定的退出信号)。
    • on-failure:服务进程不正常退出时进行重启(exit code 为非0,或被信号中断)。
    • on-abnormal:服务进程被信号中断时进行重启。
    • no-watchdog:watchdog观测到服务进程过时后重启服务。
    • no-abort:进程被未捕获的信号中断时将进行重启。
    • always:服务在不管何种状况退出后或者超时时老是重启。

exit causes

关于启动、中止的超时设置请参考用户手册。

服务进程限制
  • PrivateNetwork=[BOOL] :若服务不须要网络链接可开启本选项,更加安全。

  • PrivateTmp=[BOOl] :因为传统/tmp目录是全部本地用户和服务共用,会带来不少安全性问题,开启本选项后,服务将有一个私有的tmp,可防止攻击。

  • InaccessibleDirectories= :限制服务进程访问某些目录。

  • ReadOnlyDirectories= :设置服务进程对某些目录只读,保证目录下数据不被服务意外撰改。

  • OOMScoreAdjust= :调整服务OOM值,从-1000(对该服务进程关闭OOM)到1000(严格)。

  • IOSchedulingClass= :IO调度类型,可设置为0,1,2,3中的某个数值,分配对应none,realtime,betst-effort和idle。

  • IOSchedulingPriority= :IO调度优先级,0~7(高到低)。

  • CPUSchedulingPriority= :CPU调度优先级,99~1(高到低)

  • Nice= :进程调度等级。

-(更多介绍请查看本节参考连接7,8)

关于服务进程的权限限制、资源限制、安全性管理将在进阶部分中进一步介绍。

[Install]
  • Alias= :在安装使用应该使用的额外名字(即别名)。名字必须和服务自己有一样的后缀(即一样的类型)。这个选项能够指定屡次,全部的名字都起做用,当执行 systemctl enable 命令时,会创建至关的连接

  • WantedBy=, RequiredBy= :在 .wants/ 或 .requires/ 子目录中为服务创建相应的连接。这样作的效果是当列表中的服务启动,本服务也会启动。 在 bar.service 中的 WantedBy=foo.service  和 Alias=foo.service.wants/bar.service 基本是一个意思。

  • Also=  :当此服务安装时同时须要安装的附加服务。 若是用户请求安装的服务中配置了此项,则 systemctl enable 命令执行时会自动安装本项所指定的服务。

  • 在 [Install] 段使用这些字符串有特定含义: %n, %N, %p, %i, %U, %u, %m, %H, %b。请查看查看本节参考连接1。

注:修改service单元文件以后,需使用命令systemctl daemon-reload从新载入systemd,扫描新的或有变更的单元。

服务进程执行环境配置:

更多关于服务进程执行环境的配置请参考:systemd.exec

服务进程基本操做

systemd的主要命令行工具是systemctl,可用于管理单元文件,不只仅是service。

注:若只给出名称,而为给出扩展名,将默认为service单元,例:systemctl start foosystemctl start foo.service等价。

在sysv下,经常使用的管理命令是service,在systemd中此命令可用(参考本文档“SysV兼容性”一节),更建议使用systemctl命令,如下为两者的命令基础操做对照表:

systemctl

服务进程(组)查看

使用systemctl能够查看服务进程的状态。

查看服务进程运行状态(同时查看到其下属进程们的PID)

# systemctl status nginx.service

查看系统全部service及其状态,将会打印4列信息,分别为UNIT、LOAD、ACTIVE、SUB、DESCRIPTION

# systemctl list-units --type service

查看特定服务的依赖关系

# systemctl list-dependencies nginx

更多关于systemctl在服务进程查看方面的用法请查看systemctl用户手册,另外也可经过 pstree命令查看系统进程树。

杀死服务进程(组)

Systemd 采用 Linux 的 Cgroup 特性跟踪和管理进程的生命周期。CGroup 提供了相似文件系统的接口,使用方便。当进程建立子进程时,子进程会继承父进程的 CGroup。所以不管服务如何启动新的子进程,全部的这些相关进程都会属于同一个 CGroup,systemd 只须要简单地遍历指定的 CGroup 便可正确地找到全部的相关进程,将它们一一中止便可。 例如:一个CGI程序派生两次,脱离和Apache的父子关系,当apache进程被中止后,该CGI程序还在继续运行的状况,在systemd的管理下 将不会存在。

中止服务进程建议使用systemctl工具。

杀死一个服务的全部进程(传递信号到指定服务的全部进程):

$ sudo systemctl kill crond.service
#指定信号类型
$ sudo systemctl kill -s SIGKILL crond.service
或
$ sudo systemctl kill -s 9 crond.service
#不管服务进程通过多少层fork,使用以上命令便可杀死全部进程。

# 发送指定信号到服务的主进程
$ sudo systemctl kill -s HUP --kill-who=main crond.service

另外在单元文件中,能够指定KillMode,KillSignal,SengSIGHUP,SendSIGKILL,请查看本节参考连接4。

实例

fork.c

fork.c

编译生成二进制程序fork,编写fork.service,调用fork程序。运行fork.service,使用pstree查看进程树:

pstree1

kill 掉某个fork出的子进程,使该子进程的子进程们脱离主进程。

pstree2

查看fork.service 的状态,发现脱离父进程的进程们依然在其crgoup中,未脱离。

fork status

kill掉fork.service,发现该服务的全部进程,包括脱离主进程的进程都被中止,无一例外。

fork stop

本节参考连接

  1. systemd.unit
  2. systemd.service
  3. Killing Services
  4. systemd.kill
  5. systemctl
  6. ArchWiki-systemd
  7. Securing Your Services
  8. systemd.exec

socket-based activation

绝大多数的服务依赖是套接字依赖。好比服务 A 经过一个套接字端口 S1 提供本身的服务,其余的服务若是须要服务 A,则须要链接 S1。所以若是服务 A 还没有启动,S1 就不存在,其余的服务就会获得启动错误。因此传统地,人们须要先启动服务 A,等待它进入就绪状态,再启动其余须要它的服务。Systemd 认为,只要咱们预先把 S1 创建好,那么其余全部的服务就能够同时启动而无需等待服务 A 来建立 S1 了。若是服务 A 还没有启动,那么其余进程向 S1 发送的服务请求实际上会被 Linux 操做系统缓存,其余进程会在这个请求的地方等待。一旦服务 A 启动就绪,就能够当即处理缓存的请求,一切都开始正常运行。

Linux 操做系统有一个特性,当进程调用 fork 或者 exec 建立子进程以后,全部在父进程中被打开的文件句柄 (file descriptor) 都被子进程所继承。套接字也是一种文件句柄,进程 A 能够建立一个套接字,此后当进程 A 调用 exec 启动一个新的子进程时,只要确保该套接字的 close_on_exec 标志位被清空,那么新的子进程就能够继承这个套接字。子进程看到的套接字和父进程建立的套接字是同一个系统套接字,就仿佛这个套接字是子进程本身建立的一 样,没有任何区别。

这个特性之前被一个叫作 inetd 的系统服务所利用。Inetd 进程会负责监控一些经常使用套接字端口,好比 Telnet,当该端口有链接请求时,inetd 才启动 telnetd 进程,并把有链接的套接字传递给新的 telnetd 进程进行处理。这样,当系统没有 telnet 客户端链接时,就不须要启动 telnetd 进程。Inetd 能够代理不少的网络服务,这样就能够节约不少的系统负载和内存资源,只有当有真正的链接请求时才启动相应服务,并把套接字传递给相应的服务进程。

和 inetd 相似,systemd 是全部其余进程的父进程,它能够先创建全部须要的套接字,而后在调用 exec 的时候将该套接字传递给新的服务进程,而新进程直接使用该套接字进行服务便可。

systemd中这个并行化Socket服务极大的加快了系统的启动速度(固然,不全是它的功劳,此处不赘述)。

systemd的并行启动能力(与sysv、upstart对比)

parallelization

Socket Activation 带来的益处(摘自Systemd主做者Blog):

  • parallelization.
  • We no longer need to configure dependencies explicitly. Since the sockets are initialized before all services they are simply available, and no userspace ordering of service start-up needs to take place anymore. Socket activation hence drastically simplifies configuration and development of services.
  • If a service dies its listening socket stays around, not losing a single message. After a restart of the crashed service it can continue right where it left off.
  • If a service is upgraded we can restart the service while keeping around its sockets, thus ensuring the service is continously responsive. Not a single connection is lost during the upgrade.
  • We can even replace a service during runtime in a way that is invisible to the client. For example, all systems running systemd start up with a tiny syslog daemon at boot which passes all log messages written to /dev/log on to the kernel message buffer. That way we provide reliable userspace logging starting from the first instant of boot-up. Then, when the actual rsyslog daemon is ready to start we terminate the mini daemon and replace it with the real daemon. And all that while keeping around the original logging socket and sharing it between the two daemons and not losing a single message. Since rsyslog flushes the kernel log buffer to disk after start-up all log messages from the kernel, from early-boot and from runtime end up on disk.

上文提到inetd能够代理不少网络服务以此节约系统资源,Systemd 也能够提供按需启动的能力,只有在某个服务被真正请求的时候才启动它。空闲时使该服务结束,等待下次须要时再次启动它。例:

服务器上的SSHD服务,只有当系统管理员或者用户登陆时候,此服务才被使用到,而后它却一直驻留在后台,极大的浪费系统资源。而在systemd下,咱们能够作得按需启动,即有人经过SSH访问服务时,SSHD服务才会被启动。(具体实现请查看/lib/systemd/system下的ssh.servicessh.socket文件)

更多例子:Socket Activation with Popular Daemons

注:systemd的socket单元负责监控端口时,当外部链接带来,而systmed没法将socket转移给对应服务单元时候(若服务进程崩 溃、中止退出、没法重启等),对应socket单元将进入failed状态,外部链接将不通(telnet ip pot 测试对应服务端口将不通)。

按需启动实例:

使用systemd部署Node.js应用(myservice.service),见图myservice.service:

myservice.service

$ sudo systemctl enable myservice.service # 设置服务随机启动
$ sudo systemctl start myservice.service #启动服务

在这种配置下,没有使用socket激活,systemd运行且监控着服务的守护进程,Node.js监听TCP端口和服务器请求,进程是时刻运行着,即便没有外部访问,极大的浪费了系统的资源。

系统运行原理,见图no-socket-activation:

no-socket-activation

须要将其改成socket激活。空闲时,systemd监控着TCP端口,当有请求来到时,systemd激活Node.js服务并将socket 移教给Node.js处理,而后Nojde.js接管并处理所有的客户端请求,systemd从新变成监控Node.js的角色。 当 Node.js 处理完任务后,它自动中止。而再由 systemd 来监控 TCP 端口,直至下次客户端请求的到来并再启动 Node.js 的应用,循环往复,见图socket-activation1,2

socket-activation 1

socket-activation 2

要达到以上目的,Node.js须要安装systemd相关模块,同时安装node-autoquit模块在空闲时关闭应用,以及作好应用数据的保存工做,具体此处不赘述,请参考本节参考连接。

systemd的相关配置:须要修改以前的service单元文件(去除自动重启以及自动启动),并增长一个同名socket单元文件让systemd监听TCP端口,见图socket-activation3

socket-activation 3

$ sudo systemctl enable myservice.socket #使socket单元自启动
$ sudo systemctl start myservice.socket #启动socket

本节参考连接


instantiated services

部分服务例如syslog、apache等,在系统上通常是只有单进程在运行。而另一些服务须要多实例化,例如Dovecot-IMAP服务会有 多个实例进程运行在不一样的IP端口或者本地IP地址上。 在systemd中,咱们不须要为每一个实例化进程编写一份配置文件,只须要编写一个模板文件,而后在启用时候调用模板文件便可,模板文件能够经过匹配模板 时传入的字符串实例化进程,例如咱们系统上有3块网卡,分别为eth0、eth一、eth2,咱们但愿经过每块网卡都经过dhcpd配置动态ip。利用 systemd的模板\实例机制,咱们只须要写一份dhcpd的模板文件,便可为3块网卡所用。

dhcpd@.service

[Unit]
Description=dhcpcd on %I
Wants=network.target
Before=network.target
BindsTo=sys-subsystem-net-devices-%i.device
After=sys-subsystem-net-devices-%i.device

[Service]
Type=forking
PIDFile=/run/dhcpcd-%I.pid
ExecStart=/usr/bin/dhcpcd -4qb %I
ExecStop=/usr/bin/dhcpcd -x %I

[Install]
WantedBy=multi-user.target

经过如下命令启用:

# systemctl start dhcpcd@eth0.service
# systemctl start dhcpcd@eth1.service
# systemctl start dhcpcd@eth2.service

命令中的字符串ethN将在service文件中被匹配展开:

dhcp@eth0.service

[Unit]
Description=dhcpcd on eth0
Wants=network.target
Before=network.target
BindsTo=sys-subsystem-net-devices-eth0.device
After=sys-subsystem-net-devices-eth0.device

[Service]
Type=forking
PIDFile=/run/dhcpcd-eth0.pid
ExecStart=/usr/bin/dhcpcd -4qb eth0
ExecStop=/usr/bin/dhcpcd -x eth0

[Install]
WantedBy=multi-user.target

模板文件中的%I%i被展开为命令中@以后.service以前的字符串,即ethN%I%i的区别在此例中看不出来,实际上%I展开后是不转义的字符串,而%i展开后是转移的字符串。例如若是咱们调用串口tty的模板文件实例化某个接口的tty服务, systemctl start 'serial-getty@serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0.service',实际上接口地址为 “serial/by-path/pci-0000:00:1d.0-usb-0:1.4:1.1-port0” ,以上命令是通过转义的, /被替换为-,特殊符号被替换为16进制符号等。service文件中的%I将展开为转义前的“serial/by-path/pci-0000:00:1d.0-usb-0:1.4:1.1-port0”, 而%i将展开为serial-getty@serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0.service

  • "%I" Unescaped instance name Same as "%i", but with escaping undone
  • "%i" Instance name For instantiated units: this is the string between the "@" character and the suffix of the unit name

单元文件的特殊符号更多解释请查看 systemd.unit#Specifiers

另一个例子: systemd-fsck@.service

[Unit]
Description=File System Check on %f
Documentation=man:systemd-fsck@.service(8)
DefaultDependencies=no
BindsTo=%i.device
After=systemd-readahead-collect.service systemd-readahead-replay.service %i.device
Before=shutdown.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/lib/systemd/systemd-fsck %f
StandardOutput=journal+console
TimeoutSec=0

systemd-fsck是systemd的磁盘检查工具,经过此模板文件能够对多块磁盘设备进行实例化后检查(%f表示未转义的文件名)。

本节参考连接


进程资源管理

基础介绍

systemd内部使用cgroups对其下的单元进行资源管理,包括CPU、BlcokIO以及MEM方面。systemd的资源管理主要基于三个单元service、scope以及slice。

  • service单元主要用于配置单项服务进程,具体请参考上文的基础--进程管理--服务进程管理一节。
  • scope 单元由 systemd 在已有进程外自动建立。经过将某个进程和其子进程分组,scope 单元可用来组织进程,应用资源单元,或者杀死进程组。用户会话就是一个进程都包含在单个 scope 单元中的实例。
  • slice 单元用于将管理进程的单元分组成层级,层级可容许控制分配给 slice 的资源。例:默认的 slice 有用于虚拟机及容器 (container) 的 machine.slice;用于系统服务的 system.slice;用于用户会话的 user.slice。在service单元文件中使用 Slice=slicename,可将服务可添加到特定slice。

每个用户登陆时候会自动建立一个此用户的slice单元,同时用户的每一个对话会自动建立一个scope用于管理对话下的进程。 每个模板service单元文件实例化时会自动建立一个对应的slcie,例如getty@.service进行实例化为一个 getty@tty1.service时会自动建立一个systemd-getty.slice用于管理此模板下的全部实例化的进程,关于service 模板文件的介绍请查看上文的基础--进程管理-instandtiatied serivices一节。

可以使用systemd-cgls( Recursively show control group contents)命令查看系统中以上三种单元的层级管理,见图systemd-cgls。

systemd-cgls

systemd另外提供了一个工具systemd-cgtop( Show top control groups by their resource usage)用于监控各个单元实时、动态的资源占用状况,见图systemd-cgtop。

systemd-cgtop

配置与实例

本小节仅简单介绍关于CPU、MEM、BlockIO常见的资源管理选项,更多配置选项详情请查看本节参考连接四、6等。

CPU
  • CPUAccounting=
    • [true,false],是否打开服务单元的CPU用量统计
    • Note that turning on CPU accounting for one unit might also implicitly turn it on for all units contained in the same slice and for all its parent slices and the units contained therein.
  • CPUShare=,StartupCPUShare=
    • 设置服务单元的CPU时间比重,默认值为1024。设置此两项以后,即表示默认开启"CPUAccounting=true"。
    • StartupCPUShare做用于系统启动阶段,CPUShares做用与系统启动以后运行阶段。
  • CPUQuota=
    • 服务进程执行时的CPU时间配额设置。设置的值是百分数的形式,表示占一个CPU总共时间的百分比。若值大于100%,表示分配的的CPU时间多于一个CPU。
    • Example: CPUShares=20% ensures that the executed processes will never get more than 20% CPU time on one CPU.
    • 设置此项以后,默认设置"CPUAccounting=true"。
  • CPUSchedulingPolicy=
    • 设置服务进程执行时CPU的调度策略,可设置为other, batch, idle, fifo 或 rr,具体请查看sched_setscheduler(2)
  • CPUSchedulingPriorityp=
    • 设置服务进程执行时CPU的调度优先级,设置的数值范围依上文CPUSchedulingPolicy指定的类型不一样而不通,例如real-time类型的调度方式,优先级数值范围为1(低优先级)到99(高优先级)。更多信息请参考sched_setscheduler(2)

/etc/systemd/system/httpd.service

.include /usr/lib/systemd/system/httpd.service

[Service]
CPUShares=1500

而后重载systemd deamon,重启http.service便可。

#systemctl daemon-reload
#systemctl restart httpd.service
MEM
  • MemoryAccounting=
  • [true,false],是否打开服务单元的内存用量统计。
  • MemoryLimit=bytes
  • 指定执行进程的内存使用量,默认单位为bytes。数值后可加单元:K,M,G或T,表示KB,MB,GB,TB。设置此项即意味着设置了 "MemoryAccounting=true"。

/etc/systemd/system/httpd.service

.include /usr/lib/systemd/system/httpd.service

[Service]
MemoryLimit=1G

在debian testing 上没法完成内存限制的实验,缘由不明。正常配置,service单元与slice单元可正常启动,可是slice单元中限制的内存无效,日志正常。更换到fedora 20上实验成功。 注意若在service单元中指定了slice单元,而限制选项主要在slice单元中,最好slice单元比service单元先启动,不然可能报错。 若服务程序内存超过MemoryLimit的限制,服务单元将退出进入failed状态。

BlockIO
  • BlockIOAccounting=
    • [true,false],是否开启服务单元的块设备的IO统计。
  • BlockIOWeight=weight, StartupBlockIOWeight=weight
  • 设置服务单元执行时候的块设备IO比重,默认数值为1000,可设置范围为10到1000。BlcokcIOWeight与StartupBlockIOWeight,前者为系统运行时作的限制,后者为系统启动阶段作的限制。
  • 例1:BlockIOWeight=500
  • 例2,指定设备:BlockIOWeight=/dev/disk/by-id/ata-SAMSUNG_MMCRE28G8MXP-0VBL1_DC06K01009SE009B5252 750
  • 例3,指定实际的块设备点:BlockIOWeight=/home/lennart 750
  • BlockIOReadBandwidth=device bytes, BlockIOWriteBandwidth=device bytes
  • 设置服务单元执行时对执行块设备的读写速率设置。
  • 例:BlockIOReadBandwith=/var/log 5M,表示服务单元对/var/log的读限制在5Mb/s内。
Others

上文提到的这些设置选项其实都是与cgroup对应的控制组相关,例如MemoryLimit选项对应memory.limit_in_bytes控制组。systemd的单元文件中能够经过ControlGroupAttribute配置对应cgroup的控制组,例如:controlGroupAttribute=memory.swappiness 70。更多介绍请查看本节参考连接9,10。

ControlGroupAttribute= Set a specific control group attribute for executed processes, and(if needed) add the executed processes to a cgroup in the hierarchy of the controller the attribute belongs to.

实例

咱们将上文关于CPU,IO,BlockIO等相关的设置整合在一块,可获得以下的service文件:

/etc/systemd/system/httpd.service

.include /usr/lib/systemd/system/httpd.service

[Service]
CPUShares=1500
MemoryLimit=1G
BlockIOWeight=500
BlockIOReadBandwith=/var/log 5M
ControlGroupAttribute=memory.swappiness 70

咱们知道service单元文件中service 段能够经过Slice=选项指定所属的slice单元,因此除了将资源管理配置写在service单元文件中,还能够将服务进程的资源限制写在特定slice单元中。

例:

/etc/systemd/system/limits.slice:

[Unit]
Description=Limited resources Slice
DefaultDependencies=no
Before=slices.target

[Slice]
CPUShares=512
MemoryLimit=1G

/lib/systemd/system/nginx.service:

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

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=/usr/sbin/nginx -s quit
Slice=limits.slice

[Install]
WantedBy=multi-user.target

而后执行如下命令:

# systemctl daemon-reload
# systemctl restart nginx.service

使用systemd-cgls查看,结果如图 nginx-slice。

nginx-slice

另外也可经过systemctl永久性或者临时性的对正在运行的service、slice、scope单元进行即时的资源控制。

例如:

#systemctl set-property nginx.service CPUShares=500 MemoryLimit=500M

会发现/etc/systemd/system出现了nginx.service.d目录,其中包含着与nginx.service相关的资源控制配置文件:90-CPUShare.conf,90-MemoryLimit.conf。 这种更改是永久性的,即下次重启依然有效。若是须要临时性的更改使用选项--runtime替代set-property便可。

本节参考连接

  1. systemd.service
  2. systemd.scope
  3. systemd.slice
  4. systemd.resource-control
  5. systemd.cgroup
  6. systemd.exec
  7. Resource Management with systemd
  8. The New Control Group Interfaces
  9. systemd for Administrators, Part XVIII
  10. Man systemd.exec

进程权限与安全管理

关于本节内容建议阅读本节参考连接2:《Securing Your Services》。

chroot()与目录访问控制

service单元中可使用 RootDirectory= 选项来达到chroot的效果。

RootDirectory=

Takes an absolute directory path. Sets the root directory for executed processes, with the chroot(2) system call. If this is used, it must be ensured that the process and all its auxiliary files are available in the chroot() jail.

例:

[Unit]
Description=A chroot()ed Service

[Service]
RootDirectory=/srv/chroot/foobar
ExecStartPre=/usr/local/bin/setup-foobar-chroot.sh
ExecStart=/usr/bin/foobard
RootDirectoryStartOnly=yes

如上文服务单元,对于foobard来讲,根目录为/srv/chroot/foobar,注意若指定了RootDirectory,则ExecStart(ExecStop等等)选项的路径需以新指定的RootDirectory为准, 例如本例中ExecStart实际指向为/srv/chroot/foobar/usr/bin/foobard,但在service单元中需写成相对于RootDirectory的路径:/usr/bin/foorbad。

注:systemd中提供了一个方便的轻量级的容器管理工具systemd-naspawn,具体请查看container(systemd-nspawn)一节。

另外若是想控制服务单元对特定目录的访问,systemd的service单元提供了如下选项(详细解释请查看本节参考连接1对应条目):

  • User=,Group=
    • 指定服务单元执行时候的用户与用户组,能够配合目录权限设置与此选项的设置而控制服务单元的访问各目录的权限。
  • InaccessibleDirectories=
    • 指定服务单元不可访问的目录,指定的目录对于服务单元来讲将为空,且访问模式为000,若值以'-'开头,则目录不存在时将自动忽略。
  • ReadWriteDirectories=
    • 指定服务单元可读写的目录。
  • ReadOnlyDirectories=
    • 指定服务单元只读的目录,若值以'-'开头,则目录不存在时将自动忽略。
    • 注意一个已知bug:ReadOnlyDirectories不支持递归,例如即便指定了/var目录,而/var目录下若包含其余子文件夹,这些子文件夹对于服务单元来讲依旧可读写,后续版本将修复。

例(/home目录对于此服务单元不可访问,而/var目录对于此服务单元只读):

...
[Service]
ExecStart=...
InaccessibleDirectories=/home
ReadOnlyDirectories=/var
...

Isolating Services from the Network

若服务单元无需网络服务,可将其与网络隔离,以保证安全性。

PrivateNetwork= :

Takes a boolean argument. If true, sets up a new network namespace for the executed processes and configures only the loopback network device "lo" inside it. No other network devices will be available to the executed process. This is useful to securely turn off network access by the executed process. Defaults to false. It is possible to run two or more units within the same private network namespace by using the JoinsNamespaceOf= directive, see systemd.unit(5) for details. Note that this option will disconnect all socket families from the host, this includes AF_NETLINK and AF_UNIX. The latter has the effect that AF_UNIX sockets in the abstract socket namespace will become unavailable to the processes (however, those located in the file system will continue to be accessible).

例:

...
[Service]
ExecStart=...
PrivateNetwork=yes
...

Service-Private /tmp

因为/tmp目录的权限问题,容易带来一些安全问题,systemd中能够在服务单元中设置私有/tmp目录,可以使服务进程更加安全。

PrivateTmp=

Takes a boolean argument. If true, sets up a new file system namespace for the executed processes and mounts private /tmp and /var/tmp directories inside it that is not shared by processes outside of the namespace. This is useful to secure access to temporary files of the process, but makes sharing between processes via /tmp or /var/tmp impossible. If this is enabled, all temporary files created by a service in these directories will be removed after the service is stopped. Defaults to false. It is possible to run two or more units within the same private /tmp and /var/tmp namespace by using the JoinsNamespaceOf= directive, see systemd.unit(5) for details. Note that using this setting will disconnect propagation of mounts from the service to the host (propagation in the opposite direction continues to work). This means that this setting may not be used for services which shall be able to install mount points in the main mount namespace.

注意:使用privatetmp以后,基于/tmp或/var/tmp目录进行进程间通信(IPC)将不可用。

/privatetmp目录位于/tmp目录下,如图privatetmp (在nginx.service服务单元中启用了PrivateTmp):

privatetmp

例:

...
[Service]
ExecStart=...
PrivateTmp=yes
...

Capabilities

systemd还可对服务进程的Capabilities(能力)进行控制,相关的选项有Capabilities=,CapabilityBoundingSet=,SecureBits=。 具体请查看本节参考连接1与 capabilities 用户手册。

注:此部分选项未实验。

服务进程运行中的资源限制

LimitCPU=, LimitFSIZE=, LimitDATA=, LimitSTACK=, LimitCORE=, LimitRSS=, LimitNOFILE=, LimitAS=, LimitNPROC=, LimitMEMLOCK=, LimitLOCKS=, LimitSIGPENDING=, LimitMSGQUEUE=, LimitNICE=, LimitRTPRIO=, LimitRTTIME=

以上选项可用于服务进程运行时的资源限制,选项具体意义请参考setrlimit

例:

...
[Service]
ExecStart=...
LimitNPROC=1
LimitFSIZE=0
...

查看setrlimit手册,以下:

  • RLIMIT_NPROC
    • The maximum number of processes (or, more precisely on Linux,threads) that can be created for the real user ID of thecalling process.
  • RLIMIT_FSIZE
    • The maximum size of files that the process may create.Attempts to extend a file beyond this limit result in delivery of a SIGXFSZ signal

可知以上单元文件限制了此服务进程最多只能有一个进程,即禁止了其fork,且此服务进程能建立的最大文件大小为0。

设备节点访问控制

DeviceAllow选项可指定服务进程可访问的设备节点以及相关权限,

例如:

...
[Service]
ExecStart=...
DeviceAllow=/dev/null rw
...

该服务单元只对/dev/null有访问权限,且权限为读写。

另外还有PrivateDevices=,ProtectSystem=,ProtectHome=等等选项,具体用法请参考本节参考连接1。

本节参考连接

  1. systemd.exec
  2. Securing Your Services
  3. http://0pointer.de/blog/projects/changing-roots.html

监控与报警

思路:

  • service单元中的ExecStartPre与ExecStopPost等选项可用于监控与报警。关于service单元介绍请查看服务进程管理一节。
    • 服务启动前可经过ExecStartPre执行脚本进行通知或作环境的初始化
    • 服务退出后,可经过ExecStopPost执行脚本,例如报警或清理工做。
    • 注:除了正常中止服务进程会触发此选项指定的命令,即便是kill -9使服务进程异常停止,ExecStopPost指定的命令或脚本依旧成功运行,此特性可用于服务进程异常退出后的报警
    • 注:若ExecStart失败,ExecStopPost将不会执行!若能在ExecStopPost中进行了某些清除工做,需注意是否会由于清除工做不彻底而带来后续的进程启动运行的异常。
    • 可是ExecStartPost在ExecStart执行失败以后依旧可执行,能够在此处检测服务单元日志状态判断ExecStart是否执行失败而执行清理工做。因为journal的日志转发到syslog是经过socket, 存在buffer,若读取/var/log下日志内容可能存在必定延时,因此请经过journal的接口查看服务单元日志。
  • service单元中的FailureAction选项可指定服务进程进入failed状态后的自动执行动做(操做系统的重启、强制重启或当即重启),默认为none。
  • 经过后台自定义守护进程或轮询任务检测 systemd journal 日志信息,对服务状态进行监控和报警。关于日志信息的过滤与查询等内容请查看日志的记录、分类、分发和监控一节。
    • 注:使用systemctl查看特定进程systemctl status name.service,除了输出服务进程基础状态,还可输出关于此服务进程最近的10条日志记录。

经过服务单元中指定ExecStopPost等命令,在服务单元异常退出时候,经过ExecStopPost指定的程序检测对应日志可即时报警,须要注意目前若ExecStart执行失败,EcecStopPost将不会执行(这多是一个bug,不知后续是否会修复)!

ExecStopPost以及ExecStartPost等命令中能够传入Environment=等选项配置的环境变量以及Unit单元中内置的特殊变量。前者请man systemd.exec查看Exviroment相关条目,后者请man systemd.unit查看Specifiers。 因此在ExecStopPost中,除了经过检测日志获得服务进程信息做出报警,还能够经过服务进程中设置的环境变量做为参数传入ExecStopPost,从而获得主服务进程的相关信息而执行对应操做。

例:/etc/default/ssh 文件中指定了须要的环境变量($SSHD_OPTS),而$MAINPID是sshd daemon中设置的。

[Unit]
Description=OpenBSD Secure Shell server
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Service]
EnvironmentFile=-/etc/default/ssh
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure

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

另外经过后台程序轮询查看过滤分析各个服务单元的日志也可作监控与报警,但因为是轮询可能存在必定的延时性。

本节参考连接

相关文章
相关标签/搜索