Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

287 строки
8.1 KiB

  1. #!/usr/bin/env bash
  2. set -Eeuo pipefail
  3. # 在 shunfeng 单个节点上执行镜像快照、服务更新、健康检查或回滚。
  4. # 本脚本由 apply-update.sh 在主机本地调用,并临时复制到从机调用。
  5. ACTION="${1:-}"
  6. DEPLOY_ROLE="${2:-}"
  7. RUN_ID="${3:-}"
  8. IMAGE_TAG="${4:-}"
  9. shift $(( $# >= 4 ? 4 : $# ))
  10. REQUESTED_SERVICES=("$@")
  11. DEPLOY_HOME="${DEPLOY_HOME:-/home/emp-shunfeng}"
  12. RUNTIME_DIR="${RUNTIME_DIR:-$DEPLOY_HOME/runtime}"
  13. RELEASE_ENV_FILE="${RELEASE_ENV_FILE:-$RUNTIME_DIR/release.env}"
  14. log() {
  15. echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$DEPLOY_ROLE] $*"
  16. }
  17. die() {
  18. echo "ERROR: [$DEPLOY_ROLE] $*" >&2
  19. exit 1
  20. }
  21. need_cmd() {
  22. command -v "$1" >/dev/null 2>&1 || die "missing command: $1"
  23. }
  24. normalize_service() {
  25. case "$1" in
  26. admin|emp-admin) echo "admin" ;;
  27. gateway|emp-gateway) echo "gateway" ;;
  28. auth|emp-auth) echo "auth" ;;
  29. monitor|emp-monitor) echo "monitor" ;;
  30. data|emp-data) echo "data" ;;
  31. *) die "unsupported service: $1" ;;
  32. esac
  33. }
  34. service_allowed_on_role() {
  35. local service="$1"
  36. if [[ "$DEPLOY_ROLE" == "master" ]]; then
  37. return 0
  38. fi
  39. [[ "$DEPLOY_ROLE" == "sub" && "$service" != "data" ]]
  40. }
  41. image_name() {
  42. echo "emp-$1"
  43. }
  44. container_name() {
  45. echo "emp-$1"
  46. }
  47. if [[ -f "$RELEASE_ENV_FILE" ]]; then
  48. set -a
  49. # shellcheck disable=SC1090
  50. . "$RELEASE_ENV_FILE"
  51. set +a
  52. fi
  53. BACKEND_PROJECT_NAME="${BACKEND_PROJECT_NAME:-emp_server}"
  54. BACKEND_COMPOSE_FILE="${BACKEND_COMPOSE_FILE:-$RUNTIME_DIR/docker-compose.yml}"
  55. BACKEND_ENV_FILE="${BACKEND_ENV_FILE:-$RUNTIME_DIR/.env}"
  56. ADMIN_HOST_PORT="${ADMIN_HOST_PORT:-4080}"
  57. ADMIN_CONTAINER_PORT="${ADMIN_CONTAINER_PORT:-80}"
  58. EMP_ADMIN_LOG_DIR="${EMP_ADMIN_LOG_DIR:-/data/emp/logs/emp-admin}"
  59. DOCKER_LOG_MAX_SIZE="${DOCKER_LOG_MAX_SIZE:-200m}"
  60. DOCKER_LOG_MAX_FILE="${DOCKER_LOG_MAX_FILE:-5}"
  61. HEALTH_RETRIES="${HEALTH_RETRIES:-30}"
  62. HEALTH_INTERVAL_SECONDS="${HEALTH_INTERVAL_SECONDS:-2}"
  63. NACOS_CONTAINER_NAME="${NACOS_CONTAINER_NAME:-emp-nacos}"
  64. REQUIRE_EXISTING_CONTAINERS="${REQUIRE_EXISTING_CONTAINERS:-1}"
  65. IMAGE_TAR="${IMAGE_TAR:-}"
  66. [[ "$ACTION" =~ ^(snapshot|restore-tags|deploy|apply|rollback)$ ]] || die "invalid action: $ACTION"
  67. [[ "$DEPLOY_ROLE" == "master" || "$DEPLOY_ROLE" == "sub" ]] || die "invalid role: $DEPLOY_ROLE"
  68. [[ "$RUN_ID" =~ ^[a-zA-Z0-9_.-]+$ ]] || die "invalid run id: $RUN_ID"
  69. [[ "$IMAGE_TAG" =~ ^[a-zA-Z0-9_.-]+$ ]] || die "invalid image tag: $IMAGE_TAG"
  70. [[ "${#REQUESTED_SERVICES[@]}" -gt 0 ]] || die "no services specified"
  71. ACTIVE_SERVICES=()
  72. declare -A SEEN_SERVICES=()
  73. for raw_service in "${REQUESTED_SERVICES[@]}"; do
  74. service="$(normalize_service "$raw_service")"
  75. if service_allowed_on_role "$service" && [[ -z "${SEEN_SERVICES[$service]:-}" ]]; then
  76. ACTIVE_SERVICES+=("$service")
  77. SEEN_SERVICES[$service]=1
  78. fi
  79. done
  80. [[ "${#ACTIVE_SERVICES[@]}" -gt 0 ]] || die "no services apply to role $DEPLOY_ROLE"
  81. need_cmd docker
  82. if docker compose version >/dev/null 2>&1; then
  83. COMPOSE_CMD=(docker compose)
  84. elif command -v docker-compose >/dev/null 2>&1; then
  85. COMPOSE_CMD=(docker-compose)
  86. else
  87. die "missing docker compose"
  88. fi
  89. snapshot_images() {
  90. local service container image image_id rollback_image
  91. for service in "${ACTIVE_SERVICES[@]}"; do
  92. container="$(container_name "$service")"
  93. image="$(image_name "$service")"
  94. if ! docker container inspect "$container" >/dev/null 2>&1; then
  95. if [[ "$REQUIRE_EXISTING_CONTAINERS" == "1" ]]; then
  96. die "existing container is required for rollback: $container"
  97. fi
  98. log "No existing container to snapshot: $container"
  99. continue
  100. fi
  101. image_id="$(docker container inspect --format '{{.Image}}' "$container")"
  102. rollback_image="$image:rollback-$RUN_ID"
  103. docker image tag "$image_id" "$rollback_image"
  104. log "Rollback image saved: $rollback_image"
  105. done
  106. }
  107. restore_latest_tags() {
  108. local service image rollback_image
  109. for service in "${ACTIVE_SERVICES[@]}"; do
  110. image="$(image_name "$service")"
  111. rollback_image="$image:rollback-$RUN_ID"
  112. if docker image inspect "$rollback_image" >/dev/null 2>&1; then
  113. docker image tag "$rollback_image" "$image:latest"
  114. log "Latest tag restored: $image:latest"
  115. fi
  116. done
  117. }
  118. ensure_nacos() {
  119. local needs_backend=0 service
  120. for service in "${ACTIVE_SERVICES[@]}"; do
  121. [[ "$service" == "admin" ]] || needs_backend=1
  122. done
  123. [[ "$needs_backend" == "1" ]] || return 0
  124. if [[ "$(docker container inspect --format '{{.State.Running}}' "$NACOS_CONTAINER_NAME" 2>/dev/null || true)" != "true" ]]; then
  125. die "Nacos container is not running: $NACOS_CONTAINER_NAME"
  126. fi
  127. }
  128. run_admin() {
  129. local tag="$1"
  130. local image="emp-admin:$tag"
  131. docker image inspect "$image" >/dev/null 2>&1 || die "image not found: $image"
  132. docker stop emp-admin >/dev/null 2>&1 || true
  133. docker rm emp-admin >/dev/null 2>&1 || true
  134. docker run -d \
  135. --name emp-admin \
  136. -p "$ADMIN_HOST_PORT:$ADMIN_CONTAINER_PORT" \
  137. -v "$EMP_ADMIN_LOG_DIR:/var/log/nginx" \
  138. --log-opt "max-size=$DOCKER_LOG_MAX_SIZE" \
  139. --log-opt "max-file=$DOCKER_LOG_MAX_FILE" \
  140. --restart unless-stopped \
  141. "$image" >/dev/null
  142. log "Admin started: $image"
  143. }
  144. run_backend_services() {
  145. local tag="$1"
  146. local compose_services=()
  147. local service image
  148. for service in "${ACTIVE_SERVICES[@]}"; do
  149. [[ "$service" == "admin" ]] && continue
  150. image="$(image_name "$service"):$tag"
  151. docker image inspect "$image" >/dev/null 2>&1 || die "image not found: $image"
  152. compose_services+=("emp-$service")
  153. done
  154. [[ "${#compose_services[@]}" -gt 0 ]] || return 0
  155. [[ -f "$BACKEND_COMPOSE_FILE" ]] || die "compose file not found: $BACKEND_COMPOSE_FILE"
  156. [[ -f "$BACKEND_ENV_FILE" ]] || die "env file not found: $BACKEND_ENV_FILE"
  157. IMAGE_TAG="$tag" "${COMPOSE_CMD[@]}" \
  158. --env-file "$BACKEND_ENV_FILE" \
  159. -f "$BACKEND_COMPOSE_FILE" \
  160. -p "$BACKEND_PROJECT_NAME" \
  161. up -d --no-deps --no-build --force-recreate "${compose_services[@]}"
  162. }
  163. http_ok() {
  164. local url="$1"
  165. if command -v curl >/dev/null 2>&1; then
  166. curl -fsS --connect-timeout 2 --max-time 5 "$url" >/dev/null
  167. elif command -v wget >/dev/null 2>&1; then
  168. wget -q -T 5 -O /dev/null "$url"
  169. else
  170. die "curl or wget is required for health checks"
  171. fi
  172. }
  173. health_url() {
  174. case "$1" in
  175. admin) echo "http://127.0.0.1:$ADMIN_HOST_PORT/" ;;
  176. gateway) echo "http://127.0.0.1:9000/api/health" ;;
  177. auth) echo "http://127.0.0.1:9001/auth/health" ;;
  178. monitor) echo "http://127.0.0.1:9002/health" ;;
  179. data) echo "http://127.0.0.1:9003/health" ;;
  180. esac
  181. }
  182. wait_for_service() {
  183. local service="$1"
  184. local container url attempt
  185. container="$(container_name "$service")"
  186. url="$(health_url "$service")"
  187. for ((attempt = 1; attempt <= HEALTH_RETRIES; attempt += 1)); do
  188. if [[ "$(docker container inspect --format '{{.State.Running}}' "$container" 2>/dev/null || true)" == "true" ]] \
  189. && http_ok "$url"; then
  190. log "Health check passed: $service ($url)"
  191. return 0
  192. fi
  193. sleep "$HEALTH_INTERVAL_SECONDS"
  194. done
  195. log "Health check failed: $service ($url)"
  196. docker logs --tail=80 "$container" 2>/dev/null || true
  197. return 1
  198. }
  199. health_check() {
  200. local service
  201. for service in "${ACTIVE_SERVICES[@]}"; do
  202. wait_for_service "$service" || return 1
  203. done
  204. }
  205. deploy_services() {
  206. local tag="$1"
  207. local service
  208. ensure_nacos
  209. for service in "${ACTIVE_SERVICES[@]}"; do
  210. if [[ "$service" == "admin" ]]; then
  211. run_admin "$tag" || return 1
  212. fi
  213. done
  214. run_backend_services "$tag" || return 1
  215. health_check
  216. }
  217. rollback_services() {
  218. log "Rolling back services: ${ACTIVE_SERVICES[*]}"
  219. restore_latest_tags
  220. deploy_services latest
  221. }
  222. case "$ACTION" in
  223. snapshot)
  224. snapshot_images
  225. ;;
  226. restore-tags)
  227. restore_latest_tags
  228. ;;
  229. deploy)
  230. deploy_services "$IMAGE_TAG"
  231. ;;
  232. rollback)
  233. rollback_services
  234. ;;
  235. apply)
  236. [[ -n "$IMAGE_TAR" && -f "$IMAGE_TAR" ]] || die "image tar not found: $IMAGE_TAR"
  237. snapshot_images
  238. log "Load images: $IMAGE_TAR"
  239. docker load -i "$IMAGE_TAR"
  240. if ! deploy_services "$IMAGE_TAG"; then
  241. log "Deployment failed, start rollback"
  242. rollback_services || true
  243. exit 1
  244. fi
  245. if [[ "$DEPLOY_ROLE" == "sub" ]]; then
  246. docker stop emp-data >/dev/null 2>&1 || true
  247. docker rm emp-data >/dev/null 2>&1 || true
  248. fi
  249. ;;
  250. esac