ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux应用管理实战:从安装来源到systemd与资源限制

Linux应用管理实战:从安装来源到systemd与资源限制 应用管理这四个字听着平淡真在 linux 上待久了就知道出事故的地方八成不在装不上而在装上了之后没人管得住。这篇是我那条应用管理笔记链条上的第 28 到第 37 条前面 27 条铺的是环境准备和基础命令从这批开始才真正动手一个东西从哪来、装到哪去、怎么让它开机自己起、跑起来之后怎么限住它、出事怎么查、不想要了怎么清干净。这十条我自己前后踩了大概两三年坑才摸顺写给两类人看——一类是刚把系统装上、连 apt 和 dnf 都还没分清的新手另一类是平时能装能跑但一遇到端口占用、日志撑爆磁盘、卸载完还留着半个残骸就发怵的朋友。每条我都按为什么这么选—具体怎么敲—哪里容易翻车的顺序讲命令基本可以直接抄。1. 地基三件事来源、工具、目录1.1 第28条先搞清楚一个应用到底从哪来我见过太多人一上来就curl ... | bash装完之后问他是哪个版本、配置在哪、怎么升级全答不上来。应用管理的第一个动作不是敲命令而是分类。Linux 上的应用来源其实就六种搞清楚来源后面升级、卸载、排查全都有方向。第一类是发行版官方仓库Debian 系走 apt/dpkgRed Hat 系走 dnf/rpmArch 系走 pacman。优点是有签名校验、依赖自动解析、能统一升级缺点是版本偏旧尤其是服务器发行版。第二类是第三方仓库比如各家软件自己维护的 apt/dnf 源装之前必须把 GPG key 导进来否则 apt 会直接拒绝。第三类是官方二进制压缩包解压即用典型的就是各种中间件。第四类是源码编译可控性最高但维护成本也最高。第五类是容器镜像应用和环境一起打包隔离性最好。第六类是 AppImage、Flatpak、Snap 这类沙箱式打包格式主要用在桌面场景。怎么快速判断一个已经装好的应用属于哪一类我给你三条命令基本能覆盖# 这个可执行文件属于哪个包Debian 系 dpkg -S $(readlink -f $(which nginx)) # Red Hat 系对应写法 rpm -qf $(readlink -f $(which nginx)) # 看它到底在跑哪个文件而不是 PATH 里那个名字 ls -l /proc/$(pgrep -x nginx | head -1)/exe最后那条特别关键。同名二进制在系统里存在多个版本是常态which给的是 PATH 里第一个不代表进程真正加载的那个。用/proc/PID/exe看才是真相这个坑我在一次明明改了配置却没生效的排查里栽了整整一下午。六种来源的取舍我用一张表说清楚你自己对号入座来源升级方式卸载干净度适合场景发行版仓库一条命令全升高基础依赖、常用工具第三方仓库同仓库升级中需要较新版本的成熟软件官方二进制包手动替换目录中中间件、数据库源码编译重新编译低需要自定义编译参数容器镜像换 tag高微服务、隔离环境沙箱格式包管理器升级高桌面应用我个人的原则是能在仓库里解决的绝不自己编译仓库版本实在不满足先看有没有官方源官方源也没有才考虑二进制包或容器源码编译是最后手段而且必须做好记录。1.2 第29条包管理器的四类操作别混着用包管理器看着简单其实最容易出事的就是查、装、升、清这四类操作混着用。我先给一套 Debian 系的完整流程再解释每一步在干嘛# 1. 刷新索引先让本地知道仓库里有什么 sudo apt update # 2. 查清楚再装看看版本和来源 apt-cache policy nginx # 3. 试运行看它到底要动哪些包 sudo apt install -s nginx # 4. 正式安装跳过推荐包 sudo apt install -y --no-install-recommends nginx # 5. 查已安装状态第一列的 ii 表示正常安装 dpkg -l | grep nginxapt update和apt upgrade是两件事前者只更新索引不装包后者才真正动系统。很多人把update当成升级敲完发现版本没变回头怀疑源有问题。apt-cache policy会告诉你候选版本、已装版本、来源地址这三项一出来版本对不对、源是不是你要的那个一目了然。--no-install-recommends这个参数我强烈建议加上。Debian 系默认会把 Recommends 字段的包一起装上一个命令行工具装完拖进来三十几个包是常事。服务器环境下能省则省少一个包就少一个被扫的目标、少一份升级负担。-s是模拟执行正式动手前跑一遍能看到依赖变更列表这个习惯救过我很多次。卸载这块要分清三档remove删包留配置purge连配置文件一起删autoremove清理没人依赖的孤儿包。标准的三连是sudo apt purge nginx nginx-common sudo apt autoremove --purge三个发行版的命令对照我列一下省得你来回查操作Debian/UbuntuRHEL/FedoraArch刷新索引apt updatednf makecachepacman -Sy安装apt installdnf installpacman -S查信息apt-cache policydnf infopacman -Si查包归属dpkg -Srpm -qfpacman -Qo卸载保留配置apt removednf removepacman -R卸载含配置apt purgednf remove默认含配置pacman -Rns清理孤儿apt autoremovednf autoremovepacman -Qtdq | pacman -Rns -注意apt 和 dpkg 的写操作不要交叉混用。dpkg 不做依赖解析dpkg -i装一个依赖缺失的包会把系统留在半配置状态apt -f install才能救回来。同理也不要手工去删/usr/lib下的文件清理软件包管理器不知道你删了下次升级会乱七八糟。1.3 第30条源码编译安装的目录规划到了必须源码编译的时候第一件事不是./configure是先决定装到哪。默认的/usr/local是对的但直接把所有软件都塞进去后面版本一多就成一锅粥。我推荐按软件名-版本号建独立目录再用软链接指向当前版本./configure --prefix/usr/local/nginx-1.24.0 \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_stub_status_module make -j$(nproc) sudo make install # 建软链接切换版本只改这一行 sudo ln -sfn /usr/local/nginx-1.24.0 /usr/local/nginxmake -j$(nproc)是并行编译把机器所有逻辑核都用上。但如果你这台机器上还有别的服务在跑建议用nproc减一留一个核给系统否则编译期间别的服务响应会明显变慢。这个取舍没有绝对答案我一般是在纯构建机上用nproc在生产机上留一个核。目录规划的第二步是让系统能找到这个软件。三种方式优先级从低到高一是把/usr/local/nginx/bin加进 PATH二是把库目录写进/etc/ld.so.conf.d/再跑ldconfig三是编译时直接用-Wl,-rpath把库路径写死进二进制。第三种最稳但最不灵活第一种最灵活但最依赖环境变量我一般用第二种。# 让动态链接器知道去哪找库 echo /usr/local/nginx-1.24.0/lib | sudo tee /etc/ld.so.conf.d/nginx.conf sudo ldconfig -v | grep nginx编译前把 configure 参数原样记到一个文件里比如/usr/local/nginx-1.24.0/BUILD_INFO写清楚编译时间、参数、依赖版本。半年后你要加一个模块重新编译靠的就是这个文件。另外make install会把文件复制到各处想干净卸载基本不可能所以要么保留 build 目录用install_manifest.txt反查要么一开始就用checkinstall把它打成系统包来管后者对新手更友好。2. 运行态管理服务、进程、自启动2.1 第31条用 systemd 把应用托管成服务应用装好了接下来最该做的一件事就是别再用nohup xxx 硬扛。systemd 是现在主流的服务管理工具把应用托管进去之后开机自启、崩溃重启、日志收集、资源限制全都统一了。一个生产可用的 unit 文件长这样[Unit] DescriptionMy App Service Documentationhttps://example.internal/myapp Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple Userappuser Groupappuser WorkingDirectory/opt/myapp EnvironmentFile-/etc/myapp/env ExecStart/opt/myapp/bin/myapp --config /etc/myapp/config.yml ExecReload/bin/kill -HUP $MAINPID Restarton-failure RestartSec3 LimitNOFILE65535 StandardOutputjournal StandardErrorjournal SyslogIdentifiermyapp [Install] WantedBymulti-user.targetType这个字段是新手最容易填错的。simple表示 ExecStart 启动的就是主进程这是最常用的forking表示进程会自己后台化父进程退出这时候必须配PIDFile不配的话 systemd 找不到主进程重启会失控notify需要应用主动调sd_notify上报就绪状态oneshot适合只跑一次就退出的初始化任务。选错了的典型症状是服务状态显示active (exited)但进程其实没起来或者反复重启。写完 unit 文件必须走这三步顺序不能反# 1. 改了 unit 文件必须 reload否则 systemd 用的还是旧配置 sudo systemctl daemon-reload # 2. 设置开机自启 sudo systemctl enable myapp # 3. 立即启动 sudo systemctl start myapp # 查看状态 systemctl status myapp journalctl -u myapp -n 50 --no-pagerenable和start是两件独立的事enable管开机自启start管当前这次。只start不enable重启机器服务就没了只enable不start得等下次开机。这两件事经常被混为一谈。Restart的取值我整理成表选错了会出现服务一直重启、日志被刷爆的场面取值触发条件适用场景no从不重启一次性任务on-failure非零退出码或被信号杀死大多数常驻服务always任何情况都重启必须常驻的守护进程on-abnormal被信号杀或被超时杀不想因配置错误无限重启配always的时候一定要配RestartSec不然崩溃循环会以毫秒级速度刷日志。StartLimitIntervalSec和StartLimitBurst在[Unit]段里可以限制单位时间内的重启次数超过就不再尝试避免拖垮整机。注意ExecStart必须写绝对路径systemd 不走你的 PATH。EnvironmentFile里的行不能带export格式必须是KEYvalue。用户环境变量比如~/.bashrc里的systemd 一概读不到这是新手最常见的我本地能跑、服务就是起不来的根因。2.2 第32条进程排查的三板斧服务起不来、CPU 飙高、端口被占这三类问题的排查动作高度重合我总结成三板斧谁在跑、它连着谁、它念的是哪本经。第一板斧是谁在跑。ps配合排序能一眼看出资源占用大户ps -eo pid,ppid,user,%cpu,%mem,etime,stat,args --sort-%cpu | head -20字段里stat那一列值得单独讲R是运行中S是可中断睡眠正常D是不可中断睡眠通常在等 IOZ是僵尸进程T是被暂停。看到大量D状态别急着 kill先看磁盘和网络kill -9对D状态进程是无效的它得等 IO 返回。第二板斧是它连着谁也就是网络和文件句柄# 看端口被谁占了 ss -lntup | grep -E :(80|443|8080)\b # 看这个进程打开了哪些文件 lsof -p 12345 # 看进程的工作目录和真实二进制 ls -l /proc/12345/cwd /proc/12345/exess已经全面替代了netstat速度更快信息更全。lsof -p的输出里REG是普通文件IPv4/IPv6是网络连接unix是本地套接字。排查文件句柄泄漏的时候直接lsof -p PID | wc -l看总数跟ulimit -n一比就知道是不是快撞上限了。第三板斧是它念的哪本经也就是配置文件到底加载的是哪个。很多应用支持多个层级的配置/etc/xxx/config、~/.xxx/config、命令行--config谁最后生效要看加载顺序。比较靠谱的办法是启动时加最高等级的日志或者在/proc/PID/cmdline里看实际参数tr \0 /proc/12345/cmdline; echo进程排查这个事我整理过一张速查表放在手边能省很多时间现象首选命令关注点端口被占ss -lntup进程名和 PIDCPU 飙高ps --sort-%cpu父子进程关系内存不释放cat /proc/PID/statusVmRSS 是否持续增长句柄泄漏lsof -p PID | wc -l与 ulimit -n 对比卡死不动cat /proc/PID/stack内核态调用栈启动即退journalctl -u xxx -n 100退出码和最后一行日志2.3 第33条开机自启的四种方式到底选哪个开机自启这件事Linux 上有四种常见做法坑的分布也不一样。第一种是 systemd也就是前面讲的systemctl enable。这是现代发行版的首选因为有依赖关系管理、失败重试、日志统一。第二种是crontab里的rebootreboot /opt/myapp/bin/myapp /var/log/myapp.log 21这种写法简单但有两个坑一是 cron 的环境变量极简PATH 只有/usr/bin:/bin你的应用依赖的自定义变量一个都没有二是 cron 守护进程启动时机不确定如果应用依赖网络或数据库reboot可能跑得比它们还早然后就失败了。所以我只在极简单的场景下用它。第三种是rc.local本质是启动脚本的一种遗留形式。较新的发行版默认不启用它得自己建文件、加可执行权限、再 enable 一个 systemd 单元来拉起。第四种是自己写 init 脚本放到/etc/init.d/这套 SysV 风格的东西现在还能跑但已经没有任何理由再新写。我的建议很干脆常驻服务一律用 systemd需要延迟执行的初始化任务可以写成一个Typeoneshot的 unit靠Before/After控制顺序真要在特定时间点跑一次性脚本用systemd-run --on-calendar或者at命令比 cron 干净。reboot只留给那些没有依赖、不需要保活的临时任务。3. 资源限制与依赖隔离3.1 第34条给应用划线——ulimit、cgroup 和 systemd 的资源限制应用跑起来不管早晚会给你惹事一个模块泄漏内存把整机 OOM一个进程疯狂开文件句柄把系统拖死。给应用划资源边界是应用管理里最容易被跳过、又最不该跳过的一步。先说最经典的Too many open files报错。这个报错的根因是文件描述符上限但上限有三个层级容易搞混系统级/proc/sys/fs/file-max、用户级/etc/security/limits.conf、进程级ulimit -n。关键是——limits.conf只对通过 PAM 登录的会话生效systemd 启动的服务不走 PAM所以你在limits.conf里写多少都没用。这是我见过最普遍的一个误会。对 systemd 托管的服务正确的做法是在 unit 文件里直接写[Service] LimitNOFILE65535 LimitNPROC8192 MemoryMax1G MemoryHigh800M CPUQuota150% TasksMax512 IOWeight200MemoryMax是硬上限超过直接 OOM killMemoryHigh是软上限超过之后内核会开始回收尽量压回这个值。两个搭配用既不希望它被杀、又要防止它无限膨胀的场景特别合适。CPUQuota150%表示最多用 1.5 个核这个百分比是按单核 100% 算的多核机器上可以放心往上给。TasksMax限制进程和线程总数防 fork 炸弹。改完别忘了sudo systemctl daemon-reload sudo systemctl restart myapp systemctl show myapp -p LimitNOFILE -p MemoryMax -p CPUQuota最后那条systemctl show是验证的关键别自己猜生效没生效。注意MemoryMax设得太小会频繁触发 OOM应用表现成随机崩溃看日志才有oom-kill字样。排查用dmesg -T | grep -i oom能看到被杀进程的名字和内存用量。给内存上限的时候留出至少 20% 的余量别贴着应用峰值设。3.2 第35条依赖冲突的隔离思路依赖冲突是源码部署里最烦的一类问题典型表现是这个版本要库 A 大于 2.0那个版本要库 A 小于 2.0。处理思路有四层从轻到重。最轻的是改环境变量。LD_LIBRARY_PATH能在运行时覆盖动态库搜索路径注意它的优先级高于/etc/ld.so.conf里的路径但低于二进制里的 rpath。调库问题的时候先用ldd看依赖树ldd /opt/myapp/bin/myapp | grep -i not found ldconfig -p | grep libsslnot found那一行就是缺的库。如果ldd显示都找到了但运行时报符号错误多半是版本不匹配这时候用LD_DEBUGlibs看它实际加载了哪个路径的文件问题立刻现形LD_DEBUGlibs /opt/myapp/bin/myapp 21 | grep -i calling init第二层是虚拟环境。Python 用户最该记住的一句话是不要往系统解释器里pip install。系统里的包是给系统工具用的你装的东西跟系统包互相覆盖出问题是迟早的事。用python3 -m venv /opt/myapp/venv建独立环境激活之后再装依赖干净利落。第三层是容器把应用和它依赖的整个用户态环境一起打包。这一层的隔离最彻底代价是运维复杂度上升镜像体积也要管理。第四层才是多版本共存用不同前缀目录加 rpath 或 wrapper 脚本来隔离。这是最后的办法能不用就不用因为长期维护成本很高。我把这四层的取舍整理一下方式隔离程度维护成本适用场景LD_LIBRARY_PATH低低临时验证、单库冲突venv / conda中低语言级依赖容器高中服务化部署多前缀 rpath高高同一机器多版本并存桌面场景下还有 AppImage、Flatpak、Snap 这条路。它们的思路是把应用和依赖一起打包运行时挂载到一个受控环境里跟系统库基本不打架。代价是体积大、启动稍慢、跟系统主题的融合需要额外配置。桌面用户图省事这条路值得考虑服务器上我一般不用。4. 日志、升级与干净卸载4.1 第36条日志管理别让根分区被写满根分区被日志写满是 Linux 上排得上号的经典事故。多数发行版默认把 journald 日志放在/var/log/journal但不设上限长时间运行能涨到几个 G。先看看当前占用journalctl --disk-usage然后限制它# 只保留最近 500M sudo journalctl --vacuum-size500M # 只保留最近 7 天 sudo journalctl --vacuum-time7d这两个是临时生效的清法。要长期生效改/etc/systemd/journald.conf[Journal] Storagepersistent SystemMaxUse500M SystemKeepFree1G MaxRetentionSec14day MaxFileSec1daySystemMaxUse是 journal 目录总上限SystemKeepFree保证磁盘至少有这么多空闲两个都配能防止极端情况。MaxFileSec1day让日志按天切分文件方便定位。应用自己写到文件里的日志交给 logrotate 管/opt/myapp/logs/*.log { daily rotate 14 missingok notifempty compress delaycompress copytruncate create 0640 appuser appuser }copytruncate这个参数要特别说一句。默认的 logrotate 是靠发信号让应用重开日志文件如果你的应用不支持这个信号就得用copytruncate先复制一份再清空原文件。它的缺点是复制和清空之间那一小段窗口可能会丢几行日志但对不配合的应用来说是最省事的方案。把配置放进/etc/logrotate.d/记得用logrotate -d干跑一遍验证语法。注意journald 和 logrotate 管的不是一回事。journalctl --vacuum-*只清 journal应用自己写的文件它管不着反过来 logrotate 也管不了 journal。两边都要配缺一个根分区迟早还会满。4.2 第37条卸载要卸干净别留半个残骸卸载这件事很多人以为apt remove敲完就完了其实系统里还留着四类残留被 purge 跳过的用户配置文件、应用自己写的数据目录、专门为它建的用户和用户组、以及被 autoremove 漏掉的孤儿依赖。先看被卸载但留有配置的包状态列是rc的那些dpkg -l | awk /^rc/ {print $2} sudo apt purge $(dpkg -l | awk /^rc/ {print $2})rc的意思是 removed but config files remainremove之后就是这个状态。想要干净必须purge。然后是应用自己写的数据这些位置要逐个检查/etc/app、/var/lib/app、/var/log/app、/opt/app、/usr/local/app*。包管理器不会碰/opt和/usr/local源码编译装在/usr/local的东西全靠你手动清。再是用户和用户组。为了安全我一般会给每个常驻应用建独立用户sudo useradd --system --no-create-home \ --shell /usr/sbin/nologin \ --home-dir /opt/myapp appuser--system表示系统用户--shell /usr/sbin/nologin禁止登录这是最小权限原则的体现。卸载的时候对应的清理是sudo userdel appuser sudo groupdel appuseruserdel默认保留家目录要一起删得加-r但对系统用户我建议先手动检查/opt/myapp里有没有你要留的数据确认后再删别一上来就-r。5. 常见问题速查与踩坑实录5.1 应用管理高频问题对照表下面这张表是我平时真会翻的一张现象、根因、命令、处理都列上了遇到直接对号入座现象常见根因排查命令处理方向服务启动即退出ExecStart 路径错/缺环境变量journalctl -u xxx -n 50改绝对路径、加 EnvironmentFile改了 unit 不生效没执行 daemon-reloadsystemctl cat xxxdaemon-reload 后 restart报 Too many open fileslimits.conf 对服务无效systemctl show xxx -p LimitNOFILEunit 里加 LimitNOFILE命令找不到PATH 没包含安装目录echo $PATH; which xxx加 profile.d 或做软链接动态库找不到ldconfig 未更新ldd xxx | grep not found写 ld.so.conf.d 后 ldconfig端口被占旧进程未退出ss -lntup确认后 kill或改监听端口根分区写满journal 或应用日志膨胀du -sh /var/log/*配 SystemMaxUse 和 logrotate卸载后仍占空间只 remove 没 purgedpkg -l | awk /^rc/purge autoremove解压文件名乱码压缩包用了非 UTF-8 编码file 包名unzip -O CP936 或 iconv 转码命令行为跟预期不符PATH 里有同名多版本/proc/PID/exe用绝对路径或调整 PATH 顺序解压乱码那一条单独说下这个在部署时挺常见。Windows 上打的 zip 包经常用 GBK 编码存文件名Linux 按 UTF-8 解出来就是一堆问号。处理办法是解压时指定编码unzip -O CP936 package.zip -d ./target如果是已经解压出来的乱码文件可以配合convmv批量改名改之前务必先复制一份出来试改名是不可逆操作。5.2 几条用时间和事故换来的经验第一条动手前先记答案。装任何东西之前把我现在是什么版本、我要装什么版本、装到哪、配置在哪、怎么退回去这五个问题的答案写在纸上或者文档里。我吃过的最大的亏就是升级完发现问题想回退却发现没记旧版本号只能从备份里翻。第二条环境变量和配置文件分开放。应用自己的默认配置不要直接改复制一份到/etc/myapp/config.yml在 unit 文件里用--config指过去。这样升级应用时包管理器覆盖的只是它自己的默认配置你的配置一动不动。第三条能用 systemd 就别用 shell 脚本守护。自己写的守护脚本看着简单但进程意外退出检测、日志轮转、启动顺序、资源限制全都要自己实现一遍最后写出来的东西大概率不如 systemd 稳定。第四条给每个服务建独立用户。这不仅是最小权限原则还能在排查时通过ps -u appuser一秒筛出这个应用的所有进程比按名字 grep 可靠得多。第五条日志级别和生产环境要匹配。开发时开 debug 是常规操作上线忘了改回来一个高并发场景半天就能写出几十个 G。上线前把debug改成info或warn应该列进部署清单。第六条所有操作留痕。不管是apt install还是源码编译装完写一条记录到团队的运维文档里时间、操作人、命令、验证结果。半年后出问题的人可能就是你自己那时候你会感谢当初写下来的那两行字。最后分享一个我自己的小习惯每台新机器我都建一个~/ops-notes目录里面按服务名分文件夹每个文件夹存三样东西——安装命令、配置备份、验证步骤。机器重装、迁移、交接的时候把这个目录打包带走基本上能省掉大半的重复劳动。这个习惯我坚持了好几年它带来的省心程度远超维护它花的几分钟。
返回列表