起因
git push origin main,然后 SSH 到服务器,git pull,重启服务。一天两次还行,一天十次就开始怀疑人生了。
问题很明确:代码推上去之后,部署能不能自己跑?
能。一个 webhook 就够了。
最小方案
整条链路只有三个角色:Git仓库(发信号)、webhook 服务(收信号)、部署脚本(干活)。
不需要 Jenkins,不需要 GitLab Runner,甚至不需要任何 CI 平台。一台服务器,一个轻量进程,搞定。
这里用 adnanh/webhook,一个 Go 写的小工具,单二进制文件,配置是 JSON。
配置 webhook服务
hook 定义文件 hooks.json:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| [ { "id": "deploy", "execute-command": "/opt/scripts/deploy.sh", "command-working-directory": "/opt/app", "pass-arguments-to-command": [ { "source": "payload", "name": "ref" } ], "trigger-rule": { "and": [ { "match": { "type": "value", "value": "refs/heads/main", "parameter": { "source": "payload", "name": "ref" } } }, { "match": { "type": "value", "value": "[YOUR_SECRET]", "parameter": { "source": "header", "name": "X-Hub-Signature-256" } } } ] } } ]
|
两个关键点:只响应 main 分支的推送;通过 secret 验签,防止随便谁都能触发。
启动服务:
1
| webhook -hooks hooks.json -port 9000 -verbose
|
然后在 Git 仓库的 webhook 设置里填上 http://[YOUR_SERVER]:9000/hooks/deploy,payload 选 JSON,填好 secret。
部署脚本
deploy.sh 没什么花活:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #!/usr/bin/env bash set -euo pipefail
REPO_DIR="/opt/app" LOG="/var/log/deploy.log"
echo "$(date '+%Y-%m-%d %H:%M:%S') Deploy started" >> "$LOG"
cd "$REPO_DIR" git fetch origin main git reset --hard origin/main
npm ci --production >> "$LOG" 2>&1 pm2 restart app >> "$LOG" 2>&1
echo "$(date '+%Y-%m-%d %H:%M:%S') Deploy finished" >> "$LOG"
|
set -euo pipefail 是底线,任何一步出错直接停掉,别让半成品跑起来。
用 git reset --hard 而不是 git pull,是因为服务器上不应该有本地改动。如果有,那是另一个问题。
几个容易踩的坑
webhook 进程的运行用户得有仓库目录的读写权限,也得有重启服务的权限。建议单独建一个用户,用 sudoers 精确授权,别图省事用 root 跑。
日志一定要写。部署失败的时候,没日志等于瞎猜。
还有一个不太明显的问题:并发。如果短时间内连续推了好几次,多个部署脚本会同时跑。简单的解法是加个 flock 文件锁:
1 2
| exec 200>/tmp/deploy.lock flock -n 200 || { echo "Deploy already running, skip"; exit 0; }
|
拿不到锁就跳过,反正下一次推送还会触发。
然后呢
这套东西跑起来之后,自然会想要更多:部署失败发通知、自动回滚、跑完测试再部署。到了那个阶段,可能就该认真考虑一个真正的 CI/CD 平台了。
下一步值得想的问题:部署失败了,怎么自动回滚到上一个可用版本?