| @@ -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: | tdengine: | ||||
| condition: service_healthy | condition: service_healthy | ||||
| emp-pdf: | emp-pdf: | ||||
| condition: service_started | |||||
| condition: service_healthy | |||||
| emp-data: | emp-data: | ||||
| <<: *app-env | <<: *app-env | ||||
| @@ -206,6 +206,11 @@ services: | |||||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-pdf:${IMAGE_TAG:-latest} | image: ${IMAGE_NAMESPACE:-emp-test}/emp-pdf:${IMAGE_TAG:-latest} | ||||
| ports: | ports: | ||||
| - "127.0.0.1:${PDF_HOST_PORT:-3100}:3100" | - "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: | emp-ws: | ||||
| <<: *app-env | <<: *app-env | ||||
| @@ -173,7 +173,7 @@ services: | |||||
| tdengine: | tdengine: | ||||
| condition: service_healthy | condition: service_healthy | ||||
| emp-pdf: | emp-pdf: | ||||
| condition: service_started | |||||
| condition: service_healthy | |||||
| emp-data: | emp-data: | ||||
| <<: *app-env | <<: *app-env | ||||
| @@ -190,6 +190,11 @@ services: | |||||
| emp-pdf: | emp-pdf: | ||||
| <<: *app-env | <<: *app-env | ||||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-pdf:${IMAGE_TAG:-latest} | 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: | emp-ws: | ||||
| <<: *app-env | <<: *app-env | ||||
| @@ -228,4 +233,4 @@ volumes: | |||||
| tdengine_data: | tdengine_data: | ||||
| tdengine_log: | tdengine_log: | ||||
| nacos_data: | 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 | |||||
| ``` | |||||