| @@ -0,0 +1,128 @@ | |||
| #!/usr/bin/env bash | |||
| set -Eeuo pipefail | |||
| # Apply an incremental image update on the target runtime server. | |||
| # Usage: | |||
| # bash apply-update.sh | |||
| # bash apply-update.sh emp-admin emp-monitor | |||
| # PROJECT_NAME=emp-test ENV_FILE=.env COMPOSE_FILE=docker-compose.yml bash apply-update.sh | |||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |||
| cd "$SCRIPT_DIR" | |||
| PROJECT_NAME="${PROJECT_NAME:-emp-test}" | |||
| ENV_FILE="${ENV_FILE:-.env}" | |||
| COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" | |||
| IMAGE_TAR="${IMAGE_TAR:-images.tar}" | |||
| SERVICE_FILE="${SERVICE_FILE:-services.txt}" | |||
| log() { | |||
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | |||
| } | |||
| die() { | |||
| echo "ERROR: $*" >&2 | |||
| exit 1 | |||
| } | |||
| need_cmd() { | |||
| command -v "$1" >/dev/null 2>&1 || die "Missing command: $1" | |||
| } | |||
| normalize_service() { | |||
| case "$1" in | |||
| gateway|emp-gateway) echo "emp-gateway" ;; | |||
| auth|emp-auth) echo "emp-auth" ;; | |||
| monitor|emp-monitor) echo "emp-monitor" ;; | |||
| data|emp-data) echo "emp-data" ;; | |||
| pdf|emp-pdf) echo "emp-pdf" ;; | |||
| ws|emp-ws) echo "emp-ws" ;; | |||
| admin|emp-admin) echo "emp-admin" ;; | |||
| *) die "Unknown service: $1. Allowed: gateway auth monitor data pdf ws admin" ;; | |||
| esac | |||
| } | |||
| resolve_compose_cmd() { | |||
| if docker compose version >/dev/null 2>&1; then | |||
| echo "docker compose" | |||
| elif command -v docker-compose >/dev/null 2>&1; then | |||
| echo "docker-compose" | |||
| else | |||
| die "Missing docker compose" | |||
| fi | |||
| } | |||
| resolve_runtime_file() { | |||
| local requested="$1" | |||
| local default_name="$2" | |||
| if [[ -f "$requested" ]]; then | |||
| echo "$requested" | |||
| return | |||
| fi | |||
| if [[ "$requested" != "$default_name" ]]; then | |||
| echo "$requested" | |||
| return | |||
| fi | |||
| local candidates=( | |||
| "../runtime/$default_name" | |||
| "/home/admin-x99/emp-test/runtime/$default_name" | |||
| ) | |||
| local candidate | |||
| for candidate in "${candidates[@]}"; do | |||
| if [[ -f "$candidate" ]]; then | |||
| echo "$candidate" | |||
| return | |||
| fi | |||
| done | |||
| echo "$requested" | |||
| } | |||
| read_services() { | |||
| local raw_services=() | |||
| if [[ "$#" -gt 0 ]]; then | |||
| raw_services=("$@") | |||
| elif [[ -f "$SERVICE_FILE" ]]; then | |||
| mapfile -t raw_services < "$SERVICE_FILE" | |||
| else | |||
| die "No services specified. Pass services as args or provide $SERVICE_FILE." | |||
| fi | |||
| local service | |||
| for service in "${raw_services[@]}"; do | |||
| [[ -n "$service" ]] || continue | |||
| normalize_service "$service" | |||
| done | |||
| } | |||
| need_cmd docker | |||
| ENV_FILE="$(resolve_runtime_file "$ENV_FILE" ".env")" | |||
| COMPOSE_FILE="$(resolve_runtime_file "$COMPOSE_FILE" "docker-compose.yml")" | |||
| [[ -f "$ENV_FILE" ]] || die "Missing env file: $ENV_FILE" | |||
| [[ -f "$COMPOSE_FILE" ]] || die "Missing compose file: $COMPOSE_FILE" | |||
| [[ -f "$IMAGE_TAR" ]] || die "Missing image tar: $IMAGE_TAR" | |||
| mapfile -t UPDATE_SERVICES < <(read_services "$@") | |||
| [[ "${#UPDATE_SERVICES[@]}" -gt 0 ]] || die "No services to update." | |||
| COMPOSE_CMD="$(resolve_compose_cmd)" | |||
| log "Load images: $IMAGE_TAR" | |||
| docker load -i "$IMAGE_TAR" | |||
| log "Recreate services: ${UPDATE_SERVICES[*]}" | |||
| # shellcheck disable=SC2086 | |||
| $COMPOSE_CMD \ | |||
| --env-file "$ENV_FILE" \ | |||
| -f "$COMPOSE_FILE" \ | |||
| -p "$PROJECT_NAME" \ | |||
| up -d --no-deps --force-recreate "${UPDATE_SERVICES[@]}" | |||
| log "Current service status" | |||
| # shellcheck disable=SC2086 | |||
| $COMPOSE_CMD \ | |||
| --env-file "$ENV_FILE" \ | |||
| -f "$COMPOSE_FILE" \ | |||
| -p "$PROJECT_NAME" \ | |||
| ps "${UPDATE_SERVICES[@]}" | |||
| @@ -0,0 +1,316 @@ | |||
| #!/usr/bin/env bash | |||
| set -Eeuo pipefail | |||
| # Build selected app images and package them as an incremental update. | |||
| # Usage: | |||
| # ./build-update.sh admin monitor | |||
| # SERVICES="admin monitor" ./build-update.sh | |||
| # EMP_ROOT=/path/to/emp IMAGE_TAG=20260602153000 ./build-update.sh emp-admin emp-monitor | |||
| # | |||
| # Output: | |||
| # dist/emp-test-update-${IMAGE_TAG}-${services}.tar.gz | |||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |||
| DIST_DIR="${DIST_DIR:-$SCRIPT_DIR/dist}" | |||
| BUILD_CONTEXT_DIR="$SCRIPT_DIR/.build-context" | |||
| IMAGE_NAMESPACE="${IMAGE_NAMESPACE:-emp-test}" | |||
| IMAGE_TAG="${IMAGE_TAG:-$(date '+%Y%m%d%H%M%S')}" | |||
| NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmjs.org}" | |||
| SKIP_BUILD="${SKIP_BUILD:-0}" | |||
| log() { | |||
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | |||
| } | |||
| die() { | |||
| echo "ERROR: $*" >&2 | |||
| exit 1 | |||
| } | |||
| need_cmd() { | |||
| command -v "$1" >/dev/null 2>&1 || die "Missing command: $1" | |||
| } | |||
| has_emp_root() { | |||
| [[ -d "$1/emp_server" && -d "$1/emp_admin" && -d "$1/emp_ws" ]] | |||
| } | |||
| resolve_root_dir() { | |||
| if [[ -n "${EMP_ROOT:-}" ]]; then | |||
| local root | |||
| root="$(cd "$EMP_ROOT" && pwd)" | |||
| has_emp_root "$root" || die "EMP_ROOT is invalid: $EMP_ROOT" | |||
| printf '%s\n' "$root" | |||
| return | |||
| fi | |||
| local candidates=( | |||
| "$SCRIPT_DIR/../.." | |||
| "$SCRIPT_DIR/../../emp" | |||
| "$SCRIPT_DIR/.." | |||
| "$PWD" | |||
| ) | |||
| local candidate root | |||
| for candidate in "${candidates[@]}"; do | |||
| if [[ -d "$candidate" ]]; then | |||
| root="$(cd "$candidate" && pwd)" | |||
| if has_emp_root "$root"; then | |||
| printf '%s\n' "$root" | |||
| return | |||
| fi | |||
| fi | |||
| done | |||
| die "Cannot find EMP root. Set EMP_ROOT=/path/to/emp where emp_server, emp_admin and emp_ws exist." | |||
| } | |||
| normalize_service() { | |||
| case "$1" in | |||
| gateway|emp-gateway) echo "gateway" ;; | |||
| auth|emp-auth) echo "auth" ;; | |||
| monitor|emp-monitor) echo "monitor" ;; | |||
| data|emp-data) echo "data" ;; | |||
| pdf|emp-pdf) echo "pdf" ;; | |||
| ws|emp-ws) echo "ws" ;; | |||
| admin|emp-admin) echo "admin" ;; | |||
| *) die "Unknown service: $1. Allowed: gateway auth monitor data pdf ws admin" ;; | |||
| esac | |||
| } | |||
| compose_service_name() { | |||
| echo "emp-$1" | |||
| } | |||
| image_name() { | |||
| echo "$IMAGE_NAMESPACE/emp-$1" | |||
| } | |||
| java_module_name() { | |||
| case "$1" in | |||
| gateway) echo "emp_gateway" ;; | |||
| auth) echo "emp_auth" ;; | |||
| monitor) echo "emp_monitor" ;; | |||
| data) echo "emp_data" ;; | |||
| *) return 1 ;; | |||
| esac | |||
| } | |||
| is_java_service() { | |||
| case "$1" in | |||
| gateway|auth|monitor|data) return 0 ;; | |||
| *) return 1 ;; | |||
| esac | |||
| } | |||
| read_requested_services() { | |||
| local raw_services=() | |||
| if [[ "$#" -gt 0 ]]; then | |||
| raw_services=("$@") | |||
| elif [[ -n "${SERVICES:-}" ]]; then | |||
| # shellcheck disable=SC2206 | |||
| raw_services=($SERVICES) | |||
| else | |||
| die "No services specified. Example: ./build-update.sh admin monitor" | |||
| fi | |||
| local -A seen=() | |||
| local service normalized | |||
| for service in "${raw_services[@]}"; do | |||
| normalized="$(normalize_service "$service")" | |||
| if [[ -z "${seen[$normalized]:-}" ]]; then | |||
| seen[$normalized]=1 | |||
| echo "$normalized" | |||
| fi | |||
| done | |||
| } | |||
| join_by_comma() { | |||
| local IFS=, | |||
| echo "$*" | |||
| } | |||
| run_step() { | |||
| local workdir="$1" | |||
| shift | |||
| (cd "$workdir" && "$@") | |||
| } | |||
| build_java_images() { | |||
| local services=("$@") | |||
| [[ "${#services[@]}" -gt 0 ]] || return 0 | |||
| local server_dir="$ROOT_DIR/emp_server" | |||
| local modules=() | |||
| local service module jar target_dir | |||
| for service in "${services[@]}"; do | |||
| modules+=("$(java_module_name "$service")") | |||
| done | |||
| log "Build backend jars: ${modules[*]}" | |||
| run_step "$server_dir" mvn package -DskipTests -B -pl "$(join_by_comma "${modules[@]}")" -am | |||
| for service in "${services[@]}"; do | |||
| module="$(java_module_name "$service")" | |||
| target_dir="$server_dir/$module/target" | |||
| jar="$(find "$target_dir" -maxdepth 1 -name '*.jar' ! -name '*original*' ! -name 'app.jar' | head -1)" | |||
| [[ -n "$jar" ]] || die "Jar not found for $module" | |||
| cp "$jar" "$target_dir/app.jar" | |||
| log "Build image: $(image_name "$service"):$IMAGE_TAG" | |||
| run_step "$server_dir" docker build \ | |||
| -f Dockerfile.service \ | |||
| --build-arg "MODULE=$module" \ | |||
| -t "$(image_name "$service"):$IMAGE_TAG" \ | |||
| -t "$(image_name "$service"):latest" \ | |||
| . | |||
| done | |||
| } | |||
| build_pdf_image() { | |||
| local server_dir="$ROOT_DIR/emp_server" | |||
| log "Build image: $(image_name pdf):$IMAGE_TAG" | |||
| run_step "$server_dir" docker build \ | |||
| -f emp_pdf/Dockerfile \ | |||
| -t "$(image_name pdf):$IMAGE_TAG" \ | |||
| -t "$(image_name pdf):latest" \ | |||
| emp_pdf | |||
| } | |||
| build_ws_image() { | |||
| log "Build image: $(image_name ws):$IMAGE_TAG" | |||
| run_step "$ROOT_DIR" docker build \ | |||
| -f "$SCRIPT_DIR/dockerfiles/emp-ws.Dockerfile" \ | |||
| --build-arg "NPM_REGISTRY=$NPM_REGISTRY" \ | |||
| -t "$(image_name ws):$IMAGE_TAG" \ | |||
| -t "$(image_name ws):latest" \ | |||
| . | |||
| } | |||
| build_admin_image() { | |||
| local admin_dir="$ROOT_DIR/emp_admin" | |||
| local context_dir="$BUILD_CONTEXT_DIR/emp-admin" | |||
| log "Build frontend dist" | |||
| run_step "$admin_dir" pnpm install --frozen-lockfile | |||
| run_step "$admin_dir" pnpm run build:shunfeng | |||
| log "Prepare frontend image context" | |||
| rm -rf "$context_dir" | |||
| mkdir -p "$context_dir" | |||
| cp -R "$admin_dir/dist" "$context_dir/dist" | |||
| cp "$SCRIPT_DIR/nginx/admin.conf" "$context_dir/admin.conf" | |||
| log "Build image: $(image_name admin):$IMAGE_TAG" | |||
| run_step "$context_dir" docker build \ | |||
| -f "$SCRIPT_DIR/dockerfiles/emp-admin.Dockerfile" \ | |||
| -t "$(image_name admin):$IMAGE_TAG" \ | |||
| -t "$(image_name admin):latest" \ | |||
| . | |||
| } | |||
| prepare_package() { | |||
| local services_slug="$1" | |||
| PACKAGE_NAME="${PACKAGE_NAME:-emp-test-update-${IMAGE_TAG}-${services_slug}}" | |||
| PACKAGE_DIR="$DIST_DIR/$PACKAGE_NAME" | |||
| PACKAGE_ARCHIVE="$DIST_DIR/${PACKAGE_NAME}.tar.gz" | |||
| rm -rf "$PACKAGE_DIR" | |||
| mkdir -p "$PACKAGE_DIR" | |||
| cp "$SCRIPT_DIR/apply-update.sh" "$PACKAGE_DIR/apply-update.sh" | |||
| chmod +x "$PACKAGE_DIR/apply-update.sh" | |||
| local service | |||
| for service in "${REQUESTED_SERVICES[@]}"; do | |||
| compose_service_name "$service" | |||
| done > "$PACKAGE_DIR/services.txt" | |||
| { | |||
| echo "image_namespace=$IMAGE_NAMESPACE" | |||
| echo "image_tag=$IMAGE_TAG" | |||
| echo "services=${REQUESTED_SERVICES[*]}" | |||
| echo "compose_services=$(tr '\n' ' ' < "$PACKAGE_DIR/services.txt")" | |||
| echo "created_at=$(date '+%Y-%m-%d %H:%M:%S')" | |||
| } > "$PACKAGE_DIR/manifest.txt" | |||
| } | |||
| save_images() { | |||
| local images=() | |||
| local service image | |||
| for service in "${REQUESTED_SERVICES[@]}"; do | |||
| image="$(image_name "$service")" | |||
| images+=("$image:$IMAGE_TAG" "$image:latest") | |||
| done | |||
| log "Save images: ${images[*]}" | |||
| docker save -o "$PACKAGE_DIR/images.tar" "${images[@]}" | |||
| } | |||
| archive_package() { | |||
| mkdir -p "$DIST_DIR" | |||
| rm -f "$PACKAGE_ARCHIVE" | |||
| tar -czf "$PACKAGE_ARCHIVE" -C "$DIST_DIR" "$PACKAGE_NAME" | |||
| log "Update package created: $PACKAGE_ARCHIVE" | |||
| } | |||
| need_cmd docker | |||
| need_cmd tar | |||
| mapfile -t REQUESTED_SERVICES < <(read_requested_services "$@") | |||
| [[ "${#REQUESTED_SERVICES[@]}" -gt 0 ]] || die "No services to update." | |||
| ROOT_DIR="$(resolve_root_dir)" | |||
| SERVICES_SLUG="$(IFS=-; echo "${REQUESTED_SERVICES[*]}")" | |||
| log "EMP root: $ROOT_DIR" | |||
| log "Deploy root: $SCRIPT_DIR" | |||
| log "Update services: ${REQUESTED_SERVICES[*]}" | |||
| log "Image tag: $IMAGE_TAG" | |||
| if [[ "$SKIP_BUILD" != "1" ]]; then | |||
| JAVA_SERVICES=() | |||
| NEED_PNPM=0 | |||
| for service in "${REQUESTED_SERVICES[@]}"; do | |||
| if is_java_service "$service"; then | |||
| JAVA_SERVICES+=("$service") | |||
| fi | |||
| if [[ "$service" == "admin" ]]; then | |||
| NEED_PNPM=1 | |||
| fi | |||
| done | |||
| if [[ "${#JAVA_SERVICES[@]}" -gt 0 ]]; then | |||
| need_cmd mvn | |||
| fi | |||
| if [[ "$NEED_PNPM" == "1" ]]; then | |||
| need_cmd pnpm | |||
| fi | |||
| build_java_images "${JAVA_SERVICES[@]}" | |||
| for service in "${REQUESTED_SERVICES[@]}"; do | |||
| case "$service" in | |||
| pdf) build_pdf_image ;; | |||
| ws) build_ws_image ;; | |||
| admin) build_admin_image ;; | |||
| esac | |||
| done | |||
| else | |||
| log "Skip build, package existing local images only" | |||
| fi | |||
| prepare_package "$SERVICES_SLUG" | |||
| save_images | |||
| archive_package | |||
| cat <<EOF | |||
| Next steps on target server: | |||
| mkdir -p /tmp/emp-update | |||
| tar -xzf $(basename "$PACKAGE_ARCHIVE") -C /tmp/emp-update --strip-components=1 | |||
| cd /tmp/emp-update | |||
| bash apply-update.sh | |||
| EOF | |||
| @@ -187,7 +187,7 @@ services: | |||
| tdengine: | |||
| condition: service_healthy | |||
| emp-pdf: | |||
| condition: service_started | |||
| condition: service_healthy | |||
| emp-data: | |||
| <<: *app-env | |||
| @@ -206,6 +206,11 @@ services: | |||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-pdf:${IMAGE_TAG:-latest} | |||
| ports: | |||
| - "127.0.0.1:${PDF_HOST_PORT:-3100}:3100" | |||
| healthcheck: | |||
| test: ["CMD-SHELL", "node -e \"require('http').get('http://127.0.0.1:3100/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))\""] | |||
| interval: 10s | |||
| timeout: 5s | |||
| retries: 30 | |||
| emp-ws: | |||
| <<: *app-env | |||
| @@ -173,7 +173,7 @@ services: | |||
| tdengine: | |||
| condition: service_healthy | |||
| emp-pdf: | |||
| condition: service_started | |||
| condition: service_healthy | |||
| emp-data: | |||
| <<: *app-env | |||
| @@ -190,6 +190,11 @@ services: | |||
| emp-pdf: | |||
| <<: *app-env | |||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-pdf:${IMAGE_TAG:-latest} | |||
| healthcheck: | |||
| test: ["CMD-SHELL", "node -e \"require('http').get('http://127.0.0.1:3100/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))\""] | |||
| interval: 10s | |||
| timeout: 5s | |||
| retries: 30 | |||
| emp-ws: | |||
| <<: *app-env | |||
| @@ -228,4 +233,4 @@ volumes: | |||
| tdengine_data: | |||
| tdengine_log: | |||
| nacos_data: | |||
| nacos_logs: | |||
| nacos_logs: | |||
| @@ -0,0 +1,207 @@ | |||
| # 隔离测试环境增量更新说明 | |||
| 本文档用于后续只更新部分 EMP 服务镜像,不再每次全量打包中间件和所有服务。 | |||
| ## 一、适用场景 | |||
| 适用于只更新以下应用服务中的一个或多个: | |||
| | 简写 | Compose 服务 | 镜像 | | |||
| | --- | --- | --- | | |||
| | gateway | emp-gateway | emp-test/emp-gateway | | |||
| | auth | emp-auth | emp-test/emp-auth | | |||
| | monitor | emp-monitor | emp-test/emp-monitor | | |||
| | data | emp-data | emp-test/emp-data | | |||
| | pdf | emp-pdf | emp-test/emp-pdf | | |||
| | ws | emp-ws | emp-test/emp-ws | | |||
| | admin | emp-admin | emp-test/emp-admin | | |||
| 中间件 MySQL、Redis、Kafka、TDengine、Nacos 不走增量更新包。 | |||
| ## 二、构建机生成增量包 | |||
| 进入部署脚本目录: | |||
| ```bash | |||
| cd /home/git/emp_test_deploy/isolated | |||
| ``` | |||
| 只更新 `admin` 和 `monitor`: | |||
| ```bash | |||
| EMP_ROOT=/home/git/emp \ | |||
| IMAGE_NAMESPACE=emp-test \ | |||
| ./build-update.sh admin monitor | |||
| ``` | |||
| 生成文件在: | |||
| ```bash | |||
| dist/emp-test-update-<时间戳>-admin-monitor.tar.gz | |||
| ``` | |||
| 也可以更新其他服务: | |||
| ```bash | |||
| # 只更新前端 | |||
| EMP_ROOT=/home/git/emp IMAGE_NAMESPACE=emp-test ./build-update.sh admin | |||
| # 只更新数据服务 | |||
| EMP_ROOT=/home/git/emp IMAGE_NAMESPACE=emp-test ./build-update.sh data | |||
| # 更新模拟器 / WebSocket | |||
| EMP_ROOT=/home/git/emp IMAGE_NAMESPACE=emp-test ./build-update.sh ws | |||
| # 更新 PDF 服务 | |||
| EMP_ROOT=/home/git/emp IMAGE_NAMESPACE=emp-test ./build-update.sh pdf | |||
| ``` | |||
| 如果镜像已经在本机构建好,只想重新打包已有镜像: | |||
| ```bash | |||
| EMP_ROOT=/home/git/emp \ | |||
| IMAGE_NAMESPACE=emp-test \ | |||
| SKIP_BUILD=1 \ | |||
| ./build-update.sh admin monitor | |||
| ``` | |||
| ## 三、传输到甲方服务器 | |||
| 将增量包传到甲方服务器任意目录,例如: | |||
| ```bash | |||
| /home/admin-x99/emp-test/update/emp-test-update-20260602153000-admin-monitor.tar.gz | |||
| ``` | |||
| ## 四、甲方服务器应用增量包 | |||
| 进入服务器: | |||
| ```bash | |||
| cd /home/admin-x99/emp-test | |||
| mkdir -p update-runtime | |||
| tar -xzf update/emp-test-update-20260602153000-admin-monitor.tar.gz \ | |||
| -C update-runtime \ | |||
| --strip-components=1 | |||
| cd update-runtime | |||
| ``` | |||
| 执行增量更新: | |||
| ```bash | |||
| bash apply-update.sh | |||
| ``` | |||
| 脚本会优先使用当前目录下的 `.env`、`docker-compose.yml`;如果当前目录没有,会自动查找 `../runtime/` 和 `/home/admin-x99/emp-test/runtime/` 下的运行配置。 | |||
| 脚本会自动执行: | |||
| ```bash | |||
| docker load -i images.tar | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test \ | |||
| up -d --no-deps --force-recreate emp-admin emp-monitor | |||
| ``` | |||
| 如果当前目录没有 `.env` 和 `docker-compose.yml`,可以显式指定运行环境目录中的文件: | |||
| ```bash | |||
| PROJECT_NAME=emp-test \ | |||
| ENV_FILE=/home/admin-x99/emp-test/runtime/.env \ | |||
| COMPOSE_FILE=/home/admin-x99/emp-test/runtime/docker-compose.yml \ | |||
| bash apply-update.sh | |||
| ``` | |||
| 也可以手动指定更新服务,覆盖包内 `services.txt`: | |||
| ```bash | |||
| PROJECT_NAME=emp-test \ | |||
| ENV_FILE=/home/admin-x99/emp-test/runtime/.env \ | |||
| COMPOSE_FILE=/home/admin-x99/emp-test/runtime/docker-compose.yml \ | |||
| bash apply-update.sh emp-admin emp-monitor | |||
| ``` | |||
| ## 五、验证 | |||
| 查看服务状态: | |||
| ```bash | |||
| cd /home/admin-x99/emp-test/runtime | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test ps emp-admin emp-monitor | |||
| ``` | |||
| 查看后端日志: | |||
| ```bash | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test logs --tail=100 emp-monitor | |||
| ``` | |||
| 验证前端: | |||
| ```bash | |||
| curl -I http://127.0.0.1:37361 | |||
| ``` | |||
| ## 六、注意事项 | |||
| 1. 增量更新默认同时打 `latest` 和时间戳 tag。 | |||
| 2. 当前测试环境 `.env` 建议继续使用 `IMAGE_TAG=latest`,不要为了增量包修改成时间戳。 | |||
| 3. 如果把 `.env` 的 `IMAGE_TAG` 改成某个新时间戳,但只传了部分服务镜像,其他服务会因为缺少该 tag 而无法重建。 | |||
| 4. 如果修改了 `docker-compose.yml`、`.env.example`、中间件初始化逻辑,建议重新全量打包或单独同步配置文件。 | |||
| 5. 数据库结构变更不包含在镜像增量包中,需要单独执行 SQL 迁移。 | |||
| ## 七、PDF 导出故障排查 | |||
| 报错: | |||
| ```text | |||
| PDF导出失败: I/O error on GET request for "http://emp-pdf:3100/pdf": Connection refused | |||
| ``` | |||
| 含义:`emp-monitor` 已经访问到 Docker 内网地址 `emp-pdf:3100`,但 PDF 服务端口没有进程监听,常见原因是 `emp-pdf` 容器未启动、启动后退出、正在重启,或 Node 服务未正常监听 3100。 | |||
| 新版 `docker-compose.yml` 已给 `emp-pdf` 增加 `/health` 健康检查,并让 `emp-monitor` 等待 `emp-pdf` 健康后再启动。若服务器仍使用旧 compose,需要先同步新的 `docker-compose.yml` 或按下面命令手动重启 PDF 服务。 | |||
| 先看容器状态: | |||
| ```bash | |||
| cd /home/admin-x99/emp-test/runtime | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test ps emp-pdf emp-monitor | |||
| ``` | |||
| 查看 PDF 服务日志: | |||
| ```bash | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test logs --tail=200 emp-pdf | |||
| ``` | |||
| 在 PDF 容器内检查健康接口: | |||
| ```bash | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test exec emp-pdf \ | |||
| node -e "require('http').get('http://127.0.0.1:3100/health', r => { console.log(r.statusCode); r.pipe(process.stdout) }).on('error', e => { console.error(e.message); process.exit(1) })" | |||
| ``` | |||
| 在同一个 Docker 网络里检查 `emp-monitor` 到 `emp-pdf` 的访问: | |||
| ```bash | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test exec emp-monitor \ | |||
| sh -lc "curl -sS http://emp-pdf:3100/health || wget -qO- http://emp-pdf:3100/health" | |||
| ``` | |||
| 如果 `emp-pdf` 未运行或健康检查失败,先重启 PDF 服务: | |||
| ```bash | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test up -d --no-deps --force-recreate emp-pdf | |||
| ``` | |||
| 再重启 monitor,使其重新调用可用的 PDF 服务: | |||
| ```bash | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test restart emp-monitor | |||
| ``` | |||
| 如果日志中出现 Chromium/Puppeteer 相关错误,重新构建并增量更新 `pdf`: | |||
| ```bash | |||
| EMP_ROOT=/home/git/emp IMAGE_NAMESPACE=emp-test ./build-update.sh pdf | |||
| ``` | |||