做运维和开发的这些年,见过太多人在 Linux 面前碰壁:面试被问到「怎么排查端口占用」答不上来,服务器磁盘写满了不知道从哪下手,写了个 Shell 脚本上线跑了一周才发现日志没清理把机器撑爆了。这些问题其实都不复杂,难的是缺少一条从「能敲命令」到「能扛项目」的完整路径。本文围绕 Linux 零基础入门到进阶这条主线,把系统知识、常用软件环境部署、Shell 脚本、云平台实践和大数据集群项目串成一条可执行的路线,每一块都配可复制的命令和配置。不管你是刚接触 Linux 的学生,还是需要独立维护服务器的一线开发者,都能照着跑一遍。
1. 先想清楚:Linux 到底要学哪些东西
1.1 Linux 是什么,和 Windows 有什么本质区别
很多人对 Linux 的第一印象是「黑框框」「没有桌面」「命令记不住」。这些描述其实说的是它的使用方式,不是它的本质。
严格一点说,Linux 指的是 Linux 内核,它负责管理 CPU 调度、内存分配、磁盘 IO、网络收发这些底层资源。我们平时说的「装了个 Linux」,装的其实是「内核 + 一堆用户态工具 + 包管理器」组合起来的发行版,比如 Ubuntu、Rocky Linux、Debian。
和 Windows 相比,最关键的差别有三个:
第一,一切皆文件。磁盘、网卡、进程状态、硬件设备,在 Linux 里都能通过文件路径访问。比如 /proc/cpuinfo 看 CPU 信息,/dev/sda 代表第一块磁盘。这种统一抽象让脚本化操作成为可能。
第二,配置即文本。Windows 的配置大量存在注册表里,Linux 的配置几乎全是纯文本文件,放在 /etc 目录下。这意味着你可以用版本管理、可以用脚本批量改、可以 diff 出差异,这背后就是自动化运维的基础。
第三,权限模型更细。Linux 用「用户 + 用户组 + 其他人」三层,配合读(4)、写(2)、执行(1)三种权限位,能精确控制谁能动哪个文件。这点在做多用户服务器和数据库目录隔离时非常重要。
理解了这三点,后面学命令就不会觉得是在背字典,而是能明白每条命令在操作什么对象。
1.2 零基础到精通的能力分层
我一般把 Linux 能力拆成四层,学习时按层推进,效率最高:
| 层级 | 能力描述 | 典型场景 |
|---|---|---|
| 第一层:会用 | 文件增删改查、查看日志、切换目录、编辑文本 | 登服务器看日志、改配置 |
| 第二层:会管 | 用户权限、进程服务、磁盘网络排查 | 服务起不来、端口被占用 |
| 第三层:会写 | Shell 脚本、定时任务、批量处理 | 日志清理、自动备份、批量部署 |
| 第四层:会搭 | 中间件部署、容器化、集群搭建 | 部署 MySQL/Nginx、搭大数据集群 |
绝大多数「学不会 Linux」的情况,是卡在第一层就往第四层跳。上来就照着教程搭 Hadoop,结果 java: command not found 都排不出来,自然挫败感极强。
正确的做法是:第一层和第二层必须练熟,第三层写出能用的脚本,第四层再按项目需求逐个攻坚。
1.3 新手最容易踩的三个认知误区
第一个误区是「命令要全背下来」。完全没必要。真正高频的命令不超过 60 个,其余遇到再查 man 或 --help 就行。重要的是记住「有哪些类别的工具」,比如查文件用 find,查内容用 grep,查进程用 ps/top,查端口用 ss/lsof。
第二个误区是「必须装桌面版」。恰恰相反,服务器场景下图形界面是负担,会占用大量内存。建议一开始就用纯命令行环境练习,强迫自己熟悉终端操作。
第三个误区是「出错了就重装系统」。重装确实能解决问题,但什么也没学到。每次报错都应该先看清楚错误信息,再去查日志。这个习惯的价值远大于多背几十条命令。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 学习环境准备与版本选择
2.1 三种练习环境怎么选
练 Linux 一共三种方式,各有适用场景:
方式一:本机虚拟机。用 VMware Workstation 或 VirtualBox 装一台虚拟机,优点是免费、可随时打快照、断网也能练。缺点是占本机内存,通常给 2~4GB 比较合理。这是新手首选。
方式二:云服务器。买一台低配云主机,优点是天然贴近生产环境,能练安全组、公网 IP、远程登录这些真实技能。缺点是要花钱,而且误操作可能产生费用。建议在自己已经把基础命令练熟之后再上。
方式三:WSL2 或容器。Windows 上装 WSL2,或者本机跑个 Docker 容器,启动快、隔离好。适合快速验证某个命令或写个脚本,但不适合模拟「多台机器组集群」这种场景。
实践中比较稳妥的组合是:虚拟机练基础命令和系统管理,云主机练部署和大数据集群。
2.2 发行版怎么选,别踩版本坑
这是很多人忽略但影响很大的问题。选发行版时要注意一件事:CentOS 7 已经在 2024 年 6 月底结束维护,CentOS 8 更早就停了,官方重心转向了 CentOS Stream。如果你在网上找教程时看到大量 CentOS 7 的内容,需要留意软件源可能已经不可用。
比较推荐的替代方案:
- 想延续 CentOS 的使用习惯:选 Rocky Linux 或 AlmaLinux,都是 RHEL 兼容的下游发行版,命令和包管理与 CentOS 基本一致。
- 想要资料多、社区活跃:选 Ubuntu Server LTS 版本,长期支持周期长,文档丰富。
- 企业真实环境:通常是 RHEL 或国产化衍生版本。
本文示例以「RHEL 系命令 + systemd」为主线,Ubuntu 上差异主要在包管理器(yum/dnf 换成 apt)和部分路径,遇到时我会单独标注。
版本这块需要说明:文中的具体版本号只是示例,你的实际环境可能不同,安装时以官方仓库当前提供的版本为准,重点理解配置思路而不是死记版本数字。
2.3 装完之后先做这几件事
系统装好第一件事不是急着敲命令,而是做几项基础检查,确认环境和网络没问题。
bash复制# 查看系统版本
cat /etc/os-release
# 查看内核版本
uname -r
# 查看 CPU 核心数和内存
nproc
free -h
# 查看磁盘使用情况
df -h
# 查看本机 IP
ip addr
# 测试外网连通性
ping -c 3 www.baidu.com
这几条命令的输出要能看懂。df -h 的 Use% 列在生产环境排查磁盘告警时用得最多;ip addr 输出里的 ens33、eth0 这类名字是网卡名,不同系统命名规则不同,不用纠结。
如果 ping 不通,先检查虚拟机的网络模式(NAT 还是桥接),再检查 DNS 配置:
bash复制cat /etc/resolv.conf
国内环境常常需要换成可用的 DNS,比如 223.5.5.5。这一步搞不定,后面装软件全是坑。
2.4 一定要学会用快照
虚拟机装好、能联网、装完常用工具之后,立刻打一个快照,命名为「base-clean」。
原因很实际:后面练权限、练分区、练集群,必然会把系统搞坏。有快照的话,三十秒回滚,继续练。没快照就要重装,一晚上没了。
云主机对应的是「自定义镜像」功能,思路一样,把干净环境保存成镜像,后续开新机器直接用。
3. 核心系统知识拆解
3.1 目录结构:每个目录是干什么的
Linux 的目录结构是 FHS 标准规定的,不是随便建的。记清楚几个关键目录,后面看日志、找配置会顺很多:
| 目录 | 用途 |
|---|---|
/etc |
所有系统和服务配置文件,改配置基本都在这 |
/var/log |
日志目录,排查问题的第一现场 |
/var/lib |
服务运行产生的数据,比如 MySQL 数据目录 |
/usr/bin、/usr/local/bin |
可执行程序目录,自己装的东西常放后者 |
/opt |
第三方大型软件的安装位置,习惯上放这里 |
/home |
普通用户的家目录 |
/root |
root 用户的家目录 |
/tmp |
临时目录,可能会被系统清理,不要存重要文件 |
/proc、/sys |
内核和进程的虚拟文件系统,只存在于内存中 |
最容易混淆的是 /bin 和 /usr/bin。现在很多发行版已经把 /bin 做成指向 /usr/bin 的软链接,实际是同一个地方。同理 /sbin 指向 /usr/sbin。
另一个高频问题:自己做的东西该放哪?我的习惯是,自编译安装的软件放 /usr/local/,大型中间件(比如大数据组件、数据库)放 /opt/,业务代码放 /data/。这个约定成俗的规则能避免后面找不到东西。
3.2 权限模型:为什么总是 Permission denied
Linux 权限是新手最容易卡住的地方。先看一个真实输出:
bash复制$ ls -l /etc/passwd
-rw-r--r--. 1 root root 2841 Mar 10 09:12 /etc/passwd
从左往右拆解:
- 第 1 位
-表示普通文件,d表示目录,l表示软链接。 - 接下来 9 位分三组,分别是属主(owner)、属组(group)、其他人(others)的权限。
rw-表示属主可读可写不可执行,r--表示属组只读,r--表示其他人只读。- 后面的
root root是属主和属组。 .表示启用了 SELinux 安全上下文。
权限位对应的数字:读=4,写=2,执行=1。所以 rwxr-xr-x 就是 755,rw-r--r-- 就是 644。
常用的修改命令:
bash复制# 改属主和属组
chown appuser:appgroup /data/app
# 递归改目录及其下所有内容
chown -R appuser:appgroup /data/app
# 改权限:目录 755,文件 644 是最常见组合
chmod 755 /data/app/bin/start.sh
chmod 644 /data/app/conf/application.yml
# 用符号方式加执行权限,更直观
chmod +x /data/app/bin/start.sh
这里有个容易忽略的细节:目录的「执行权限」x 实际含义是「能否进入该目录」,不是「能否运行」。如果一个目录没有 x 权限,即使有 r 权限也 cd 不进去,看不了里面的文件内容。这是很多「明明 chmod 777 了还是打不开」问题的根源,通常是因为上级目录缺权限。
还有一个高频坑:用 sudo 或 su 切换到普通用户后,发现写不了某个文件,本质是文件属主还是 root,需要用 chown 改属主,而不是简单粗暴 chmod 777。生产环境上 777 是明确的安全隐患,等于给所有人写权限。
3.3 进程与服务管理:systemd 是核心
现在的发行版几乎都用 systemd 管理服务。核心命令就一个 systemctl,掌握这组操作基本够用:
bash复制# 启动 / 停止 / 重启 / 查看状态
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl status nginx
# 设置开机自启 / 关闭开机自启
systemctl enable nginx
systemctl disable nginx
# 查看是否开机自启
systemctl is-enabled nginx
# 重新加载配置(不重启进程)
systemctl reload nginx
# 列出所有运行中的服务
systemctl list-units --type=service --state=running
restart 和 reload 的区别要搞清楚:restart 是停掉进程再启动,会中断正在处理的请求;reload 是给进程发信号让它重读配置,通常不中断服务。Nginx、MySQL 这类服务改配置后优先用 reload。
排查进程时常用的还有:
bash复制# 查看所有进程,按 CPU 排序
ps aux --sort=-%cpu | head -10
# 实时查看资源占用
top
# 或者用更友好的 htop(需要先安装)
# 按名字找进程
ps -ef | grep java
# 查某个端口被哪个进程占用
ss -lntp | grep 8080
# 或者
lsof -i:8080
ss 现在基本取代了老的 netstat,很多新系统默认没装 netstat,找不到命令时不用慌,用 ss 即可。
3.4 网络与端口:服务启动了却访问不了
这是日常最高频的问题之一。服务明明 systemctl status 显示 running,但浏览器访问就是连不上。排查顺序建议固定成下面四步:
第一步,确认服务在监听什么地址和端口:
bash复制ss -lntp | grep java
重点看 Local Address 列。如果显示 127.0.0.1:8080,说明只监听了本机回环地址,外部访问不了,需要改配置监听 0.0.0.0。如果显示 0.0.0.0:8080 或 *:8080,说明监听正常,往下走。
第二步,确认本机防火墙是否放行:
bash复制# 查看防火墙状态
systemctl status firewalld
# 查看已放行的端口
firewall-cmd --list-ports
# 开放端口(永久生效,需要 reload)
firewall-cmd --add-port=8080/tcp --permanent
firewall-cmd --reload
Ubuntu 上通常用 ufw:
bash复制ufw status
ufw allow 8080/tcp
第三步,如果是在云主机上,检查安全组规则。安全组是云平台层面的防火墙,很多「本机能访问、外网访问不了」的问题都是安全组没放行。这一步在本地虚拟机上不存在,容易形成思维盲区。
第四步,从客户端侧验证连通性:
bash复制telnet 服务器IP 8080
# 或者用 nc
nc -zv 服务器IP 8080
连不上会明确报 connection refused 或 timeout,两者的区别能进一步定位问题:refused 通常是服务没起或没监听该端口,timeout 通常是中间被防火墙拦了。
4. 高频命令与文本处理技巧
4.1 文件操作:先记住这十几个
文件操作命令是地基,必须练到不用想就能敲出来:
bash复制# 查看当前目录
pwd
# 列出文件,-l 详细,-a 含隐藏文件,-h 人类可读
ls -lah
# 切换目录
cd /var/log
cd ~ # 回家目录
cd - # 回上一个目录
# 创建目录,-p 递归创建
mkdir -p /data/app/logs
# 创建空文件 / 更新时间戳
touch app.log
# 复制,-r 递归,-p 保留权限和时间
cp -rp /data/app /backup/
# 移动 / 重命名
mv old.txt new.txt
mv /tmp/app.tar.gz /opt/
# 删除,-r 递归,-f 强制
rm -rf /tmp/test
rm -rf 这条命令必须特别强调:它不会进回收站,删了就没了。生产环境操作前务必先 ls 确认路径,再执行删除。有个实用习惯是把 rm -rf 写成脚本包装,把文件先移到回收目录再定时清理,能救命。
查看文件内容方面:
bash复制cat file.txt # 全部输出,适合小文件
less file.txt # 分页查看,适合大文件,q 退出
head -n 20 file.txt # 前 20 行
tail -n 20 file.txt # 后 20 行
tail -f app.log # 实时跟踪,排查日志必用
tail -f 是看日志的神器,但文件被切割(logrotate)之后会失效,这时候要用 tail -F,它会自动重新打开文件。
4.2 查找与过滤:grep 和 find 的组合拳
grep 查内容,find 查文件,这两个工具用熟了,效率提升非常明显。
bash复制# 在文件中找关键字,-n 显示行号,-i 忽略大小写
grep -n "ERROR" app.log
# 递归搜索整个目录
grep -rn "datasource" /data/app/conf/
# 反向匹配:排除包含 DEBUG 的行
grep -v "DEBUG" app.log
# 统计出现次数
grep -c "ERROR" app.log
# 同时匹配多个关键字
grep -E "ERROR|FATAL" app.log
# 只输出匹配部分,适合提取
grep -o "user_id=[0-9]*" app.log | head
find 的用法:
bash复制# 按名字查找
find /var/log -name "*.log"
# 按修改时间查找:7 天前的文件
find /data/app/logs -name "*.log" -mtime +7
# 按大小查找:大于 100M 的文件
find / -type f -size +100M 2>/dev/null
# 组合:找出 7 天前的日志并删除
find /data/app/logs -name "*.log" -mtime +7 -delete
最后这条命令杀伤力很大,执行前务必把 -delete 换成 -print 先看一眼会删哪些文件,确认无误再改成真删。这是我在生产环境养成的固定习惯,花几秒钟换一次安心。
4.3 重定向与管道:Linux 组合能力的来源
管道 | 把上一条命令的输出作为下一条命令的输入,这是 Linux 最优雅的设计之一。
bash复制# 统计日志中 ERROR 出现次数
grep "ERROR" app.log | wc -l
# 找出占用 CPU 最高的 3 个进程
ps aux --sort=-%cpu | head -4
# 统计访问日志中 top10 IP
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# 提取指定时间段的日志
grep "2024-03-10 1[0-2]:" app.log
重定向这边,四个符号要分清:
bash复制# > 覆盖输出
echo "hello" > test.txt
# >> 追加输出
echo "world" >> test.txt
# 2> 只重定向错误输出
command 2> error.log
# &> 标准输出和错误一起重定向(bash 支持)
command &> all.log
# 丢弃输出,不想看又不想要文件
command > /dev/null 2>&1
> /dev/null 2>&1 这个写法在写定时任务时极其常见,作用是彻底静默。但要注意,静默意味着出错了你也看不到,所以生产环境的定时任务更推荐把输出重定向到带日期的日志文件,而不是直接丢掉。
4.4 打包压缩与文件传输
bash复制# 打包并 gzip 压缩
tar -czvf app-20240310.tar.gz /data/app
# 解压
tar -xzvf app-20240310.tar.gz -C /opt/
# 只查看包内文件列表,不解压
tar -tzvf app-20240310.tar.gz
# zip 格式(跨平台用得多)
zip -r app.zip /data/app
unzip app.zip -d /opt/
参数记忆方法:c 创建,x 解压,t 查看,z 走 gzip,v 显示过程,f 指定文件名。组合起来基本覆盖所有日常需求。
传输文件在两台机器之间:
bash复制# 从本地传到远程
scp app.tar.gz user@192.168.1.10:/opt/
# 递归传目录
scp -r /data/app user@192.168.1.10:/opt/
# 从远程拉回来
scp user@192.168.1.10:/var/log/app.log ./
# 大文件或断点续传更推荐 rsync
rsync -avz --progress /data/app/ user@192.168.1.10:/opt/app/
rsync 相比 scp 的优势是增量传输和断点续传,几百 M 以上的目录传输建议直接用 rsync。
5. Shell 脚本:从能跑到能维护
5.1 一个规范脚本的骨架
很多人写 Shell 脚本就是直接堆命令,能跑就行。但一旦交给别人维护或者半年后自己回来看,就是灾难。一个可维护的脚本至少要有这几部分:
bash复制#!/bin/bash
# ============================================
# 脚本名称:clean_logs.sh
# 功能说明:清理指定目录下 N 天前的日志文件
# 使用方法:./clean_logs.sh /data/app/logs 7
# 作者:yourname
# 日期:2024-03-10
# ============================================
# set -e:任何命令失败立即退出
# set -u:使用未定义变量时报错
# set -o pipefail:管道中任一环节失败即视为失败
set -euo pipefail
# 脚本所在目录,避免因执行路径不同导致相对路径失效
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
set -euo pipefail 这三个选项强烈建议每个脚本都加上。不加的话,脚本中间某步失败了会继续往下跑,最后可能把正常数据也删了,问题非常隐蔽。
SCRIPT_DIR 这一行解决的是一个经典坑:脚本里写 ./conf/app.conf,在脚本所在目录执行没问题,用绝对路径从别处调用就找不到文件。用 $SCRIPT_DIR 拼绝对路径能彻底规避。
5.2 变量、参数与输入校验
bash复制# 变量赋值,等号两边不能有空格
APP_NAME="demo"
LOG_DIR="/data/${APP_NAME}/logs"
# 参数:$1 第一个参数,$# 参数个数,$@ 所有参数
TARGET_DIR="${1:-}"
KEEP_DAYS="${2:-7}"
# 参数校验
if [ -z "$TARGET_DIR" ]; then
log "错误:必须指定目标目录"
echo "用法:$0 <目录> [保留天数]"
exit 1
fi
if [ ! -d "$TARGET_DIR" ]; then
log "错误:目录不存在 $TARGET_DIR"
exit 1
fi
if ! [[ "$KEEP_DAYS" =~ ^[0-9]+$ ]]; then
log "错误:保留天数必须是数字"
exit 1
fi
${1:-} 这种写法表示「取第一个参数,没有就取默认空值」,配合 set -u 使用可以避免变量未定义报错。${2:-7} 则是「没传就用 7 作为默认值」。
参数校验这块看似啰嗦,但它的价值在于把错误挡在执行之前。删日志脚本如果因为参数没传导致 TARGET_DIR 为空,rm -rf "" 这类操作很可能造成事故。防御性编程在 Shell 里比在任何语言里都重要。
5.3 条件判断与循环
Shell 的条件判断有几个容易混的点:
bash复制# 数值比较:用 -eq -ne -gt -lt -ge -le
if [ "$count" -gt 100 ]; then
log "数量超限"
fi
# 字符串比较:用 = != -z -n
if [ "$status" = "running" ]; then
log "服务运行中"
fi
# 文件判断:-f 普通文件,-d 目录,-e 存在,-s 非空
if [ -f "/data/app/app.pid" ]; then
log "pid 文件存在"
fi
注意 [ ] 里面变量一定要加双引号。如果变量为空,[ $name = "x" ] 会变成 [ = "x" ],直接语法错误。加引号后变成 [ "" = "x" ],才能正常判断。
循环主要有两种:
bash复制# for 遍历列表
for host in 192.168.1.11 192.168.1.12 192.168.1.13; do
log "检查节点 $host"
ping -c 1 -W 1 "$host" > /dev/null 2>&1 && log "$host 可达" || log "$host 不可达"
done
# for 遍历文件
for file in /data/app/logs/*.log; do
# 通配符没匹配到时会保留字面量,需要判断
[ -e "$file" ] || continue
log "处理 $file"
done
# while 读取文件内容
while IFS= read -r line; do
echo "行内容:$line"
done < /data/app/hosts.txt
while read 里的 IFS= 和 -r 组合要记住:IFS= 防止首尾空格被去掉,-r 防止反斜杠被转义。少了这两个参数,处理带空格或特殊字符的文本就会出现莫名其妙的问题。
5.4 完整实战:日志清理脚本
把上面这些串起来,写一个真正能上生产的日志清理脚本:
bash复制#!/bin/bash
# 文件路径:/data/scripts/clean_logs.sh
# 功能:清理指定目录下 N 天前的日志,保留最近 N 天
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
TARGET_DIR="${1:-}"
KEEP_DAYS="${2:-7}"
DRY_RUN="${3:-false}"
if [ -z "$TARGET_DIR" ] || [ ! -d "$TARGET_DIR" ]; then
log "错误:目标目录无效 -> ${TARGET_DIR}"
exit 1
fi
if ! [[ "$KEEP_DAYS" =~ ^[0-9]+$ ]]; then
log "错误:保留天数必须为正整数 -> ${KEEP_DAYS}"
exit 1
fi
log "开始清理:目录=${TARGET_DIR} 保留天数=${KEEP_DAYS} dry_run=${DRY_RUN}"
total_size=0
count=0
while IFS= read -r -d '' file; do
size=$(stat -c %s "$file" 2>/dev/null || echo 0)
total_size=$((total_size + size))
count=$((count + 1))
if [ "$DRY_RUN" = "true" ]; then
log "[DRY-RUN] 将删除 $file"
else
rm -f "$file"
log "已删除 $file"
fi
done < <(find "$TARGET_DIR" -type f -name "*.log" -mtime +"$KEEP_DAYS" -print0)
total_mb=$((total_size / 1024 / 1024))
log "清理完成:文件数=${count} 释放空间≈${total_mb}MB"
这个脚本有几个设计点值得说明:
第一,支持 dry_run 模式。第三个参数传 true 时只打印不删除,上线前先试跑一遍,这是操作生产环境的基本素养。
第二,用 find ... -print0 配合 read -d '',能正确处理文件名里带空格的情况。直接用 for file in $(find ...) 遇到带空格的文件名就会断开,是非常经典的坑。
第三,用 while read 而不是命令替换,避免一次性把大量文件名读进内存。
使用方法:
bash复制# 先赋执行权限
chmod +x /data/scripts/clean_logs.sh
# 预演:看看会删哪些
/data/scripts/clean_logs.sh /data/app/logs 7 true
# 确认后真正执行
/data/scripts/clean_logs.sh /data/app/logs 7 false
5.5 定时任务:crontab 的正确姿势
脚本写完要用 crontab 定时跑:
bash复制# 编辑当前用户的定时任务
crontab -e
# 查看当前用户的定时任务
crontab -l
# 删除所有定时任务(慎用)
crontab -r
crontab 的格式是五个时间字段加命令:
code复制* * * * * command
│ │ │ │ │
│ │ │ │ └── 星期几 (0-7,0和7都表示周日)
│ │ │ └──── 月份 (1-12)
│ │ └────── 日 (1-31)
│ └──────── 小时 (0-23)
└────────── 分钟 (0-59)
实际写的时候:
bash复制# 每天凌晨 2 点清理 7 天前的日志
0 2 * * * /data/scripts/clean_logs.sh /data/app/logs 7 false >> /data/scripts/logs/clean.log 2>&1
# 每 5 分钟检查一次服务状态
*/5 * * * * /data/scripts/check_service.sh >> /data/scripts/logs/check.log 2>&1
# 每周一早上 6 点做一次备份
0 6 * * 1 /data/scripts/backup.sh >> /data/scripts/logs/backup.log 2>&1
三个高频坑必须提前知道:
第一,crontab 的环境变量非常精简,PATH 里通常没有 /usr/local/bin。如果脚本里调用了自定义路径的命令,一定在脚本开头显式设置 PATH,或者全部用绝对路径。
第二,crontab 默认不会加载 /etc/profile 和 ~/.bashrc,你在 shell 里 java -version 正常,crontab 里就找不到 java,原因就在这里。解决方法是在脚本里 source /etc/profile,或者直接写 JAVA_HOME 的绝对路径。
第三,命令必须重定向输出。crontab 执行出错时默认会发邮件,但大多数环境没配邮件服务,结果就是静默失败,你完全不知道。>> xxx.log 2>&1 是必须的。
6. 常用软件环境部署实战
6.1 JDK 安装:大数据和 Java 服务的地基
部署环境几乎都从 JDK 开始。推荐用压缩包方式安装,比包管理器装更可控,也不会被系统更新换掉。
bash复制# 创建统一目录
mkdir -p /opt/module
# 下载后解压(版本以实际下载为准)
tar -xzf jdk-17_linux-x64_bin.tar.gz -C /opt/module/
# 重命名,方便后续引用
cd /opt/module
mv jdk-17* jdk
配置环境变量:
bash复制# 文件路径:/etc/profile.d/my_env.sh
# 用 profile.d 的好处是每台新机器只要拷这个文件就行
cat > /etc/profile.d/my_env.sh <<'EOF'
export JAVA_HOME=/opt/module/jdk
export PATH=$JAVA_HOME/bin:$PATH
EOF
# 让配置生效
source /etc/profile
# 验证
java -version
echo $JAVA_HOME
验证输出要同时看到版本信息和正确的 JAVA_HOME 路径。如果 java -version 报 command not found,先 echo $PATH 看有没有包含 $JAVA_HOME/bin,再确认文件是否真的被 source 了。
在 Ubuntu 上,/etc/profile.d/ 机制同样有效,但如果你用的是 zsh,需要额外在 ~/.zshrc 里 source 一下。
6.2 MySQL 部署要点
以二进制包方式部署 MySQL 8 为例,关键步骤如下(具体版本以官方下载为准):
bash复制# 解压
tar -xzf mysql-8.0.xx-linux-glibc2.17-x86_64.tar.xz -C /opt/module/
cd /opt/module
mv mysql-8.0* mysql
# 创建 mysql 用户和组
groupadd mysql
useradd -r -g mysql -s /bin/false mysql
# 创建数据目录
mkdir -p /data/mysql/data
chown -R mysql:mysql /data/mysql
# 初始化数据目录
cd /opt/module/mysql
bin/mysqld --initialize --user=mysql --basedir=/opt/module/mysql --datadir=/data/mysql/data
初始化完成后,控制台会打印一次性的 root 初始密码,务必立刻复制保存。如果没记下来,只能清空数据目录重新初始化。
配置文件 /etc/my.cnf 至少要写这些:
ini复制[mysqld]
basedir=/opt/module/mysql
datadir=/data/mysql/data
socket=/tmp/mysql.sock
port=3306
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
max_connections=500
log-error=/data/mysql/error.log
pid-file=/data/mysql/mysql.pid
[client]
socket=/tmp/mysql.sock
default-character-set=utf8mb4
character-set-server=utf8mb4 这一行必须有。默认字符集如果不设成 utf8mb4,存 emoji 或某些中文特殊字符会报错,后期改非常麻烦。
启动并改密码:
bash复制# 启动
bin/mysqld_safe --user=mysql &
# 登录(输入初始化时记录的临时密码)
bin/mysql -u root -p
# 改密码
ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourStrongPassword123!';
FLUSH PRIVILEGES;
安全提示:生产环境不要用弱密码,也不要让 root 允许远程登录。正确做法是创建独立的业务账号,只授予必要权限:
sql复制CREATE USER 'app_user'@'192.168.1.%' IDENTIFIED BY 'StrongPass!2024';
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app_user'@'192.168.1.%';
FLUSH PRIVILEGES;
只给 DML 权限,不给 DROP、GRANT 这类高危权限,符合最小权限原则。数据库操作涉及删除和更新时,一定要先在测试库验证,并对生产库做备份。
6.3 Nginx 安装与反向代理配置
bash复制# 安装依赖
dnf install -y gcc gcc-c++ make pcre-devel zlib-devel openssl-devel
# 解压编译安装
tar -xzf nginx-1.24.0.tar.gz -C /opt/module/
cd /opt/module/nginx-1.24.0
./configure --prefix=/opt/module/nginx --with-http_ssl_module --with-http_gzip_static_module
make && make install
核心配置示例:
nginx复制# 文件路径:/opt/module/nginx/conf/nginx.conf
worker_processes auto;
events {
worker_connections 4096;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
client_max_body_size 50m;
gzip on;
gzip_min_length 1k;
gzip_types text/plain text/css application/json application/javascript;
upstream app_backend {
server 192.168.1.11:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.12:8080 weight=1 max_fails=2 fail_timeout=30s;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backend;
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_connect_timeout 5s;
proxy_read_timeout 60s;
}
}
}
几个参数的作用值得说明:
proxy_set_header X-Forwarded-For 让后端能拿到真实客户端 IP,不配的话后端日志里全是 Nginx 的 IP,排查问题会很难受。
max_fails=2 fail_timeout=30s 表示 30 秒内失败 2 次就把该节点标记为不可用,这是最基础的故障转移机制。
client_max_body_size 50m 如果不设,默认只有 1M,用户传个大一点的图片就报 413,这个坑踩过的人不少。
改完配置先检查语法再 reload:
bash复制/opt/module/nginx/sbin/nginx -t
/opt/module/nginx/sbin/nginx -s reload
nginx -t 一定要养成习惯,配置写错了 reload 会失败但旧进程还在跑,看起来「没事」,实际上新配置没生效。
6.4 Docker:环境一致性的解法
Docker 解决的是「我这能跑,你那跑不了」的问题。安装用官方仓库脚本最省事,具体命令以官方文档为准。装完验证:
bash复制docker version
docker info
常用操作:
bash复制# 拉取并运行一个 MySQL 容器
docker run -d \
--name mysql-test \
-p 3307:3306 \
-e MYSQL_ROOT_PASSWORD=RootPass123! \
-v /data/docker/mysql:/var/lib/mysql \
mysql:8.0
# 查看运行中的容器
docker ps
# 查看日志
docker logs -f mysql-test
# 进入容器
docker exec -it mysql-test bash
# 停止并删除
docker stop mysql-test
docker rm mysql-test
-v /data/docker/mysql:/var/lib/mysql 这个挂载是关键。不挂载的话,容器删了数据就没了。挂载到宿主机的数据目录,容器可以随便重建,数据安全。
7. 云平台实践与大数据集群项目
7.1 云主机准备与网络规划
在云平台上跑集群,第一步是规划机器。以三台机器搭建最小可用集群为例:
| 主机名 | IP | 角色 | 配置建议 |
|---|---|---|---|
| node01 | 192.168.1.11 | NameNode / ResourceManager | 4C8G |
| node02 | 192.168.1.12 | DataNode / NodeManager | 2C4G |
| node03 | 192.168.1.13 | DataNode / NodeManager | 2C4G |
创建时要注意三点:
一是三台机器尽量放同一个可用区、同一个交换机下,走内网通信,既快又省流量费。
二是安全组只需要放行内网互通和必要的 SSH 端口,不要把 8088、9870 这类管理端口直接暴露到公网。如果确实需要从本地访问,用跳板机或者做 IP 白名单限制。
三是统一配置主机名和 hosts。三台机器都要改:
bash复制# 每台机器执行,主机名各不相同
hostnamectl set-hostname node01
# 文件路径:/etc/hosts,三台机器内容一致
cat >> /etc/hosts <<'EOF'
192.168.1.11 node01
192.168.1.12 node02
192.168.1.13 node03
EOF
hosts 不统一是集群搭建最常见的失败原因之一,很多组件默认用主机名通信,解析不了就连不上。
7.2 SSH 免密登录配置
集群管理必须配免密,否则每次启动都要敲密码。
bash复制# 在 node01 上生成密钥对,一路回车
ssh-keygen -t rsa -b 4096
# 分发公钥到三台机器(包括自己)
ssh-copy-id node01
ssh-copy-id node02
ssh-copy-id node03
# 验证
ssh node02 hostname
如果 ssh-copy-id 不可用(部分精简系统没装),可以手工拷:
bash复制# 在 node01 上执行
for host in node01 node02 node03; do
ssh $host "mkdir -p ~/.ssh && chmod 700 ~/.ssh"
cat ~/.ssh/id_rsa.pub | ssh $host "cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
done
验证时如果能直接返回主机名而不提示输密码,就成功了。如果还是提示密码,检查 ~/.ssh 目录权限是不是 700、authorized_keys 是不是 600,权限不对 SSH 会拒绝使用密钥。
7.3 Hadoop 集群配置
Hadoop 的核心配置文件有四个,都在 $HADOOP_HOME/etc/hadoop/ 下。
core-site.xml:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://node01:8020</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/data/hadoop/tmp</value>
</property>
<property>
<name>hadoop.http.staticuser.user</name>
<value>root</value>
</property>
</configuration>
hadoop.tmp.dir 必须改成非默认路径。默认路径在 /tmp 下,机器重启可能被清空,导致集群元数据丢失、namenode 起不来。
hdfs-site.xml:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>/data/hadoop/name</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>/data/hadoop/data</value>
</property>
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>node02:9868</value>
</property>
</configuration>
dfs.replication 是副本数,三节点集群设为 3 比较合理,设成大于节点数虽然不报错但会有副本一直处于待复制状态。
mapred-site.xml:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>yarn.app.mapreduce.am.env</name>
<value>HADOOP_MAPRED_HOME=/opt/module/hadoop</value>
</property>
<property>
<name>mapreduce.map.env</name>
<value>HADOOP_MAPRED_HOME=/opt/module/hadoop</value>
</property>
<property>
<name>mapreduce.reduce.env</name>
<value>HADOOP_MAPRED_HOME=/opt/module/hadoop</value>
</property>
</configuration>
后面这三个环境变量配置经常被忽略,不配的话 MapReduce 任务提交到 YARN 后会报 Could not find or load main class,因为容器里找不到 Hadoop 的类路径。
yarn-site.xml:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>yarn.resourcemanager.hostname</name>
<value>node01</value>
</property>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.nodemanager.resource.memory-mb</name>
<value>4096</value>
</property>
<property>
<name>yarn.nodemanager.resource.cpu-vcores</name>
<value>4</value>
</property>
</configuration>
yarn.nodemanager.resource.memory-mb 要按机器实际内存设,设得太大会导致容器启动失败,设得太小又跑不了任务。经验值是机器内存的 70% 左右。
最后配置 workers 文件,指定哪些节点是 DataNode:
bash复制# 文件路径:/opt/module/hadoop/etc/hadoop/workers
node01
node02
node03
配置完成后,用脚本把整个 hadoop 目录分发到另外两台:
bash复制for host in node02 node03; do
rsync -av /opt/module/hadoop/ $host:/opt/module/hadoop/
done
7.4 格式化与启动集群
第一次启动前必须格式化 NameNode,而且只能执行一次:
bash复制# 格式化(只执行一次!)
hdfs namenode -format
这一步必须强调:格式化会清空 NameNode 的元数据目录,如果集群里已经有数据,执行后数据就找不回来了。第二次以后启动千万不要再格式化。很多人「集群起不来」的第一反应就是重新格式化,这是极具破坏性的操作。
启动集群:
bash复制# 启动 HDFS
start-dfs.sh
# 启动 YARN
start-yarn.sh
# 或者一起启动
start-all.sh
# 查看各节点进程
jps
jps 是验证集群状态最快的工具。正常情况下的进程分布:
- node01:
NameNode、ResourceManager、SecondaryNameNode(如果配置在这台) - node02、node03:
DataNode、NodeManager
如果某个节点少了进程,去对应机器的日志目录 $HADOOP_HOME/logs/ 下看对应的 .log 文件,错误原因基本都能找到。
验证 HDFS 是否可用:
bash复制# 查看根目录
hdfs dfs -ls /
# 创建目录
hdfs dfs -mkdir -p /data/test
# 上传文件
hdfs dfs -put /etc/hosts /data/test/
# 查看文件内容
hdfs dfs -cat /data/test/hosts
# 查看集群报告
hdfs dfsadmin -report
hdfs dfsadmin -report 会输出每个 DataNode 的容量、剩余空间、最后心跳时间。如果某个节点 Last contact 时间很大,说明节点掉线了。
Web UI 也能访问(注意安全组要放行对应端口):
- HDFS NameNode:
http://node01:9870 - YARN ResourceManager:
http://node01:8088
7.5 集群的规范关闭与资源回收
用完一定要按顺序关,避免元数据损坏:
bash复制# 先关 YARN
stop-yarn.sh
# 再关 HDFS
stop-dfs.sh
在云平台上,机器不用时记得停机或释放,否则会持续计费。释放前先确认数据是否需要备份,云盘通常可以保留单独计费。
8. 常见问题与排查思路
8.1 高频问题速查表
| 问题现象 | 常见原因 | 排查与解决 |
|---|---|---|
command not found |
PATH 未包含该命令目录;包未安装 | echo $PATH 检查;which 命令 确认;补环境变量或装包 |
Permission denied |
文件权限或上级目录缺 x 权限 | ls -l 看权限,namei -l 路径 逐级检查 |
| 服务启动失败 | 端口占用、配置语法错、依赖缺失 | systemctl status 看报错,journalctl -u 服务名 -n 100 |
| 端口不通 | 服务监听 127.0.0.1、防火墙、安全组 | ss -lntp → firewall-cmd --list-ports → 云安全组 |
| 磁盘写满 | 日志未清理、大文件堆积 | df -h 找分区,du -sh /* 逐层定位 |
| SSH 免密无效 | 目录权限不对 | ~/.ssh 700,authorized_keys 600 |
| 定时任务不执行 | 环境变量缺失、脚本无执行权限 | 脚本内 source 环境;chmod +x |
| 集群节点未启动 | hosts 不匹配、免密失败、端口冲突 | 看 $HADOOP_HOME/logs/ 对应日志 |
NameNode 反复格式化后数据丢失 |
误执行 -format |
严禁二次格式化,从备份恢复 |
| 中文乱码 | 字符集不统一 | 数据库 utf8mb4,文件 file 命令确认编码 |
8.2 磁盘写满的标准排查流程
磁盘告警是生产环境最常见的问题,处理流程建议固定下来:
bash复制# 第一步:确认哪个分区满了
df -h
# 第二步:进入该分区,逐层找大目录
du -sh /* 2>/dev/null | sort -rh | head -10
# 第三步:进入最大目录继续
du -sh /var/* 2>/dev/null | sort -rh | head -10
# 第四步:找出大文件
find /var -type f -size +500M 2>/dev/null -exec ls -lh {} \;
# 第五步:检查是否有被删除但未释放的文件(进程还占着)
lsof | grep deleted | head -20
第五步是最容易被忽略的。有时候 df 显示满了,但 du 加起来只有一半,原因就是有进程持有已删除文件的句柄,空间没真正释放。这时候只能重启对应进程,或者用 > /proc/pid/fd/xxx 清空。
8.3 服务起不来的排查顺序
固定按下面顺序走,能覆盖 90% 的情况:
bash复制# 1. 看服务状态和最近日志
systemctl status 服务名
journalctl -u 服务名 -n 200 --no-pager
# 2. 如果是配置文件问题,先做语法检查
# Nginx
nginx -t
# MySQL 看 error.log
tail -100 /data/mysql/error.log
# 3. 检查端口是否被占用
ss -lntp | grep 端口号
# 4. 检查磁盘和内存
df -h
free -h
# 5. 检查权限和目录是否存在
ls -ld 数据目录
日志是排查的第一现场,几乎所有问题都能在日志里找到线索。养成先看日志再动手的习惯,比凭猜测改配置效率高得多。
9. 最佳实践与进阶路线
9.1 服务器操作的几条硬规矩
第一条,任何删除、覆盖、格式化操作前,先确认路径。我的习惯是:rm 之前先 ls 一遍,find -delete 之前先 find -print,format 之前先确认这是不是第一次。多花五秒,能避免一次事故。
第二条,改配置文件前先备份。最简单的做法:
bash复制cp /etc/my.cnf /etc/my.cnf.bak.$(date +%Y%m%d%H%M%S)
带时间戳的备份能让你在改坏三个版本之后还能找回最初的配置。
第三条,生产环境变更走测试环境。特别是数据库的 DDL、配置中心的配置修改、集群的格式化这类不可逆操作,必须在测试环境验证过再动生产。
第四条,权限最小化。不用 root 跑业务进程,数据库账号不给 DDL 权限,管理端口不暴露公网。这三点做到了,能挡掉大部分安全事故。
9.2 脚本与自动化的工程化建议
脚本不是写完能跑就完事,还需要考虑可维护性:
统一目录规划。建议固定成 /data/scripts(脚本)、/data/scripts/logs(脚本日志)、/data/backup(备份)、/data/app(应用)这几个位置,不要东放一个西放一个。
统一日志格式。所有脚本的日志都用 [时间] 级别 内容 的格式,这样后面用 grep 过滤、用 awk 统计都方便。
统一错误处理。每个脚本开头加 set -euo pipefail,关键步骤加显式判断,出错时打印足够上下文再退出。
把常用操作封装成函数库。比如日志函数、通知函数(发钉钉/企业微信)、校验函数,放在一个 common.sh 里,其他脚本 source 进来,避免重复造轮子。
9.3 下一步该学什么
把本文的内容练熟之后,可以往这几个方向继续深入:
容器与编排方向。Docker 熟练之后学 Kubernetes,这是当前主流的部署方式。重点理解 Pod、Service、Deployment、ConfigMap 这几个核心概念。
自动化配置方向。Ansible 用来自动化批量部署和配置管理,语法是 YAML,学习曲线比 Shell 平缓,适合管理几十上百台机器。
监控告警方向。Prometheus + Grafana 是目前最流行的组合,配合 node_exporter 采集主机指标,能提前发现磁盘、内存、负载问题,而不是等用户投诉。
大数据生态方向。Hadoop 之后可以继续学 Hive(SQL 化查询)、Spark(内存计算)、Flink(流处理),这些组件都建立在本文的 HDFS + YARN 基础之上,基础打牢了学起来会顺很多。
内核与性能方向。如果对底层感兴趣,可以研究 strace、perf、/proc 文件系统,理解一个进程从启动到退出的完整生命周期,这是从「会用」到「精通」的分水岭。
9.4 一份可执行的练习清单
光看不练等于没学,建议按这个清单动手做一遍:
第一周,装虚拟机,练熟文件操作、权限、进程管理这三大块,能独立完成「给新用户配置 sudo 权限并限制目录访问」。
第二周,练文本处理,用 grep、awk、sort、uniq 从一份 Nginx 访问日志里统计出 top10 IP、top10 接口、各状态码占比。
第三周,写三个脚本:日志清理、数据库定时备份、服务健康检查。全部加上参数校验、日志输出和 dry-run 模式,配到 crontab 里跑一周。
第四周,用 Docker 部署 MySQL + Nginx + 一个 Java 应用,配好反向代理和数据卷挂载,模拟一次「重启机器后服务自动恢复」。
第五周,开三台云主机,从零搭一套 Hadoop 集群,跑通一次 WordCount,然后把其中一台关机,观察集群如何反应。
这套练习做下来,基本能覆盖日常运维和开发的绝大多数场景。遇到报错别急着搜答案,先自己看日志、看状态、验证假设,这个过程中积累的判断力,才是真正属于你的东西。
如果文中的命令和配置对你有帮助,可以收藏备用,后面搭环境的时候直接翻出来对照着改就行。
