Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92a7915de0 | |||
| ca2e044246 |
@@ -1047,52 +1047,6 @@ jobs:
|
||||
needs: build-production-runtime-images
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
shell: sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -eu
|
||||
python3 - <<'PY'
|
||||
import io, os, tarfile, time, urllib.request, urllib.error
|
||||
url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz"
|
||||
request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"})
|
||||
last_err = None
|
||||
for attempt in range(5):
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=120) as response:
|
||||
archive = response.read()
|
||||
break
|
||||
except urllib.error.HTTPError as e:
|
||||
last_err = e
|
||||
if e.code >= 500 and attempt < 4:
|
||||
wait = 2 ** attempt
|
||||
print(f"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...")
|
||||
time.sleep(wait)
|
||||
continue
|
||||
raise
|
||||
except Exception as e:
|
||||
last_err = e
|
||||
if attempt < 4:
|
||||
wait = 2 ** attempt
|
||||
print(f"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...")
|
||||
time.sleep(wait)
|
||||
continue
|
||||
raise
|
||||
else:
|
||||
raise last_err
|
||||
with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar:
|
||||
root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/'
|
||||
for member in tar.getmembers():
|
||||
name = member.name
|
||||
if name == root_prefix[:-1]:
|
||||
continue
|
||||
if name.startswith(root_prefix):
|
||||
member.name = name[len(root_prefix):]
|
||||
if member.name:
|
||||
tar.extract(member, '.')
|
||||
PY
|
||||
|
||||
- name: Install SSH client
|
||||
shell: sh
|
||||
run: |
|
||||
@@ -1127,16 +1081,10 @@ jobs:
|
||||
key_path="$HOME/.ssh/xiaoxia_runtime_builder"
|
||||
echo "Using key: $key_path (home key)"
|
||||
elif [ -n "${PRODUCTION_SSH_KEY:-}" ]; then
|
||||
key_path="$HOME/.ssh/production_deploy_key"
|
||||
key_path="$HOME/.ssh/id_ed25519"
|
||||
printf '%s\n' "$PRODUCTION_SSH_KEY" > "$key_path"
|
||||
chmod 600 "$key_path"
|
||||
echo "Using key from PRODUCTION_SSH_KEY secret"
|
||||
elif [ -f "$HOME/.ssh/id_ed25519" ]; then
|
||||
key_path="$HOME/.ssh/id_ed25519"
|
||||
echo "Using key: $key_path (default id_ed25519)"
|
||||
elif [ -f /root/.ssh/id_ed25519 ]; then
|
||||
key_path="/root/.ssh/id_ed25519"
|
||||
echo "Using key: $key_path (root id_ed25519)"
|
||||
else
|
||||
echo "ERROR: No SSH key available"
|
||||
ls -la ~/.ssh/ 2>/dev/null || true
|
||||
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/bin/bash
|
||||
# 灰度发布脚本:通过Nginx权重调整流量比例
|
||||
# 用法: ./scripts/gray_deploy.sh <版本号> <灰度百分比>
|
||||
#
|
||||
# 需要在目标服务器上执行,或通过SSH执行
|
||||
# 前提:服务器上运行两个版本的容器(stable + canary),Nginx做加权轮询
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
VERSION="${1:-}"
|
||||
GRAY_PCT="${2:-10}"
|
||||
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
echo "用法: $0 <版本号> [灰度百分比]"
|
||||
echo "示例: $0 v0.1.129 5"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STABLE_VERSION="${STABLE_VERSION:-current}"
|
||||
REGISTRY="${REGISTRY:-git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas}"
|
||||
|
||||
echo "=========================================="
|
||||
echo " 灰度发布"
|
||||
echo " 新版本: $VERSION"
|
||||
echo " 灰度比例: ${GRAY_PCT}%"
|
||||
echo " 稳定版本: $STABLE_VERSION"
|
||||
echo "=========================================="
|
||||
|
||||
# 1. 拉取新版本镜像
|
||||
echo ""
|
||||
echo ">>> 拉取新版本镜像..."
|
||||
for component in api worker web; do
|
||||
echo " 拉取 $component:$VERSION ..."
|
||||
docker pull "${REGISTRY}-${component}:${VERSION}" 2>&1 | tail -1
|
||||
done
|
||||
|
||||
# 2. 启动灰度版本容器(canary)
|
||||
echo ""
|
||||
echo ">>> 启动灰度版本容器..."
|
||||
|
||||
# API canary
|
||||
CANARY_API_NAME="saas-api-canary"
|
||||
if docker ps -a --format '{{.Names}}' | grep -q "^${CANARY_API_NAME}$"; then
|
||||
echo " 停止旧 canary 容器..."
|
||||
docker stop "$CANARY_API_NAME" 2>/dev/null || true
|
||||
docker rm "$CANARY_API_NAME" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo " 启动 api canary..."
|
||||
docker run -d \
|
||||
--name "$CANARY_API_NAME" \
|
||||
--network saas-network \
|
||||
-e DATABASE_URL="${DATABASE_URL}" \
|
||||
-e REDIS_URL="${REDIS_URL}" \
|
||||
-e FEATURE_FLAG_PROVIDER=redis \
|
||||
--restart unless-stopped \
|
||||
"${REGISTRY}-api:${VERSION}"
|
||||
|
||||
# Worker canary
|
||||
CANARY_WORKER_NAME="saas-worker-canary"
|
||||
if docker ps -a --format '{{.Names}}' | grep -q "^${CANARY_WORKER_NAME}$"; then
|
||||
echo " 停止旧 worker canary..."
|
||||
docker stop "$CANARY_WORKER_NAME" 2>/dev/null || true
|
||||
docker rm "$CANARY_WORKER_NAME" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo " 启动 worker canary..."
|
||||
docker run -d \
|
||||
--name "$CANARY_WORKER_NAME" \
|
||||
--network saas-network \
|
||||
-e DATABASE_URL="${DATABASE_URL}" \
|
||||
-e REDIS_URL="${REDIS_URL}" \
|
||||
--restart unless-stopped \
|
||||
"${REGISTRY}-worker:${VERSION}"
|
||||
|
||||
# 3. 等待容器健康
|
||||
echo ""
|
||||
echo ">>> 等待容器健康..."
|
||||
sleep 10
|
||||
if ! docker ps --format '{{.Names}} {{.Status}}' | grep -q "$CANARY_API_NAME"; then
|
||||
echo "错误: API canary 容器未运行"
|
||||
docker logs "$CANARY_API_NAME" --tail 20
|
||||
exit 1
|
||||
fi
|
||||
echo " ✅ API canary 运行中"
|
||||
|
||||
# 4. 更新Nginx权重
|
||||
echo ""
|
||||
echo ">>> 更新Nginx权重 (稳定: $((100-GRAY_PCT))% / 灰度: ${GRAY_PCT}%)..."
|
||||
|
||||
NGINX_CONF="${NGINX_CONF:-/etc/nginx/conf.d/saas-api.conf}"
|
||||
if [[ -f "$NGINX_CONF" ]]; then
|
||||
# 备份
|
||||
cp "$NGINX_CONF" "${NGINX_CONF}.bak.$(date +%Y%m%d%H%M%S)"
|
||||
|
||||
# 更新 upstream 权重(需要根据实际配置调整)
|
||||
echo " 请手动更新 Nginx upstream 配置中的权重"
|
||||
echo " 示例配置:"
|
||||
cat <<EOF
|
||||
upstream saas_api_backend {
|
||||
server saas-api:8000 weight=$((100-GRAY_PCT));
|
||||
server saas-api-canary:8000 weight=${GRAY_PCT};
|
||||
}
|
||||
EOF
|
||||
nginx -t && nginx -s reload
|
||||
echo " ✅ Nginx 已reload"
|
||||
else
|
||||
echo " 警告: Nginx 配置文件不存在 ($NGINX_CONF)"
|
||||
echo " 请手动配置灰度流量权重"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " ✅ 灰度发布完成"
|
||||
echo " 新版本: $VERSION (${GRAY_PCT}%流量)"
|
||||
echo " 监控: Grafana / 日志"
|
||||
echo "=========================================="
|
||||
Executable
+126
@@ -0,0 +1,126 @@
|
||||
#!/bin/bash
|
||||
# 一键发布脚本:打tag → 触发生产镜像构建 → 部署到灰度
|
||||
# 用法: ./scripts/release.sh v0.1.129 [--gray 5]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
usage() {
|
||||
echo "用法: $0 <版本号> [--gray 百分比] [--no-deploy]"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 v0.1.129 # 打tag + 全量发布"
|
||||
echo " $0 v0.1.129 --gray 5 # 打tag + 5%灰度发布"
|
||||
echo " $0 v0.1.129 --no-deploy # 只打tag,不部署"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 参数解析
|
||||
VERSION=""
|
||||
GRAY_PCT=0
|
||||
DEPLOY=true
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--gray)
|
||||
GRAY_PCT="$2"
|
||||
shift 2
|
||||
;;
|
||||
--no-deploy)
|
||||
DEPLOY=false
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
;;
|
||||
v*)
|
||||
VERSION="$1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $1"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
echo "错误: 请指定版本号(如 v0.1.129)"
|
||||
usage
|
||||
fi
|
||||
|
||||
echo "=========================================="
|
||||
echo " 发布版本: $VERSION"
|
||||
echo " 灰度比例: ${GRAY_PCT}%"
|
||||
echo " 自动部署: $DEPLOY"
|
||||
echo "=========================================="
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
# 1. 确认在 develop 分支
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
if [[ "$CURRENT_BRANCH" != "develop" ]]; then
|
||||
echo "错误: 请切换到 develop 分支后再发布"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. 拉取最新代码
|
||||
echo ""
|
||||
echo ">>> 拉取最新代码..."
|
||||
git pull origin develop
|
||||
|
||||
# 3. 生成 CHANGELOG
|
||||
echo ""
|
||||
echo ">>> 生成 CHANGELOG..."
|
||||
if [[ -f "scripts/generate_changelog.py" ]]; then
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
||||
if [[ -n "$PREV_TAG" ]]; then
|
||||
python3 scripts/generate_changelog.py \
|
||||
--from-tag "$PREV_TAG" \
|
||||
--to-tag HEAD \
|
||||
--gitea-token "${GITEA_TOKEN:-}" \
|
||||
--output /tmp/changelog_$$.md
|
||||
echo "CHANGELOG 已生成到 /tmp/changelog_$$.md"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4. 打tag
|
||||
echo ""
|
||||
echo ">>> 打 tag $VERSION ..."
|
||||
if git rev-parse "$VERSION" >/dev/null 2>&1; then
|
||||
echo "警告: tag $VERSION 已存在,跳过打tag"
|
||||
else
|
||||
git tag -a "$VERSION" -m "Release $VERSION"
|
||||
git push origin "$VERSION"
|
||||
echo "Tag $VERSION 已推送,触发生产镜像构建..."
|
||||
fi
|
||||
|
||||
# 5. 等待镜像构建
|
||||
if [[ "$DEPLOY" == "true" ]]; then
|
||||
echo ""
|
||||
echo ">>> 等待镜像构建完成(约10-15分钟)..."
|
||||
echo " 镜像: git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas-{api,worker,web}:$VERSION"
|
||||
|
||||
# 这里可以加镜像存在性检查
|
||||
echo " (镜像构建由CI自动完成,请在Gitea Actions中确认)"
|
||||
fi
|
||||
|
||||
# 6. 灰度部署
|
||||
if [[ "$DEPLOY" == "true" && "$GRAY_PCT" -gt 0 ]]; then
|
||||
echo ""
|
||||
echo ">>> 灰度部署: ${GRAY_PCT}% 流量到 $VERSION"
|
||||
if [[ -f "scripts/gray_deploy.sh" ]]; then
|
||||
./scripts/gray_deploy.sh "$VERSION" "$GRAY_PCT"
|
||||
else
|
||||
echo "警告: gray_deploy.sh 不存在,跳过灰度部署"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " ✅ 发布流程完成"
|
||||
echo " 版本: $VERSION"
|
||||
echo " 灰度: ${GRAY_PCT}%"
|
||||
echo "=========================================="
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
# 灰度回滚脚本:切回稳定版本流量
|
||||
# 用法: ./scripts/rollback.sh [稳定版本号]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
STABLE_VERSION="${1:-current}"
|
||||
|
||||
echo "=========================================="
|
||||
echo " 灰度回滚"
|
||||
echo " 切回稳定版本: $STABLE_VERSION"
|
||||
echo "=========================================="
|
||||
|
||||
# 1. 恢复Nginx全量到稳定版本
|
||||
echo ""
|
||||
echo ">>> 恢复Nginx全量流量到稳定版本..."
|
||||
|
||||
NGINX_CONF="${NGINX_CONF:-/etc/nginx/conf.d/saas-api.conf}"
|
||||
if [[ -f "$NGINX_CONF" ]]; then
|
||||
# 找最近的备份
|
||||
LATEST_BAK=$(ls -t "${NGINX_CONF}".bak.* 2>/dev/null | head -1)
|
||||
if [[ -n "$LATEST_BAK" ]]; then
|
||||
cp "$LATEST_BAK" "$NGINX_CONF"
|
||||
echo " 从备份恢复: $LATEST_BAK"
|
||||
else
|
||||
echo " 未找到备份,请手动移除 canary upstream"
|
||||
fi
|
||||
|
||||
nginx -t && nginx -s reload
|
||||
echo " ✅ Nginx 已回滚"
|
||||
fi
|
||||
|
||||
# 2. 停止灰度版本容器(保留30分钟以便排查)
|
||||
echo ""
|
||||
echo ">>> 灰度版本容器将在30分钟后停止(便于排查问题)"
|
||||
echo " 立即停止请执行: docker stop saas-api-canary saas-worker-canary"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " ✅ 回滚完成"
|
||||
echo " 流量已全部切回稳定版本"
|
||||
echo "=========================================="
|
||||
|
||||
Reference in New Issue
Block a user