ARTICLE DETAIL

资讯详情

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

Linux综合实战:从部署Nginx到磁盘满故障排查的运维全流程

Linux综合实战:从部署Nginx到磁盘满故障排查的运维全流程 上个月给自己安排了一个Linux综合实验独立交付一台内部服务器要求能提供Web服务、文件共享、定时备份还得能扛住一次让人头疼的故障排查。真做起来才发现平时在终端里东敲一个命令西敲一个命令和完整跑通一个项目之间的差距比想象中大得多。这篇文章把整个实验过程整理出来从选发行版、装系统、初始化到配网络、挂载存储、部署nginx、编写脚本最后还人为制造了一次磁盘满的故障来做排查演练。如果你正在准备运维面试或者一直想从会敲命令走到能管机器这份记录应该能给你不少可以照着抄的东西。先说实验环境。我选的是虚拟机加Rocky Linux原因很简单它是RHEL系和市面上多数生产服务器的操作习惯一致遇到问题时搜到的资料也最多。机器本身配置不高4核8G一块80G系统盘加一块200G数据盘完全够用。综合实验的目的不是跑多复杂的业务而是把零散知识点串成一条完整的链路。1. 实验背景一台什么都要干的小服务器先交代清楚这台机器要承担什么角色后面所有的操作都是围绕角色来的。我给它的定位是部门内部的小型基础设施对内提供静态Web页面方便放文档和通知对外提供文件共享目录每天凌晨自动备份关键数据同时还要承担一个局域网出口的角色让同一网段里的几台办公机器能通过它统一访问外部网络。这个定位决定了技术选型。Web服务用nginx文件共享用NFS挂载一块NAS存储备份用tar加crontab局域网共享上网通过内核转发加iptables来做。你会发现这些都不是什么新技术但组合在一起就构成了一个非常典型的Linux服务器日常运维场景。很多Linux面试题考的无非就是这些东西的变体进程管理、权限控制、网络配置、磁盘管理、脚本编写。综合实验的价值不在于你会背多少命令而在于当这些技术点同时出现在一台机器上时你能不能清楚地知道每一层发生了什么。当时我还在README文件里给自己列了一份验收清单包括新用户能否正常登录、sudo权限是否生效、NAS开机能否自动挂载、nginx是否随系统启动、定时任务是否按计划执行、磁盘满时监控脚本会不会报警。这份清单一开始觉得很基础后来才发现它在故障演练阶段救了我一命。先把整个实验的路径在脑子里过一遍再动手是这类综合项目最值得遵守的规则。2. 装系统时的三个关键决定分区、镜像源、网络初始化2.1 磁盘分区/home独立分区到底要不要装系统时我是吃过亏的。早些年贪图省事一块盘一个根分区全部搞定结果用户数据越攒越多根分区一满整台机器就变得卡顿、服务频繁报错。所以这次分区我做了几件和以前不一样的事。系统盘80G按这个方案切分挂载点大小文件系统用途/boot1Gxfs内核引导文件/60Gxfs系统根目录/home15Gxfs用户家目录swap4Gswap交换空间我之前一直犹豫要不要把/home单独分出去后来想明白了这台机器要建用户、要挂共享目录用户的个人文件如果直接塞在根分区里后续做数据迁移、备份、配额管理都很难受。单独划一个/home将来就算要重装系统用户数据也可以保留不会跟着系统一起被格式化。这是一个很多人都忽略的细节重装系统时只要不动/home分区用户文件就还在。数据盘200G没有在装系统时就格式化我打算装完系统之后再用fdisk分区、mkfs.xfs格式化、mount挂载到/backup。这样做有一个额外的好处完整走一遍后期加盘的流程生产环境里这是常事早晚会遇到。2.2 镜像源替换与软件包源系统装完之后第一件事不是急着配服务而是先把软件源换掉。Rocky Linux默认的官方源在国外实际下载速度经常让人血压升高。我这台实验机在国内网络环境下统一换成清华源过程很标准cd /etc/yum.repos.d/ # 先备份原始文件 mkdir /root/repo-backup cp Rocky-*.repo /root/repo-backup/ # 替换 baseurl注释掉 mirrorlist sed -i -e s|^mirrorlist|#mirrorlist|g \ -e s|^#baseurlhttp://dl.rockylinux.org/$contentdir|baseurlhttps://mirrors.tuna.tsinghua.edu.cn/rocky|g \ Rocky-*.repo dnf clean all dnf makecache这个操作看起来简单但有两个细节值得说。第一一定要先备份repo文件别小看这一步我见过有人把源改坏了又忘了原始地址最后只能手动重建配置文件第二不要直接删掉mirrorlist行用#注释掉就行万一需要切回去还能快速恢复。至于为什么选清华源而不是其他源纯粹是个人习惯它同步快、仓库全、和Rocky的版本对应关系清楚。换完源之后顺手执行dnf update -y把系统补丁打了一遍这是综合实验的起点也是生产环境必须养成的习惯。2.3 首次启动前的网络规划很多新手喜欢把网络配置留到装完系统再折腾结果没有网络dnf都用不了陷入死循环。我现在都是安装到网络与主机名这一步时就把IP、网关、DNS填好相当于进系统之前网络已经是通的。不过这次我吃了Rocky Linux的一个亏。新版Rocky默认的网卡名和连接名不一定相同经常出现改了IP但NetworkManager不认识连接的情况。比如我的网卡叫ens33连接名却叫System ens33用nmcli操作时如果搞错名字配置半天根本不生效。正确姿势是用nmcli先把连接名看清楚nmcli con show # 或者直接查看网卡连接 nmcli device status如果安装时网络没配好进系统之后补救也不难一条命令的事nmcli con mod System ens33 \ ipv4.addresses 192.168.10.10/24 \ ipv4.gateway 192.168.10.1 \ ipv4.dns 223.5.5.5 8.8.8.8 \ ipv4.method manual nmcli con up System ens33刚写到这里我就想把主机名也一起定了。整台机器叫lab-server修改之后我的提示符立刻就能反映出来后续写脚本、看日志也更清晰。主机名和进程名管理在Linux里是两回事但经常混着谈等到后面部署服务时我再细说。3. 系统初始化用户、权限、密码策略和目录规划3.1 新建用户与sudo授权综合实验里最容易被一笔带过但其实至关重要的是用户和权限管理。我给自己建了一个日常账号而不是直接拿root往机器上怼。建用户的流程就三行命令useradd zhangsan -m -s /bin/bash passwd zhangsan usermod -aG wheel zhangsan第三条命令是关键。Rocky Linux的sudo配置中wheel组的成员默认拥有所有命令的sudo权限。我只需要把用户加进组就完成了授权完全不需要直接改/etc/sudoers。如果你非要手动编辑sudoers请务必用visudo因为这个工具会在保存前做语法检查防止你写错一行让整台机器的sudo直接瘫痪。运维里常说的提权落到实操上其实就是sudo和su的正确使用。我的原则是能sudo就不要切rootsudo执行的所有命令都会被记录到/var/log/secure里哪天出了事还能回溯到底是谁在什么时间执行了什么操作。直接用root干活操作记录几乎不可查这是很多线上事故最后说不清责任人的原因。有一点要补充创建用户时如果机器上有/etc/skel目录里的模板文件useradd会自动复制到用户家目录。我习惯在skel里放一个自用的.bashrc片段里面设置了alias和PATH这样后面每建一个新用户都能继承一套自己熟悉的终端环境节省大量沟通成本。3.2 密码过期提醒与登录通知这个选题是来自一个真实经历。之前公司有一台跳板机有位同事的密码到期当天才发现自己登不进去了大半夜打电话让我远程给他重置密码。密码过期本身不是问题问题在于很多人根本不知道自己的密码什么时候到期。用chage命令可以给每个用户设置密码有效期和提前警告天数chage -M 90 -W 7 zhangsan这条命令的含义是密码最长使用90天到期前7天开始提醒。验证是否生效就查一下chage -l zhangsan不过光靠系统自带的警告还不够因为用户登录时那行小字warning: your password will expire in X days很容易被刷过去。我后来写了一个简单的登录提示脚本放到/etc/profile.d/下面用户一登录就计算当前密码剩余天数并打印出来。脚本思路很简单用chage -l输出日期再用date命令算差值循环遍历所有普通用户。这样即使是不太细心的同事也会在登录瞬间看到醒目的还剩12天密码过期。3.3 目录规划data、logs、backup三层结构这台机器的目录结构我是花了心思规划的。Linux本身的目录规范很成熟/usr装程序、/etc放配置、/var存运行时数据但那是给系统用的。针对业务和数据我额外建立了三个目录并写进了实验文档/data放业务数据、共享文件、NAS挂载点/logs放所有应用日志配合logrotate做滚动/backup放tar备份包数据盘单独挂在这里当时有人问我为什么不直接放/var下面反正系统规范也是这样。我说/var和根分区在同一个磁盘上如果日志爆炸式增长最先被拖垮的是根分区的剩余空间可能导致整个系统假死。把/data、/logs、/backup做成独立挂载点日志再大也只是撑爆它自己的分区不会连累系统盘根分区。这是一个非常关键的容错设计后面故障演练章节里这个设计的价值会体现得非常充分。为了配合这个规划我在/etc/fstab里把数据盘挂载到/backup并专门为/logs建了一个逻辑卷。装机时没有给/var单独分区但现在通过LVM的方式做了一次无损扩容也就是把新增的一块磁盘动态加入卷组再给/var增加逻辑卷容量。整套路径走完我对LVM、fstab、mount的理解比单纯看文档深了一个量级。4. 网络与存储挂载从静态IP到NAS存储4.1 静态IP配置IP配置是很多人能够启动网卡但不一定配置正确的分水岭。实验机要求固定IP因为它是文件共享服务器和出口网关如果IP漂移所有依赖它的机器都会断连。我在第2章里已经用nmcli配好了静态地址但静态IP配置完成后还有一个动作要做确认NetworkManager的配置确实写入了连接文件。我见过只执行nmcli命令、没有up连接结果重启后IP还是原来的场景。实际上nmcli con mod只是把配置写入文件真正让配置生效需要nmcli con up或者干脆用nmcli con reload再up。这条教训在故障排查时经常用得上。另外如果服务器上配置了静态IP一定要先确认这个地址没有和局域网里其他机器冲突。最土但有效的办法在配置之前先ping一下自己要用的IP如果通了说明这个IP已经被占用赶紧换一个。这个步骤虽简单却是在真实网络中反复用到的经验。4.2 NAS挂载与开机自动挂载文件共享是这台服务器的核心功能之一。内网里有一台NAS存的是部门公共资料地址192.168.10.20。我在/data下面建了nas目录作为挂载点先用命令行测试挂载一次mount -t nfs 192.168.10.20:/srv/nfs /data/nas这一条命令能通只代表当前会话里挂载成功了。要想重启后还能自动挂载必须写进/etc/fstab192.168.10.20:/srv/nfs /data/nas nfs defaults,_netdev 0 0这个_netdev参数非常关键。它的作用是告诉系统等到网络就绪后再挂载这个文件系统。如果漏了它系统启动时网络尚未初始化就尝试挂载NFS极大概率会超时失败然后启动流程卡在那里严重一点直接进入急救模式。这个问题在CSDN和各类论坛上出现过无数次属于挂了NAS但踩坑的经典案例。挂载CIFS/SMB共享时同理mkdir -p /data/share mount -t cifs //192.168.10.20/public /data/share \ -o usernameshareuser,passwordsharepass,uid1001,gid1001,iocharsetutf8,vers3.0,_netdev写进fstab后别忘了验证一下mount -amount -a会把fstab里所有项目都重新挂载一遍。如果配置有误它会当场报错不会等到重启才暴雷。每次改完fstab我都要执行这个命令相当于给你一次后悔药机会。另一个细节是uid和gid参数CIFS挂载时如果不指定uid挂载上来的文件属主会显示为root普通用户只能看不能改。指定uid1001就映射到zhangsan这个用户权限问题迎刃而解。4.3 局域网出口让办公机器通过实验机统一访问外部网络这是实验里比较有意思的一步把Linux当成一台软路由用。需求很简单同一局域网里几台办公机器的外联访问希望统一走这台实验机的出口便于在出口做访问控制。实现它不复杂两个要点开启内核转发再加一条iptables NAT规则。先开启IP转发echo net.ipv4.ip_forward 1 /etc/sysctl.conf sysctl -p然后配置iptables伪装规则iptables -t nat -A POSTROUTING -o ens33 -j MASQUERADE iptables-save /etc/sysconfig/iptables这条MASQUERADE规则的含义是凡是经过这台机器转发出去的数据包源地址都被替换成这台实验机的IP。办公机器的网关指向192.168.10.10后它们访问外部网络时看起来都是从一个出口出去的。这套NAT机制的学名叫地址伪装是局域网共享上网最经典的做法。我再加一步防止自己忘掉如何恢复iptables的规则是运行时状态重启就没了所以必须iptables-save持久化。同时用systemctl enable iptables确保重启后规则自动加载。生产环境里这里应该再加一层限制比如只允许特定网段的源地址做NAT而不是所有流量都放行但实验机上先跑通基本链路最重要。5. Web服务与脚本自动化nginx定时任务与进程管理5.1 编译安装nginx还是yum安装部署Web服务时我几乎没有犹豫就选择了dnf安装nginx而不是去网上找源码包编译。两者的差别是dnf/yum安装的nginx版本略旧但和系统集成度高systemd服务文件、用户、目录结构全都替你安排好了开机自启只需要systemctl enable --now nginx一条命令源码编译则会新很多模块也可以自己裁剪但升级和维护全靠个人手工对一个综合实验来说性价比不高。安装过程就三条命令dnf install -y nginx systemctl enable --now nginx curl -I http://127.0.0.1配置一个简单的静态站点时我把站点根目录放在了/data/web下而不是默认的/usr/share/nginx/html。这算是一个刻意为之的决定所有业务相关的东西都放/data备份时只需要备份/data这一个目录不用满系统找散落的文件。nginx默认站点配置放在/etc/nginx/conf.d/default.conf我建了一个test.conf指向/data/webserver { listen 80; server_name lab-server; root /data/web; index index.html; }nginx启动后用curl -I验证返回200Web服务这步就算通了。但服务通了不代表万事大吉后面第6章的故障正是从这里爆发的。5.2 脚本自动化日志清理、数据备份、服务检查服务部署好之后重头戏是自动化。实验要求三个场景必须用脚本覆盖日志清理、数据备份、服务存活检查。我统一把脚本放到了/opt/scripts目录作为这台机器的运维工具箱。日志清理脚本的设计思路是分层处理。对老旧的日志文件超过30天直接删除对大而活跃的日志文件用truncate截断而不是rm删除因为正在写入的进程还持有文件句柄你rm了它也不会释放空间。脚本内容大致如下#!/bin/bash # find 删除超过30天的历史日志 find /logs -type f -name *.log -mtime 30 -delete # 对活跃日志做截断置空保留文件让进程继续写 truncate -s 0 /var/log/nginx/access.log 2/dev/null || true备份脚本用tar配合日期命名同时做保留策略只保留最近7天的备份包#!/bin/bash backup_dir/backup data_dir/data mkdir -p $backup_dir tar czf $backup_dir/data_$(date %F).tar.gz -C $data_dir . find $backup_dir -name *.tar.gz -mtime 7 -delete服务检查脚本每5分钟跑一次探测nginx是否还能正常响应如果失败就尝试重启并记录到日志#!/bin/bash if ! curl -sf http://127.0.0.1 /dev/null 21; then systemctl restart nginx echo $(date) nginx was down, restarted /logs/script_monitor.log fi挂进crontab30 2 * * * /opt/scripts/clean_logs.sh /logs/script_cron.log 21 0 1 * * * /opt/scripts/backup_data.sh /logs/script_cron.log 21 */5 * * * * /opt/scripts/check_service.sh /logs/script_cron.log 21写crontab时最容易忽略的是环境变量。cron执行的环境和登录shell不一样PATH可能只包含/sbin和/usr/sbin脚本顶部#!/bin/bash虽然是固定的但脚本里如果调用某个不存在于PATH的命令就会失败。我的习惯是在每个脚本开头显式export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin。这一个小细节能省掉大量手动执行成功、cron执行失败的排查时间。5.3 进程管理修改进程名与进程间通信自动化脚本跑起来后进程数量变多ps输出也开始乱糟糟。尤其是多个功能类似的进程同时运行光靠PID根本分不清谁是谁。这时就体现出修改进程名的重要性。最简单的改法针对脚本进程用exec -a直接给进程起一个可辨识的名字。exec -a backup-worker tar czf /backup/data_$(date %F).tar.gz -C /data .exec会用新进程替换当前shell-a参数修改argv[0]显示名称。执行后ps aux里看到的就是backup-worker而不是一串tar命令。对于Python等服务进程我会利用/proc/self/comm接口在程序内部给自己改名#!/usr/bin/env python3 import time, os def rename_process(name): with open(/proc/self/comm, w) as f: f.write(name[:15]) # 内核限制最长15字节 rename_process(web-checker) while True: print(working...) time.sleep(5)这个技巧花了我不少时间才搞明白因为/proc/self/comm不是普通文件它映射的是内核里的进程名称字段写入长度被限制在15个字符内。运维值班时ps一眼扫过去全是有意义的进程名比什么都强。进程间通信在这台机器上我用了一个命名管道FIFO来做示范。mkfifo建一个管道文件一个进程往里面写事件另一个进程读出来处理。当时我在脚本监控里加了一个事件源nginx每次自动重启监控脚本就往管道里写一条重启记录另开一个服务进程专门读取管道内容并负责通知告警。脚本只有几行但模型很完整生产者写、消费者读、管道作中间缓冲。mkfifo /tmp/event.pipe # 生产者 echo nginx restarted /tmp/event.pipe # 消费者 cat /tmp/event.pipe需要说明的是真实生产系统里进程间通信用得更多的是systemd的socket机制、消息队列、共享内存但理解FIFO的原理有助于理解所有这些更复杂机制的底层模型一端产生数据一端消费数据中间有一个双方都认可的通道并伴随阻塞、缓冲、超时等行为。6. 实战故障排查一次磁盘满导致的服务假死6.1 故障现象与初步排查实验做到这一步一切看起来都正常。此时我决定人为制造故障来找茬——在/logs目录下快速生成一个大文件模拟线上日志爆炸的场景。没过多久系统开始出现诡异现象访问nginx页面偶尔超时curl探测时好时坏nginx error_log里出现No space left on device。排查的第一反应是看系统整体状态。uptime看负载free -h看内存df -h看磁盘。结果一眼锁定问题/logs分区的使用率达到100%根分区还好只有62%。这就是当初把/logs独立挂载带来的好处日志把/logs塞满了系统盘根分区没有跟着遭殃整台机器还能通过SSH连进去做修复而不是彻底卡死。继续往深处挖。df显示的是文件系统层还要找到具体是哪个文件在疯狂占空间。用du从顶层开始逐层排查du -sh /logs/* 2/dev/null结果发现/var/log/nginx/access.log竟然有90多G。这台机器才跑了一天正常情况下access.log不该这么大一定是某个服务在循环写请求日志大概率是之前的探测脚本每5秒访问一次导致日志疯狂增长。真相来得比想象中快但要清理它却引出了更经典的问题。6.2 定位根因文件被删除但空间不释放我先尝试删除这个巨大的日志文件心想rm之后空间总该回来了吧。结果rm之后df再看/logs还是100%一点变化都没有。这个现象几乎每个Linux运维都遇到过原因在于nginx进程仍然持有这个被删除文件的文件句柄只要进程不释放句柄磁盘空间就不会真正归还。用lsof确认lsof | grep deleted输出里赫然躺着几个nginx worker进程句柄指向已经unlink的access.log。这时候只有两条路要么重启nginx让进程重新打开日志文件要么优雅地发信号让nginx重新打开日志也不需要重启服务。nginx提供了USR1信号用于日志重开kill -USR1 $(cat /var/run/nginx.pid)执行完再看df/logs的使用率终于降下来了。这个排查链路虽然只有几步但每一步都踩在经典的Linux文件系统机制上目录项、文件句柄、inode生命周期。文件被rm只是删除目录项只要还有进程持有句柄inode就不会释放磁盘空间也就一直被占用。这个知识点面试经常考但亲手复现一次带来的理解深度完全不同。6.3 修复与预防logrotate与监控告警故障恢复只是第一步怎么防止它再次发生才是重点。我的修复分三层。第一层是日志滚动配置。Linux自带的logrotate完全可以解决日志无限增长的问题。nginx的日志轮转配置文件放在/etc/logrotate.d/nginx我改成了每天滚动、压缩、缺失不报错、截断式复制/var/log/nginx/*.log { daily missingok rotate 30 compress create 0640 nginx adm sharedscripts postrotate if [ -f /var/run/nginx.pid ]; then kill -USR1 $(cat /var/run/nginx.pid) fi endscript }这样日志永远只保留30份压缩文件最老的自动清除从根本上避免日志撑爆磁盘。第二层是监控告警。我在第5章的check_service.sh里加了一段磁盘检查逻辑当任何分区使用率超过80%时把告警写入日志并打印到控制台。更严肃的做法是接邮件或IM告警但实验机上先把检测能力做出来就够了。第三层是目录规划的收益验证。因为/logs是独立挂载点这次日志爆炸只影响/logs分区没有拖垮根分区SSH还能连、诊断命令还能跑。如果在生产环境里不把日志和系统盘分开一个日志文件就能把根分区塞满导致整台机器连登录都做不到那种情况只能强制重启或者进救援模式。这次故障演练让我对目录规划这四个字有了实打实的理解而不再只是文档上的一段规范。由此我还想提一个进阶概念fence机制。单机上日志爆炸顶多自己假死但换到集群环境一台机器假死会造成整个集群状态分裂。这时就需要fence机制来物理隔离故障节点常见手段是断开电源或锁住共享存储保证故障节点不会再产生错误数据。这次的磁盘故障演练虽然离集群还很远但它让我意识到故障处理和故障隔离是两件事真正上生产之前fence、监控、备份这三样一个都不能少。7. 实验验收清单与个人体会实验做完我把自己的验收清单逐项打勾验证项验证方式结果用户与sudo用zhangsan登录执行sudo whoami通过密码策略chage -l zhangsan显示90天有效期通过静态IPip addr显示192.168.10.10重启后未变通过NAS挂载mount -a后df显示NAS正常挂载重启后自动挂载通过局域网出口办公机器网关指向实验机后可正常上网通过nginx站点curl -I返回200通过定时备份crontab执行后/backup出现当日tar包通过日志清理logrotate执行后access.log产生压缩文件通过故障演练人为写满/logs监控脚本报警nginx未连累崩溃通过这份清单本身也是给面试准备的素材。当被问到你做过什么项目时能说清楚一台机器的完整交付、故障处理过程、每一步背后的原因比报一串技术名词有说服力得多。最后说一点个人体会。综合实验最值得花时间的不是那些看起来高级的操作而是反复演练故障排查那条线df、du、lsof、fstab、logrotate这些基础命令和配置文件在真正出事的时候一个都不能少。我建议每个做完实验的人都试着故意把系统弄坏几次比如删掉nginx的pid文件、把fstab里的挂载项写错、用dd塞满磁盘再一步步把它救回来。亲手处理过故障再看到生产环境的报警通知心里才有底。
返回列表