跳过正文
  1. 系列/
  2. VPS 实战系列/

任务 10:打通 CI&CD(持续集成与部署),全自动上云实战

其雁过无痕
作者
其雁过无痕
目录

在经历了前几个阶段的“毛坯房装修”和环境搭建后,终于迎来了工程化实践的重头戏:打通 CI/CD 流水线 。

过去写完一篇笔记,我们需要手动执行编译指令,打开 FTP 软件,再把文件生硬地拖拽到服务器上。而今天,我们将通过 GitHub Actions,实现一套极致的极客工作流:只要在本地执行 git push 把代码推送到 GitHub 的主分支,云端就会自动完成编译,并通过 SSH 悄无声息地将最新博客更新到 VPS 上 。这不仅让我们彻底告别了手动上传的繁琐,更是掌握现代互联网大厂 DevOps 核心工作流的关键一步 。

针对咱们这台小内存 VPS,我们采用了最轻量级的方案:利用 GitHub 免费的高配置服务器进行“重体力”编译,然后直接通过 SCP 协议将纯静态网页传输到 Nginx 目录下,全程不占用 VPS 的一丁点算力。

第一步:将服务器“钥匙”托管给 GitHub
#

为了让 GitHub 机器人能够免密登录 VPS 传文件,必须将安全凭证存入仓库的密码箱(Secrets)中。

在私有仓库的 Settings -> Secrets and variables -> Actions 中,依次添加独立变量:

  • SERVER_IP:VPS 的公网 IP。
  • SERVER_USERNAME:登录用户名(通常为 root)。
  • SERVER_PORT:修改过后的高位 SSH 端口。
  • SERVER_SSH_KEY:本地电脑的 SSH 私钥(包含头尾的完整多行文本)。
  • SERVER_SSH_PASSPHRASE(高频踩坑点!) 如果生成密钥时设置了保护密码,必须填入此项,否则机器人握手会被无情拒绝。

当然,可以为机器人专门生成一个“无密码”密钥,这样也更符合 DevOps 规范,同时下方的 deploy.yml 要将 passphrase: ${{ secrets.SERVER_SSH_PASSPHRASE }} 这一行删掉

第二步:编写云端流水线剧本 (GitHub Actions)
#

在项目根目录新建 .github/workflows/deploy.yml,为 GitHub 的临时服务器下达指令集:

name: Deploy Hugo Blog to VPS

on:
  push:
    branches:
      - main # 监听 main 分支的代码推送

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      # 1. 拉取最新代码 (必须开启 submodules 以拉取主题)
      - name: Checkout Code
        uses: actions/checkout@v4
        with:
          submodules: true
          fetch-depth: 0

      # 2. 配置编译环境
      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v3
        with:
          hugo-version: "latest"
          extended: true

      # 3. 执行编译并压缩静态资源
      - name: Build Site
        run: hugo --minify

      # 4. SCP 传输到 VPS
      - name: Deploy to VPS
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USERNAME }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          passphrase: ${{ secrets.SERVER_SSH_PASSPHRASE }} # 密钥保护密码
          port: ${{ secrets.SERVER_PORT }}
          source: "public/" # 编译产物目录
          target: "/opt/blog/public" # VPS 上的目标存放路径
          strip_components: 1 # 剥离 public 外壳,直接倾倒内部文件

第三步:Nginx 容器的最终接应
#

文件顺利抵达 VPS 后,还需要让前端的 Nginx 准确找到它们。

在部署 Nginx 的 docker-compose.yml 中,将宿主机的接收目录挂载到容器内部:

volumes:
  # 将 GitHub Actions 传过来的静态文件,挂载到 Nginx 默认读取目录
  - /opt/blog/public:/usr/share/nginx/html

并在映射的 nginx.conf 中,将根目录精确指向容器内的挂载点:

    location / {
        root /usr/share/nginx/html;  # 必须是容器内的绝对路径
        index index.html index.htm;
    }

执行 docker compose downdocker compose up -d 重启容器,大功告成!

🛠️ 避坑指南 (Troubleshooting)
#

在打通这条流水线的过程中,我踩过了几个极其经典的工程化“大坑”,特此记录:

  1. 私钥的密码保护门槛:报错 ssh: this private key is passphrase protected。就是生成密钥时设置了 Passphrase,机器人将无法输入。必须在流水线的 scp 插件参数中显式传递 passphrase 字段。
  2. 连字符问题:在 VPS 终端执行重启时报错 Command 'docker-compose' not found。现代 Docker 环境(V2+)已将 compose 集成为内置插件,必须去掉中间的连字符,改用 docker compose
  3. Nginx 强拦截指令的遮挡:部署成功后依旧显示旧版测试页面。原因在于 nginx.conf 中遗留了 return 200 '...'; 拦截指令。Nginx 识别到该指令会直接返回文本,不再读取本地 HTML,必须将其替换为标准的 root 指令。

至此,整套自动化流程已彻底打通。把重复的劳动外包给机器,把宝贵的精力留给创作!

相关文章

番外:3X-UI 节点订阅合并与优化

在使用 3X-UI 搭建代理节点时,很多朋友可能会遇到这样一个痛点: 因为 3X-UI 的 Vision 流控(xtls-rprx-vision)是需要在具体客户端上设置的,这就导致开启了 Vision 和未开启 Vision 的节点,被强制拆分成了不同的订阅链接。 当把它们导入到代理软件(比如 V2rayN)时,软件里会显示出多个不同的订阅分组,不仅观感杂乱,后期维护起来也极其不便。

任务 7:部署限制内存的数据库

在服务器部署的进阶之路上,部署限制内存的数据库是一个必经的关卡 。对于这种小内存的 VPS(比如 RackNerd),如果直接裸跑默认配置的 MySQL 8.0,它会一口气吞掉 400MB 甚至更多的内存,极易导致服务器 OOM(内存溢出)死机。

番外:1G 小内存 VPS 部署 Java JSP 项目实战:Docker 本地构建 + 远程运行完美方案

在拥有了一台属于自己的 VPS(如 1核 1G内存,配置了 2G Swap)后,很多新手在尝试部署 Java 项目时,往往会选择直接在服务器上安装 Maven 或运行 docker build。但现实很残酷:Java 编译极其消耗内存,1G 的内存在构建瞬间就会被挤爆,导致系统卡死或触发 OOM (Out Of Memory) 杀掉进程。

任务 3:熟练使用终端复用工具

核心目标 # 操作:安装并学习使用 tmux。 收获:掌握在服务器上跑长耗时任务的能力。即使本地 SSH 突然断开,任务依然会在后台运行,不会前功尽弃。 核心工具:tmux # tmux (Terminal Multiplexer) 是现代服务端开发的必备工具,解决了远程连接中途掉线导致任务中断的痛点。