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 <noreply@anthropic.com>
This commit is contained in:
165
.github/workflows/cd.yaml
vendored
165
.github/workflows/cd.yaml
vendored
@@ -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/
|
||||
|
||||
Reference in New Issue
Block a user