Gunicorn启停脚本开发与生产环境实践 1. 为什么需要Gunicorn启停脚本在Python Web开发中Gunicorn作为WSGI HTTP服务器几乎是生产环境的标准选择。但每次手动输入完整的启动命令既繁琐又容易出错特别是在需要频繁重启的开发阶段。一个典型的Gunicorn启动命令可能长这样gunicorn -w 4 -k gevent -b 0.0.0.0:8000 --access-logfile - --error-logfile - --pid /tmp/gunicorn.pid myapp:app想象一下每天要输入这种命令十几次的场景。更糟的是当需要停止服务时你还得先找到PID文件再kill进程。这就是为什么我们需要一个智能化的启停脚本——它不仅能简化操作还能避免人为失误导致的服务异常。2. 基础版启停脚本实现2.1 脚本框架设计我们先从最基础的版本开始创建一个名为gunicorn_manager.sh的文件#!/bin/bash APP_NAMEmyapp APP_MODULE${APP_NAME}:app PID_FILE/tmp/gunicorn.pid LOG_FILE/var/log/gunicorn.log GUNICORN_CMDgunicorn -w 4 -k gevent -b 0.0.0.0:8000 --access-logfile ${LOG_FILE} --error-logfile ${LOG_FILE} --pid ${PID_FILE} ${APP_MODULE} case $1 in start) echo Starting Gunicorn... $GUNICORN_CMD ;; stop) echo Stopping Gunicorn... kill -TERM cat ${PID_FILE} ;; *) echo Usage: $0 {start|stop} exit 1 esac这个脚本已经实现了基本功能通过start参数启动服务通过stop参数停止服务将日志统一输出到指定文件使用PID文件管理进程2.2 使用方式与权限设置给脚本添加执行权限chmod x gunicorn_manager.sh启动服务./gunicorn_manager.sh start停止服务./gunicorn_manager.sh stop注意首次运行前请确保已创建日志文件目录并设置适当权限sudo mkdir -p /var/log/ sudo touch /var/log/gunicorn.log sudo chown whoami /var/log/gunicorn.log3. 增强版脚本功能实现基础版虽然能用但缺乏健壮性。我们来逐步添加生产环境需要的功能。3.1 状态检查与进程守护添加status和restart命令并实现进程存在性检查#!/bin/bash # ...保留之前的变量定义 is_running() { [ -f $PID_FILE ] kill -0 cat $PID_FILE /dev/null 21 } case $1 in start) if is_running; then echo Gunicorn is already running exit 1 fi echo Starting Gunicorn... $GUNICORN_CMD ;; stop) if ! is_running; then echo Gunicorn is not running exit 1 fi echo Stopping Gunicorn... kill -TERM cat ${PID_FILE} ;; restart) $0 stop sleep 2 $0 start ;; status) if is_running; then echo Gunicorn is running (PID: cat ${PID_FILE}) else echo Gunicorn is not running fi ;; *) echo Usage: $0 {start|stop|restart|status} exit 1 esac新增功能status命令检查服务状态restart命令实现重启防止重复启动或停止不存在的服务使用kill -0检查进程是否存在3.2 日志轮转与后台运行生产环境还需要以下改进#!/bin/bash # ...保留之前的定义 # 新增日志轮转配置 LOG_ROTATE_SIZE10M # 日志文件达到10MB时轮转 LOG_BACKUP_COUNT5 # 保留5个历史日志文件 rotate_logs() { if [ -f $LOG_FILE ] [ stat -c %s $LOG_FILE -gt $(( ${LOG_ROTATE_SIZE%M} * 1024 * 1024 )) ]; then for i in $(seq $((LOG_BACKUP_COUNT-1)) -1 1); do [ -f ${LOG_FILE}.$i ] mv ${LOG_FILE}.$i ${LOG_FILE}.$((i1)) done mv $LOG_FILE ${LOG_FILE}.1 touch $LOG_FILE fi } case $1 in start) if is_running; then echo Gunicorn is already running exit 1 fi rotate_logs echo Starting Gunicorn... nohup $GUNICORN_CMD /dev/null 21 echo Gunicorn started (PID: $!) ;; # ...其他命令保持不变 esac关键改进使用nohup实现真正的后台运行自动日志轮转防止日志文件过大启动后显示进程PID4. 生产环境最佳实践4.1 多环境配置支持实际项目中我们通常需要区分开发、测试和生产环境。改进脚本支持环境变量#!/bin/bash # 默认环境配置 ENV${ENV:-production} CONFIG_DIR/etc/gunicorn CONFIG_FILE${CONFIG_DIR}/${ENV}.conf # 加载环境特定配置 if [ -f $CONFIG_FILE ]; then source $CONFIG_FILE else echo Warning: Config file not found for environment $ENV, using defaults fi # 默认值 APP_NAME${APP_NAME:-myapp} WORKERS${WORKERS:-4} BIND_ADDR${BIND_ADDR:-0.0.0.0:8000} # ...其他变量同理 GUNICORN_CMDgunicorn -w ${WORKERS} -k ${WORKER_CLASS} -b ${BIND_ADDR} --access-logfile ${ACCESS_LOG} --error-logfile ${ERROR_LOG} --pid ${PID_FILE} ${APP_MODULE}然后创建不同环境的配置文件如/etc/gunicorn/development.conf# 开发环境配置 WORKERS2 ACCESS_LOG/var/log/gunicorn_dev.access.log ERROR_LOG/var/log/gunicorn_dev.error.log使用方式ENVdevelopment ./gunicorn_manager.sh start4.2 系统服务集成为了让脚本能在系统启动时自动运行我们可以创建systemd服务文件/etc/systemd/system/gunicorn.service[Unit] DescriptionGunicorn application server Afternetwork.target [Service] Userwww-data Groupwww-data WorkingDirectory/path/to/your/app EnvironmentENVproduction ExecStart/path/to/gunicorn_manager.sh start ExecStop/path/to/gunicorn_manager.sh stop ExecReload/path/to/gunicorn_manager.sh restart Restarton-failure [Install] WantedBymulti-user.target然后启用服务sudo systemctl daemon-reload sudo systemctl enable gunicorn sudo systemctl start gunicorn5. 高级技巧与故障排查5.1 优雅停机与超时控制默认的kill -TERM可能无法在所有情况下正常关闭工作进程。改进停止逻辑stop() { if ! is_running; then echo Gunicorn is not running return 1 fi echo Stopping Gunicorn gracefully... kill -TERM cat ${PID_FILE} # 等待最多30秒 TIMEOUT30 while is_running [ $TIMEOUT -gt 0 ]; do sleep 1 TIMEOUT$((TIMEOUT-1)) done if is_running; then echo Force stopping Gunicorn... kill -9 cat ${PID_FILE} rm -f $PID_FILE else echo Gunicorn stopped gracefully fi }5.2 常见问题排查问题1PID文件存在但进程已死解决方案在检查进程状态时同时验证PID文件内容is_running() { if [ ! -f $PID_FILE ]; then return 1 fi local pid$(cat $PID_FILE) if ! kill -0 $pid /dev/null 21; then # 清理无效PID文件 rm -f $PID_FILE return 1 fi return 0 }问题2端口已被占用解决方案启动前检查端口port_in_use() { local port$(echo $BIND_ADDR | awk -F: {print $2}) netstat -tuln | grep -q :$port } case $1 in start) if port_in_use; then echo Error: Port ${BIND_ADDR##*:} is already in use exit 1 fi # ...其余启动逻辑 ;; esac问题3依赖缺失解决方案启动前检查Python环境和依赖check_dependencies() { if ! command -v gunicorn /dev/null 21; then echo Error: gunicorn is not installed exit 1 fi if ! python -c import ${APP_NAME} /dev/null 21; then echo Error: Python application ${APP_NAME} cannot be imported exit 1 fi } case $1 in start|restart) check_dependencies # ...其余逻辑 ;; esac6. 完整脚本示例结合所有改进点最终的脚本可能如下#!/bin/bash # 配置部分 ENV${ENV:-production} CONFIG_DIR/etc/gunicorn CONFIG_FILE${CONFIG_DIR}/${ENV}.conf # 加载环境配置 [ -f $CONFIG_FILE ] source $CONFIG_FILE # 默认值 APP_NAME${APP_NAME:-myapp} APP_MODULE${APP_MODULE:-${APP_NAME}:app} WORKERS${WORKERS:-4} WORKER_CLASS${WORKER_CLASS:-gevent} BIND_ADDR${BIND_ADDR:-0.0.0.0:8000} PID_FILE${PID_FILE:-/tmp/gunicorn.pid} ACCESS_LOG${ACCESS_LOG:-/var/log/gunicorn.access.log} ERROR_LOG${ERROR_LOG:-/var/log/gunicorn.error.log} LOG_ROTATE_SIZE${LOG_ROTATE_SIZE:-10M} LOG_BACKUP_COUNT${LOG_BACKUP_COUNT:-5} GUNICORN_CMDgunicorn -w ${WORKERS} -k ${WORKER_CLASS} -b ${BIND_ADDR} --access-logfile ${ACCESS_LOG} --error-logfile ${ERROR_LOG} --pid ${PID_FILE} ${APP_MODULE} # 函数定义 is_running() { [ -f $PID_FILE ] kill -0 cat $PID_FILE /dev/null 21 } port_in_use() { local port$(echo $BIND_ADDR | awk -F: {print $2}) netstat -tuln | grep -q :$port } check_dependencies() { if ! command -v gunicorn /dev/null 21; then echo Error: gunicorn is not installed exit 1 fi if ! python -c import ${APP_NAME} /dev/null 21; then echo Error: Python application ${APP_NAME} cannot be imported exit 1 fi } rotate_logs() { for logfile in $ACCESS_LOG $ERROR_LOG; do if [ -f $logfile ] [ stat -c %s $logfile -gt $(( ${LOG_ROTATE_SIZE%M} * 1024 * 1024 )) ]; then for i in $(seq $((LOG_BACKUP_COUNT-1)) -1 1); do [ -f ${logfile}.$i ] mv ${logfile}.$i ${logfile}.$((i1)) done mv $logfile ${logfile}.1 touch $logfile fi done } stop() { if ! is_running; then echo Gunicorn is not running return 1 fi echo Stopping Gunicorn gracefully... kill -TERM cat ${PID_FILE} TIMEOUT30 while is_running [ $TIMEOUT -gt 0 ]; do sleep 1 TIMEOUT$((TIMEOUT-1)) done if is_running; then echo Force stopping Gunicorn... kill -9 cat ${PID_FILE} rm -f $PID_FILE else echo Gunicorn stopped gracefully fi } # 主逻辑 case $1 in start) if is_running; then echo Gunicorn is already running (PID: cat ${PID_FILE}) exit 1 fi if port_in_use; then echo Error: Port ${BIND_ADDR##*:} is already in use exit 1 fi check_dependencies rotate_logs echo Starting Gunicorn for ${APP_NAME} in ${ENV} environment... nohup $GUNICORN_CMD /dev/null 21 echo Gunicorn started (PID: $!) ;; stop) stop ;; restart) $0 stop sleep 2 $0 start ;; status) if is_running; then echo Gunicorn is running (PID: cat ${PID_FILE}) else echo Gunicorn is not running fi ;; *) echo Usage: $0 {start|stop|restart|status} echo Environment variables: echo ENV Runtime environment (default: production) echo APP_NAME Python application name (default: myapp) echo BIND_ADDR Binding address (default: 0.0.0.0:8000) exit 1 esac exit 0这个脚本已经具备了生产环境所需的大部分功能多环境支持依赖检查端口冲突检测日志轮转优雅停机完善的错误处理详细的用法说明7. 实际部署建议7.1 目录结构规范建议采用以下目录结构管理相关文件/opt/myapp/ ├── app/ # 应用代码 ├── logs/ # 日志目录 │ ├── gunicorn.access.log │ └── gunicorn.error.log ├── scripts/ # 管理脚本 │ └── gunicorn_manager.sh └── config/ # 配置文件 ├── development.conf └── production.conf7.2 权限管理确保正确的文件权限sudo chown -R www-data:www-data /opt/myapp/logs sudo chmod -R 755 /opt/myapp/scripts7.3 监控与告警集成到现有监控系统# 简单的监控检查脚本 #!/bin/bash if ! /opt/myapp/scripts/gunicorn_manager.sh status | grep -q running; then echo Gunicorn is down! | mail -s Gunicorn Alert adminexample.com /opt/myapp/scripts/gunicorn_manager.sh restart fi添加到cron每5分钟运行一次*/5 * * * * /opt/myapp/scripts/monitor_gunicorn.sh8. 性能调优技巧8.1 Worker数量计算经验公式# 建议worker数量 (2 * CPU核心数) 1 CPU_CORES$(nproc) RECOMMENDED_WORKERS$((2 * CPU_CORES 1))8.2 Worker类选择根据应用特点选择同步Worker适合CPU密集型gevent适合IO密集型tornado需要WebSocket支持时8.3 其他重要参数# 在GUNICORN_CMD中添加这些参数 --max-requests 1000 # 每个worker处理1000请求后重启 --max-requests-jitter 100 # 添加随机性防止所有worker同时重启 --timeout 30 # 请求超时时间(秒) --graceful-timeout 30 # 优雅停机超时9. 安全加固措施9.1 限制绑定IP生产环境不要绑定到0.0.0.0BIND_ADDR127.0.0.1:8000 # 只允许本地访问9.2 使用Unix Socket更高性能和安全SOCKET_FILE/tmp/gunicorn.sock GUNICORN_CMDgunicorn -w ${WORKERS} -k ${WORKER_CLASS} --bind unix:${SOCKET_FILE} ... # 记得设置socket权限 chmod 660 $SOCKET_FILE chown www-data:www-data $SOCKET_FILE9.3 日志敏感信息过滤在应用代码中添加过滤器防止记录敏感信息如密码、token等。10. 扩展思考10.1 多应用管理如果需要管理多个Gunicorn应用可以扩展脚本#!/bin/bash APPS(app1 app2 app3) BASE_DIR/opt manage_app() { local app$1 local action$2 ( cd ${BASE_DIR}/${app} ENVproduction ./scripts/gunicorn_manager.sh $action ) } case $1 in start-all) for app in ${APPS[]}; do manage_app $app start done ;; # 其他命令类似 esac10.2 与Supervisor集成替代systemd的方案[program:myapp] command/opt/myapp/scripts/gunicorn_manager.sh run directory/opt/myapp userwww-data autostarttrue autorestarttrue stderr_logfile/var/log/myapp.err.log stdout_logfile/var/log/myapp.out.log10.3 容器化部署对于Docker环境调整脚本#!/bin/bash # 在容器中直接运行不使用nohup exec gunicorn -w ${WORKERS} -k ${WORKER_CLASS} -b ${BIND_ADDR} --access-logfile - --error-logfile - ${APP_MODULE}然后在Dockerfile中COPY gunicorn_manager.sh /app/ CMD [/app/gunicorn_manager.sh, start]