提交
This commit is contained in:
1 parent
80138c6388
commit
693e8a678e
682 files changed
+211375
-5306
No files matched your search
+24
-7
@@ -11,11 +11,12 @@
|
||||
|
||||
| 角色 | 地址 |
|
||||
|------|------|
|
||||
| 入口 | `http://101.35.245.122:10001` |
|
||||
| 入口 | `https://www.chengyantek.cn`(兼容 `http://101.35.245.122:10001`) |
|
||||
| Nginx 静态 | `/root/zkxw/web-admin/dist` |
|
||||
| 后端 | `127.0.0.1:10002`(仅本机) |
|
||||
| 中间件 | 与本机 `backend/.env` 相同(`47.99.164.66`) |
|
||||
| MinIO 浏览器 | `http://47.99.164.66:20004/zkxw`(直连,Nginx 不反代) |
|
||||
| MinIO | 同域 `/minio/zkxw` → `47.99.164.66:20004/zkxw` |
|
||||
| SSL 证书 | 服务器 `/root/zkxw/cert/www.chengyantek.cn.{pem,key}` |
|
||||
|
||||
流量路径:
|
||||
|
||||
@@ -123,10 +124,13 @@ sudo nginx -t && sudo systemctl reload nginx
|
||||
|
||||
Nginx 关键配置摘要:
|
||||
|
||||
- `listen 10001`
|
||||
- `root /root/zkxw/web-admin/dist`
|
||||
- `location ^~ /api/` → `http://127.0.0.1:10002`
|
||||
- **不**反代 MinIO(前端直连 `http://47.99.164.66:20004/zkxw`)
|
||||
- `https://www.chengyantek.cn`(443 SSL)+ `http` 80/apex → 301 到 https://www
|
||||
- 兼容 `http://IP:10001`
|
||||
- `root /root/zkxw/web-admin/dist`;`/api`、`/health` → `127.0.0.1:10002`
|
||||
- `/minio/zkxw/` → `47.99.164.66:20004/zkxw/`(HTTPS 同域,避免混合内容)
|
||||
- 证书:`/root/zkxw/cert/www.chengyantek.cn.{pem,key}`(来源仓库旁证书包,私钥勿提交 git;`*.pem`/`*.key` 已在 `.gitignore`)
|
||||
|
||||
**云安全组须放行 TCP 80、443**(10001 已通不代表 443 已开)。
|
||||
|
||||
静态目录在 `/root/...` 下时,Nginx 工作用户(如 `www-data`)须能穿越 `/root`,否则首页 500 / Permission denied:
|
||||
|
||||
@@ -137,6 +141,16 @@ chmod -R a+rX /root/zkxw/web-admin
|
||||
|
||||
(或将静态目录放到 `/var/www/zkxw` 等非 `/root` 路径。)
|
||||
|
||||
### 4.5 更新 SSL 证书
|
||||
|
||||
```bash
|
||||
# 本机
|
||||
scp 26548929_www.chengyantek.cn_nginx/www.chengyantek.cn.pem \
|
||||
26548929_www.chengyantek.cn_nginx/www.chengyantek.cn.key \
|
||||
root@101.35.245.122:/root/zkxw/cert/
|
||||
ssh root@101.35.245.122 'chmod 600 /root/zkxw/cert/* && nginx -t && systemctl reload nginx'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 脚本与环境对照
|
||||
@@ -167,10 +181,12 @@ python3 测试环境部署/deploy_test_env.py --backend-only # 仅后端并
|
||||
python3 测试环境部署/deploy_test_env.py --verify # 仅验收
|
||||
python3 测试环境部署/deploy_test_env.py --upload-env # 额外上传本机 backend/.env → 远端 .env.test
|
||||
python3 测试环境部署/deploy_test_env.py --pip # 远端重新 pip install -r requirements.txt
|
||||
python3 测试环境部署/deploy_test_env.py --health-timeout 90 # 重启后等待健康检查最长时间(默认 60s)
|
||||
```
|
||||
|
||||
依赖本机:`python3`、`pnpm`、`rsync`、`ssh`/`scp`;密码登录需 `sshpass`。
|
||||
SSH 密码默认读仓库根 `测试环境机器的账号密码`,或环境变量 `ZKXW_TEST_SSH_PASS`(**勿提交密码到 git**)。
|
||||
SSH 密码默认读仓库根 `测试环境机器的账号密码`,或环境变量 `ZKXW_TEST_SSH_PASS`(**勿提交密码到 git**)。
|
||||
发版若重启了后端,会先轮询 `127.0.0.1:10002/health`(再确认 Nginx `/health`)通过后再跑验收,避免瞬时 502。
|
||||
|
||||
### 6.2 手工命令(等价)
|
||||
|
||||
@@ -282,3 +298,4 @@ python3.12 -m venv .venv
|
||||
| 2026-06-01 | 补充:`web-admin` 测试打包(`pnpm build:test`)、Nginx 8112、`/root/zkxw/dist`、API 反代 `47.99.164.66:8112` |
|
||||
| 2026-08-06 | 裸机部署 10001/10002 于 101.35.245.122 |
|
||||
| 2026-08-06 | 新增 `deploy_test_env.py` 日常发版脚本 |
|
||||
| 2026-08-10 | 启用 `www.chengyantek.cn` HTTPS(443)+ HTTP→HTTPS;MinIO 同域 `/minio/zkxw` |
|
||||
@@ -26,6 +26,7 @@ import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Sequence
|
||||
|
||||
@@ -40,6 +41,8 @@ REMOTE_DIST = f"{REMOTE_ROOT}/web-admin/dist"
|
||||
BACKEND_HOST = "127.0.0.1"
|
||||
BACKEND_PORT = "10002"
|
||||
PUBLIC_HTTP_PORT = "10001"
|
||||
DEFAULT_HEALTH_TIMEOUT_SEC = 60
|
||||
DEFAULT_HEALTH_INTERVAL_SEC = 2
|
||||
|
||||
BACKEND_EXCLUDES = (
|
||||
".venv",
|
||||
@@ -253,6 +256,82 @@ def restart_backend(args: argparse.Namespace, password: Optional[str]) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _health_ok_via_ssh(args: argparse.Namespace, password: Optional[str]) -> bool:
|
||||
"""本机回环健康检查(不依赖公网 / Nginx)。"""
|
||||
remote = (
|
||||
f"curl -sf --connect-timeout 2 --max-time 5 "
|
||||
f"http://{BACKEND_HOST}:{BACKEND_PORT}/health"
|
||||
)
|
||||
cmd = [*ssh_argv(password), remote_target(args), remote]
|
||||
cp = subprocess.run(
|
||||
cmd,
|
||||
env=with_sshpass_env(password),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return cp.returncode == 0 and "healthy" in (cp.stdout or "")
|
||||
|
||||
|
||||
def _health_ok_via_nginx(args: argparse.Namespace) -> bool:
|
||||
"""经 Nginx 公开入口检查(与验收路径一致)。"""
|
||||
url = f"http://{args.host}:{PUBLIC_HTTP_PORT}/health"
|
||||
cp = subprocess.run(
|
||||
["curl", "-sf", "--connect-timeout", "3", "--max-time", "8", url],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return cp.returncode == 0 and "healthy" in (cp.stdout or "")
|
||||
|
||||
|
||||
def wait_for_health(
|
||||
args: argparse.Namespace,
|
||||
password: Optional[str],
|
||||
*,
|
||||
prefer_ssh: bool,
|
||||
) -> None:
|
||||
"""
|
||||
重启后轮询健康检查,成功后再验收。
|
||||
prefer_ssh=True:优先 SSH 打 127.0.0.1:10002(刚重启时更准)。
|
||||
否则优先 Nginx :10001/health。
|
||||
"""
|
||||
require_bin("curl")
|
||||
timeout = max(1, int(args.health_timeout))
|
||||
interval = max(0.5, float(args.health_interval))
|
||||
deadline = time.monotonic() + timeout
|
||||
attempt = 0
|
||||
info(f"等待后端健康(最多 {timeout}s,间隔 {interval}s)…")
|
||||
while True:
|
||||
attempt += 1
|
||||
ssh_ok = _health_ok_via_ssh(args, password)
|
||||
nginx_ok = _health_ok_via_nginx(args)
|
||||
ready = ssh_ok if prefer_ssh else (nginx_ok or ssh_ok)
|
||||
detail = f"ssh={'ok' if ssh_ok else 'fail'} nginx={'ok' if nginx_ok else 'fail'}"
|
||||
|
||||
if ready:
|
||||
info(f"健康检查通过(第 {attempt} 次,{detail})")
|
||||
# 本机已通但 Nginx 仍 502 时,再短等公开入口
|
||||
if prefer_ssh and not nginx_ok:
|
||||
info("本机 health 已通,等待 Nginx /health …")
|
||||
nginx_deadline = time.monotonic() + min(15.0, float(timeout))
|
||||
while time.monotonic() < nginx_deadline:
|
||||
if _health_ok_via_nginx(args):
|
||||
info("Nginx /health 已就绪")
|
||||
break
|
||||
time.sleep(interval)
|
||||
else:
|
||||
info("Nginx /health 仍未就绪,将继续验收(可能失败)")
|
||||
return
|
||||
|
||||
if time.monotonic() >= deadline:
|
||||
die(
|
||||
f"等待健康检查超时({timeout}s,共 {attempt} 次,{detail})。"
|
||||
f"请 SSH 查看: cd {REMOTE_BACKEND} && ./start-test.sh logs",
|
||||
code=3,
|
||||
)
|
||||
print(f"[WAIT] 未就绪(第 {attempt} 次,{detail}),{interval}s 后重试…")
|
||||
time.sleep(interval)
|
||||
|
||||
|
||||
def verify(args: argparse.Namespace) -> None:
|
||||
require_bin("curl")
|
||||
base = f"http://{args.host}:{PUBLIC_HTTP_PORT}"
|
||||
@@ -281,7 +360,7 @@ def verify(args: argparse.Namespace) -> None:
|
||||
)
|
||||
body = (cp.stdout or "").strip()
|
||||
print(f"[VERIFY] {name}: {body[:120]} ({url})")
|
||||
if "healthy" not in body and cp.returncode != 0:
|
||||
if "healthy" not in body:
|
||||
ok = False
|
||||
|
||||
# 10002 应对外不可达
|
||||
@@ -333,6 +412,18 @@ def parse_args(argv: Optional[Sequence[str]] = None) -> argparse.Namespace:
|
||||
p.add_argument("--upload-env", action="store_true", help="上传本机 backend/.env 为远端 .env.test")
|
||||
p.add_argument("--pip", action="store_true", help="远端执行 pip install -r requirements.txt")
|
||||
p.add_argument("--no-verify", action="store_true", help="发版后不自动验收")
|
||||
p.add_argument(
|
||||
"--health-timeout",
|
||||
type=int,
|
||||
default=DEFAULT_HEALTH_TIMEOUT_SEC,
|
||||
help=f"重启后等待健康检查的最长时间(秒,默认 {DEFAULT_HEALTH_TIMEOUT_SEC})",
|
||||
)
|
||||
p.add_argument(
|
||||
"--health-interval",
|
||||
type=float,
|
||||
default=DEFAULT_HEALTH_INTERVAL_SEC,
|
||||
help=f"健康检查轮询间隔(秒,默认 {DEFAULT_HEALTH_INTERVAL_SEC})",
|
||||
)
|
||||
return p.parse_args(argv)
|
||||
|
||||
|
||||
@@ -354,6 +445,7 @@ def main(argv: Optional[Sequence[str]] = None) -> None:
|
||||
|
||||
do_fe = not args.backend_only
|
||||
do_be = not args.frontend_only
|
||||
did_restart = False
|
||||
|
||||
if do_fe:
|
||||
if not args.skip_build:
|
||||
@@ -375,10 +467,16 @@ def main(argv: Optional[Sequence[str]] = None) -> None:
|
||||
if not args.skip_restart:
|
||||
info("重启后端 …")
|
||||
restart_backend(args, password)
|
||||
did_restart = True
|
||||
else:
|
||||
info("已跳过后端重启")
|
||||
|
||||
if not args.no_verify:
|
||||
if did_restart:
|
||||
wait_for_health(args, password, prefer_ssh=True)
|
||||
else:
|
||||
# 仅前端发版:仍确认当前后端/Nginx 可用再验收
|
||||
wait_for_health(args, password, prefer_ssh=False)
|
||||
info("发版后验收 …")
|
||||
verify(args)
|
||||
|
||||
|
||||
+132
-5
@@ -1,10 +1,122 @@
|
||||
# ZKXW 测试环境(裸机)— 部署到 101.35.245.122
|
||||
# 用法(按发行版二选一):
|
||||
# Ubuntu/Debian: 复制到 /etc/nginx/sites-available/zkxw-test-10001.conf
|
||||
# ln -sf ... /etc/nginx/sites-enabled/
|
||||
# RHEL/CentOS: 复制到 /etc/nginx/conf.d/zkxw-test-10001.conf
|
||||
# 支持:
|
||||
# - https://www.chengyantek.cn (443,SSL)
|
||||
# - http://www.chengyantek.cn / http://chengyantek.cn → 301 到 https://www
|
||||
# - http://IP:10001 (兼容旧入口,无强制跳转)
|
||||
#
|
||||
# 证书部署路径(服务器):
|
||||
# /root/zkxw/cert/www.chengyantek.cn.pem
|
||||
# /root/zkxw/cert/www.chengyantek.cn.key
|
||||
#
|
||||
# 用法(Ubuntu/Debian):
|
||||
# cp 到 /etc/nginx/sites-available/zkxw-test-10001.conf
|
||||
# ln -sfn ... /etc/nginx/sites-enabled/
|
||||
# nginx -t && systemctl reload nginx
|
||||
|
||||
# ---------- HTTP → HTTPS(域名)----------
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name www.chengyantek.cn chengyantek.cn;
|
||||
return 301 https://www.chengyantek.cn$request_uri;
|
||||
}
|
||||
|
||||
# ---------- apex HTTPS → www ----------
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name chengyantek.cn;
|
||||
|
||||
ssl_certificate /root/zkxw/cert/www.chengyantek.cn.pem;
|
||||
ssl_certificate_key /root/zkxw/cert/www.chengyantek.cn.key;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 1h;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
return 301 https://www.chengyantek.cn$request_uri;
|
||||
}
|
||||
|
||||
# ---------- 主站 HTTPS ----------
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name www.chengyantek.cn;
|
||||
|
||||
ssl_certificate /root/zkxw/cert/www.chengyantek.cn.pem;
|
||||
ssl_certificate_key /root/zkxw/cert/www.chengyantek.cn.key;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 1h;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
root /root/zkxw/web-admin/dist;
|
||||
index index.html;
|
||||
client_max_body_size 100M;
|
||||
|
||||
access_log /var/log/nginx/zkxw_admin_https_access.log;
|
||||
error_log /var/log/nginx/zkxw_admin_https_error.log;
|
||||
|
||||
location = /health {
|
||||
proxy_pass http://127.0.0.1:10002/health;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_read_timeout 30s;
|
||||
}
|
||||
|
||||
location ^~ /docs {
|
||||
proxy_pass http://127.0.0.1:10002;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location = /openapi.json {
|
||||
proxy_pass http://127.0.0.1:10002/openapi.json;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location ^~ /api/ {
|
||||
proxy_pass http://127.0.0.1:10002;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_send_timeout 120s;
|
||||
proxy_read_timeout 120s;
|
||||
}
|
||||
|
||||
# MinIO 同域反代(避免 HTTPS 页面对 http://47.99.164.66:20004 混合内容)
|
||||
location ^~ /minio/zkxw/ {
|
||||
proxy_pass http://47.99.164.66:20004/zkxw/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_request_buffering off;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_send_timeout 600s;
|
||||
proxy_connect_timeout 10s;
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
# ---------- 兼容旧入口:IP:10001(HTTP)----------
|
||||
server {
|
||||
listen 10001;
|
||||
listen [::]:10001;
|
||||
@@ -12,7 +124,6 @@ server {
|
||||
|
||||
root /root/zkxw/web-admin/dist;
|
||||
index index.html;
|
||||
|
||||
client_max_body_size 100M;
|
||||
|
||||
access_log /var/log/nginx/zkxw_admin_10001_access.log;
|
||||
@@ -53,6 +164,22 @@ server {
|
||||
proxy_read_timeout 120s;
|
||||
}
|
||||
|
||||
location ^~ /minio/zkxw/ {
|
||||
proxy_pass http://47.99.164.66:20004/zkxw/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_request_buffering off;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_send_timeout 600s;
|
||||
proxy_connect_timeout 10s;
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user