问题
在我们对某些进程做升级时,并且这些进程是被systemd托管的,比如ovs热升级后发现有新ovs进程起来后又退出了,并且退出后,并没有被systemd重新拉起,这个比较严重,热升级后进程退出,还没有重新拉起,会导致虚拟机网络中断时间过长。
分析
分析退出原因:
1、首先分析什么原因退出的,分析 ovs 日志,并没有发现有报错。
2、手动起ovs进程,然后进行热升级,发现进程状态正常,但是用systemd起的ovs进程,只要热升级就退出。
3、查看日志,systemctl status ovs-vswitchd 能看到有报错 "Refusing to accept PID outside of service control group, acquired through unsafe symlink chain: /var/run/openvswitch/ovs-vswitchd-current.pid "
从以上可以粗略的得出是systemd杀掉 新 ovs 进程的
分析systemd代码流程
找到报错的函数 service_load_pid_file:
1、会发现先进行了个 symlink 的检查,如果symlink检查失败会给 questionable_pid_file 变量赋值为 true, 表示这是个有问题的pid 文件。
2、然后会调用 service_is_suitable_main_pid 去判断是否是一个正确的service服务,如果不是正常的service服务,就会去判断 questionable_pid_file ,如果pid file 也是有问题的,就会 return -EPERM 报错
service_load_pid_file
static int service_load_pid_file(Service *s, bool may_warn) {
r = chase_symlinks(s->pid_file, NULL, CHASE_SAFE, NULL, &fd);
if (r == -ENOLINK) {
questionable_pid_file = true;
}
r = service_is_suitable_main_pid(s, pid, prio);
if (r < 0)
return r;
if (r == 0) {
struct stat st;
if (questionable_pid_file) {
log_unit_error(UNIT(s), "Refusing to accept PID outside of service control group, acquired through unsafe symlink chain: %s", s->pid_file);
return -EPERM;
}
}
分析 chase_symlinks 函数,只贴了部分代码,目前的FLAG只有CHASE_SAFE, 所以我们只分析部分和目前场景相关的代码, 函数大意就是会遍历整个 pid 文件的路径,比如 /var/run/openvswitch/openvswitch-current.pid,会遍历/var、/var/run、/var/run/openvswitch、/var/run/openvswitch/openvswitch-current.pid 整个路径。
1、只要以上其中路径一个有软链都会返回错误。
2、同时会调用 unsafe_transition 检查上下文件的所有者,如果文件的所有者不相同也会返回错误。
chase_symlinks 和 unsafe_transition 函数
int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret_path, int *ret_fd) {
if (fstat(child, &st) < 0)
return -errno;
previous_stat = st;
fd = open(root ?: "/", O_CLOEXEC|O_NOFOLLOW|O_PATH); //O_NOFOLLOW表示如果是软链,打开失败
if (fd < 0)
return -errno;
if (flags & CHASE_SAFE) {
if (fstat(fd, &st) < 0)
return -errno;
if (unsafe_transition(&previous_stat, &st))
return log_unsafe_transition(child, fd, path, flags);
previous_stat = st;
}
}
static bool unsafe_transition(const struct stat *a, const struct stat *b) {
/* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to
* privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files
* making us believe we read something safe even though it isn't safe in the specific context we open it in. */
if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */
return false;
return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */
}
线上环境现状
从下面可以看出 目前这个 /var/run/openvswitch/ovs-vswitchd-current.pid 路径上的问题比较多,和systemd冲突比较大,所以需要按systemd的方式来修改。
解决办法
根据以上代码分析,只要pid文件上没有软链,并且整条pid文件的路径上用户都是同一个,即可解决问题。
平时启动服务都不会有这问题,因为我们热升级的特殊性,在新进程创建后,需要把新的pid写入pid文件,在老进程退出时,systemd会重新加载这个 pidfile,由于有老进程的退出,新进程的创建,所以会比较麻烦。
其实这里还会有其它问题,比如pid文件要做到写的原子性,不然 systemd 读的时候会出现问题,导致systemd又把新进程杀掉了,还有就是对应 systemd service的配置要准备,这些在这里不细讲了。