Docker + Nginx 實(shí)現(xiàn) Java 服務(wù)零停機(jī)發(fā)布并解決發(fā)布502問題
本文為生產(chǎn)環(huán)境真實(shí)踩坑總結(jié),通用適用于:SpringBoot / 單體服務(wù) / Docker Compose + Nginx 架構(gòu),無 Kubernetes 也能實(shí)現(xiàn)低成本高可用發(fā)布。
一、前言
在傳統(tǒng)服務(wù)器部署中,我們更新后端服務(wù)通常是:
- 停止舊容器
- 啟動(dòng)新容器
這個(gè)過程會(huì)直接導(dǎo)致:
- 發(fā)布期間接口 502 Bad Gateway
- 用戶訪問中斷、前端報(bào)錯(cuò)
- 監(jiān)控告警、接口超時(shí)失敗
即使使用 “先啟動(dòng)臨時(shí)實(shí)例、再關(guān)閉舊實(shí)例”,如果 Nginx 配置、健康檢查、腳本邏輯 不正確,依然會(huì)出現(xiàn)各種詭異問題:發(fā)布瞬間 502、腳本卡死、接口不通等。
本文帶你從零搭建一套真正可用、無感知、零停機(jī)的發(fā)布方案
二、遇到的典型問題(你大概率也踩過)
直接重啟容器 → 必現(xiàn) 502
nginx 502 Bad Gateway
原因:主容器重啟期間,Nginx 仍在轉(zhuǎn)發(fā)請(qǐng)求,無可用后端服務(wù)。
加了 backup 副本 → 依然偶爾 502
upstream ring_servers {
server ring:8080;
server ring_temp:8080 backup;
}
原因:
- backup 只有主節(jié)點(diǎn)完全掛掉才會(huì)切換
- Nginx 沒有失敗重試機(jī)制,重啟瞬間仍會(huì)丟請(qǐng)求
- 健康檢查不及時(shí)
發(fā)布腳本檢查接口 → 直接卡死
curl 檢查接口返回 000,腳本不動(dòng)了
原因:
- 接口未就緒時(shí),curl 阻塞等待
- 檢查公網(wǎng)入口受 Nginx 影響,無法真實(shí)判斷服務(wù)狀態(tài)
健康檢查接口被登錄攔截 → 返回 401
原因:健康接口走了登錄校驗(yàn),HTTP 200 但業(yè)務(wù)碼 401,導(dǎo)致腳本誤判。
三、整體零停機(jī)發(fā)布方案(通用架構(gòu))
核心思路:雙實(shí)例兜底 + Nginx 自動(dòng)重試 + 安全發(fā)布腳本
- 正常運(yùn)行:主服務(wù)提供流量
- 發(fā)布時(shí):先啟動(dòng)臨時(shí)副本 → 等待就緒 → 重啟主服務(wù) → 主服務(wù)就緒 → 關(guān)閉臨時(shí)副本
- Nginx:自動(dòng)轉(zhuǎn)發(fā)到可用節(jié)點(diǎn),用戶無感知
四、第一步:Nginx 配置(最關(guān)鍵,解決 502)
- upstream 負(fù)載均衡配置
upstream ring_servers {
least_conn;
# 主服務(wù)
server ring:8080 max_fails=1 fail_timeout=1s;
# 臨時(shí)服務(wù)(發(fā)布兜底,backup 模式)
server ring_temp:8080 max_fails=3 fail_timeout=30s backup;
# 核心:出錯(cuò)自動(dòng)重試下一個(gè)節(jié)點(diǎn),用戶看不到 502
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_next_upstream_tries 2;
keepalive 32;
}
- 代理通用配置
proxy_connect_timeout 10s; proxy_send_timeout 10s; proxy_read_timeout 30s; proxy_buffering on;
說明:proxy_next_upstream 是零停機(jī)的靈魂,請(qǐng)求失敗會(huì)自動(dòng)重試下一個(gè)節(jié)點(diǎn)。
五、第二步:docker-compose.yml 配置
只保留核心結(jié)構(gòu),可直接套用:
version: "3.9"
services:
# 主服務(wù)
ring:
image: ring:1.0.0
restart: always
ports:
- "8080:8080"
networks:
- webnet
healthcheck:
test: ["CMD", "curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "http://localhost:8080/api/health"]
interval: 5s
timeout: 3s
retries: 10
start_period: 60s
# 臨時(shí)服務(wù)(發(fā)布用)
ring_temp:
image: ring:1.0.0
restart: "no" # 不自動(dòng)重啟
ports:
- "8081:8080"
networks:
- webnet
networks:
webnet:
driver: bridge六、第三步:生產(chǎn)可用零停機(jī)發(fā)布腳本
重要說明
腳本中使用的接口:
/api/app/checkServerStatus
這只是一個(gè)樣例接口,你可以替換為自己項(xiàng)目中能判斷項(xiàng)目正常運(yùn)行的任意接口,例如:
- /actuator/health
- /api/health
- /system/health
只要該接口能在服務(wù)啟動(dòng)完成后返回 HTTP 200 即可。
零停機(jī)發(fā)布腳本(最終版)
#!/bin/bash
set +e
# ==================== 配置項(xiàng)(根據(jù)自己項(xiàng)目修改) ====================
# 公網(wǎng)要監(jiān)控的接口(驗(yàn)證用戶是否能正常訪問)
MONITOR_URL="https://你的域名/api/app/checkServerStatus"
# 主服務(wù)端口檢查(不經(jīng)過 Nginx)
MAIN_CHECK_URL="http://127.0.0.1:8080/api/app/checkServerStatus"
# 臨時(shí)服務(wù)端口檢查
TEMP_CHECK_URL="http://127.0.0.1:8081/api/app/checkServerStatus"
MAX_RETRY=30
RETRY_INTERVAL=5
# ====================================================================
# 后臺(tái)實(shí)時(shí)監(jiān)控公網(wǎng)接口
start_monitor() {
while true; do
current_time=$(date "+%Y-%m-%d %H:%M:%S")
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --insecure $MONITOR_URL 2>/dev/null)
if [ "$HTTP_CODE" = "200" ]; then
echo -e "\033[32m[${current_time}] ? 公網(wǎng)接口正常:200\033[0m"
else
echo -e "\033[31m[${current_time}] ? 公網(wǎng)接口異常:${HTTP_CODE}\033[0m"
fi
sleep 1
done
}
# 檢查端口服務(wù)是否就緒
check_service_ready() {
local url=$1
local desc=$2
echo -e "\033[36m=== 等待 ${desc} 就緒 ===\033[0m"
local retry=0
while [ $retry -lt $MAX_RETRY ]; do
code=$(curl -s -o /dev/null -w "%{http_code}" --insecure $url 2>/dev/null)
if [ "$code" = "200" ]; then
echo -e "\033[32m? ${desc} 啟動(dòng)成功\033[0m"
return 0
fi
retry=$((retry+1))
echo "重試 ${retry}/${MAX_RETRY},狀態(tài)碼:$[code],等待 ${RETRY_INTERVAL}s..."
sleep $RETRY_INTERVAL
done
echo -e "\033[31m? ${desc} 啟動(dòng)超時(shí)\033[0m"
kill $MONITOR_PID 2>/dev/null
exit 1
}
# ==================== 發(fā)布主流程 ====================
echo -e "\n==================== 開始零停機(jī)發(fā)布 ====================\n"
# 1. 啟動(dòng)后臺(tái)監(jiān)控
start_monitor &
MONITOR_PID=$!
# 2. 啟動(dòng)臨時(shí)副本
echo -e "\n1. 啟動(dòng)臨時(shí)服務(wù)"
docker compose up -d ring_temp
check_service_ready "$TEMP_CHECK_URL" "臨時(shí)服務(wù)"
# 3. 重啟主服務(wù)
echo -e "\n2. 重啟主服務(wù)"
docker compose up -d --force-recreate ring
check_service_ready "$MAIN_CHECK_URL" "主服務(wù)"
# 4. 關(guān)閉臨時(shí)服務(wù)
echo -e "\n3. 關(guān)閉臨時(shí)服務(wù)"
docker compose stop ring_temp
docker compose rm -f ring_temp
# 5. 停止監(jiān)控
kill $MONITOR_PID 2>/dev/null
sleep 1
echo -e "\n\033[32m=====================================================\033[0m"
echo -e "\033[32m? 發(fā)布完成!全程無 502,用戶無感知\033[0m"
echo -e "\033[32m=====================================================\033[0m"
exit 0七、第四步:使用與驗(yàn)證
- 賦予執(zhí)行權(quán)限
chmod +x deploy_ring.sh
- 執(zhí)行發(fā)布
./deploy_ring.sh
- 觀察效果
你會(huì)看到:
公網(wǎng)接口全程輸出 ? 200
主服務(wù)重啟期間無任何 502
發(fā)布完成后自動(dòng)關(guān)閉臨時(shí)副本
用戶完全無感知
八、核心避坑總結(jié)(非常重要)
- 必須先啟動(dòng)臨時(shí)副本,再重啟主服務(wù)
- Nginx 必須配置 proxy_next_upstream,這是無 502 的關(guān)鍵
- 健康檢查不要檢查公網(wǎng)入口,直接檢查容器端口更穩(wěn)定
- 臨時(shí)服務(wù)使用 backup 模式,只做兜底不做負(fù)載,避免啟動(dòng)期間被流量打掛
- 健康接口盡量免登錄,只返回 HTTP 200 即可,不要做業(yè)務(wù)校驗(yàn)
- 腳本中 /api/app/checkServerStatus只是示例,替換成你自己的健康檢查接口
九、適用場(chǎng)景
- SpringBoot 后端服務(wù)
- 單體應(yīng)用 / 無微服務(wù)架構(gòu)
- Docker Compose 部署
- 中小企業(yè)服務(wù)器、無 K8s 環(huán)境
- 追求低成本、穩(wěn)定、無感知發(fā)布
到此這篇關(guān)于Docker + Nginx 實(shí)現(xiàn) Java 服務(wù)零停機(jī)發(fā)布并解決發(fā)布502問題的文章就介紹到這了,更多相關(guān)Docker Nginx Java 服務(wù)零停機(jī)發(fā)布內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解docker中Dockerfile指令創(chuàng)建鏡像
這篇文章主要介紹了詳解docker中Dockerfile指令創(chuàng)建鏡像,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11
Docker下MySQL配置文件不生效的解決方法(超全面!)
在Docker中運(yùn)行MySQL并遇到需要調(diào)整配置的情況時(shí),比如想要關(guān)閉ONLY_FULL_GROUP_BY的嚴(yán)格模式,我們可以通過以下步驟來實(shí)現(xiàn)sql_mode的修改:以下是解決此類問題的步驟和思路,需要的朋友可以參考下2024-09-09
解決docker數(shù)據(jù)文件過大導(dǎo)致根磁盤滿的問題
本篇文章主要介紹了解決docker數(shù)據(jù)文件過大導(dǎo)致根磁盤滿的問題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-04-04
firewalld防火墻開啟后無法啟動(dòng)docker問題及解決
文章描述了在Linux上開啟或重啟防火墻后,創(chuàng)建docker自定義網(wǎng)絡(luò)時(shí)出現(xiàn)的錯(cuò)誤,原因是firewalld和docker在操作iptables時(shí)發(fā)生了沖突,文章提供了兩種解決辦法:1. 重啟Docker服務(wù);2. 讓Docker繞過firewalld2025-12-12
使用docker部署java項(xiàng)目運(yùn)行環(huán)境的實(shí)現(xiàn)步驟
本文主要介紹了使用docker部署java項(xiàng)目運(yùn)行環(huán)境的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01

