文章

写一个flask服务响应webhook

基于 Flask + WebHook + Supervisor 的博客自动化部署实践方案

写一个flask服务响应webhook

1. 背景

  • 目标:gitcode 上有最新的提交后,服务器会自动 fetch 最新代码,并自动完成 blog 网站的更新。
  • 技术方案:使用了 GitHub 的 WebHook ,并在 Ubuntu 上布置了对应的 Flask 服务。

2. 服务器部署接口(Flask 服务)

  • 服务器安装 Web 服务依赖(用于接收 GitHub 回调):
1
2
sudo apt install -y python3-pip
pip3 install flask
  • 编写 WebHook 服务脚本(webhook-server.py)::
1
nano /var/myblog/webhook-server.py
  • py 脚本内容:(以服务器实际运行版本为准)
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from flask import Flask, request
import subprocess
# 新增:导入日志模块
import logging

app = Flask(__name__)

# 配置日志:确保输出到标准输出(supervisor 会捕获并写入 webhook.out.log)
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[logging.StreamHandler()]  # 输出到标准输出,supervisor 能捕获
)
logger = logging.getLogger(__name__)

# 1. SECRET 需与 GitCode WebHook 中设置的「令牌」完全一致(字符串类型)
SECRET = "952581716"  # 替换为 GitCode 中填写的令牌

@app.route('/update-blog', methods=['POST'])
def update_blog():
    
    # 新增日志:记录请求进入
    logger.info("收到 /update-blog 请求,开始验证令牌")
    
    # 2. 获取 GitCode 传递的令牌(请求头字段为 X-GitCode-Token 或 X-Gitlab-Token)
    # 注意:不同版本的 GitCode 可能用不同字段,若 X-GitCode-Token 无效,尝试 X-Gitlab-Token
    gitcode_token = request.headers.get('X-GitCode-Token') or request.headers.get('X-Gitlab-Token')
    
    # 验证令牌是否存在
    if not gitcode_token:
        logger.error("请求缺少令牌(X-GitCode-Token 或 X-Gitlab-Token)")
        return "缺少令牌", 403
    
    # 验证令牌是否匹配(直接比对,无需哈希计算)
    if gitcode_token != SECRET:
        logger.error(f"令牌错误:收到的令牌是 {gitcode_token},预期是 {SECRET}")
        return "令牌错误", 403

    # 新增日志:令牌验证通过,准备执行脚本
    logger.info("令牌验证通过,开始执行 abuild.sh 脚本")
    
    # 3. 执行脚本:
    result = subprocess.run(
        ['sh', '/var/myblog/abuild.sh'],  # 确保脚本路径和名称正确
        capture_output=True,
        text=True
    )
    
    # 新增日志:记录脚本执行结果(关键!)
    logger.info(f"abuild.sh 执行结果:returncode={result.returncode}")
    logger.info(f"abuild.sh 标准输出:{result.stdout}")
    logger.info(f"abuild.sh 错误输出:{result.stderr}")

    if result.returncode == 0:
        logger.info("博客更新成功,返回 200")
        return "令牌验证通过,博客更新成功!", 200
    else:
        logger.error(f"博客更新失败,脚本错误输出:{result.stderr}")
        return f"令牌验证通过,但脚本执行失败:{result.stderr}", 500

if __name__ == '__main__':
    logger.info("WebHook 服务启动,监听 0.0.0.0:5000")
    # 监听 5000 端口,允许外部访问(与 GitCode WebHook 的 Payload URL 对应)
    app.run(host='0.0.0.0', port=5000)
  • 若需要通过 ip + 端口 的方式访问接口,需要在阿里云上为 5000 端口开放防火墙。

    打开 5000 端口的防火墙后,在日志 webhook.err.log 中会看到很多外部网络的无效请求或恶意扫描相关的日志。是因为 5000 端口是开放了 0.0.0.0:5000 ,整个公网可访问。

  • 若按照本文后面的操作使用 Nginx 反向代理,就可以不开放 5000 端口的防火墙了。

测试运行脚本:(这一步可以跳过,因为后面会使用 supervisor 管理)

  • 使用 nohup 命令后台运行 WebHook 服务:
1
2
3
root@Hanbai-Linux:/var/myblog# nohup python3 /var/myblog/webhook-server.py &
[1] 81630
root@Hanbai-Linux:/var/myblog# nohup: ignoring input and appending output to 'nohup.out'

如上所示输出,webhook-server.py 已经成功在后台启动了,[1] 81630 是服务的进程 ID,nohup: ignoring input and appending output to 'nohup.out' 是正常提示,表示日志会写入当前目录的 nohup.out 文件。

  • 再次校验是否成功运行,查看 webhook-server.py 对应的进程是否存在:
1
2
3
4
root@Hanbai-Linux:/var/myblog# ps -ef | grep webhook-server.py
root       81630   60892  0 18:19 pts/0    00:00:00 python3 /var/myblog/webhook-server.py
root       81688   60892  0 18:21 pts/0    00:00:00 grep --color=auto webhook-server.py
root@Hanbai-Linux:/var/myblog# 
  • 验证接口是否正常响应:
1
2
3
4
# 发送 POST 请求,带上脚本中设置的 SECRET(替换为实际的密钥,比如 123456)
curl -X POST http://localhost:5000/update-blog \
  -H "Content-Type: application/json" \
  -d '{"secret": "952581716"}'  # 这里的 secret 要和脚本中 SECRET 变量一致

3. supervisor 管理接口

3.1 supervisor 配置

nohup 启动的服务,服务器重启后会自动停止,若想让服务开机自启,推荐用 supervisor 管理:

  • 安装 supervisor:
1
sudo apt install -y supervisor
  • 新建配置文件,让 supervisor 管理 webhook 服务:
1
sudo nano /etc/supervisor/conf.d/webhook.conf
  • 粘贴以下内容(修改路径为实际路径):
1
2
3
4
5
6
7
8
[program:webhook-server]
command=python3 /var/myblog/webhook-server.py  ; 服务启动命令
directory=/var/myblog  ; 工作目录(日志会保存在这里)
autostart=true  ; 开机自启
autorestart=true  ; 服务崩溃后自动重启
stderr_logfile=/var/log/webhook.err.log  ; 错误日志
stdout_logfile=/var/log/webhook.out.log  ; 正常日志
user=root  ; 执行服务的用户(用 root 或其他常用的用户)
  • 启动 supervisor 并加载配置:
1
2
3
sudo supervisorctl reload
# 查看服务状态(显示 RUNNING 即正常)
sudo supervisorctl status webhook-server

这样后续服务器重启,webhook 服务会自动启动,无需手动执行 nohup 命令。

3.2 supervisor 重启服务

若修改了 py 脚本后,需要通过 supervisor 重启服务,加载新代码:

1
2
3
# 格式:sudo supervisorctl restart 服务名
#(服务名在 webhook.conf 中定义,即 [program:xxx] 中的 xxx)
sudo supervisorctl restart webhook-server

4. Nginx 反向代理

将 WebHook 请求从 http://IP:5000/update-blog 转发到 https://www.blog.klizzard.top/update-blog,通过 Nginx 处理 HTTPS 并代理到本地 5000 端口的 Flask 服务。

  1. 编辑 Nginx 配置文件(假设博客的 Nginx 配置文件为 /etc/nginx/sites-available/blog):

    1
    
    sudo nano /etc/nginx/sites-available/blog
    
  2. 添加反向代理规则:找到 server_name www.blog.klizzard.top; 对应的 443 服务器块,在 location / { ... } 下方添加 location /update-blog 反向代理配置,修改后如下::

    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
    
    server {
        server_name www.blog.klizzard.top;
           
        # 原有博客静态文件配置
        location / {
            root /var/myblog/blog/_site;     # Jekyll 构建的静态文件路径
            index index.html index.htm;
        }
       
        # 新增:WebHook 反向代理配置(核心内容)
        location /update-blog {
            proxy_pass http://127.0.0.1:5000/update-blog;  # 转发到本地 Flask 服务
            proxy_set_header Host $host;                   # 传递请求域名
            proxy_set_header X-Real-IP $remote_addr;       # 传递真实客户端 IP
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;   # 传递 HTTPS 协议标识
            proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;  # 异常处理
        }
       
        # 原有 SSL 配置(保持不变)
        listen 443 ssl; # managed by Certbot
        ssl_certificate /etc/letsencrypt/live/klizzard.top/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/klizzard.top/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    }
    
  3. 验证 Nginx 配置并重启

    1
    2
    
    sudo nginx -t        # 检查配置语法
    sudo systemctl restart nginx         # 重启 Nginx 生效
    

5. 仓库 WebHook 配置

本文最后其实是采用的 GitCode 作为云仓库进行同步的,因为 Github 在服务器上有时候会拉取失败。

但无论 GitCode 还是 Github,操作步骤和顺序都是一样的。

进入存放博客文章的仓库(就是本地 Typora 编辑文章后提交的仓库),按以下步骤配置 WebHook:

5.1 进入仓库 WebHook 配置页

仓库主页 → 点击顶部「Settings」→ 左侧菜单找到「Webhooks」→ 点击右上角「Add webhook」。

5.2 填写核心配置项

配置项 填写内容
Payload URL (详见下面)
Content type 选择 application/json(与脚本中接收 JSON 数据的逻辑匹配)
Secret 填写 webhook-server.py 脚本中设置的 SECRET 值(比如脚本中写的 123456,必须完全一致,否则鉴权失败)
Which events would you like to trigger this webhook? 选择「Just the push event」(只在 Git 推送代码时触发,避免不必要的请求)
Active 勾选(启用 WebHook)
  • Payload URL:填写 https://[域名]/update-blog,必须用 HTTPS,避免签名泄露。例如:

    https://www.blog.klizzard.top/update-blog

    若没有域名或没有配置 Nginx 反向代理,那就用 “服务器公网 IP + 端口” 来代替,例如:

    http://59.110.31.194:5000/update-blog

5.3 保存并测试

点击底部「Add webhook」保存,GitHub 会自动发送一个 “测试请求” 到服务器。

6. 管理 Linux 日志

因为 supervisor 会写日志,为避免长时间的日志打爆磁盘,通过 logrotate(Linux 系统自带的日志轮转工具),配置 supervisor 日志的自动分割、压缩、清理规则,彻底解决日志膨胀问题。

6.1 创建 logrotate 配置文件

为 supervisor 日志创建专属的 logrotate 规则:

1
sudo nano /etc/logrotate.d/webhook-logs

6.2 写入配置内容(按需调整参数)

写入以下配置,适配日志路径(/var/log/webhook.out.log/var/log/webhook.err.log):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 指定需要轮转的日志文件路径
/var/log/webhook.out.log
/var/log/webhook.err.log
{
    daily          # 轮转频率:每天一次
    rotate 7       # 保留7天的日志(超过7天的自动删除)
    size 10M      # 额外触发条件:日志文件超过100M时,立即轮转(不管是否到每天)
    compress       # 轮转后的旧日志自动压缩(节省磁盘空间)
    delaycompress  # 延迟压缩:只压缩前一天的日志(避免压缩正在写入的日志)
    missingok      # 若日志文件不存在,忽略错误(不报错)
    notifempty     # 日志为空时,不执行轮转
    create 0644 root root  # 新建日志文件的权限和所有者(和原日志一致)
    su root root  # 明确指定用户和组为 root,解决权限安全检查问题
}

使用以下命令直接生成文件内容,避免额外字符导致文件异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sudo sh -c 'cat > /etc/logrotate.d/webhook-logs << EOF
/var/log/webhook.out.log
/var/log/webhook.err.log
{
    daily
    rotate 7
    size 10M
    compress
    delaycompress
    missingok
    notifempty
    create 0644 root root
}
EOF'

6.3 验证配置并手动测试

检查配置语法是否正确,若输出中无报错,说明配置有效:

1
sudo logrotate -d /etc/logrotate.d/webhook-logs

手动触发一次轮转(测试效果):

1
sudo logrotate -f /etc/logrotate.d/webhook-logs

执行后查看 /var/log/ 目录,会生成压缩的旧日志(如 webhook.out.log.1.gz),同时创建新的 webhook.out.log 文件。

6.4 确认 logrotate 自动运行

Linux 系统默认通过 cron 定时执行 logrotate(通常每天凌晨),无需额外配置。可通过以下命令查看定时任务:

1
cat /etc/cron.daily/logrotate

7. 整体流程图

整体流程可以总结为:写文章推git → git通知服务器 → 验证后自动build → 网站即刻更新

流程示意如下:

graph TD
    A[开发者写作并推送代码] --> B[代码仓库<br/>GitCode/GitHub]
    B -->|检测到push事件| C[发送WebHook请求]
    
    C --> D[Nginx接收请求<br/>HTTPS://域名/update-blog]
    D -->|反向代理| E[Flask服务<br/>127.0.0.1:5000]
    
    E -->|验证令牌| F{令牌正确?}
    F -->|是| G[执行更新脚本]
    F -->|否| H[拒绝请求]
    
    G --> I[拉取最新代码]
    I --> J[Jekyll构建静态网站]
    J --> K[更新网站目录]
    K --> L[博客网站内容更新]
    
    M[Supervisor<br/>守护进程] -->|监控| E
    N[Logrotate<br/>日志管理] -->|轮转| O[日志文件]
    
    style A fill:#e1f5fe
    style B fill:#f3e5f5
    style D fill:#e8f5e8
    style E fill:#fff3e0
    style G fill:#fce4ec
    style M fill:#e8eaf6
    style N fill:#e8f5e8

核心流程说明

  • 触发阶段:开发者推送代码 → 代码仓库检测到变化 → 发送 WebHook 通知

  • 接收处理阶段:Nginx 接收请求 → 转发给 Flask 服务 → 验证安全令牌 → 执行更新脚本

  • 更新阶段:拉取代码 → 构建网站 → 更新文件 → 网站内容刷新

  • 运维保障

    • Supervisor:确保 Flask 服务持续运行
    • Logrotate:自动管理日志文件,防止磁盘占满

核心组件关系

组件 作用 类比
WebHook 触发器 门铃(有人按门铃就通知你)
Flask 接收器 管家(听到门铃后处理请求)
Nginx 转发器 前台接待(接收外部请求并转发)
Supervisor 守护者 保安(确保管家一直在岗)
Logrotate 清洁工 保洁(定期清理日志垃圾)

8. 名词解释

8.1 Flask

Flask 是一个轻量级的 Python Web 框架,用于快速构建 Web 应用和 API 接口。本文使用 Flask 创建了一个 WebHook 接收服务,当 GitCode 检测到代码推送时,会向这个 Flask 服务发送 HTTP 请求,触发博客更新的自动化流程。

特点:

  • 轻量简单:核心功能简洁,易于上手,适合小型项目和微服务
  • 灵活可扩展:通过插件可以轻松添加数据库、用户认证等功能
  • 开发快速:几行代码就能创建一个可运行的 Web 服务

8.2 WebHook

WebHook 是一种 “反向 API” 机制,允许一个应用程序在特定事件发生时,主动向另一个应用程序发送实时通知。本文通过 WebHook 实现了博客的自动部署:当本地写完文章并推送到 GitCode,服务器会自动获取最新内容并更新网站,无需手动登录服务器操作。

工作原理:

  1. 订阅事件:在服务器上创建一个接收端点(比如 https://你的域名/update-blog
  2. 配置触发:在 GitCode/GitHub 上配置,当有代码推送(push)时,就向这个端点发送 POST 请求
  3. 自动执行:服务器收到请求后,执行预设的脚本(如拉取最新代码、重新构建博客)

与传统轮询的区别:

  • 轮询:客户端不断询问服务器 “有新内容吗?”(效率低,实时性差)
  • WebHook:服务器主动说 “有新内容了!”(效率高,实时性好)

8.3 Supervisor

Supervisor 是一个用 Python 开发的进程管理工具,用于监控和控制后台进程。

主要功能:

  • 进程守护:确保关键服务持续运行,如果进程意外退出会自动重启
  • 开机自启:配置后,服务会在系统启动时自动运行
  • 日志管理:集中收集服务的输出和错误日志
  • Web界面:提供 Web 管理界面(可选)查看和控制进程状态

在本文中 Supervisor 用于管理 Flask WebHook 服务:

  • 保证 webhook-server.py 持续运行,即使崩溃也会自动重启
  • 服务器重启后,Flask 服务会自动启动,无需手动操作
  • 集中管理日志,便于排查问题

基本命令:

1
2
3
sudo supervisorctl status webhook-server  # 查看服务状态
sudo supervisorctl restart webhook-server  # 重启服务
sudo supervisorctl reload  # 重新加载配置文件

8.4 Logrotate

Logrotate 是 Linux 系统中用于管理日志文件的工具,负责自动轮转、压缩、删除和重新创建日志文件。

Logrotate 通过系统的 cron 定时任务每天运行一次,检查配置规则,对符合条件的日志文件执行轮转操作。

主要功能:

  • 日志轮转:定期将当前日志文件重命名(如 webhook.out.logwebhook.out.log.1),创建新的空日志文件
  • 压缩归档:对旧的日志文件进行压缩(生成 .gz 文件),节省磁盘空间
  • 自动清理:按配置保留一定数量的历史日志,删除过期的日志文件
  • 无需重启服务:大多数情况下可以平滑切换日志文件,不影响正在运行的服务

为什么需要 Logrotate?

日志文件会随时间不断增长,如果不加管理会有以下问题:

  • 可能占用大量磁盘空间,导致系统故障
  • 单个大文件难以查看和分析
  • 影响系统性能

在本文中 Logrotate 用于管理 Supervisor 产生的日志文件:

  • 每天自动轮转 webhook.out.logwebhook.err.log
  • 保留最近 7 天的日志
  • 当日志超过 10MB 时立即轮转
  • 压缩旧日志以节省空间
本文由作者按照 CC BY 4.0 进行授权