CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Linux 服务器入门/课时/部署 CodeRoadMap
RawBlame
课时目录12 节

Linux 服务器入门

  1. 01Linux 是什么
  2. 02Shell 与 Bash
  3. 03文件系统与权限
  4. 04常用命令
  5. 05编辑器与配置
  6. 06进程与服务
  7. 07用户与 sudo
  8. 08包管理
  9. 09SSH 与密钥
  10. 10防火墙与端口
  11. 11部署 CodeRoadMap
  12. 12综合练习

部署 CodeRoadMap

目录布局、JAR、Node release、Nginx 协同。

35 分钟

部署 CodeRoadMap

这一课你将能做到

  1. 按规范目录落地 CodeRoadMap:/www/wwwroot/coderoadmap/ 放 JAR、前端产物与配置,属主/权限对齐运行用户
  2. 用 systemd 托管后端 :8080、PM2 托管前端 :3000,并让 Nginx 反代 :443,实现「首页 + /api/」全链路可访问
  3. 执行一次完整发布(换 JAR → restart → 健康检查 → 回滚演练),并把流程写成自己的检查单

为什么要学

前面十课全是零件:Shell 是语言、权限是规矩、systemd 是进程管家、SSH 是通道、防火墙是门。本课把它们组装成一台真正能访问的 CodeRoadMap——这是本 track 的验收课:每一步都会回头用到之前学过的命令,缺课的地方会立刻暴露。

你要建立的工程认知是:「部署成功」= 目录 + 权限 + 进程 + 反代 + 端口策略同时正确,缺一个就是「半上线」。而「发布成功」= 上传 + 重启 + 验证 + 可回滚,缺回滚就不敢发布。

先建立图像

/www/wwwroot/coderoadmap/
  ├─ backend.jar                 ← Spring Boot 产物(systemd 托管)
  ├─ application-prod.yml        ← 生产配置:DB 地址/密钥,属主 www、权限 640,绝不进 Git
  ├─ platform/                   ← Next.js 构建产物(PM2 托管)
  ├─ releases/                   ← 历史版本 JAR(回滚用)
  ├─ logs/                       ← 应用日志
  └─ scripts/                    ─ 发布/回滚脚本

浏览器 ──HTTPS 443──► Nginx
                        ├─ /      → 127.0.0.1:3000  (PM2 → next start)
                        └─ /api/  → 127.0.0.1:8080  (systemd → java -jar) ──► 127.0.0.1:3306 MySQL

三个约定先说死:公网只见 Nginx(80/443)、业务端口只听 127.0.0.1(8080/3000/3306)、进程全部有管家(systemd/PM2,不是 nohup)。

跟着做

前置条件自查(前面各课的验收):

java -version && node -v                      # 包管理课:JDK17 + Node20 就位
systemctl is-active mysql                     # MySQL 活着
ssh prod "echo ok"                            # SSH 课:别名免密可连
sudo ufw status | grep -E "80|443"            # 防火墙课:门面已开

步骤 1:目录与权限(文件课 + 用户课合体)

ssh prod
sudo mkdir -p /www/wwwroot/coderoadmap/{platform,logs,scripts,releases}

# 运行用户:宝塔默认 www;自建环境用你定义的服务用户(用户课)
sudo useradd -r -s /usr/sbin/nologin www 2>/dev/null || true
sudo chown -R www:www /www/wwwroot/coderoadmap
sudo chmod 750 /www/wwwroot/coderoadmap        # 目录:属主全权,组可进,他人无份

# 双身份模型:deploy(人)+ www(服务)。deploy 维护经 sudo,www 跑服务
sudo usermod -aG www deploy 2>/dev/null || true

# 交叉验证(文件课的 sudo -u 验证法):
sudo -u www touch /www/wwwroot/coderoadmap/logs/.probe && echo "www 可写日志 ✅" \
  && sudo rm /www/wwwroot/coderoadmap/logs/.probe

步骤 2:上传产物(SSH 课的 scp/rsync)

本机执行(构建产物假设已在本地打好;构建应在 CI 完成,服务器不跑 npm build):

# 后端 JAR(Maven/Gradle 打出的 bootJar)
scp target/backend-1.0.0.jar prod:/tmp/backend.jar.new

# 前端 Next.js 构建产物(.next、public、package.json、next.config.js)
rsync -avz --exclude node_modules .next prod:/tmp/platform-new/
rsync -avz public package.json next.config.js prod:/tmp/platform-new/

服务器上落位(注意先停后换,避免「半新半旧」):

ssh prod
# JAR:版本归档 + 软链切换(可回滚的关键设计)
sudo mv /tmp/backend.jar.new /www/wwwroot/coderoadmap/releases/backend-$(date +%Y%m%d%H%M).jar
sudo ln -sfn /www/wwwroot/coderoadmap/releases/backend-$(date +%Y%m%d%H%M).jar \
             /www/wwwroot/coderoadmap/backend.jar
sudo chown www:www /www/wwwroot/coderoadmap/backend.jar
sudo chmod 644 /www/wwwroot/coderoadmap/backend.jar

# 前端:整体换目录(原子切换思路)
sudo rsync -a --delete /tmp/platform-new/ /www/wwwroot/coderoadmap/platform/
sudo chown -R www:www /www/wwwroot/coderoadmap/platform

生产配置单独放(不在 CI 里传):

sudo vim /www/wwwroot/coderoadmap/application-prod.yml
# 内容要点:datasource 指向 127.0.0.1:3306、server.address=127.0.0.1、密钥手动维护
sudo chown www:www /www/wwwroot/coderoadmap/application-prod.yml
sudo chmod 640 /www/wwwroot/coderoadmap/application-prod.yml    # 组内只读,他人无权

步骤 3:后端 systemd 单元(进程课直接复用)

sudo vim /etc/systemd/system/coderoadmap-backend.service
[Unit]
Description=CodeRoadMap Spring Boot backend
After=network.target mysql.service

[Service]
Type=simple
User=www
WorkingDirectory=/www/wwwroot/coderoadmap
ExecStart=/usr/bin/java -Xms512m -Xmx1024m -jar /www/wwwroot/coderoadmap/backend.jar \
  --spring.profiles.active=prod \
  --spring.config.additional-location=/www/wwwroot/coderoadmap/application-prod.yml
Restart=on-failure
RestartSec=5
SuccessExitStatus=143
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now coderoadmap-backend
sleep 15 && systemctl status coderoadmap-backend --no-pager
curl -s http://127.0.0.1:8080/actuator/health; echo    # {"status":"UP"} 即成功
# 没起来看日志(命令课 + 进程课):
journalctl -u coderoadmap-backend -n 50 --no-pager

MySQL 建库授权(应用连接的前提):

sudo mysql -e "CREATE DATABASE IF NOT EXISTS coderoadmap DEFAULT CHARACTER SET utf8mb4;"
sudo mysql -e "CREATE USER IF NOT EXISTS 'crm'@'127.0.0.1' IDENTIFIED BY '强密码自定';"
sudo mysql -e "GRANT ALL ON coderoadmap.* TO 'crm'@'127.0.0.1'; FLUSH PRIVILEGES;"
# 密码写进 application-prod.yml(640 权限保护),不要写进代码库

步骤 4:前端 PM2 托管(Node 生态的管家)

sudo npm i -g pm2        # PM2 装一次即可
cd /www/wwwroot/coderoadmap/platform
npm ci --omit=dev        # 只装运行依赖(构建已在 CI 完成)
pm2 start npm --name platform -- start -- -H 127.0.0.1 -p 3000
pm2 save                 # 保存进程列表
pm2 startup              # 按提示执行它输出的 sudo env... 命令 → 开机自启
pm2 list                 # online 即成功
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3000/     # 200
pm2 logs platform --lines 20 --nostream    # 排障看日志

步骤 5:Nginx 反代(门面)

sudo vim /etc/nginx/sites-available/coderoadmap.conf
server {
    listen 80;
    server_name 你的域名;
    client_max_body_size 50m;

    # API 优先匹配,反代后端
    location /api/ {
        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;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # 其余全给前端
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }
}
sudo ln -sfn /etc/nginx/sites-available/coderoadmap.conf /etc/nginx/sites-enabled/
sudo nginx -t                                   # 语法检查,ok 才继续
sudo systemctl reload nginx                     # reload 而非 restart(不断连接)
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1/    # 本机验证反代通

# HTTPS(有域名后):certbot 自动改配置
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d 你的域名                # 按提示选 redirect;nginx -t && reload 自动完成
curl -I https://你的域名 | head -3              # HTTP/2 200

宝塔用户的差异:面板「添加站点 → 反代」生成的就是上面这类配置,理解了手写版,面板里的每个字段你都知道对应什么。

步骤 6:全链路验收 + 回滚演练

# 验收清单(从公网视角逐项过):
curl -s -o /dev/null -w "front:%{http_code}\n" https://你的域名/
curl -s https://你的域名/api/actuator/health; echo
sudo ss -tlnp | grep -E ':(80|443|3000|8080|3306)\s'
# 期望:80/443 → nginx(0.0.0.0);3000/8080/3306 → 127.0.0.1

# 故障演练:停前端,观察 502,再恢复(理解 Nginx 依赖上游)
pm2 stop platform && curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1/
pm2 start platform

# 回滚演练:releases 里挑上一个版本,切链重启(2 分钟内完成)
ls -lt /www/wwwroot/coderoadmap/releases/ | head -3
sudo ln -sfn /www/wwwroot/coderoadmap/releases/上一个版本.jar /www/wwwroot/coderoadmap/backend.jar
sudo systemctl restart coderoadmap-backend
curl -s http://127.0.0.1:8080/actuator/health; echo

步骤 7:把发布流程固化成脚本(scripts/ 的第一个住户)

sudo vim /www/wwwroot/coderoadmap/scripts/release.sh
#!/bin/bash
# 用法:./release.sh /path/to/new.jar
set -euo pipefail
APP_DIR=/www/wwwroot/coderoadmap
STAMP=$(date +%Y%m%d%H%M)

test -f "$1" || { echo "用法: $0 新JAR路径"; exit 1; }

# systemctl 走 sudoers 白名单(用户课的 NOPASSWD 配置)
sudo mv "$1" "$APP_DIR/releases/backend-$STAMP.jar"
sudo ln -sfn "$APP_DIR/releases/backend-$STAMP.jar" "$APP_DIR/backend.jar"
sudo chown www:www "$APP_DIR/backend.jar"
sudo systemctl restart coderoadmap-backend
sleep 12
curl -sf http://127.0.0.1:8080/actuator/health > /dev/null \
  && echo "RELEASE OK: $STAMP" \
  || { echo "HEALTH CHECK FAILED → 自动回滚"; \
       sudo ln -sfn "$(ls -t $APP_DIR/releases/*.jar | sed -n 2p)" "$APP_DIR/backend.jar"; \
       sudo systemctl restart coderoadmap-backend; exit 1; }
chmod +x /www/wwwroot/coderoadmap/scripts/release.sh

健康检查不过自动回滚——这一段脚本就是「敢在周五下午发布」的全部底气。CI 集成(GitHub Actions scp + ssh 执行此脚本)见 github-actions 轨道。

讲清楚要点

  1. 因为多进程协作,所以部署是「五件事的与运算」:目录对、权限对、进程对、反代对、端口策略对。逐层验收(本机 curl → 127.0.0.1 → 公网),任何一层断了先修那一层。
  2. 因为回滚比修复快,所以发布必须留后路:releases/ 归档 + 软链切换 + 健康检查不过自动回滚。磁盘允许时保留最近 5 个版本,配 cron 清理更早的。
  3. 因为配置含密钥,所以 prod 配置与代码库分离:服务器落盘、640 权限、不进 Git。CI 只传产物不传密钥——这是安全底线不是风格问题。
  4. 因为端口唯一,所以「覆盖文件」不等于「发布新版本」:JVM 加载的是启动时的 JAR,改完必须 restart。发布流程 = 停旧(或直接 restart)→ 换文件 → 起新 → 验活,管家(systemd/PM2)负责保证这四步之后进程活着。
  5. 因为前端是构建产物,所以服务器只装运行时:npm ci --omit=dev 而不是全量 install,绝不在生产机上 build(慢、不可复现、内存吃紧)。构建进 CI(github-actions 轨道)。
  6. 因为公网只应有 Nginx,所以应用全部绑 127.0.0.1:HTTPS/限流/缓存/日志都收口在 Nginx 一层,Java/Node 不直接面对公网——这是性能、安全、证书管理的三重需要。

你可能会懵的地方

  • 宝塔「网站根目录」应该填 platform 还是加反代? Next.js 动态渲染必须反代到 :3000(PM2 跑着的服务),根目录方式只适合纯静态导出(output: 'export')。判断标准:访问 / 时浏览器 Network 里是否有 /_next/... 动态请求。有 → 反代;无 → 静态目录也行。
  • curl 127.0.0.1:8080 通,但经域名访问 /api/ 404 或 502? 404:Nginx 的 location /api/ 匹配后 proxy_pass 是否带了路径拼接问题(带斜杠=替换前缀);应用自身 context-path 是否为空。502:上游没起或 Nginx 的目标写错。区分工具:nginx error.log 的 tail + nginx -T | grep -A3 location。
  • actuator/health 返回 404? Spring Boot 2.x 需要暴露端点:management.endpoints.web.exposure.include=health;或你们项目有统一前缀(/api/actuator/health)。以实际项目配置为准,把「健康 URL」写进发布脚本。
  • CI 显示 deploy 成功,浏览器还是旧页面? 四连查:scp/rsync 目标路径对吗(ls -lt releases/)?restart 执行了吗(systemctl status 看 ActiveEnterTimestamp)?多台机器发的是同一台吗(hostname)?浏览器/CDN 缓存吗(Ctrl+Shift+R 或 curl 带 -H "Cache-Control: no-cache")。
  • 服务器内存 2G,MySQL + JAR + Node 同时跑得动吗? 紧张但可行:MySQL 调小 buffer pool(~256M)、JVM -Xmx768m、Node 本身不大;再加 2G swap 兜底(fallocate -l 2G /swapfile 系列)。生产正式跑建议 4G 起步。预算方法见进程课的内存三口径。

错法对照

错法问题正解
公网直连 8080 当「已上线」无 TLS/限流/缓存,应用裸奔Nginx 443 对外,应用绑 127.0.0.1
生产配置提交进 GitDB 密码等秘钥永久留在提交历史服务器 640 落盘,CI 不碰密钥
覆盖 JAR 不重启就验收跑的还是旧字节码restart 是发布的组成部分,验活后才算完成
目录属主 root、进程用户 wwwPermission denied 连环报chown 对齐运行用户(文件课标准流程)
生产机 npm install/build 当常规流程慢、不可复现、内存爆CI 构建 + rsync 产物 + npm ci --omit=dev
出错后反复 restart「碰运气」日志刷屏,根因永远看不见journalctl/应用日志先定性,再动手
发布不留旧版本回滚只能重新打包,故障窗口拉长releases/ 归档 + 软链切换,秒级回滚

卡点急救

  1. 后端起不来:先分层看——systemctl status 看退出码;journalctl -u coderoadmap-backend -n 80 看临终日志。高频根因:① DB 连不上(MySQL 没建库/账号错/密码错)→ 补步骤 3 的建库授权;② 端口被占 → ss -tlnp | grep 8080;③ yml 缩进/字段错 → 本地先跑一遍同配置。
  2. 前端 502 且 pm2 list 显示 errored:pm2 logs platform --lines 50 --nostream 看崩溃原因。高频:.next 不完整(rsync 漏文件)、npm ci 没跑(模块缺失)、端口被旧进程占。修复后 pm2 save 固化。
  3. HTTPS 证书申请失败:域名未解析到本机(dig +short 你的域名 核对)、80 未放行(certbot 校验走 HTTP)、Nginx server_name 与域名不一致。三项都过再重跑 certbot。
  4. 静态资源 404(页面能开但 CSS/JS 丢):多为产物目录不完整或 Nginx 没把 /_next/ 反代给 3000。本课配置里 location / 已覆盖;若拆了静态规则,检查 root/alias 指向与实际目录。
  5. 回滚后「还是新版」:软链切了但没 restart(JVM 不热加载);或 systemd 的 ExecStart 写的是绝对路径文件而非软链——确认 ExecStart 指向 /www/wwwroot/coderoadmap/backend.jar(软链本身),daemon-reload 后 restart。

面试常问

  • 描述你部署一个 Spring Boot + Next.js 项目的完整流程。 主线:① 规范目录 + 用户/权限模型(www 运行、deploy 操作);② CI 构建产物,scp/rsync 上传,releases 归档 + 软链切换;③ systemd 托管 JAR(开机自启/崩溃重启),PM2 托管前端;④ Nginx 反代 80/443,应用全部绑 127.0.0.1,MySQL 仅回环;⑤ 发布脚本带健康检查与自动回滚;⑥ 验收从公网逐层 curl。每一步都对应 Linux 层的可验证命令。

  • 应用健康检查你们怎么做?部署脚本如何利用它? 进程层:systemd 只保证进程活;业务层用 actuator/health(DB/Redis 检查包含在内)。发布脚本 curl -sf health || 自动回滚到上一版本;外部监控定时打健康接口,失败告警。「假活」(进程在但接口 5xx)靠业务健康检查兜住,这是 systemctl status 看不出来的。

  • 线上发布后出现 5xx,你的应急流程? 决策树:健康检查挂 → 直接回滚(脚本已备);个别接口 5xx → 看错误日志(journalctl + 应用 logs)定位是新代码问题还是环境差异;流量突增导致 → 先限流/扩容。原则:先止血(回滚/限流)再查因,把 MTTR 压到最短。

  • 为什么生产环境不让应用直接对外提供服务,而要经 Nginx 反代? Nginx 统一收口:TLS 终结(证书一处管理)、HTTP/2、gzip/缓存、限流防刷、静态资源直接服务、多 upstream 负载与灰度切换;应用进程重启时 Nginx 可缓存 502 页面且端口稳定不漂移。安全上公网只暴露 80/443,应用绑定回环,攻击面最小化。

  • 如何保证「回滚」是随时可用的? 三件套:版本归档(releases/ 保留 N 个历史 JAR/产物)、切换机制(软链/PM2/镜像 tag,切换动作≤1 条命令)、切换后的验证自动化(健康检查脚本)。定期演练回滚(本课步骤 6),没演练过的回滚等于没有回滚。

自测三问

  1. 「部署成功」由哪五层条件构成?写出每层对应的验证命令。
  2. 发布新 JAR 的完整命令序列是什么?如果健康检查失败,你如何 1 分钟内回到上一个版本?
  3. application-prod.yml 为什么权限是 640 而不是 644?它应该由谁拥有、谁能读?

小结一句话

CodeRoadMap 的部署拓扑一句话:产物进 /www/wwwroot(属主对齐运行用户),JAR 交 systemd、前端交 PM2、公网只留 Nginx 443、数据库锁回环;发布 = 归档 + 软链 + restart + 健康检查 + 可回滚——至此,你已能独立完成一次可敬畏的生产部署。

延伸

  • Spring Boot 官方 — Deployment(systemd 章节)
  • Next.js 官方 — Deployment
  • Nginx 官方 — Reverse Proxy 指南
  • 下一课预告:综合练习——502、端口占用、Permission denied、磁盘打满四大故障实弹演习 + 一份属于你自己的上线检查单,把十二课串成肌肉记忆。
加载本节练习…
← 上一课防火墙与端口下一课 →综合练习