【软件运维】前后端部署启动的几种方式
.sh启动
#!/bin/bash# 解析软链接,获取真实脚本目录
SOURCE="${BASH_SOURCE[0]}"
while [ -L "$SOURCE" ]; doDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"SOURCE="$(readlink "$SOURCE")"[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
CURRENT_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
echo "当前脚本所在目录: $CURRENT_DIR"# ================ 配置参数 ================
# conda环境名称
CONDA_ENV="hr"# 程序路径 - 使用当前目录作为基础
PROGRAM_PATH="$CURRENT_DIR/backend/run.py"# 程序启动参数(如有)
PROGRAM_ARGS=""# 日志文件路径 - 使用当前目录下的logs文件夹
LOG_FILE="$CURRENT_DIR/logs/backend/backend.log"# 日志保留天数
LOG_RETENTION_DAYS=7# 日志切割频率(分钟)
LOG_ROTATE_INTERVAL=1440 # 默认24小时# 上次切割日志的时间戳文件
LAST_ROTATE_FILE="/tmp/backend_log_rotate.timestamp"# ================ 函数定义 ================# 加载conda环境
load_conda_env() {# 尝试加载conda初始化脚本,增加Miniforge的路径if [ -f "$HOME/miniforge3/etc/profile.d/conda.sh" ]; thensource "$HOME/miniforge3/etc/profile.d/conda.sh"elif [ -f "$HOME/mambaforge/etc/profile.d/conda.sh" ]; thensource "$HOME/mambaforge/etc/profile.d/conda.sh"elif [ -f "$HOME/miniconda3/etc/profile.d/conda.sh" ]; thensource "$HOME/miniconda3/etc/profile.d/conda.sh"elif [ -f "$HOME/anaconda3/etc/profile.d/conda.sh" ]; thensource "$HOME/anaconda3/etc/profile.d/conda.sh"elif [ -f "/opt/conda/etc/profile.d/conda.sh" ]; thensource "/opt/conda/etc/profile.d/conda.sh"else# 尝试查找系统中的conda.sh文件CONDA_SH=$(find $HOME -name "conda.sh" -path "*/etc/profile.d/*" 2>/dev/null | head -n 1)if [ -n "$CONDA_SH" ]; thensource "$CONDA_SH"elseecho "错误: 找不到conda初始化脚本,请检查Miniforge/conda安装路径" >&2exit 1fifi# 激活指定环境conda activate "$CONDA_ENV"if [ $? -ne 0 ]; thenecho "错误: 无法激活conda环境 '$CONDA_ENV'" >&2exit 1fiecho "已激活conda环境: $CONDA_ENV"
}# 启动后端程序
start_backend() {# 确保日志目录存在LOG_DIR=$(dirname "$LOG_FILE")mkdir -p "$LOG_DIR"# 检查程序是否已运行if pgrep -f "python.*$PROGRAM_PATH $PROGRAM_ARGS" > /dev/null; thenecho "警告: 程序已在运行中,PID: $(pgrep -f "python.*$PROGRAM_PATH $PROGRAM_ARGS")"return 1fi# 启动程序并记录PID - 使用python命令echo "正在启动程序: python $PROGRAM_PATH $PROGRAM_ARGS"cd "$(dirname "$PROGRAM_PATH")" # 切换到脚本所在目录nohup python "$(basename "$PROGRAM_PATH")" $PROGRAM_ARGS > "$LOG_FILE" 2>&1 &PID=$!# 保存PID到文件(可选)echo $PID > "$LOG_DIR/backend.pid"echo "程序已启动,PID: $PID,日志输出到: $LOG_FILE"# 启动守护进程DAEMON_SH="$CURRENT_DIR/deamon.sh"if ! pgrep -f "$DAEMON_SH" > /dev/null; thennohup bash "$DAEMON_SH" > /dev/null 2>&1 &echo "守护进程已启动"elseecho "守护进程已在运行"fireturn 0
}# 切割日志文件
rotate_log() {# 创建日志目录(如果不存在)LOG_DIR=$(dirname "$LOG_FILE")mkdir -p "$LOG_DIR"# 生成备份文件名(包含时间戳)TIMESTAMP=$(date +%Y%m%d_%H%M%S)BACKUP_FILE="${LOG_FILE}.${TIMESTAMP}"# 移动当前日志文件到备份if [ -f "$LOG_FILE" ]; thenmv "$LOG_FILE" "$BACKUP_FILE"echo "日志已切割: $BACKUP_FILE"# 更新上次切割时间echo $(date +%s) > "$LAST_ROTATE_FILE"# 清理过期日志find "$LOG_DIR" -name "$(basename $LOG_FILE).*" -type f -mtime +$LOG_RETENTION_DAYS -deletefi
}# 检查是否需要切割日志
check_rotate_needed() {# 如果时间戳文件不存在,创建并返回需要切割if [ ! -f "$LAST_ROTATE_FILE" ]; thenecho $(date +%s) > "$LAST_ROTATE_FILE"return 0fi# 计算上次切割到现在的时间(分钟)LAST_ROTATE=$(cat "$LAST_ROTATE_FILE")CURRENT_TIME=$(date +%s)ELAPSED_MINUTES=$(( (CURRENT_TIME - LAST_ROTATE) / 60 ))# 如果超过设定的时间间隔,返回需要切割if [ $ELAPSED_MINUTES -ge $LOG_ROTATE_INTERVAL ]; thenreturn 0fireturn 1
}# 显示使用帮助
show_help() {echo "用法: $0 [选项]"echo "选项:"echo " start 启动后端程序"echo " rotate 手动切割日志"echo " status 检查程序状态"echo " stop 停止后端程序"echo " restart 重启后端程序"echo " help 显示此帮助信息"exit 0
}# 停止后端程序
stop_backend() {LOG_DIR=$(dirname "$LOG_FILE")PID_FILE="$LOG_DIR/backend.pid"# 如果有PID文件,尝试通过PID停止if [ -f "$PID_FILE" ]; thenPID=$(cat "$PID_FILE")if ps -p $PID > /dev/null; thenecho "正在停止程序,PID: $PID"kill $PID# 等待最多10秒for i in {1..10}; doif ! ps -p $PID > /dev/null; thenecho "程序已停止"rm -f "$PID_FILE"return 0fisleep 1done# 如果10秒后仍在运行,强制终止echo "程序未响应,强制终止"kill -9 $PIDrm -f "$PID_FILE"return 0elseecho "警告: PID文件存在但进程不存在,删除PID文件"rm -f "$PID_FILE"fifi# 如果没有PID文件,尝试通过程序名查找PROGRAM_NAME=$(basename "$PROGRAM_PATH")PID=$(pgrep -f "python.*$PROGRAM_PATH $PROGRAM_ARGS")if [ -n "$PID" ]; thenecho "找到程序实例,PID: $PID"kill $PID# 等待最多10秒for i in {1..10}; doif ! ps -p $PID > /dev/null; thenecho "程序已停止"return 0fisleep 1done# 如果10秒后仍在运行,强制终止echo "程序未响应,强制终止"kill -9 $PIDreturn 0fiecho "未找到运行中的程序实例"return 1
}# 检查程序状态
check_status() {LOG_DIR=$(dirname "$LOG_FILE")PID_FILE="$LOG_DIR/backend.pid"if [ -f "$PID_FILE" ]; thenPID=$(cat "$PID_FILE")if ps -p $PID > /dev/null; thenecho "程序正在运行,PID: $PID"echo "日志文件: $LOG_FILE"return 0elseecho "程序未运行"return 1fielseecho "程序未运行"return 1fi
}# ================ 主程序 ================# 检查命令行参数
case "$1" instart)load_conda_envstart_backend;;rotate)rotate_log;;status)check_status;;stop)stop_backend;;restart)stop_backendload_conda_envstart_backend;;help|--help|-h)show_help;;*)echo "错误: 未知命令 '$1'"show_help;;
esacexit 0
这个Bash脚本是一个用于管理Python后端程序的工具脚本,提供了启动、停止、重启、状态检查以及日志管理等功能。下面我将分部分详细解释这个脚本的内容和工作原理。
1. 获取真实脚本目录
SOURCE="${BASH_SOURCE[0]}"
while [ -L "$SOURCE" ]; doDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"SOURCE="$(readlink "$SOURCE")"[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
CURRENT_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
echo "当前脚本所在目录: $CURRENT_DIR"
这部分代码的作用是获取脚本的真实路径,即使脚本是通过软链接调用的。它通过以下步骤实现:
-
获取脚本路径(
SOURCE
) -
如果路径是软链接(
-L
),则解析真实路径 -
处理相对路径的情况(
SOURCE != /*
) -
最终获取脚本所在目录的绝对路径(
CURRENT_DIR
)
2. 配置参数
CONDA_ENV="hr" # conda环境名称
PROGRAM_PATH="$CURRENT_DIR/backend/run.py" # 程序路径
PROGRAM_ARGS="" # 程序启动参数
LOG_FILE="$CURRENT_DIR/logs/backend/backend.log" # 日志文件路径
LOG_RETENTION_DAYS=7 # 日志保留天数
LOG_ROTATE_INTERVAL=1440 # 日志切割频率(分钟,默认24小时)
LAST_ROTATE_FILE="/tmp/backend_log_rotate.timestamp" # 上次切割日志的时间戳文件
这部分定义了脚本使用的各种参数,包括:
-
Conda环境名称
-
要运行的Python程序路径
-
程序启动参数
-
日志文件位置和保留策略
3. 函数定义
3.1 load_conda_env
函数
load_conda_env() {# 尝试从多个可能的位置加载conda初始化脚本if [ -f "$HOME/miniforge3/etc/profile.d/conda.sh" ]; thensource "$HOME/miniforge3/etc/profile.d/conda.sh"elif [ -f ... ]; then # 其他可能的位置...fi# 激活指定conda环境conda activate "$CONDA_ENV"...
}
这个函数负责加载conda环境,它会尝试从多个常见位置查找conda初始化脚本,然后激活指定的conda环境(hr
)。
3.2 start_backend
函数
start_backend() {# 确保日志目录存在mkdir -p "$LOG_DIR"# 检查程序是否已运行if pgrep -f "python.*$PROGRAM_PATH $PROGRAM_ARGS" > /dev/null; thenecho "警告: 程序已在运行中..."return 1fi# 启动程序并记录PIDnohup python "$(basename "$PROGRAM_PATH")" $PROGRAM_ARGS > "$LOG_FILE" 2>&1 &PID=$!echo $PID > "$LOG_DIR/backend.pid"# 启动守护进程DAEMON_SH="$CURRENT_DIR/deamon.sh"if ! pgrep -f "$DAEMON_SH" > /dev/null; thennohup bash "$DAEMON_SH" > /dev/null 2>&1 &fi
}
这个函数负责启动后端程序,它会:
-
创建必要的日志目录
-
检查程序是否已经在运行
-
使用nohup在后台启动Python程序,并重定向输出到日志文件
-
保存进程ID(PID)到文件
-
启动守护进程(如果未运行)
3.3 日志管理函数
rotate_log() {# 创建日志备份文件TIMESTAMP=$(date +%Y%m%d_%H%M%S)BACKUP_FILE="${LOG_FILE}.${TIMESTAMP}"mv "$LOG_FILE" "$BACKUP_FILE"# 清理过期日志find "$LOG_DIR" -name "$(basename $LOG_FILE).*" -type f -mtime +$LOG_RETENTION_DAYS -delete
}check_rotate_needed() {# 检查是否需要切割日志(基于时间间隔)...
}
这些函数负责日志的轮转(切割)管理:
-
rotate_log
: 将当前日志重命名为带时间戳的备份文件,并清理过期的日志 -
check_rotate_needed
: 检查是否到了需要切割日志的时间
3.4 其他管理函数
stop_backend() {# 通过PID文件或程序名查找并停止程序...
}check_status() {# 检查程序运行状态...
}show_help() {# 显示使用帮助...
}
这些函数提供了其他管理功能:
-
停止程序
-
检查程序状态
-
显示帮助信息
4. 主程序
case "$1" instart)load_conda_envstart_backend;;rotate)rotate_log;;status)check_status;;stop)stop_backend;;restart)stop_backendload_conda_envstart_backend;;help|--help|-h)show_help;;*)echo "错误: 未知命令 '$1'"show_help;;
esac
这部分是脚本的入口,根据传入的参数调用相应的函数:
-
start
: 启动程序 -
rotate
: 手动切割日志 -
status
: 检查状态 -
stop
: 停止程序 -
restart
: 重启程序 -
help
: 显示帮助
要启动这个脚本,你需要根据脚本提供的功能选项来执行相应的命令。以下是使用这个脚本的各种方法:
基本使用方法
bash 脚本文件名.sh [选项]
具体操作命令
-
启动后端程序:
bash script.sh start
-
这会激活conda环境并启动Python后端程序
-
同时会启动守护进程(如果配置了的话)
-
-
停止后端程序:
bash script.sh stop
-
重启后端程序:
bash script.sh restart
-
手动切割日志:
bash script.sh rotate
-
检查程序状态:
bash script.sh status
-
查看帮助信息:
bash script.sh help 或 bash script.sh --help 或 bash script.sh -h
守护进程脚本
后端启动后意外停止的常见原因:
Python 脚本自身崩溃
-
可能原因:
-
未捕获的异常(如
KeyError
,ImportError
)。 -
内存泄漏导致被系统 OOM Killer 终止。
-
第三方库的 Bug 或兼容性问题。
-
-
排查方法:
# 查看脚本是否有崩溃日志(如果未重定向输出) cat nohup.out # 或主动记录错误到文件 python run.py >> run.log 2>&1
系统资源不足
-
可能原因:
-
内存不足:进程占用内存过多被系统 OOM Killer 杀死。
dmesg | grep -i "killed" # 检查是否有 OOM 记录
-
CPU/磁盘过载:长时间高负载导致进程卡死。
top -p $(pgrep -f "run.py") # 监控资源占用
-
会话中断导致信号终止
-
可能原因:
-
nohup
虽然能忽略SIGHUP
信号,但可能收到其他信号(如SIGTERM
)。 -
服务器运维操作(如定期重启、Cron 任务)可能主动终止进程。
-
-
排查方法:
# 检查系统日志(如 syslog/auth.log) grep -i "kill" /var/log/syslog
依赖服务不可用
-
可能原因:
-
数据库/Redis 等依赖服务断开,导致 Python 脚本主动退出。
-
网络波动导致 HTTP 请求超时。
-
-
排查方法:
-
在代码中添加依赖服务的健康检查(如数据库连接重试逻辑)。
-
Python 环境问题
-
可能原因:
-
虚拟环境未激活或包版本冲突。
-
Python 解释器自身崩溃(罕见但可能)。
-
-
排查方法:
# 检查 Python 版本和依赖 python -V pip list | grep "关键依赖包名"
定时任务或运维脚本干扰
-
可能原因:
-
服务器上有其他 Cron 任务或监控脚本误杀进程。
crontab -l # 检查当前用户的定时任务
-
因此用了一个监控和自动重启后端服务的 Bash 守护进程脚本(deamon.sh
)。我来逐步解析它的功能和工作原理:
1. 基础设置
OP_SCRIPT="$(dirname "$(readlink -f "$0")")/op.sh"
-
作用:定位同目录下的
op.sh
脚本(假设这是管理后端服务的脚本)。 -
技术点:
-
$0
是当前脚本路径(如./deamon.sh
)。 -
readlink -f
解析符号链接并返回绝对路径。 -
dirname
提取目录路径。
-
CHECK_INTERVAL=300 # 检查间隔(秒,这里是5分钟)
LOG_FILE="$(dirname "$OP_SCRIPT")/logs/backend/daemon.log"
-
定义检查服务状态的间隔时间(300秒)和日志文件路径。
2. 日志函数
log() {echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOG_FILE"
}
-
将带时间戳的消息追加到日志文件(如
2024-01-01 12:00:00 守护进程启动...
)。
3. 服务状态检测
is_running() {"$OP_SCRIPT" status | grep -q "程序正在运行"
}
-
逻辑:
-
调用
op.sh status
检查服务状态。 -
用
grep -q
静默匹配输出中是否包含程序正在运行
。 -
如果匹配成功(服务在运行),返回
0
(成功);否则返回非零。
-
4. 服务启动函数
start_service() {log "检测到服务未运行,尝试启动...""$OP_SCRIPT" start >> "$LOG_FILE" 2>&1
}
-
如果服务未运行:
-
记录日志。
-
调用
op.sh start
启动服务,并将输出和错误重定向到日志文件。
-
5. 主循环
log "守护进程启动,开始监控后端服务..."
while true; doif ! is_running; thenstart_servicefisleep $CHECK_INTERVAL
done
-
无限循环:
-
检查服务状态(
is_running
)。 -
如果服务停止,调用
start_service
。 -
休眠
CHECK_INTERVAL
秒后重复检查。
-
关键点总结
-
依赖关系:
-
需要同目录下的
op.sh
脚本,且该脚本需支持status
和start
命令。 -
假设
op.sh status
的输出中包含程序正在运行
字符串。
-
-
日志管理:
-
所有操作记录到
logs/backend/daemon.log
,便于排查问题。
-
-
工作场景:
-
适合监控需要长期运行的后端服务(如Web API、数据库等)。
-
当服务意外崩溃时,自动尝试重启。
-
#!/bin/bash# deamon.sh:守护op.sh启动的后端服务OP_SCRIPT="$(dirname "$(readlink -f "$0")")/op.sh"
CHECK_INTERVAL=300
LOG_FILE="$(dirname "$OP_SCRIPT")/logs/backend/daemon.log"log() {echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOG_FILE"
}is_running() {"$OP_SCRIPT" status | grep -q "程序正在运行"
}start_service() {log "检测到服务未运行,尝试启动...""$OP_SCRIPT" start >> "$LOG_FILE" 2>&1
}log "守护进程启动,开始监控后端服务..."
while true; doif ! is_running; thenstart_servicefisleep $CHECK_INTERVAL
done
Make dev命令
看到了一个谷歌项目,他的前后端启动方式很方便
https://github.com/google-gemini/gemini-fullstack-langgraph-quickstart/blob/main/README.md
项目最外部有一个makefile
.PHONY: help dev-frontend dev-backend devhelp:@echo "Available commands:"@echo " make dev-frontend - Starts the frontend development server (Vite)"@echo " make dev-backend - Starts the backend development server (Uvicorn with reload)"@echo " make dev - Starts both frontend and backend development servers"dev-frontend:@echo "Starting frontend development server..."@cd frontend && npm run devdev-backend:@echo "Starting backend development server..."@cd backend && langgraph dev# Run frontend and backend concurrently
dev:@echo "Starting both frontend and backend development servers..."@make dev-frontend & make dev-backend
这个 Makefile 是一个用于管理项目开发工作流的构建文件,特别适合同时包含前端和后端的项目。让我们逐部分深入分析:
1. .PHONY 声明
.PHONY: help dev-frontend dev-backend dev
-
.PHONY
是一个特殊目标,用于声明哪些目标是"伪目标"(不是实际的文件名) -
这里声明了
help
,dev-frontend
,dev-backend
和dev
都是伪目标 -
这意味着即使存在同名文件,Make 也会执行这些目标下的命令
2. help 目标
help:@echo "Available commands:"@echo " make dev-frontend - Starts the frontend development server (Vite)"@echo " make dev-backend - Starts the backend development server (Uvicorn with reload)"@echo " make dev - Starts both frontend and backend development servers"
-
这是一个帮助信息目标,显示可用的命令及其描述
-
@
符号表示在执行时不显示命令本身,只显示输出 -
这是典型的自文档化 Makefile 的做法
3. dev-frontend 目标
dev-frontend:@echo "Starting frontend development server..."@cd frontend && npm run dev
-
用于启动前端开发服务器
-
先显示启动信息,然后:
-
cd frontend
进入 frontend 目录 -
npm run dev
运行 npm 的 dev 脚本(假设使用 Vite,如帮助信息所示)
-
-
这要求项目有一个 frontend 目录,并且其中 package.json 中定义了 dev 脚本
4. dev-backend 目标
dev-backend:@echo "Starting backend development server..."@cd backend && langgraph dev
-
用于启动后端开发服务器
-
先显示启动信息,然后:
-
cd backend
进入 backend 目录 -
langgraph dev
运行 langgraph 的开发命令(看起来是使用某种 Python 框架)
-
-
这要求项目有一个 backend 目录,并且安装了 langgraph
5. dev 目标
dev:@echo "Starting both frontend and backend development servers..."@make dev-frontend & make dev-backend
-
这是最常用的目标,同时启动前后端开发服务器
-
使用
&
运算符在后台并行运行两个 make 命令 -
注意:
-
这种方式简单但不够健壮,如果某个进程失败不会自动停止另一个
-
更高级的替代方案是使用
concurrently
或docker-compose
等工具
-
使用场景
这个 Makefile 典型用于全栈 JavaScript/Python 项目,其中:
-
前端使用 Vite(现代前端构建工具)
-
后端使用某种 Python 框架(可能是 FastAPI 或其他,基于
langgraph
命令判断)
总结
这个 Makefile 提供了一个简洁的接口来管理常见的开发任务,通过简单的 make dev
命令就能启动整个开发环境,大大简化了开发者的工作流程。它体现了 Makefile 作为项目任务自动化工具的经典用法,特别适合需要同时管理多个服务的项目。
Docker前端启动
FROM m.daocloud.io/docker.io/library/nginx:latest# 移除默认的nginx配置
RUN rm -rf /etc/nginx/conf.d/*# 创建目录结构
RUN mkdir -p /usr/share/nginx/html/dist
RUN mkdir -p /etc/nginx/ssl# 暴露端口
EXPOSE 80CMD ["nginx", "-g", "daemon off;"]
这是一个用于配置 Nginx 服务器的 Dockerfile,让我们逐部分分析它的结构和功能:
基础镜像
FROM m.daocloud.io/docker.io/library/nginx:latest
-
使用 DaoCloud 镜像源(中国国内常用的镜像加速源)拉取官方的 Nginx 镜像
-
library/nginx:latest
表示使用 Docker 官方仓库中的最新版 Nginx 镜像 -
m.daocloud.io/
前缀是为了加速在中国地区的下载
清理默认配置
RUN rm -rf /etc/nginx/conf.d/*
-
删除 Nginx 默认的配置文件
-
/etc/nginx/conf.d/
是 Nginx 加载额外配置文件的目录 -
这样做是为了准备完全自定义的 Nginx 配置
创建目录结构
RUN mkdir -p /usr/share/nginx/html/dist
RUN mkdir -p /etc/nginx/ssl
-
前端静态文件目录:
-
/usr/share/nginx/html/dist
是准备存放前端构建产物(如 Vue/React 打包后的文件)的目录 -
-p
参数确保即使父目录不存在也会创建
-
-
SSL 证书目录:
-
/etc/nginx/ssl
是为 HTTPS 准备的 SSL 证书存放目录 -
虽然当前 Dockerfile 没有配置 HTTPS,但预留了这个目录
-
暴露端口
EXPOSE 80
-
声明容器将监听 80 端口(HTTP 默认端口)
-
这只是文档性质的声明,实际端口映射需要在
docker run
时用-p
参数指定
启动命令
CMD ["nginx", "-g", "daemon off;"]
-
以非守护进程模式运行 Nginx
-
-g "daemon off;"
是 Nginx 的参数,表示在前台运行 -
这是 Docker 容器的最佳实践,因为容器需要有一个持续运行的前台进程
典型使用场景
这个 Dockerfile 适合用于:
-
部署前端静态资源(如 Vue/React 应用)
-
需要自定义 Nginx 配置的情况
-
作为基础镜像进一步扩展
docker-compose.yaml
version: '3' services:nginx:build: .image: hrfusion-fd-nginx:v1.0container_name: hrfusion-fd-hyqrestart: alwaysports:- "18001:80" # docker端口映射[宿主机端口:容器内部端口]volumes:# - ./ssl_certs:/etc/nginx/ssl # 证书目录- ./frontend/dist:/usr/share/nginx/html/dist # 项目目录- ./frontend/default.conf:/etc/nginx/conf.d/default.conf # 服务配置
这个 docker-compose.yml
文件定义了一个 Nginx 服务的配置,用于部署前端应用。让我们逐部分深入分析:
文件结构概览
version: '3'
services:nginx:# 配置详情...
-
version: '3'
- 指定使用 Docker Compose 文件格式版本 3 -
services:
- 定义要运行的服务列表 -
nginx:
- 定义一个名为 "nginx" 的服务
服务配置详解
1. 构建与镜像
build: .
image: hrfusion-fd-nginx:v1.0
-
build: .
- 使用当前目录下的 Dockerfile 构建镜像 -
image: hrfusion-fd-nginx:v1.0
- 为构建的镜像指定名称和标签-
这样构建后会有两个引用:
-
一个是通过构建过程生成的匿名镜像
-
一个是命名为
hrfusion-fd-nginx:v1.0
的镜像
-
-
2. 容器设置
container_name: hrfusion-fd-hyq
restart: always
-
container_name: hrfusion-fd-hyq
- 为容器指定固定名称(而不是随机生成) -
restart: always
- 容器退出时自动重启-
这确保了服务在崩溃或服务器重启后自动恢复
-
3. 端口映射
ports:- "18001:80"
-
将宿主机的 18001 端口映射到容器的 80 端口
-
格式:
"主机端口:容器端口"
-
这意味着:
-
外部访问
http://主机IP:18001
-
请求会被转发到容器的 80 端口
-
Nginx 在容器内监听 80 端口
-
4. 数据卷挂载
volumes:# - ./ssl_certs:/etc/nginx/ssl- ./frontend/dist:/usr/share/nginx/html/dist- ./frontend/default.conf:/etc/nginx/conf.d/default.conf
-
前端静态文件:
-
./frontend/dist:/usr/share/nginx/html/dist
-
将本地
frontend/dist
目录挂载到容器的 Nginx 静态文件目录 -
这样前端构建后文件可以直接被 Nginx 服务
-
-
Nginx 配置:
-
./frontend/default.conf:/etc/nginx/conf.d/default.conf
-
使用本地的 Nginx 配置文件覆盖容器默认配置
-
这是自定义 Nginx 行为的常见方式
-
-
注释掉的 SSL 证书:
-
如果需要 HTTPS 支持,可以取消注释并配置证书路径
-
典型工作流程
-
前端构建:
-
开发者运行
npm run build
生成frontend/dist
目录
-
-
启动服务:
-
运行
docker-compose up -d
启动服务 -
Docker 会:
-
根据 Dockerfile 构建镜像
-
创建并启动容器
-
设置所有端口映射和卷挂载
-
-
-
访问应用:
-
通过
http://服务器IP:18001
访问应用 -
Nginx 会:
-
读取挂载的配置文件
-
从挂载的
dist
目录提供静态文件
-
-
配置亮点
-
开发-生产一致性:
-
使用相同的 Nginx 配置和部署方式开发和生产环境
-
-
快速更新:
-
修改前端代码后只需重新构建
dist
目录 -
无需重建 Docker 镜像或重启容器(Nginx 会自动提供新文件)
-
-
配置与代码分离:
-
Nginx 配置可以独立修改而不影响应用代码
-
这个配置非常适合前端项目的容器化部署,提供了灵活性、可维护性和便捷的开发体验。
具体联系
(1) docker-compose.yml
依赖 Dockerfile
构建镜像
services:nginx:build: . # 这里会调用当前目录下的 Dockerfile 构建镜像image: hrfusion-fd-nginx:v1.0
-
build: .
表示 Compose 会读取当前目录的Dockerfile
来构建镜像。 -
构建后的镜像会被标记为
hrfusion-fd-nginx:v1.0
。
(2) Dockerfile
提供基础环境,docker-compose.yml
补充运行时配置
-
Dockerfile
中:-
定义了基础镜像(
nginx:latest
)。 -
清除了默认配置(
rm -rf /etc/nginx/conf.d/*
)。 -
创建了目录结构(如
/usr/share/nginx/html/dist
)。
-
-
docker-compose.yml
中:-
通过
volumes
动态挂载了配置文件(default.conf
)和前端代码(dist
)。 -
通过
ports
暴露了端口(18001:80
)。 -
这些配置在容器运行时生效,覆盖或补充了
Dockerfile
的静态设置。
-
总结
下面我将系统性地总结这些启动方式及其适用场景,并给出如何统一管理的建议:
一、后端启动方式
1. 直接运行 .py
文件
python main.py # 或使用模块方式
python -m uvicorn main:app --reload
-
适用场景:开发调试、快速验证
-
特点:
-
最直接的方式,依赖本地Python环境
-
需手动安装所有依赖(
pip install -r requirements.txt
)
-
2. 通过 .sh
脚本启动
#!/bin/bash
# run.sh
pip install -r requirements.txt
python main.py
-
适用场景:标准化本地开发或简单部署
-
特点:
-
可封装复杂命令(如环境变量、参数传递)
-
适合团队统一开发流程
-
3. 通过 Docker 启动
# Dockerfile
FROM python:3.9
COPY . /app
RUN pip install -r requirements.txt
CMD ["python", "main.py"]
docker build -t backend . && docker run -p 8000:8000 backend
-
适用场景:生产部署、环境隔离
-
特点:
-
环境一致性高
-
需提前构建镜像
-
4. 通过 Docker Compose 启动
# docker-compose.yml
services:backend:build: ./backendports:- "8000:8000"
-
适用场景:多服务协作(如需要同时启动数据库)
二、前端启动方式
1. 开发模式(Node.js 直接运行)
npm run dev # Vite/Webpack等工具
-
适用场景:前端开发热重载
2. 生产构建 + Docker 托管
# 前端Dockerfile
FROM nginx
COPY dist /usr/share/nginx/html
-
适用场景:生产环境部署
-
特点:
-
需先执行
npm run build
生成静态文件 -
通过Nginx提供高性能静态资源服务
-
3. 通过 Docker Compose 与后端联动
services:frontend:build: ./frontendports:- "80:80"backend:build: ./backendports:- "8000:8000"
三、统一启动方案
方案1:Makefile 整合多命令
dev:@echo "Starting all services..."cd backend && python main.py & cd frontend && npm run devdocker-up:docker-compose up -d --build
-
优点:简单直接,适合开发者本地使用
-
缺点:进程管理较原始(如用
&
后台运行)
方案2:Docker Compose 统一管理
version: '3'
services:frontend:build: ./frontendports: ["3000:3000"]volumes:- ./frontend:/app- /app/node_modulesbackend:build: ./backendports: ["8000:8000"]volumes:- ./backend:/codedb:image: postgresenvironment:POSTGRES_PASSWORD: example
-
优点:
-
一键启动完整环境(前端+后端+数据库)
-
环境隔离彻底
-
-
缺点:
-
开发时前端热更新需要配置额外卷(如
node_modules
排除)
-
方案3:混合模式(开发 vs 生产)
.
├── Makefile # 开发命令(直接运行.py/npm)
├── docker-compose.yml # 生产环境Docker部署
└── docker-compose.dev.yml # 开发环境(带热更新)
-
开发时:
make dev # 使用本地Python/Node环境
-
生产部署:
docker-compose -f docker-compose.prod.yml up -d
四、如何选择?
场景 | 推荐方案 |
---|---|
个人开发调试 | Makefile + 直接运行.py/npm |
团队统一开发环境 | Docker Compose + 代码卷挂载 |
生产部署 | Docker Compose(无卷挂载) |
需要快速切换多环境 | 混合模式(Makefile + 多Compose文件) |
通过灵活组合这些方案,你可以实现从开发到生产的一体化高效工作流!