在 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. Nacos开机自启

    1.添加nacos.service文件 vi /lib/systemd/system/nacos.service 2.将以下内容写到nacos.service文件中 ps:我的nacos路径是/usr ...

  2. MAC帧的格式&&wireshark分析MAC帧

    MAC帧的格式 MAC帧较为简单,由五个字段组成 目的地址:6字节 源地址:6字节 类型字段:2字节,用来标志上一层使用的是什么协议,以便把收到的MAC地址帧的数据交给上一层的这个协议. 数据字段:其 ...

  3. Codeforces Round #791 (Div. 2) A-C

    Codeforces Round #791 (Div. 2) A-C A 题目 https://codeforces.com/contest/1679/problem/A 题解 思路 知识点:数学,暴 ...

  4. (一)Linux环境的学习环境的搭建

    我们使用VMWARE来安装Debian11系统来进行我们的LINUX学习 Debian虚拟机的安装 vmware-tools的安装 xShell的安装使用 samba的配置 gcc环境的配置 Debi ...

  5. Linux系列之文本操作命令

    前言 Linux 有八个常用的文本操作命令:cat.head.tail.nl.grep.sed.more.less.本文介绍它们的区别和简单用法. cat命令 显示文本的最基本命令. cat file ...

  6. 注意力机制最新综述:A Comprehensive Overview of the Developments in Attention Mechanism

    (零)注意力模型(Attention Model) 1)本质:[选择重要的部分],注意力权重的大小体现选择概率值,以非均匀的方式重点关注感兴趣的部分. 2)注意力机制已成为人工智能的一个重要概念,其在 ...

  7. dubbo(九):timeout超时机制解析

    在网络请求时,总会有各种异常情况出现,我们需要提前处理这种情况.在完善的rpc组件dubbo中,自然是不会少了这一层东西的.我们只需要通过一些简单的配置就可以达到超时限制的作用了. dubbo的设计理 ...

  8. 从傅里叶级数(Fourier series)到离散傅里叶变换(Discrete Fourier transform)

    从傅里叶级数(Fourier series)到离散傅里叶变换(Discrete Fourier transform) 一. 傅里叶级数(FS) 首先从最直观的开始,我们有一个信号\(x(t)\)(满足 ...

  9. 论文阅读 Inductive Representation Learning on Temporal Graphs

    12 Inductive Representation Learning on Temporal Graphs link:https://arxiv.org/abs/2002.07962 本文提出了时 ...

  10. X86寄存器

    前置知识 x86泛指一系列基于Intel 8086且向后兼容的中央处理器指令集架构.最早的8086处理器于1978年由Intel推出,为16位微处理器. 80X86 包括Intel 8086.8018 ...