#!/bin/bash # ============================================================ # 0524 平台:按现有约定替换/回滚单个(或多个)服务容器 # 位置:服务器 /mnt/web/0524-replace.sh # # 用法: # bash 0524-replace.sh admin # 用 0524admin:latest 重建 admin # bash 0524-replace.sh fishgame admin # 一次替换多个 # TAG=dual-20261008 bash 0524-replace.sh admin # 回滚 admin 到旧镜像 # # 约定(与线上既有部署保持一致): # - 网络 webapi_backend # - 端口只绑 127.0.0.1 # - 配置挂载 /mnt/web/config -> /app/Config(全服务共用) # - 日志挂载 /mnt/web/logs -> /app/logs # - ASPNETCORE_URLS 按各服务内部端口 # ============================================================ set -u NET=webapi_backend CFG=/mnt/web/config LOGS=/mnt/web/logs UPLOADS=/mnt/web/uploads TAG="${TAG:-latest}" ROLLBACK_TAG="${ROLLBACK_TAG:-dual-20261008}" ALL="gateway admin user customer chess fishroom fishgame" # 容器内部监听端口 internal_port() { case "$1" in gateway) echo 8001 ;; admin) echo 5241 ;; user) echo 5245 ;; customer) echo 5247 ;; chess) echo 5242 ;; fishroom) echo 5244 ;; fishgame) echo 8001 ;; esac } # 宿主机映射端口(只绑 127.0.0.1) host_port() { case "$1" in gateway) echo 8001 ;; admin) echo 5241 ;; user) echo 5245 ;; customer) echo 5247 ;; chess) echo 5242 ;; fishroom) echo 5244 ;; fishgame) echo 5248 ;; esac } # 额外的 -v/-e 参数 extra_args() { case "$1" in gateway) echo "-v $CFG/ocelot.json:/app/ocelot.json -v $UPLOADS:/app/uploads" ;; admin) echo "-v $UPLOADS:/app/wwwroot/uploads" ;; customer) echo "-v $UPLOADS:/app/wwwroot/uploads" ;; *) echo "" ;; esac } # INSTANCE_ID(日志文件名里用于区分实例) instance_id() { case "$1" in gateway) echo "" ;; fishgame) echo "1" ;; *) echo "Container_Port_$(host_port "$1")" ;; esac } run_container() { local svc=$1 tag=$2 local ip hp inst extra ip=$(internal_port "$svc"); hp=$(host_port "$svc"); inst=$(instance_id "$svc"); extra=$(extra_args "$svc") docker rm -f "0524$svc" >/dev/null 2>&1 local envar="ASPNETCORE_URLS=http://+:$ip" [ -n "$inst" ] && envar="$envar -e INSTANCE_ID=$inst" # shellcheck disable=SC2086 docker run -d --name "0524$svc" --restart always --network "$NET" \ -e $envar $extra \ -p "127.0.0.1:$hp:$ip" \ -v "$CFG:/app/Config" -v "$LOGS:/app/logs" -v "$CFG/nlog.config:/app/nlog.config" \ "0524$svc:$tag" >/dev/null } health() { local svc=$1 hp; hp=$(host_port "$svc") for _ in $(seq 1 15); do local c; c=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:$hp/health") if [ "$svc" = "gateway" ]; then # 网关的 /health 被 Ocelot 接管,会返回 404;只要端口有 HTTP 响应即视为存活 [ "$c" != "000" ] && return 0 else [ "$c" = "200" ] && return 0 fi sleep 2 done return 1 } replace_one() { local svc=$1 echo "=== 替换 $svc (0524$svc:$TAG)===" run_container "$svc" "$TAG" if health "$svc"; then echo " ✔ $svc 健康检查通过" else echo " ✘ $svc 未通过,输出日志并回滚到 $ROLLBACK_TAG" docker logs --tail 40 "0524$svc" 2>&1 | tail -20 run_container "$svc" "$ROLLBACK_TAG" health "$svc" && echo " ↩ 已回滚,$svc 恢复正常" || echo " !! 回滚后仍不健康,请人工介入" return 1 fi } if [ $# -eq 0 ]; then echo "用法: bash 0524-replace.sh <服务...> (可选服务: $ALL)" echo " TAG=dual-20261008 bash 0524-replace.sh <服务...> # 回滚" exit 1 fi for svc in "$@"; do case " $ALL " in *" $svc "*) replace_one "$svc" ;; *) echo "未知服务: $svc(可选: $ALL)" ;; esac done echo "" echo "=== 当前业务容器 ===" docker ps --format '{{.Names}} => {{.Image}} => {{.Status}}' | grep 0524