Explorar el Código

build: 添加 emoon-okr 持久化部署配置

wangkangyjy hace 4 días
padre
commit
639e0979e4
Se han modificado 5 ficheros con 154 adiciones y 6 borrados
  1. 6 0
      .env.example
  2. 1 6
      Dockerfile
  3. 22 0
      compose.yaml
  4. 64 0
      docs/docker-deployment.md
  5. 61 0
      scripts/test-docker-persistence.sh

+ 6 - 0
.env.example

@@ -0,0 +1,6 @@
+# 复制为 .env 后替换所有 CHANGE_ME 值。
+APP_IMAGE_TAG=latest
+APP_PORT=18080
+APP_JWT_SECRET=CHANGE_ME_USE_A_RANDOM_STRING_OF_AT_LEAST_32_CHARACTERS
+APP_SUPER_ADMIN_PASSWORD=CHANGE_ME_USE_A_STRONG_PASSWORD_OF_AT_LEAST_12_CHARACTERS
+APP_CORS_ORIGINS=https://okr.example.com

+ 1 - 6
Dockerfile

@@ -10,7 +10,6 @@ RUN npm run build
 FROM maven:3.9.9-eclipse-temurin-17 AS backend-build
 WORKDIR /build/backend
 COPY backend/pom.xml ./
-RUN mvn -B dependency:go-offline
 COPY backend/src ./src
 RUN rm -rf src/main/resources/static && mkdir -p src/main/resources/static
 COPY --from=frontend-build /build/frontend/dist ./src/main/resources/static
@@ -19,10 +18,6 @@ RUN mvn -B clean package -DskipTests
 FROM eclipse-temurin:17-jre-jammy
 WORKDIR /app
 
-RUN apt-get update \
-    && apt-get install -y --no-install-recommends curl \
-    && rm -rf /var/lib/apt/lists/*
-
 COPY docker/entrypoint.sh /app/entrypoint.sh
 COPY --from=backend-build /build/backend/target/okr-performance-1.0.0.jar /app/okr-performance.jar
 RUN chmod +x /app/entrypoint.sh
@@ -35,7 +30,7 @@ EXPOSE 18080
 VOLUME ["/app/data"]
 
 HEALTHCHECK --interval=15s --timeout=5s --start-period=30s --retries=5 \
-  CMD curl --fail --silent http://127.0.0.1:18080/ >/dev/null || exit 1
+  CMD bash -c 'exec 3<>/dev/tcp/127.0.0.1/18080'
 
 ENTRYPOINT ["/app/entrypoint.sh"]
 CMD ["java", "-jar", "/app/okr-performance.jar"]

+ 22 - 0
compose.yaml

@@ -0,0 +1,22 @@
+services:
+  okr-performance:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    image: emoon-okr:${APP_IMAGE_TAG:-latest}
+    container_name: okr-performance
+    restart: unless-stopped
+    stop_grace_period: 30s
+    ports:
+      - "${APP_PORT:-18080}:18080"
+    environment:
+      SPRING_PROFILES_ACTIVE: prod
+      SPRING_DATASOURCE_URL: jdbc:sqlite:/app/data/okr-performance.db
+      APP_DATA_DIR: /app/data
+      APP_BACKUP_DIR: /app/data/backups
+      APP_JWT_SECRET: ${APP_JWT_SECRET}
+      APP_SUPER_ADMIN_PASSWORD: ${APP_SUPER_ADMIN_PASSWORD}
+      APP_CORS_ORIGINS: ${APP_CORS_ORIGINS}
+      TZ: Asia/Shanghai
+    volumes:
+      - ./data:/app/data

+ 64 - 0
docs/docker-deployment.md

@@ -0,0 +1,64 @@
+# Docker 部署与升级
+
+默认镜像名称为 `emoon-okr:latest`。
+
+## 数据持久化约定
+
+SQLite 数据库不进入镜像,始终保存在宿主机:
+
+```text
+宿主机 ./data/okr-performance.db
+              ↕ bind mount
+容器   /app/data/okr-performance.db
+```
+
+容器内 `/app/data/backups` 同样对应宿主机 `./data/backups`。删除镜像、重建容器不会删除数据库;删除宿主机 `./data` 会永久删除业务数据。
+
+## 首次启动
+
+```bash
+cp .env.example .env
+# 修改 .env 中的 JWT 密钥、管理员密码和访问域名
+mkdir -p data
+docker compose up -d --build
+docker compose logs -f okr-performance
+```
+
+启动脚本会确认 `/app/data` 存在且可写。`okr-performance.db` 不存在时,Spring Boot 自动创建表结构、执行数据库迁移并初始化基础数据。
+
+## 版本升级
+
+建议升级前先备份,再重建镜像和容器:
+
+```bash
+mkdir -p data/backups
+sqlite3 data/okr-performance.db ".backup 'data/backups/manual-before-upgrade.db'"
+docker compose build --pull
+docker compose up -d --force-recreate --remove-orphans
+docker compose logs -f okr-performance
+```
+
+升级过程只替换镜像和容器,`./data` 继续挂载到新容器。应用启动后会在原数据库上执行尚未执行的增量迁移。
+
+不要在升级脚本中加入 `rm -rf data`,也不要把数据库复制到 Dockerfile 或镜像中。
+
+> `docker compose down -v` 不会删除这里的宿主机 `./data` 绑定目录,但仍建议生产环境只执行上面的升级命令,避免误操作其他 Docker 资源。
+
+## 恢复备份
+
+恢复前先停止服务,避免 SQLite 仍有写入:
+
+```bash
+docker compose stop okr-performance
+cp data/backups/manual-before-upgrade.db data/okr-performance.db
+rm -f data/okr-performance.db-wal data/okr-performance.db-shm
+docker compose up -d
+```
+
+## 常用检查
+
+```bash
+docker compose ps
+docker compose logs --tail=200 okr-performance
+sqlite3 data/okr-performance.db "PRAGMA integrity_check;"
+```

+ 61 - 0
scripts/test-docker-persistence.sh

@@ -0,0 +1,61 @@
+#!/bin/sh
+set -eu
+
+ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
+IMAGE_NAME=${IMAGE_NAME:-emoon-okr:persistence-test}
+CONTAINER_NAME="okr-persistence-test-$$"
+DATA_DIR=$(mktemp -d "${TMPDIR:-/tmp}/okr-docker-data.XXXXXX")
+
+cleanup() {
+  docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
+  rm -rf "$DATA_DIR"
+}
+trap cleanup EXIT INT TERM
+
+wait_for_startup() {
+  attempts=0
+  while [ "$attempts" -lt 90 ]; do
+    if docker logs "$CONTAINER_NAME" 2>&1 | grep -q "Started OkrApplication"; then
+      return 0
+    fi
+    if [ "$(docker inspect -f '{{.State.Running}}' "$CONTAINER_NAME" 2>/dev/null || true)" != "true" ]; then
+      docker logs "$CONTAINER_NAME" >&2 || true
+      return 1
+    fi
+    attempts=$((attempts + 1))
+    sleep 1
+  done
+  docker logs "$CONTAINER_NAME" >&2 || true
+  echo "容器启动超时" >&2
+  return 1
+}
+
+start_container() {
+  docker run -d \
+    --name "$CONTAINER_NAME" \
+    -e APP_SEED_DEMO_DATA=false \
+    -v "$DATA_DIR:/app/data" \
+    "$IMAGE_NAME" >/dev/null
+  wait_for_startup
+}
+
+cd "$ROOT_DIR"
+docker build -t "$IMAGE_NAME" .
+
+start_container
+test -f "$DATA_DIR/okr-performance.db"
+docker rm -f "$CONTAINER_NAME" >/dev/null
+
+sqlite3 "$DATA_DIR/okr-performance.db" \
+  "CREATE TABLE docker_persistence_probe (value TEXT NOT NULL); INSERT INTO docker_persistence_probe VALUES ('survived');"
+
+start_container
+docker rm -f "$CONTAINER_NAME" >/dev/null
+
+RESULT=$(sqlite3 "$DATA_DIR/okr-performance.db" "SELECT value FROM docker_persistence_probe LIMIT 1;")
+if [ "$RESULT" != "survived" ]; then
+  echo "容器重建后 SQLite 哨兵数据丢失" >&2
+  exit 1
+fi
+
+echo "Docker SQLite 首次初始化与容器重建持久性检查通过"