From 515339f2a5a7d617f1bc97bd23f4dbf8817559da Mon Sep 17 00:00:00 2001 From: OG T Date: Tue, 24 Mar 2026 14:13:56 +0800 Subject: [PATCH] perf(cd): Optimize CD workflow based on wooo-aiops patterns Changes: - Add change detection (only build what changed) - Add skip_api/skip_web manual inputs for selective builds - Use native Docker BuildKit (remove buildx-action overhead) - Add local Next.js cache (/home/wooo/build-cache/awoooi/) - Split build-images into build-api and build-web jobs Reference: wooo-aiops ci.yml and fast-deploy-uat.yml Co-Authored-By: Claude Opus 4.5 --- .github/workflows/cd.yaml | 165 +++++++++++++++++++++++++++++--------- 1 file changed, 128 insertions(+), 37 deletions(-) diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 3d717126..6f67a37b 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -7,6 +7,15 @@ on: - 'docs/**' - '*.md' workflow_dispatch: + inputs: + skip_api: + description: '跳過 API 建構 (只改前端時)' + type: boolean + default: false + skip_web: + description: '跳過 Web 建構 (只改後端時)' + type: boolean + default: false # 沿用 AIOPS 設計: 新 commit 自動取消舊 workflow concurrency: @@ -16,35 +25,56 @@ concurrency: env: REGISTRY: 192.168.0.110:5000 IMAGE_PREFIX: library/awoooi + # 本地快取路徑 (比 Registry cache 更快) + LOCAL_CACHE_DIR: /home/wooo/build-cache/awoooi jobs: - # ==================== Build & Push Images ==================== - build-images: - name: Build & Push Images + # ==================== 變更偵測 ==================== + detect-changes: + name: Detect Changes runs-on: self-hosted - strategy: - matrix: - app: [web, api] + outputs: + api_changed: ${{ steps.changes.outputs.api }} + web_changed: ${{ steps.changes.outputs.web }} steps: - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 with: - # 修復: Harbor 是 HTTP,需要設定 insecure registry - driver-opts: | - network=host - buildkitd-config-inline: | - [registry."192.168.0.110:5000"] - http = true - insecure = true + fetch-depth: 2 - - name: Login to WOOO Harbor - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.HARBOR_USER }} - password: ${{ secrets.HARBOR_PASSWORD }} + - name: Check changed files + id: changes + run: | + # 取得變更的檔案 + CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "") + echo "Changed files: $CHANGED_FILES" + + # 偵測 API 變更 + if echo "$CHANGED_FILES" | grep -qE "^apps/api/|^packages/"; then + echo "api=true" >> $GITHUB_OUTPUT + else + echo "api=false" >> $GITHUB_OUTPUT + fi + + # 偵測 Web 變更 + if echo "$CHANGED_FILES" | grep -qE "^apps/web/|^packages/"; then + echo "web=true" >> $GITHUB_OUTPUT + else + echo "web=false" >> $GITHUB_OUTPUT + fi + + # ==================== Build API ==================== + build-api: + name: Build & Push API + runs-on: self-hosted + needs: detect-changes + if: | + github.event_name == 'workflow_dispatch' && !inputs.skip_api || + github.event_name == 'push' && needs.detect-changes.outputs.api_changed == 'true' || + github.event_name == 'push' && needs.detect-changes.outputs.api_changed == 'false' && needs.detect-changes.outputs.web_changed == 'false' + outputs: + image_tag: ${{ steps.tag.outputs.tag }} + steps: + - uses: actions/checkout@v4 - name: Generate image tag id: tag @@ -53,26 +83,87 @@ jobs: RUN_ID=${{ github.run_id }} echo "tag=${SHA}-${RUN_ID}" >> $GITHUB_OUTPUT - - name: Build & Push to Harbor - uses: docker/build-push-action@v5 - with: - context: . - file: apps/${{ matrix.app }}/Dockerfile - push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.app }}:${{ steps.tag.outputs.tag }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Output image tag + - name: Login to Harbor run: | - echo "::notice::Image pushed: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.app }}:${{ steps.tag.outputs.tag }}" + echo "${{ secrets.HARBOR_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u ${{ secrets.HARBOR_USER }} --password-stdin + + # 🚀 沿用 AIOPS: 原生 BuildKit (無中間層損耗) + - name: Build & Push API (Native BuildKit) + env: + DOCKER_BUILDKIT: 1 + run: | + echo "🐳 使用原生 Docker BuildKit 建構 API..." + docker build \ + --push \ + --tag ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api:${{ steps.tag.outputs.tag }} \ + --file apps/api/Dockerfile \ + . + echo "✅ API 映像: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api:${{ steps.tag.outputs.tag }}" + + # ==================== Build Web ==================== + build-web: + name: Build & Push Web + runs-on: self-hosted + needs: detect-changes + if: | + github.event_name == 'workflow_dispatch' && !inputs.skip_web || + github.event_name == 'push' && needs.detect-changes.outputs.web_changed == 'true' || + github.event_name == 'push' && needs.detect-changes.outputs.api_changed == 'false' && needs.detect-changes.outputs.web_changed == 'false' + outputs: + image_tag: ${{ steps.tag.outputs.tag }} + steps: + - uses: actions/checkout@v4 + + - name: Generate image tag + id: tag + run: | + SHA=$(git rev-parse --short HEAD) + RUN_ID=${{ github.run_id }} + echo "tag=${SHA}-${RUN_ID}" >> $GITHUB_OUTPUT + + - name: Login to Harbor + run: | + echo "${{ secrets.HARBOR_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u ${{ secrets.HARBOR_USER }} --password-stdin + + # 🚀 沿用 AIOPS: 恢復本地 Next.js 快取 + - name: Restore Next.js cache + run: | + mkdir -p apps/web/.next/cache + if [ -d "${{ env.LOCAL_CACHE_DIR }}/nextjs" ]; then + cp -r ${{ env.LOCAL_CACHE_DIR }}/nextjs/* apps/web/.next/cache/ 2>/dev/null || true + echo "✅ Next.js 快取已恢復" + fi + + # 🚀 沿用 AIOPS: 原生 BuildKit + - name: Build & Push Web (Native BuildKit) + env: + DOCKER_BUILDKIT: 1 + run: | + echo "🎨 使用原生 Docker BuildKit 建構 Web..." + docker build \ + --push \ + --tag ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web:${{ steps.tag.outputs.tag }} \ + --file apps/web/Dockerfile \ + . + echo "✅ Web 映像: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web:${{ steps.tag.outputs.tag }}" + + # 🚀 沿用 AIOPS: 儲存本地快取 + - name: Save Next.js cache + run: | + mkdir -p ${{ env.LOCAL_CACHE_DIR }}/nextjs + if [ -d "apps/web/.next/cache" ]; then + cp -r apps/web/.next/cache/* ${{ env.LOCAL_CACHE_DIR }}/nextjs/ 2>/dev/null || true + echo "✅ Next.js 快取已儲存" + fi # ==================== Deploy to Production ==================== # Memory 鐵律: 禁止 UAT,只有 Dev + Prod deploy-prod: name: Deploy to Production runs-on: self-hosted - needs: build-images + needs: [detect-changes, build-api, build-web] + # 允許部分 build 被跳過 + if: always() && (needs.build-api.result == 'success' || needs.build-api.result == 'skipped') && (needs.build-web.result == 'success' || needs.build-web.result == 'skipped') environment: production steps: - uses: actions/checkout@v4 @@ -86,13 +177,13 @@ jobs: - name: Install kubectl and Kustomize run: | mkdir -p $HOME/.local/bin - # Install kubectl + # Install kubectl (110 主機應已預裝) if ! command -v kubectl &> /dev/null; then curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x kubectl mv kubectl $HOME/.local/bin/ fi - # Install kustomize + # Install kustomize (110 主機應已預裝) if ! command -v kustomize &> /dev/null; then curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash mv kustomize $HOME/.local/bin/