Some checks failed
CD Pipeline / deploy (push) Failing after 59s
- 建立 Gitea Actions CD pipeline (.gitea/workflows/cd.yaml) - 部署模式: rsync Python 檔案至 188 → docker restart (volume mount) - Dockerfile/requirements 變動時自動重建 Docker image - 部署通知: Telegram (開始/成功/失敗) - 健康檢查: https://mo.wooo.work/health (最多 5 次重試) - 同步最新 CLAUDE.md / ADR-008 / memory (2026-04-19) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
265 lines
9.3 KiB
YAML
265 lines
9.3 KiB
YAML
# =============================================================================
|
||
# WOOO TECH - Momo Pro System
|
||
# GitLab CI/CD Pipeline
|
||
# =============================================================================
|
||
#
|
||
# 流程:
|
||
# 1. test: 執行 pytest 測試
|
||
# 2. security: Bandit 安全掃描
|
||
# 3. build: 構建 Docker 映像並推送到 Registry
|
||
# 4. deploy-uat: 部署到 UAT 環境
|
||
# 5. deploy-gcp: 部署到 GCP 環境 (手動觸發)
|
||
#
|
||
# Registry:
|
||
# - URL: registry.wooo.work
|
||
# - 帳號: GitLab CI/CD 變數 REGISTRY_USER
|
||
# - 密碼: GitLab CI/CD 變數 REGISTRY_PASSWORD
|
||
#
|
||
# =============================================================================
|
||
|
||
stages:
|
||
- test
|
||
- build
|
||
- deploy
|
||
|
||
variables:
|
||
# Docker Registry
|
||
REGISTRY_URL: "registry.wooo.work"
|
||
IMAGE_NAME: "wooo/momo-pro-system"
|
||
IMAGE_PATH: "${REGISTRY_URL}/${IMAGE_NAME}"
|
||
|
||
# 目標環境
|
||
UAT_HOST: "192.168.0.110"
|
||
UAT_USER: "wooo"
|
||
GCP_HOST: "35.194.233.141"
|
||
GCP_USER: "ogt"
|
||
|
||
# =============================================================================
|
||
# Test Stage
|
||
# =============================================================================
|
||
test:
|
||
stage: test
|
||
image: python:3.11-slim
|
||
before_script:
|
||
- pip install --quiet -r requirements.txt
|
||
- pip install --quiet pytest pytest-cov
|
||
script:
|
||
- python -m pytest tests/ -v --tb=short || echo "No tests or tests skipped"
|
||
allow_failure: true
|
||
tags:
|
||
- docker
|
||
rules:
|
||
- if: '$CI_PIPELINE_SOURCE == "push"'
|
||
- if: '$CI_PIPELINE_SOURCE == "web"'
|
||
|
||
# =============================================================================
|
||
# Security Scan (Bandit)
|
||
# =============================================================================
|
||
security-scan:
|
||
stage: test
|
||
image: python:3.11-slim
|
||
before_script:
|
||
- pip install --quiet bandit
|
||
script:
|
||
- bandit -r . -x ./tests,./venv -f json -o bandit-report.json || true
|
||
- |
|
||
if [ -f bandit-report.json ]; then
|
||
HIGH=$(cat bandit-report.json | python3 -c "import sys,json; print(len([r for r in json.load(sys.stdin).get('results',[]) if r.get('issue_severity')=='HIGH']))" 2>/dev/null || echo "0")
|
||
echo "High severity issues: $HIGH"
|
||
if [ "$HIGH" -gt "0" ]; then
|
||
echo "⚠️ 發現高危安全問題"
|
||
fi
|
||
fi
|
||
artifacts:
|
||
paths:
|
||
- bandit-report.json
|
||
expire_in: 1 week
|
||
allow_failure: true
|
||
tags:
|
||
- docker
|
||
|
||
# =============================================================================
|
||
# Build & Push to Registry
|
||
# =============================================================================
|
||
build:
|
||
stage: build
|
||
image: docker:24.0.7
|
||
services:
|
||
- docker:24.0.7-dind
|
||
variables:
|
||
DOCKER_TLS_CERTDIR: "/certs"
|
||
before_script:
|
||
- echo "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin $REGISTRY_URL
|
||
script:
|
||
- echo "Building image..."
|
||
- BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||
- docker build --build-arg BUILD_DATE=$BUILD_DATE --build-arg GIT_SHA=$CI_COMMIT_SHA -t $IMAGE_PATH:$CI_COMMIT_SHORT_SHA -t $IMAGE_PATH:latest .
|
||
- echo "Pushing to registry..."
|
||
- docker push $IMAGE_PATH:$CI_COMMIT_SHORT_SHA
|
||
- docker push $IMAGE_PATH:latest
|
||
- echo "Image pushed - $IMAGE_PATH:latest"
|
||
after_script:
|
||
- if [ -n "$TELEGRAM_BOT_TOKEN" ] && [ "$CI_JOB_STATUS" = "success" ]; then curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" -d chat_id="${TELEGRAM_CHAT_ID}" -d parse_mode="HTML" -d text="Build Success - ${IMAGE_PATH}:${CI_COMMIT_SHORT_SHA}" > /dev/null 2>&1; fi
|
||
tags:
|
||
- docker
|
||
rules:
|
||
- if: '$CI_COMMIT_BRANCH == "main"'
|
||
|
||
# =============================================================================
|
||
# Deploy to UAT
|
||
# =============================================================================
|
||
deploy-uat:
|
||
stage: deploy
|
||
image: alpine:latest
|
||
before_script:
|
||
- apk add --no-cache openssh-client curl
|
||
- mkdir -p ~/.ssh
|
||
- echo "$UAT_SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||
- chmod 600 ~/.ssh/id_rsa
|
||
- ssh-keyscan -H $UAT_HOST >> ~/.ssh/known_hosts 2>/dev/null
|
||
script:
|
||
- echo "🚀 部署到 UAT..."
|
||
- |
|
||
ssh -o StrictHostKeyChecking=no ${UAT_USER}@${UAT_HOST} << ENDSSH
|
||
# 設定 kubectl 環境
|
||
export KUBECONFIG=/home/wooo/.kube/config
|
||
|
||
# 同步程式碼
|
||
cd /home/wooo/momo_pro_system
|
||
git fetch gitlab main
|
||
git reset --hard gitlab/main
|
||
|
||
# 更新 K8s registry secret
|
||
kubectl delete secret registry-secret -n momo 2>/dev/null || true
|
||
kubectl create secret docker-registry registry-secret \
|
||
--docker-server=${REGISTRY_URL} \
|
||
--docker-username=${REGISTRY_USER} \
|
||
--docker-password=${REGISTRY_PASSWORD} \
|
||
-n momo
|
||
|
||
# 重啟服務 (會自動拉取新映像)
|
||
kubectl rollout restart deployment/momo-app deployment/momo-scheduler -n momo
|
||
|
||
# 等待就緒
|
||
kubectl rollout status deployment/momo-app -n momo --timeout=180s
|
||
echo "✅ UAT 部署完成"
|
||
ENDSSH
|
||
- |
|
||
# 健康檢查
|
||
sleep 10
|
||
if curl -s "https://mo.wooo.work/health" | grep -q "healthy"; then
|
||
echo "✅ 健康檢查通過"
|
||
else
|
||
echo "⚠️ 健康檢查失敗"
|
||
fi
|
||
after_script:
|
||
- |
|
||
if [ -n "$TELEGRAM_BOT_TOKEN" ]; then
|
||
if [ "$CI_JOB_STATUS" == "success" ]; then
|
||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||
-d chat_id="${TELEGRAM_CHAT_ID}" \
|
||
-d parse_mode="HTML" \
|
||
-d text="✅ <b>UAT 部署成功</b>%0A%0A🌐 https://mo.wooo.work%0A📌 ${CI_COMMIT_SHORT_SHA}%0A👤 ${GITLAB_USER_NAME}" > /dev/null 2>&1
|
||
else
|
||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||
-d chat_id="${TELEGRAM_CHAT_ID}" \
|
||
-d parse_mode="HTML" \
|
||
-d text="❌ <b>UAT 部署失敗</b>%0A%0A🔗 ${CI_PIPELINE_URL}" > /dev/null 2>&1
|
||
fi
|
||
fi
|
||
tags:
|
||
- docker
|
||
rules:
|
||
- if: '$CI_COMMIT_BRANCH == "main"'
|
||
needs:
|
||
- build
|
||
|
||
# =============================================================================
|
||
# Deploy to GCP (手動觸發)
|
||
# =============================================================================
|
||
deploy-gcp:
|
||
stage: deploy
|
||
image: alpine:latest
|
||
before_script:
|
||
- apk add --no-cache openssh-client curl docker
|
||
- mkdir -p ~/.ssh
|
||
- echo "$GCP_SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||
- chmod 600 ~/.ssh/id_rsa
|
||
- ssh-keyscan -H $GCP_HOST >> ~/.ssh/known_hosts 2>/dev/null
|
||
script:
|
||
- echo "🚀 部署到 GCP..."
|
||
- |
|
||
# 從 Registry 拉取映像
|
||
echo "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin $REGISTRY_URL
|
||
docker pull $IMAGE_PATH:latest
|
||
|
||
# 匯出映像
|
||
docker save $IMAGE_PATH:latest -o /tmp/momo-image.tar
|
||
|
||
# 傳輸到 GCP (使用用戶家目錄)
|
||
scp -o StrictHostKeyChecking=no /tmp/momo-image.tar ${GCP_USER}@${GCP_HOST}:~/momo-image.tar
|
||
|
||
# 在 GCP 上匯入並部署
|
||
ssh -o StrictHostKeyChecking=no ${GCP_USER}@${GCP_HOST} << ENDSSH
|
||
# 匯入映像到 K3s
|
||
sudo k3s ctr images import ~/momo-image.tar
|
||
rm ~/momo-image.tar
|
||
|
||
# 更新 Deployment 使用新映像
|
||
sudo kubectl set image deployment/momo-app momo-app=${IMAGE_PATH}:latest -n momo
|
||
sudo kubectl set image deployment/momo-scheduler scheduler=${IMAGE_PATH}:latest -n momo
|
||
|
||
# 設定 imagePullPolicy 為 IfNotPresent(允許使用本地匯入的映像)
|
||
sudo kubectl patch deployment momo-app -n momo -p '{"spec":{"template":{"spec":{"containers":[{"name":"momo-app","imagePullPolicy":"IfNotPresent"}]}}}}'
|
||
sudo kubectl patch deployment momo-scheduler -n momo -p '{"spec":{"template":{"spec":{"containers":[{"name":"scheduler","imagePullPolicy":"IfNotPresent"}]}}}}'
|
||
|
||
# 等待部署完成
|
||
sudo kubectl rollout status deployment/momo-app -n momo --timeout=180s
|
||
|
||
echo "✅ GCP 部署完成"
|
||
ENDSSH
|
||
- |
|
||
# 健康檢查
|
||
sleep 10
|
||
if curl -s "https://momo.wooo.work/health" | grep -q "healthy"; then
|
||
echo "✅ GCP 健康檢查通過"
|
||
else
|
||
echo "⚠️ GCP 健康檢查失敗"
|
||
fi
|
||
after_script:
|
||
- |
|
||
if [ -n "$TELEGRAM_BOT_TOKEN" ]; then
|
||
if [ "$CI_JOB_STATUS" == "success" ]; then
|
||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||
-d chat_id="${TELEGRAM_CHAT_ID}" \
|
||
-d parse_mode="HTML" \
|
||
-d text="✅ <b>GCP 部署成功</b>%0A%0A🌐 https://momo.wooo.work%0A📌 ${CI_COMMIT_SHORT_SHA}" > /dev/null 2>&1
|
||
fi
|
||
fi
|
||
tags:
|
||
- docker
|
||
rules:
|
||
- if: '$CI_COMMIT_BRANCH == "main"'
|
||
needs:
|
||
- deploy-uat
|
||
allow_failure: false
|
||
|
||
# =============================================================================
|
||
# Health Check (手動)
|
||
# =============================================================================
|
||
health-check:
|
||
stage: deploy
|
||
image: alpine:latest
|
||
before_script:
|
||
- apk add --no-cache curl
|
||
script:
|
||
- echo "🏥 健康檢查..."
|
||
- echo "UAT:" && curl -s "https://mo.wooo.work/health" || echo "無法連線"
|
||
- echo "GCP:" && curl -s "https://momo.wooo.work/health" || echo "無法連線"
|
||
- echo "Registry:" && curl -s -u "$REGISTRY_USER:$REGISTRY_PASSWORD" "https://$REGISTRY_URL/v2/_catalog" || echo "無法連線"
|
||
tags:
|
||
- docker
|
||
rules:
|
||
- if: '$CI_PIPELINE_SOURCE == "web"'
|
||
when: manual
|