1. 部署前的整体思路别急着买服务器先把方案定下来做后端开发这几年我见过太多人把SpringBoot项目本地跑得好好的一上服务器就各种翻车。其实大部分问题不是因为技术多难而是部署前没想清楚几个核心问题上来就瞎搞最后被环境、版本、权限这些基础问题折腾到怀疑人生。这个系列的第一篇我不打算讲太花哨的东西就聚焦在“如何把一个SpringBoot项目稳稳当当跑在云服务器上”这件最基础的事。我会从方案选型开始带你梳理整个部署链路然后一步步实操把打包、上传、启动、守护进程、日志排查这些环节全部走一遍。适合刚接触服务器部署的同学也适合那些部署过几次但总遇到奇怪问题的朋友对照排查。在开始之前先把几个关键选择说清楚。1.1 部署方式选型jar包直接跑还是用Docker现在部署SpringBoot应用主流方案基本就两条路直接跑jar包或者用Docker容器化。直接跑jar包是最朴素的方式你的服务器上装好JDK环境把打包出来的jar传上去java -jar一下就起来了。这种方式的好处是简单直接没有额外学习成本一台机器部署一个小项目完全够用而且排查问题的时候特别直观日志、进程、内存情况一眼就能看到。Docker方式则是把应用连同环境一起打包成镜像好处是环境一致性极强换机器部署几乎零成本扩容也方便。但代价是你需要额外掌握Docker、Docker Compose这些工具对新手来说又多了一层需要理解的概念。我个人的建议是如果你刚接触部署或者项目规模不大第一篇文章先用最直接的jar包方式把整个流程跑通。先搞清楚一台服务器上到底需要哪些东西、应用是怎么跑起来的再去接触容器化你会理解得更透彻。这个系列的第一篇我就以最经典的jar包部署为主线来展开。1.2 服务器环境规划先想清楚要装什么一台全新的云服务器拿到手的时候基本是“毛坯房”需要你自己装修。在动手之前先把环境清单列出来避免装到一半发现缺东西。对于跑一个最基础的SpringBoot项目你最少需要准备这些东西操作系统主流云厂商都提供多个镜像可选如果没有特殊要求优先选CentOS 7.9或Ubuntu 20.04以上版本。这两个系统用户基数大遇到问题搜解决方案最容易命中。JDKSpringBoot 2.x推荐用JDK 8或JDK 11SpringBoot 3.x则需要JDK 17起步。先确认你本地开发用的JDK版本服务器上尽量保持一致避免本地编译目标版本高于服务器运行版本导致的兼容问题。构建工具Maven。虽然打包也可以在本地做但服务器上装一个Maven有时候排查问题时更方便比如手动触发打包或跑测试。如果你完全依赖本地打包上传那服务器上这一步可以省略。数据库与中间件如果你的项目用了MySQL、Redis这些需要考虑是装在同一个服务器上还是使用云厂商提供的托管服务。初期图省事全部装在同一台机器上问题不大但要注意服务器内存分配。Nginx可选如果项目前后端分离通常需要Nginx来做静态资源服务和反向代理。这一篇先不展开后面单独讲。把这些东西理清楚了后面每一步操作就都有目标了。2. 服务器初始化五步走安全组、系统更新、目录规划服务器买好以后第一步不是急着装JDK而是先把服务器本身“收拾干净”。这一步做得好后面能省掉很多坑。2.1 安全组配置最容易被忽略的第一道门槛云服务器不像你本地虚拟机它是有一道“隐形防火墙”的也就是云厂商控制台里的安全组规则。很多人刚买完服务器SSH连不上、应用访问不了第一反应就是系统防火墙问题其实往往是被安全组挡在了外面。安全组的逻辑很简单你需要在云厂商控制台里明确放行哪些端口允许外部访问。对于Web应用至少需要放行这几个端口22端口SSH远程连接端口建议不要改成特别冷门的端口否则容易把自己锁在外面。8080端口SpringBoot应用默认的启动端口如果你后面改了端口这里要对应调整。3306端口可选如果需要从外部连接MySQL才需要放行。生产环境建议数据库只允许内网访问不要直接暴露到公网。操作入口一般在云厂商控制台的“实例管理”里找到“安全组”点进去后配置入方向规则。源地址可以选择0.0.0.0/0表示允许所有IP访问也可以限定特定IP段后者更安全。我在实际部署中发现很多人把安全组和系统防火墙比如firewalld或ufw搞混。这两者是叠加关系外部流量必须先通过安全组再到达系统防火墙。如果安全组没放行系统防火墙就算全开着也没用反过来也一样。所以排查网络问题时要记得两层都要检查。2.2 系统更新与基础工具安装新服务器拿到后先做一次系统更新把内核和软件包补丁打上这一步很有必要尤其是长时间没有使用过的旧镜像。以CentOS为例登录服务器后先执行sudo yum update -y如果是Ubuntu系统对应命令是sudo apt update sudo apt upgrade -y更新的过程可能需要几分钟取决于当前镜像的滞后程度。更新完成后顺手把一些基础工具装上后面操作会顺手很多sudo yum install -y wget curl vim net-tools lrzszlrzsz这个工具强烈推荐装一下它能让你通过SSH工具直接上传下载文件实测在传小文件的时候比SFTP直观很多。后面上传jar包的时候你就知道它有多方便了。2.3 创建专用部署账号很多新手习惯直接用root用户跑应用这个做法我强烈建议改掉。root权限太大一旦应用被攻破或者误操作后果是整个服务器遭殃。更稳妥的做法是创建一个专用的部署账号只给它需要的权限。# 创建用户比如叫 deploy sudo useradd deploy # 设置密码 sudo passwd deploy # 给 deploy 用户授权 sudo 权限注意仅限于管理操作 sudo usermod -aG wheel deploy # CentOS系统 # Ubuntu系统则执行sudo usermod -aG sudo deploy后面我们就把应用放在这个用户的家目录下运行运行身份也是它这样即使应用出现安全漏洞攻击者拿到的也只是普通用户权限而不是root。2.4 规划目录结构目录规划看似不起眼但项目多了以后你会发现当初随手放的文件找起来能让人崩溃。我自己的习惯是固定一套标准目录结构/home/deploy/ ├── apps/ # 存放各个应用的jar包 │ └── demo/ ├── logs/ # 统一存放应用日志 │ └── demo/ └── scripts/ # 启动、停止、备份脚本创建好这套目录结构后后面所有项目都遵循同样的规则。好处很明显换一台服务器或者接手别人的服务器只要看到这个目录结构就一目了然不需要再浪费时间去猜某个文件放在哪里。2.5 JDK安装与版本确认JDK的安装方式有几种直接用包管理器装、下载官方tar包解压、或者用openjdk。我推荐用包管理器安装简单省事而且升级维护也方便。CentOS下执行sudo yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-develUbuntu下执行sudo apt install -y openjdk-8-jdk装完之后确认版本java -version javac -version注意javac来自-devel包很多人只装了运行环境没装开发包结果后面用Maven编译时报错找不到编译器。我曾经在一台服务器上折腾了整整一下午Maven打包一直报错后来才发现是只装了JRE没装JDK。这个经验提醒我每次配置完环境一定要确认javac -version能正常输出再做下一步。3. SpringBoot项目打包配置本地多花十分钟服务器少踩两小时坑环境准备好了以后接下来就要回到项目本身了。SpringBoot项目打包这块有几个细节如果不在本地提前处理好上传到服务器跑起来就会各种报错。3.1 多环境配置文件拆分几乎每个项目都会有本地、测试、生产至少三套环境配置。如果所有环境共用一个application.yml你部署前就需要手动改数据库地址、Redis地址、日志级别这些参数改错了直接连不上库。正确做法是用application-{profile}.yml的方式拆分配置文件再通过spring.profiles.active指定当前激活的环境。比如说你的项目结构里应该有src/main/resources/ ├── application.yml # 公共配置 ├── application-dev.yml # 本地开发环境 └── application-prod.yml # 生产环境application.yml中指定公共配置比如应用名、端口号再加一行关键配置spring: profiles: active: prod然后在application-prod.yml里写上生产环境的数据库地址、账号密码、日志配置等。这样打包出来的jar启动时就自动加载生产配置不需要再人工介入。我在实际项目里经常看到有人把数据库账号密码直接明文写在配置文件里这个习惯很危险。如果你用的是云数据库建议把密码通过环境变量的方式注入或者用配置中心统一管理。这一篇先不展开但一定要有这个意识。3.2 Maven打包参数与普通打包的区别SpringBoot项目的标准打包方式是执行mvn clean package -DskipTests有些人会疑惑mvn package和mvn install有什么区别在这条部署链路里我们只是要得到一个可运行的jar包package阶段就够了。install会把jar安装到本地Maven仓库那是供其他本地模块依赖用的部署场景不需要。-DskipTests的意思是跳过测试用例的执行但会编译测试代码。如果你连测试代码编译都嫌慢可以用-Dmaven.test.skiptrue这个参数会连同测试代码编译一起跳过打包速度更快。注意区分这两个参数。打包完成后jar包在target目录下。注意看文件名后缀是什么SpringBoot Maven插件默认会生成两个jar一个是原始jar包名字以.jar.original结尾另一个是可直接运行的瘦身jar包。运行SpringBoot应用必须使用那个可执行的fat jar而不是原始jar包。我见过有人手滑把.original文件改个名传上去结果怎么跑都报“没有主清单属性”其实就是用错了jar包。如果你感觉打出来的底层包体积比预期小很多也要检查一下是不是用错了文件。正常的SpringBoot fat jar通常有几十MB大小包含了所有依赖如果只有几KB那肯定不对。这里的原理是Maven插件在打包时把依赖库全部打进了可执行jar里所以就让它体积偏大这也是它开箱即用的原因。3.3 本地验证打包后先在本机跑一遍上传服务器之前先在本地模拟一下运行环境能过滤掉一大批低级问题。java -jar target/demo-0.0.1-SNAPSHOT.jar观察启动日志重点确认三件事应用是否正常启动没有报错堆栈监听的端口是不是你期望的端口默认8080数据库、Redis等依赖组件是否连接成功确认没问题后CtrlC停掉再执行一次本地请求测试curl http://localhost:8080/api/hello能正常返回结果再继续下一步。这一步如果省略很有可能你打包出来的jar在本地都跑不起来却已经在服务器上折腾半天环境了。4. 上传、启动与守护进程让SpringBoot在服务器上稳稳运行到了这一节前面准备的所有东西就派上用场了。这个部分也是整个部署链路里“手感”最强烈的环节我把每一步都拆细了来讲。4.1 上传jar包到服务器两种方式实测对比jar包上传到服务器常用方式有两种方式一lrzsz工具直接上传在本地终端如果你用的SSH工具是SecureCRT或Xshell进入服务器后执行rz会弹出一个文件选择窗口选中本地jar包确认即可上传路径默认是你当前所在的目录。这个方式适合文件不太大几十MB没问题的场景胜在直观。但要注意rz上传大文件时如果网络不稳容易中断而且中断后没有续传功能。如果你用的是Ubuntu系统且没有装lrzsz先执行安装命令。方式二scp命令上传scp更适合在本地终端直接操作也是我更常用的方式。在本地命令行执行scp /path/to/demo.jar deploy服务器IP:/home/deploy/apps/demo/如果SSH端口不是默认的22则需要加上-P参数指定端口scp -P 2222 /path/to/demo.jar deploy服务器IP:/home/deploy/apps/demo/上传完成后到服务器上确认一下文件大小是否和本地一致。这一步不是多余的。我遇到过网络传输中断但scp没有报错的情况导致jar包不完整启动时各种莫名其妙的问题最后MD5比对发现和本地文件完全不一样才定位到问题。4.2 前台启动测试先确认能跑起来再谈守护我始终认为第一次启动应用时不要直接配守护进程先用最简单的方式前台跑一遍确认能起来再说后面的事。cd /home/deploy/apps/demo/ java -jar demo-0.0.1-SNAPSHOT.jar观察启动日志。如果正常你会看到类似这样的关键日志Tomcat started on port(s): 8080 (http) Started DemoApplication in 4.321 seconds如果启动报错常见错误如下注意看堆栈来源数据库连接失败Communications link failure或Connection refused——检查数据库地址、端口、账号密码端口被占用Port 8080 was already in use——用netstat -tlnp | grep 8080找出占用进程配置加载失败No active profile set——检查spring.profiles.active配置确认能正常启动后先别急着关掉新开一个终端窗口用curl测试一下curl http://localhost:8080/api/hello返回正常结果后回到原来的终端按CtrlC停掉应用。这说明应用本身没问题接下来就可以配置守护进程了。4.3 systemd守护进程配置保证应用崩溃后自动拉起用java -jar前台启动的话只要SSH会话一断开应用就会跟着退出。这个时候就需要一个守护进程来管理应用的启动、停止、崩溃重启。Linux上最标准的方案就是systemd。在/etc/systemd/system/目录下创建一个服务配置文件命名规则一般是{应用名}.service[Unit] DescriptionDemo SpringBoot Application Afternetwork.target [Service] Typesimple Userdeploy Groupdeploy WorkingDirectory/home/deploy/apps/demo ExecStart/usr/bin/java -Xms256m -Xmx512m -jar demo-0.0.1-SNAPSHOT.jar ExecStop/bin/kill -s QUIT $MAINPID SuccessExitStatus143 Restartalways RestartSec10 [Install] WantedBymulti-user.target几个关键配置项解释一下User和Group指定应用运行的用户这里使用之前创建的deploy用户而不是root。WorkingDirectory应用的工作目录虽然很多情况下不是必须的但如果你在代码里使用了相对路径读取文件这个目录就是“基准点”最好固定它否则可能因为启动目录不同而找不到文件。ExecStart启动命令这里根据你自己的JDK路径调整。一般情况下/usr/bin/java是个软链接指向你安装的JDK真实路径。Restartalways无论什么原因退出都自动重启。这个配置很重要因为我见过不少应用因为内存不够被系统杀掉后没人发现直到用户来投诉才知道挂了。RestartSec10表示重启前等待10秒给系统一个缓冲时间。SuccessExitStatus143这个配置是因为SpringBoot应用接收SIGTERM信号kill -s QUIT时退出码是143如果不配置systemd会把这个退出码当作异常退出而误判为失败。配置编辑完成后先重新加载systemd配置sudo systemctl daemon-reload然后启动服务sudo systemctl start demo设置开机自启sudo systemctl enable demo查看运行状态sudo systemctl status demo可以看到服务的当前状态应该显示active (running)同时能看到进程号、运行用户、最新日志输出等信息非常直观。4.4 日志查看技巧别靠肉眼盯着终端应用以后台服务方式运行后日志输出不再出现在终端里而是由systemd管理。查看日志有几个常用命令查看最近200行日志并持续跟踪新日志sudo journalctl -u demo -n 200 -f查看指定时间段的日志sudo journalctl -u demo --since 10 minutes ago但systemd的日志是存内存的默认情况重启服务或服务器后历史日志可能会丢失。如果你需要保留完整的日志记录建议在application-prod.yml里配置日志输出到文件logging: file: name: /home/deploy/logs/demo/application.log或者更科学的做法是在日志配置里同时输出到控制台和文件方便实时看控制台日志的同时也能随时翻历史文件。另外SpringBoot默认的日志级别是INFO。如果某天排查问题需要看更详细的SQL语句或调试信息可以通过启动命令临时调整级别不需要重新打包sudo systemctl stop demo sudo systemctl edit demo在编辑窗口里添加[Service] EnvironmentLOGGING_LEVEL_ORG_APACHE_IBATISDEBUG不过这个方法改的是环境变量需要你的日志配置能读取到环境变量才有效。更省事的办法是启动时加参数覆盖但systemd服务的启动命令在ExecStart里改动后要daemon-reload。这些细节等真遇到了再灵活处理。对于查看日志这个小环节我的核心建议是先把journalctl的命令用熟然后在项目配置文件里把日志输出到固定文件。两套方案都掌握排查问题时自由度就大很多。5. 常见问题与排查技巧实录部署过程中遇到的问题五花八门这里把出现频率最高的几个拿出来单独讲都是我自己踩过的坑。5.1 端口无法从外部访问这是一个经典场景应用在服务器本地curl能通换到外部浏览器就访问不了。排查顺序如下# 第一步确认应用监听的是不是外部可见的地址 netstat -tlnp | grep 8080如果看到127.0.0.1:8080而对应的不是0.0.0.0:8080说明应用只监听了本地回环地址外部自然访问不了。SpringBoot默认监听0.0.0.0一般不会出现这个问题但如果你在配置里设置了server.address127.0.0.1就会踩中。# 第二步确认系统防火墙是否放行 sudo firewall-cmd --list-all如果防火墙开着需要放行8080端口sudo firewall-cmd --zonepublic --add-port8080/tcp --permanent sudo firewall-cmd --reload# 第三步确认云厂商安全组是否放行这一步需要登录云厂商控制台检查就是在2.1节说过的那个安全组配置。很多人在前两步排查了半天最后发现是安全组没加规则。5.2 Java进程莫名其妙消失应用跑着跑着进程不见了systemd日志里也看不出所以然。这种情况大概率是内存不足进程被系统的OOM Killer杀掉了。排查方法是先看系统整体内存情况free -h如果你的服务器只有2G内存而应用配置了1G以上的堆内存同时系统上还跑着MySQL等其他服务内存就很容易紧张。查看系统日志确认是否有OOM记录sudo dmesg | grep -i out of memory解决方法要么调整JVM内存参数把-Xmx设小一些要么升级服务器配置。我见过最小可以跑一个简单SpringBoot应用的配置是1G内存让JVM最多使用512M堆内存同时系统关掉不必要的服务。5.3 MySQL连接不上导致启动失败SpringBoot启动过程中如果配置了数据库连接检查而数据库没准备就绪会直接启动失败。排查方向依次确认数据库服务是否在运行MySQL里systemctl status mysqld确认一下数据库账号是否能远程连接在服务器上mysql -h127.0.0.1 -u用户名 -p试试项目的数据库地址配的是不是服务器内网IP而不是localhost有些云数据库只允许内网访问配置外部域名是连不上的用localhost在本机有时没问题、从另一台机器就连不上还有一个细节如果数据库密码包含特殊字符比如、#、$在yaml配置里要注意转义否则会解析错乱导致认证失败。复杂密码建议用双引号或单引号包起来或者用环境变量注入。5.4 SpringBoot打包后jar包运行报“没有主清单属性”这个错误基本可以确定是用了错误的jar包。检查一下target目录你会看到两个以项目名开头的文件demo-0.0.1-SNAPSHOT.jar demo-0.0.1-SNAPSHOT.jar.original没有.original后缀的才是SpringBoot的可执行jar包。如果你把.original改名后上传SpringBoot插件的repackage配置没有正确执行或者你在pom.xml里没引入SpringBoot的Maven插件都会导致jar包没有主清单属性。在pom.xml的build标签里确保有这段配置plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins这样Maven打包时才会生成可执行的fat jar。这个插件的工作原理是在打包阶段对原始jar包做一次二次加工把依赖全部塞进去重新生成带启动入口的可执行jar所以你看到目标目录里两个jar文件的生成逻辑就明白了。5.5 修改代码后如何快速重新部署部署不是一劳永逸的事代码改完之后重新部署的流程也需要熟练。完整流程是本地重新打包mvn clean package -DskipTests上传新jar包可以用scp但注意不要覆盖正在运行的旧jar因为文件正在被进程占用直接覆盖可能导致未知问题。正确做法是上传成新文件比如demo-0.0.2-SNAPSHOT.jar停止服务、替换文件、启动服务sudo systemctl stop demo mv /home/deploy/apps/demo/demo-0.0.1-SNAPSHOT.jar /home/deploy/apps/demo/demo-0.0.1-SNAPSHOT.jar.bak mv /home/deploy/apps/demo/demo-0.0.2-SNAPSHOT.jar /home/deploy/apps/demo/demo-0.0.2-SNAPSHOT.jar如果你希望始终使用同一个文件名避免频繁修改ExecStart配置可以在打包前先给jar重命名或者用一条命令先停服务、再替换文件、最后启动。我这个过程中吃过亏有一回直接覆盖了正在运行的jar文件应用倒是没立刻停但后面重启时报文件损坏这才学会“先停后替换”这个规矩。另外如果你的ExecStart配置里写死了带版本号的文件名每次版本升级都要同步修改service文件再daemon-reload比较繁琐。更推荐在service文件里写一个不带版本号的软链接指向当前版本的jar比如ln -sf demo-0.0.2-SNAPSHOT.jar demo-current.jar然后ExecStart指向demo-current.jar。这样以后换版本只需要更新软链接systemd配置不用动。这个习惯可以说是一个非常实用的日常小技巧。6. 部署完成后的检查清单服务跑起来了不代表部署就结束了。我习惯在部署完成后过一遍检查清单确认整个链路是健康的而不是“能跑就行”。进程状态sudo systemctl status demo确认是active (running)端口监听netstat -tlnp | grep 8080确认监听地址和端口正确本地请求curl http://localhost:8080/api/hello确认应用正常响应外部访问从本地电脑浏览器访问http://服务器IP:8080/api/hello确认安全组和防火墙都放行成功开机自启systemctl is-enabled demo确认输出enabled避免服务器重启后应用没起来日志写入确认/home/deploy/logs/demo/application.log有持续增长说明日志落盘正常这套检查清单看起来简单但每一条都是实际运维中真正与用户体验直接相关的东西。尤其是最后一两条服务器重启后应用还能不能自动起来日志有没有正常记录这两点决定了你后续排查问题的效率。我个人在实际操作中还有个习惯部署完成后会在服务器上写一个简单的健康检查脚本定时请求应用的健康检查接口连续失败几次就自动发提醒。如果你的项目里有actuator依赖SpringBoot本身就提供/actuator/health端点这个功能就很好实现。第一篇就先讲到这里。整个流程走下来你已经完成了把一个SpringBoot项目从本地搬到云服务器的全过程。这个系列后续的内容我计划接着讲Nginx反向代理配置、HTTPS证书接入、数据库托管方案选型、Docker化部署迁移等更进阶的主题。如果你在实操过程中遇到文里没有覆盖到的问题可以顺着排查思路去定位先确认安全组和防火墙再确认端口和进程然后看日志和应用配置。大部分问题都逃不出这几层。