公司里每次来一台新机器,最烦的就是那些重复到让人麻木的初始化操作。装系统、改主机名、配 yum 源、关防火墙、调内核参数、装 JDK、装 Docker……一套流程走下来,少说半小时,多则一小时,中间还容易漏掉一两个细节。后来我索性把整个初始化流程写成了一个 CentOS 7 初始化脚本,一条命令跑完,干净利落。这篇文章就把这个脚本的思路、完整实现、踩坑记录都摊开来讲,希望能给同样被重复劳动折磨的运维和开发同学一点参考。
这个脚本适合谁?适合所有需要批量维护 CentOS 7 机器的同学,不管你是运维、后端开发还是刚入门的 Linux 新手。你不需要精通 Shell,脚本拿到手改几个变量就能用,但我会把关键逻辑也讲清楚,让你敢改、会改、改完不慌。这里我默认你手上是一台刚装好最小化系统的 CentOS 7 机器,架构是 x86_64,有 root 权限,网络能通外网。
1. 内容整体设计与思路拆解
1.1 为什么偏偏要给初始化写脚本
CentOS 7 的初始化操作看起来不难,每一项单独拎出来都有现成命令,难的是“每一项都要做、顺序不能乱、漏了不好查”。举个例子,如果你不先把 SELinux 关掉就急着装 Docker,后面容器起不来你八成会先怀疑 Docker 装错了,排查半天才发现是 SELinux 拦截。再比如防火墙默认 zone 的配置,不同版本的系统默认网卡叫法还不一样,有的是 eth0,有的是 ens33,脚本里就得做动态判断。
写脚本还有一个更实际的考虑:人记不住那么细。就算你经验丰富,每次手动敲命令,总有那么一两次会漏掉内核参数没改,或者忘记同步时间。我踩过一次坑,新机器没有配置时间同步,日志时间漂移,排查线上问题的时候对不上时间戳,折腾到半夜。把初始化脚本化之后,至少每台机器的基础配置是一致的,这就是“可复现”的价值。
另外,脚本能帮你省下大量重复劳动。公司一次采购 10 台机器,手动一台一台配置,弄到第三台就开始烦躁,后面效率明显下降。我用脚本批量跑,10 台机器铺开同时配置,半小时内全部搞定,还能自动输出每台机器的执行日志,谁成功谁失败一目了然。
1.2 模块划分:先想清楚再动手
写这个脚本之前,我先把初始化需要做的事按模块拆了一遍,避免想到哪写到哪。模块划分大概是这样:
- 前置检查:确认系统是 CentOS 7、有 root 权限、磁盘空间足够、网络通。
- 基础配置:主机名、时区、时间同步、DNS 解析。
- 软件源配置:备份并替换 yum 源为国内镜像,安装 EPEL 源和常用工具。
- 安全加固:SELinux、防火墙、SSH 配置、创建普通用户并配置免密登录。
- 内核与性能调优:修改文件句柄数、TCP 内核参数、关闭不必要的服务。
- 运行环境:安装 JDK、Docker、Python、Git 等与具体业务相关的组件。
这个划分不是拍脑袋定的,而是按“先环境后应用、先系统后服务”的顺序。比如时间同步必须放在前面,不然后面编译安装软件时时间戳不对会引发各种奇怪问题。yum 源也要尽量靠前,因为后续绝大多数软件都依赖它。
脚本里的每个函数对应一个模块,主流程只负责按顺序调用。这样有个明显好处:你不需要我的业务场景,可以直接删掉不需要的模块;或者你自己想加一个模块,比如安装 Nginx,只需要再写一个 install_nginx 函数,在主流程里加上一行调用就行,其他部分不用动。
1.3 设计原则:幂等性、可追溯、可重跑
我在写脚本时给自己定了几条军规,第一条就是脚本必须幂等。所谓幂等,就是同一台机器上跑两遍、三遍,结果都是一样的,不会因为重复执行而出问题。比如添加用户前先判断用户是否存在,配置文件修改前先做备份,软件已安装就跳过安装步骤。这些细节让脚本可以安全地反复执行,重跑不会把机器搞坏。
第二条是日志要完整。脚本里每一步操作都写日志,包括时间、操作内容、执行结果。日志文件统一放在 /var/log/centos7-init.log,执行完后我可以 grep 这个文件快速定位失败点。给机器最多的就是时间,日志留痕能让你在出问题时很快知道这台机器当初是怎么配置的。
第三条是变量要收敛。脚本里所有可变的配置项,比如主机名、DNS 服务器地址、要安装的 JDK 版本、Docker 镜像加速地址,都集中放在脚本开头的配置区。跑的时候改配置区,不要改函数体。这样做的好处显而易见:你接手别人的脚本时,不需要读懂每个函数,看一眼配置区就知道这台机器会被配置成什么样。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心细节解析与实操要点
2.1 前置检查:别让脚本跑了一半才报错
脚本刚开始的前置检查是个“快速失败”机制,目的是尽早发现问题,而不是让脚本跑了一多半才报错,留下一个半配置状态的系统。这个环节我主要检查三件事:操作系统版本、当前用户权限、网络连通性。
操作系统版本检查不能只靠 cat /etc/redhat-release,因为有些发行版会伪装成 CentOS。我用的是 grep -qi "CentOS" /etc/redhat-release && rpm -q centos-release 双保险,确保系统确实是 CentOS 7。网络检查用 curl -sI http://mirrors.aliyun.com 探测,能通才继续往下走,否则提示配置网络后重新运行。
这里有一个容易忽略的点:脚本里很多操作需要 root 权限,但并不是每个人都会用 root 登录。我在脚本开头用 [ "$(id -u)" -eq 0 ] 判断当前用户 ID,如果不是 0 就提示用 sudo 执行或者切换到 root,然后退出。别小看这个检查,没有了它,脚本可能在中途因为权限不足而输出一堆晦涩的错误信息,新手根本看不明白怎么回事。
磁盘空间检查也值得做。初始化时要安装的软件、拉取的镜像、编译的临时文件都会占用空间,如果根分区只有 5GB,装完基础环境大概率就满了。我用 df -P / | awk 'NR==2 {print $4}' 取根分区剩余空间,小于 2GB 直接退出,并给出清理建议。
2.2 配置 yum 源:换不换镜像,体验差距极大
CentOS 7 默认的 yum 源在公网环境下速度慢得让人崩溃,尤其是安装 Docker、GCC 这类大的依赖树时。我一般直接把 yum 源替换成阿里云镜像,顺手把 EPEL 源也换上。EPEL 是 Extra Packages for Enterprise Linux 的缩写,里面有大量 CentOS 官方源没有的软件包,比如 htop、jq、sshpass 这些运维常用工具。
这里有个细节:替换源之前必须先备份。我习惯把原始 repo 文件复制到一个 backup 目录,命名加上时间戳。这样万一新源的仓库配置有问题,可以一键还原。备份命令就是 mkdir -p /etc/yum.repos.d/backup && cp /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/。
替换源之后必须清理并重建缓存。这个步骤很多人会忘记,直接导致安装软件时提示找不到包。正确做法是:
bash复制yum clean all
yum makecache fast
如果公司网络环境比较特殊,只能访问内网镜像,也可以把 baseurl 改成公司仓库地址。我在脚本中把 MIRROR_BASE 抽成了变量,默认指向阿里云,你可以改成自己的内网地址。另外还有一个细节:CentOS 7 官方的 yum 源文件里有 $releasever 这个变量,替换时要注意保留,否则路径会解析错误。
2.3 网络、主机名与 DNS:初始化里最琐碎的部分
主机名、静态 IP、DNS 这些配置是最琐碎的,每台机器都不一样,也是最容易出错的地方。我在脚本里通过变量和判断来处理:如果你在配置区填了目标主机名,脚本就用 hostnamectl 设置,否则保持默认。
静态 IP 配置比较个性化,我没有把它做成全自动,而是生成一个网卡配置示例,然后提示手动确认。因为每台机器的网卡名称、IP 网段、网关都不一样,硬编码进脚本里反而不灵活。但我把关键步骤写成了注释,方便你复制出来手动执行。网络这块建议还是手动改,然后 systemctl restart network,确认 SSH 不断掉。
DNS 配置倒是可以自动化。我脚本里默认写入 114.114.114.114 和 223.5.5.5 两个公共 DNS,配合 systemd-resolved 或直接写入 /etc/resolv.conf。注意写 /etc/resolv.conf 时别把原有内容覆盖掉,尤其是有些云厂商会把 DNS 写到 /etc/resolv.conf 里,直接覆盖会导致内网域名解析失败。
2.4 安全基线:SELinux、防火墙、SSH 与用户配置
安全配置是初始化脚本的重头戏,也是最容易“一刀切”出错的地方。先说 SELinux,很多教程上来就让你 sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config,然后重启。这种做法的确简单,但粗暴。如果你只是跑测试环境,可以这么干;生产环境我更建议先设成 permissive 模式,观察一段时间日志,确认没有 SELinux 拦截导致的问题后再永久关闭。
不过在我的脚本里,还是默认提供了关闭 SELinux 的函数,因为大部分开发测试环境不需要它,而且关闭之后能减少很多莫名其妙的问题。函数里我会先执行 setenforce 0 让它立即生效,再修改配置文件保证重启后依然生效。这里有个新手容易踩的坑:只改配置文件不执行 setenforce 0,当前会话里 SELinux 还是 enforcing,后续操作照样会被拦截。
防火墙这边,同样做两层处理:先用 systemctl stop firewalld 停止,再用 systemctl disable firewalld 禁用开机自启。如果业务上需要保留防火墙,就把 stop 注释掉,改成只放行必要端口。我记得第一次给客户机器跑脚本时忘了考虑这个,结果客户要求必须开防火墙,我只好又写了一个反向脚本,很尴尬。
SSH 配置主要是提高安全性。默认 root 登录密码登录在公网机器上风险太高,我一般是做三件事:修改 SSH 端口、禁止 root 密码登录、用公钥登录。修改端口时记得把新端口加到防火墙放行列表(如果防火墙还开着的话),另一个坑是千万别把 PermitRootLogin yes 改成 no 之后又没配好公钥,结果自己 SSH 都登不进去了,最后只能通过 VNC 控制台救援。我脚本里加了一个保护逻辑:只有检测到 ~/.ssh/authorized_keys 文件存在时,才执行禁止密码登录的配置。
用户配置方面,脚本创建了一个名为 deploy 的普通用户,加入 wheel 组,并把公钥写入该用户的 authorized_keys。这个 deploy 用户是给应用部署用的,不直接用 root 跑业务。创建用户的幂等写法是:
bash复制if id deploy >/dev/null 2>&1; then
echo "用户 deploy 已存在"
else
useradd deploy
echo "P@ssw0rd" | passwd --stdin deploy
fi
2.5 内核参数与性能调优:让新机器更好地干活
内核参数这块,大多数人会直接改 /etc/sysctl.conf,贴一份模板进去,然后 sysctl -p 生效。这种做法没问题,但我建议把自定义内核参数单独放到 /etc/sysctl.d/99-custom.conf,便于和系统默认配置区分开,后续维护也更清晰。
比较常用的几个参数包括:net.core.somaxconn 增加 TCP 连接队列长度,net.ipv4.tcp_syncookies 开启 SYN 洪水防护,vm.swappiness 调低,减少 swap 使用频率。对于数据库或高并发 Web 服务,还会调大 net.ipv4.ip_local_port_range,让系统能使用更多临时端口。修改这些参数之前在脚本里先备份原文件,这条经验我反复强调,因为真的有人改崩过网络栈,连 SSH 都连不上。
另外一个和性能强相关的配置是文件句柄数。默认的 1024 对数据库或 Java 应用来说完全不够,要修改 /etc/security/limits.conf 和 /etc/security/limits.d/20-nproc.conf,把 nofile 和 nproc 的硬软限制都调大。需要注意,limits.conf 里 * 和用户名写法的作用域不一样,如果希望所有用户生效就用 *,但某些情况下 systemd 服务不读 limits.conf,需要额外在 service 文件里写 LimitNOFILE。这是一个很深的坑,后面我会展开说。
3. 实操过程与核心环节实现
3.1 完整脚本骨架:可以直接抄作业
下面这个脚本就是我目前在用的 CentOS 7 初始化脚本的简化版,做了一些注释,方便直接照着改。我把它放在 GitHub Gist 里,但文章里也贴了一份,方便直接复制。
bash复制#!/bin/bash
###############################################################################
# centos7-init.sh - CentOS 7 新机器初始化脚本
# 用法: bash centos7-init.sh
###############################################################################
set -euo pipefail
# -------------------- 配置区(按需修改) --------------------
TARGET_HOSTNAME="app-server-01"
TIMEZONE="Asia/Shanghai"
PUBLIC_KEY="ssh-rsa AAAAB3Nza... user@example.com"
MIRROR_BASE="https://mirrors.aliyun.com"
DNS_SERVERS="114.114.114.114 223.5.5.5"
INSTALL_EVPN="false" # 是否安装 EPEL 源
INSTALL_DOCKER="true" # 是否安装 Docker
INSTALL_JDK="true" # 是否安装 OpenJDK 11
LOG_FILE="/var/log/centos7-init.log"
# -------------------- 功能函数 --------------------
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${LOG_FILE}"
}
die() {
echo "[ERROR] $*" >&2
exit 1
}
check_root() {
[ "$(id -u)" -eq 0 ] || die "请使用 root 用户运行此脚本"
}
check_os() {
grep -qi "CentOS" /etc/redhat-release || die "系统不是 CentOS"
grep -q "release 7" /etc/redhat-release || die "系统不是 CentOS 7"
}
check_network() {
curl -sI "${MIRROR_BASE}" >/dev/null 2>&1 || die "网络不通,请检查网络配置"
}
set_hostname() {
hostnamectl set-hostname "${TARGET_HOSTNAME}"
log "主机名已设置为 ${TARGET_HOSTNAME}"
}
set_timezone_and_sync() {
timedatectl set-timezone "${TIMEZONE}"
yum install -y chrony >/dev/null 2>&1
systemctl enable chronyd && systemctl start chronyd
chronyc makestep >/dev/null 2>&1 || true
log "时区已设置为 ${TIMEZONE},时间同步已开启"
}
config_yum_source() {
local backup_dir="/etc/yum.repos.d/backup_$(date +%F_%T)"
mkdir -p "${backup_dir}"
cp /etc/yum.repos.d/*.repo "${backup_dir}/" 2>/dev/null || true
rm -f /etc/yum.repos.d/CentOS-*.repo
curl -o /etc/yum.repos.d/CentOS-Base.repo \
"${MIRROR_BASE}/repo/Centos-7.repo"
yum install -y epel-release >/dev/null 2>&1 || true
yum clean all >/dev/null 2>&1
yum makecache fast >/dev/null 2>&1
log "yum 源已替换完成"
}
install_base_tools() {
yum install -y vim wget curl net-tools unzip zip lsof \
iotop iftop nload htop jq tree >/dev/null
log "基础工具安装完成"
}
config_security() {
# 关闭 SELinux
sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
setenforce 0 2>/dev/null || true
# 关闭防火墙
systemctl stop firewalld || true
systemctl disable firewalld || true
# 配置 SSH 公钥登录
mkdir -p /root/.ssh
echo "${PUBLIC_KEY}" >> /root/.ssh/authorized_keys
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
log "安全基线配置完成"
}
config_sysctl() {
cat > /etc/sysctl.d/99-custom.conf <<'EOF'
net.core.somaxconn = 32768
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_syncookies = 1
net.ipv4.ip_local_port_range = 1024 65535
vm.swappiness = 10
fs.file-max = 6553560
EOF
sysctl -p /etc/sysctl.d/99-custom.conf >/dev/null 2>&1
# 修改文件句柄数
cat >> /etc/security/limits.conf <<'EOF'
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535
EOF
log "内核参数与文件句柄配置完成"
}
install_docker() {
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo \
"${MIRROR_BASE}/docker-ce/linux/centos/docker-ce.repo"
yum install -y docker-ce docker-ce-cli containerd.io
systemctl enable docker && systemctl start docker
# 配置镜像加速(可选)
mkdir -p /etc/docker
cat > /etc/docker/daemon.json <<'EOF'
{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}
EOF
systemctl restart docker
log "Docker 安装完成"
}
install_jdk() {
yum install -y java-11-openjdk java-11-openjdk-devel
log "OpenJDK 11 安装完成"
}
main() {
touch "${LOG_FILE}"
log "===== CentOS 7 初始化开始 ====="
check_root
check_os
check_network
set_hostname
set_timezone_and_sync
config_yum_source
install_base_tools
config_security
config_sysctl
if [ "${INSTALL_DOCKER}" = "true" ]; then
install_docker
fi
if [ "${INSTALL_JDK}" = "true" ]; then
install_jdk
fi
log "===== CentOS 7 初始化完成 ====="
}
# 执行主流程
main "$@"
这段代码比较长,但逻辑很清楚:配置区在前,函数区居中,main 函数按顺序执行。你可以直接在配置区改主机名、公钥、加速地址这些内容,然后把脚本放到新机器上执行。
需要注意的是,脚本里 set -euo pipefail 这一行很关键,含义是:任何一个命令报错就立即退出(-e),使用未定义变量就报错(-u),管道中任意命令失败就算失败(pipefail)。这样能避免脚本在某个环节出错以后继续往下跑,最后留下一台半配置状态的机器。
3.2 关键函数逐个拆解
set_timezone_and_sync 这个函数里,chronyc makestep 是我后来才加上的。原因是刚装好的机器硬件时间可能和真实时间差很多,如果只用 chrony 慢慢同步,可能要等很久,而在同步完成前,Yum 安装软件、写入日志的时间戳都可能不对。makestep 的直接含义是让 chrony 立刻把系统时间跳到正确值,代价是时间可能向前或向后跳,但对于刚初始化的机器来说完全没问题。
config_yum_source 里 rm -f /etc/yum.repos.d/CentOS-*.repo 这一步要特别注意。有些 CentOS 7 默认源文件可能被自定义过,尤其是云厂商提供的镜像,直接把默认 repo 删掉再替换成新源,能彻底避免旧源配置干扰。当然,我前面在 backup 目录里做了备份,所以删掉是有后路的。
install_docker 里用到了 yum-config-manager,这个命令来自 yum-utils,所以在安装 Docker 之前必须先装 yum-utils。另外 Docker repo 的路径我写的是 ${MIRROR_BASE}/docker-ce/linux/centos/docker-ce.repo,如果你是使用清华或者其他镜像源,路径可能会有差异,务必要确认一下。这里如果写错,最直接的表现是 yum install docker-ce 时提示找不到包。
config_sysctl 里我把自定义参数单独写在 /etc/sysctl.d/99-custom.conf,而不是直接追加到 /etc/sysctl.conf。理由前面提过:方便维护,也方便回滚。执行 sysctl -p 时指定这个文件路径,不会影响其他系统默认参数。
3.3 日志与错误处理:出问题时不至于手足无措
脚本里所有 log 都使用了 tee -a "${LOG_FILE}",意思是输出到屏幕的同时写入日志文件。这样做有一个直接的好处:如果脚本在后台跑,你可以随时用 tail -f /var/log/centos7-init.log 看进度,不用一直盯着终端。
错误处理方面,我依赖 set -e 的“跑挂即停”机制,另外在需要容错的地方单独处理。比如 setenforce 0 在某些环境里可能执行失败,但不影响整体流程,所以加了 || true。这个写法的意思是:即使命令返回非零退出码,也忽略这个错误继续往下走。
这里想提醒一下:|| true 不要滥用。不加判断地给每条命令都加 || true,会让脚本失去快速失败的能力,出了问题也发现不了。我通常是只给“顺手做一下、失败也无所谓”的辅助命令加,比如清缓存、设置时区同步这些。像安装软件、修改核心配置文件这类关键操作,绝对不用 || true。
3.4 执行流程演示:从空机器到可用环境
假设你刚拿到一台 CentOS 7 虚拟机,IP 是 192.168.1.100,想让脚本跑一遍。具体操作流程是:
- 把脚本传到机器上,比如用
scp centos7-init.sh root@192.168.1.100:/root/。 - SSH 登录机器,执行
bash centos7-init.sh。 - 观察屏幕输出,如果某一步报错,根据日志定位并修复。
- 脚本执行完后,重新登录一次,确认主机名、IP、Docker 状态都正常。
第一次执行时,脚本会花较长时间,主要耗时在 yum makecache 和安装 Docker 的依赖上。建议在 tmux 或 screen 会话里跑,避免 SSH 断开导致脚本中断。我亲眼见过有人在普通终端里跑长任务,结果网络抖动 SSH 断开,脚本卡在一半,机器状态变得不可控。用 tmux 再跑脚本,是一个低成本高收益的习惯。
4. 常见问题与排查技巧实录
4.1 问题速查表
结合实际使用中反馈较多的问题,我整理了一张速查表:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 脚本提示“系统不是 CentOS 7” | 系统版本不匹配或镜像裁剪 | 执行 cat /etc/redhat-release 确认版本 |
| yum install 提示找不到包 | yum 源未更新或 repo 文件路径错误 | 执行 yum clean all && yum makecache |
| Docker 启动失败 | SELinux 未关闭或内核模块缺失 | 执行 getenforce 确认状态,必要时重启 |
| SSH 登录卡住 | DNS 反向解析超时 | 修改 /etc/ssh/sshd_config 加 UseDNS no |
| 修改主机名后提示符未变化 | 当前 shell 缓存 | 重新登录或执行 exec bash |
| 文件句柄数修改后不生效 | 未重新登录或服务未重启 | 确认 limits.conf 写法,必要时重启服务 |
| 系统时间偏差大 | chrony 未成功同步 | 手动执行 chronyc makestep |
这张表是我的“故障字典”,遇到问题先查表,再深挖。比如 UseDNS no 这个配置,很多新机器的 SSH 登录会等待几十秒甚至更久,就是因为 SSH 默认会尝试反向解析客户端 IP,DNS 不通就卡住。在初始化脚本里加上这个配置,能明显改善登录体验。
4.2 yum 源配置失败的具体排查思路
yum 源配置问题在所有问题中出现的频率最高。如果出现 Could not resolve host,那可能是网络不通,先 curl 一下镜像站测试连通性。如果是 404,那基本是 repo 文件里的路径不对,尤其要检查 $releasever 变量是否被错误替换,或者镜像站是否支持对应架构。
还有一个比较隐蔽的问题:curl 下载的 repo 文件可能包含了 Windows 换行符。如果在 Windows 上编辑过 repo 文件再传到 Linux,Yum 解析时容易报奇怪错误。解决方法是执行 yum clean all 后,用 vim 或 sed -i 's/\r$//' 去除换行符。我一般建议所有配置文件都在 Linux 上用 vi 编辑,或者用 dos2unix 工具转换一下。
4.3 幂等性相关的坑:跑第二次竟然报错
脚本默认设计为可以重复执行,但我在实际使用中确实遇到过跑第二次报错的情况。最典型的是 /etc/yum.repos.d/backup_* 目录堆积,第一次备份后第二次执行时 cp 会多出一层嵌套备份,不影响使用但看着难受。解决方法是判断备份目录不存在时才创建。
另一个坑是 Docker 的 daemon.json 写入了多次。如果第一次运行脚本时网络不好,Docker 没装上,但 daemon.json 已经写入,第二次运行时脚本重新安装 Docker,然后再次覆盖 daemon.json,看起来没问题。但如果第一次运行到一半就被 set -e 中断,第二次运行时会跳过已装好的组件,却可能重复追加 limits.conf 中的内容。所以我后来在追加配置的代码前都加了一个 grep -q 判断,如果内容已存在就不再重复写入。这个习惯强烈推荐大家养成。
4.4 调试技巧:脚本出问题,怎么快速定位
当脚本运行报错退出时,我的第一反应不是去看一堆日志,而是先确认是哪个函数、哪一行报错。最简单的方法是给脚本加 bash -x centos7-init.sh,这样会打印每一条命令的执行细节,很快就能定位到具体命令。配合 set -e,报错的那一刻脚本就停了,最后几步打印的命令就是问题所在。
有时候问题不是脚本语法,而是某台机器环境比较特殊,比如系统没有装 curl。我在脚本里尽量用系统自带的工具,比如用 > 重定向写文件、用 sed 改配置,尽量减少对外部命令的依赖。实在要用外部命令,就在前置检查里确认已安装,或者用 command -v curl || yum install -y curl 这种自动安装兜底。
日志定位追亿还有一个好用的方法:在重要函数入口处加 log 输出“开始执行 xxx”“完成执行 xxx”,这样即使脚本中途挂掉,也能知道挂在哪一步之间。这个习惯帮我排查过很多次问题,尤其是脚本在客户机器上跑失败时,对方发回一份日志我就能定位,不用反复远程调试。
5. 扩展方向与个人心得
5.1 按角色扩展:让一套脚本适配不同业务
这个脚本目前是一个通用基线,但实际业务中不同角色的机器,初始化需求差别很大。比如数据库服务器的内核参数和普通 Web 服务器完全不同,日志服务器要安装 Filebeat,监控的 agent 也要额外装。
我建议的做法是把脚本改造成支持角色参数,执行的时候传入 --role web 或 --role db,根据角色动态加载不同的配置模块。这是我脚本的一个演进思路:配置区里加一个 ROLE 变量,main 函数里用 case 分支选择不同的应用安装策略。例如 db 角色额外执行 config_db_sysctl,把 vm.swappiness 改成 1,把磁盘 IO 调度器改成 deadline。
如果你需要批量执行,还可以配合 Ansible 或 pssh 这类工具,把脚本下发到多台机器上同时执行。我自己的经验是:初始化这种高频标准化操作,脚本化是第一步,批量化和自动化是后面自然而然要做的延伸。先保证单台机器跑得对,再考虑规模化。
5.2 脚本的安全性和维护注意事项
脚本里有几条容易被忽略但很重要的安全习惯,简单说一下。首先,脚本中如果出现密码,建议不要硬编码,而是通过环境变量或交互方式输入,避免别人翻看脚本时直接看到密码。我的脚本创建用 deploy 用户时用了一个占位密码,实际使用中最好用 passwd --stdin 配合外部传入,或者直接跳过设置密码,只保留公钥登录。
其次,公钥是敏感信息。我在配置区写死公钥是方便演示,但真实项目里建议把公钥放到独立的 key 文件里,脚本读取文件内容。这样公钥需要轮换时,只需更新一个文件,不需要改脚本本身。
最后,脚本本身也要做校验。我上个月有一个教训:本地改好的脚本没在测试机验证就直接上生产,结果一个变量名写错了,差点把生产机器的主机名改成空字符串。从那以后我给自己定了规矩:任何脚本改动,先在一台干净的测试虚拟机上跑一遍,确认没问题再推广到其他机器。这套流程看起来多花了几分钟,实际上省下的排障时间远不止这些。
5.3 从脚本到意识:初始化这件事还有哪些坑
关于 CentOS 7 初始化,最后想分享一个容易被忽略的坑:CentOS 7 的官方生命周期已经进入维护阶段,新的安全更新和软件包会逐步减少,所以在初始化新机器时,建议尽早规划未来迁移到替代系统的路径。具体到脚本层面,可以先把版本相关的命令单独抽出来,比如 rpm -q centos-release 这种检查,后续切换系统时只需要改这个文件,不影响其他模块。
另外,脚本写多了以后,我的体会是尽量不要为了炫技而使用高级写法。Shell 脚本里最怕看到过于复杂的花式写法,因为三个月后你自己都未必能一眼看懂。用最简单直白的命令,加上充分注释,每个函数只负责一件事,这就是一个能长期维护的脚本应该有的样子。
我现在的习惯是,每个季度都会重新审视一遍这个初始化脚本,看看有没有新的系统配置要求、新的安全基线、新的通用工具需要加入。因为机器初始化不是一次性的工作,而是需要持续迭代和积累的长期资产。这套脚本从一个简单的 yum 源切换脚本开始,到现在覆盖系统、安全、运行环境,背后就是一次次踩坑和需求积累的过程。
