Linux一键安装JDK/MySQL/Redis/Nginx的幂等与回滚 简介Linux 一键式安装 JDK、MySQL、Redis、Nginx 的部署工具包面向需要在 Linux 服务器上快速搭建 Java Web 运行环境的运维人员、后端开发者与部署初学者重点解决多组件逐一手动安装、依赖处理繁琐、配置易出错的问题。压缩包共 27 个文件约 930.56MB以 sh 安装脚本、gz 源码与依赖包、conf 和 cnf 配置模板、rpm 离线依赖及 tar 归档为主兼顾自动化执行与离线部署目录按 JDK、MySQL、Redis、Nginx 及基础编译依赖分模块组织。已有 299 人学习说明该方案在实际搭建场景中具备一定参考价值。读者可从中获得组件安装与初始化脚本、服务配置样例、密码重置与系统服务注册思路以及依赖缺失时的补齐办法适合用于搭建本地测试环境、课程实验环境或对照学习 Linux 下常见中间件的标准化部署流程。1. Linux 一键安装 JDK、MySQL、Redis、Nginx真正难的是边界Linux 一键安装 JDK、MySQL、Redis、Nginx很多人第一反应是找一条能跑的脚本真正让人翻车的地方往往不是命令本身而是目录、用户、端口和数据目录之间的耦合。这份资源把四个组件拆成同一条安装链统一包目录、统一运行用户、统一日志和 systemd 托管脚本按install.conf驱动重复执行不会把已有环境冲掉。它适合需要在内网测试机、CI 临时环境或新购云主机上快速搭出 Java Web 基础栈的从业者不适合已经在生产环境跑着定制化中间件、要求原地升级的集群。新手照着参数改能复现熟手更该关注它的幂等边界和回滚点。2. 安装链的骨架目录布局、依赖检测与幂等开关一键脚本最容易被误解成把四条安装命令塞进一个文件。实际拆下来JDK、MySQL、Redis、Nginx 的安装动作只占一半另一半是边界约定程序装在哪、数据放哪、用哪个用户跑、端口冲突时改哪里、脚本第二遍执行时该跳过还是重装。骨架没定好后面每个组件都会长出自己的“小脾气”排障时只能靠猜。我一般把安装链分成四层配置层只放变量函数层只做可复用动作组件层负责单个软件的安装与配置主流程层按顺序调用并做健康检查。这样做的直接好处是某个组件失败时能单独重跑不用把整条链再走一遍。下面从目录和用户开始把这条链的骨架拆开。2.1 先定目录和运行用户别让 /usr/local 变成垃圾场目录布局决定了后面备份、升级和权限排查的成本。四个组件如果全部塞进/usr/local程序文件、配置文件、数据文件和日志会混在一起出问题时很难判断哪些该备份、哪些可以删。更稳妥的做法是把程序、数据、离线包、日志分开变量示例值作用改动风险BASE_DIR/opt/apps放 JDK、MySQL、Redis、Nginx 程序改后所有软链和 unit 文件都要同步DATA_DIR/data放 MySQL 数据、Redis 持久化文件不要放在系统盘临时目录PKG_DIR/opt/pkg放离线压缩包脚本按通配符找包包名不要随意改LOG_DIR/var/log/apps放 MySQL 错误日志、Nginx 日志注意 logrotate 和磁盘配额RUN_USERappsvc四个组件的运行用户不要用 root 跑业务进程配置文件只保留这些变量密码不要写进文件。MySQL 和 Redis 的密码用环境变量传入脚本启动时检查是否为空为空就退出。这样配置文件可以进版本库密码不会跟着泄露。# install.conf只改这里不要改 install.sh BASE_DIR/opt/apps DATA_DIR/data PKG_DIR/opt/pkg LOG_DIR/var/log/apps RUN_USERappsvc MYSQL_PORT3306 REDIS_PORT6379 HTTP_PORT80 USE_ALTERNATIVES0 SKIP_DEPS0这段配置里USE_ALTERNATIVES控制 JDK 是否注册到系统alternatives默认关闭避免和/etc/profile.d里的JAVA_HOME打架SKIP_DEPS用于离线环境依赖已经预装时设为1脚本不再调用包管理器。MYSQL_PORT和REDIS_PORT只改监听端口防火墙规则和客户端连接串要同步改不要只改一处。运行用户用系统用户禁止登录数据目录权限收窄到0750。创建动作必须幂等用户已存在时不能报错退出ensure_user() { if ! id -u $RUN_USER /dev/null 21; then useradd -r -s /sbin/nologin $RUN_USER fi } ensure_dirs() { install -d -m 0755 -o root -g root $BASE_DIR $PKG_DIR $LOG_DIR install -d -m 0750 -o $RUN_USER -g $RUN_USER \ $DATA_DIR/mysql $DATA_DIR/redis $LOG_DIR/mysql $LOG_DIR/nginx }useradd -r创建系统用户-s /sbin/nologin禁止交互登录install -d的好处是目录已存在时只校正权限不会像mkdir那样报File exists。数据目录给appsvc程序目录保持root:root运行进程只能读程序、写数据不能改二进制文件。这个分界后面会反复用到MySQL 的datadir、Redis 的dir、Nginx 的logs都要落在运行用户可写的路径上。注意如果机器上已经有同名用户但家目录或 shell 不对不要直接删用户重建。先看id appsvc和/etc/passwd确认没有进程在用再决定是否调整。生产机上删用户比装错包更难收场。2.2 依赖检测与包管理器分流yum 与 apt 的最小交集依赖安装是一键脚本里最容易“看起来能跑、换台机器就挂”的部分。Nginx 源码编译需要 PCRE、zlib、OpenSSL 的开发包MySQL 二进制包依赖libaioRedis 只需要编译工具JDK 解压即可。不同发行版的包名不一样脚本不能只写一条yum install -y。常见做法是先探测包管理器再按最小依赖集安装。dnf优先于yum因为新系统上的yum往往只是兼容层Debian 系用apt-get并且加DEBIAN_FRONTENDnoninteractive避免交互卡住。依赖清单不需要追求大而全缺什么补什么编译报错时再回来看缺失的头文件。need_cmd() { command -v $1 /dev/null 21; } install_deps() { if need_cmd dnf; then dnf install -y gcc gcc-c make tar xz pcre-devel zlib-devel openssl-devel libaio elif need_cmd yum; then yum install -y gcc gcc-c make tar xz pcre-devel zlib-devel openssl-devel libaio elif need_cmd apt-get; then [[ ${APT_UPDATE:-1} 1 ]] apt-get update DEBIAN_FRONTENDnoninteractive apt-get install -y \ gcc g make tar xz-utils libpcre3-dev zlib1g-dev libssl-dev libaio1 else echo 未识别的包管理器请手动安装依赖 2 return 1 fi }这段函数的逻辑是“先探测、再分流、最后兜底”。need_cmd dnf命中时不走yum避免同一个系统上两个包管理器重复装包APT_UPDATE默认执行apt-get update内网无外网源时可以设为0直接用本地缓存。xz-utils不能漏MySQL 的.tar.xz包解压依赖它libaio也不能漏缺了之后mysqld启动会直接报共享库找不到错误日志里不一定写得很直白。离线环境把SKIP_DEPS1打开脚本只做command -v检查不调用包管理器。检查项不要只查gcc还要查make、tar、xz因为镜像里经常有编译器但没有解压工具。依赖装完后把结果写进日志后面排障时能快速判断是不是“包没装全”导致的编译失败。2.3 幂等与失败回滚脚本必须能跑第二遍一键脚本的价值不在于第一次跑通而在于第二次、第三次跑的时候不会把环境弄坏。幂等的核心是每个动作先探测再执行目录用install -d软链用ln -sfn服务用systemctl enable --now数据库初始化前判断数据目录是否已经存在。失败回滚则要克制不要一失败就rm -rf $DATA_DIR数据目录里可能是上一次没跑完但已经初始化的库。backup_file() { local f$1 [[ -e $f ]] || return 0 cp -a $f ${f}.bak.$(date %Y%m%d%H%M%S) } on_error() { local rc$? printf 失败第 %s 行退出码 %s\n $1 $rc 2 exit $rc } trap on_error $LINENO ERRbackup_file在覆盖/etc/my.cnf、/etc/redis/redis.conf、systemd unit 之前调用备份文件带时间戳不覆盖旧备份。trap on_error $LINENO ERR配合set -Eeuo pipefail任何未捕获的错误都会打印行号并退出避免脚本“带病继续跑”。注意pipefail下find ... | head -n1可能因为 SIGPIPE 返回 141脚本会误判失败所以查找单个包时用find ... -print -quit不要接head。主流程按“先基础设施、再组件、最后启服务”的顺序排列main() { need_root load_conf ensure_user ensure_dirs [[ ${SKIP_DEPS:-0} 1 ]] || install_deps install_jdk install_nginx install_mysql install_redis enable_services health_check }need_root检查EUID不是 root 直接退出load_conf用source读配置但配置里不要放可执行逻辑health_check放在最后四个组件都启动后再统一检查端口和进程。组件函数内部各自做幂等判断比如 JDK 已经存在且FORCE_JDK不为1就跳过MySQL 数据目录里已经有mysql子目录就跳过初始化。FORCE_*开关是留给熟手的后悔药默认关闭避免误删。提示回滚只回滚软链、unit 文件和临时目录不要自动删数据目录。真需要重装数据库时先停服务、备份DATA_DIR/mysql再手动执行带FORCE_MYSQL1的流程。3. JDK 与 Nginx 的静默安装解压、环境变量与 systemd 接管JDK 和 Nginx 放在一起讲是因为它们都涉及“程序目录 启动入口”的切换。JDK 的入口是java命令和JAVA_HOMENginx 的入口是nginx二进制和 systemd unit。两者都不需要跑初始化 SQL但都需要处理旧版本残留、环境变量来源和 systemd 不读登录 shell 的问题。3.1 JDK 解压与环境变量profile.d 和 alternatives 只选一条路JDK 安装本身很简单解压、设JAVA_HOME、把bin加进PATH。麻烦的是系统里可能已经有/usr/bin/java它可能来自发行版仓库也可能来自alternatives管理。两条路同时走就会出现java -version和$JAVA_HOME/bin/java -version不一致的情况。脚本按通配符找包不写死版本号。解压到临时目录确认顶层目录唯一后再移动到BASE_DIR/jdk最后写/etc/profile.d/jdk.sh。如果USE_ALTERNATIVES1再注册系统命令入口默认不注册避免和 profile 重复。install_jdk() { local pkg tmp src pkg$(find $PKG_DIR -maxdepth 1 \ \( -name jdk-*.tar.gz -o -name jdk-*.tar.xz \) -print -quit) [[ -n $pkg ]] || { echo PKG_DIR 下没有 JDK 压缩包 2; return 1; } tmp$BASE_DIR/.jdk.tmp.$$ rm -rf $tmp; mkdir -p $tmp tar -xf $pkg -C $tmp src$(find $tmp -maxdepth 1 -mindepth 1 -type d -print -quit) [[ -n $src ]] || { echo JDK 包结构不符合预期 2; return 1; } rm -rf $BASE_DIR/jdk mv $src $BASE_DIR/jdk chown -R root:root $BASE_DIR/jdk chmod -R go-w $BASE_DIR/jdk cat /etc/profile.d/jdk.sh EOF export JAVA_HOME/opt/apps/jdk export PATH$JAVA_HOME/bin:$PATH EOF chmod 0644 /etc/profile.d/jdk.sh if [[ ${USE_ALTERNATIVES:-0} 1 ]]; then alternatives --install /usr/bin/java java $BASE_DIR/jdk/bin/java 20000 || true alternatives --install /usr/bin/javac javac $BASE_DIR/jdk/bin/javac 20000 || true fi }find里用\( ... \)把两个-name括起来否则-o的优先级会让-maxdepth只作用于前半段。-print -quit找到第一个就退出不接head避免pipefail误伤。rm -rf $BASE_DIR/jdk只在首次安装或明确重装时执行升级前要先停 Java 服务如果$BASE_DIR/jdk是软链rm -rf删的是链接本身不会删目标目录但脚本里仍建议先readlink -f确认。/etc/profile.d/jdk.sh只对登录 shell 生效systemd 启动的服务不读它。因此 Java 服务自己的 unit 里要显式写EnvironmentJAVA_HOME/opt/apps/jdk或者启动命令用绝对路径。USE_ALTERNATIVES1时/usr/bin/java会指向alternatives的链接和PATH里的$JAVA_HOME/bin/java可能不是同一个文件排查时用readlink -f $(command -v java)看最终指向。注意不要同时用alternatives和发行版自带的 JDK 包管理。系统里如果已经有/usr/lib/jvm下的 JDK先把旧的服务配置改掉再切换新入口否则重启后又回到旧版本。3.2 Nginx 编译参数--prefix、--user 与临时目录Nginx 用源码编译目的是控制安装路径和模块不依赖发行版仓库的默认配置。编译参数不需要堆满按常见 Web 场景保留 SSL、真实 IP、gzip 静态压缩和状态页即可。--prefix决定配置、日志、PID 文件的默认路径后面 systemd unit 里的PIDFile要和它对齐。install_nginx() { local pkg build src pkg$(find $PKG_DIR -maxdepth 1 -name nginx-*.tar.gz -print -quit) [[ -n $pkg ]] || { echo 缺少 Nginx 源码包 2; return 1; } build$BASE_DIR/.nginx-build.$$ rm -rf $build; mkdir -p $build tar -xf $pkg -C $build src$(find $build -maxdepth 1 -mindepth 1 -type d -name nginx-* -print -quit) ( cd $src ./configure \ --prefix$BASE_DIR/nginx \ --user$RUN_USER --group$RUN_USER \ --with-http_ssl_module \ --with-http_realip_module \ --with-http_gzip_static_module \ --with-http_stub_status_module \ --with-cc-opt-O2 \ make -j$(nproc) \ make install ) install -d -m 0755 $BASE_DIR/nginx/conf/conf.d install -d -m 0750 -o $RUN_USER -g $RUN_USER $LOG_DIR/nginx chown -R root:root $BASE_DIR/nginx $BASE_DIR/nginx/sbin/nginx -t }--prefix是安装根目录--user和--group是 worker 进程身份本文还有配套的精品资源点击获取