name: Build and Deploy on: push: branches: - main - master jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 with: config-inline: | [registry."docker.io"] mirrors = ["https://docker.m.daocloud.io", "https://docker.1panel.live", "https://hub.rat.dev"] - name: Login to Huawei Cloud SWR uses: docker/login-action@v2 with: registry: ${{ secrets.SWR_SERVER }} username: ${{ secrets.SWR_USERNAME }} password: ${{ secrets.SWR_PASSWORD }} - name: Build and Push Backend id: build_backend run: | set -o pipefail docker buildx build \ --push \ --no-cache \ --provenance=false \ --tag ${{ secrets.SWR_SERVER }}/${{ secrets.SWR_ORG }}/video-backend:latest \ ./backend 2>&1 | tee /tmp/build.log - name: Build and Push Web id: build_web run: | set -o pipefail docker buildx build \ --push \ --provenance=false \ --tag ${{ secrets.SWR_SERVER }}/${{ secrets.SWR_ORG }}/video-web:latest \ ./web 2>&1 | tee -a /tmp/build.log - name: Setup SSH run: | mkdir -p ~/.ssh echo "${{ secrets.K3S_SSH_KEY }}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan -H ${{ secrets.K3S_HOST }} >> ~/.ssh/known_hosts 2>/dev/null - name: Deploy to K3s via SSH id: deploy run: | SWR_IMAGE="${{ secrets.SWR_SERVER }}/${{ secrets.SWR_ORG }}" # Replace image placeholders in yaml files sed -i "s|\${CI_REGISTRY_IMAGE}/video-backend:latest|${SWR_IMAGE}/video-backend:latest|g" k8s/backend-deployment.yaml sed -i "s|\${CI_REGISTRY_IMAGE}/video-backend:latest|${SWR_IMAGE}/video-backend:latest|g" k8s/celery-deployment.yaml sed -i "s|\${CI_REGISTRY_IMAGE}/video-web:latest|${SWR_IMAGE}/video-web:latest|g" k8s/web-deployment.yaml # Copy k8s manifests to server scp -o StrictHostKeyChecking=no k8s/backend-deployment.yaml k8s/web-deployment.yaml k8s/ingress.yaml k8s/celery-deployment.yaml root@${{ secrets.K3S_HOST }}:/tmp/ # Create/update secrets and apply manifests on server set -o pipefail ssh -o StrictHostKeyChecking=no root@${{ secrets.K3S_HOST }} << ENDSSH export KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl create secret generic video-backend-secrets \ --from-literal=ARK_API_KEY='${{ secrets.ARK_API_KEY }}' \ --from-literal=TOS_ACCESS_KEY='${{ secrets.TOS_ACCESS_KEY }}' \ --from-literal=TOS_SECRET_KEY='${{ secrets.TOS_SECRET_KEY }}' \ --from-literal=DJANGO_SECRET_KEY='${{ secrets.DJANGO_SECRET_KEY }}' \ --from-literal=DB_HOST='${{ secrets.DB_HOST }}' \ --from-literal=DB_USER='${{ secrets.DB_USER }}' \ --from-literal=DB_PASSWORD='${{ secrets.DB_PASSWORD }}' \ --from-literal=ALIYUN_SMS_ACCESS_KEY='${{ secrets.ALIYUN_SMS_ACCESS_KEY }}' \ --from-literal=ALIYUN_SMS_ACCESS_SECRET='${{ secrets.ALIYUN_SMS_ACCESS_SECRET }}' \ --dry-run=client -o yaml | kubectl apply -f - kubectl apply -f /tmp/backend-deployment.yaml kubectl apply -f /tmp/celery-deployment.yaml kubectl apply -f /tmp/web-deployment.yaml kubectl apply -f /tmp/ingress.yaml # Preserve real client IP: disable SNAT on Traefik kubectl patch svc traefik -n kube-system -p '{"spec":{"externalTrafficPolicy":"Local"}}' 2>/dev/null || true kubectl rollout restart deployment/video-backend kubectl rollout restart deployment/celery-worker kubectl rollout restart deployment/video-web rm -f /tmp/backend-deployment.yaml /tmp/web-deployment.yaml /tmp/ingress.yaml /tmp/celery-deployment.yaml ENDSSH # ===== Log Center: failure reporting ===== - name: Report failure to Log Center if: failure() run: | BUILD_LOG="" DEPLOY_LOG="" FAILED_STEP="unknown" if [[ "${{ steps.build_backend.outcome }}" == "failure" || "${{ steps.build_web.outcome }}" == "failure" ]]; then FAILED_STEP="build" if [ -f /tmp/build.log ]; then BUILD_LOG=$(tail -50 /tmp/build.log | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g') fi elif [[ "${{ steps.deploy.outcome }}" == "failure" ]]; then FAILED_STEP="deploy" if [ -f /tmp/deploy.log ]; then DEPLOY_LOG=$(tail -50 /tmp/deploy.log | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g') fi fi ERROR_LOG="${BUILD_LOG}${DEPLOY_LOG}" if [ -z "$ERROR_LOG" ]; then ERROR_LOG="No captured output. Check Gitea Actions UI for details." fi if [[ "$FAILED_STEP" == "deploy" ]]; then SOURCE="deployment" ERROR_TYPE="DeployError" else SOURCE="cicd" ERROR_TYPE="DockerBuildError" fi curl -s -X POST "https://qiyuan-log-center-api.airlabs.art/api/v1/logs/report" \ -H "Content-Type: application/json" \ -d "{ \"project_id\": \"video_backend\", \"environment\": \"${{ github.ref_name }}\", \"level\": \"ERROR\", \"source\": \"${SOURCE}\", \"commit_hash\": \"${{ github.sha }}\", \"repo_url\": \"https://gitea.airlabs.art/zyc/video-shuoshan.git\", \"error\": { \"type\": \"${ERROR_TYPE}\", \"message\": \"[${FAILED_STEP}] Build and Deploy failed on branch ${{ github.ref_name }}\", \"stack_trace\": [\"${ERROR_LOG}\"] }, \"context\": { \"job_name\": \"build-and-deploy\", \"step_name\": \"${FAILED_STEP}\", \"workflow\": \"${{ github.workflow }}\", \"run_id\": \"${{ github.run_number }}\", \"branch\": \"${{ github.ref_name }}\", \"actor\": \"${{ github.actor }}\", \"commit\": \"${{ github.sha }}\", \"run_url\": \"https://gitea.airlabs.art/${{ github.repository }}/actions/runs/${{ github.run_number }}\" } }" || true