.sh启动
#!/bin/bash
# 解析软链接,获取真实脚本目录
SOURCE="${BASH_SOURCE[0]}"
while [ -L "$SOURCE" ]; do
DIR="$( 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" ]; then
source "$HOME/miniforge3/etc/profile.d/conda.sh"
elif [ -f "$HOME/mambaforge/etc/profile.d/conda.sh" ]; then
source "$HOME/mambaforge/etc/profile.d/conda.sh"
elif [ -f "$HOME/miniconda3/etc/profile.d/conda.sh" ]; then
source "$HOME/miniconda3/etc/profile.d/conda.sh"
elif [ -f "$HOME/anaconda3/etc/profile.d/conda.sh" ]; then
source "$HOME/anaconda3/etc/profile.d/conda.sh"
elif [ -f "/opt/conda/etc/profile.d/conda.sh" ]; then
source "/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" ]; then
source "$CONDA_SH"
else
echo "错误: 找不到conda初始化脚本,请检查Miniforge/conda安装路径" >&2
exit 1
fi
fi
# 激活指定环境
conda activate "$CONDA_ENV"
if [ $? -ne 0 ]; then
echo "错误: 无法激活conda环境 '$CONDA_ENV'" >&2
exit 1
fi
echo "已激活conda环境: $CONDA_ENV"
}
# 启动后端程序
start_backend() {
# 确保日志目录存在
LOG_DIR=$(dirname "$LOG_FILE")
mkdir -p "$LOG_DIR"
# 检查程序是否已运行
if pgrep -f "python.*$PROGRAM_PATH $PROGRAM_ARGS" > /dev/null; then
echo "警告: 程序已在运行中,PID: $(pgrep -f "python.*$PROGRAM_PATH $PROGRAM_ARGS")"
return 1
fi
# 启动程序并记录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; then
nohup bash "$DAEMON_SH" > /dev/null 2>&1 &
echo "守护进程已启动"
else
echo "守护进程已在运行"
fi
return 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" ]; then
mv "$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 -delete
fi
}
# 检查是否需要切割日志
check_rotate_needed() {
# 如果时间戳文件不存在,创建并返回需要切割
if [ ! -f "$LAST_ROTATE_FILE" ]; then
echo $(date +%s) > "$LAST_ROTATE_FILE"
return 0
fi
# 计算上次切割到现在的时间(分钟)
LAST_ROTATE=$(cat "$LAST_ROTATE_FILE")
CURRENT_TIME=$(date +%s)
ELAPSED_MINUTES=$(( (CURRENT_TIME - LAST_ROTATE) / 60 ))
# 如果超过设定的时间间隔,返回需要切割
if [ $ELAPSED_MINUTES -ge $LOG_ROTATE_INTERVAL ]; then
return 0
fi
return 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" ]; then
PID=$(cat "$PID_FILE")
if ps -p $PID > /dev/null; then
echo "正在停止程序,PID: $PID"
kill $PID
# 等待最多10秒
for i in {1..10}; do
if ! ps -p $PID > /dev/null; then
echo "程序已停止"
rm -f "$PID_FILE"
return 0
fi
sleep 1
done
# 如果10秒后仍在运行,强制终止
echo "程序未响应,强制终止"
kill -9 $PID
rm -f "$PID_FILE"
return 0
else
echo "警告: PID文件存在但进程不存在,删除PID文件"
rm -f "$PID_FILE"
fi
fi
# 如果没有PID文件,尝试通过程序名查找
PROGRAM_NAME=$(basename "$PROGRAM_PATH")
PID=$(pgrep -f "python.*$PROGRAM_PATH $PROGRAM_ARGS")
if [ -n "$PID" ]; then
echo "找到程序实例,PID: $PID"
kill $PID
# 等待最多10秒
for i in {1..10}; do
if ! ps -p $PID > /dev/null; then
echo "程序已停止"
return 0
fi
sleep 1
done
# 如果10秒后仍在运行,强制终止
echo "程序未响应,强制终止"
kill -9 $PID
return 0
fi
echo "未找到运行中的程序实例"
return 1
}
# 检查程序状态
check_status() {
LOG_DIR=$(dirname "$LOG_FILE")
PID_FILE="$LOG_DIR/backend.pid"
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p $PID > /dev/null; then
echo "程序正在运行,PID: $PID"
echo "日志文件: $LOG_FILE"
return 0
else
echo "程序未运行"
return 1
fi
else
echo "程序未运行"
return 1
fi
}
# ================ 主程序 ================
# 检查命令行参数
case "$1" in
start)
load_conda_env
start_backend
;;
rotate)
rotate_log
;;
status)
check_status
;;
stop)
stop_backend
;;
restart)
stop_backend
load_conda_env
start_backend
;;
help|--help|-h)
show_help
;;
*)
echo "错误: 未知命令 '$1'"
show_help
;;
esac
exit 0
这个Bash脚本是一个用于管理Python后端程序的工具脚本,提供了启动、停止、重启、状态检查以及日志管理等功能。下面我将分部分详细解释这个脚本的内容和工作原理。
1. 获取真实脚本目录
SOURCE="${BASH_SOURCE[0]}"
while [ -L "$SOURCE" ]; do
DIR="$( 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" ]; then
source "$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; then
echo "警告: 程序已在运行中..."
return 1
fi
# 启动程序并记录PID
nohup 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; then
nohup 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" in
start)
load_conda_env
start_backend
;;
rotate)
rotate_log
;;
status)
check_status
;;
stop)
stop_backend
;;
restart)
stop_backend
load_conda_env
start_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; do
if ! is_running; then
start_service
fi
sleep $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; do
if ! is_running; then
start_service
fi
sleep $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 dev
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"
dev-frontend:
@echo "Starting frontend development server..."
@cd frontend && npm run dev
dev-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 80
CMD ["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.0
container_name: hrfusion-fd-hyq
restart: always
ports:
- "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 文件格式版本 3services:
- 定义要运行的服务列表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: ./backend
ports:
- "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: ./frontend
ports:
- "80:80"
backend:
build: ./backend
ports:
- "8000:8000"
三、统一启动方案
方案1:Makefile 整合多命令
dev:
@echo "Starting all services..."
cd backend && python main.py & cd frontend && npm run dev
docker-up:
docker-compose up -d --build
优点:简单直接,适合开发者本地使用
缺点:进程管理较原始(如用
&
后台运行)
方案2:Docker Compose 统一管理
version: '3'
services:
frontend:
build: ./frontend
ports: ["3000:3000"]
volumes:
- ./frontend:/app
- /app/node_modules
backend:
build: ./backend
ports: ["8000:8000"]
volumes:
- ./backend:/code
db:
image: postgres
environment:
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文件) |
通过灵活组合这些方案,你可以实现从开发到生产的一体化高效工作流!