# Ubuntu/Debian
sudo apt update
sudo apt install -y python3 python3-pip python3-venv git
# CentOS
sudo yum install -y python3 python3-pip git
#创建并移动目录
mkdir -p /haproxy-api/{backups,venv}
cd /haproxy-api
#创建 app.py 主程序
nano /haproxy-api/app.py
from flask import Flask, request, jsonify, make_response
import subprocess
import os
import shutil
import re
import fcntl
from datetime import datetime, timedelta
from threading import Lock
app = Flask(__name__)
# ========== 配置区域 ==========
API_KEY = "1a912afe9d0e7b7120b22d74ab0de81d" # 务必修改此密钥!
HAPROXY_CFG_PATH = "/etc/haproxy/haproxy.cfg" # 原配置文件位置
BACKUP_DIR = "/home/ubuntu/haproxy-api/backups" # 备份目录
TMP_DIR = "/home/ubuntu/haproxy-api/tmp" # 临时文件目录
LOCK_FILE = "/home/ubuntu/haproxy-api/lockfile" # 锁文件路径
MAX_BACKUP_DAYS = 7 # 保留最近7天备份
# =============================
# 初始化目录
os.makedirs(BACKUP_DIR, exist_ok=True)
os.makedirs(TMP_DIR, exist_ok=True)
# 初始化锁
memory_lock = Lock()
def auth_required(func):
"""API Key 认证装饰器"""
def wrapper(*args, **kwargs):
if request.headers.get('X-API-KEY') != API_KEY:
return jsonify({"error": "Unauthorized"}), 401
return func(*args, **kwargs)
wrapper.__name__ = func.__name__ # 关键修复行
return wrapper
# ----------------------------
# 核心功能函数
# ----------------------------
def run_command(cmd):
try:
result = subprocess.run(
cmd, shell=True, check=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
timeout=10
)
return {"success": True, "output": result.stdout}
except subprocess.CalledProcessError as e:
return {"success": False, "output": e.stderr}
except subprocess.TimeoutExpired:
return {"success": False, "output": "Command timed out"}
def create_backup():
"""备份当前正在运行的配置文件"""
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
backup_path = os.path.join(BACKUP_DIR, f"haproxy.cfg.bak.{timestamp}")
# 从原位置复制到备份目录
shutil.copy2(HAPROXY_CFG_PATH, backup_path)
cleanup_old_backups()
return backup_path
def cleanup_old_backups():
"""清理过期备份"""
now = datetime.now()
for filename in os.listdir(BACKUP_DIR):
if re.match(r"^haproxy\\.cfg\\.bak\\.\\d{14}$", filename):
timestamp_str = filename.split(".")[-1]
try:
file_date = datetime.strptime(timestamp_str, "%Y%m%d%H%M%S")
if (now - file_date) > timedelta(days=MAX_BACKUP_DAYS):
os.remove(os.path.join(BACKUP_DIR, filename))
except ValueError:
pass
def sanitize_output(data):
"""清理所有换行和回车"""
if isinstance(data, dict):
return {k: sanitize_output(v) for k, v in data.items()}
elif isinstance(data, str):
return data.replace('\\n', ' ').replace('\\r', '')
return data
# ----------------------------
# API 接口
# ----------------------------
@app.route('/get_config', methods=['GET'])
@auth_required
def get_config():
try:
with open(HAPROXY_CFG_PATH, 'r') as f:
return make_response(f.read(), 200, {'Content-Type': 'text/plain'})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/update_haproxy', methods=['POST'])
@auth_required
def update_haproxy():
# 内存锁
if not memory_lock.acquire(blocking=False):
return jsonify({"error": "Another request is processing"}), 429
try:
# 文件锁
lock_file = open(LOCK_FILE, 'w')
try:
fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
return jsonify({"error": "System busy, please retry later"}), 429
# 获取新配置
new_config = request.json.get("config")
if not new_config:
return jsonify({"error": "Missing 'config' in JSON body"}), 400
# 创建备份(从原位置复制到备份目录)
try:
create_backup()
except Exception as e:
return jsonify({"error": f"Backup failed: {str(e)}"}), 500
# 生成临时文件(在项目tmp目录)
tmp_path = os.path.join(TMP_DIR, f"haproxy.cfg.tmp.{os.getpid()}")
try:
# 写入临时文件
with open(tmp_path, 'w') as f:
f.write(new_config)
# 验证配置
check_result = run_command(f"sudo haproxy -f {tmp_path} -c")
if not check_result['success']:
raise Exception(f"Config invalid: {check_result['output']}")
# 用sudo覆盖原配置(需要配置sudo权限)
copy_result = run_command(f"sudo cp {tmp_path} {HAPROXY_CFG_PATH}")
if not copy_result['success']:
raise Exception("Replace config failed")
# 重载服务
reload_result = run_command("sudo systemctl reload haproxy")
status_result = run_command("sudo systemctl is-active haproxy")
return jsonify({
"check": sanitize_output(check_result),
"copy": sanitize_output(copy_result),
"reload": sanitize_output(reload_result),
"status": sanitize_output(status_result)
})
finally:
if os.path.exists(tmp_path):
os.remove(tmp_path)
finally:
fcntl.flock(lock_file, fcntl.LOCK_UN)
lock_file.close()
memory_lock.release()
if __name__ == '__main__':
app.run(host='0.0.0.0', port=12001)
#设置 HAProxy 配置文件权限
sudo chmod 644 /etc/haproxy/haproxy.cfg # 确保可读
#配置 sudoers 权限(重要!)确保python 有权限修改haproxy的配置文件
sudo visudo
#在文件末尾 添加以下内容(按 i 进入编辑模式):
www-data ALL=(ALL) NOPASSWD: /usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -c, /bin/systemctl reload haproxy, /bin/systemctl is-active ha>
#按 Esc → 输入 :wq 保存退出
#创建虚拟环境
python3 -m venv venv
#进入虚拟环境(下面安装和运行的代码需要在虚拟环境中执行)
source venv/bin/activate
pip install flask
**pip install gunicorn
#下面根据自己需求选择模式:**
#退出虚拟环境的命令代码:deactivate
#方案一:第一种测试模式:直接启动服务(需要进入虚拟环境:source venv/bin/activate)
python app.py
#方案二:生产环境运行(推荐)(需要进入虚拟环境:source venv/bin/activate)
gunicorn -w 4 -b 0.0.0.0:12001 app:app
#第三种,系统服务自启动方式(最推荐)下面是服务启动的部分
sudo nano /etc/systemd/system/haproxy-api.service
#添加以下内容,下面是正确配置,主要是User和Group(root或者ubuntu)看系统用户到底是啥
[Unit]
Description=HAProxy Config API
After=network.target
[Service]
User=root
Group=root
WorkingDirectory=/home/ubuntu/haproxy-api
Environment="PATH=/home/ubuntu/haproxy-api/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
ExecStart=/home/ubuntu/haproxy-api/venv/bin/gunicorn \\
-w 4 \\
-b 0.0.0.0:7678 \\
--access-logfile - \\
app:app
Restart=always
RestartSec=5
# 权限配置(关键!)
ReadWritePaths=/etc/haproxy /home/ubuntu/haproxy-api
ProtectSystem=strict
[Install]
WantedBy=multi-user.target
#配置结束-------------------------
#启用服务:
sudo systemctl daemon-reload
sudo systemctl start haproxy-api
sudo systemctl enable haproxy-api
#------------------------------
#管理命令
# 查看状态
sudo systemctl status haproxy-api
sudo systemctl **is-active** haproxy-api
# 停止服务
sudo systemctl stop haproxy-api
# 查看日志
journalctl -u haproxy-api -f
# 查看 HAProxy 日志
tail -f /var/log/syslog | grep haproxy
# 查看服务状态
journalctl -u haproxy.service -f
#------------------------------
#要让 Flask 应用在后台长期稳定运行,推荐以下几种专业解决方案,按场景选择最适合你的方式:
#方案一:使用 nohup 命令(快速临时方案)
nohup python -u app.py > app.log 2>&1 &
#-u 参数确保日志实时写入
#> app.log 将标准输出重定向到日志文件
#2>&1 将错误输出合并到标准输出
#& 表示后台运行
#停止命令:pkill -f "python app.py"
#方案二:使用生产级 WSGI 服务器(推荐生产环境)
#1. 安装 Gunicorn
pip install gunicorn
#2. 启动服务
gunicorn -w 4 -b 0.0.0.0:5000 --daemon app:app
#-w 4:使用4个工作进程(根据CPU核心数调整)
#-b 0.0.0.0:5000:监听所有网卡的5000端口
#--daemon:以守护进程模式运行
#停止命令:pkill -f "gunicorn app:app"
#方案三:使用进程管理工具(适合开发测试)
#1. 安装 tmux(终端复用工具)
sudo apt-get install tmux # Debian/Ubuntu
brew install tmux # macOS
# 使用
tmux new -s myapp # 创建新会话
python app.py # 启动应用
Ctrl+B 然后按 D # 分离会话(应用继续运行)
# 重新连接
tmux attach -t myapp
#方案四:配置 systemd 服务(Linux 系统级管理)
#1. 创建服务文件
sudo nano /etc/systemd/system/flaskapp.service
#2. 写入配置内容
#ini:
[Unit]
Description=Flask App Service
After=network.target
[Service]
User=www-data # 指定运行用户
Group=www-data # 指定运行组
WorkingDirectory=/path/to/your/app
ExecStart=/usr/local/bin/gunicorn -w 4 -b 0.0.0.0:5000 app:app
Restart=always
[Install]
WantedBy=multi-user.target
#3. 管理命令
sudo systemctl daemon-reload
sudo systemctl start flaskapp
sudo systemctl enable flaskapp # 开机自启
sudo systemctl status flaskapp # 查看状态
#方案五:Docker 容器化部署(推荐生产环境)
#1. 创建 Dockerfile
#dockerfile:
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"]
#2. 构建并运行
docker build -t myflaskapp .
docker run -d -p 5000:5000 --name myapp myflaskapp
#关键注意事项:
#生产环境必须使用 Gunicorn/uWSGI:Flask 自带的开发服务器不适合生产环境
#日志管理:建议配置日志轮转(如使用 logrotate)
#进程监控:可配合 supervisord 实现进程守护
#反向代理:生产环境应通过 Nginx/Apache 代理(配置示例):
#nginx:
server {
listen 80;
server_name example.com;
location / {
proxy_pass <http://127.0.0.1:5000>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
#根据你的使用场景选择方案:
#临时测试 → 方案一
#个人项目 → 方案二或三
#生产环境 → 方案四+五
#建议新手从方案二(Gunicorn)开始尝试,这是最简单有效的生产环境部署方式。
<aside> 💡
GET: **http://localhost:12001/get_config**
headers:{
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36
X-API-KEY: 1a912afe9d0e7b7120b22d74ab0de81d
}
</aside>
<aside> 💡
POST: **http://localhost:12001/update_haproxy**
headers:{
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36
X-API-KEY: 1a912afe9d0e7b7120b22d74ab0de81d
Content-Type: application/json
}
data:{"config":"global\n log /dev/log local0\n log /dev/log local1 notice\n chroot /var/lib/haproxy\n stats socket /run/haproxy/admin.sock mode 660 level admin\n stats timeout 30s\n user haproxy\n group haproxy\n daemon\n maxconn 5000\n tune.ssl.default-dh-param 2048\n\ndefaults\n mode tcp\n log global\n option tcp-check\n timeout connect 5s\n timeout client 50s\n timeout server 50s\n retries 3\n maxconn 5000\n\nfrontend tcp_frontend_12001\n bind *:12001\n use_backend bk_11759\n\nfrontend tcp_frontend_12002\n bind *:12002\n use_backend bk_12002\n\nfrontend tcp_frontend_12003\n bind *:12003\n use_backend bk_12003\n\nfrontend tcp_frontend_12004\n bind *:12004\n use_backend bk_12004\n\nfrontend tcp_frontend_12005\n bind *:12005\n use_backend bk_12005\n\nfrontend tcp_frontend_12006\n bind *:12006\n use_backend bk_12006\n\nfrontend tcp_frontend_12007\n bind *:12007\n use_backend bk_12007\n\nfrontend tcp_frontend_12008\n bind *:12008\n use_backend bk_12008\n\nfrontend tcp_frontend_yushion_12345\n bind *:12345\n use_backend bk_8080\n\nfrontend tcp_frontend_user_3000\n bind *:3000\n use_backend bk_3000\n\nfrontend tcp_frontend_yushion_12010\n bind *:12010\n use_backend bk_12010\n\nfrontend tcp_frontend_user_12071\n bind *:12071\n use_backend bk_12071\n\nfrontend tcp_frontend_user_49200\n bind *:49200\n use_backend bk_0\n\nfrontend tcp_frontend_user_49201\n bind *:49201\n use_backend bk_49201\n\nfrontend tcp_frontend_user_49202\n bind *:49202\n use_backend bk_49202\n\nfrontend tcp_frontend_user_49203\n bind *:49203\n use_backend bk_49203\n\nbackend bk_11759\n option tcp-check\n server server11759 12.tcp.cpolar.top:11759 check maxconn 2000\n\nbackend bk_12002\n option tcp-check\n server server12002 12.tcp.cpolar.top:11759 check maxconn 2000\n\nbackend bk_12003\n option tcp-check\n server server12003 127.0.0.1:12003 check maxconn 2000\n\nbackend bk_12004\n option tcp-check\n server server12004 127.0.0.1:12004 check maxconn 2000\n\nbackend bk_12005\n option tcp-check\n server server12005 127.0.0.1:12005 check maxconn 2000\n\nbackend bk_12006\n option tcp-check\n server server12006 127.0.0.1:12006 check maxconn 2000\n\nbackend bk_12007\n option tcp-check\n server server12007 127.0.0.1:12007 check maxconn 2000\n\nbackend bk_12008\n option tcp-check\n server server12008 127.0.0.1:12008 check maxconn 2000\n\nbackend bk_8080\n option tcp-check\n server server8080 125.36.45.98:8080 check maxconn 2000\n\nbackend bk_3000\n option tcp-check\n server server3000 example.com:3000 check maxconn 2000\n\nbackend bk_12010\n option tcp-check\n server server12010 254.64.95.61:12010 check maxconn 2000\n\nbackend bk_12071\n option tcp-check\n server server12071 127.0.0.1:12071 check maxconn 2000\n\nbackend bk_0\n option tcp-check\n server server0 127.0.0.1:0 check maxconn 2000\n\nbackend bk_49201\n option tcp-check\n server server49201 127.0.0.1:49201 check maxconn 2000\n\nbackend bk_49202\n option tcp-check\n server server49202 127.0.0.1:49202 check maxconn 2000\n\nbackend bk_49203\n option tcp-check\n server server49203 127.0.0.1:49203 check maxconn 2000\n"}
</aside>