9.1 KiB
title, date, tags, categories, draft
| title | date | tags | categories | draft | |||||
|---|---|---|---|---|---|---|---|---|---|
| 聊聊我博客部署的优化:从 her-cat/upyun-deployer 换到官方 upx | 2026-06-25T19:54:00+08:00 |
|
|
false |
端午假期,我把用了挺久的 her-cat/upyun-deployer 又拍云自动部署脚本换成了官方出品的 upx。
这个工具是我在逛官方文档时偶然发现的。之前一直用 GitHub 开源作者 hercat 的 her-cat/upyun-deployer,稳定性确实不错,跑了一年多都没出什么问题。不过现在 Hugo 博客的文章越来越多,部署时间也越来越长。
GitHub Actions 对免费用户有每月 2000 分钟的运行时长限制,有时候遇到网络波动,部署就会卡很久,甚至直接超时失败。
这篇文章简单记录一下迁移过程,顺便对比分析两款工具的实现差异,希望能给同样在用 Hugo + 又拍云的朋友一些参考。
先聊聊 her-cat/upyun-deployer 的实现
在说 upx 之前,我让ai 查了查 her-cat/upyun-deployer 的源码,看看它是怎么工作的。作者是 her-cat,这个项目帮了很多人,包括我之前也一直在用,非常感谢作者的付出。
它的核心逻辑在 main.go 里,大概是这样的:
func (d *UpYunDeployer) UploadFiles() {
// 第一步:获取远程所有文件列表
files, dirs := d.GetAllRemoteFilesByPath(publishDir)
// 第二步:遍历本地文件
err := filepath.Walk(d.localDir, func(filename string, file os.FileInfo, err error) error {
// ...
// 第三步:对每个文件,先获取远程文件信息
remoteFileInfo, err := d.getFileInfo(relativeFilename)
// 第四步:判断是否需要上传
if upyun.IsNotExist(err) {
d.uploadFile(putObjectConfig, false)
return
}
// 第五步:MD5 对比,如果相同就跳过
if remoteFileInfo.MD5 == fmt.Sprintf("%x", md5.Sum(data)) {
fmt.Printf("[%s] cached!\n", relativeFilename)
return
}
// 第六步:不同的话,先删除再上传
err = d.deleteFile(relativeFilename, true)
d.uploadFile(putObjectConfig, true)
return nil
})
// 第七步:全量 CDN 刷新
d.up.Purge(urls)
// 第八步:删除远程多余的文件和目录
d.deleteFiles(files)
d.deleteDirs(dirs)
}
整体思路其实很清晰:
- 先把远程所有文件列表拉下来
- 遍历本地每个文件,和远程对比 MD5
- 不一样就先删再上传
- 最后全量刷新 CDN,再删掉远程多余的文件
这个思路很不错,就是在实际使用中,有几个地方会比较耗时:
第一个是每个文件都要单独调 API。
上传一个文件之前,先要调 GetInfo 检查远程是否存在;如果存在且 MD5 不同,还要先调 Delete 删除,再调 Put 上传。一个文件搞不好就要发 2-3 次请求,文件多了自然就慢了。
第二个是获取远程文件列表的过程。
GetAllRemoteFiles 是递归遍历所有目录的,虽然用了 goroutine 并发请求,但文件越多,请求次数就越多,这块也会花不少时间。
第三个是全量 CDN 刷新。
每次部署完都会把所有上传过的文件都 Purge 一遍,虽然又拍云的 Purge 是异步的,但全量刷新既浪费配额,也可能导致用户访问的时候缓存命中率下降。
upx 是什么
upx 是又拍云官方出的命令行工具,全称是 UpYun Uploader(我猜的)。
UPX 是专为开发者设计的,基于命令行的云存储管理工具。它可以实现文件上下传、增量文件同步、目录创建删除、文件删除等。
项目地址:https://github.com/upyun/upx
我最看重的就是它的 sync 命令,类似于Linux的 rsync,支持增量同步。而且因为是官方维护的,对 API 的利用应该会更高效一些。
我的部署方案
下面说说我现在的部署配置,大家可以参考一下。
GitHub Actions 配置
我把构建和部署拆成了两个 job,这样构建产物可以复用。
name: Deploy to Production
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Build Hugo site
run: hugo --gc --minify
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: public
path: ./public
deploy-upyun:
runs-on: ubuntu-latest
needs: build
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: public
path: ./public
- name: Deploy to UpYun
env:
UPYUN_SERVICE: ${{ secrets.UPYUN_BUCKET }}
UPYUN_OPERATOR: ${{ secrets.UPYUN_OPERATOR }}
UPYUN_PASSWORD: ${{ secrets.UPYUN_PASSWORD }}
run: |
chmod +x scripts/deploy_upyun.sh
./scripts/deploy_upyun.sh ./public / 10
部署脚本
然后是具体的部署脚本,放在 scripts/deploy_upyun.sh:
#!/bin/bash
# UpYun 部署脚本 - 使用 upx 官方工具
set -e
LOCAL_DIR="${1:-./public}"
REMOTE_DIR="${2:-/}"
THREADS="${3:-10}"
# 又拍云 upx sync 的线程数限制在 1-10 之间
if [ "$THREADS" -lt 1 ]; then
THREADS=1
elif [ "$THREADS" -gt 10 ]; then
THREADS=10
fi
SERVICE_NAME="${UPYUN_SERVICE:-}"
OPERATOR="${UPYUN_OPERATOR:-}"
PASSWORD="${UPYUN_PASSWORD:-}"
if [ -z "$SERVICE_NAME" ] || [ -z "$OPERATOR" ] || [ -z "$PASSWORD" ]; then
echo "❌ 错误:缺少必要的环境变量"
exit 1
fi
echo "🚀 UpYun 部署开始"
# 下载又拍云 upx
UPX_VERSION="0.4.9"
UPX_URL="https://collection.b0.upaiyun.com/softwares/upx/upx_${UPX_VERSION}_linux_amd64.tar.gz"
UPX_BIN="/tmp/upyun-upx"
if [ ! -x "$UPX_BIN" ]; then
echo "📥 下载又拍云 upx v${UPX_VERSION}..."
curl -sL "$UPX_URL" -o /tmp/upx.tar.gz
tar -xzf /tmp/upx.tar.gz -C /tmp
mv /tmp/upx "$UPX_BIN"
chmod +x "$UPX_BIN"
echo "✅ upx 安装完成"
fi
# 登录并同步
"$UPX_BIN" login "$SERVICE_NAME" "$OPERATOR" "$PASSWORD"
"$UPX_BIN" sync "$LOCAL_DIR" "$REMOTE_DIR" \
-w "$THREADS" \
--delete \
--strong
echo "✅ UpYun 部署完成!"
脚本很简单,就是下载 upx,登录,然后 sync 一下。
upx 为什么更快?
我自己用下来,upx 确实比 her-cat/upyun-deployer 快不少,我个人认为主要有这几个原因:
第一是官方实现,对 API 的利用更高效。
毕竟是官方自己的工具,很多操作应该是批量处理的,而不是每个文件单独调一次 API。这一点从速度上就能明显感觉出来。
第二是 sync 命令做了专门的优化。
upx sync 作为核心功能,内部的文件对比、差异计算应该都是经过优化的。不像社区项目,作者可能是业余时间维护,精力有限。
第三是线程控制更稳定。
upx 的 -w 参数可以控制并发线程数,而且内部应该有完整的错误重试和限流机制,不容易触发又拍云的 API 频率限制。
不过有一点要注意,又拍云的 upx sync 线程数上限是 10,别开太大了,开了也没用。
一些小技巧
build hash 跳过重复部署
有时候我们可能只是改了一下 README,或者 CI 配置变了,但 Hugo 构建的产物其实是一样的。这时候可以用 build hash 来判断是否需要重新部署。
- name: Generate build hash
id: hash
run: echo "build_hash=$(find ./public -type f -exec md5sum {} \; | md5sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT
- name: Cache build
uses: actions/cache@v4
with:
key: ${{ runner.os }}-deploy-upyun-${{ steps.hash.outputs.build_hash }}
path: ./public
如果缓存命中了,说明构建产物没变化,就可以跳过部署步骤,省不少时间。
注意和系统 upx 重名
Linux 系统里可能已经装了另一个 upx,就是那个可执行文件压缩工具。为了避免冲突,我在脚本里把下载的又拍云 upx 改名叫 upyun-upx,路径放在 /tmp 下面,这样就不会搞混了。
一些注意事项
-
敏感文件别传上去了:部署前确认一下
.git、.env这些东西不在 public 目录里,不然传到 CDN 上就尴尬了。 -
权限尽量给小一点:去又拍云后台创建一个专门的操作员,只给上传权限就行,别用主账户密码。安全第一。
-
CDN 刷新按需来:upx sync 上传的文件会自动更新,一般不需要手动 Purge。除非你改了文件名没变内容,那可能需要手动刷新一下。
-
网络问题:GitHub Actions 的服务器在海外,部署到又拍云国内节点可能会慢一些。我自己用下来还能接受,如果觉得慢的话,可以考虑用国内的 CI 服务。
相关链接:
- upx 开源项目:https://github.com/upyun/upx
- 又拍云官方文档:https://help.upyun.com/
