cgroup与systemd: 通过src rpm获取systemd源代码,添加日志并使用rpmbuild重新打包
问题起源
服务跑在富容器中。容器使用init进程作为一号进程,然后用systemd管理所有service。
在做一次升级时,nginx启动脚本有更新,原来是root拉起,现在进行了去root改造,使用nginx用户拉起。
升级过程中,发现nginx进程无法被拉起,报错:
"Refusing to accept PID outside of service control group, acquired through unsafe symlink chain: %s", s->pid_file);
经排查,相同版本systemd和nginx,相同改动,在物理机上正常,但在k8s环境上有问题。因此猜测时容器化环境相关问题。
提出疑问:
●什么场景会触发这个问题?
●为什么物理化环境没有问题,而容器化环境有此问题?
问题分析
要确认问题所在,需要从systemd代码入手,从有问题的日志反查。
最初直接从github中查看代码仓库,但是难以与当前环境的版本对应,因此直接下载到src.rpm包,解压包出来,打上所有patch,来获取当前rpm的源代码:
现场systemd版本为systemd.x86_64:219-78.tl2.7.1 (该版本与219-78.el7_9.7代码相同)。
对应src rpm下载地址:https://mirrors.tencent.com/tlinux/2.4/tlinux/SRPMS/systemd-219-78.tl2.7.1.src.rpm
通过src rpm获取源代码:
# 起一个tlinux2.4容器作为编译机,挂载/data/目录:
IMAGE_ID='xxx'
NAME=tlinux2_compile
docker run --privileged -idt \
--name $NAME \
-v /data:/data \
--net host \
${IMAGE_ID} \
/usr/sbin/init
docker exec -it $NAME /bin/bash
# 设定rpmbuild目录为/data/rpmbuild,以方便后续安装rpm:
bash-4.2# cat ~/.rpmmacros
%_topdir /data/rpmbuild
cd /data/rpmbuild
# 放置rpm到本目录, 安装基础systemd rpm:
rpm -ivh systemd-219-78.tl2.7.1.src.rpm
cd SOURCES
ls
使用如下脚本,获取打了所有patch的完整源码:
#!/bin/bash
# 检查是否提供了 SRPM 文件
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <path_to_srpm>"
exit 1
fi
SRPM_FILE=$1
# 提取 SRPM 文件
# rpm -ivh $SRPM_FILE
# 获取包名和版本号
SPEC_FILE=$(find /data/rpmbuild/SPECS -name "*.spec" | head -n 1)
PACKAGE_NAME=$(rpmspec -q --qf "%{NAME}\n" $SPEC_FILE | head -n 1)
VERSION=$(rpmspec -q --qf "%{VERSION}\n" $SPEC_FILE | head -n 1)
# 解压源代码
SOURCE_TARBALL=$(find /data/rpmbuild/SOURCES -name "${PACKAGE_NAME}-${VERSION}*.tar.*" | head -n 1)
mkdir -p /tmp/${PACKAGE_NAME}
tar -xf $SOURCE_TARBALL -C /tmp/${PACKAGE_NAME}
cd /tmp/${PACKAGE_NAME}/${PACKAGE_NAME}-${VERSION}
# 应用补丁
PATCHES=$(grep '^Patch[0-9]*:' $SPEC_FILE | awk '{print $2}')
for patch in $PATCHES; do
patch -p1 < /data/rpmbuild/SOURCES/$patch
done
echo "所有补丁已应用,打了补丁的源码在 /tmp/${PACKAGE_NAME}/${PACKAGE_NAME}-${VERSION} 目录中。"
添加打印日志,重新编译,查看日志,gdb
# 进入/data/rpmbuild目录:
cd /data/rpmbuild
# 修改代码,添加日志:
# 提交commit,生成patch:
git commit -m "xxx"
git format-patch HEAD^
# 将patch放到/data/rpmbuild/SOURCES:
# 修改/data/rpmbuild/SPECS/systemd.spec
## 添加该patch信息:
Patch0852: 0001-comment-to-test.patch
## 添加-g -O0 flag以允许gdb:
%configure "${CONFIGURE_OPTS[@]}"
export CFLAGS="-g -O0" #HERE
make %{?_smp_mflags} GCC_COLORS="" V=1
# 重新打包rpm
rpmbuild -ba SPECS/systemd.spec
# 在目标机器上重新安装:
rpm -e --nodeps systemd
rpm -e --nodeps systemd-debuginfo
rpm -ivh /data/rpmbuild/systemd-219-78.tl2.7.1.x86_64.rpm
rpm -ivh /data/rpmbuild/systemd-debuginfo-219-78.tl2.7.1.x86_64.rpm
# tailf -f查看systemd日志:
journalctl -f
# 重启csp-nginx
systemctl restart csp-nginx
# gdb systemd:
gdb /usr/lib/systemd/systemd 1
参考:1.https://systemd-devel.freedesktop.narkive.com/bLn5kkmz/systemd-debugging-with-gdb
最小复现环境
1.tlinux2.4操作系统,systemd版本为219-78.tl2.7.1 ,以docker容器拉起。
2.配置nginx为nginx用户:
chown nginx:nginx -R /var/log/nginx/
chown nginx:nginx -R /var/lib/nginx/
nginx_user=$(cat /etc/passwd|grep -w nginx|wc -l)
if [ $nginx_user == 0 ] ; then
useradd -d /var/lib/nginx -s /sbin/nologin nginx
fi
sed -i 's/user root;/user nginx;/g' /var/lib/nginx/nginx/conf/nginx.conf
之后重启nginx:
systemctl restart nginx
代码流程分析
service_load_pid_file(Service *s, bool may_warn)
// 传入pid_file与CHASE_SAFE的flag,检查权限是否正常:
fd = chase_symlinks(s->pid_file, NULL, CHASE_OPEN|CHASE_SAFE, NULL);
if (fd == -EPERM) {
questionable_pid_file = true;//设置该flag,表示该pid_file是有疑问的,可以暂且不检查该项目,继续只检查其他项目。后面还有其他判断,再决定本pidfile是否可以使用:
fd = chase_symlinks(s->pid_file, NULL, CHASE_OPEN, NULL);
}
此处chase_symlinks的作用:
- 逐级遍历pid路径:如当前服务的pid路径被定义为/var/lib/csp_nginx/nginx/sbin/nginx.pid,则会遍历到如下路径:
/var
/var/csp_nginx
/var/csp_nginx/nginx
/var/csp_nginx/nginx/sbin
/var/csp_nginx/nginx/sbin/nginx.pid
检查每个层级目录或文件归属的用户uid。若前者权限高于后者,则认为允许。若后者权限高于前者,则认为有安全风险,设置flag:questionable_pid_file=true,以供后续进一步检查,如下代码所示:
而物理机中,r > 0,因此未进入当前流程,而是认为允许,可以继续后续流程。
而当前docker中返回0,同时判断变量questionable_pid_file=true,随即打印日志并退出。
//进一步检查当前pid是否符合预期。
r = service_is_suitable_main_pid(s, pid, prio);
if (r == 0) {
if (questionable_pid_file) {
log_unit_error(UNIT(s)->id, "Refusing to accept PID outside of service control group, acquired through unsafe symlink chain: %s", s->pid_file);
return -EPERM;
}
}
函数static int service_is_suitable_main_pid(Service *s, pid_t pid, int prio)的主要作用为:
1.基本容错判断(略);
2.通过本service获取到manager,根据pid获取Unit owner。
3.判断owner是否与UNIT(s)相同。若相同,则返回1(物理机流程)。否则,返回0(当前docker容器流程)。
static int service_is_suitable_main_pid(Service *s, pid_t pid, int prio) {
Unit *owner;
/* Checks whether the specified PID is suitable as main PID for this service. returns negative if not, 0 if the
* PID is questionnable but should be accepted if the source of configuration is trusted. > 0 if the PID is
* good */
owner = manager_get_unit_by_pid(UNIT(s)->manager, pid);
if (owner == UNIT(s)) {
log_unit_debug(UNIT(s)->id, "New main PID "PID_FMT" belongs to service, we are happy.", pid);
return 1; /* Yay, it's definitely a good PID */
}
return 0; /* Hmm it's a suspicious PID, let's accept it if configuration source is trusted */
}
通过pid获取unit:
Unit *manager_get_unit_by_pid(Manager *m, pid_t pid) {
_cleanup_free_ char *cgroup = NULL;
int r;
//通过name:systemd和pid获取cgroup
r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup);
if (r < 0) {
return NULL;
}
//根据cgroup,获取unit:
return manager_get_unit_by_cgroup(m, cgroup);
}
此时docker里获取到的cgroup为/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/system.slice/nginx.service
根据cgroup获取unit:
Unit* manager_get_unit_by_cgroup(Manager *m, const char *cgroup) {
char *p;
Unit *u;
//1
u = hashmap_get(m->cgroup_unit, cgroup);
if (u){
return u;
}
p = strdupa(cgroup);
for (;;) {
char *e;
// find the position where '/' last appears, set it to e:
e = strrchr(p, '/');
if (e == p || !e){
return NULL;
}
// set *e to 0, so p can be stripped by position e:
*e = 0;
//2
u = hashmap_get(m->cgroup_unit, p);
if (u){
return u;
}
}
}
此时未从1中直接获取到。而是对传入的cgroup进行切割,获取不同层级的新cgroup名称传给p,然后在m->cgroup_unit中查询。
例如:
/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/system.slice/
/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/
/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/docker/
/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d
最终通过如下cgroup切割结果,可以从manager->cgroup_unit哈希表中找到unit,且该unit的id为'-.slice',即根层:p:'/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d', u->id:'-.slice', u->instance:'(null)'
而该函数返回后,在判断通过cgroup获取到的owner,与通过UNIT(s)获取到的unit时,二者不同,因此未返回1:
static int service_is_suitable_main_pid(Service *s, pid_t pid, int prio) {
if (owner == UNIT(s)) {
log_unit_debug(UNIT(s)->id, "New main PID "PID_FMT" belongs to service, we are happy.", pid);
return 1; /* Yay, it's definitely a good PID */
}
}
owner.id:'-.slice', UNIT(s).id:'nginx.service'
为何通过pid->cgroup->unit会与UNIT(s)获取到的不同?
UNIT(s)的定义为,根据service的meta指针获取unit:
/* For casting the various unit types into a unit */
#define UNIT(u) (&(u)->meta)
该值的初始化:
static void service_init(Unit *u) {
Service *s = SERVICE(u);
assert(u);
assert(u->load_state == UNIT_STUB);
s->timeout_start_usec = u->manager->default_timeout_start_usec;
s->timeout_stop_usec = u->manager->default_timeout_stop_usec;
s->restart_usec = u->manager->default_restart_usec;
s->type = _SERVICE_TYPE_INVALID;
s->socket_fd = -1;
s->bus_endpoint_fd = -1;
s->guess_main_pid = true;
RATELIMIT_INIT(s->start_limit, u->manager->default_start_limit_interval, u->manager->default_start_limit_burst);
s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
}
service_is_suitable_main_pid()函数中:
service->meta->cgroup_path:为/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/system.slice/nginx.service:
在/proc/<PID/cgroup中,获取定义:
int cg_pid_get_path(const char controller, pid_t pid, char *path)
{
//在/proc/<PID>/cgroup
fs = procfs_file_alloca(pid, "cgroup");
得到的cgroup为:“/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/s
ystem.slice/nginx.service”
注意此时manager->cgroup_root为"/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d"
从右向左,截取cgroup路径:
/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/system.slice/nginx.service
/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/system.slice
/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d
/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/docker
/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d
获取到的 id = 0x55910789b670 "-.slice",
p owner,可以看到id为"-.slice", description为"Root Slice",cgroup_path为: cgroup_path = 0x5591078b40d0 "/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d",
添加unit到manager->cgroup_unit时机
service_spawn
unit_realize_cgroup
unit_realize_cgroup_now
unit_create_cgroups
此时给manager->cgroup_unit添加的值为
cgroup_path: "/docker/9fc2f4125a5a54bdc029dc4c4a9a73f6524c9aee41a09b56d6b4cbfd28b3179d/system.slice/nginx.service"
是正确的值。
这一点也可以在/sys/fs/cgroup/systemd/docker/xxx/system.slice中得到印证:
也就是,通过service定义的unit还是正确的,但是在通过/proc//cgroup获取unit时,由于获取到的cgroup地址不正确(如:/docker/xx/docker/xx/systemd.slice/nginx,重复了两遍),且systemd代码中将其截取查询,最终以/docker/xxx为key,在manager->cgroup_unit中找到了root slice,即-.slice。
因此问题的根因在于:csp-nginx被拉起时,生成的/proc//cgroup是何处定义的,为什么不对?
《TODO》
cgroup与systemd: 通过src rpm获取systemd源代码,添加日志并使用rpmbuild重新打包的更多相关文章
- SLES 12 SP2 安装src.rpm软件包
系统型号: SUSE Enterprise mv systemd-228-117.12.src.rpm systemd cd systemd 执行下面的命令解压: rpm2c ...
- systemd 和 如何修改和创建一个 systemd service (Understanding and administering systemd)
系统中经常会使用到 systemctl 去管理systemd程序,刚刚看了一篇关于 systemd 和 SysV 相关的文章,这里简要记录一下: systemd定义: (英文来解释更为原汁原味) sy ...
- 一次性安装src.rpm编译所依赖的软件包
yum-builddep SRPMS/fcitx-4.2.8.4-4.1.cgdl21.src.rpm NAME yum-builddep - install missing depend ...
- 从.src.rpm包中提取出完整的源码的方法
1 什么是完整的源码 就是说,最初始的源码加上打了所有的patch后的源码,即最新的源码. 2 过程 2.1 从.src.rpm中提取完整的rpm工程文件 2.1.1 rpm to cpio rpm2 ...
- 安装.src.rpm
.src.rpm在坟墓镜像中能找到,例如6.8 os 的rpm包的.src.rpm格式就存放在http://vault.centos.org/6.8/os/Source/ .src.rpm是源码包,是 ...
- 活用RPM获取包的信息
rpm -q 功效大 如果你想要在系统上安装.卸载或是升级软件,需要对系统软件进行查询:或是有如下的场景: 安装了一个软件,需要知道这个软件的版本. 遇到一个文件,不认识它,需要知道它是什么软件,有什 ...
- src.rpm包的解压
有时候,我们在找源码包时候,发现有src.rpm的包:而不是tar.gz/tgz/zip结尾的. 那么如何去看这个src.rpm里面的详细信息呢? 看完下面这个例子,基本上明白了. 1,首先,生成sp ...
- src.rpm格式的RHCS源码提取
RHCS源码下载(地址1:地址2) 参考文档(RHCS安装也配置) RHCS源码提取(参考) 方法一: (1)rpm –ivh magma-plugins-1.0.15-3.src.rpm 执行r ...
- [转] Linux 安装.src.rpm源码包的方法
方法一:以setarch-1.3-1.src.rpm 软件包为例(可以到CSDN http://download.csdn.net/source/215173#acomment下载) 假设该文件已经存 ...
- src.rpm包安装方法
有些软件包是以.src.rpm结尾的,这类软件包是包含了源代码的rpm包,在安装时需要进行编译.这类软件包有多种安装方法,以redhat为例说明如下: 注意: 如果没有rpmbuild可以从系统安装光 ...
随机推荐
- 记ios的input框获取焦点之后界面放大问题
在移动端开发项目中,发现页面在使用 iPhone 访问的时候,点击 input 和 textarea 等文本输入框聚焦 focus() 时,页面会整体放大,而且失去焦点之后页面不能返回原来的样子.检查 ...
- Dapper.SimpleCRUD:Dapper的CRUD助手
我们在项目开发中,面对一些高并发.大数据量等业务场景,往往对SQL语句的性能要求比较高,这个时候为了方便灵活控制,我们一般就会编写原生的SQL. Dapper就是一个非常高性能的轻量级ORM框架,Da ...
- Java调用与发布Webservice接口(一)
一 准备工作 (一)开发环境 demo以springboot为基础框架,使用到了httpclient.hutool等依赖,详情如下: springboot版本: org.spri ...
- Linux sudo 提权之软链接攻击
软链接提权的原理 低权限用户能够以 root 用户的权限执行某个脚本,该脚本中又使用到了诸如 chown 等命令修改文件的权限,且该文件又能够被低权限的用户所修改.因此低权限的用户可以删除该文件,然后 ...
- fatal: repository 'http:/xxxx/root/x.git/' not found
我遇到的问题其实就是gitlab旧服务器迁移到新服务器之后用户的没权限了没有权限操作了 所以抛出该异常 解决办法是赋予新的权限
- Qt编写视频播放器(支持pbonon/qmediaplayer/ffmpeg/vlc/mpv等多种内核)
一.前言 花了一年多的时间,终于把这个超级播放器做成了自己想要的架构,用户的需求是一方面,自己架构方面的提升也是一方面,最主要是将界面和解码解耦了,这样才能动态的挂载不同的解码内核到不同的视频监控窗体 ...
- Qt编写安防视频监控系统57-子模块1设备列表
一.前言 近期在经历过这次UI大重构以后,很多拆分的功能都以单独的模块的形式出现,以悬停窗体的形式嵌入或者悬浮在主窗体中,这种方式极大的增强了系统的拓展性,客户想要什么模块就开启什么模块,放置到合适的 ...
- Linux服务器环境安装mysql
背景 1.安装环境:kvm虚拟机 2.运行环境:linux 3.架构:x86 4.安装mysql版本:mysql-5.7 1.安装准备 # Mysql官网 https://downloads.mysq ...
- 通过WebView2获取HTTP-only cookie
通过WebView2获取HTTP-only cookie可以使用`WebView2.CookieManager`类的方法.以下是一个示例代码,演示如何获取HTTP-only cookie: using ...
- 引发类型为“System.Windows.Forms.AxHost+InvalidActiveXStateException”的异常 解决办法
出现题目的异常,多是引用第三方控件引起的. 在NEW时,需要初始化该对象. AxESACTIVEXLib.AxESActiveX ax = new AxESACTIVEXLib.AxESActiveX ...