很多人学 Linux 会陷入一种误区:命令背得滚瓜烂熟,但真到了“把应用持续跑起来”的环节却抓瞎。1月14日,我把自己之前写的内部待办统计小工具部署到一台虚拟机上,把整个过程记成了一份 Linux 学习笔记。这篇笔记不是命令堆砌,而是一次真实的服务化部署实践:从目录设计、账户隔离、systemd 配置,到防火墙排查、日志轮转和资源限制——每个环节我都记录了为什么这么做、踩了什么坑、最后如何验证。适合已经会基本命令、但还没完整走通过一遍服务部署链路的人。
1. 今天的学习目标:把“跑通命令”升级成“跑通服务”
1.1 为什么选“服务化部署”作为学习主线
我在很多交流群里看到过一类初学者:文件操作、grep、awk 都用得很溜,甚至可以背出几十个命令的参数,但只要让他把一个小程序部署成一台服务器上长期运行的服务,就完全没了头绪。这不是态度问题,而是学习路径走偏了——大多数人学 Linux 是从“命令”入手的,但真实系统中的服务是围绕“生命周期”运转的。
所谓服务化部署,简单说就是让你的程序像 Nginx、数据库服务那样被系统统一管理:开机自动启动、崩溃自动拉起、日志集中记录、权限被隔离在一个专用账户下。这个过程会把 Linux 的进程管理、用户权限、网络监听、系统日志等核心知识串起来,比单纯刷命令列表有用得多。
所以我给今天的 Linux 学习笔记定了一个非常具体的主线:模拟一个内部场景,把名为 todo-svc 的待办统计小工具部署到一台干净的虚拟机上。这个工具本身并不复杂,它只是一个监听 HTTP 端口的小程序,关键是把“跑起来”变成“可靠地跑起来”。
1.2 今天要验证的完整学习闭环
在动手之前,我先给自己列了四个验收标准,避免学到一半就跑偏:
- 应用必须以专用非登录账户运行,而不是 root;
- 通过 systemd 管理启动、开机自启、崩溃自动重启;
- 能从另一台机器访问到服务,中间涉及端口监听和防火墙放行;
- 日志有落盘方案,并且包含基础的资源限制配置。
这四个标准刻意覆盖了 Linux 日常运维中最容易出问题的几个区域。更重要的是,我要求自己把每一步的“为什么”也写进笔记里。因为光知道命令,下次换个发行版、换个场景还是会卡住;理解了命令背后的机制,才能真正举一反三。
这天的整体时间安排也很简单:前两个小时做目录、账户等规划,中间三个小时写 systemd 和调权限、防火墙,最后半个小时做日志和资源限制。实践证明,计划看起来“简单”,真正执行时的坑一个都没少,而且大部分都藏在“你以为已经配好了”的地方。
2. 动手前的四项设计决策:目录、账户、端口与运行方式
2.1 目录结构:先想好东西放哪,后面才不会乱
在还没有写任何配置之前,我先确定了文件布局。这一步看似不起眼,却决定了后面排障时的思路是否清晰。我采用的方案是:
| 路径 | 用途 | 选择原因 |
|---|---|---|
| /srv/todo-svc/app | 程序代码和脚本 | /srv 目录用于存放本机提供服务的数据,语义契合 |
| /srv/todo-svc/data | 运行时的数据文件 | 和程序放一起,备份和迁移时方便整体处理 |
| /etc/todo-svc/config.yaml | 配置文件 | 配置和代码分离,符合系统惯例 |
| /var/log/todo-svc | 日志目录 | 集中放日志,后续做 logrotate 容易管理 |
有人习惯把这类程序直接丢在 /home 下的某个用户目录里,或者塞进 /opt。在真实服务器上,这两种做法都不太合适:/home 是给交互式用户放个人文件用的,混入服务目录会让备份和权限管理都变得混乱;/opt 适合第三方闭源软件的安装位置,但会破坏“源码、配置、日志分离”的整洁性。所以用 /srv 作为服务主目录,是符合大多数人习惯又能少踩坑的选择。
配置文件放在 /etc 下还有一个额外好处:系统安全工具会自动扫描 /etc 下面的文件权限异常,万一你权限设置失误,很容易通过常规审计发现。
2.2 账户隔离:最小权限可不是可选项
第二个决策是:绝不使用 root 运行 todo-svc。很多新手为了省事,直接把程序在 root 下跑起来,还觉得“服务器就我一个人用,没关系”。但真实环境里,服务一旦被入侵或被异常流量打穿,攻击者拿到的就是 root 权限,后果完全不同。
正确的做法是给服务单独创建一个系统账户,这个账户只需要对自身目录和日志目录有读写权限,对系统其它目录没有任何特殊权利。这种最小权限设计一开始会增加一点命令量,但收益非常明显:程序出问题后的影响面被限制在一个很小的范围内。
这一步我会在下一个章节详细展开,但设计阶段就要把结论定下来:todo-svc 使用专用系统用户运行,shell 指定为 nologin,禁止交互登录。
2.3 端口选择:避开约定俗成的麻烦
端口方面我选了 17280。原因很简单:
- 1024 以下的端口需要 root 权限绑定,既然不用 root 运行服务,就自然避开低端口;
- 很多默认服务和扫描工具会关注常用端口,选一个高位端口的业务端口,可以减少“占用了没注意到的端口”这类冲突;
- 端口号并不是越怪越好,关键是固定并写进配置,后续防火墙、监控都按这个端口统一管理。
当时我也考虑过直接用 8080,但 8080 太容易被各种默认应用占用,而且大多数服务器的安全策略对 8080 的排查也比较“敏感”。所以还是用了 17280 这个不太容易撞车的高位端口。
这里顺带提醒一句:如果你绑定的端口和某个已有服务冲突,启动时往往会报“Address already in use”。如果没报错但端口却连不上,多半不是端口本身的问题,而是防火墙或监听地址的问题,后面第五节会细说。
2.4 为什么这次不用容器直接部署
可能有人会问,现在部署服务不都是容器化吗?为什么还要这么麻烦地搞账户、systemd、防火墙?
容器确实把部署流程简化了很多,但它把“操作系统层”的很多细节也一并掩盖了。我们做学习笔记的目的是搞懂 Linux 本身,而不是把问题推给容器编排工具。所以我刻意选择裸机部署:不写 Dockerfile,不用编排平台,让每个系统组件都直接暴露在你面前。
这不是说容器没用。在实际生产里,容器化有它不可替代的优势。但作为一次系统学习,先用最原始的部署方式建立起对进程、用户、套接字、日志的感知,再去看容器会轻松得多。很多服务在容器里“莫名连不上”的问题,本质最后还是回到端口、防火墙、用户权限这几件事上。
3. 权限模型落地:从 sudo 到用户隔离的完整配置过程
3.1 创建专用系统用户
设计阶段定了“专用系统用户”之后,落地命令并不复杂:
sudo useradd --system --home /srv/todo-svc --shell /usr/sbin/nologin todo-svc这里几个参数的作用分别是:
--system:创建一个系统账户。系统账户的 UID 通常在系统保留区间,不会和普通用户混在一起,也意味着它没有正常的登录家目录需求;--home:指定它的家目录为 /srv/todo-svc,实际就是告诉系统这个账户的工作空间在哪;--shell /usr/sbin/nologin:设置登录 shell 为 nologin,配合前面提到的“禁止交互登录”需求。
创建完先确认一下:
id todo-svc grep todo-svc /etc/passwd正常情况下,你应该看到 uid、gid 信息都在合理范围内,而且 shell 是 /usr/sbin/nologin。
3.2 目录授权:一条 chown 命令引出的排障过程
账户建好后,我把程序代码放进了 /srv/todo-svc/app,数据文件放在 /srv/todo-svc/data,然后执行:
sudo chown -R todo-svc:todo-svc /srv/todo-svc sudo chmod 750 /srv/todo-svc /srv/todo-svc/app逻辑上这样已经没问题了:目录所有者是 todo-svc,组也是 todo-svc,权限为 750,意味着所有者可读写执行、同组用户可读执行、其他人完全不可访问。
但我第一次启动服务时,程序直接报错说无法读取自己的配置文件。当时我愣了一下:所有者和权限都给了,为什么还不行?
后来我用自带的文件路径解析命令查了一遍:
namei -l /etc/todo-svc/config.yaml这才发现问题的根源在 /etc/todo-svc 目录本身。虽然配置文件权限是正常的,但它所在的父目录由 root 所有,权限是 755。正常逻辑下 755 允许其他用户读和执行,可如果系统开启了额外的安全模块,它就会阻止 todo-svc 用户读取这个目录下的文件。
解决方式也简单:要么把配置目录的所有权改成 todo-svc,要么调整目录权限让同组用户能进入。考虑到安全审计要求配置目录不能随意打开,我最终选择了把它改为 todo-svc 用户所有:
sudo chown -R todo-svc:todo-svc /etc/todo-svc sudo chmod 600 /etc/todo-svc/config.yaml在 chmod 600 之后,配置文件只有 todo-svc 自己能读,其他人连读都读不到,这才符合“系统服务配置不向普通用户暴露”的预期。
这里的经验很直接:排权限问题时不要只盯着报错文件本身,还要看它所有父目录的权限。服务启动时,系统需要一层层进入每个目录,任何一层的“执行”权限缺失都会导致访问失败。
3.3 验证用户权限:不要“感觉可以”,要实际测
即使权限设置看起来对了,我也会用命令实际验证一下。比如确认 todo-svc 用户能执行程序:
sudo -u todo-svc /srv/todo-svc/app/todo-svc --version如果程序会输出版本号,说明该用户的权限链路是通的。还可以使用:
sudo runuser -u todo-svc -- /srv/todo-svc/app/todo-svc --check这两条命令的作用类似,都是以 todo-svc 用户的身份执行一次命令。区别在于sudo -u依赖 sudo 配置,而runuser更接近系统初始化时切用户的方式。
我在这一步还顺便验证了“这个账户不能登录”这一点。用su - todo-svc试了一下,系统直接把所有交互式登录请求弹回来,这正是 nologin shell 的效果。日常操作中,如果只是为了以某个用户身份执行命令,用 sudo -u 或 runuser 就够了,没必要也不应该切换到那个账户里做交互式操作。
4. systemd 单元文件:把“能启动”变成“能自愈”
4.1 单元文件里真正决定命运的几个选项
服务能不能被系统管理,核心在于给 systemd 编写一个服务单元文件。我在 /etc/systemd/system/todo-svc.service 创建了如下内容:
[Unit] Description=Internal Todo Stat Service After=network-online.target Wants=network-online.target [Service] Type=simple User=todo-svc Group=todo-svc WorkingDirectory=/srv/todo-svc/app ExecStart=/srv/todo-svc/app/todo-svc --config /etc/todo-svc/config.yaml Restart=on-failure RestartSec=5 NoNewPrivileges=true PrivateTmp=true [Install] WantedBy=multi-user.target这里最容易被忽略的是Type=simple。systemd 对服务类型有多种假设,Type=simple表示通过 ExecStart 启动的进程就是主进程本身。如果你的程序自带后台守护化参数(比如 fork 到后台),千万别在 Type=simple 下使用,否则 systemd 会误以为服务没有成功启动,然后反复重启。规范做法是让程序以前台方式运行,让 systemd 接管它的生命周期。
After=network-online.target是在告诉 systemd,这个服务应该等网络完全就绪后再启动。对需要绑定端口、提供对外服务的程序来说,这一步能避免开机时网络还没准备好的尴尬情况。
Restart=on-failure负责崩溃自动重启。它和Restart=always的区别在于:如果程序是正常退出(退出码为 0),on-failure 不会重新拉起;always 则不管什么退出原因都会拉起。对于大多数现场服务,on-failure 是更合理的选择,毕竟正常退出往往意味着运维人员有意停止服务。
4.2 第一次启动为什么会失败:一个典型的主谓宾错误
单元文件写好之后,执行:
sudo systemctl daemon-reload sudo systemctl start todo-svc sudo systemctl status todo-svc --no-pager结果出乎意料:服务启动失败,状态里直接提示“Executable path is not absolute / lacks execute permission”。
我第一反应是路径写错了,但检查了好几遍,路径分明是对的。后来用 ls 看了文件权限:
ls -l /srv/todo-svc/app/todo-svc才发现问题出在“可执行权限”上。文件在复制到 /srv/todo-svc/app 时确实没有执行权限,或者说执行位缺失。修复:
sudo chmod +x /srv/todo-svc/app/todo-svc然后重新加载、启动,这次终于变成了 running。整个过程看起来简单,但特别容易发生在“把代码从开发机拷贝到服务器”的场景中,尤其是通过压缩包或同步工具拷贝的情况。
如果你遇到服务起不来,第一动作永远应该是看状态和日志,而不是瞎猜:
sudo systemctl status todo-svc sudo journalctl -u todo-svc -n 50 --no-pager日志会告诉你真正的错误原因。很多人一上来就检查网络、改权限,结果绕了一大圈才发现只是路径或可执行权限问题。
4.3 自动重启与开机自启的实测验证
服务正常跑起来后,我做了两个验证动作。
第一个是测试系统能否自动拉起崩溃进程。我找到 todo-svc 的进程号,直接杀掉它:
ps -ef | grep todo-svc sudo kill -9 <pid> sleep 6 sudo systemctl status todo-svc --no-pager几秒钟后回到状态页,进程又被拉起来了。这说明Restart=on-failure生效,系统会在异常退出后自动恢复服务。
第二个是开启开机自启:
sudo systemctl enable --now todo-svcenable负责把服务链接到多用户启动目标,--now表示立即启动。之后再重启虚拟机,服务就会自动跟着系统起来,不需要人工干预。
这两个验证看似简单,但价值很大。很多人配置完 systemd 后只看到“服务在跑”就觉得大功告成,根本没测过崩溃恢复和开机自启。等你真正需要在凌晨无人值守时自动恢复服务,才发现之前的配置根本没生效,那才是最被动的时刻。
5. 端口明明在监听,外部却连不上:防火墙排查的完整链路
5.1 先从 ss 命令开始,而不是先怀疑防火墙
服务起来之后,我在本机测试访问是正常的:
curl http://127.0.0.1:17280/healthz返回了正常的健康检查结果。但换成从另一台机器访问,请求直接卡住,直到超时。这个现象非常典型:本机能通、外面连不上。第一反应不该是“防火墙”,而应该先确认监听状态。
我执行了:
ss -tlnp | grep 17280输出显示 Local Address 是 0.0.0.0:17280,也就是说服务监听在机器的所有网卡接口上,并不只是本机回环地址。这条信息很重要——如果监听地址是 127.0.0.1,那外部访问不到是理所当然的,因为服务根本没有开放给外部接口。
确认监听正常之后,问题多数就落在防火墙上了。
5.2 防火墙放行:临时规则和永久规则不要搞混
我的环境用的是带 firewalld 的发行版,所以排查命令如下:
sudo firewall-cmd --state sudo firewall-cmd --list-all--list-all会列出当前生效的防火墙规则,包括已放行的端口和服务。正常情况下,17280 不在默认放行列表里,所以外部访问才会被拦截。
放行命令:
sudo firewall-cmd --add-port=17280/tcp这条命令会立即生效,但它的作用只是临时的,重启防火墙或系统后就会消失。要让规则长期保留,还需要执行:
sudo firewall-cmd --runtime-to-permanent把当前运行时规则固化为永久规则。顺序不能反过来——不要先尝试永久添加再期望它立即生效,除非你同时执行了--reload。
然后从另一台机器再次访问:
curl http://<服务器IP>:17280/healthz这次成功了。整个过程看起来就三条命令,但背后的排查顺序才是真正有价值的东西:先确认监听地址,再确认防火墙状态,最后才做放行和验证。
5.3 用 nc 区分问题到底出在哪一段
如果你不用 curl,也可以用端口探测命令来缩小问题范围:
nc -vz 127.0.0.1 17280 nc -vz <服务器IP> 17280第一条命令在本机测试,用来确认服务进程本身在监听;第二条命令在另一台机器上测试,用来确认网络路径是否通畅。如果本机能通但远程不通,问题基本在防火墙或安全组;如果两条都不通,那要回头检查服务是否真的启动、监听地址是不是只绑了回环接口。
我当时就靠这个顺序排掉了误区。很多人一遇到“远程连不上”,第一反应是改服务配置、换端口,结果防火墙压根没放行,白折腾半天。先花一分钟做分层探测,比盲目改配置高效得多。
6. 日志轮转与资源限制:把生产意识提前装进学习环境
6.1 日志持久化:systemd 接管一切的前提
服务日志有两类处理思路:一类是程序自己往文件里写日志,另一类是让程序输出到标准输出/标准错误,由 systemd 的日志服务统一接收。我在这里选择了后者,因为它更符合 systemd 的使用习惯,也方便后续用 journalctl 集中查询。
但这样一来,日志默认只保存在内存里,重启机器就丢了。要让它持久保存,得在日志服务的配置里打开持久化选项。编辑 /etc/systemd/journald.conf:
[Journal] Storage=persistent SystemMaxUse=200M RuntimeMaxUse=100MStorage=persistent表示日志持久存储到磁盘;SystemMaxUse=200M限制了系统日志占用的最大磁盘空间,避免日志无限膨胀撑满硬盘。改完重启日志服务:
sudo systemctl restart systemd-journald之后查看 todo-svc 的日志就很简单:
journalctl -u todo-svc --since today这套配置特别适合“程序自己不太会管理日志文件”的情况。只要程序正常打印输出,日志管理交给系统统一处理,既不会丢了日志,也不会因为日志文件太大而拖垮磁盘。
6.2 logrotate 配置:防止单个日志文件长成怪物
如果程序确实把日志写到了独立文件里,那就要借助 logrotate 定期切割日志文件。我为 /var/log/todo-svc 下的日志文件写了一份轮转配置:
/var/log/todo-svc/*.log { daily rotate 7 compress delaycompress missingok notifempty copytruncate }几个关键策略解释一下:
daily rotate 7:每天切一次,保留最近的 7 份;compress:轮转后压缩旧日志,节省空间;delaycompress:延迟一天压缩,方便当天日志还能被直接查;copytruncate:在日志文件被程序持续写入的情况下,先复制内容再清空原文件,避免因为文件被占用导致切割失败。
写完配置后用以下命令验证语法是否正确:
sudo logrotate -d /etc/logrotate.d/todo-d是调试模式,只打印执行过程,不实际去切割文件。这一步能提前发现配置里的语法问题,避免轮转脚本真的跑起来时才报错。
6.3 资源限制:让服务在异常流量前不至于拖垮整机
最后一项是资源限制。在 systemd 单元文件里可以给服务设置进程能使用的最大资源量,比如文件描述符数量和进程数量:
LimitNOFILE=4096 LimitNPROC=512LimitNOFILE是最大文件描述符数。很多网络服务在处理大量连接时会需要大量文件描述符,设置太小会导致连接被拒绝或报错;设置太大又会浪费系统资源。4096 对于这个内部小工具来说已经够用。
LimitNPROC是用户可创建的最大进程数量,防止程序在异常情况下 fork 出失控的子进程。
配置修改后照例要重载并重启服务:
sudo systemctl daemon-reload sudo systemctl restart todo-svc除此之外,systemd 还提供了一套安全诊断命令。比如:
systemd-analyze security todo-svc它会根据服务单元文件里的安全配置给当前服务打一个暴露级别,并列出哪些安全选项可以改进。我在跑通基本功能后顺手看了一次,发现没设置NoNewPrivileges就对整体分数有明显影响。这类工具适合在配置完服务后作为一次“体检”,花几分钟就能发现不少可以加固的点。
资源限制这件事往往是学习阶段最容易被忽略的。反正自己一台虚拟机,怎么跑都行。可一旦你以后要接手线上服务,没有资源限制的进程就像没系安全带的乘客,平时没问题,真遇到突发流量时可能把整台机器都拖垮。
7. 复盘:这天的 Linux 学习笔记最值得长期保留的三个经验
7.1 最小权限不只是安全原则,它本身就是排障工具
过去我经常把“最小权限”理解成一种抽象的安全口号,直到这次实践才真正意识到它也是一种高效的排障方式。当服务以专用账户运行时,系统会把权限问题暴露得明明白白:哪个目录访问不了、哪个文件没有执行权限,都会直接体现在日志里。反过来,习惯用 root 跑服务的人遇到权限问题时,系统永远是“畅通无阻”的,很多潜在问题反而被掩盖了。
以后不管部署什么服务,我建议你把专用账户和目录权限的配置当成第一步,而不是最后一步。它不会让你的部署变得更快,但会让你在服务出问题时少绕很多弯。
7.2 排查顺序比排查命令本身重要得多
今天排错的大部分时间其实都花在“确认问题出在哪一层”上面。一个服务外部访问不了,背后的可能原因包括:
- 服务进程没启动或已经崩溃;
- 服务监听地址只绑定了回环接口;
- 系统防火墙拦截了端口;
- 网络路径中存在其他安全策略拦截。
我自己总结了一套固定的排查顺序:先看进程和日志,再用 ss 看监听,接着本机 curl 或 nc 验证,最后才测试防火墙和远程访问。这套顺序能把问题快速收敛到一个层面,而不是到处乱试。
7.3 systemd 不只是“开机启动工具”,它是服务生命周期管理员
很多人把 systemd 的 enable 当成“开机启动工具”来用,其实它的价值远不止这些。通过单元文件,你可以统一管理服务的启动顺序、崩溃重启策略、运行用户、资源上限,甚至还能用安全分析命令做加固体检。一旦熟练使用这套机制,你部署一个服务时脑子里就会自动浮现出完整的治理模型,而不是“启动一个进程”这么简单。
最后分享一个非常实用的小操作:每次修改完单元文件,都记得先执行daemon-reload再启动或重启服务。我见过不少人改了文件后直接 restart,结果 systemd 还在用旧配置,折腾半天才发现问题是“没重新加载”。记住这一个细节,能省掉很多不必要的排障时间。