转载:http://www.csdn.net/article/2015-01-08/2823477docker
摘要:CoreOS是采用了高度精简的系统内核及外围定制的操做系统。ThoughtWorks的软件工程师林帆将带来“漫步云端:CoreOS实践指南”系列文章,介绍CoreOS精华和推荐的实践方法。本文为基础第三篇:系统服务管家Systemd。windows
【编者按】做为一个操做系统,CoreOS 采用了高度精简的系统内核及外围定制,将许多本来须要复杂人工操做或者第三方软件支持的功能在操做系统级别进行了实现,同时剔除了其余对于服务器系统非核心的软件,好比GUI和包管理器。来自ThoughtWorks的软件工程师林帆将带来“漫步云端:CoreOS实践指南”系列文章,带你们了解CoreOS的精华和推荐的实践方法。本文为基础第三篇:系统服务管家Systemd。缓存
做者简介:服务器
林帆,生在80后尾巴的IT攻城狮,ThoughtWorks成都办公室CloudOps小组成员,平时喜欢在业余时间研究DevOps相关的应用,目前在备考AWS认证和推广Docker相关技术。网络
在系列教程的第一篇里咱们已经提到了Systemd,它主要的设计目标是克服传统Linux主流启动程序SysVinit 固有的缺点,提升系统的启动速度。相比同类的 SysVinit 竞争者,例如Ubuntu 的 upstart,Systemd 的设计更加前卫,简单来讲,它的设计思路借鉴了Mac系统的启动程序Launchd。事实上Systemd的做用远不只是启动系统,它还接管了系统服务的启动、结束、状态查询和日志归档等职责,并支持定时任务和经过特定事件(如插入特定USB设备)和特定端口数据触发的任务。在CoreOS的世界里,推荐的作法是使用Systemd来管理全部用户服务,包括运行在应用容器(如Docker)中的服务。并发
值得指出的是,Systemd并非CoreOS特有的服务。本质上说Systemd是没有依附于任何一个Linux发行版的独立项目,因为Systemd的做者Lennart Poettering 就任于红帽,整个项目实际由RedHat公司主导。虽然RedHat Linux直到2014年中旬才用上Systemd,但RedHat旗下的Fedora早在2011年时就已经引进了Systemd做为其启动管理程序了。ssh
在开始使用Systemd以前,先了解一下Systemd有哪些特别之处。 工具
当SysVinit 程序初始化系统的时,会将全部可能用到的后台服务进程所有运行起来。然而用户须要等待系统将全部服务都启动完成以后,才可以登陆。这种作法会带来两个问题:系统的启动时间过长和系统资源的浪费。性能
Systemd 提供了服务按需启动的能力,使得特定的服务只有在被真正请求的时候才启动。特别是具体硬件相关的服务,好比蓝牙服务仅在蓝牙适配器被插入时才须要运行,打印服务仅在打印机链接或程序要打印时才须要运行,甚至sshd服务也只须要在用户使用ssh链接到服务器时才须要启动。这种能力是创建在对Systemd对DBus总线或特定Socket端口监听的特性上的,这种设计相比于传统启动程序具备颠覆性的进步。 ui
在SysVinit的时代,将每一个服务项目编号的方式依次执行启动脚本。后来Ubuntu的Upstart解决了没有直接依赖的启动项之间的并行启动。而Systemd经过Socket缓存、DBus缓存和创建临时挂载点等方法进一步解决了启动进程之间的依赖,作到了全部系统服务并发启动,这一设计一样是Systemd独具特点的创意。固然,对于用户自定义的服务,Systemd容许配置其启动依赖项目,从而确保服务按必要的顺序运行,稍后会详细描述具体的使用方法。
Systemd启动模型与其它启动模型的对比
Cgroup的全称是controller group,是将任意进程进行分组化管理的Linux内核功能,最初由Google的工程师提出,从Linux内核版本2.6.24正式启用。拿Android来讲,它的应用程序隔离就是使用的这种技术。而很长一段时间里,在更广阔的服务器领域,一直并无一种主流的服务管理程序可以充分利用这种早已在手机端带来普遍好处的特性。
而Systemd正是Cgroup方面的行家,它的出现正好弥补了这个领域的缺漏。经过Cgroup,Systemd不只实现了服务之间的访问隔离,还可以限制特定应用程序对系统资源访问配额(好比CPU的用量、内存的量),以及精确的管理服务的生命周期。在这篇文章的后面部分会讲述相关操做具体的作法。
先介绍两个概念,Unit和Target。
Unit是Systemd管理服务的基本单元,能够认为每一个服务就是一个Unit,并使用一个Unit文件定义。Unit文件中须要包含相应服务的描述、属性以及须要运行的命令。在CoreOS中服务运行的命令一般是一系列的容器操做,而将具体的服务进程封装在容器中。
Target是Systemd中用于指定服务启动组的方式(至关于SysVinit中的“运行级别”,若是不清楚这个概念也没有关系,搜索“Linux运行级别”能够查到不少相关文章)。每次系统启动的时候都会运行与当前系统相同级别Target关联的全部服务,若是服务不须要跟随系统自动启动,则彻底能够忽略这个Target的内容。一般来讲咱们大多数的Linux用户平时使用的都是“多用户模式”这个级别,对应的Target值为“multi-user.target”。
只说不作假把式,如今咱们来用Systemd建立一个简单的系统服务。
在这个系列的上一节内容里,咱们建立了一个由3个CoreOS虚拟机节点组成的集群,在这节中,咱们只须要使用到其中的任意一个,好比coreo-01节点。首先使用ssh链接进入这个节点(这种方法适用于Linux/Mac用户,对于Windows用户需使用Putty客户端, 具体参考)。
vagrant ssh core-01
登陆成功后提示符变成 “core@core-01 ~ $” ,祝贺你又向CoreOS迈出了重要一步,接下来就能够开始在CoreOS里面玩耍了。
Systemd约定,服务的Unit文件需放置在 /etc/systemd/system 或 /usr/lib/systemd/system 目录中,但因为在CoreOS的后一个目录是只读分区(整个/usr目录挂载的都是只读的系统分区),所以咱们一般会将用户定义的Unit服务文件放在在/etc/systemd/system目录中。进入这个目录,新建一个叫“hello.service”的文件,内容入下。
[Unit] Description=Hello World After=docker.service Requires=docker.service [Service] TimeoutStartSec=0 ExecStartPre=-/usr/bin/docker kill busybox1 ExecStartPre=-/usr/bin/docker rm busybox1 ExecStartPre=/usr/bin/docker pull busybox ExecStart=/usr/bin/docker run --name busybox1 busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done" ExecStop=”/usr/bin/docker kill busybox1” [Install] WantedBy=multi-user.target
在这个Unit文件里,咱们首先为这个服务提供了一行简短的描述,而后指明它须要依赖docker的服务,而且要在docker服务运行之后才能运行。整个Unit文件是用的ini文件风格的分组配置格式,最开始的这段配置被放在了Unit组里面。在接下来的Service组中,使用ExecStart和ExecStop属性分别指定了服务运行时和结束时须要执行的命令。最后在Install组的配置中,咱们指定了服务所属的Target为multi-user.target。
这里须要注意两个地方,首先ExecStart属性只能包含一条主要命令,而在这个属性的先后能够分别使用ExecStartPre和ExecStartPost指定更多的辅助命令,ExecStop同理。有些辅助命令会加上一个减号,表示忽略这些命令的出错(由于有些“辅助”命令原本就不必定成功,好比尝试清空一个文件,但文件可能不存在)。其次TimeoutStartSec=0这行的目的是将Systemd的服务启动超时检查关闭,对于docker应用这样作是必须的,由于docker在运行时可能会须要下载或更新镜像文件,使得服务启动时间变得很长,这样能够防止Systemd认为服务启动失败而将进程误杀。有了Unit文件,如今就能够启动Hello World服务了,在控制台输入如下命令:
sudo systemctl start hello.service
Tip:这个名字末尾的 .service 后缀是能够省略的,由于systemctl默认的后缀就是 .service。关于Unit文件后缀的含义,会在后续进阶篇的文章里详细说明。
Systemd会自动找到 /usr/lib/systemd/system 目录中的 hello.service 文件,并启动其中定义的服务。若是以前建立的Unit文件是放在其余目录下的,这里须要使用文件的完整路径。首次运行的时候须要等待一段时间,由于docker须要从网络上下载须要的镜像。启动完成后能够经过“systemctl list-units”命令查看服务是否已经在运行(这个命令接受一个可选参数做为服务名的过滤条件,若是不带任何参数则输出全部服务)。
core@core-01 ~ $ sudo systemctl list-units hello* UNIT LOAD ACTIVE SUB DESCRIPTION hello.service loaded active running Hello World
咱们还能够经过“systemctl enable”命令来将服务指定为在系统启动时自动启动。
sudo systemctl enable hello.service
此时就用到了以前定义的Target组,实际上enable操做只是建立了一个链接文件到指定的Target组的目录下面。经过下面命令能够证明。
core@core-01 ~ $ ls -l /etc/systemd/system/multi-user.target.wants/hello.service /etc/systemd/system/multi-user.target.wants/hello.service -> /etc/systemd/system/hello.service系统启动时,会自动运行其所在Target级别相应的目录里全部连接的服务。
至此,咱们的第一个服务已经在后台哈皮的玩耍了,但是说好的“echo Hello World”呢?咱们从头至尾都没有见到服务的任何输出啊。
其实咱们启动的服务已经在后台默默的输出“Hello World”了。
Systemd经过其标准日志服务Journald将其管理的全部后台进程打印到到std:out(即控制台)的输出重定向到了日志文件。日志文件是二进制格式的,所以必须使用特定的工具才能查看。Journald提供了配套的程序Journalctl用于处理日志内容。Journalctl的使用很是简单,默认不带任何参数的时候会输出系统和全部后台进程的混合日志,经常使用的参数有--dmesg用于查看内核输出的日志,--system用于查看系统输出的日志,--unit加上Unit的名字来指定输出特定Unit的日志,例如如下命令。
journalctl --unit hello.service其余还有一些比较实用的参数,好比使用 --follow 实时跟踪日志输出,使用 --since 和 --until 指定显示的日志时间区间等,能够经过 journalctl --help 命令得到完整的参数说明。
当一个新的Unit文件被放入 /etc/systemd/system/ 或 /usr/lib/systemd/system/ 目录时,它是不会自动被Systemd识别到的。例如在 hello.service 文件刚刚建立好时,若是咱们让Systemd列出全部的Unit。
sudo systemctl list-units
此时在输出的内容中是找不到hello.service这个Unit的。直到咱们经过 systemctl 的 start 或 enable 命令将这个Unit登记到Systemd的服务列表中,这个过程就是Unit的激活。
在服务被激活前,Unit仅仅是以Unit 文件的形式存在,Systemd提供 list-unit-files 命令查看全部的Unit 文件。
sudo systemctl list-unit-files
这个命令一样接受一个可选的参数做为Unit名称的匹配条件,不带任何参数时会输出全部Systemd找到的(也就是在那两个目录)Unit文件。
PS:顺便回答一个常常被问到的问题,这个命令的输出的第一列是Unit文件名,第二列是相应的Unit是否开机启动,它的值能够是enable、disable或static,这里的static是神马意思呢?其实它是指对应的 Unit 文件中没有定义[Install]区域,所以没法配置为开机启动服务。启动、结束、强制终止和从新启动,没啥可说的,分别对应如下几个命令。
sudo systemctl start <Unit名称> sudo systemctl stop <Unit名称> sudo systemctl kill <Unit名称> sudo systemctl restart <Unit名称>
这里存在一个陷阱,直到目前版本的Systemd(v215)和Docker(v1.4.0)中,当Unit的主要命令是经过docker容器托管的时候,systemctl的kill命令会没法正确的杀掉服务进程,而必须使用 kill -s SIGKILL 才能正常的工做,缘由见笔者在“ 不完美的CoreOS”中的分析。
服务的开机自动启动的启用和取消,分别对应下面两个。
sudo systemctl enable <Unit名称> sudo systemctl disable <Unit名称>
这两部分是 Systemd 当中比较Tricky的地方。
首先,若是咱们修改了一个放在 /etc/systemd/system/ 的文件,好比将输出的“Hello World”改为了“Bye World”,当执行 systemctl restart 之后,从新启动的服务输出的将依然是“Hello World”。这是由于当Unit文件被激活时,Systemd会将其中的内容记入到本身的缓存当中,所以为了获得更新后的内容,咱们须要告诉Systemd从新读取全部的Unit文件。
sudo systemctl daemon-reload
再次重启Unit,能够看到更新就会生效了。
其次是Unit文件的移除,直接删除Unit文件后因为缓存的做用,Systemd仍然能够继续使用这个Unit,即便经过daemon-reload更新缓存,在list-units中会看见这个Unit只是被标为了not-found,依旧阴魂不散。
core@core-01 ~ $ sudo systemctl list-units hello* UNIT LOAD ACTIVE SUB DESCRIPTION hello.service not-found failed failed hello.service
此时,咱们须要明确的告诉Systemd,移除这些已经被标记为丢失的Unit文件。
sudo systemctl reset-failed如今这个Unit才真正的从Systemd的记录中被抹去了。
Systemd 做为默认系统启动和服务管理器不但具有优秀的并行化处理能力,也提供了更好的系统进程追踪管理能力,加之按需启动等特色,结合 Docker 的快速启动,在 CoreOS 集群中大规模部署 Docker 容器与使用其余操做系统相比在性能上的优点更加明显。
实际上,Systemd的能力远远不止这些,在这篇文章仅仅介绍了它在服务管理方面的运用。在下一篇里咱们将继续探索CoreOS是如何经过Fleet服务将这些功能扩展到大规模集群的。(做者/林帆 审校/周小璐)