chore(ci): Disable deploy-prod.yml to prevent duplicate deployments
- Rename to deploy-prod.yml.disabled - Keep only cd.yaml (v2.0) with full AIOPS features - See: feedback_single_deploy_workflow.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
329
.github/workflows/deploy-prod.yml
vendored
329
.github/workflows/deploy-prod.yml
vendored
@@ -1,329 +0,0 @@
|
||||
# =============================================================================
|
||||
# AWOOOI - Production Deployment Pipeline (Phase 8)
|
||||
# =============================================================================
|
||||
# 沿用 WOOO AIOps 穩定架構
|
||||
# Registry: harbor.wooo.work (192.168.0.110)
|
||||
# K8s: 192.168.0.120 (K3s Master)
|
||||
# Runner: 192.168.0.110 (Self-Hosted)
|
||||
#
|
||||
# 鐵律:
|
||||
# - 生產環境嚴禁 Docker Compose,K8s 唯一合法部署
|
||||
# - 110 金庫 Build + Push
|
||||
# - 120 K3s Deploy
|
||||
# =============================================================================
|
||||
|
||||
name: Deploy to Production
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'apps/api/**'
|
||||
- 'apps/web/**'
|
||||
- 'k8s/awoooi-prod/**'
|
||||
- '.github/workflows/deploy-prod.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
deploy_api:
|
||||
description: 'Deploy API'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
deploy_web:
|
||||
description: 'Deploy Web'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
deploy_worker:
|
||||
description: 'Deploy Worker'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
skip_tests:
|
||||
description: 'Skip smoke tests (emergency only)'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
# 沿用 AIOPS 設計: 新 commit 自動取消舊 workflow
|
||||
concurrency:
|
||||
group: deploy-prod-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# Harbor 金庫 (110 主機) - 使用 IP 避免 TLS 證書問題
|
||||
REGISTRY: 192.168.0.110:5000
|
||||
HARBOR_PROJECT: library
|
||||
# K3s 叢集 (120 主機)
|
||||
K8S_NAMESPACE: awoooi-prod
|
||||
KUBECONFIG: /home/wooo/.kube/config-120
|
||||
# 加速配置 (沿用 AIOPS)
|
||||
BASE_REGISTRY: "192.168.0.110:5000/dockerhub-cache"
|
||||
PYPI_INDEX_URL: "http://192.168.0.110:3141/root/pypi/+simple/"
|
||||
PYPI_TRUSTED_HOST: "192.168.0.110"
|
||||
NPM_REGISTRY: "http://192.168.0.110:4873"
|
||||
|
||||
jobs:
|
||||
# ===========================================================================
|
||||
# Stage 1: Build & Push Images (110 金庫)
|
||||
# ===========================================================================
|
||||
build:
|
||||
name: "Build Images"
|
||||
runs-on: [self-hosted, harbor, k8s]
|
||||
outputs:
|
||||
image_tag: ${{ steps.meta.outputs.tag }}
|
||||
api_image: ${{ steps.meta.outputs.api_image }}
|
||||
web_image: ${{ steps.meta.outputs.web_image }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Generate Image Tags
|
||||
id: meta
|
||||
run: |
|
||||
SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7)
|
||||
TAG="${SHORT_SHA}-${{ github.run_id }}"
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "api_image=${{ env.REGISTRY }}/${{ env.HARBOR_PROJECT }}/api:${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "web_image=${{ env.REGISTRY }}/${{ env.HARBOR_PROJECT }}/web:${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "📦 Image Tag: ${TAG}"
|
||||
|
||||
- name: Login to Harbor Registry
|
||||
run: |
|
||||
echo "${{ secrets.HARBOR_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u ${{ secrets.HARBOR_USER }} --password-stdin
|
||||
echo "✅ Harbor 登入成功"
|
||||
|
||||
# ----- Build API Image -----
|
||||
# Phase 6.4i: 必須從 monorepo 根目錄建構 (context: .)
|
||||
# 因為 Dockerfile 需要複製 packages/lewooogo-* 本地套件
|
||||
- name: "Build API Image"
|
||||
if: ${{ github.event_name == 'push' || inputs.deploy_api }}
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
run: |
|
||||
echo "🐳 Building API image..."
|
||||
docker build \
|
||||
--push \
|
||||
-t ${{ steps.meta.outputs.api_image }} \
|
||||
-t ${{ env.REGISTRY }}/${{ env.HARBOR_PROJECT }}/api:latest \
|
||||
--build-arg BASE_REGISTRY=${{ env.BASE_REGISTRY }} \
|
||||
--build-arg PYPI_INDEX_URL=${{ env.PYPI_INDEX_URL }} \
|
||||
--build-arg PYPI_TRUSTED_HOST=${{ env.PYPI_TRUSTED_HOST }} \
|
||||
-f apps/api/Dockerfile \
|
||||
.
|
||||
echo "✅ API Image: ${{ steps.meta.outputs.api_image }}"
|
||||
|
||||
# ----- Build Web Image -----
|
||||
- name: "Build Web Image"
|
||||
if: ${{ github.event_name == 'push' || inputs.deploy_web }}
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
run: |
|
||||
echo "🎨 Building Web image..."
|
||||
docker build \
|
||||
--push \
|
||||
-t ${{ steps.meta.outputs.web_image }} \
|
||||
-t ${{ env.REGISTRY }}/${{ env.HARBOR_PROJECT }}/web:latest \
|
||||
--build-arg BASE_REGISTRY=${{ env.BASE_REGISTRY }} \
|
||||
--build-arg NEXT_PUBLIC_API_URL=https://awoooi.wooo.work \
|
||||
-f apps/web/Dockerfile \
|
||||
.
|
||||
echo "✅ Web Image: ${{ steps.meta.outputs.web_image }}"
|
||||
|
||||
# ===========================================================================
|
||||
# Stage 2: Deploy to K3s (120 主機)
|
||||
# ===========================================================================
|
||||
deploy:
|
||||
name: "Deploy to K3s"
|
||||
needs: build
|
||||
runs-on: [self-hosted, harbor, k8s]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure kubectl
|
||||
run: |
|
||||
export KUBECONFIG=${{ env.KUBECONFIG }}
|
||||
export PATH=$HOME/bin:$PATH
|
||||
echo "🔌 Connecting to K3s cluster..."
|
||||
kubectl cluster-info
|
||||
kubectl get nodes -o wide
|
||||
|
||||
- name: Update Image Tags in Manifests
|
||||
run: |
|
||||
IMAGE_TAG="${{ needs.build.outputs.image_tag }}"
|
||||
echo "🔄 Updating manifests with tag: ${IMAGE_TAG}"
|
||||
sed -i "s|IMAGE_TAG_PLACEHOLDER|${IMAGE_TAG}|g" k8s/awoooi-prod/*.yaml
|
||||
grep -r "image:" k8s/awoooi-prod/ | head -10
|
||||
|
||||
- name: Apply Kubernetes Manifests
|
||||
run: |
|
||||
export KUBECONFIG=${{ env.KUBECONFIG }}
|
||||
export PATH=$HOME/bin:$PATH
|
||||
echo "📦 Applying K8s manifests..."
|
||||
# 排除 kustomization.yaml 與 secrets (Secrets 由手動管理,避免覆蓋)
|
||||
for f in k8s/awoooi-prod/*.yaml; do
|
||||
BASENAME="$(basename "$f")"
|
||||
if [[ "$BASENAME" != "kustomization.yaml" && "$BASENAME" != "03-secrets.yaml" && "$BASENAME" != "03-secrets.example.yaml" ]]; then
|
||||
kubectl apply -f "$f" --namespace=${{ env.K8S_NAMESPACE }}
|
||||
else
|
||||
echo "⏭️ Skipped: $BASENAME (managed separately)"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Rollout Restart Deployments
|
||||
run: |
|
||||
export KUBECONFIG=${{ env.KUBECONFIG }}
|
||||
export PATH=$HOME/bin:$PATH
|
||||
echo "🔄 Triggering rolling restart..."
|
||||
kubectl rollout restart deployment/awoooi-api -n ${{ env.K8S_NAMESPACE }} || true
|
||||
kubectl rollout restart deployment/awoooi-web -n ${{ env.K8S_NAMESPACE }} || true
|
||||
# Worker 暫停,不重啟
|
||||
echo "ℹ️ Worker restart skipped"
|
||||
|
||||
- name: Wait for Rollout
|
||||
run: |
|
||||
export KUBECONFIG=${{ env.KUBECONFIG }}
|
||||
export PATH=$HOME/bin:$PATH
|
||||
echo "⏳ Waiting for rollout..."
|
||||
kubectl rollout status deployment/awoooi-api -n ${{ env.K8S_NAMESPACE }} --timeout=300s
|
||||
kubectl rollout status deployment/awoooi-web -n ${{ env.K8S_NAMESPACE }} --timeout=300s
|
||||
# Worker 暫停 (replicas=0),Phase 6.5 完善後啟用
|
||||
echo "ℹ️ Worker deployment skipped (replicas=0)"
|
||||
|
||||
- name: Verify Deployment
|
||||
run: |
|
||||
export KUBECONFIG=${{ env.KUBECONFIG }}
|
||||
export PATH=$HOME/bin:$PATH
|
||||
echo "=== Deployment Status ==="
|
||||
kubectl get pods -n ${{ env.K8S_NAMESPACE }} -l system=awoooi -o wide
|
||||
echo ""
|
||||
echo "=== Services ==="
|
||||
kubectl get svc -n ${{ env.K8S_NAMESPACE }} -l system=awoooi
|
||||
|
||||
# ===========================================================================
|
||||
# Stage 3: Smoke Tests
|
||||
# ===========================================================================
|
||||
smoke-test:
|
||||
name: "Smoke Tests"
|
||||
needs: deploy
|
||||
if: ${{ !inputs.skip_tests }}
|
||||
runs-on: [self-hosted, harbor, k8s]
|
||||
|
||||
steps:
|
||||
- name: API Health Check
|
||||
run: |
|
||||
echo "🏥 Running API health check..."
|
||||
for i in {1..10}; do
|
||||
if curl -sf http://192.168.0.120:32334/api/v1/health; then
|
||||
echo ""
|
||||
echo "✅ API is healthy"
|
||||
exit 0
|
||||
fi
|
||||
echo "Attempt $i/10 failed, retrying in 10s..."
|
||||
sleep 10
|
||||
done
|
||||
echo "❌ API health check failed after 10 attempts"
|
||||
exit 1
|
||||
|
||||
- name: Web Health Check
|
||||
run: |
|
||||
echo "🏥 Running Web health check..."
|
||||
for i in {1..5}; do
|
||||
if curl -sf http://192.168.0.120:32335/ -o /dev/null; then
|
||||
echo "✅ Web is healthy"
|
||||
exit 0
|
||||
fi
|
||||
echo "Attempt $i/5 failed, retrying in 5s..."
|
||||
sleep 5
|
||||
done
|
||||
echo "⚠️ Web health check failed"
|
||||
|
||||
# ===========================================================================
|
||||
# Stage 4: Notify (閉環通報)
|
||||
# ===========================================================================
|
||||
notify:
|
||||
name: "Send Notification"
|
||||
needs: [build, deploy, smoke-test]
|
||||
if: always()
|
||||
runs-on: [self-hosted, harbor, k8s]
|
||||
|
||||
steps:
|
||||
- name: Determine Status
|
||||
id: status
|
||||
run: |
|
||||
BUILD="${{ needs.build.result }}"
|
||||
DEPLOY="${{ needs.deploy.result }}"
|
||||
SMOKE="${{ needs.smoke-test.result }}"
|
||||
|
||||
if [ "$BUILD" = "success" ] && [ "$DEPLOY" = "success" ]; then
|
||||
if [ "$SMOKE" = "success" ] || [ "$SMOKE" = "skipped" ]; then
|
||||
echo "status=success" >> $GITHUB_OUTPUT
|
||||
echo "emoji=✅" >> $GITHUB_OUTPUT
|
||||
echo "message=部署成功" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "status=warning" >> $GITHUB_OUTPUT
|
||||
echo "emoji=⚠️" >> $GITHUB_OUTPUT
|
||||
echo "message=部署完成但健康檢查失敗" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
else
|
||||
echo "status=failure" >> $GITHUB_OUTPUT
|
||||
echo "emoji=❌" >> $GITHUB_OUTPUT
|
||||
echo "message=部署失敗" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Send Telegram Notification
|
||||
# Phase 8: 升級為結構化 HTML + Inline Keyboard UX
|
||||
run: |
|
||||
STATUS="${{ steps.status.outputs.status }}"
|
||||
BUILD_TAG="${{ needs.build.outputs.image_tag }}"
|
||||
SHORT_SHA="${{ github.sha }}"
|
||||
SHORT_SHA="${SHORT_SHA:0:7}"
|
||||
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
ACTOR="${{ github.actor }}"
|
||||
|
||||
# 根據狀態設定 Emoji 與標題
|
||||
if [ "$STATUS" = "success" ]; then
|
||||
EMOJI="✅"
|
||||
TITLE="部署成功"
|
||||
elif [ "$STATUS" = "warning" ]; then
|
||||
EMOJI="⚠️"
|
||||
TITLE="部署警告"
|
||||
else
|
||||
EMOJI="❌"
|
||||
TITLE="部署失敗"
|
||||
fi
|
||||
|
||||
# HTML 結構化訊息 (使用 \n 換行)
|
||||
MESSAGE="${EMOJI} <b>AWOOOI 部署通知</b>\n\n━━━━━━━━━━━━━━━━━\n📦 <b>狀態:</b> ${TITLE}\n🌍 <b>環境:</b> Production\n🏷️ <b>版本:</b> <code>${BUILD_TAG}</code>\n🔗 <b>Commit:</b> <code>${SHORT_SHA}</code>\n👤 <b>觸發者:</b> ${ACTOR}\n━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Inline Keyboard 按鈕 (成功時多一個按鈕)
|
||||
if [ "$STATUS" = "success" ]; then
|
||||
KEYBOARD='{"inline_keyboard":[[{"text":"📋 查看部署紀錄","url":"'"${RUN_URL}"'"},{"text":"🚀 開啟正式站","url":"https://awoooi.wooo.work"}]]}'
|
||||
else
|
||||
KEYBOARD='{"inline_keyboard":[[{"text":"📋 查看部署紀錄","url":"'"${RUN_URL}"'"}]]}'
|
||||
fi
|
||||
|
||||
# 發送 Telegram 訊息 (JSON payload with HTML + Inline Keyboard)
|
||||
# 鐵律: Token 必須用 Secrets,禁止硬編碼
|
||||
curl -s -X POST "https://api.telegram.org/bot${{ secrets.OPENCLAW_TG_BOT_TOKEN }}/sendMessage" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"chat_id\":\"${{ secrets.OPENCLAW_TG_CHAT_ID }}\",\"text\":\"${MESSAGE}\",\"parse_mode\":\"HTML\",\"reply_markup\":${KEYBOARD}}"
|
||||
|
||||
- name: Send OpenClaw Webhook
|
||||
if: always()
|
||||
run: |
|
||||
curl -sf -X POST "http://192.168.0.188:8088/api/v1/webhook/deploy" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"event\": \"deploy_${{ steps.status.outputs.status }}\",
|
||||
\"system\": \"awoooi\",
|
||||
\"env\": \"prod\",
|
||||
\"tag\": \"${{ needs.build.outputs.image_tag }}\",
|
||||
\"actor\": \"${{ github.actor }}\",
|
||||
\"commit\": \"${{ github.sha }}\",
|
||||
\"run_url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
|
||||
}" || echo "⚠️ OpenClaw webhook 發送失敗 (非阻塞)"
|
||||
Reference in New Issue
Block a user