Unit示例Hello World(docker)
- Unit和Target
先介绍两个概念,Unit和Target。
Unit是Systemd管理服务的基本单元,可以认为每个服务就是一个Unit,并使用一个Unit文件定义。Unit文件中需要包含相应服务的描述、属性以及需要运行的命令。在CoreOS中服务运行的命令通常是一系列的容器操作,而将具体的服务进程封装在容器中。
Target是Systemd中用于指定服务启动组的方式(相当于SysVinit中的“运行级别”,如果不清楚这个概念也没有关系,搜索“Linux运行级别”可以查到很多相关文章)。每次系统启动的时候都会运行与当前系统相同级别Target关联的所有服务,如果服务不需要跟随系统自动启动,则完全可以忽略这个Target的内容。通常来说我们大多数的Linux用户平时使用的都是“多用户模式”这个级别,对应的Target值为“multi-user.target”。
- Hello World服务的Unit文件
只说不做假把式,现在我们来用Systemd创建一个简单的系统服务。
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 命令获得完整的参数说明。
服务的生命周期
前面我们使用了 systemctl 的 start 和 enable 两个命令将 Hello World 服务在系统后台启动并设置为了开机自动运行。实际我们会遇到的情况远不止这些,下面我们来将一个服务进程在Systemd的生命周期补充完整。
- 服务的激活
当一个新的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的记录中被抹去了。
https://linuxeye.com/400.html