# ============================================================================= # 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="✅ UAT 部署成功%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="❌ UAT 部署失敗%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="✅ GCP 部署成功%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