systemd即为system daemon,是linux下的一种init软件,由Lennart Poettering带头开发,并在LGPL 2.1及其后续版本许可证下开源发布,开发目标是提供更优秀的框架以表示系统服务间的依赖关系,并依此实现系统初始化时服务的并行启动,同时达到降低Shell的系统开销的效果,最终代替现在常用的System V与BSD风格init程序。
## 新建一个服务 `systemd`的`unit`文件分布在 >系统模式 + `/usr/lib/systemd/system/` 软件包安装 + `/etc/systemd/system/` 系统管理员维护 >用户模式 + `/usr/lib/systemd/user/` 软件包安装 + `/etc/systemd/user/` 管理员维护的用户服务 + `~/.config/systemd/user/` 用户自身的服务 新建服务时,新建一个`sample....
保存下面的配置到 /lib/systemd/system/nginx.service ``` [Unit] Description=The NGINX HTTP and reverse proxy server After=syslog.target network.target remote-fs.target nss-lookup.target [Service] Type=forking PIDFile=/usr/local/nginx/logs/nginx.pid ExecStartPre=/usr/local/nginx/sbin/nginx -t ExecSt...
sysvinit 命令|systemd 命令|说明 -|-|- service httpd start| systemctl start httpd.service|启动httpd服务 service httpd stop| systemctl stop httpd.service|关闭httpd服务 service httpd restart| systemctl restart httpd.service|重启httpd服务 service httpd reload| systemctl reload httpd.service|重新载入h...
从 init 系统说起linux 操作系统的启动首先从 BIOS 开始,接下来进入 boot loader,由 bootloader 载入内核,进行内核初始化。内核初始化的最后一步就是启动 PID 为 1 的 init 进程。这个进程是系统的第一个进程。它负责产生其他所有的用户进程。init 进程以守护进程(也就是服务)的方式存在,是所有其他进程的祖先。init 进程非常独特,能够完成其他进程无法完成的任务。init 系统能够定义、管理和控制 init 进程的行为。它负责组织和运行许多独立的或相关的初始化工作(因此被称为 init 系统),从而让计算机系统进入某种用户预定义的运行模式,比如命令行模式或图形界面模式 。对于一个操作系统而言,仅仅将内核运行起来是毫无实际用途的,必须由 init 系统将操作系统初始化为可操作的状态。比如启动 shell 后,便有了人机交互,这样就可以让计算机执行一些程序完成有实际意义的任务。或者启动 X 图形系统以便提供更佳的人机界面,更加高效的完成任务。这里,字符界面的 shell 或者 X 系统都是一种预设的运行模式。随着计算机系统软硬件的发展,init 系统也在不断的发展变化之中。大体上的演进路线为 sysvinit -> upstart -> systemd。虽然本文的目的是要介绍 systemd,但是笔者觉得如果能从历史发展的角度观察 init 系统的演进,将会帮助我们更好的理解、使用 systemd。sysvinitsysvinit 就是 System V 风格的 init 系统,顾名思义,它源于 System V 系列的 UNIX。最初的 linux 发行版几乎都是采用 sysvinit 作为 init 系统。sysvinit 用术语 runlevel 来定义 "预订的运行模式"。比如 runlevel 3 是命令行模式,runlevel 5 是图形界面模式,runlevel 0 是关机,runlevel 6 是重启。sysvinit 会按照下面的顺序按部就班的初始化系统:激活 udev 和 selinux设置定义在 /etc/sysctl.conf 中的内核参数设置系统时钟加载 keymaps启用交换分区设置主机名(hostname)根分区检查和 remount激活 RAID 和 LVM 设备开启磁盘配额检查并挂载所有文件系统清除过期的 locks 和 PID 文件最后找到指定 runlevel 下的脚本并执行,其实就是启动服务。除了负责初始化系统,sysvinit 还要负责关闭系统,主要是在系统关闭是为了保证数据的一致性,需要小心地按照顺序进行任务的结束和清理工作。另外,sysvinit 还提供了很多管理和控制系统的命令,比如 halt、init、mesg、shutdown、reboot 等等。sysvinit 的优点是概念简单。特别是服务(service)的配置,只需要把启动/停止服务的脚本链接接到合适的目录就可以了。sysvinit 的另一个重要优点是确定的执行顺序,脚本严格按照顺序执行(sysvinit 靠脚本来初始化系统),一个执行完毕再执行下一个,这非常有益于错误排查。同时,完全顺序执行任务也是 sysvinit 最致命的缺陷。如果 linux 系统只用于服务器系统,那么漫长的启动过程可能并不是什么问题,毕竟我们是不会经常重启服务器的。但是现在 linux 被越来越多的用在了桌面系统中,漫长的启动过程对桌面用户来说是不能接受的。除了启动慢,sysvinit 还有一些其它的缺陷,比如不能很好的处理即插即用的设备,对网络共享磁盘的挂载也存在一定的问题,于是 init 系统开始了它的进化之旅。upstart由于 sysvinit 系统的种种弊端,ubuntu 的开发人员决定重新设计和开发一个全新的 init 系统,即 upstart 。upstart 是第一个被广泛应用的新一代 init 系统。upstart 基于事件机制,比如 U 盘插入 USB 接口后,udev 得到内核通知,发现该设备,这就是一个新的事件。upstart 在感知到该事件之后触发相应的等待任务,比如处理 /etc/fstab 中存在的挂载点。采用这种事件驱动的模式,upstart 完美地解决了即插即用设备带来的新问题。采用事件驱动机制也带来了一些其它有益的变化,比如加快了系统启动时间。sysvinit 运行时是同步阻塞的。一个脚本运行的时候,后续脚本必须等待。这意味着所有的初始化步骤都是串行执行的,而实际上很多服务彼此并不相关,完全可以并行启动,从而减小系统的启动时间。upstart 的特点upstart 解决了之前提到的 sysvinit 的缺点。采用事件驱动模型的 upstart 可以:更快地启动系统当新硬件被发现时动态启动服务硬件被拔除时动态停止服务这些特点使得 upstart 可以很好地应用在桌面或者便携式系统中,处理这些系统中的动态硬件插拔特性。主角 systemd 登场systemd 是 linux 系统中最新的初始化系统(init),它主要的设计目标是克服 sysvinit 固有的缺点,提高系统的启动速度。systemd 和 ubuntu 的 upstart 是竞争对手,但是时至今日 ubuntu 也采用了 systemd,所以 systemd 在竞争中胜出,大有一统天下的趋势。其实,systemd 的很多概念都来源于苹果 Mac OS 操作系统上的 launchd。systemd 的优点是功能强大,使用方便,缺点是体系庞大,非常复杂,下图展示了 systemd 的架构(此图来自互联网):systemd 能够在与 upstart 的竞争中胜出自然有很多过人之处,接下来让我们介绍一些 systemd 的主要优点。兼容性systemd 提供了和 sysvinit 兼容的特性。系统中已经存在的服务和进程无需修改。这降低了系统向 systemd 迁移的成本,使得 systemd 替换现有初始化系统成为可能。启动速度systemd 提供了比 upstart 更激进的并行启动能力,采用了 socket / D-Bus activation 等技术启动服务。一个显而易见的结果就是:更快的启动速度。为了减少系统启动时间,systemd 的目标是:尽可能启动更少的进程尽可能将更多进程并行启动同样地,upstart 也试图实现这两个目标。下图展示了 upstart 相对于 sysvinit 在并发启动这个方面的改进(此图来自互联网):upstart 增加了系统启动的并行性,从而提高了系统启动速度。但是在 upstart 中,有依赖关系的服务还是必须先后启动。比如任务 A,B,(C,D)因为存在依赖关系,所以在这个局部,还是串行执行。systemd 能够更进一步提高并发性,即便对于那些 upstart 认为存在相互依赖而必须串行的服务,比如 Avahi 和 D-Bus 也可以并发启动。从而实现如下图所示的并发启动过程(此图来自互联网):在 systemd 中,所有的任务都同时并发执行,总的启动时间被进一步降低为 T1。可见 systemd 比 upstart 更进一步提高了并行启动能力,极大地加速了系统启动时间。systemd 提供按需启动能力当 sysvinit 系统初始化的时候,它会将所有可能用到的后台服务进程全部启动运行。并且系统必须等待所有的服务都启动就绪之后,才允许用户登录。这种做法有两个缺点:首先是启动时间过长,其次是系统资源浪费。某些服务很可能在很长一段时间内,甚至整个服务器运行期间都没有被使用过。比如 CUPS,打印服务在多数服务器上很少被真正使用到。您可能没有想到,在很多服务器上 SSHD 也是很少被真正访问到的。花费在启动这些服务上的时间是不必要的;同样,花费在这些服务上的系统资源也是一种浪费。systemd 可以提供按需启动的能力,只有在某个服务被真正请求的时候才启动它。当该服务结束,systemd 可以关闭它,等待下次需要时再次启动它。这有点类似于以前系统中的 inetd,并且有很多文章介绍如何把过去 inetd 管理的服务迁移到 systemd。采用 linux 的 cgroups 跟踪和管理进程的生命周期systemd 利用了 Linux 内核的特性即 cgroups 来完成跟踪的任务。当停止服务时,通过查询 cgroups ,systemd 可以确保找到所有的相关进程,从而干净地停止服务。cgroups 已经出现了很久,它主要用来实现系统资源配额管理。cgroups 提供了类似文件系统的接口,使用方便。当进程创建子进程时,子进程会继承父进程的 cgroups 。因此无论服务如何启动新的子进程,所有的这些相关进程都会属于同一个 cgroups ,systemd 只需要简单地遍历指定的 cgroups 即可正确地找到所有的相关进程,将它们一一停止即可。启动挂载点和自动挂载的管理传统的 linux 系统中,用户可以用 /etc/fstab 文件来维护固定的文件系统挂载点。这些挂载点在系统启动过程中被自动挂载,一旦启动过程结束,这些挂载点就会确保存在。这些挂载点都是对系统运行至关重要的文件系统,比如 HOME 目录。和 sysvinit 一样,Systemd 管理这些挂载点,以便能够在系统启动时自动挂载它们。systemd 还兼容 /etc/fstab 文件,您可以继续使用该文件管理挂载点。有时候用户还需要动态挂载点,比如打算访问 DVD 或者 NFS 共享的内容时,才临时执行挂载以便访问其中的内容,而不访问光盘时该挂载点被取消(umount),以便节约资源。传统地,人们依赖 autofs 服务来实现这种功能。systemd 内建了自动挂载服务,无需另外安装 autofs 服务,可以直接使用 systemd 提供的自动挂载管理能力来实现 autofs 的功能。实现事务性依赖关系管理系统启动过程是由很多的独立工作共同组成的,这些工作之间可能存在依赖关系,比如挂载一个 NFS 文件系统必须依赖网络能够正常工作。systemd 虽然能够最大限度地并发执行很多有依赖关系的工作,但是类似"挂载 NFS"和"启动网络"这样的工作还是存在天生的先后依赖关系,无法并发执行。对于这些任务,systemd 维护一个"事务一致性"的概念,保证所有相关的服务都可以正常启动而不会出现互相依赖,以至于死锁的情况。日志服务systemd 自带日志服务 journald,该日志服务的设计初衷是克服现有的 syslog 服务的缺点。比如:syslog 不安全,消息的内容无法验证。每一个本地进程都可以声称自己是 Apache PID 4711,而 syslog 也就相信并保存到磁盘上。数据没有严格的格式,非常随意。自动化的日志分析器需要分析人类语言字符串来识别消息。一方面此类分析困难低效;此外日志格式的变化会导致分析代码需要更新甚至重写。systemd journal 用二进制格式保存所有日志信息,用户使用 journalctl 命令来查看日志信息。无需自己编写复杂脆弱的字符串分析处理程序。systemd journal 的优点如下:    简单性:代码少,依赖少,抽象开销最小。    零维护:日志是除错和监控系统的核心功能,因此它自己不能再产生问题。举例说,自动管理磁盘空间,避免由于日志的不断产生而将磁盘空间耗尽。    移植性:日志文件应该在所有类型的 Linux 系统上可用,无论它使用的何种 CPU 或者字节序。    性能:添加和浏览日志非常快。    最小资源占用:日志数据文件需要较小。    统一化:各种不同的日志存储技术应该统一起来,将所有的可记录事件保存在同一个数据存储中。所以日志内容的全局上下文都会被保存并且可供日后查询。例如一条固件记录后通常会跟随一条内核记录,最终还会有一条用户态记录。重要的是当保存到硬盘上时这三者之间的关系不会丢失。syslog 将不同的信息保存到不同的文件中,分析的时候很难确定哪些条目是相关的。    扩展性:日志的适用范围很广,从嵌入式设备到超级计算机集群都可以满足需求。    安全性:日志文件是可以验证的,让无法检测的修改不再
目录预热管理单个 unit查看系统上的 unit管理不同的操作环境(target unit)检查 unit 之间的依赖性相关的目录和文件systemctl daemon-reload 子命令总结笔者在前文中概要的介绍了 systemd 的基本概念和主要特点。由于 systemd 相关的绝大多数任务都是通过 systemctl 命令管理的,所以本文将集中的介绍 systemctl 命令的用法。注意,本文以 ubuntu 16.04 进行介绍,文中所有的 demo 都在 ubuntu 16.04 中完成。systemctl 命令有两大类功能:控制 systemd 系统管理系统上运行的服务在介绍这些功能前让我们先来热个身,了解些 systemctl 命令自身相关的信息。预热检查 systemd 的版本$ systemctl --version查看 systemd 和 systemctl 程序相关的目录$ whereis systemd$ whereis systemctl确认 1 号进程作为 init 系统,systemd 进程作为系统中的 1 号进程应该是毋庸置疑的,让我们搜索与 systemd 相关的进程:结果让人有点不敢相信自己的眼睛,我们搜到了好几个与 systemd 相关的进程,但是却没有 1 号进程。让我们看看 1 号进程是谁:/sbin/init 是什么?好吧,/sbin/init 只是到 /lib/systemd/systemd 的链接文件,系统的 1 号进程的确是 systemd,只不过在 ubuntu 系统中被起了个别名叫 /sbin/init。管理单个 unitsystemctl 提供了一组子命令来管理单个的 unit,其命令格式为:systemctl [command] [unit]command 主要有:start:立刻启动后面接的 unit。stop:立刻关闭后面接的 unit。restart:立刻关闭后启动后面接的 unit,亦即执行 stop 再 start 的意思。reload:不关闭 unit 的情况下,重新载入配置文件,让设置生效。enable:设置下次开机时,后面接的 unit 会被启动。disable:设置下次开机时,后面接的 unit 不会被启动。status:目前后面接的这个 unit 的状态,会列出有没有正在执行、开机时是否启动等信息。is-active:目前有没有正在运行中。is-enable:开机时有没有默认要启用这个 unit。kill :不要被 kill 这个名字吓着了,它其实是向运行 unit 的进程发送信号。show:列出 unit 的配置。mask:注销 unit,注销后你就无法启动这个 unit 了。unmask:取消对 unit 的注销。我们先通过 prometheus.service 来观察服务类型 unit 的基本信息:输出内容的第一行是对 unit 的基本描述。第二行中的 Loaded 描述操作系统启动时会不会启动这个服务,enabled 表示开机时启动,disabled 表示开机时不启动。而启动该服务的配置文件路径为:/lib/systemd/system/prometheus.service。第三行 中的 Active 描述服务当前的状态,active (running) 表示服务正在运行中。如果是 inactive (dead) 则表示服务当前没有运行。后面则是服务的启动时间。第四行的 Docs 提供了在线文档的地址。下面的 Main PID 表示进程的 ID,接下来是任务的数量,占用的内存和 CPU 资源。再下面的 Cgroup 描述的是 cgrpup 相关的信息,笔者会在后续的文章中详细的介绍。最后是输出的日志信息。关于 unit 的启动状态,除了 enable 和 disable 之外还有:static:这个 unit 不可以自己启动,不过可能会被其它的 enabled 的服务来唤醒。mask:这个 unit 无论如何都无法被启动!因为已经被强制注销。可通过 systemctl unmask 改回原来的状态。关于 unit 的运行状态 Active,除了 active 和 inactive 之外还有:active (exited):仅执行一次就正常结束的服务,目前并没有任何程序在系统中执行。举例来说,开机或者是挂载时才会进行一次的 quotaon 功能,就是这种模式! Quotaon 不需要一直执行,只在执行一次之后,就交给文件系统去自行处理。通常用 bash shell 写的小型服务,大多是属于这种类型。active (waiting):正在执行当中,不过还再等待其他的事件才能继续处理。举例来说,打印的相关服务就是这种状态。enable 和 disable 操作比如我们为 prometheus 服务创建了配置文件 /lib/systemd/system/prometheus.service,然后执行 enable 命令:$ sudo systemctl enable prometheus.service所谓的 enable 就是在 multi-user.target.wants 下面创建了一个链接文件:至于为什么会链接到 multi-user.target.wants 目录下,则是由 prometheus.server 文件中的配置信息决定的。查看 unit 的配置使用 show 子命令可以查看 unit 的详细配置情况:$ sudo systemctl show prometheus.service这里的输出非常多,主要是很多的默认设置也都显示出来了。注销与反注销 unit如果我们想暂时的禁用某个 unit,比如 prometheus.service,可以注销这个 unit,注销之后就无法再启动这个服务了:$ sudo systemctl mask prometheus.service从上图中的输出我们可以看到,所谓的注销就是把 prometheus.service 文件链接到 /dev/null 这个空设备中去了。所以就无法再启动该服务了。下面我们尝试执行一次反注销:$ sudo systemctl unmask prometheus.serviceunmask 操作就是删除掉 mask 操作中创建的链接。查看系统上的 unitsystemctl 提供了子命令可以查看系统上的 unit,命令格式为:systemctl [command] [--type=TYPE] [--all]command 有:list-units:列出当前已经启动的 unit,如果添加 -all 选项会同时列出没有启动的 unit。list-unit-files:根据 /lib/systemd/system/ 目录内的文件列出所有的 unit。--type=TYPE:可以过滤某个类型的 unit。不带任何参数执行 systemctl 命令会列出所有已启动的 unit:系统默认启动的服务是非常多的,上图只截取了前面几行。下面是对输出的介绍:UNIT:项目的名称,包括各个 unit 的类别(看扩展名)。LOAD:开机时 unit 的配置是否被加载。ACTIVE:目前的状态,须与后续的 SUB 搭配!就是我们用 systemctl status 观察时,active的内容。DESCRIPTION:描述信息。注意,systemctl 不加参数,其实等同于 systemctl list-units!下面我们查看系统上一共安装了多少 unit:$ sudo systemctl list-unit-files结果也非常的多,我们仍然只截取一部分结果。这里的 STATE 就是我们前面介绍的 服务的启动状态,有 enable 和 disable、static 和 mask。只查看某种类型的 unit比如我们只想看服务类型的 unit:$ systemctl list-units --type=service --all这次只有以 .service 结尾的 unit 才会出现。如果要看 mount 类型的 unit 指定 --type=mount 就可以了:查看加载失败的 unit如果发现某个 unit 不工作,可以查看是否有 unit 加载失败:$ systemctl --failed管理不同的操作环境(target unit)通过指定 --type=target 就可以用 systemctl list-units 命令查看系统中默认有多少种 target:笔者的 ubuntu 16.04 中居然有 27 个 target!我们在此仅介绍几个常用的 target。graphical.target:就是文字界面再加上图形界面,这个 target 已经包含了下面的 multi-user.target。multi-user.target:纯文本模式!rescue.target:在无法使用 root 登陆的情况下,systemd 在开机时会多加一个额外的临时系统,与你原本的系统无关。这时你可以取得 root 的权限来维护你的系统。emergency.target:紧急处理系统的错误,在无法使用 rescue.target 时,可以尝试使用这种模式!shutdown.target:就是执行关机。getty.target:可以设置 tty 的配置。正常的模式是 multi-user.target 和 graphical.target 两个,救援方面的模式主要是 rescue.target 以及更严重的 emergency.target。如果要修改可提供登陆的 tty 数量,则修改 getty.target。下面是操作 target unit 命令的格式:systemctl [command] [unit.target]command 有:get-default:取得目前的 target。set-default:设置后面接的 target 成为默认的操作模式。isolate:切换到后面接的模式。查看和设置默认的 target$ sudo systemctl get-default笔者系统的默认 target 为 graphical.target,所以启动时默认启动图形界面。下面我们把它设置为 multi-user.target:$ sudo systemctl set-default multi-user.target实际的执行过程是删除之前的链接文件,然后创建新的到 /lib/systemd/system/multi-user.target 的链接文件。这样下次开机时默认会进入纯文本模式。切换 target我们还可以在不重新启动的情况下切换不同的 target,比如从图形界面切换到纯文本的模式:$ sudo systemctl isolate multi-user.target在一般情况下,使用上述 isolate 的方式即可完成不同 target 的切换。不过为了方便起见,systemd 也提供了几个简单的指令用来切换操作模式,大致如下所示:$ sudo systemctl poweroff # 系统关机$ sudo systemctl reboot # 重新开机$ sudo systemctl suspend # 进入暂停模式$ sudo systemctl hibernate # 进入休眠模式$ sudo systemctl rescue # 强制进入救援模式$ sudo systemctl emergency # 强制进入紧急救援模式这里简单介绍一下暂停模式与休眠模式的区别suspend:暂停模式会将系统的状态保存到内存中,然后关闭掉大部分的系统硬件,当然,并没有实际关机。当用户按下唤醒机器的按钮,系统数据会从内存中回复,然后重新驱动被大部分关闭的硬件,所以唤醒系统的速度比较快。hibernate:休眠模式则是将系统状态保存到硬盘当中,保存完毕后,将计算机关机。当用户尝试唤醒系统时,系统会开始正常运行,然后将保存在硬盘中的系统状态恢复回来。因为数据需要从硬盘读取,因此唤醒的速度比较慢(如果你使用的是 SSD 磁盘,唤醒的速度也是非常快的)。检查 unit 之间的依
我们运行 linux 服务器的主要目的是通过运行程序提供服务,比如 mysql、web server等。因此管理 linux 服务器主要工作就是配置并管理上面运行的各种服务程序。在 linux 系统中服务程序的管理主要由 init 系统负责。如同笔者在《初识 systemd》一文中的介绍,linux 的 init 系统已经从最初的 sysvinit 进化到了如今的 systemd。本文主要介绍在 systemd 环境中如何编写运行服务的配置文件。unit(单元)的配置文件Unit 是 systemd 进行任务管理的基本单位,我们在前文中已经介绍过,service 类型的 unit 代表一个后台服务进程。接下来我们就详细的介绍如何配置 service 类型的 unit。下面我们先来看一个简单的服务配置:[Unit]Description=Prometheus ServerDocumentation=https://prometheus.io/docs/introduction/overview/After=network.target[Service]User=prometheusRestart=on-failureWorkingDirectory=/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_configAfter    说明本 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 语法也不支持。所以,要使用这些特殊的字符时,最好直接写入到脚本里面去!ExecStartPre 和 ExecStartPost 分别在服务启动前后,执行额外的命令。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 VarsEnvironment=ETCD_CA_FILE=/path/to/CA.pemEnvironment=ETCD_CERT_FILE=/path/to/server.crtEnvironmentFile 通过文件的方式设置环境变量,可以把下面的内容保存到文件 testenv 中:AAA_IPV4_ANCHOR_0=X.X.X.XBBB_IPV4_PRIVATE_0=X.X.X.XCCC_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 StoreAfter=network.target[Service]User=redisGroup=redisExecStart=/usr/local/bin/redis-server /etc/redis/redis.confExecStop=/usr/local/bin/redis-cli shutdownRestart=always[Install]WantedBy=multi-user.target启动服务并配置为开机启动:$ sudo systemctl start redis$ sudo
目录Help输出所有的日志记录匹配(match)把日志保存到文件中限定日志所能占用的最高容量查看某次启动后的日志查看指定时间段的日志同时应用 match 和时间过滤条件按 unit 过滤日志通过日志级别进行过滤实时更新日志只显示最新的 n 行控制输出按可执行文件的路径过滤查看内核日志总结journalctl 用来查询 systemd-journald 服务收集到的日志。systemd-journald 服务是 systemd init 系统提供的收集系统日志的服务。命令格式为:journalctl [OPTIONS…] [MATCHES…]journalctl 命令的路径为:/bin/journalctlHelp 可以通过 man page 和 -h 选项来获得最直接的帮助文档:$ man journalctl$ journalctl -h输出所有的日志记录不带任何选项时,journalctl 输出所有的日志记录:$ sudo journalctl这基本上没什么用处,因为你立即就被洪水般的日志记录给淹没了。所以,接下来我们学习如何高效的过滤出有价值的日志信息。匹配(match)我们可以通过 "FIELD=VALUE" 的格式来匹配具体的日志记录, 如:_SYSTEMD_UNIT=cron.service日志信息的定义也类似一个实体类型,具体的信息被保存在各个对应的字段中,比如 MESSAGE、MESSAGE_ID、_PID、_UID、_HOSTNAME、_SYSTEMD_UNIT 等等(通过 man 7 systemd.journal-fields 可以查看所有可用的 match 字段)。因此可以通过这些字段的内容匹配相关的日志记录:上图中的输出是 cron.service 服务相关的日志记录。可以同时添加多个字段进行匹配,它们之间是与的关系,就是同时符合多个条件的记录才会被匹配,比如添加 PRIORITY 字段的匹配条件:$ journalctl _SYSTEMD_UNIT=cron.service PRIORITY=6注意各个字段的取值,比如为 PRIORITY 设置 debug、info 是不工作的,必须设置为对应的数字。可以通过 -F 选项来查看某个字段的可选值:$ journal -F PRIORITY具体的对应方式如下:0: emerg1: alert2: crit3: err4: warning5: notice6: info7: debug对同一个字段应用多个 match 条件的情况,比如:$ journalctl _SYSTEMD_UNIT=cron.service _SYSTEMD_UNIT=prometheus.service此时 cron.service 和 prometheus.service 的日志都会输出。多个 match 条件的或操作使用 "+" 号可以对多个匹配字段执行或操作:$ journalctl _SYSTEMD_UNIT=cron.service + _PID=28097上面的命令会输出 cron.service 的日志和进程 28097 的日志。下面是一个更复杂的例子:$ journalctl _SYSTEMD_UNIT=avahi-daemon.service _PID=28097 + _SYSTEMD_UNIT=dbus.service前面的两个条件是与的关系,最后一个条件与前面的两个条件是或的关系,也就是相对于用小括号把前面的两个条件括起来。把日志保存到文件中systemd-journald 服务收集到的日志默认保存在 /run/log 目录中,重启系统会丢掉以前的日志信息。 我们可以通过两种方式让 systemd-journald 服务把所有的日志都保存到文件中,这样重新启动后就不会丢掉以前的日志。方法一:创建目录 /var/log/journal,然后重启日志服务 systemd-journald.service。方法二:修改配置文件 /etc/systemd/journald.conf,把 Storage=auto 改为 Storage=persistent,并取消注释,然后重启日志服务 systemd-journald.service。方法一的详细操作在 /var/log/ 下面创建名为 journal 的目录,并设置权限即可:$ sudo mkdir /var/log/journal$ sudo chown root:systemd-journal /var/log/journal$ sudo chmod 2775 /var/log/journal$ sudo systemctl restart systemd-journald.service之后 /run/log 下面就没有 journal 的日志了,日志文件被保存在 /var/log/journal 目录下:查看日志占据的磁盘空间$ sudo journalctl --disk-usage注意:无论是否设置把日志存储到文件,都会得到 disk-usage。清理日志数据如果大家打算对 journal 记录进行清理,则可使用两种不同方式。使用 –vacuum-size 选项使用 –vacuum-time 选项如果使用 –vacuum-size 选项,则可硬性指定日志的总体体积,意味着其会不断删除旧有记录直到所占容量符合要求:$ sudo journalctl --vacuum-size=1G另一种方式则是使用 –vacuum-time 选项。任何早于这一时间点的条目都将被删除。例如,去年之后的条目才能保留:$ sudo journalctl --vacuum-time=1years限定日志所能占用的最高容量我们可以通过 /etc/systemd/journald.conf 文件来配置 systemd-journald 服务的行为。以下条目可用于限定日志数据可以占用的最大存储数量和日志数据体积的膨胀速度:SystemMaxUse=:指定journal所能使用的最高持久存储容量。SystemKeepFree=:指定journal在添加新条目时需要保留的剩余空间。SystemMaxFileSize=:控制单一journal文件大小,符合要求方可被转为持久存储。RuntimeMaxUse=:指定易失性存储中的最大可用磁盘容量(/run文件系统之内)。RuntimeKeepFree=:指定向易失性存储内写入数据时为其它应用保留的空间量(/run文件系统之内)。RuntimeMaxFileSize=:指定单一journal文件可占用的最大易失性存储容量(/run文件系统之内)。通过设置上述值,大家可以控制 systemd-journald 服务对服务器空间的消耗及保留方式。查看某次启动后的日志默认情况下 systemd-journald 服务只保存本次启动后的日志(重新启动后丢掉以前的日志)。此时 -b 选项是没啥用的。当我们把 systemd-journald 服务收集到的日志保存到文件中之后,就可以通过下面的命令查看系统的重启记录:$ journalctl --list-boots此时我们就可以通过 -b 选项来选择查看某次运行过程中的日志:$ sudo journalctl -b -1或$ sudo journalctl -b 9eaabbc25fe343999ef1024e6a16fb58下面的命令都会输出最后一次启动后的日志信息:$ sudo journalctl -b$ sudo journalctl -b 0查看指定时间段的日志利用 --since 与 --until 选项设定时间段,二者分别负责指定给定时间之前与之后的日志记录。时间值可以使用多种格式,比如下面的格式:YYYY-MM-DD HH:MM:SS如果我们要查询 2018 年 3 月 26 日下午 8:20 之后的日志:$ journalctl --since "2018-03-26 20:20:00"如果以上格式中的某些组成部分未进行填写,系统会直接进行默认填充。例如,如果日期部分未填写,则会直接显示当前日期。如果时间部分未填写,则缺省使用 "00:00:00"(午夜)。秒字段亦可留空,默认值为 "00",比如下面的命令:$ journalctl --since "2018-03-26" --until "2018-03-26 03:00"另外,journalctl 还能够理解部分相对值及命名简写。例如,大家可以使用 "yesterday"、"today"、"tomorrow" 或者 "now" 等。比如获取昨天的日志数据可以使用下面的命令:$ journalctl --since yesterday要获得早上 9:00 到一小时前这段时间内的日志,可以使用下面的命令:$ journalctl --since 09:00 --until "1 hour ago"同时应用 match 和时间过滤条件实际的使用中更常见的用例是同时应用 match 和时间条件,比如要过滤出某个时间段中 cron 服务的日志记录:$ sudo journalctl _SYSTEMD_UNIT=cron.service --since "2018-03-27" --until "2018-03-27 01:00"按 unit 过滤日志systemd 把几乎所有的任务都抽象成了 unit,因此我们可以方便的使用 -u 选项通过 unit 的名称来过滤器日志记录。查看某个 unit 的日志:$ sudo journalctl -u nginx.service$ sudo journalctl -u nginx.service --since today还可以使用多个 -u 选项同时获得多个 unit 的日志:$ journalctl -u nginx.service -u php-fpm.service --since today通过日志级别进行过滤除了通过 PRIORITY= 的方式,还可以通过 -p 选项来过滤日志的级别。 可以指定的优先级如下:# 0: emerg# 1: alert# 2: crit# 3: err# 4: warning# 5: notice# 6: info# 7: debug$ sudo journalctl -p err注意,这里指定的是优先级的名称。实时更新日志与 tail -f 类似,journalctl 支持 -f 选项来显示实时的日志:$ sudo journalctl -f如果要查看某个 unit 的实时日志,再加上 -u 选项就可以了:$ sudo journalctl -f -u prometheus.service只显示最新的 n 行命令行选项 -n 用来控制只显示最新的 n 行日志,默认是显示尾部的最新 10 行日志:$ sudo journalctl -n也可以显示尾部指定行数的日志:$ sudo journalctl -n 20下面则是显示 cron.service 服务最新的三行日志:$ journalctl -u cron.service -n 3控制输出把结果重定向到标准输出默认情况下,journalctl 会在 pager 内显示输出结果。如果大家希望利用文本操作工具对数据进行处理,则需要使用标准输出。在这种情况下,我们需要使用 --no-pager 选项。$ sudo journalctl --no-pager这样就可以把结果重定向到我们需要的地方(一般是磁盘文件或者是文本工具)。格式化输出的结果如果大家需要对日志记录进行处理,可能需要使用更易使用的格式以简化数据解析工作。幸运的是,journalctl 能够以多种格式进行显示,只须添加 -o 选项即可。-o 选项支持的类型如下:    short       这是默认的格式,即经典的 syslog 输出格式。    short-iso       与 short 类
cgroups(Control Groups) 是 linux 内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。简单说,cgroups 可以限制、记录任务组所使用的物理资源。本质上来说,cgroups 是内核附加在程序上的一系列钩子(hook),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。本文以 Ubuntu 16.04 系统为例介绍 cgroups,所有的 demo 均在该系统中演示。为什么要了解 cgroups在以容器技术为代表的虚拟化技术大行其道的时代了解 cgroups 技术是非常必要的!比如我们可以很方便的限制某个容器可以使用的 CPU、内存等资源,这究竟是如何实现的呢?通过了解 cgroups 技术,我们可以窥探到 linux 系统中整个资源限制系统的脉络。从而帮助我们更好的理解和使用 linux 系统。cgroups 的主要作用实现 cgroups 的主要目的是为不同用户层面的资源管理提供一个统一化的接口。从单个任务的资源控制到操作系统层面的虚拟化,cgroups 提供了四大功能:资源限制:cgroups 可以对任务是要的资源总额进行限制。比如设定任务运行时使用的内存上限,一旦超出就发 OOM。优先级分配:通过分配的 CPU 时间片数量和磁盘 IO 带宽,实际上就等同于控制了任务运行的优先级。资源统计:cgoups 可以统计系统的资源使用量,比如 CPU 使用时长、内存用量等。这个功能非常适合当前云端产品按使用量计费的方式。任务控制:cgroups 可以对任务执行挂起、恢复等操作。相关概念Task(任务) 在 linux 系统中,内核本身的调度和管理并不对进程和线程进行区分,只是根据 clone 时传入的参数的不同来从概念上区分进程和线程。这里使用 task 来表示系统的一个进程或线程。Cgroup(控制组) cgroups 中的资源控制以 cgroup 为单位实现。Cgroup 表示按某种资源控制标准划分而成的任务组,包含一个或多个子系统。一个任务可以加入某个 cgroup,也可以从某个 cgroup 迁移到另一个 cgroup。Subsystem(子系统) cgroups 中的子系统就是一个资源调度控制器(又叫 controllers)。比如 CPU 子系统可以控制 CPU 的时间分配,内存子系统可以限制内存的使用量。以笔者使用的 Ubuntu 16.04.3 为例,其内核版本为 4.10.0,支持的 subsystem 如下( cat /proc/cgroups): blkio         对块设备的 IO 进行限制。 cpu           限制 CPU 时间片的分配,与 cpuacct 挂载在同一目录。 cpuacct     生成 cgroup 中的任务占用 CPU 资源的报告,与 cpu 挂载在同一目录。 cpuset       给 cgroup 中的任务分配独立的 CPU(多处理器系统) 和内存节点。 devices     允许或禁止 cgroup 中的任务访问设备。 freezer      暂停/恢复 cgroup 中的任务。 hugetlb     限制使用的内存页数量。               memory    对 cgroup 中的任务的可用内存进行限制,并自动生成资源占用报告。 net_cls      使用等级识别符(classid)标记网络数据包,这让 Linux 流量控制器(tc 指令)可以识别来自特定 cgroup 任务的数据包,并进行网络限制。 net_prio    允许基于 cgroup 设置网络流量(netowork traffic)的优先级。 perf_event  允许使用 perf 工具来监控 cgroup。 pids          限制任务的数量。Hierarchy(层级) 层级有一系列 cgroup 以一个树状结构排列而成,每个层级通过绑定对应的子系统进行资源控制。层级中的 cgroup 节点可以包含零个或多个子节点,子节点继承父节点挂载的子系统。一个操作系统中可以有多个层级。cgroups 的文件系统接口cgroups 以文件的方式提供应用接口,我们可以通过 mount 命令来查看 cgroups 默认的挂载点:$ mount | grep cgroup第一行的 tmpfs 说明 /sys/fs/cgroup 目录下的文件都是存在于内存中的临时文件。第二行的挂载点 /sys/fs/cgroup/systemd 用于 systemd 系统对 cgroups 的支持,相关内容笔者今后会做专门的介绍。其余的挂载点则是内核支持的各个子系统的根级层级结构。需要注意的是,在使用 systemd 系统的操作系统中,/sys/fs/cgroup 目录都是由 systemd 在系统启动的过程中挂载的,并且挂载为只读的类型。换句话说,系统是不建议我们在 /sys/fs/cgroup 目录下创建新的目录并挂载其它子系统的。这一点与之前的操作系统不太一样。下面让我们来探索一下 /sys/fs/cgroup 目录及其子目录下都是些什么:/sys/fs/cgroup 目录下是各个子系统的根目录。我们以 memory 子系统为例,看看 memory 目录下都有什么?这些文件就是 cgroups 的 memory 子系统中的根级设置。比如 memory.limit_in_bytes 中的数字用来限制进程的最大可用内存,memory.swappiness 中保存着使用 swap 的权重等等。既然 cgroups 是以这些文件作为 API 的,那么我就可以通过创建或者是修改这些文件的内容来应用 cgroups。具体该怎么做呢?比如我们怎么才能限制某个进程可以使用的资源呢?接下来我们就通过简单的 demo 来演示如何使用 cgroups 限制进程可以使用的资源。查看进程所属的 cgroups可以通过 /proc/[pid]/cgroup 来查看指定进程属于哪些 cgroup:每一行包含用冒号隔开的三列,他们的含义分别是:cgroup 树的 ID, 和 /proc/cgroups 文件中的 ID 一一对应。和 cgroup 树绑定的所有 subsystem,多个 subsystem 之间用逗号隔开。这里 name=systemd 表示没有和任何 subsystem 绑定,只是给他起了个名字叫 systemd。进程在 cgroup 树中的路径,即进程所属的 cgroup,这个路径是相对于挂载点的相对路径。既然 cgroups 是以这些文件作为 API 的,那么我就可以通过创建或者是修改这些文件的内容来应用 cgroups。具体该怎么做呢?比如我们怎么才能限制某个进程可以使用的资源呢?接下来我们就通过简单的 demo 来演示如何使用 cgroups 限制进程可以使用的资源。cgroups 工具在介绍通过 systemd 应用 cgroups 之前,我们先使用 cgroup-bin 工具包中的 cgexec 来演示 demo。Ubuntu 默认没有安装 cgroup-bin 工具包,请通过下面的命令安装:$ sudo apt install cgroup-bindemo:限制进程可用的 CPU在我们使用 cgroups 时,最好不要直接在各个子系统的根目录下直接修改其配置文件。推荐的方式是为不同的需求在子系统树中定义不同的节点。比如我们可以在 /sys/fs/cgroup/cpu 目录下新建一个名称为 nick_cpu 的目录:$ cd /sys/fs/cgroup/cpu$ sudo mkdir nick_cpu然后查看新建的目录下的内容:是不是有点吃惊,cgroups 的文件系统会在创建文件目录的时候自动创建这些配置文件!让我们通过下面的设置把 CPU 周期限制为总量的十分之一:$ sudo su$ echo 100000 > nick_cpu/cpu.cfs_period_us$ echo 10000 > nick_cpu/cpu.cfs_quota_us上面的两个参数眼熟吗?没错,笔者在《Docker: 限制容器可用的 CPU》一文中介绍的 "--cpu-period=100000 --cpu-quota=200000" 就是由它们实现的。然后创建一个 CPU 密集型的程序:void main(){unsigned int i, end;end = 1024 * 1024 * 1024;for(i = 0; i < end; ){i ++;}}保存为文件 cputime.c 编译并通过不同的方式执行:$ gcc cputime.c -o cputime$ sudo su$ time ./cputime$ time cgexec -g cpu:nick_cpu ./cputimetime 命令可以为我们报告程序执行消耗的时间,其中的 real 就是我们真实感受到的时间。使用 cgexec 能够把我们添加的 cgroup 配置 nick_cpu 应用到运行 cputime 程序的进程上。 上图显示,默认的执行只需要 2s 左右。通过 cgroups 限制 CPU 资源后需要运行 23s。demo:限制进程可用的内存这次我们来限制进程可用的最大内存,在 /sys/fs/cgroup/memory 下创建目录nick_memory:$ cd /sys/fs/cgroup/memory$ sudo mkdir nick_memory下面的设置把进程的可用内存限制在最大 300M,并且不使用 swap:# 物理内存 + SWAP <= 300 MB;1024*1024*300 = 314572800$ sudo su$ echo 314572800 > nick_memory/memory.limit_in_bytes$ echo 0 > nick_memory/memory.swappiness然后创建一个不断分配内存的程序,它分五次分配内存,每次申请 100M:#include<stdio.h>#include<stdlib.h>#include<string.h>#define CHUNK_SIZE 1024 * 1024 * 100void main(){char *p;int i;for(i = 0; i < 5; i ++){p = malloc(sizeof(char) * CHUNK_SIZE);if(p == NULL){printf("fail to malloc!");return ;}// memset() 函数用来将指定内存的前 n 个字节设置为特定的值memset(p, 0, CHUNK_SIZE);printf("malloc memory %d MBn", (i + 1) * 100);}}把上面的代码保存为 mem.c 文件,然后编译:$ gcc mem.c -o mem执行生成的 mem 程序:$ ./mem此时一切顺利,然后加上刚才的约束试试:$ cgexec -g memory:nick_memory ./mem由于内存不足且禁止使用 swap,所以被限制资源的进程在申请内存时被强制杀死了。下面再使用 stress 程序测试一个类似的场景(通过 stress 程序申请 500M 的内存):$ sudo cgexec -g memory:nick_memory stress --vm 1 --vm-bytes 500000000 --vm-keep --verbosestress 程序能够提供比较详细的信息,进程被杀掉的方式是收到了 SIGKILL(signal 9) 信号。实际应用中往往要同时限制多种的资源,比如既限制 CPU 资源又限制内存资源。使用 cgexec 实现这样的用例其实很简单,直接指定多个 -g 选项就可以了:$ cgexec -g cpu:nick_cpu -g memory:nick_memory
Cgroups 是 linux 内核提供的一种机制,如果你还不了解 cgroups,请参考前文《Linux cgroups 简介》先了解 cgroups。当 Linux 的 init 系统发展到 systemd 之后,systemd 与 cgroups 发生了融合(或者说 systemd 提供了 cgroups 的使用和管理接口,systemd 管的东西越来越多啊!)。本文将简单的介绍 cgroups 与 systemd 的关系以及如何通过 systemd 来配置和使用 cgroups。Systemd 依赖 cgroups要理解 systemd 与 cgroups 的关系,我们需要先区分 cgroups 的两个方面:层级结构(A)和资源控制(B)。首先 cgroups 是以层级结构组织并标识进程的一种方式,同时它也是在该层级结构上执行资源限制的一种方式。我们简单的把 cgroups 的层级结构称为 A,把 cgrpups 的资源控制能力称为 B。对于 systemd 来说,A 是必须的,如果没有 A,systemd 将不能很好的工作。而 B 则是可选的,如果你不需要对资源进行控制,那么在编译 Linux 内核时完全可以去掉 B 相关的编译选项。Systemd 默认挂载的 cgroups 系统在系统的开机阶段,systemd 会把支持的 controllers (subsystem 子系统)挂载到默认的 /sys/fs/cgroup/ 目录下面:除了 systemd 目录外,其它目录都是对应的 subsystem。/sys/fs/cgroup/systemd 目录是 systemd 维护的自己使用的非 subsystem 的 cgroups 层级结构。这玩意儿是 systemd 自己使用的,换句话说就是,并不允许其它的程序动这个目录下的内容。其实 /sys/fs/cgroup/systemd 目录对应的 cgroups 层级结构就是 systemd 用来使用 cgoups 中 feature A 的。Cgroup 的默认层级通过将 cgroup 层级系统与 systemd unit 树绑定,systemd 可以把资源管理的设置从进程级别移至应用程序级别。因此,我们可以使用 systemctl 指令,或者通过修改 systemd unit 的配置文件来管理 unit 相关的资源。默认情况下,systemd 会自动创建 slice、scope 和 service unit 的层级(slice、scope 和 service 都是 systemd 的 unit 类型,参考《初识 systemd》),来为 cgroup 树提供统一的层级结构。系统中运行的所有进程,都是 systemd init 进程的子进程。在资源管控方面,systemd 提供了三种 unit 类型:service: 一个或一组进程,由 systemd 依据 unit 配置文件启动。service 对指定进程进行封装,这样进程可以作为一个整体被启动或终止。scope:一组外部创建的进程。由进程通过 fork() 函数启动和终止、之后被 systemd 在运行时注册的进程,scope 会将其封装。例如:用户会话、 容器和虚拟机被认为是 scope。slice: 一组按层级排列的 unit。slice 并不包含进程,但会组建一个层级,并将 scope 和 service 都放置其中。真正的进程包含在 scope 或 service 中。在这一被划分层级的树中,每一个 slice 单位的名字对应通向层级中一个位置的路径。我们可以通过 systemd-cgls 命令来查看 cgroups 的层级结构:service、scope 和 slice unit 被直接映射到 cgroup 树中的对象。当这些 unit 被激活时,它们会直接一一映射到由 unit 名建立的 cgroup 路径中。例如,cron.service 属于 system.slice,会直接映射到 cgroup system.slice/cron.service/ 中。注意,所有的用户会话、虚拟机和容器进程会被自动放置在一个单独的 scope 单元中。默认情况下,系统会创建四种 slice:-.slice:根 slicesystem.slice:所有系统 service 的默认位置user.slice:所有用户会话的默认位置machine.slice:所有虚拟机和 Linux 容器的默认位置创建临时的 cgroup对资源管理的设置可以是 transient(临时的),也可以是 persistent (永久的)。我们先来介绍如何创建临时的 cgroup。需要使用 systemd-run 命令创建临时的 cgroup,它可以创建并启动临时的 service 或 scope unit,并在此 unit 中运行程序。systemd-run 命令默认创建 service 类型的 unit,比如我们创建名称为 toptest 的 service 运行 top 命令:$ sudo systemd-run --unit=toptest --slice=test top -b然后查看一下 test.slice 的状态:创建了一个 test.slice/toptest.service cgroup 层级关系。再看看 toptest.service 的状态:top 命令被包装成一个 service 运行在后台了!接下来我们就可以通过 systemctl 命令来限制 toptest.service 的资源了。在限制前让我们先来看一看 top 进程的 cgroup 信息:$ vim /proc/2850/cgroup # 2850 为 top 进程的 PID比如我们限制 toptest.service 的 CPUShares 为 600,可用内存的上限为 550M:$ sudo systemctl set-property toptest.service CPUShares=600 MemoryLimit=500M再次检查 top 进程的 cgroup 信息:在 CPU 和 memory 子系统中都出现了 toptest.service 的名字。同时去查看 /sys/fs/cgroup/memory/test.slice 和 /sys/fs/cgroup/cpu/test.slice 目录,这两个目录下都多出了一个 toptest.service 目录。我们设置的 CPUShares=600 MemoryLimit=500M 被分别写入了这些目录下的对应文件中。临时 cgroup 的特征是,所包含的进程一旦结束,临时 cgroup 就会被自动释放。比如我们 kill 掉 top 进程,然后再查看 /sys/fs/cgroup/memory/test.slice 和 /sys/fs/cgroup/cpu/test.slice 目录,刚才的 toptest.service 目录已经不见了。通过配置文件修改 cgroup所有被 systemd 监管的 persistent cgroup(持久的 cgroup)都在 /usr/lib/systemd/system/ 目录中有一个 unit 配置文件。比如我们常见的 service 类型 unit 的配置文件。我们可以通过设置 unit 配置文件来控制应用程序的资源,persistent cgroup 的特点是即便系统重启,相关配置也会被保留。需要注意的是,scope unit 不能以此方式创建。下面让我们为 cron.service 添加 CPU 和内存相关的一些限制,编辑 /lib/systemd/system/cron.service 文件:$ sudo vim /lib/systemd/system/cron.service添加红框中的行,然后重新加载配置文件并重启 cron.service:$ sudo systemctl daemon-reload$ sudo systemctl restart cron.service现在去查看 /sys/fs/cgroup/memory/system.slice/cron.service/memory.limit_in_bytes 和 /sys/fs/cgroup/cpu/system.slice/cron.service/cpu.shares 文件,是不是已经包含我们配置的内容了!通过 systemctl 命令修改 cgroup除了编辑 unit 的配置文件,还可以通过 systemctl set-property 命令来修改 cgroup,这种方式修该的配置也会在重启系统时保存下来。现在我们把 cron.service 的 CPUShares 改为 700:$ sudo systemctl set-property cron.service CPUShares=700查看 /sys/fs/cgroup/cpu/system.slice/cron.service/cpu.shares 文件的内容应该是 700,重启系统后该文件的内容还是 700。Systemd-cgtop 命令类似于 top 命令,systemd-cgtop 命令显示 cgoups 的实时资源消耗情况:通过它我们就可以分析应用使用资源的情况。总结Systemd 是一个强大的 init 系统,它甚至为我们使用 cgorups 提供了便利!Systemd 提供的内在机制、默认设置和相关的操控命令降低了配置和使用 cgroups 的难度,即便是 Linux 新手,也能轻松的使用 cgroups 了。参考:The New Control Group Interfacessystemd for Administrators, Part XVIIIControl Groups vs. Control GroupsRedHat Cgroups docSystemd-cglsSystemd-cgtop
早上群上讨论了一下systemd的作用,还导致了一个人的直接退群,出于求知心理,搜索了一些systemd,对此也作出了一些相应的整理; 一、systemd的诞生: 学习嵌入式bootloader与ke