注意了!这样用 systemd 可能会有风险
在 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 参数,可以确保子进程不会被清除。
参考资料
注意了!这样用 systemd 可能会有风险的更多相关文章
- COSO企业风险管理框架2017版发布!看看有哪些变化?
近期,COSO发布了新版(2017版)的企业风险管理框架:<企业风险管理—与战略和业绩的整合>.相较于2004年发布的上一版框架<企业风险管理—整合框架>,新框架强调了制定战略 ...
- Linux集群环境下NTP服务器时间同步
NTP介绍 NTP(Network Time Protocol,网络时间协议)是用来使网络中的各个计算机时间同步的一种协议.它的用途是把计算机的时钟同步到世界协调时UTC(Universal Time ...
- 谈一谈Http Request 与 Http Response
写在前面的话:今天来总结一下http相关的request和response,就从以下几个问题入手吧. ======正文开始======== 1.什么是HTTP Request 与HTTP Respon ...
- 在MongoDB的MapReduce上踩过的坑
太久没动这里,目前人生处于一个新的开始.这次博客的内容很久前就想更新上来,但是一直没找到合适的时间点(哈哈,其实就是懒),主要内容集中在使用Mongodb时的一些隐蔽的MapReduce问题: 1.R ...
- CentOS7 NTP 安装配置
NTP 网络时间协议用来同步网络上不同主机的系统时间.你管理的所有主机都可以和一个指定的被称为 NTP 服务器的时间服务器同步它们的时间.而另一方面,一个 NTP 服务器会将它的时间和任意公共 NTP ...
- 解读2015年互联网UGC内容发展态势,安全事件频发
<2015内容安全年报> 阿里移动安全 第一章 2015年内容安全形势 随着互联网业务的迅速发展,互联网上的信息内容带来了爆炸式的增长.由于缺乏对网络活动进行有效监督和管理的措施,致使互联 ...
- 内存中 OLTP - 常见的工作负荷模式和迁移注意事项(二)
----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<In-Memory OLTP – Comm ...
- Nginx反向代理+负载均衡简单实现(http方式)
1)nginx的反向代理:proxy_pass2)nginx的负载均衡:upstream 下面是nginx的反向代理和负载均衡的实例: 负载机:A机器:103.110.186.8/192.168.1. ...
- iOS身份证的正则验证
在ios项目的开发中可能很多地方都需要用到身份证校验,一般在开发的时候很多人都是直接百度去网上荡相关的正则表达式和校验代码,但是网上疯狂粘贴复制的校验代码本身也可能并不准确,可能会有风险,比如2013 ...
随机推荐
- 【前端面试】(三)JavaScript相等(==)和全等(===)运算符的区别
视频链接: JavaScript相等()和全等(=)运算符的区别 - Web前端工程师面试题讲解 参考链接: JavaScript == 与 === 区别 区别 对于string.number 等基础 ...
- Java模拟西宝高速公路
@ 目录 写在前面 一.仿真模拟的具体要求 二.类的设计 2.1 抽象父类PubVehicles 2.2 Expressway类 2.3 Passenger类 2.4 Timer类 2.5 Displ ...
- 虚拟机启动时报’A start job is running for /etc/rc.local .. Compatibility错误。
虚拟机启动时报'A start job is running for /etc/rc.local .. Compatibility错误. 问题已经存在很长时间了,但是不影响ssh登录,遂置之未理. 经 ...
- ReentrantLock源码详解
前言 以前只知道ReentrantLock底层基于AQS实现,相对于(旧版本的)synchronized: 更轻量(基于CAS而不是管程),由JDK实现 可以实现公平/非公平 可中断等待 可绑定多个条 ...
- 如何用车辆历史违章查询API接口进行快速开发
最近公司项目有一个车辆的历史违章查询显示的小功能,想着如果用现成的API就可以大大提高开发效率,所以在网上的API商店搜索了一番,发现了 APISpace,它里面的车辆历史违章查询API非常符合我的开 ...
- Visual Studio 生产环境配置方案:SlowCheetah
原文 SlowCheetah 能满足你不同编译模式产生不同 app.config 配置的需求,已被微软纳入麾下,支持XML,JSON格式. 下面我们用XML的格式来试试如何使用SlowCheetah, ...
- 编程技巧│超实用 nginx 中常见的配置合集
目录 封禁 IP 仅开放内网 负载均衡 列出文件列表 路由转发 开启 gzip 压缩 解决跨域 资源防盗链 Keepalived 提高吞吐量 HTTP 强制跳转 HTTPS 封禁 IP 通过 deny ...
- led跑马灯(模糊时钟ambiguous color,非法字符 non printable character,寄存器初值,计数器计数注意事项)
1.设计定义 让8个led以100ns的速度循环闪烁. 2.设计输入 循环闪烁,还是周期问题,用时钟驱动,所以需要一个时钟信号clk.再给一个复位输入reset,八个输出led信号. 每100ns只有 ...
- 如何在CentOS上找出逐渐耗尽磁盘空间的目录和文件
起因 随着系统运行,CentOS空间不断减少,对此非常焦虑,到底磁盘空间被哪些新增文件占用了呢? 分析过程,主要使用du命令,逐层找出消耗空间的目录 1.在根目录下检索一下文件的占用情况,执行du命令 ...
- WebWorker:工作者线程初探
WebWorker:工作者线程初探 参考资料: 1.Web Worker 使用教程 - 阮一峰:http://www.ruanyifeng.com/blog/2018/07/web-worker.ht ...