systemd 命令与 sysvinit 命令对比

编程之家收集整理的这篇文章主要介绍了systemd 命令与 sysvinit 命令对比编程之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
                                        <table&gt;
sysvinit 命令 systemd 命令 说明 stemctl start httpd.servicestemctl stop httpd.service关闭httpd服务stemctl restart httpd.servicestemctl reload httpd.servicestemctl status httpd.servicefig httpd onstemctl on httpd.servicefig httpd offstemctl off httpd.service禁止httpd服务开机自启动fig httpdstemctl is-enable httpd.servicefig httpd --liststemd/system/*.wants/httpd.servicefig httpd --addstemctl daemon-reload一个新服务文件或者变更配置时使用fig --liststemctl list-unit-files --type=service输出在各个运行级别下所有服务的启用和禁用情况

总结

以上是编程之家为你收集整理的systemd 命令与 sysvinit 命令对比全部内容,希望文章能够帮你解决systemd 命令与 sysvinit 命令对比所遇到的程序开发问题。

如果觉得编程之家网站内容还不错,欢迎将编程之家网站推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
喜欢与人分享编程技术与工作经验,欢迎加入编程之家官方交流群!

相关文章

猜你在找的sysTemd相关文章

## 新建一个服务 `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...
引入简介saltstack是由thomas Hatch于2011年创建的一个开源项目,设计初衷是为了实现一个快速的远程执行系统。 早期运维人员会根据自己的生产环境来写特定脚本完成大量重复性工作,这些脚本复杂且难以维护。系统管理员面临的问题主要是1、系统配置管理,2、远程执行命令,因此诞生了很多开源软件,系统维护方面有fabric、puppet、chef、ansible、saltstack等,这些软件擅长维护系统状态或方便的对大量主机进行批量的命令执行。salt灵活性强大,可以进行大规模部署,也能进行小规模的系统部署。salt的设计架构适用于任意数量的服务器,从少量本地网络系统到跨越数个数据中心,拓扑架构都是c/s模型,配置简单。不管是几台、几百台、几千台服务器,都可以使用salt在一个中心节点上进行管控,灵活定位任意服务器子集来运行命令。Salt是python编写的,支持用户通过python自定义功能模块,也提供了大量的python API接口,用户可以根据需要进行简单快速的扩展。 saltstack的运维方式Local  本地运行,交付管理Master/Minion (常用方式 )Salt SSH   不需要客户端salt基本架构在安装salt之前,先理解salt架构中各个角色,主要区分是salt-master和salt-minion,顾名思义master是中心控制系统,minion是被管理的客户端。salt架构中的一种就是master > minion。服务器环境准备服务器环境centos7(master)centos7(master)ip地址192.168.178.131192.168.178.132身份masterslave软件包salt-mastersalt-minion关闭服务器安全策略# 关闭firewalldsystemctl disable firewalldsystemctl stop firewalld# 关闭iptablesiptables -F# 关闭selinux安装saltstack注意:salt软件包需要epel源的支持, 如果没有安装epel源 <点这里>有教程在 192.168.178.131 中安装salt-masteryum install salt-master -y另一台安装salt-minionyum install salt-minion -ysalt端口安装好salt之后开始配置,salt-master默认监听两个端口:4505 publish_port # 提供远程命令发送功能4506 ret_port # 提供认证,文件服务,结果收集等功能# 确保客户端可以通信服务器的此2个端口,保证防火墙允许端口通过。因此在测试环境直接关闭防火墙。配置文件salt-master的配置文件是/etc/salt/master # 主机 192.168.178.131salt-minion的配置文件是/etc/salt/minion # 主机 192.168.178.132# 配置文件中包含了大量可调整的参数,这些参数控制master和minion各个方面 写入salt-master配置文件如下:interface: 0.0.0.0 # 绑定到本地的0.0.0.0地址publish_port: 4505user: rootworker_threads: 5ret_port: 4506pidfile: /var/run/salt-master.pidlog_file: /var/log/salt/master# 自动接收minion的key# auto_accept: False# salt运行的用户,影响到salt的执行权限user: root#s alt的运行线程,开的线程越多一般处理的速度越快,但一般不要超过CPU的个数worker_threads: 10# master的管理端口publish_port : 4505# master跟minion的通讯端口,用于文件服务,认证,接受返回结果等ret_port : 4506# 如果这个master运行的salt-syndic连接到了一个更高层级的master,那么这个参数需要配置成连接到的这个高层级master的监听端口syndic_master_port : 4506# 指定pid文件位置pidfile: /var/run/salt-master.pidsalt-master配置参数详解写入sale-master配置文件如下:master: 192.168.178.132 # 填自己的端口或者域名master_port: 4506user: rootid: slave # id自定义acceptance_wait_time: 10log_file: /var/log/salt/minion# minion的识别ID,可以是IP,域名,或是可以通过DNS解析的字符串id: slave# salt运行的用户权限user: root# master的识别ID,可以是IP,域名,或是可以通过DNS解析的字符串master : master# master通信端口master_port: 4506# 备份模式,minion是本地备份,当进行文件管理时的文件备份模式backup_mode: minion# 执行salt-call时候的输出方式output: nested# minion等待master接受认证的时间acceptance_wait_time: 10# 失败重连次数,0表示无限次,非零会不断尝试到设置值后停止尝试acceptance_wait_time_max: 0# 重新认证延迟时间,可以避免因为master的key改变导致minion需要重新认证的syn风暴random_reauth_delay: 60# 日志文件位置log_file: /var/logs/salt_minion.logsalt-minion配置参数详解启动# 192.168.178.131中:systemctl start salt-master # 启动mastersystemctl status salt-master # 检查start状态====================================# 192.168.178.132systemctl start salt-minion # 启动minionsystemctl status salt-minion # 检查状态注意:如果启动报错,查看是否有 /var/log/salt/ 这个文件在master上接收minion密钥在minion启动后连接master会请求master为其签发证书,等待证书签发完成后,master可以信任minion,并且minion和master之间的通信是加密的。在salt-master执行salt-key命令用于管理mionion秘钥[root@master ~]# salt-key -L# 输出结果:Accepted Keys:Denied Keys:Unaccepted Keys:slave# 此时已经出现slaveRejected Keys:此时slave已经出现在unaccepted keys中,说明minion已经和master联系,并且master已经获取了minion的公钥,等待下一步指令。密钥匹配在master上执行:[root@master ~]# salt-key -f 192.162.178.132Unaccepted Keys:slave: 0a:b9:97:b5:9a:65:b8:49:a1:50:a5:6a:66:ce:33:ea然后在minion上获取密钥:[root@slave ~]# salt-call --local key.fingerlocal:0a:b9:97:b5:9a:65:b8:49:a1:50:a5:6a:66:ce:33:ea此时可确认密钥匹配,在master上接收密钥[root@master ~]# salt-key -a slaveThe following keys are going to be accepted:Unaccepted Keys:slaveProceed? [n/Y] yKey for minion slave accepted.确认接收秘钥后,检验minion秘钥是否被接收[root@master ~]# salt-key -LAccepted Keys:slave # 表示接收成功Denied Keys:Unaccepted Keys:Rejected Keys:好了,配置完成,可以执行命令了。常用命令salt-key只有master接收了minion key后才能进行管理。[root@master ~]# salt-key -LAccepted Keys: # 已经接受的keyDenied Keys: # 拒绝的keyUnaccepted Keys:# 未加入的keyRejected Keys:# 吊销的key#常用参数-L # 查看KEY状态-A # 允许所有-D # 删除所有-a # 认证指定的key-d # 删除指定的key-r # 注销掉指定key(该状态为未被认证)# 在master端/etc/salt/master配置auto_accept: True # 如果对Minion信任,可以配置master自动接受请求常用命令参数:rpm -ql salt-master/etc/salt/master # salt master主配置文件/usr/bin/salt # salt master 核心操作命令/usr/bin/salt-cp # salt 文件传输命令/usr/bin/salt-key # salt证书管理/usr/bin/salt-master # salt master 服务命令/usr/bin/salt-run # salt master runner命令master端rpm -ql salt-minion/etc/salt/minion # minion配置文件/usr/bin/salt-call # 拉取命令/usr/bin/salt-minion # minion服务命令/usr/lib/systemd/system/salt-minion.service # minion启动脚本slave端salt命令:[root@master ~]# salt '*' test.ping # 检测所有minion主机是否存活slave:True# salt 是一个命令# * 表示目标主机, 在这里代表所有目标主机# test.ping是salt远程执行的一个模块下面的方法。[root@master ~]$salt '*' test.fib 50 # 生成斐波那契数列[root@master ~]$salt 'slave' sys.list_modules # 列出所有salt的sys模块[root@master ~]$salt '*' sys.list_functions testslave:- test.arg- test.arg_repr- test.arg_type- test.assertion- test.attr_call- test.collatz- test.conf_test- test.cross_test- test.echo- test.exception- test.fib- test.get_opts- test.kwarg- test.module_report- test.not_loaded- test.opts_pkg- test.outputter- test.ping- test.provider- test.providers- test.rand_sleep- test.rand_str- test
从 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 将不同的信息保存到不同的文件中,分析的时候很难确定哪些条目是相关的。    扩展性:日志的适用范围很广,从嵌入式设备到超级计算机集群都可以满足需求。    安全性:日志文件是可以验证的,让无法检测的修改不再
我们运行 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
chroot,即 change root directory (更改 root 目录)。在 linux 系统中,系统默认的目录结构都是以 /,即以根 (root) 开始的。而在使用 chroot 之后,系统的目录结构将以指定的位置作为 / 位置。基本语法chroot NEWROOT [COMMAND [ARG]...]具体用法请参考本文的 demo。为什么要使用 chroot 命令增加了系统的安全性,限制了用户的权力:在经过 chroot 之后,在新根下将访问不到旧系统的根目录结构和文件,这样就增强了系统的安全性。一般会在用户登录前应用 chroot,把用户的访问能力控制在一定的范围之内。建立一个与原系统隔离的系统目录结构,方便用户的开发:使用 chroot 后,系统读取的是新根下的目录和文件,这是一个与原系统根下文件不相关的目录结构。在这个新的环境中,可以用来测试软件的静态编译以及一些与系统不相关的独立开发。切换系统的根目录位置,引导 Linux 系统启动以及急救系统等:chroot 的作用就是切换系统的根位置,而这个作用最为明显的是在系统初始引导磁盘的处理过程中使用,从初始 RAM 磁盘 (initrd) 切换系统的根位置并执行真正的 init,本文的最后一个 demo 会详细的介绍这种用法。通过 chroot 运行 busybox 工具busybox 包含了丰富的工具,我们可以把这些工具放置在一个目录下,然后通过 chroot 构造出一个 mini 系统。简单起见我们直接使用 docker 的 busybox 镜像打包的文件系统。先在当前目录下创建一个目录 rootfs:$ mkdir rootfs然后把 busybox 镜像中的文件释放到这个目录中:$ (docker export $(docker create busybox) | tar -C rootfs -xvf -)通过 ls 命令查看 rootfs 文件夹下的内容:$ ls rootfs万事俱备,让我们开始吧!执行 chroot 后的 ls 命令$ sudo chroot rootfs /bin/ls虽然输出结果与刚才执行的 ls rootfs 命令形同,但是这次运行的命令却是 rootfs/bin/ls。运行 chroot 后的 pwd 命令$ sudo chroot rootfs /bin/pwd哈,pwd 命令真把 rootfs 目录当根目录了!不带命令执行 chroot$ sudo chroot rootfs这次出错了,因为找不到 /bin/bash。我们知道 busybox 中是不包含 bash 的,但是 chroot 命令为什么会找 bash 命令呢? 原来,如果不给 chroot 指定执行的命令,默认它会执行 '${SHELL} -i',而我的系统中 ${SHELL} 为 /bin/bash。既然 busybox 中没有 bash,我们只好指定 /bin/sh 来执行 shell 了。$ sudo chroot rootfs /bin/sh运行 sh 是没有问题的,并且我们打印出了当前进程的 PID。检查程序是否运行在 chroot 环境下虽然我们做了好几个实验,但是肯定会有朋友心存疑问,怎么能证明我们运行的命令就是在 chroot 目录后的路径中呢?其实,我们可以通过 /proc 目录下的文件检查进程的中的根目录,比如我们可以通过下面的代码检查上面运行的 /bin/sh 命令的根目录(请在另外一个 shell 中执行):$ pid=$(pidof -s sh)$ sudo ls -ld /proc/$pid/root输出中的内容明确的指出 PID 为 46644 的进程的根目录被映射到了 /tmp/rootfs 目录。通过代码理解 chroot 命令下面我们尝试自己实现一个 chroot 程序,代码中涉及到两个函数,分别是 chroot() 函数和 chdir() 函数,其实真正的 chroot 命令也是通过调用它们实现的:#include <stdio.h>#include <unistd.h>#include <stdlib.h>int main(int argc, char *argv[]){if(argc<2){printf("Usage: chroot NEWROOT [COMMAND...] n");return 1;}if(chroot(argv[1])) {perror("chroot");return 1;}if(chdir("/")) {perror("chdir");return 1;}if(argc == 2) {// hardcode /bin/sh for my busybox tools.argv[0] = (char *)"/bin/sh";argv[1] = (char *) "-i";argv[2] = NULL;} else {argv += 2;}execvp (argv[0], argv);printf("chroot: cannot run command `%s`n", *argv);return 0;}把上面的代码保存到文件 mychroot.c 文件中,并执行下面的命令进行编译:$ gcc -Wall mychroot.c -o mychrootmychroot 的用法和 chroot 基本相同:$ sudo ./mychroot ./rootfs特别之处是我们的 mychroot 在没有传递命令的情况下执行了 /bin/sh,原因当然是为了支持我们的 busybox 工具集,笔者在代码中 hardcode 了默认的 shell:argv[0] = (char *)"/bin/sh";从代码中我们也可以看到,实现 chroot 命令的核心逻辑其实并不复杂。实例:通过 chroot 重新设置 root 密码忘记了 root 密码该怎么办?接下来的 demo 将演示如何通过 chroot 命令重新设置 centos7 中被忘记了的 root 密码。systemd 的管理机制中,rescure 模式和 emeryency 模式是无法直接取得 root 权限的,需要使用 root 密码才能进入 rescure 和 emeryency 环境。所以我们需要通过其他方式来设置 root 密码。我们可以为内核的启动指定 "rd.break" 参数,从而让系统在启动的早期停下来,此时我们可以通过使用 root 权限并结合 chroot 命令完成设置 root 密码的操作。下面我们一起来看具体的操作过程。在系统启动过程中进入开机菜单时按下字母键 e 进程开机菜单的编辑模式:这就是系统的开机菜单,按下 e 后进入编辑界面:找到以 "linux16 /vmlinuz-" 开头的行。如果默认没有看到该行,需要按向下键把它滚动出来。然后定位到该行结尾处,输入一个空格和字符串 "rd.break",如下图所示:接着按下 ctrl + x 以该设置继续启动,启动过程中操作系统会停下来,这是系统启动过程中的一个非常早的时间点:所以系统的根目录还挂载在 RAM disk 上(就是内存中的一个文件系统),我们可以通过 mount 命令检查系统当前挂载的文件系统,下面是我们比较关心的两条:上图中 mount 命令输出的第一行说明此时的根目录在一个 RAM disk 中, 即 rootfs。图中输出的第二行说明我们的文件系统此时被挂载到了 /sysroot 目录,并且是只读的模式:/dev/mapper/centos-root on /sysroot type xfs (ro,relatime,attr2,inode64,noquota)而在我们正常登陆系统的情况下,系统根目录的挂载情况如下:/dev/mapper/centos-root on / type xfs (rw,relatime,seclabel,attr2,inode64,noquota)该时间点的最大优势是我们具有 root 权限!所以让我们开始设置新的 root 密码吧。先通过下面的命令把 /sysroot 重新挂载为可读写的模式:switch_root:/# mount -o remount,rw /sysroot然后用下面 chroot 命令把根目录切换到我们原来的环境中:switch_root:/# chroot /sysroot此时可以理解为:我们以 root 权限登录了原来的系统,修改密码就很容易了!用下面的命令为 root 用户设置新的密码:sh-4.2# echo "new_root_pw" | passwd --stdin root接下来还要处理 SELinux 相关的问题。由于当前的环境中 SELinux 并未启动,所以我们对文件的修改可能造成文件的 context 不正确。为了确保开机时重新设定 SELinux context,必須在根目录下添加隐藏文件 .autorelabel:sh-4.2# touch /.autorelabel最后从 chroot 中退出,并重启系统:sh-4.2# exitswitch_root:/# reboot重新进入登陆界面时就可以使用刚才设置的密码以 root 登陆了!总结chroot 是一个很有意思的命令,我们可以用它来简单的实现文件系统的隔离。但在一个容器技术繁荣的时代,用 chroot 来进行资源的隔离实在是 low 了点。所以 chroot 的主要用途还是集中在系统救援、维护等一些特殊的场景中。参考:理解 chrootLinux – RedHat7 / CentOS 7 忘记root密码修改
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