ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Linux 一键部署 JDK、MySQL、Redis、Nginx 实操与避坑

Linux 一键部署 JDK、MySQL、Redis、Nginx 实操与避坑

简介:面向Linux运维、后端开发及刚接触服务器部署的读者,这套“Linux一键式安装JDK、Mysql、Redis、Nginx”资料围绕Web服务常用基础组件的自动化部署展开,旨在减少逐项下载、编译、配置环境变量和处理依赖时的重复劳动。包内共27个文件,以11个sh安装与启动脚本、9个gz源码/依赖压缩包为主,并配有cnf、conf配置样例、rpm依赖包及tar归档,压缩包约930.56MB;内容涵盖JDK 8u201、MySQL 8.0.30、Redis 6.2.4与Nginx 1.20.1等版本,同时纳入gcc、net-tools、libaio、perl、zlib、openssl、pcre等编译运行依赖。已有299人学习下载,说明其在同类部署场景中具有一定参考价值。借助init.conf总控脚本、各组件安装脚本、systemd服务脚本、Nginx主配置、MySQL密码重置脚本等,读者可梳理一键部署的脚本组织方式、组件配置入口、依赖安装顺序与服务管理思路,适合用来搭建本地或测试环境,并作为排错与二次改写的参考。

1. 一台空机器摆在面前:先想清楚“一键”到底省了哪一步

一台刚交付的 Linux 测试机,JDK、MySQL、Redis、Nginx 四样东西全要装,谁都想一条命令跑完就收工。真上手才明白,Linux 一键式安装 JDK、MySQL、Redis、Nginx 的难点从来不在敲命令,而在版本组合和装完之后的收尾:MySQL 8.0 默认的认证插件能把老客户端挡在门外,Redis 改完 bind 忘了 protected-mode 就连不上,Nginx 少编一个 ssl 模块,后面上证书还得从头再来一遍。这套环境几乎是 Java 后端机器的标配,装顺了一小时收工,装拧了能折腾一整天,最后还得删目录重来。下面按我实际落地一遍的顺序写:先把版本和环境卡死,再逐个组件装、配、托管成服务,最后给一套能一次验证四个服务都活着的自检方法。适合手上正有一台空机器、需要把 Java 运行环境一次铺好的人。

2. 落地前把环境清单定死:四个组件的版本与资源占用

装之前花二十分钟把版本、目录、账号定下来,比装到一半改主意省事得多。一键脚本最怕的不是写错命令,而是每台机器上跑出来的结果不一样——这台是 JDK 8 配 MySQL 5.7,那台是 JDK 17 配 MySQL 8.0,同一份脚本迟早会出问题。所以我的习惯是先把组合写进脚本头部的变量区,后面所有步骤只引用变量,不写死路径和版本号。

2.1 版本组合表:保守和激进各留一套

版本不是越新越好,尤其是这台机器上还要跑别人的 jar 包和旧客户端的时候。我一般准备两套组合,按业务侧的实际编译目标来挑:

组件保守选型激进选型主要差异与影响
JDK8u 系列17 LTS17 移除了 Nashorn、默认强封装 JDK 内部 API,老框架反射调用容易直接报错
MySQL5.78.08.0 默认caching_sha2_password、默认 utf8mb4,老驱动连不上是高频问题
Redis6.27.x7.x 引入了多线程 IO,老客户端对 RESP3 的兼容性要提前确认
Nginx1.201.24新版对部分老第三方模块的支持滞后,编译前先确认模块清单

挑版本的判断标准其实只有一条:这台机器上跑的代码是谁编译的。业务方给的是 JDK 8 打的 war 包,就别为了“新”硬上 17,回头 class 版本号对不上(UnsupportedClassVersionError)还得回退。MySQL 同理,客户端驱动版本低于 8.0 的话,要么驱动跟着升级,要么在 my.cnf 里显式把认证插件降级,二者选一个,别装作看不见。

Redis 6.2 和 7.x 的差别在日常使用中几乎感知不到,但 7.x 编译对 GCC 版本要求更高,CentOS 7 自带的 gcc 4.8 编 7.x 会报一串 C11 相关的错,这种时候要么升级编译器,要么老老实实退回 6.2。Nginx 我倾向选一个编译参数能覆盖全部需求的稳定分支,而不是追最新——线上换个版本重编一次,风险比省下的那点性能大得多。

2.2 系统基线检查:四个命令排除一半的坑

很多“装不上”的问题,其实在装之前就已经注定了。花一分钟把这几个数打出来,能省掉后面大量返工:

# 内存:MySQL 8.0 空跑就要 400MB 上下,加上 Redis 和 JVM,2G 机器要谨慎 free -m # 磁盘:安装目录和数据目录分开看,MySQL 数据目录后期涨得最快 df -h /opt /data /var # 位数:32 位机器直接放弃,MySQL 8.0 根本不提供 32 位包 getconf LONG_BIT # 发行版:决定后面用 yum 还是 apt,别混着来 cat /etc/os-release # 端口占用:3306、6379、80、8080 有没有被系统自带的旧服务占着 ss -lntp | grep -E ':(3306|6379|80|8080)\s'

free -m看的是 available 那一列而不是 free,Linux 会把空闲内存拿去做缓存,free 小是正常的。ss -lntp这一步特别值得单独跑,很多精简镜像自带 mariadb-libs 甚至一个跑着的 mariadb 实例,3306 被占着,MySQL 初始化完起不来,日志里只有一句含糊的启动失败。

时间和时区也顺手确认一下:

timedatectl set-timezone Asia/Shanghai timedatectl set-ntp true

时间不同步在单机上顶多让日志时间对不上,但一旦后面要做主从或者接口签名校验,几秒的偏差就能让人查一下午。时区不对更常见——MySQL 里now()出来的时间差八小时,业务侧会以为是代码问题。

2.3 目录与账号规划:让脚本可以重复跑第二遍

一键脚本的价值有一半在于“能重跑”。要做到这点,所有路径必须是幂等的:目录存在就跳过,用户存在就复用,配置文件覆盖前先备份。我一般按下面这套结构铺:

# 统一的运行账号,Redis 和 Nginx 用它跑,不用 root groupadd -r app 2>/dev/null useradd -r -g app -s /sbin/nologin app 2>/dev/null # 安装目录和数据目录分离,便于以后单独扩容或迁移 mkdir -p /opt/{jdk,mysql,redis,nginx} \ /data/{mysql,redis,nginx} \ /var/log/app \ /usr/local/src # 数据目录和日志目录归 app,安装目录保持 root,防止误改 chown -R app:app /data /var/log/app chmod 750 /data/mysql /data/redis

这里有个取舍:MySQL 官方建议用独立的 mysql 用户,但小规模环境下统一用一个 app 账号更好管,权限边界靠目录权限控制就够了。真要上生产,还是把 MySQL 单独拆出去,mysqld --initialize --user=mysql那一步会自己建用户,不需要手工加。

文件描述符限制必须提前放开,否则 Redis 高并发下会报Too many open files,Nginx 也会在上量之后才开始报错:

cat > /etc/security/limits.d/app.conf <<'EOF' app soft nofile 65535 app hard nofile 65535 app soft nproc 65535 app hard nproc 65535 EOF

注意这个文件只对通过 PAM 登录的会话生效,systemd 托管的服务读不到,所以后面写 unit 文件时还要单独加一行LimitNOFILE=65535,两处都写才不会漏。

3. 主流程上篇:JDK 解包与 MySQL 静默初始化的具体参数

环境铺好之后就是真正动手。这一段的顺序有讲究:JDK 先装,因为后面如果要用到 Java 写的管理工具;MySQL 放在 Redis 和 Nginx 前面,是因为它初始化最慢、最容易被别的问题干扰。每一步我都尽量写成“命令 + 之后要检查什么”的形式,而不是一路回车到底。

3.1 JDK 解包与环境变量:只写三行,写在 /etc/profile.d

JDK 本身没什么安装过程,解压、指路径、验证三步。真正容易翻车的是环境变量写在哪:

# --strip-components=1 去掉压缩包里那层版本目录,后面路径就不用带版本号 tar -zxvf jdk-8uXXX-linux-x64.tar.gz -C /opt/jdk --strip-components=1 cat > /etc/profile.d/jdk.sh <<'EOF' export JAVA_HOME=/opt/jdk export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export PATH=$JAVA_HOME/bin:$PATH EOF chmod +x /etc/profile.d/jdk.sh source /etc/profile.d/jdk.sh java -version

写在/etc/profile.d/而不是~/.bashrc,是因为 systemd 启动的服务不会加载用户的 bashrc,只靠.bashrc配的 JAVA_HOME,在命令行里跑得好好的,一旦做成服务就报JAVA_HOME not found。这个坑我踩过一次,排查了半天才发现是启动上下文不同。

--strip-components=1也值得说一下:JDK 压缩包解开后默认是jdk1.8.0_xxx/这么一层目录,如果不剥掉,JAVA_HOME就得写成/opt/jdk/jdk1.8.0_xxx,以后升个小版本还得改所有引用它的脚本。剥掉之后/opt/jdk就是 JDK 根目录,升级时只要把软链或者目录内容换掉,配置一行不用动。

验证别只看java -version,顺手把javac -version也跑一遍:

javac -version 2>&1 echo $JAVA_HOME

有些精简版 JDK 包只有 JRE 没有编译器,java -version正常,javac找不到,等到要编译点东西才发现。另外 JDK 17 之后tools.jar已经不存在了,CLASSPATH 那一行可以省掉,写了也不报错,但没必要留着误导后人。

3.2 MySQL 的 my.cnf:小内存机器先改这五个参数

MySQL 的配置文件决定了它能不能在这台机器上安稳跑起来。默认配置是按“你有很多内存”设计的,2 核 4G 的测试机上直接照搬,很容易 OOM。我一般从下面这份精简版起步:

[mysqld] basedir=/opt/mysql datadir=/data/mysql socket=/data/mysql/mysql.sock port=3306 user=app # 字符集:8.0 默认就是 utf8mb4,5.7 必须显式写,否则中文表情符号存不进去 character-set-server=utf8mb4 collation-server=utf8mb4_general_ci # 时区:不写就是 UTC,业务查出来的时间差八小时 default-time-zone='+08:00' # 连接数与缓冲池:4G 机器给 512M 起步,独占机器可以给到物理内存的 50%~60% max_connections=300 innodb_buffer_pool_size=512M innodb_log_file_size=128M # 表名大小写:8.0 只能在初始化时定,装完再改会起不来 lower_case_table_names=1 log-error=/var/log/app/mysql-error.log slow_query_log=1 long_query_time=2

innodb_buffer_pool_size是最值得调的一个参数,它直接决定热数据能不能留在内存里。测试机上给 512M 够用,给太大反而会和 Redis、JVM 抢内存,导致系统开始 swap,性能断崖式下跌。

lower_case_table_names是另一个需要在动手前想清楚的点。很多从 Windows 迁移过来的项目,SQL 里表名大小写混着写,Linux 上默认区分大小写就会报表不存在。这个参数在 MySQL 8.0 里只能在初始化时指定,初始化完成后再改,mysqld会直接拒绝启动并提示不允许。所以要么一开始就加在 my.cnf 里,要么就别动,别装完再回头折腾。

default-time-zone用'+08:00'这种偏移写法最稳,写'Asia/Shanghai'依赖系统时区表,精简镜像里经常没装,启动时报Unknown or incorrect time zone。

3.3 初始化与改密码:临时密码、socket 登录、远程授权

配置写完就可以初始化数据目录了。注意这一步只跑一次,跑完再去改 my.cnf 里的字符集、大小写这些,就来不及了:

# 初始化,临时密码会写进 error log,不会打印在终端 /opt/mysql/bin/mysqld --defaults-file=/etc/my.cnf --initialize --user=app # 从日志里捞临时密码 grep -i 'temporary password' /var/log/app/mysql-error.log

--initialize和--initialize-insecure的区别要分清楚:前者生成随机临时密码,后者生成空密码的 root。测试环境图省事可以用后者,但初始化完必须立刻设密码,否则本机任何用户都能无密码进库,这是个实打实的风险点。

第一次登录得用 socket 方式,因为此时 root 只允许从 localhost 走 socket 连:

/opt/mysql/bin/mysql -uroot -p -S /data/mysql/mysql.sock

进去之后三件事:改 root 密码、建业务账号、按需降级认证插件:

ALTER USER 'root'@'localhost' IDENTIFIED BY '换成你的强密码'; -- 业务账号给到具体库,别图省事给 *.* CREATE USER 'appuser'@'%' IDENTIFIED BY '换成你的业务密码'; GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'%'; FLUSH PRIVILEGES; -- 老客户端连 8.0 报 "Authentication plugin 'caching_sha2_password' cannot be loaded" 时用这句 ALTER USER 'appuser'@'%' IDENTIFIED WITH mysql_native_password BY '换成你的业务密码';

最后那句是 8.0 最经典的兼容问题。老版本的 JDBC 驱动、老版本的 Navicat 类客户端,遇到caching_sha2_password直接报错,很多人第一反应是换客户端,其实是认证插件不匹配。要么升级驱动,要么把账号降级成mysql_native_password,两条路都行,就是别在那儿反复试密码。

远程访问打不通的时候,先确认三件事的先后顺序:账号的 host 是不是%、bind-address有没有限制成 127.0.0.1、防火墙有没有放行 3306。三处任意一处没配对,表现都是“连接超时”,但原因完全不同。

4. 主流程下篇:Redis 编译、Nginx 编译参数与 systemd 托管

MySQL 起来之后,剩下三个组件的节奏就快了。Redis 和 Nginx 都走源码编译,好处是版本和模块自己说了算,坏处是依赖得自己补齐,编译参数漏一个后面就得重编。

4.1 Redis:make 之前先想好,改完这四行再启动

Redis 编译本身不复杂,麻烦的是编译环境和启动配置:

cd /usr/local/src tar -zxvf redis-6.2.x.tar.gz cd redis-6.2.x # -j 按核数并行,编译快很多;报错先 make distclean 再重来 make -j$(nproc) make PREFIX=/opt/redis install # 配置文件单独放,别用源码目录里的那份 mkdir -p /opt/redis/etc cp redis.conf /opt/redis/etc/redis.conf

make PREFIX=/opt/redis install会把redis-server、redis-cli这些可执行文件装到/opt/redis/bin,源码目录里的东西就可以留着不管了。如果编译时报jemalloc/jemalloc.h: No such file,八成是之前编译残留或者缺依赖,先make distclean,再确认gcc、make、jemalloc-devel都装了。

装完不要急着启动,redis.conf里至少四行要改:

# 绑定内网地址而不是 0.0.0.0;只给本机用就写 127.0.0.1 bind 127.0.0.1 # 和 bind 配套,两个一起改才有效 protected-mode yes # 交给 systemd 托管,这里必须 no,否则 systemd 会以为进程退出了 daemonize no # 数据落盘目录和持久化 dir /data/redis appendonly yes appendfsync everysec # 密码和内存上限,两个都要设 requirepass 换成你的强密码 maxmemory 512mb maxmemory-policy allkeys-lru

bind和protected-mode必须成对考虑:只改bind 0.0.0.0不改密码,等于把库敞开;只设密码不改 bind,本地能连远程连不上,会误以为是密码错了。maxmemory不设的话,Redis 会一直吃内存直到把机器拖垮,allkeys-lru在纯缓存场景下是合理选择,如果这个 Redis 里存的是不能丢的数据,那就该换成noeviction并盯着内存告警。

daemonize no是配合 systemd 的关键。很多人习惯改成yes让 Redis 自己后台化,结果 systemd 启动后立刻判定服务已退出,systemctl status显示 failed,实际上进程还在跑,这种情况最容易让人怀疑人生。

4.2 Nginx:编译参数漏一个,后面就得整台重来

Nginx 的编译参数是这套环境里最值得提前想清楚的一环,因为装完再补模块只能重新编译:

cd /usr/local/src/nginx-1.24.x ./configure \ --prefix=/opt/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_gzip_static_module \ --with-http_realip_module \ --with-stream \ --with-pcre make -j$(nproc) && make install

--with-http_ssl_module默认是不编进去的,等要上 HTTPS 才发现nginx -V里没有这个模块,就只能重来。--with-http_realip_module在走反向代理时用来拿真实客户端 IP,不加的话日志里全是上游地址,排查问题时会很难受。--with-stream是四层转发,做数据库或 Redis 的 TCP 代理时需要。

./configure阶段报the HTTP rewrite module requires the PCRE library,是缺pcre-devel;报 zlib 相关的是缺zlib-devel;报 SSL 相关的是缺openssl-devel。三条一起装最省事:

yum install -y gcc gcc-c++ make pcre pcre-devel zlib zlib-devel openssl openssl-devel

配置文件我一般拆成主配置和站点配置两部分,主配置只管全局,站点配置放conf.d:

worker_processes auto; events { worker_connections 10240; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; include /opt/nginx/conf/conf.d/*.conf; }

站点配置里最常写的是反向代理到后端 Java 服务:

server { listen 80; server_name _; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

worker_processes auto按 CPU 核数自动设置,比手写数字靠谱。X-Forwarded-For那两行不加,后端拿到的客户端 IP 永远是 127.0.0.1,做限流或者审计的时候会完全失效。

4.3 systemd 托管:四个 unit 文件与开机自启

编译安装的东西默认不带服务管理,全靠手工nohup启动,机器一重启就全没了。补上 unit 文件才算真正装完:

# /etc/systemd/system/redis.service [Unit] Description=Redis After=network.target [Service] User=app ExecStart=/opt/redis/bin/redis-server /opt/redis/etc/redis.conf Restart=always LimitNOFILE=65535 [Install] WantedBy=multi-user.target

MySQL 的 unit 稍微多一点,重点是ExecStart要带--defaults-file,否则它会去读默认路径的配置:

# /etc/systemd/system/mysqld.service [Unit] Description=MySQL After=network.target [Service] User=app ExecStart=/opt/mysql/bin/mysqld --defaults-file=/etc/my.cnf Restart=on-failure LimitNOFILE=65535 [Install] WantedBy=multi-user.target

Nginx 的ExecStart用/opt/nginx/sbin/nginx -g 'daemon off;',-g 'daemon off;'同样是给 systemd 用的,不加会以后台模式启动然后被判定为退出。

四个 unit 写完之后统一重载并设开机自启:

systemctl daemon-reload systemctl enable --now mysqld redis nginx systemctl status mysqld redis nginx --no-pager

enable --now一次搞定开机自启和立即启动,比enable完再start少一步。Restart=always和on-failure的差别在于:前者无论怎么退出都拉起来,后者只在非零退出码时拉。Redis 用always更稳,MySQL 用on-failure,避免配置写错时无限重启刷日志。

5. 避坑与排查:一键脚本最容易翻车的五个地方

这套流程我跑过不下十遍,报错信息翻来覆去就是那几条。下面五条按“现象、原因、解决”记,碰到了直接对号入座。

5.1 端口与网络层:本机通、外面不通

现象:机器上curl 127.0.0.1:8080正常返回,从另一台机器访问超时,telnet也不通。

原因:多半是两层防火墙只开了一层。系统层的 firewalld 放行了,云主机的安全组没放;或者反过来,安全组开了,firewalld 还拦着。

解决:两边都查一遍,别只看一边。

firewall-cmd --list-ports firewall-cmd --permanent --add-port=3306/tcp --add-port=6379/tcp --add-port=80/tcp firewall-cmd --reload

现象:MySQL 初始化成功,systemctl start mysqld反复失败,日志里只有一句Address already in use。

原因:系统自带 mariadb-libs 或者一个已经在跑的 mariadb 实例占着 3306,rpm -qa | grep -i maria一看就清楚。

解决:卸载或者改端口。改端口更快,但要记得同步改业务侧的连接串。

rpm -qa | grep -i maria systemctl stop mariadb 2>/dev/null sed -i 's/^port=3306/port=3306/' /etc/my.cnf

5.2 权限与安全模块:配置文件对了还是 403

现象:Nginx 起来了,访问静态文件返回 403,错误日志写Permission denied,但文件权限明明是 644。

原因:SELinux 在 enforcing 模式,目录安全上下文不对。getenforce一查就是Enforcing。

解决:内网测试环境可以临时关掉,正式环境建议改上下文而不是直接关。

getenforce setenforce 0 # 临时生效,重启失效 chcon -R -t httpd_sys_content_t /opt/nginx/html # 或者只改上下文

现象:Redis 启动报Can't open the log file或者Permission denied,配置文件明明写对了。

原因:/data/redis属主还是 root,而服务是以 app 用户跑的。编译安装的时候习惯用 root,容易漏掉这一步。

解决:把数据目录和日志目录的属主改成运行账号,一次改彻底。

chown -R app:app /data/redis /var/log/app ls -ld /data/redis

5.3 依赖与编译:make 阶段的报错最费时间

现象:Redismake报jemalloc/jemalloc.h: No such file or directory。

原因:源码目录里有上次编译的残留,或者缺jemalloc-devel。残留这种情况更常见,尤其在同一目录反复折腾的时候。

解决:

make distclean yum install -y gcc gcc-c++ make jemalloc-devel make -j$(nproc)

现象:Nginx./configure报the HTTP rewrite module requires the PCRE library或SSL modules require the OpenSSL library。

原因:只装了运行库没装开发包。pcre和pcre-devel是两个包,前者是运行时,后者才提供头文件。

解决:编译依赖一次性装齐,别一个一个试。

yum install -y pcre pcre-devel zlib zlib-devel openssl openssl-devel

还有一条不在上面五条里但值得单独记:MySQL 8.0 装完之后想改lower_case_table_names,systemctl restart直接失败,日志提示只能在初始化时设置。这个没有后悔药,只能删数据目录重来,所以初始化之前一定要把 my.cnf 定稿。

6. 装完怎么验证:一次把四个服务都叫醒再重启一遍

装完不验证,等于没装。我的习惯是写一个固定的小脚本,每次都跑同一套检查,输出全绿才算收工。散着一条条试太容易漏,尤其是 Redis 那种“启动了但没真正可用”的情况。

6.1 四项连通性自检

#!/bin/bash # 端口层:四个服务该监听的端口都在 for p in 3306 6379 80; do ss -lntp | grep -q ":$p " && echo "PORT $p OK" || echo "PORT $p FAIL" done # 进程层:JDK 看 java,其余看 systemd 状态 java -version 2>&1 | head -1 systemctl is-active mysqld redis nginx # 应用层:真正连一下,比看端口靠谱 /opt/mysql/bin/mysql -h127.0.0.1 -P3306 -uappuser -p"$MYSQL_PWD" -e 'select version();' /opt/redis/bin/redis-cli -h 127.0.0.1 -a "$REDIS_PWD" ping curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/

端口检查只能证明进程在监听,证明不了能干活。MySQL 端口开着但账号密码错、Redis 端口开着但requirepass没对上,这两种情况都得靠应用层那三条才能真正暴露出来。redis-cli返回PONG才算通,返回NOAUTH说明密码没传对,返回Connection refused才是服务没起来,三种输出对应三种完全不同的处理方向。

对一下预期结果:

检查项命令预期输出
JDKjava -version版本号,且与脚本里定的版本一致
MySQLselect version();版本号,能查出结果说明账号权限正常
Redisredis-cli pingPONG
Nginxcurl -I 127.0.0.1200或403(说明服务活着,只是没首页)

6.2 最容易漏的一步:重启一次

所有服务都起来之后,别急着交付。systemctl restart只能验证重启命令,验证不了开机自启。真正该做的是重启整台机器,然后再跑一遍上面的自检:

reboot # 等机器起来后重跑自检脚本,四项全绿才算完成

这一步能抓出两类问题:一是 unit 文件里After=依赖写错,MySQL 比网络先起来导致启动失败;二是环境变量只写在.bashrc里,重启后 systemd 的服务根本读不到JAVA_HOME。这两类问题不重启永远发现不了,而它们恰好会在某次机房维护之后集中爆发。

从那以后我每次铺完这套环境,都强制走一遍「自检脚本 + 真重启 + 再自检」的流程,不给自己留“应该是好的”这种侥幸。装环境这件事,能一次跑通不算本事,能扛住一次重启才算。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表