做运维这些年,见过太多人卡在同一个地方:命令背了一堆,真上手却不知道怎么把一台刚装好的机器变成能对外提供服务的生产节点。网上的 Linux 资料不是太碎就是太老,讲命令的只讲命令,讲部署的只贴配置,中间那层"为什么这么配、出错怎么查"往往被跳过。这篇文章想把这条链路补齐:从系统安装后的初始化、常用命令体系的组织方式,到 Shell 脚本、服务托管、定时任务、批量自动化,最后落到排错清单和工程规范。内容面向刚接触 Linux 的同学,也适合写过一些脚本但没系统梳理过的后端开发者,跟着敲一遍,能自己搭出一套可复现的最小运维体系。
1. Linux运维的知识地图与核心概念
1.1 运维工作的真实边界
很多人对 Linux 运维的理解停留在"敲命令",实际上命令只是最表层的交互手段。运维的本质是维持一组可预期的状态:系统资源处于合理水位、服务进程持续可用、配置变更可追溯、故障发生时能快速定位。
把这个目标拆开,会得到四个相对独立又互相支撑的能力块。第一块是系统层,包括文件系统、用户权限、进程管理、网络配置、磁盘与内存;第二块是运行层,包括服务如何启动、如何托管、如何随系统自启、日志写到哪里;第三块是自动化层,包括脚本、定时任务、配置管理、批量执行;第四块是观测层,包括日志采集、指标监控、告警规则。缺少任何一块,日常都会很难受:只会命令不会托管,重启机器服务就挂;只会托管不会观测,出问题只能靠猜。
所以学习路径不应该是"把命令手册从头看到尾",而应该是"围绕一个真实目标,把四块能力逐层补齐"。本文后面所有的示例都会围绕同一个目标推进:在一台干净的 Linux 机器上,把一个应用稳定地跑起来,并且能被监控、能自动备份、能批量复制到多台机器。
1.2 从零基础到实战的四个阶段
第一个阶段是"能连上、能操作"。这个阶段的标志是你可以不看教程完成:登录服务器、在目录间跳转、查看和编辑文件、启动和停止一个进程、看端口有没有被占用。这一层靠的是命令熟练度,没有捷径,但也不需要背全,掌握高频的三十来个就够日常使用。
第二个阶段是"能装、能配"。你能独立安装一个中间件,知道它的配置文件在哪、日志在哪、数据目录在哪、用哪个系统用户运行。这一步的核心不是记住安装命令,而是建立"一个服务的标准目录约定"这个心智模型。后面无论装什么软件,你都会先问同样几个问题:装在哪、谁运行、配置在哪、日志在哪、怎么重启。
第三个阶段是"能写、能自动化"。重复三次以上的操作就该写成脚本,重复发生在多台机器上的操作就该用配置管理工具。这一层是从"手工运维"跨到"工程运维"的分水岭。
第四个阶段是"能定位、能兜底"。故障发生时,你能在十分钟内判断问题在系统层、网络层、应用层还是依赖层,并且知道哪些操作有风险、需要先备份。这一层靠的是排查方法论和一次次真实故障的积累。
1.3 几个容易混淆的概念
新手最容易混的是这几个词:进程、服务、守护进程。进程是操作系统调度的基本单位,任何一个正在运行的程序都是一个或多个进程;服务是一个逻辑概念,指对外提供某项能力的程序集合;守护进程是脱离终端在后台长期运行的进程。三者的关系是:服务通常由一个或多个守护进程实现,而守护进程本质上是进程。
另一个高频混淆点是"重载"和"重启"。重载(reload)通常指进程不退出、重新读取配置文件,连接一般不会断;重启(restart)是先停后起,期间服务不可用。很多人改完配置直接重启,对无状态服务影响不大,但对承载长连接的网关类服务可能就是一次抖动。
还有一个是软链接和硬链接。软链接是一个指向路径的特殊文件,原文件删除后软链接失效;硬链接是指向同一 inode 的另一个目录项,删除其中一个不影响另一个。部署时常用软链接做版本切换,这个后面会具体讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与版本说明
2.1 实验环境的选择
本文示例以主流的企业级 Linux 发行版为背景,命令与配置在常见的 RHEL 系和 Debian 系发行版上都能找到对应写法,差异处会单独标注。具体的内核版本、发行版小版本号请以你实际使用的环境为准,不同版本在包管理命令、服务管理细节上可能存在差异,遇到不一致时优先查对应版本的官方文档。
硬件方面,一台 2 核 4G 的虚拟机足够跑通本文全部示例,磁盘建议不少于 20G,因为要安装软件包和保留日志。如果资源紧张,1 核 2G 也能完成大部分练习,只是编译类操作会慢一些。
网络环境需要能访问软件源。如果处于内网环境,需要提前配置好本地源或内部镜像源,这部分属于环境差异,本文不展开具体地址配置。
2.2 系统安装的几点建议
安装时建议选择最小化安装,不要勾选图形界面。原因很实际:图形界面会额外拉起一批服务、占用内存、增加攻击面,而且生产服务器几乎不会用到。最小化安装之后按需补装软件包,依赖关系更清晰,出问题时排查范围也更小。
安装过程中有几项值得注意。主机名建议按"角色-机房-序号"的规则命名,比如 web-bj-01,而不是默认的 localhost,后面做批量管理时一眼就能看出机器用途。时区统一设置为业务所在时区,避免日志时间对不上导致排查困难。磁盘分区方面,如果条件允许,把 /var 和 /home 单独分区,这样日志写满时不会把根分区一起拖垮。swap 的大小按内存规模调整,内存充足的环境可以设小一些,但不建议完全关闭,某些应用在内存紧张时仍依赖 swap 兜底。
2.3 初始化配置与目录约定
系统装完后的第一件事是做初始化,把后面反复用到的习惯一次性定下来。下面这段命令可以写成脚本保存,新机器到手直接执行,注意根据自己的环境调整参数:
bash复制#!/bin/bash
# 文件路径:/root/init.sh
# 用途:新机器基础初始化,执行前请确认参数与网络环境
set -euo pipefail
# 1. 设置时区与时间同步
timedatectl set-timezone Asia/Shanghai
timedatectl set-ntp true
# 2. 关闭不必要的服务(按实际环境取舍)
systemctl disable --now firewalld 2>/dev/null || true
# 3. 调整文件句柄数
cat >> /etc/security/limits.conf <<'EOF'
* soft nofile 65535
* hard nofile 65535
EOF
# 4. 内核参数:允许本地端口复用、加快回收
cat >> /etc/sysctl.conf <<'EOF'
net.ipv4.tcp_tw_reuse = 1
net.core.somaxconn = 65535
vm.swappiness = 10
EOF
sysctl -p
# 5. 创建统一的运维目录结构
mkdir -p /opt/apps /opt/scripts /data/logs /data/backup
chmod 755 /opt/apps /opt/scripts
echo "init done"
这里解释几个关键点。set -euo pipefail 是脚本的安全带:-e 让命令失败时立即退出,-u 让引用未定义变量时报错,pipefail 让管道中任一环节失败都能被捕获。limits.conf 调整的是用户级文件句柄上限,很多"too many open files"报错根因就在这里。sysctl 中的 somaxconn 决定了监听队列长度,高并发场景下默认值偏小。
目录约定需要提前统一:/opt/apps 放应用安装目录,/opt/scripts 放运维脚本,/data/logs 放应用日志,/data/backup 放备份文件。把数据盘和系统盘分开,好处是数据增长不会撑爆根分区,迁移时也只需挂载数据盘。这套约定不需要多复杂,但一定要固定下来,否则半年后接手的人会非常痛苦。
3. 命令体系:文件、权限、进程、网络
3.1 文件与目录操作的组织方式
文件操作命令很多,但日常高频的就那些。与其零散地记,不如按"查看、查找、修改、统计"四类组织。
查看类最常用的是 ls、cat、less、tail。日志排查场景下 tail -f 配合 grep 是标配,less 在打开大文件时比 cat 友好得多,支持翻页和搜索。查找类以 find 和 grep 为核心,find 按文件属性找,grep 按内容找,两个组合起来几乎能覆盖所有定位需求:
bash复制# 查找 /data/logs 下 7 天前、大于 100M 的日志文件
find /data/logs -type f -name "*.log" -mtime +7 -size +100M
# 递归搜索包含关键字 error 的行,并显示行号
grep -rn "OutOfMemoryError" /data/logs --include="*.log"
# 统计某个接口的访问次数
grep "POST /api/order" access.log | wc -l
修改类里,sed 和 awk 值得单独花时间。sed 擅长流式替换,比如批量改配置;awk 擅长按列处理文本,比如从日志里提取状态码并做统计:
bash复制# 提取访问日志中的状态码并统计分布
awk '{print $9}' access.log | sort | uniq -c | sort -rn | head -10
这条命令的链路是:取第 9 列(状态码),排序让相同值相邻,uniq -c 计数,再按次数倒序。类似的组合在日志分析中会反复出现,建议亲手敲几遍形成肌肉记忆。
3.2 权限与属主
Linux 的权限模型是"属主、属组、其他人"三段,每段三位 rwx。数字表示法里 r=4、w=2、x=1,所以 755 就是属主可读写执行、属组和其他人可读可执行。
实际工作中容易踩坑的是目录权限。目录的 x 位表示"能否进入",r 表示"能否列出内容",w 表示"能否在目录内创建删除文件"。如果目录没有 x,即使有 r 也无法访问其中的文件。很多"文件明明有权限却读不到"的问题,根因是父目录缺少执行位。
另一个高频问题是属主设置错误。应用以低权限用户运行时,如果日志目录属主是 root,写入就会失败。规范做法是:应用目录和数据目录统一归属应用用户,root 只负责安装和初始化。修改属主用 chown,同时修改属组用 chown user:group path,递归修改加 -R,但递归操作前务必确认路径,这条命令在生产环境用错代价很高。
特殊权限位也值得一提。setuid 让程序以文件属主身份运行,setgid 用在目录上可以让新建文件继承目录属组,sticky bit 用在共享目录上防止用户删除他人文件。/tmp 就是典型的 sticky 目录。
3.3 进程与端口排查
服务不正常时,第一反应应该是"进程还在吗、端口在听吗"。这两个问题分别对应 ps 和 ss。
bash复制# 查看指定应用的进程
ps -ef | grep java | grep -v grep
# 查看进程的详细信息,包括启动命令、工作目录
ps -p 12345 -o pid,ppid,user,%cpu,%mem,etime,cmd
# 查看端口监听情况
ss -lntp
# 查看某个端口被哪个进程占用
ss -lntp | grep :8080
ss 比老的 netstat 更快,现在主流发行版都默认提供。-l 只看监听状态,-n 不做域名解析,-t 看 TCP,-p 显示进程,四个参数组合起来就是最实用的排查命令。
如果进程莫名消失,重点查两处:一是系统日志 /var/log/messages 或 journalctl -xe,看有没有被 OOM Killer 杀掉;二是应用自身日志,看有没有主动退出。OOM 的典型特征是日志里出现 Out of memory: Killed process,这种情况要从内存配置和堆参数入手,而不是简单加大内存。
bash复制# 查看最近被 OOM 杀掉的进程
dmesg | grep -i "killed process"
# 实时观察内存与进程占用
free -h
top -b -n 1 | head -20
3.4 网络与磁盘
网络排查有个固定顺序,从左到右逐层确认:本机网卡是否正常、网关是否可达、DNS 是否解析正确、目标端口是否连通。对应命令分别是 ip addr、ping、dig 或 nslookup、telnet 或 curl。
bash复制# 查看网卡与 IP
ip addr show
# 查看路由表,确认默认网关
ip route
# 测试端口连通性,注意不是所有环境都装了 telnet
timeout 3 bash -c "cat < /dev/tcp/10.0.0.1/3306" && echo "port open"
# 查看连接状态分布
ss -ant | awk '{print $1}' | sort | uniq -c
磁盘方面,df -h 看分区使用率,du -sh * 逐层定位大目录,lsof | grep deleted 查已删除但仍被进程占用的文件。最后这个非常关键:日志文件被 rm 之后空间没释放,就是因为进程还持有文件句柄,必须重启对应进程或清空文件内容才会释放。
bash复制# 定位根分区下占用最大的前 10 个目录
du -h --max-depth=1 / 2>/dev/null | sort -rh | head -10
# 查找已删除但仍被占用的文件
lsof +L1 2>/dev/null | head -20
4. Shell脚本:从能跑到能维护
4.1 脚本骨架与执行方式
Shell 脚本的门槛很低,但写好并不容易。一个可维护的脚本应该有固定骨架:说明用途的注释、严格的错误处理、清晰的变量命名、统一的日志输出。
bash复制#!/bin/bash
# 文件路径:/opt/scripts/backup.sh
# 用途:按日期打包指定目录并清理过期备份
# 用法:./backup.sh
set -euo pipefail
readonly SCRIPT_NAME=$(basename "$0")
readonly BACKUP_SRC="/opt/apps"
readonly BACKUP_DST="/data/backup"
readonly KEEP_DAYS=7
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$SCRIPT_NAME] $*"
}
main() {
local date_str
date_str=$(date '+%Y%m%d%H%M%S')
mkdir -p "$BACKUP_DST"
local target="${BACKUP_DST}/apps_${date_str}.tar.gz"
log "start backup: $BACKUP_SRC -> $target"
tar -czf "$target" -C "$(dirname "$BACKUP_SRC")" "$(basename "$BACKUP_SRC")"
log "backup finished, size: $(du -h "$target" | cut -f1)"
log "clean backups older than ${KEEP_DAYS} days"
find "$BACKUP_DST" -name "apps_*.tar.gz" -mtime +"$KEEP_DAYS" -print -delete
}
main "$@"
这个骨架里值得说的点不少。readonly 声明常量,避免脚本中途被意外覆盖;local 声明函数内变量,防止污染全局;main "$@" 把入口收拢到一个函数,后续加子命令很方便。执行方式上,bash script.sh 和 ./script.sh 的区别在于前者不要求执行权限,后者需要 chmod +x,并且依赖 shebang 指定的解释器。
4.2 变量、判断与循环
Shell 的坑集中在变量和判断上。变量引用要加双引号,这是最容易被忽略的一条:如果变量包含空格且没加引号,会被拆成多个参数,rm -rf $DIR/ 这类命令在这种情况下的后果不用多说。推荐的写法是 "${VAR}",花括号明确边界,引号防止拆分。
条件判断用 [[ ]] 而不是 [ ],前者是 bash 内建,支持正则匹配和更安全的字符串比较,不会因为变量为空而报语法错误。
bash复制if [[ -z "${APP_HOME:-}" ]]; then
echo "APP_HOME is empty" >&2
exit 1
fi
if [[ -f "$CONF_FILE" && -r "$CONF_FILE" ]]; then
echo "config readable"
fi
# 数值比较
if (( $(echo "$load > 10" | bc -l) )); then
echo "load too high"
fi
循环方面,for 处理列表,while read 处理文件内容。读文件时千万别用 for line in $(cat file),它会按空格拆分,遇到含空格的行就出错。正确写法是:
bash复制while IFS= read -r line; do
[[ -z "$line" ]] && continue
echo "processing: $line"
done < servers.txt
IFS= 保留行首尾空白,-r 防止反斜杠被转义,这两点不加就会出现莫名其妙的解析问题。
4.3 错误处理与调试
set -e 能在命令失败时退出,但它有盲区:在 if 条件里、在 && 或 || 组合中、在管道非末位时都不会触发。所以关键操作用显式判断更稳妥:
bash复制if ! systemctl restart myapp; then
log "restart failed, check journalctl -u myapp -n 50"
exit 1
fi
调试脚本时,bash -x script.sh 会打印每条执行命令,适合定位逻辑错误;set -x 可以在脚本内部局部开启,配合 set +x 关闭,把调试范围控制在可疑段落。另外建议在脚本退出时打印退出码,配合 trap 做清理:
bash复制cleanup() {
local code=$?
log "script exit with code $code"
rm -f /tmp/work.$$
}
trap cleanup EXIT
trap ... EXIT 确保无论正常结束还是异常退出都会执行清理,避免临时文件堆积。
5. 服务部署实战:应用 + systemd + 反向代理
5.1 部署前的规划
部署之前先回答五个问题:应用装在哪、用什么用户运行、配置放在哪、日志写到哪、怎么启动和停止。这五个问题答不清楚,后面的运维会很被动。
推荐的约定是:应用装在 /opt/apps/<应用名>/,下分 bin、conf、lib、logs 四个子目录;运行用户单独创建,不给登录 shell,不给 sudo 权限;配置放在 conf 下,敏感信息通过环境变量或独立配置文件注入,不写死在代码里;日志输出到 /data/logs/<应用名>/,按天切割;启停统一交给 systemd 托管,不手工 nohup。
5.2 创建用户与目录
bash复制# 创建不可登录的系统用户
useradd -r -s /sbin/nologin -M appuser
# 创建目录结构
mkdir -p /opt/apps/demo/{bin,conf,lib,logs}
mkdir -p /data/logs/demo
# 归属应用用户
chown -R appuser:appuser /opt/apps/demo /data/logs/demo
# 权限收紧,配置目录只允许属主读写
chmod 750 /opt/apps/demo/conf
chmod 640 /opt/apps/demo/conf/*.conf 2>/dev/null || true
用 -r 创建系统用户,uid 落在系统保留区间;-s /sbin/nologin 禁止登录,减少被利用的可能;-M 不创建家目录,因为这个用户只用来跑服务。
5.3 编写 systemd 单元
nohup ... & 的方式缺点明显:机器重启后不会自启、进程挂了不会拉起、日志没有统一管理。systemd 是现在的事实标准,写一个 unit 文件即可解决这些问题。
ini复制# 文件路径:/etc/systemd/system/demo.service
[Unit]
Description=Demo Application Service
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/apps/demo
Environment="JAVA_HOME=/usr/lib/jvm/default"
EnvironmentFile=-/opt/apps/demo/conf/env
ExecStart=/opt/apps/demo/bin/start.sh
ExecStop=/opt/apps/demo/bin/stop.sh
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
StandardOutput=journal
StandardError=journal
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target
逐项说明。After=network.target 表示在网络就绪后启动,如果应用强依赖外部服务,可以再加 network-online.target。Type=simple 适用于前台运行不 fork 的进程,如果程序会自己 daemon 化,应该改成 Type=forking。EnvironmentFile=- 前面的减号表示文件不存在也不报错,适合放可选的环境变量。Restart=on-failure 让异常退出时自动拉起,RestartSec 控制重试间隔,避免疯狂重启。LimitNOFILE 覆盖了 systemd 下的句柄限制,注意 limits.conf 对 systemd 托管的服务不一定生效,必须在这里显式声明。TimeoutStopSec 决定优雅停机的等待时间,设太短会导致进程被强杀,可能丢数据。
写好之后执行:
bash复制systemctl daemon-reload
systemctl enable demo
systemctl start demo
systemctl status demo --no-pager
daemon-reload 在每次修改 unit 文件后都要执行,漏掉这一步会出现"配置改了但没生效"的困惑。
5.4 反向代理与验证
应用用非特权端口(如 8080)运行,前面挂一层 Nginx 做反向代理,好处是:外部只暴露 80/443,应用端口不直接对外;可以在 Nginx 层做限流、压缩、静态资源缓存;证书统一在这一层管理。
nginx复制# 文件路径:/etc/nginx/conf.d/demo.conf
upstream demo_backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name demo.example.com;
access_log /data/logs/nginx/demo_access.log;
error_log /data/logs/nginx/demo_error.log warn;
client_max_body_size 20m;
location / {
proxy_pass http://demo_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
}
}
几个关键配置的意义:keepalive 复用后端连接,减少握手开销;proxy_set_header 把真实客户端 IP 传给后端,否则应用日志里全是代理的地址;proxy_read_timeout 要大于后端最长处理时间,否则长请求会被提前断开。
配置改完先做语法检查再重载,这个习惯能避免把线上打挂:
bash复制nginx -t
systemctl reload nginx
nginx -t 会校验配置语法和文件路径,通过之后再 reload,重载不会中断已有连接。验证阶段按层次来:先 curl 127.0.0.1:8080 确认应用本身通,再 curl -H "Host: demo.example.com" 127.0.0.1 确认代理通,最后从外部访问确认链路完整。
6. 自动化运维:定时任务、批量执行与观测
6.1 cron 与 systemd timer
定时任务首选 cron,语法简单,适合绝大多数场景。但 cron 有几个经典陷阱:环境变量不继承登录 shell、工作目录不是脚本所在目录、输出会尝试发邮件。
bash复制# 编辑当前用户的任务
crontab -e
# 每天凌晨 2 点执行备份
0 2 * * * /opt/scripts/backup.sh >> /data/logs/backup.log 2>&1
# 每 5 分钟检查一次服务状态,异常则拉起
*/5 * * * * /opt/scripts/health_check.sh >> /data/logs/health.log 2>&1
第一条路径必须写绝对路径,因为 cron 的 PATH 很短。重定向 >> log 2>&1 把标准输出和错误都写进日志,避免产生大量邮件。如果脚本依赖特定环境变量,在脚本开头显式 source /etc/profile 或者在 crontab 里声明。
systemd timer 是 cron 的现代替代,优势是支持依赖关系、失败重试、日志统一进 journal、可以精确到秒。对于重要的定时任务,推荐用 timer:
ini复制# 文件路径:/etc/systemd/system/backup.timer
[Unit]
Description=Daily backup timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
Persistent=true 很关键:如果机器在计划时间处于关机状态,开机后会补跑一次,避免漏执行。
6.2 用 Ansible 做批量管理
单机脚本解决不了多机一致性问题。当机器数量超过三五台,手工同步配置就开始出错了。Ansible 的价值在于无 agent、基于 SSH、用 YAML 描述状态,学习成本相对低。
yaml复制# 文件路径:/opt/ansible/deploy_demo.yml
- name: deploy demo app
hosts: webservers
become: yes
vars:
app_name: demo
app_user: appuser
app_home: "/opt/apps/{{ app_name }}"
tasks:
- name: ensure app user exists
user:
name: "{{ app_user }}"
shell: /sbin/nologin
system: yes
create_home: no
- name: ensure directories exist
file:
path: "{{ item }}"
state: directory
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0755"
loop:
- "{{ app_home }}"
- "{{ app_home }}/bin"
- "{{ app_home }}/conf"
- "/data/logs/{{ app_name }}"
- name: upload config
copy:
src: files/demo.conf
dest: "{{ app_home }}/conf/demo.conf"
owner: "{{ app_user }}"
mode: "0640"
notify: restart demo
- name: ensure service is running
systemd:
name: "{{ app_name }}"
state: started
enabled: yes
handlers:
- name: restart demo
systemd:
name: "{{ app_name }}"
state: restarted
这份 playbook 体现了配置管理的核心思想:描述"应该是什么状态",而不是"执行什么命令"。file 模块是幂等的,目录存在就跳过;copy 在文件内容变化时才触发 notify,进而调用 handler 重启服务;systemd 模块保证服务处于运行且开机自启。三者组合起来,重复执行不会产生副作用,这是脚本很难做到的。
执行命令:
bash复制ansible-playbook -i inventory.ini deploy_demo.yml --check
ansible-playbook -i inventory.ini deploy_demo.yml
先 --check 做空跑,看会变更哪些内容,确认无误再正式执行。这个习惯在生产环境能省掉很多事故。
6.3 日志与基础监控
可观测性从日志开始。最低要求是:日志有统一目录、有轮转策略、有基本的采集方式。轮转用 logrotate 配置:
bash复制# 文件路径:/etc/logrotate.d/demo
/data/logs/demo/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
copytruncate
}
daily rotate 14 表示按天切、保留 14 份;compress 压缩历史文件节省空间;copytruncate 复制后清空原文件,适用于不支持重新打开日志文件的应用,代价是切换瞬间可能丢少量日志。如果应用支持信号触发重开,用 create 配合 postrotate 更严谨。
监控方面,起步阶段先把几个核心指标盯住:CPU 负载、内存使用率、磁盘使用率、网络流量、服务端口存活、接口响应时间。用最简单的脚本做存活探测,比什么都不做要强得多:
bash复制#!/bin/bash
# 文件路径:/opt/scripts/health_check.sh
set -uo pipefail
PORT=8080
THRESHOLD=90
if ! ss -lnt | grep -q ":${PORT} "; then
echo "$(date '+%F %T') port ${PORT} not listening, restarting"
systemctl restart demo
exit 0
fi
usage=$(df -h /data | awk 'NR==2 {gsub("%","",$5); print $5}')
if [[ "$usage" -ge "$THRESHOLD" ]]; then
echo "$(date '+%F %T') disk usage ${usage}% exceeds threshold"
fi
这段脚本刻意保持简单,只做端口探测和磁盘水位检查。真实环境里更推荐用成熟的监控系统采集指标并配置告警,脚本只作为兜底手段。
7. 常见问题与排查思路
运维过程中遇到的问题大多有迹可循。下面这张表整理了高频场景,遇到时可以作为第一轮筛查依据。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 服务启动失败,无明确报错 | 端口被占用、配置语法错、文件权限不足 | ss -lntp 查端口,journalctl -u 服务名 -n 100 看日志,检查配置文件属主与权限 |
| 修改配置后不生效 | 未重载、缓存、加载了错误配置文件 | 确认配置路径,执行 nginx -t 或对应语法检查,再 reload 并观察日志 |
| 磁盘空间满但找不到大文件 | 已删除文件仍被进程占用 | lsof +L1 定位占用进程,重启该进程或清空文件句柄 |
| 进程莫名消失 | 被 OOM Killer 杀掉、脚本异常退出 | dmesg | grep -i killed,检查内存与堆参数,确认退出码 |
| 外部访问不通但本机可访问 | 防火墙、安全组、监听地址绑定 127.0.0.1 | 检查监听地址是否为 0.0.0.0,核对防火墙与安全组规则 |
| 定时任务不执行 | PATH 不全、脚本无执行权限、cron 服务未启动 | 用绝对路径,加执行权限,检查 systemctl status crond |
| 中文乱码 | 系统 locale 未设置 | 检查 locale,在 /etc/locale.conf 中设置 LANG |
| 时间不一致导致日志错乱 | 时区错误、NTP 未同步 | timedatectl 检查时区与同步状态 |
| SSH 连接慢 | DNS 反解超时 | 关闭 sshd 的 UseDNS,检查网络链路 |
| 文件句柄耗尽 | limits 配置未生效 | 检查 ulimit -n,systemd 服务需在 unit 中设置 LimitNOFILE |
排错这件事最忌讳的是"猜"。建议按固定的顺序走一遍:现象是什么 → 最近的变更是什么 → 日志怎么说 → 系统资源是否正常 → 网络链路是否通 → 依赖服务是否可用。多数问题在"最近变更"这一步就能定位,因为绝大多数线上故障都是由变更引起的。
另外两个习惯值得养成。一是任何修改之前先备份,配置文件复制一份加时间戳后缀,改坏了能立刻回滚;二是排查时记录每一步操作和结果,避免手忙脚乱时重复试错,也方便事后复盘。
8. 最佳实践与工程建议
8.1 账号与权限管理
生产环境的第一原则是禁止直接使用 root 长时间操作。正确做法是每人一个独立账号,通过 sudo 授权具体命令,/etc/sudoers 用 visudo 编辑,避免语法错误导致无法提权。需要批量登录时用密钥认证,关闭密码登录,同时限制可登录来源。
权限方面遵循最小可用原则:应用用户只给业务目录的权限,不给 sudo,不给登录 shell;配置文件权限收到 640 或 600;脚本目录不要让所有人可写。这些限制平时感觉不到价值,出问题时就是最后一道防线。
8.2 变更管理与备份
每一次生产变更都应该满足三个条件:有记录、可回滚、在低峰期。变更前把要改的文件备份到 /data/backup/,命名带时间戳;变更后立即验证核心功能;如果涉及数据库或数据结构,必须提前确认备份可用,而不只是"备份过了"。
备份的关键不是备份动作本身,而是恢复验证。定期做一次恢复演练,把备份文件真正还原到测试环境跑一次,才能确认备份是有效的。很多事故不是没有备份,而是备份文件损坏或恢复步骤没人会做。
8.3 日志规范
日志应该满足:有统一格式、有时间戳、有请求标识、有级别区分、有轮转策略。应用日志不要输出到控制台就完事,要落盘并纳入轮转;错误日志和访问日志分开;敏感信息(密码、token、身份证号)在打印前脱敏。
排查效率很大程度上取决于日志质量。建议在请求入口生成一个 traceId,全链路透传,出问题时一个 id 就能串起整个调用过程。这个改造成本不高,收益很大。
8.4 性能与容量
系统层面的调优先看瓶颈在哪,不要盲目调参数。CPU 高看是计算密集还是上下文切换;内存高看是缓存还是泄漏;磁盘 IO 高看是随机写还是顺序写;网络看带宽还是连接数。定位清楚再动手。
容量规划要有提前量。磁盘使用率到 80% 就该处理,不要等 100%;连接数、句柄数、进程数这些指标要设置告警阈值。所有阈值都应该基于历史数据设定,而不是凭感觉。
8.5 配置与文档
配置文件要版本化。可以用 Git 管理配置目录,每次变更提交记录,出问题时可以 diff 对比。敏感信息不入库,用环境变量或独立的密钥文件注入。
文档不需要写得很正式,但一定要有:机器清单、服务清单、端口清单、部署步骤、回滚步骤、常见故障处理。这些内容平时看着冗余,值班时能救命。
9. 学习路线与后续方向
把本文的内容串起来看,其实是一条完整的链路:先用初始化脚本把机器基线统一,再用命令体系完成日常操作和问题定位,接着用 Shell 脚本把重复动作固化,用 systemd 把服务托管规范,用 cron 和 Ansible 把单机和多机的自动化补齐,最后用日志与监控形成闭环。
如果要把这套能力再往上推一层,建议按这个顺序深入。先补网络基础,理解 TCP 握手、TIME_WAIT、连接复用这些概念,很多"服务时好时坏"的问题根因在网络层。再学容器与编排,把应用的运行环境标准化,这是当前部署的主流方式。然后是监控体系,从简单的存活探测过渡到指标采集、告警分级、根因分析。最后是稳定性工程,包括容量评估、故障演练、应急预案。
学习过程中有个建议:不要只看教程,一定要有一台自己的机器。所有命令亲手敲一遍,所有配置亲手改一遍,最好故意制造几次故障再去修,比如把磁盘写满、把进程 kill 掉、把配置改错。修过一次的东西,比看十遍教程记得牢。
另外一个容易被忽略的点是记录。每次排查完一个不熟悉的问题,花五分钟写下来:现象、排查过程、根因、解决方案。三个月后你会发现这份笔记比任何教程都适合自己,因为它记录的是你真实踩过的坑。这份积累,才是从"会用 Linux"走到"能扛住生产"的关键。
