在 Linux 6 / CentOS 6 中,使用 service 来进行服务的起停,但是在 Linux 7 / CentOS 7 中,替换为使用 systemctl 命令来控制。将一些常用应用注册成服务后,可以使用 systemctl 命令来方便的操作启动、停止、重启,但是作者最近发现如果配置不当,严重情况下可能影响正常业务运行,请大家务必关注。

$ service start loop.service
The service command supports only basic LSB actions (start, stop, restart, try-restart, reload, force-reload, status). For other actions, please try to use systemctl.

问题描述

如果我们有一些负责自动化的应用,例如 puppet / mco、ansible,这里简称为宿主服务。宿主服务注册为系统服务并且随系统开机自启动。宿主服务支持接收服务端指令并拉起一些常驻进程,拉起的进程我们简称为子进程。当宿主服务被 kill 或意外终止时,会引起子进程一起被 kill。

为了复现这个问题,我写了两个脚本。parent_pro.sh 作为宿主脚本,注册为系统 loop.service 并且随系统启动。该脚本的作用是每隔10秒钟检查 config.txt 的配置,如果配置文件中的数字变成 1 则拉起一个无限循环的子进程。具体的代码可以在我的 Github 上看到。

我的 service 定义信息:

[Unit]
Description=loop service
After=network.target [Service]
Type=forking
StandardOutput=/root/namespace/parent_pro.log
StandardError=/root/namespace/parent_pro.log
WorkingDirectory=/root/namespace
User=root
Group=root
ExecStart=/usr/bin/sh parent_pro.sh [Install]
WantedBy=multi-user.target

下面执行启动服务。

$ systemctl start loop.service
$ ps -ef | grep sh
root 4615 1 0 15:59 ? 00:00:00 /usr/bin/sh parent_pro.sh
root 5153 4615 0 16:00 ? 00:00:00 sh /root/namespace/child_loop.sh
root 5197 4615 0 16:00 ? 00:00:00 sh /root/namespace/child_loop.sh
root 5248 4615 0 16:00 ? 00:00:00 sh /root/namespace/child_loop.sh

从上面的这段输出可以看到,parent_pro.sh 已经启动了三个子进程,我们再看下 parent_pro.sh 的详细信息。

$ systemctl status
● qking-101
State: running
Jobs: 0 queued
Failed: 0 units
Since: Thu 2022-08-11 15:59:54 CST; 29s ago
CGroup: /
├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
├─user.slice
│ └─user-0.slice
│ └─session-1.scope
│ ├─5125 sshd: root@pts/0
│ ├─5129 -bash
│ ├─5224 systemctl status
│ └─5225 less
└─system.slice
├─rsyslog.service
│ └─4618 /usr/sbin/rsyslogd -n
├─postfix.service
│ ├─4944 /usr/libexec/postfix/master -w
│ ├─4948 pickup -l -t unix -u
│ └─4949 qmgr -l -t unix -u
├─loop.service
│ ├─4615 /usr/bin/sh parent_pro.sh
│ ├─5153 sh /root/namespace/child_loop.sh
│ ├─5197 sh /root/namespace/child_loop.sh
│ ├─5198 sleep 10
│ ├─5220 sleep 1
│ └─5222 sleep 1

可以看到我注册的 loop.service 在 system.slice 这个 cgroup 目录下。接下来复现问题场景。

$ kill 4615
$ ps -ef | grep sh
root 1857 2 0 15:59 ? 00:00:00 [kdmflush]
root 1873 2 0 15:59 ? 00:00:00 [kdmflush]
root 3220 2 0 15:59 ? 00:00:00 [kdmflush]
root 4613 1 0 15:59 ? 00:00:00 /usr/sbin/sshd -D
root 5125 4613 0 16:00 ? 00:00:00 sshd: root@pts/0
root 5129 5125 0 16:00 pts/0 00:00:00 -bash
root 5330 5129 0 16:00 pts/0 00:00:00 grep --color=auto sh

杀掉父进程之后,子进程跟着消失了。但是如果手工执行 parent_pro.sh 不会出现这个问题。

$ nohup sh parent_pro.sh > parent_pro.log  2>&1 &
$ systemctl status
● qking-101
State: running
Jobs: 0 queued
Failed: 0 units
Since: Thu 2022-08-11 15:59:54 CST; 2h 21min ago
CGroup: /
├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
├─user.slice
│ └─user-0.slice
│ ├─session-4.scope
│ │ ├─5797 sshd: root@pts/1
│ │ ├─5801 -bash
│ │ ├─6012 sh parent_pro.sh
│ │ ├─6013 sleep 10
│ │ ├─6014 systemctl status
│ │ └─6015 less
│ └─session-1.scope
│ ├─5125 sshd: root@pts/0
│ └─5129 -bash

修改配置文件让父进程拉起一些子进程。

$ echo 0 > config.txt
$ ps -ef | grep sh
root 6012 5801 0 18:21 pts/1 00:00:00 sh parent_pro.sh
root 6018 6012 0 18:21 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 6041 6012 0 18:21 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 6084 6012 0 18:22 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 6147 6012 0 18:22 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 6232 6012 0 18:22 pts/1 00:00:00 sh /root/namespace/child_loop.sh
$ systemctl status
● qking-101
State: running
Jobs: 0 queued
Failed: 0 units
Since: Thu 2022-08-11 15:59:54 CST; 2h 23min ago
CGroup: /
├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
├─user.slice
│ └─user-0.slice
│ ├─session-4.scope
│ │ ├─5797 sshd: root@pts/1
│ │ ├─5801 -bash
│ │ ├─6012 sh parent_pro.sh
│ │ ├─6018 sh /root/namespace/child_loop.sh
│ │ ├─6041 sh /root/namespace/child_loop.sh
│ │ ├─6084 sh /root/namespace/child_loop.sh
│ │ ├─6147 sh /root/namespace/child_loop.sh
│ │ ├─6232 sh /root/namespace/child_loop.sh
│ │ ├─6337 sh /root/namespace/child_loop.sh
│ │ ├─6460 sh /root/namespace/child_loop.sh
│ │ ├─6603 sh /root/namespace/child_loop.sh
│ │ ├─6766 sh /root/namespace/child_loop.sh
│ │ ├─6949 sh /root/namespace/child_loop.sh
│ │ ├─6950 sleep 10
│ │ ├─7069 sleep 1
│ │ ├─7071 sleep 1
│ │ ├─7073 sleep 1
│ │ ├─7075 sleep 1
│ │ ├─7077 sleep 1
│ │ ├─7079 sleep 1
│ │ ├─7081 sleep 1
│ │ ├─7084 sleep 1
│ │ ├─7085 sleep 1
│ │ ├─7087 sleep 1
│ │ ├─7088 systemctl status
│ │ └─7089 less
│ └─session-1.scope
│ ├─5125 sshd: root@pts/0
│ └─5129 -bash
└─system.slice
├─rsyslog.service

这时杀掉父进程不会对子进程产生影响。

$ kill 6012
$ ps -ef | grep sh
root 6018 1 0 18:21 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 6041 1 0 18:21 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 6084 1 0 18:22 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 6147 1 0 18:22 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 6232 1 0 18:22 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 6337 1 0 18:22 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 6460 1 0 18:22 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 6603 1 0 18:22 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 6766 1 0 18:23 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 6949 1 0 18:23 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 7154 1 0 18:23 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 7377 1 0 18:23 pts/1 00:00:00 sh /root/namespace/child_loop.sh
root 7620 1 0 18:23 pts/1 00:00:00 sh /root/namespace/child_loop.sh
[1]+ Terminated nohup sh parent_pro.sh > parent_pro.log 2>&1
$ systemctl status
● qking-101
State: running
Jobs: 0 queued
Failed: 0 units
Since: Thu 2022-08-11 15:59:54 CST; 2h 24min ago
CGroup: /
├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
├─user.slice
│ └─user-0.slice
│ ├─session-4.scope
│ │ ├─5797 sshd: root@pts/1
│ │ ├─5801 -bash
│ │ ├─6018 sh /root/namespace/child_loop.sh
│ │ ├─6041 sh /root/namespace/child_loop.sh
│ │ ├─6084 sh /root/namespace/child_loop.sh
│ │ ├─6147 sh /root/namespace/child_loop.sh
│ │ ├─6232 sh /root/namespace/child_loop.sh
│ │ ├─6337 sh /root/namespace/child_loop.sh
│ │ ├─6460 sh /root/namespace/child_loop.sh
│ │ ├─6603 sh /root/namespace/child_loop.sh
│ │ ├─6766 sh /root/namespace/child_loop.sh
│ │ ├─6949 sh /root/namespace/child_loop.sh
│ │ ├─7154 sh /root/namespace/child_loop.sh
│ │ ├─7377 sh /root/namespace/child_loop.sh
│ │ ├─7620 sh /root/namespace/child_loop.sh
│ │ ├─9114 sleep 1
│ │ ├─9116 sleep 1
│ │ ├─9119 sleep 1
│ │ ├─9120 sleep 1
│ │ ├─9122 sleep 1
│ │ ├─9126 sleep 1
│ │ ├─9127 sleep 1
│ │ ├─9128 sleep 1
│ │ ├─9130 sleep 1
│ │ ├─9132 sleep 1
│ │ ├─9134 sleep 1
│ │ ├─9136 sleep 1
│ │ ├─9138 sleep 1
│ │ ├─9139 systemctl status
│ │ └─9140 less
│ └─session-1.scope
│ ├─5125 sshd: root@pts/0
│ └─5129 -bash
└─system.slice

问题分析

仔细查阅了 systemd 相关文档,发现这个问题主要与 type 参数有关。

Type:定义 service 的类型,主要有以下类型:

  • simple:默认类型,启动的程序就是主体程序,这个程序要是退出那么一切皆休。因为 simple 类型不存在主进程退出的情况也就不存在有返回状态的情况,所以它一旦启动就认为是成功的,除非没起来。
  • forking:标准 Unix Daemon 使用的启动方式。启动程序后会调用 fork() 函数,把必要的通信频道都设置好之后父进程退出,留下守护精灵的子进程。如果启动的进程能够自己创建pidfile,最好也指定下PIDFile=XXX。
  • oneshot:一次性服务,这种服务类型就是启动,完成,没进程了。因为这类服务运行完就没进程了,我们经常会需要 RemainAfterExit=yes。后面配置的意思是说,即使没进程了,我们也要 Systemd 认为该服务是存在并成功了的。其他类型千万不要去设置RemainAfterExit=yes,否则systemd会认为服务启动成功了,重启或再去启动都会失败。
  • dbus:这个程序启动时需要获取一块 DBus 空间,所以需要和 BusName= 一起用。只有它成功获得了 DBus 空间,依赖它的程序才会被启动。
  • notify:这个程序在启动完成后会通过 sd_notify 发送一个通知消息。所以还需要配合 NotifyAccess 来让 Systemd 接收消息,后者有三个级别:none,所有消息都忽略掉; main,只接受我们程序的主进程发过去的消息; all,我们程序的所有进程发过去的消息都算。NotifyAccess 要是不写的话默认是 main。
  • idle:这个程序要等它里面调度的全部其它东西都跑完才会跑它自己。比如你 ExecStart 的是个 shell 脚本,里面可能跑了一些别的东西,如果不这样的话,那很可能别的东西的控制台输出里会多一个“启动成功”这样的 Systemd 消息。

最后,如果大家的服务会处理我这种场景,请务必记住这个参数要设置好了,另外还有一个 KillMode 参数也与 cgroup 有关,当我们执行 systemctl stop loop.service 的时候,整个 cgroup 会被删除,通过设置 KillMode 参数,可以确保子进程不会被清除。

参考资料

  1. unary operator expected解决方法
  2. Linux 系统注册系统服务流程
  3. Systemd使用小结
  4. 使用systemctl管理服务

注意了!这样用 systemd 可能会有风险的更多相关文章

  1. COSO企业风险管理框架2017版发布!看看有哪些变化?

    近期,COSO发布了新版(2017版)的企业风险管理框架:<企业风险管理—与战略和业绩的整合>.相较于2004年发布的上一版框架<企业风险管理—整合框架>,新框架强调了制定战略 ...

  2. Linux集群环境下NTP服务器时间同步

    NTP介绍 NTP(Network Time Protocol,网络时间协议)是用来使网络中的各个计算机时间同步的一种协议.它的用途是把计算机的时钟同步到世界协调时UTC(Universal Time ...

  3. 谈一谈Http Request 与 Http Response

    写在前面的话:今天来总结一下http相关的request和response,就从以下几个问题入手吧. ======正文开始======== 1.什么是HTTP Request 与HTTP Respon ...

  4. 在MongoDB的MapReduce上踩过的坑

    太久没动这里,目前人生处于一个新的开始.这次博客的内容很久前就想更新上来,但是一直没找到合适的时间点(哈哈,其实就是懒),主要内容集中在使用Mongodb时的一些隐蔽的MapReduce问题: 1.R ...

  5. CentOS7 NTP 安装配置

    NTP 网络时间协议用来同步网络上不同主机的系统时间.你管理的所有主机都可以和一个指定的被称为 NTP 服务器的时间服务器同步它们的时间.而另一方面,一个 NTP 服务器会将它的时间和任意公共 NTP ...

  6. 解读2015年互联网UGC内容发展态势,安全事件频发

    <2015内容安全年报> 阿里移动安全 第一章 2015年内容安全形势 随着互联网业务的迅速发展,互联网上的信息内容带来了爆炸式的增长.由于缺乏对网络活动进行有效监督和管理的措施,致使互联 ...

  7. 内存中 OLTP - 常见的工作负荷模式和迁移注意事项(二)

    ----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<In-Memory OLTP – Comm ...

  8. Nginx反向代理+负载均衡简单实现(http方式)

    1)nginx的反向代理:proxy_pass2)nginx的负载均衡:upstream 下面是nginx的反向代理和负载均衡的实例: 负载机:A机器:103.110.186.8/192.168.1. ...

  9. iOS身份证的正则验证

    在ios项目的开发中可能很多地方都需要用到身份证校验,一般在开发的时候很多人都是直接百度去网上荡相关的正则表达式和校验代码,但是网上疯狂粘贴复制的校验代码本身也可能并不准确,可能会有风险,比如2013 ...

随机推荐

  1. 关于我用python表白成功这件事【表白成功】

    520,并非情人所属, 我们可以表白万物, 不管什么时候, 这都是一个特别的日子, 今天,我要表白所有, 心里有我的人! 在这个充满幸福的日子里, 我要把最美好的祝福, 送给心里有我的每一个人: 祝愿 ...

  2. Map集合中的同一键值key重复赋值

    前言: 验证:对Map集合中的同一键值key重复赋值? 结果:对Map集合中的同一键值key重复赋值会覆盖之前的结果. 验证如下: Map<String, Object> map = ne ...

  3. 攻防世界进阶区MISC ——56-60

    56.low 得到一张bmp,世纪之吻,扔进kali中,binwalk,zsteg,无果,再放进stegsolve中,虽然发现小的数据块,但是过滤通道得不到任何信息,猜测是要用脚本进行 # lsb隐写 ...

  4. 7 什么是dubbo

    什么是dubbo 快速入门dubbo 了解什么是dubbo之前,我们得先了解什么是分布式系统? <分布式系统原理与范型>定义: 分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像 ...

  5. 「一本通 1.4 例 2」[USACO3.2]魔板 Magic Squares

    [USACO3.2]魔板 Magic Squares 题目背景 在成功地发明了魔方之后,鲁比克先生发明了它的二维版本,称作魔板.这是一张有8个大小相同的格子的魔板: 1 2 3 4 8 7 6 5 题 ...

  6. YII缓存操作

    //文件依赖 $dependency = new \yii\caching\FileDependency(['filename'=>'hw.txt'])}; $cache->add(&qu ...

  7. 停止、启动nginx以及在windows下使用dos命令停止占用的端口

    windows下使用dos命令查看占用端口号并停止 windows+R打开命令窗口 查找占用端口对应的PID(进程号) netstat -ano|findstr "port" # ...

  8. MySQL:关于MGR中监控的两个重要指标简析

    欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 转载声明:以下文章来源于MySQL学习 ,作者八怪(高鹏) 一.两个重要的指标 ...

  9. 编译器优化:何为SLP矢量化

    摘要:SLP矢量化的目标是将相似的独立指令组合成向量指令,内存访问.算术运算.比较运算.PHI节点都可以使用这种技术进行矢量化. 本文分享自华为云社区<编译器优化那些事儿(1):SLP矢量化介绍 ...

  10. P2501 [HAOI2006]数字序列 (LIS,DP)(未完成)

    第二问好迷... #include "Head.cpp" #include <vector> const int N = 35007; vector<int> ...