1. 为什么需要Gunicorn启停脚本?
每次手动敲命令启动Gunicorn实在太麻烦了。我在部署Python Web应用时,经常需要反复重启服务进行调试,手动输入一长串命令不仅效率低下,还容易出错。更糟的是,当服务器意外重启后,还得SSH登录上去重新启动服务。
一个典型的Gunicorn启动命令可能是这样的:
bash复制gunicorn -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 main:app
想象一下,每次修改代码后都要重新输入这行命令,还要记住各种参数。这促使我开发了一套完整的启停管理脚本,现在分享给各位Python开发者。
2. 基础脚本实现
2.1 最简单的启动脚本
我们先从最基础的版本开始。创建一个run.sh文件:
bash复制#!/bin/bash
# 定义应用名称和端口
APP_NAME="myapp"
PORT=8000
WORKERS=4
echo "Starting $APP_NAME..."
gunicorn -w $WORKERS -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:$PORT main:app
给脚本添加执行权限:
bash复制chmod +x run.sh
这个基础版本已经能解决手动输入命令的问题,但还有很大改进空间。
2.2 添加停止功能
让我们扩展脚本,加入停止功能。创建manager.sh:
bash复制#!/bin/bash
APP_NAME="myapp"
PID_FILE="/tmp/gunicorn_${APP_NAME}.pid"
start_server() {
echo "Starting $APP_NAME..."
gunicorn -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 main:app \
--pid $PID_FILE \
--daemon
echo "Server started with PID $(cat $PID_FILE)"
}
stop_server() {
if [ -f "$PID_FILE" ]; then
echo "Stopping $APP_NAME..."
kill $(cat $PID_FILE)
rm $PID_FILE
echo "Server stopped"
else
echo "PID file not found. Is server running?"
fi
}
case "$1" in
start)
start_server
;;
stop)
stop_server
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
esac
现在你可以这样使用:
bash复制./manager.sh start # 启动服务
./manager.sh stop # 停止服务
3. 高级功能实现
3.1 状态检查与重启
让我们添加更多实用功能:
bash复制#!/bin/bash
# ...之前的变量和函数...
status_server() {
if [ -f "$PID_FILE" ]; then
PID=$(cat $PID_FILE)
if ps -p $PID > /dev/null; then
echo "$APP_NAME is running (PID: $PID)"
return 0
else
echo "PID file exists but process not found"
return 1
fi
else
echo "$APP_NAME is not running"
return 1
fi
}
restart_server() {
stop_server
sleep 2
start_server
}
case "$1" in
start)
start_server
;;
stop)
stop_server
;;
status)
status_server
;;
restart)
restart_server
;;
*)
echo "Usage: $0 {start|stop|status|restart}"
exit 1
esac
3.2 日志记录
生产环境必须要有日志。修改start_server函数:
bash复制start_server() {
LOG_DIR="/var/log/${APP_NAME}"
ACCESS_LOG="${LOG_DIR}/access.log"
ERROR_LOG="${LOG_DIR}/error.log"
mkdir -p $LOG_DIR
echo "Starting $APP_NAME..."
gunicorn -w 4 -k uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 main:app \
--pid $PID_FILE \
--daemon \
--access-logfile $ACCESS_LOG \
--error-logfile $ERROR_LOG \
--capture-output \
--log-level info
echo "Server started with PID $(cat $PID_FILE)"
echo "Logs: $ACCESS_LOG and $ERROR_LOG"
}
4. 生产环境增强版
4.1 配置文件支持
创建一个gunicorn_config.py:
python复制import multiprocessing
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
pidfile = "/tmp/gunicorn_myapp.pid"
daemon = True
accesslog = "/var/log/myapp/access.log"
errorlog = "/var/log/myapp/error.log"
capture_output = True
loglevel = "info"
然后简化脚本:
bash复制#!/bin/bash
APP_NAME="myapp"
CONFIG_FILE="$(dirname "$0")/gunicorn_config.py"
start_server() {
echo "Starting $APP_NAME..."
gunicorn -c $CONFIG_FILE main:app
echo "Server started"
}
4.2 环境变量管理
使用.env文件管理配置:
bash复制#!/bin/bash
# 加载环境变量
if [ -f .env ]; then
export $(cat .env | xargs)
fi
APP_NAME=${APP_NAME:-"myapp"}
PORT=${PORT:-8000}
WORKERS=${WORKERS:-4}
4.3 系统服务集成
创建systemd服务文件/etc/systemd/system/gunicorn_myapp.service:
ini复制[Unit]
Description=Gunicorn instance for myapp
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/path/to/your/app
Environment="PATH=/path/to/venv/bin"
ExecStart=/path/to/venv/bin/gunicorn -c /path/to/gunicorn_config.py main:app
[Install]
WantedBy=multi-user.target
然后启用服务:
bash复制sudo systemctl daemon-reload
sudo systemctl start gunicorn_myapp
sudo systemctl enable gunicorn_myapp
5. 实用技巧与问题排查
5.1 常见问题解决
问题1:端口被占用
bash复制netstat -tulnp | grep 8000
kill -9 <PID>
问题2:权限不足
bash复制sudo chown -R www-data:www-data /var/log/myapp
问题3:虚拟环境问题
确保脚本中使用了正确的Python解释器路径:
bash复制/path/to/venv/bin/gunicorn ...
5.2 性能调优建议
- worker数量公式:
bash复制workers = (2 x $num_cores) + 1
- 使用gevent提高并发:
python复制worker_class = "gevent"
worker_connections = 1000
- 超时设置:
python复制timeout = 30
keepalive = 2
5.3 监控与维护
添加健康检查端点:
python复制# 在main.py中添加
@app.route('/health')
def health():
return {"status": "healthy"}
然后可以定期检查:
bash复制curl -I http://localhost:8000/health
6. 完整脚本示例
最后分享一个我在生产环境中使用的完整版本:
bash复制#!/bin/bash
# 加载环境配置
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/.env"
# 配置参数
APP_NAME=${APP_NAME:-"myapp"}
APP_DIR=${APP_DIR:-"${SCRIPT_DIR}"}
VENV_PATH=${VENV_PATH:-"${APP_DIR}/venv"}
GUNICORN_PATH=${GUNICORN_PATH:-"${VENV_PATH}/bin/gunicorn"}
CONFIG_FILE=${CONFIG_FILE:-"${APP_DIR}/gunicorn_config.py"}
PID_FILE=${PID_FILE:-"/tmp/gunicorn_${APP_NAME}.pid"}
LOG_DIR=${LOG_DIR:-"/var/log/${APP_NAME}"}
USER=${USER:-"www-data"}
GROUP=${GROUP:-"www-data"}
# 确保日志目录存在
mkdir -p "${LOG_DIR}"
chown -R ${USER}:${GROUP} "${LOG_DIR}"
start() {
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p $PID > /dev/null; then
echo "Server is already running (PID: $PID)"
return 1
fi
fi
echo "Starting ${APP_NAME}..."
cd "${APP_DIR}" || exit 1
sudo -u ${USER} ${GUNICORN_PATH} -c "${CONFIG_FILE}" main:app
echo "Server started with PID $(cat ${PID_FILE})"
}
stop() {
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
echo "Stopping ${APP_NAME} (PID: $PID)..."
kill $PID
rm "$PID_FILE"
echo "Server stopped"
else
echo "PID file not found. Is server running?"
fi
}
restart() {
stop
sleep 2
start
}
status() {
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p $PID > /dev/null; then
echo "${APP_NAME} is running (PID: $PID)"
return 0
else
echo "PID file exists but process not found"
return 1
fi
else
echo "${APP_NAME} is not running"
return 1
fi
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
status
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
esac
这个脚本包含了我在多年Python Web开发中积累的各种经验,特别是处理了生产环境中常见的权限、路径和进程管理问题。建议保存为gunicorn_manager.sh,然后通过以下方式使用:
bash复制# 首次运行前设置权限
chmod +x gunicorn_manager.sh
# 使用示例
./gunicorn_manager.sh start
./gunicorn_manager.sh status
./gunicorn_manager.sh stop
./gunicorn_manager.sh restart
在实际使用中,我发现有几点特别需要注意:
- 确保PID文件的路径对所有相关用户可写
- 日志目录要提前创建并设置正确权限
- 使用完整的路径引用,避免相对路径问题
- 生产环境建议配合supervisor或systemd使用
