From e293c38606a0f1f3d911c6cfe76376776e9679eb Mon Sep 17 00:00:00 2001 From: zyc <1439655764@qq.com> Date: Tue, 17 Mar 2026 14:37:42 +0800 Subject: [PATCH 1/2] add cicd --- .gitea/workflows/deploy.yaml | 128 +++++++++++++++++++++++ k8s/admin-deployment-prod.yaml | 62 ++++++++++++ k8s/backend-deployment-prod.yaml | 168 +++++++++++++++++++++++++++++++ k8s/ingress.yaml | 34 +++++++ 4 files changed, 392 insertions(+) create mode 100644 .gitea/workflows/deploy.yaml create mode 100644 k8s/admin-deployment-prod.yaml create mode 100644 k8s/backend-deployment-prod.yaml create mode 100644 k8s/ingress.yaml diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml new file mode 100644 index 0000000..d758eb2 --- /dev/null +++ b/.gitea/workflows/deploy.yaml @@ -0,0 +1,128 @@ +name: Build and Deploy LTY + +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 \ + --provenance=false \ + --tag ${{ secrets.SWR_SERVER }}/${{ secrets.SWR_ORG }}/lty-backend:latest \ + ./qy_lty 2>&1 | tee /tmp/build.log + + - name: Build and Push Admin Frontend + id: build-admin + run: | + set -o pipefail + docker buildx build \ + --push \ + --provenance=false \ + --tag ${{ secrets.SWR_SERVER }}/${{ secrets.SWR_ORG }}/lty-admin:latest \ + ./qy-lty-admin 2>&1 | tee -a /tmp/build.log + + - name: Setup Kubectl + run: | + curl -LO "https://dl.k8s.io/release/v1.28.2/bin/linux/amd64/kubectl" || \ + curl -LO "https://cdn.dl.k8s.io/release/v1.28.2/bin/linux/amd64/kubectl" + chmod +x kubectl + mv kubectl /usr/local/bin/ + + - name: Deploy to K3s + uses: Azure/k8s-set-context@v3 + with: + method: kubeconfig + kubeconfig: ${{ secrets.KUBE_CONFIG }} + + - name: Update K8s Manifests + id: deploy + run: | + # Replace image placeholders + sed -i "s|\${CI_REGISTRY_IMAGE}/lty-backend:latest|${{ secrets.SWR_SERVER }}/${{ secrets.SWR_ORG }}/lty-backend:latest|g" k8s/backend-deployment-prod.yaml + sed -i "s|\${CI_REGISTRY_IMAGE}/lty-admin:latest|${{ secrets.SWR_SERVER }}/${{ secrets.SWR_ORG }}/lty-admin:latest|g" k8s/admin-deployment-prod.yaml + + # Apply manifests and restart + set -o pipefail + { + kubectl apply -f k8s/backend-deployment-prod.yaml + kubectl apply -f k8s/admin-deployment-prod.yaml + kubectl apply -f k8s/ingress.yaml + kubectl rollout restart deployment/lty-backend + kubectl rollout restart deployment/lty-admin + } 2>&1 | tee /tmp/deploy.log + + - name: Report failure to Log Center + if: failure() + run: | + BUILD_LOG="" + DEPLOY_LOG="" + FAILED_STEP="unknown" + + if [ -f /tmp/build.log ]; then + BUILD_LOG=$(tail -50 /tmp/build.log | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g') + FAILED_STEP="build" + fi + if [ -f /tmp/deploy.log ]; then + DEPLOY_LOG=$(tail -50 /tmp/deploy.log | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g') + if [ -n "$DEPLOY_LOG" ]; then + FAILED_STEP="deploy" + fi + fi + + if [ -z "$BUILD_LOG" ] && [ -z "$DEPLOY_LOG" ]; then + BUILD_LOG="No captured output. Check Gitea Actions UI for details." + FAILED_STEP="pre-build" + fi + + ERROR_LOG="${BUILD_LOG}${DEPLOY_LOG}" + + curl -s -X POST "https://qiyuan-log-center-api.airlabs.art/api/v1/logs/report" \ + -H "Content-Type: application/json" \ + -d "{ + \"project_id\": \"lty\", + \"environment\": \"${{ github.ref_name }}\", + \"level\": \"ERROR\", + \"source\": \"cicd\", + \"commit_hash\": \"${{ github.sha }}\", + \"repo_url\": \"https://gitea.airlabs.art/zyc/lty.git\", + \"error\": { + \"type\": \"CICDFailure\", + \"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_id }}\", + \"branch\": \"${{ github.ref_name }}\", + \"actor\": \"${{ github.actor }}\", + \"commit\": \"${{ github.sha }}\" + } + }" || true diff --git a/k8s/admin-deployment-prod.yaml b/k8s/admin-deployment-prod.yaml new file mode 100644 index 0000000..aa7bf8a --- /dev/null +++ b/k8s/admin-deployment-prod.yaml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: lty-admin + labels: + app: lty-admin +spec: + replicas: 1 + selector: + matchLabels: + app: lty-admin + template: + metadata: + labels: + app: lty-admin + spec: + containers: + - name: lty-admin + image: ${CI_REGISTRY_IMAGE}/lty-admin:latest + imagePullPolicy: Always + ports: + - containerPort: 3000 + env: + - name: NODE_ENV + value: "production" + - name: NEXT_PUBLIC_API_BASE_URL + value: "https://qiyuan-lty-api.airlabs.art" + livenessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" +--- +apiVersion: v1 +kind: Service +metadata: + name: lty-admin +spec: + selector: + app: lty-admin + ports: + - protocol: TCP + port: 3000 + targetPort: 3000 diff --git a/k8s/backend-deployment-prod.yaml b/k8s/backend-deployment-prod.yaml new file mode 100644 index 0000000..18aeef9 --- /dev/null +++ b/k8s/backend-deployment-prod.yaml @@ -0,0 +1,168 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: lty-backend + labels: + app: lty-backend +spec: + replicas: 1 + selector: + matchLabels: + app: lty-backend + template: + metadata: + labels: + app: lty-backend + spec: + containers: + - name: lty-backend + image: ${CI_REGISTRY_IMAGE}/lty-backend:latest + imagePullPolicy: Always + ports: + - containerPort: 8000 + env: + # Django + - name: SECRET_KEY + value: "django-insecure-j63ZGygyiLWGqhZ-e3ZGH_HGfNVQgBkq*jjpYMz-QYqE46cuuA" + - name: DEBUG + value: "False" + + # Database (PostgreSQL) + - name: POSTGRESQL_DATABASE_NAME + value: "qy_lty" + - name: POSTGRESQL_DATABASE_USER + value: "qy_lty" + - name: POSTGRESQL_DATABASE_PASSWORD + value: "NfAfCvkad8L2" + - name: POSTGRESQL_DATABASE_HOST + value: "pgm-7xv4811oj11j86htzo.pg.rds.aliyuncs.com" + - name: POSTGRESQL_DATABASE_PORT + value: "5432" + + # Redis + - name: REDIS_LOCATION + value: "redis://r-7xvat0vez5clwbzk5vpd.redis.rds.aliyuncs.com:6379/0" + - name: REDIS_PASSWORD + value: "vAhRnAA6VMco" + + # Aliyun SMS + - name: ALIYUN_SMS_ACCESS_KEY_ID + value: "LTAI5t6ZXMo3SbKUg7YrK89m" + - name: ALIYUN_SMS_ACCESS_KEY_SECRET + value: "ygDVmjIIAcFgXcaJP7hbiSwOr8hYur" + - name: ALIYUN_SMS_SIGN_NAME + value: "广州气元科技" + - name: ALIYUN_SMS_TEMPLATE_CODE + value: "SMS_317100048" + + # AI (Volcengine/Kimi) + - name: KIMI_API_KEY + value: "846b6981-9954-4c58-bb39-63079393bdb8" + - name: KIMI_BASE_URL + value: "https://ark.cn-beijing.volces.com/api/v3/" + + # Aliyun Log + - name: ALIYUN_LOG_PROJECT + value: "lty" + - name: ALIYUN_LOG_STORE + value: "lty-backend" + - name: ALIYUN_LOG_ENDPOINT + value: "cn-guangzhou.log.aliyuncs.com" + - name: ALIYUN_LOG_ACCESS_KEY_ID + value: "LTAI5tFYGrgcAhscCdEUB9Te" + - name: ALIYUN_LOG_ACCESS_KEY_SECRET + value: "YO7Bf3QDH4ErYCNvSssFkHseNsh8PP" + + # Aliyun NLS (Voice) + - name: ALIYUN_NLS_ACCESS_KEY_ID + value: "LTAI5t6tnLjBK9edseDVH849" + - name: ALIYUN_NLS_ACCESS_KEY_SECRET + value: "DmiZyjnfQo03KalxYmELOfcDQUIrTX" + - name: ALIYUN_NLS_APP_ID + value: "Gxf9gwGYOvUTjzYC" + + # Aliyun OSS + - name: ALIYUN_OSS_ACCESS_KEY_ID + value: "LTAI5tL2aUarUR99h2kdKTss" + - name: ALIYUN_OSS_ACCESS_KEY_SECRET + value: "62OUrzdty2oQPiPLLt0XTAbKvMT1sO" + - name: ALIYUN_OSS_BUCKET + value: "lty-storage" + - name: ALIYUN_OSS_ENDPOINT + value: "https://oss-cn-guangzhou.aliyuncs.com" + - name: ALIYUN_OSS_HOST + value: "https://lty-storage.airlabs.art" + - name: ALIYUN_OSS_AUDIO_BASE_DIR + value: "audio" + + # Volcengine Audio (Luotianyi Voice Clone) + - name: AUDIO_SERVICE_HUOSHAN_APPID + value: "5801394478" + - name: AUDIO_SERVICE_HUOSHAN_ACCESS_TOKEN + value: "8pNMhvG4H6VWecnYOBmzyLnribeHVhoj" + - name: AUDIO_SERVICE_HUOSHAN_CLUSTER + value: "volcano_icl" + - name: AUDIO_SERVICE_HUOSHAN_VOICE_TYPE + value: "S_PHQ1AVPl1" + - name: AUDIO_SERVICE_HUOSHAN_STORAGE_DIR + value: "audio" + - name: AUDIO_SERVICE_HUOSHAN_BASE_URL + value: "https://lty-storage.airlabs.art" + + # Aliyun VI + - name: ALIYUN_VI_ACCESS_KEY_ID + value: "LTAI5tHXQGNyAws4eXB7ytYu" + - name: ALIYUN_VI_ACCESS_KEY_SECRET + value: "EouZq22js1pmMwMmGep4PWXqJEy9DA" + - name: ALIYUN_VI_ENDPOINT + value: "facebody.cn-shanghai.aliyuncs.com" + - name: ALIYUN_VI_REGION + value: "cn-shanghai" + + # Volcengine RTC + - name: VOLCENGINE_ACCESS_KEY + value: "AKLTNmVmNTU4NmEzNzFjNDYyYTk2OTZjMjMwYTljZGEwMjE" + - name: VOLCENGINE_SECRET_KEY + value: "TnpReFlqQTBaalUwT1RZNU5HSXhaamt3WlRBME5EQmtNVGc1WXpOallqYw==" + - name: VOLCENGINE_APP_ID + value: "68eb092fb8f0ac0173afe8c7" + - name: VOLCENGINE_APP_KEY + value: "8b43a113b59c47f3abd65bcd5ce8692d" + - name: VOLCENGINE_TOKEN_EXPIRE_TIME + value: "2592000" + + livenessProbe: + httpGet: + path: /admin/ + port: 8000 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /admin/ + port: 8000 + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "1024Mi" + cpu: "1000m" +--- +apiVersion: v1 +kind: Service +metadata: + name: lty-backend +spec: + selector: + app: lty-backend + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml new file mode 100644 index 0000000..aeea6f8 --- /dev/null +++ b/k8s/ingress.yaml @@ -0,0 +1,34 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: lty-ingress + annotations: + kubernetes.io/ingress.class: "traefik" + cert-manager.io/cluster-issuer: "letsencrypt-prod" +spec: + tls: + - hosts: + - qiyuan-lty-api.airlabs.art + - qiyuan-lty-admin.airlabs.art + secretName: lty-prod-tls + rules: + - host: qiyuan-lty-api.airlabs.art + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: lty-backend + port: + number: 8000 + - host: qiyuan-lty-admin.airlabs.art + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: lty-admin + port: + number: 3000 From 5cb1a4434e726b38049e557e03e329a0d1306b8d Mon Sep 17 00:00:00 2001 From: zyc <1439655764@qq.com> Date: Tue, 17 Mar 2026 15:03:12 +0800 Subject: [PATCH 2/2] build host --- k8s/admin-deployment-prod.yaml | 2 +- k8s/ingress.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/k8s/admin-deployment-prod.yaml b/k8s/admin-deployment-prod.yaml index aa7bf8a..8204678 100644 --- a/k8s/admin-deployment-prod.yaml +++ b/k8s/admin-deployment-prod.yaml @@ -24,7 +24,7 @@ spec: - name: NODE_ENV value: "production" - name: NEXT_PUBLIC_API_BASE_URL - value: "https://qiyuan-lty-api.airlabs.art" + value: "https://qy-lty.airlabs.art" livenessProbe: httpGet: path: / diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml index aeea6f8..e3596dc 100644 --- a/k8s/ingress.yaml +++ b/k8s/ingress.yaml @@ -8,11 +8,11 @@ metadata: spec: tls: - hosts: - - qiyuan-lty-api.airlabs.art - - qiyuan-lty-admin.airlabs.art + - qy-lty.airlabs.art + - qy-lty-admin.airlabs.art secretName: lty-prod-tls rules: - - host: qiyuan-lty-api.airlabs.art + - host: qy-lty.airlabs.art http: paths: - path: / @@ -22,7 +22,7 @@ spec: name: lty-backend port: number: 8000 - - host: qiyuan-lty-admin.airlabs.art + - host: qy-lty-admin.airlabs.art http: paths: - path: /