部署
This commit is contained in:
1 parent
8adb4d5cef
commit
e9c56f8635
6 files changed
+277
-10
No files matched your search
@@ -0,0 +1,34 @@
|
|||||||
|
# Docker build context exclusions (context = offline-backend-20260512/)
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Runtime / local data — must not enter the image
|
||||||
|
data/
|
||||||
|
backend/.deer-flow/
|
||||||
|
backend/.venv/
|
||||||
|
backend/venv/
|
||||||
|
|
||||||
|
# Python caches
|
||||||
|
backend/**/__pycache__/
|
||||||
|
backend/**/*.py[cod]
|
||||||
|
backend/.pytest_cache/
|
||||||
|
backend/.ruff_cache/
|
||||||
|
backend/.coverage
|
||||||
|
backend/htmlcov/
|
||||||
|
|
||||||
|
# IDE / OS
|
||||||
|
.git/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Docs and tests (not needed in runtime image)
|
||||||
|
backend/tests/
|
||||||
|
backend/docs/
|
||||||
|
*.md
|
||||||
|
!backend/README.md
|
||||||
|
|
||||||
|
# Large optional trees (uncomment if build context is too slow)
|
||||||
|
# backend/knowledge/
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Shell scripts must use LF so Linux/bash can run them directly.
|
||||||
|
*.sh text eol=lf
|
||||||
|
scripts/*.sh text eol=lf
|
||||||
@@ -184,17 +184,36 @@ curl -s http://127.0.0.1:8001/openapi.json | head
|
|||||||
HOST_PORT=9001 bash deploy.sh up
|
HOST_PORT=9001 bash deploy.sh up
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.2 离线镜像模式(无法构建时)
|
### 5.2 离线镜像模式(内网/无法访问 docker.io 时必用)
|
||||||
|
|
||||||
若已提供预构建镜像 tar:
|
**典型失败日志:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
failed to fetch anonymous token: Get "https://auth.docker.io/token...": i/o timeout
|
||||||
|
```
|
||||||
|
|
||||||
|
说明服务器访问不了 Docker Hub,**不能在线 build**,必须用离线 tar。
|
||||||
|
|
||||||
|
**步骤 1:在有公网的机器上导出镜像**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
bash scripts/export-offline-image.sh
|
||||||
|
# 产出: deerflow-backend-offline-image-20260505.tar
|
||||||
|
```
|
||||||
|
|
||||||
|
**步骤 2:上传到内网服务器部署目录**
|
||||||
|
|
||||||
|
**步骤 3:内网服务器加载并启动**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DEPLOY_MODE=offline \
|
||||||
IMAGE_TAR=./deerflow-backend-offline-image-20260505.tar \
|
IMAGE_TAR=./deerflow-backend-offline-image-20260505.tar \
|
||||||
IMAGE_TAG=deerflow-backend-offline:20260505 \
|
IMAGE_TAG=deerflow-backend-offline:20260505 \
|
||||||
DEPLOY_MODE=offline \
|
|
||||||
bash deploy.sh up
|
bash deploy.sh up
|
||||||
```
|
```
|
||||||
|
|
||||||
|
若 tar 已在目录中,`deploy.sh` 会**自动检测**并切换为离线模式。
|
||||||
|
|
||||||
或分步:
|
或分步:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -202,6 +221,25 @@ bash deploy.sh load
|
|||||||
IMAGE_TAG=deerflow-backend-offline:20260505 bash deploy.sh up
|
IMAGE_TAG=deerflow-backend-offline:20260505 bash deploy.sh up
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 5.3 配置 Docker 镜像加速(可选,有内网镜像源时)
|
||||||
|
|
||||||
|
编辑 `/etc/docker/daemon.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"registry-mirrors": [
|
||||||
|
"https://你的内网或国内镜像地址"
|
||||||
|
],
|
||||||
|
"dns": ["114.114.114.114", "223.5.5.5"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl restart docker
|
||||||
|
bash deploy.sh build
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. 前端对接
|
## 6. 前端对接
|
||||||
@@ -268,6 +306,73 @@ bash deploy.sh restart
|
|||||||
|
|
||||||
## 10. 故障排查
|
## 10. 故障排查
|
||||||
|
|
||||||
|
### 10.0 Docker 未运行或无权限
|
||||||
|
|
||||||
|
报错示例:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[FAIL] Docker 未运行或无权限...
|
||||||
|
```
|
||||||
|
|
||||||
|
**第一步:看具体原因**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker info
|
||||||
|
```
|
||||||
|
|
||||||
|
**情况 A:permission denied(最常见)**
|
||||||
|
|
||||||
|
说明当前用户不在 `docker` 组:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 把当前用户加入 docker 组
|
||||||
|
sudo usermod -aG docker "$USER"
|
||||||
|
|
||||||
|
# 让组权限立即生效(或重新 SSH 登录)
|
||||||
|
newgrp docker
|
||||||
|
|
||||||
|
# 验证
|
||||||
|
docker info
|
||||||
|
docker ps
|
||||||
|
|
||||||
|
# 再部署
|
||||||
|
bash deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
若仍不行,确认 socket 权限:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -l /var/run/docker.sock
|
||||||
|
# 期望类似: srw-rw---- root docker ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**情况 B:Cannot connect to the Docker daemon**
|
||||||
|
|
||||||
|
说明 Docker 服务没起来:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl status docker
|
||||||
|
sudo systemctl start docker
|
||||||
|
sudo systemctl enable docker
|
||||||
|
docker info
|
||||||
|
bash deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**情况 C:未安装 Docker**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://get.docker.com | sh
|
||||||
|
sudo usermod -aG docker "$USER"
|
||||||
|
newgrp docker
|
||||||
|
bash deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**临时绕过(仅测试用,不推荐生产)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo bash deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
### 10.1 容器启动后立即退出
|
### 10.1 容器启动后立即退出
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -281,6 +386,37 @@ bash deploy.sh logs
|
|||||||
- MySQL 连接失败(检查 `MYSQL_DATABASE_URL` 是否用了容器可达的 IP)
|
- MySQL 连接失败(检查 `MYSQL_DATABASE_URL` 是否用了容器可达的 IP)
|
||||||
- 缺少 `asyncmy`(MySQL 模式):确认 `backend/wheelhouse` 中有对应 wheel,或配置 Python 私服
|
- 缺少 `asyncmy`(MySQL 模式):确认 `backend/wheelhouse` 中有对应 wheel,或配置 Python 私服
|
||||||
|
|
||||||
|
### 10.1.1 脚本报 `$'\r': command not found` 或 `pipefail: invalid option`
|
||||||
|
|
||||||
|
这是 **Windows 换行符(CRLF)** 导致的。在 Linux 上执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sed -i 's/\r$//' deploy.sh start_backend.sh stop_backend.sh scripts/container-entrypoint.sh switch_database_to_mysql.sh
|
||||||
|
chmod +x deploy.sh start_backend.sh stop_backend.sh scripts/container-entrypoint.sh
|
||||||
|
bash deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
或在 Windows 上传前,用 VS Code 右下角把文件编码改为 **LF**。
|
||||||
|
|
||||||
|
### 10.1.2 构建时报 `.venv cannot be used because it is not a valid Python environment`
|
||||||
|
|
||||||
|
原因:开发机(尤其 Windows)上的 `backend/.venv` 被打包进 Docker 构建上下文,Linux 容器内无法使用。
|
||||||
|
|
||||||
|
解决:仓库已添加 `.dockerignore` 排除 `.venv`。更新代码后重新构建:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash deploy.sh build
|
||||||
|
# 或
|
||||||
|
bash deploy.sh up
|
||||||
|
```
|
||||||
|
|
||||||
|
若仍报错,手动删除后再构建:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm -rf backend/.venv
|
||||||
|
bash deploy.sh build
|
||||||
|
```
|
||||||
|
|
||||||
### 10.2 健康检查失败
|
### 10.2 健康检查失败
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -47,8 +47,9 @@ COPY backend ./backend
|
|||||||
|
|
||||||
# Install dependencies with cache mount
|
# Install dependencies with cache mount
|
||||||
# When UV_EXTRAS is set (e.g. "postgres"), installs optional dependencies.
|
# When UV_EXTRAS is set (e.g. "postgres"), installs optional dependencies.
|
||||||
|
# Remove host .venv if present — Windows/macOS venvs break Linux container builds.
|
||||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
sh -c "cd backend && UV_INDEX_URL=${UV_INDEX_URL:-https://pypi.org/simple} uv sync ${UV_EXTRAS:+--extra $UV_EXTRAS}"
|
sh -c "cd backend && rm -rf .venv && UV_INDEX_URL=${UV_INDEX_URL:-https://pypi.org/simple} uv sync ${UV_EXTRAS:+--extra $UV_EXTRAS}"
|
||||||
|
|
||||||
# ── Stage 2: Dev ──────────────────────────────────────────────────────────────
|
# ── Stage 2: Dev ──────────────────────────────────────────────────────────────
|
||||||
# Retains compiler toolchain from builder so startup-time `uv sync` can build
|
# Retains compiler toolchain from builder so startup-time `uv sync` can build
|
||||||
|
|||||||
@@ -37,6 +37,32 @@ IMAGE_TAR="${IMAGE_TAR:-${SCRIPT_DIR}/deerflow-backend-offline-image.tar}"
|
|||||||
DEPLOY_MODE="${DEPLOY_MODE:-build}"
|
DEPLOY_MODE="${DEPLOY_MODE:-build}"
|
||||||
SKIP_BUILD="${SKIP_BUILD:-0}"
|
SKIP_BUILD="${SKIP_BUILD:-0}"
|
||||||
|
|
||||||
|
resolve_offline_tar() {
|
||||||
|
if [ -f "${IMAGE_TAR}" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local candidate
|
||||||
|
for candidate in \
|
||||||
|
"${SCRIPT_DIR}/deerflow-backend-offline-image-20260505.tar" \
|
||||||
|
"${SCRIPT_DIR}/deerflow-backend-offline-image.tar"; do
|
||||||
|
if [ -f "${candidate}" ]; then
|
||||||
|
IMAGE_TAR="${candidate}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 内网服务器通常无法访问 docker.io,检测到离线 tar 时自动切换模式
|
||||||
|
resolve_offline_tar || true
|
||||||
|
if [ -f "${IMAGE_TAR}" ] && [ "${DEPLOY_MODE}" = "build" ]; then
|
||||||
|
DEPLOY_MODE="offline"
|
||||||
|
if [ "${IMAGE_TAG}" = "deerflow-backend:latest" ]; then
|
||||||
|
IMAGE_TAG="deerflow-backend-offline:20260505"
|
||||||
|
fi
|
||||||
|
warn "检测到离线镜像 ${IMAGE_TAR},已自动切换为 DEPLOY_MODE=offline"
|
||||||
|
fi
|
||||||
|
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
@@ -92,7 +118,28 @@ ensure_env() {
|
|||||||
|
|
||||||
check_docker() {
|
check_docker() {
|
||||||
require_cmd docker
|
require_cmd docker
|
||||||
docker info >/dev/null 2>&1 || fail "Docker 未运行或无权限,请确认 docker 服务已启动且当前用户在 docker 组"
|
|
||||||
|
local docker_err
|
||||||
|
docker_err="$(docker info 2>&1)" || {
|
||||||
|
echo "${docker_err}" >&2
|
||||||
|
echo >&2
|
||||||
|
if echo "${docker_err}" | grep -qiE 'permission denied|connect: permission'; then
|
||||||
|
fail "当前用户无权访问 Docker。请执行:
|
||||||
|
sudo usermod -aG docker \$USER
|
||||||
|
newgrp docker
|
||||||
|
然后重新运行: bash deploy.sh
|
||||||
|
|
||||||
|
临时方案(不推荐长期使用): sudo bash deploy.sh"
|
||||||
|
fi
|
||||||
|
if echo "${docker_err}" | grep -qiE 'cannot connect|Is the docker daemon running|connection refused'; then
|
||||||
|
fail "Docker 服务未运行。请先启动:
|
||||||
|
sudo systemctl start docker
|
||||||
|
sudo systemctl enable docker
|
||||||
|
然后重新运行: bash deploy.sh"
|
||||||
|
fi
|
||||||
|
fail "Docker 不可用。请先在服务器上执行 docker info 查看详细错误"
|
||||||
|
}
|
||||||
|
|
||||||
docker compose version >/dev/null 2>&1 || fail "未找到 docker compose,请安装 Docker Compose V2"
|
docker compose version >/dev/null 2>&1 || fail "未找到 docker compose,请安装 Docker Compose V2"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,16 +169,33 @@ load_offline_image() {
|
|||||||
ok "镜像已存在: ${IMAGE_TAG}"
|
ok "镜像已存在: ${IMAGE_TAG}"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
[ -f "${IMAGE_TAR}" ] || fail "离线镜像不存在: ${IMAGE_TAR},请设置 IMAGE_TAR 或先执行 bash deploy.sh build"
|
[ -f "${IMAGE_TAR}" ] || fail "离线镜像不存在: ${IMAGE_TAR}
|
||||||
|
|
||||||
|
内网服务器无法访问 docker.io 时,请:
|
||||||
|
1. 在有公网的机器上执行 bash scripts/export-offline-image.sh 导出 tar
|
||||||
|
2. 上传到本目录后执行: DEPLOY_MODE=offline bash deploy.sh up"
|
||||||
info "加载离线镜像: ${IMAGE_TAR}"
|
info "加载离线镜像: ${IMAGE_TAR}"
|
||||||
docker load -i "${IMAGE_TAR}"
|
local load_out loaded_tag
|
||||||
|
load_out="$(docker load -i "${IMAGE_TAR}")"
|
||||||
|
echo "${load_out}"
|
||||||
|
loaded_tag="$(echo "${load_out}" | sed -n 's/^Loaded image: //p' | tail -n1)"
|
||||||
|
if [ -n "${loaded_tag}" ] && ! docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then
|
||||||
|
info "将 ${loaded_tag} 标记为 ${IMAGE_TAG}"
|
||||||
|
docker tag "${loaded_tag}" "${IMAGE_TAG}"
|
||||||
|
fi
|
||||||
|
docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1 || fail "镜像加载后未找到 ${IMAGE_TAG},请检查 IMAGE_TAG 是否与 tar 内标签一致"
|
||||||
ok "镜像加载完成: ${IMAGE_TAG}"
|
ok "镜像加载完成: ${IMAGE_TAG}"
|
||||||
}
|
}
|
||||||
|
|
||||||
build_image() {
|
build_image() {
|
||||||
info "构建 Docker 镜像: ${IMAGE_TAG}"
|
info "构建 Docker 镜像: ${IMAGE_TAG}(需要能访问 docker.io / ghcr.io)"
|
||||||
export IMAGE_TAG
|
export IMAGE_TAG
|
||||||
"${COMPOSE[@]}" build gateway
|
if ! "${COMPOSE[@]}" build gateway; then
|
||||||
|
fail "在线构建失败。内网服务器请改用离线镜像:
|
||||||
|
DEPLOY_MODE=offline IMAGE_TAR=./deerflow-backend-offline-image-20260505.tar bash deploy.sh up
|
||||||
|
|
||||||
|
或在 /etc/docker/daemon.json 配置 registry-mirrors 后重试。"
|
||||||
|
fi
|
||||||
ok "镜像构建完成"
|
ok "镜像构建完成"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +248,11 @@ cmd_up() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
info "启动容器..."
|
info "启动容器..."
|
||||||
"${COMPOSE[@]}" up -d --remove-orphans
|
if [ "${DEPLOY_MODE}" = "offline" ] || docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then
|
||||||
|
"${COMPOSE[@]}" up -d --no-build --remove-orphans
|
||||||
|
else
|
||||||
|
"${COMPOSE[@]}" up -d --remove-orphans
|
||||||
|
fi
|
||||||
ok "容器已启动: ${CONTAINER_NAME}"
|
ok "容器已启动: ${CONTAINER_NAME}"
|
||||||
echo
|
echo
|
||||||
echo " API 地址: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo 127.0.0.1):${HOST_PORT}"
|
echo " API 地址: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo 127.0.0.1):${HOST_PORT}"
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 在有公网的环境构建并导出离线镜像 tar,供内网服务器 docker load 使用。
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
cd "${SCRIPT_DIR}"
|
||||||
|
|
||||||
|
IMAGE_TAG="${IMAGE_TAG:-deerflow-backend-offline:20260505}"
|
||||||
|
OUTPUT="${OUTPUT:-${SCRIPT_DIR}/deerflow-backend-offline-image-20260505.tar}"
|
||||||
|
|
||||||
|
echo "构建镜像: ${IMAGE_TAG}"
|
||||||
|
IMAGE_TAG="${IMAGE_TAG}" docker compose -p deerflow-backend -f docker-compose.yaml build gateway
|
||||||
|
|
||||||
|
loaded="$(docker images --format '{{.Repository}}:{{.Tag}}' | grep -F "${IMAGE_TAG}" | head -n1 || true)"
|
||||||
|
if [ -z "${loaded}" ]; then
|
||||||
|
# compose build 可能产出 deerflow-backend:latest,再打离线标签
|
||||||
|
docker tag deerflow-backend:latest "${IMAGE_TAG}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "导出到: ${OUTPUT}"
|
||||||
|
docker save -o "${OUTPUT}" "${IMAGE_TAG}"
|
||||||
|
ls -lh "${OUTPUT}"
|
||||||
|
echo
|
||||||
|
echo "上传到内网服务器后执行:"
|
||||||
|
echo " DEPLOY_MODE=offline IMAGE_TAR=${OUTPUT##*/} IMAGE_TAG=${IMAGE_TAG} bash deploy.sh up"
|
||||||
Reference in new issue
Block a user