先说结论:这个报错不是你的服务写错了,而是你的系统里压根没有安装提供 cgconfig.service 的软件包,或者安装的版本太老、路径不对,导致 systemd 根本找不到这个单元文件。很多人在网上搜到老教程,照着执行 systemctl start cgconfig,结果直接栽在第一步。
如果你正卡在这个地方,别急。这篇文章我把整个问题的来龙去脉、排查链路、以及三种可落地的解决方案全部拆开讲清楚,顺便把我在实际环境中踩过的坑也一并交代了。无论是 CentOS 7、Ubuntu 20.04,还是新一点的 Rocke Linux 9,你都能在里面找到对应的处理思路。
1. 这个报错到底意味着什么——从"服务不存在"到"谁该提供这个服务"的认知转换
1.1 systemctl 说"could not be found"的真实含义
我们先抠一下报错本身的措辞。Unit cgconfig.service could not be found 这句话里有两个关键词:Unit 和 could not be found。在 systemd 的世界里,Unit 是对服务、挂载点、设备、套接字等资源的统一抽象,而 cgconfig.service 是一个典型的 service 类型单元。systemd 在启动这个单元之前,会按照固定的搜索路径去找对应的单元定义文件,也就是 .service 后缀的配置文件。
如果 systemd 在整个搜索路径里都没有找到 cgconfig.service 这个文件,它就会直接返回 could not be found。注意,它不会去管这个服务是不是真的有必要存在,也不会管你是不是刚从某个教程里复制来的命令——找不到就是找不到,零容忍。
这里有个很容易混淆的点:很多人把这个报错和 cgconfig.service failed to start、cgconfig.service start request repeated too quickly 这类报错混为一谈。后者是单元文件存在、但服务启动过程出错;前者是连单元文件都不存在。两个问题的处理方向完全不同。如果你看到的是 could not be found,那排查范围可以立刻收敛到"系统里有没有这个文件"以及"systemd 能不能找到这个文件"这两个点上。
1.2 谁应该负责提供 cgconfig.service
要回答这个问题,得先搞清楚 cgconfig 是什么。cgconfig 是 libcgroup 工具集里的一个组件,它的作用是在系统启动时按照配置文件 /etc/cgconfig.conf 来创建 cgroup 层级、设置子系统参数、分配资源限额。换言之,它是 cgroup v1 时代用来做"静态资源配置"的经典工具。
那么,谁负责把它打包成 systemd 能识别的服务单元?答案是发行版的软件包管理器。在 RHEL/CentOS 系列上,这个包叫 libcgroup-tools;在 Debian/Ubuntu 系列上,对应的包名是 libcgroup-tools(是的,名字也一样,但版本和行为有差异)。这个包安装之后,会在 /usr/lib/systemd/system/ 目录下放入 cgconfig.service 文件,同时在 /etc/init.d/ 下可能还保留一份 SysV 脚本。
所以,你的第一反应不应该是去手写服务文件,而是先问自己:libcgroup-tools 到底装了没有?如果装了,版本对不对?在实际环境中,我遇到的大部分 could not be found 都是因为装了这个包但 systemd 没能正确加载单元,或者压根没装。少部分情况是装了一个阉割版——某些容器镜像为了瘦身,只装了 libcgroup 运行库,没装 tools,自然也就没有 service 文件。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统里到底有没有这个单元文件——排查链路的第一步
2.1 用最直接的方式确认文件是否存在
不要一上来就改配置、写服务,先做三件事。这三件事的顺序很重要,能帮你快速定位问题层级。
第一件事,查看单元文件是否存在。执行下面的命令:
bash复制systemctl status cgconfig.service
如果输出还是 could not be found,继续执行:
bash复制find /usr/lib/systemd/system /etc/systemd/system -name "cgconfig*" 2>/dev/null
ls -l /usr/lib/systemd/system/cgconfig.service /etc/systemd/system/cgconfig.service 2>&1
如果 ls 直接报 No such file or directory,那就说明系统里压根没有这个单元文件。这时候你再执行第二件事——确认软件包的安装情况。
在 RHEL/CentOS 系上:
bash复制rpm -qa | grep libcgroup
rpm -qf /usr/lib/systemd/system/cgconfig.service
在 Debian/Ubuntu 系上:
bash复制dpkg -l | grep libcgroup
dpkg -S /usr/lib/systemd/system/cgconfig.service
rpm -qf 和 dpkg -S 的作用是反查某个文件归属于哪个软件包。如果返回 file is not owned by any package,说明这个文件确实不是由任何包提供的,那么问题基本可以定性为:软件包未安装,或者安装的版本不包含该文件。
第三件事,看看 systemd 能识别到的单元列表里有没有 cgconfig:
bash复制systemctl list-unit-files | grep -i cgconfig
systemctl list-units --all | grep -i cgconfig
list-unit-files 查看的是所有已安装的单元定义文件,而 list-units --all 查看的是 systemd 当前加载到内存里的单元实例。如果前者有、后者没有,说明 systemd 还没来得及加载新文件,执行一遍 systemctl daemon-reload 就能解决。如果前者直接为空,那就回到第二步,确认软件包。
这三步跑完,你基本就能判断出问题出在"包没装"还是"包装了但 systemd 没加载"这两个层面上了。
2.2 daemon-reload 到底在什么时候才有效
很多教程会告诉你:改了 systemd 配置之后要执行 systemctl daemon-reload。这个指令确实重要,但它的作用范围经常被误解。daemon-reload 是让 systemd 重新扫描单元文件目录、重新加载所有单元的配置,它解决的是"文件存在但 systemd 还没感知到"的问题。
但它不解决"文件不存在"的问题。如果你系统中压根没有 cgconfig.service,执行一万遍 daemon-reload 也没用,systemd 不可能无中生有地变出一个单元来。
所以,正确的执行顺序是:先确认文件存在,再执行 daemon-reload,最后再尝试 systemctl start cgconfig.service。反过来的话,你会陷入"重载、启动、报错、再重载、再启动"的循环里,浪费大量时间。
还有一个细节容易被忽略:单元文件的位置。systemd 的单元搜索路径是有优先级的,/etc/systemd/system/ 下的文件优先级高于 /usr/lib/systemd/system/(不同发行版路径可能略有差异,但总体原则一致)。如果你在 /etc/systemd/system/ 下手工放了一个 cgconfig.service,它会覆盖 /usr/lib/systemd/system/ 下的同名文件。排错时如果两个位置都有文件,记得对比一下内容,看看是不是你自己的自定义配置覆盖了默认配置。
3. 为什么不同系统、不同版本会有完全不同的反应——根因剖析
3.1 传统发行版:libcgroup-tools 的安装经历
在 x86_64 的 CentOS 7 上,安装 libcgroup-tools 之后,你会在 /usr/lib/systemd/system/ 下看到 cgconfig.service 和 cgred.service 两个文件。前者负责管理系统启动时对 cgroup 的配置,后者负责 cgred 守护进程(用于将进程按规则自动归入 cgroup)。
但如果你用的是 CentOS 8 或 Stream 8,情况就有点变化了。由于 cgroup v2 在 RHEL 8 中成为默认,cgconfig 的定位变得有点尴尬——它能配 v1 也能配 v2,但 systemd 自己已经内置了对 cgroup v2 的管理能力,libcgroup-tools 更多是作为一种兼容手段存在。在 RHEL 9 上,情况更是如此,软件包虽然还在仓库里,但默认不安装,而且单元文件的行为也和 v1 时代有所差异。
这就引出一个关键问题:你从网上搜到的教程,可能基于 CentOS 6 或 7。在那个年代,cgconfig.service 是一个非常标准的服务,用来在开机时加载你写在 /etc/cgconfig.conf 里的配置。但在新系统上,这个服务可能已经不被默认提供了,或者即便安装,它的工作方式也和系统自带的 cgroup 管理器产生冲突。
同样的道理,在 Ubuntu 20.04 上,libcgroup-tools 包是存在的,安装后也会提供 cgconfig.service。但 Ubuntu 18.04 及更早版本的系统如果是从 upstart 时代一路升级上来的,可能会遇到 SysV 脚本和 systemd 单元文件的兼容性问题。某些情况下,/etc/init.d/cgconfig 存在,但 systemd 单元文件缺失,于是你在 systemctl 下看不到服务,却能在 service cgconfig start 下把它跑起来。这种"历史包袱"在新部署的环境里不常见,但在存量系统上排错时经常遇到。
3.2 容器镜像和精简系统:谁偷走了你的单元文件
另一种常见场景是容器镜像。Docker 或 Kubernetes 里基于 CentOS/Ubi 的镜像,为了控制体积,往往只安装运行所需的必要软件包。libcgroup-tools 这种偏向运维管理的工具通常不会被默认包含。
我在实际排查过一个案例:一个基于 centos:7 镜像的容器,应用日志里反复提示 cgroup 配置失败,我们想把 cgconfig.service 跑起来辅助调试,结果直接 could not be found。检查之后发现镜像里连 libcgroup-tools 都没有,甚至连 /etc/cgconfig.conf 都不存在。这不是系统坏了,而是镜像精简策略的结果。
这种情况下,如果业务上确实需要 cgroup 静态配置,有两个思路:一是在 Dockerfile 里通过 RUN yum install -y libcgroup-tools 把包装进去;二是完全绕开 cgconfig,改用运行时参数或 systemd 自身的 cgroup 管理能力来配置资源限制。具体怎么选,取决于你的场景是"容器内"还是"宿主机级"。
另外,还要注意 cgroup v1 与 v2 的问题。如果你的宿主机已经挂载了 cgroup v2,而容器里还在尝试用 libcgroup 工具去操作 v1 的挂载点,那就会遇到"文件存在但功能异常"的情况,比如明明安装成功了、服务也能启动了,但写入配置时报 No such file or directory,因为 /sys/fs/cgroup/cpu 这个目录在 v2 下根本不存在。这部分我在后面专门展开讲。
3.3 没有 systemd 的环境:到底该不该用 cgconfig
如果你的环境不是通过 systemd 管理的——比如用了 SysV init 的旧系统、或者一个极简的 busybox 容器——那么 cgconfig.service 的概念本身就不适用。这时候你需要的可能是 /etc/init.d/cgconfig 脚本,或者是直接手动执行 cgconfigparser -l /etc/cgconfig.conf 来加载配置。
这个区分很重要。systemd 报 could not be found 的前提是"以 systemd 为 PID 1"的系统。一旦脱离这个前提,你的排查方向就要从"systemd 单元文件"转向"初始化系统兼容层"。在 SysV init 系统上,service cgconfig start 会去执行 /etc/init.d/cgconfig 脚本,只要这个脚本存在且有执行权限,服务就能跑起来,完全绕开 systemd。
所以,遇到 could not be found 时,先确认环境里到底是不是 systemd 在管。如果输出 systemctl --version 正常,再按前面的思路走;如果 systemctl 都执行不了,说明问题根本不在 systemd 层面。
4. 把问题彻底解决掉的三种方案——按场景选,别盲目照搬
4.1 方案一:安装 libcgroup-tools,让单元文件"归位"
适合场景:系统是传统发行版(CentOS 7/8、Ubuntu 18.04/20.04),仓库里有现成的包,且你的使用习惯是继续沿用 /etc/cgconfig.conf 这套静态配置。
在 RHEL/CentOS 系上:
bash复制sudo yum install -y libcgroup-tools
sudo systemctl daemon-reload
sudo systemctl enable --now cgconfig.service
如果是 CentOS 8+ 且默认启用了 dnf:
bash复制sudo dnf install -y libcgroup-tools
sudo systemctl daemon-reload
sudo systemctl enable --now cgconfig.service
在 Debian/Ubuntu 系上:
bash复制sudo apt update
sudo apt install -y libcgroup-tools
sudo systemctl daemon-reload
sudo systemctl enable --now cgconfig.service
安装完成后,确认一下单元文件确实存在:
bash复制ls -l /usr/lib/systemd/system/cgconfig.service
如果文件存在但 systemctl 仍然报 could not be found,大概率是 systemd 的单元缓存问题,再执行一次 daemon-reload,然后 systemctl restart 看看。
此时再检查服务状态:
bash复制systemctl status cgconfig.service
正常情况下的输出应该包含 Active: active (exited) 字样。注意,(exited) 在这个服务的场景下是正常的,因为 cgconfig.service 的本质是在启动时执行一次 cgconfigparser,把配置文件解析并写入 cgroup 文件系统,执行完就退出,不会像守护进程一样常驻。如果你的输出是 Active: active (running),反而要警惕,这可能说明系统里有一个额外的常驻进程,行为不符合预期。
4.2 方案二:手动编写单元文件——绕开包管理器的依赖泥潭
适合场景:软件仓库里没有 libcgroup-tools(比如某些第三方镜像)、包版本过旧、或者你不想为了一个工具装一堆依赖。这种情况下,手动写一个最小化的 systemd 单元文件是可行的。
先创建一个服务文件:
ini复制# /etc/systemd/system/cgconfig.service
[Unit]
Description=Create cgroup directories and set resource limits
Before=multi-user.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/cgconfigparser -l /etc/cgconfig.conf
ExecStop=/usr/sbin/cgclear
[Install]
WantedBy=multi-user.target
注意两个关键点:
第一,ExecStart 执行的 cgconfigparser 路径要和你系统上实际路径一致。不确定时用 which cgconfigparser 获取绝对路径,不同发行版的路径可能不同,比如 CentOS 上默认是 /usr/sbin/cgconfigparser,但某些发行版可能会放在 /usr/bin/ 下。
第二,Type=oneshot 加上 RemainAfterExit=yes 这两个配置是配套使用的。因为 cgconfigparser 执行完就退出,不设置 RemainAfterExit=yes 的话,服务状态会显示为 inactive (dead),虽然配置已经生效,但你用 systemctl status 看到的是"死了"的状态,容易造成错觉。设置了 RemainAfterExit=yes 之后,systemd 会认为这个服务在执行完成后是处于"激活"状态的,直到显式停止。
写完后,依次执行:
bash复制sudo systemctl daemon-reload
sudo systemctl start cgconfig.service
sudo systemctl enable cgconfig.service
systemctl status cgconfig.service
如果服务启动报错,查看日志:
bash复制journalctl -u cgconfig.service -n 50 --no-pager
手动编写单元文件的优势是轻量、可控,不依赖软件包仓库。但代价是你需要自己对 cgconfigparser 的参数、配置文件格式、以及 cgclear 清理行为负责。如果你对这几个工具不熟,还是优先考虑方案一,让发行版维护者帮你处理这些细节。
4.3 方案三:彻底抛开 cgconfig.service——直接用 systemd 或运行时参数管理 cgroup
适合场景:你的系统已经默认使用 cgroup v2,或者你其实只需要对个别进程做资源限制,并不需要全系统的静态 cgroup 配置。
在很多新系统上,你完全可以不启动 cgconfig.service,而是通过 systemd-run 来创建临时 scope 或 service,限制 CPU、内存等资源:
bash复制sudo systemd-run --scope -p CPUQuota=50% -p MemoryMax=1G ./your_program
这种方式的优势在于,它走的是 systemd 原生支持的 cgroup v2 管理接口,不用关心 cgroup 文件系统的具体挂载位置,也不用手动创建层级目录。systemd 会在运行期间自动创建对应的 slice/scope,程序退出后自动清理。
如果是想限制某个已有的服务,可以在服务的 unit 文件里加:
ini复制[Service]
MemoryMax=1G
CPUQuota=50%
然后 daemon-reload 重启服务即可。
对于 Docker 容器,更直接的方案是使用运行时参数:
bash复制docker run --memory=1g --cpus=0.5 --name test your_image
这个方案的本质是"换一种姿势"实现 cgroup 配置,而不是和 cgconfig.service 死磕。换个角度来看,cgconfig.service 只是历史演进中的一个中间产物。当 systemd 自己就具备强大的 cgroup 管理能力时,再为一个工具去维护一个独立的 service,反而增加了系统的复杂度。所以,如果你的场景允许,我建议优先考虑这条路线。
5. 搞定服务之后,还有这几个坑等着你
5.1 血泪教训:cgroup v1 和 v2 不能用同一套配置
这是我在一次生产环境变更中踩过的最深的坑。当时系统从 CentOS 7 升级到 CentOS 8,我把原来的 /etc/cgconfig.conf 原封不动地拷贝过去,启动 cgconfig.service 后,systemctl status 显示 active,但是配置完全没生效。
查了半天才发现,CentOS 8 默认挂载的是 cgroup v2,文件系统布局和 v1 完全不一样。v1 时代,CPU 和内存控制分别在 /sys/fs/cgroup/cpu、/sys/fs/cgroup/memory 下,各自有独立的层级;v2 时代,统一挂载在 /sys/fs/cgroup,所有控制器共享同一个层级,并且通过 cgroup.controllers 和 cgroup.subtree_control 文件来管理控制器的开启与关闭。
检查系统用的是 v1 还是 v2:
bash复制stat -fc %T /sys/fs/cgroup
输出是 cgroup2fs 说明是 v2,输出是 tmpfs 则可能是 v1(或者处于 v1/v2 混合模式)。如果你的系统和配置文件不匹配,启动服务不报错,但实际配置无效果,这种"静默失败"比直接报错更难排查。
如果你确实需要在 v2 系统上沿用 /etc/cgconfig.conf 的写法,要注意配置语法和控制器名称的差异。比如 v1 里的 cpu 控制器,在 v2 里变成 cpu 和 cpuset 分离;v1 里的 blkio,在 v2 里对应 io。不同版本、不同发行版的兼容性差异很大,最稳妥的办法是直接查阅当前系统上 cgconfigparser 的手册页,而不是照抄网上旧文章里的配置。
5.2 不要一上来就改内核启动参数
有人遇到 cgroup 配置不生效,第一反应是去 /etc/default/grub 里加内核参数。比如强制启用 cgroup v1,通过 systemd.unified_cgroup_hierarchy=0 或者 systemd.legacy_systemd_cgroup_controller 这些参数,让系统回到 v1 布局。
这个操作确实可以让某些老的配置重新工作,但代价很大:需要重启系统、修改引导参数、承担内核参数变更带来的未知风险。而且,如果业务层已经按 v2 方式在工作(比如新版 Docker、Kubernetes 都原生支持 v2),你把系统强行切回 v1,可能会引发其他组件行为异常。
我的建议是,只有在"非用 cgconfig 不可"且"没有时间改造配置"的条件下,才考虑用这个方案。正常路径还是优先改造配置文件去适配当前内核的 cgroup 版本。
5.3 日志去哪个文件查,怎么查
排错到最后,所有问题都会指向日志。journalctl -u cgconfig.service 是第一步,但往往不够。因为 cgconfig.service 本质是执行 cgconfigparser,它的输出会打到 journal 里,但如果配置文件里面引用了不存在的路径或通配符,解析器会直接报错,日志里能看到具体的行号。
如果 journal 里信息不足,再去看两个地方:/var/log/messages 或 /var/log/syslog,取决于发行版。还有一个容易被忽视的地方是 dmesg,cgroup 相关操作在内核层失败时,会往这里打日志。举个例子,如果你尝试在一个已经有子进程的 cgroup 里修改控制器状态,内核会拒绝操作,并在 dmesg 里给出 cgroup: cannot change the control group of the process 这样的提示。
日志的阅读顺序建议是:先 journalctl -u 看服务的标准输出,然后 dmesg 看内核层提示,最后再去翻系统日志确认有没有其他进程干扰。
6. 几个能让你少走弯路的判断逻辑
最后,我把这几年处理 cgroup 和 systemd 问题积累的一些判断逻辑整理出来,希望能给你一个清晰的决策框架。
判断一:先看 unit 文件是否存在,再谈其他。systemctl status 拿到 could not be found 后,不要继续做任何无关操作,直接 ls 对应路径。
判断二:unit 文件不存在时,优先查软件包,而不是手写。手写是备选方案,不是首选。因为软件包提供的单元文件通常经过测试,还包含一些你不一定能想到的依赖关系(比如 cgconfig.service 里可能设置了 After=local-fs.target 之类的依赖)。
判断三:服务已启动但配置不生效,查 cgroup 版本。这条适用于"active 但没效果"的场景。快速判断方法是看 /sys/fs/cgroup 下有没有 cgroup.controllers 和 cgroup.subtree_control 这两个文件,如果有,就是 v2,配置语法大概率不能用 v1 的旧写法。
判断四:日志和配置文件永远是最可信的。别凭经验猜,别听人说是 v1 还是 v2 就直接改方案,在系统上执行 stat -fc %T /sys/fs/cgroup、cat /proc/cmdline 和 journalctl -u 这三个命令,事实立刻清晰。
实际上,cgconfig.service 报找不到这个问题,在排障思路上和任何 systemd 服务报 could not be found 都是相通的——先确认文件、再确认包、再确认 systemd 状态。把这套思路记在脑子里,将来遇到其他服务报类似错误,你也会更有底气,不会一上来就慌。
