From cc8a91995db9d2aec33bfe4dc541166f8b866158 Mon Sep 17 00:00:00 2001 From: zyc <1439655764@qq.com> Date: Fri, 20 Mar 2026 18:35:15 +0800 Subject: [PATCH] Add K8s --- .gitea/workflows/deploy.yaml | 140 +++++++++++++++++++++++++++++++++++ docker-compose.yml | 6 +- frontend/nginx.conf | 2 +- k8s/backend-deployment.yaml | 97 ++++++++++++++++++++++++ k8s/ingress.yaml | 34 +++++++++ k8s/web-deployment.yaml | 59 +++++++++++++++ 6 files changed, 334 insertions(+), 4 deletions(-) create mode 100644 .gitea/workflows/deploy.yaml create mode 100644 k8s/backend-deployment.yaml create mode 100644 k8s/ingress.yaml create mode 100644 k8s/web-deployment.yaml diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml new file mode 100644 index 0000000..78b1d23 --- /dev/null +++ b/.gitea/workflows/deploy.yaml @@ -0,0 +1,140 @@ +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 \ + --provenance=false \ + --tag ${{ secrets.SWR_SERVER }}/${{ secrets.SWR_ORG }}/airgate-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 }}/airgate-web:latest \ + ./frontend 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}/airgate-backend:latest|${SWR_IMAGE}/airgate-backend:latest|g" k8s/backend-deployment.yaml + sed -i "s|\${CI_REGISTRY_IMAGE}/airgate-web:latest|${SWR_IMAGE}/airgate-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 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 apply -f /tmp/backend-deployment.yaml + kubectl apply -f /tmp/web-deployment.yaml + kubectl apply -f /tmp/ingress.yaml + kubectl rollout restart deployment/airgate-backend + kubectl rollout restart deployment/airgate-web + + rm -f /tmp/backend-deployment.yaml /tmp/web-deployment.yaml /tmp/ingress.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\": \"airgate\", + \"environment\": \"${{ github.ref_name }}\", + \"level\": \"ERROR\", + \"source\": \"${SOURCE}\", + \"commit_hash\": \"${{ github.sha }}\", + \"repo_url\": \"${{ github.server_url }}/${{ github.repository }}\", + \"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\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}\" + } + }" || true diff --git a/docker-compose.yml b/docker-compose.yml index ce01bf3..62ef58c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3.8' services: - backend: + airgate-backend: build: ./backend ports: - "8101:8100" @@ -15,12 +15,12 @@ services: - backend-data:/app/data restart: unless-stopped - frontend: + airgate-web: build: ./frontend ports: - "5174:80" depends_on: - - backend + - airgate-backend restart: unless-stopped volumes: diff --git a/frontend/nginx.conf b/frontend/nginx.conf index e56a400..64b00e1 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -5,7 +5,7 @@ server { index index.html; location /api/ { - proxy_pass http://backend:8100; + proxy_pass http://airgate-backend:8100; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/k8s/backend-deployment.yaml b/k8s/backend-deployment.yaml new file mode 100644 index 0000000..fb85bfe --- /dev/null +++ b/k8s/backend-deployment.yaml @@ -0,0 +1,97 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: airgate-backend + labels: + app: airgate-backend +spec: + replicas: 1 + selector: + matchLabels: + app: airgate-backend + template: + metadata: + labels: + app: airgate-backend + spec: + imagePullSecrets: + - name: swr-secret + containers: + - name: airgate-backend + image: ${CI_REGISTRY_IMAGE}/airgate-backend:latest + imagePullPolicy: Always + ports: + - containerPort: 8100 + env: + - name: DJANGO_DEBUG + value: "False" + - name: DJANGO_ALLOWED_HOSTS + value: "*" + - name: DJANGO_SECRET_KEY + value: "HYsUppcrbCq05fEMzXfokwC8ge9CF3mV7auoSqlCbCakwC8t7lVrYG_pfixA6CHrCJc" + - name: DB_DIR + value: "/app/data" + # CORS + - name: CORS_ALLOWED_ORIGINS + value: "https://airgate.airlabs.art" + - name: AIRGATE_ENCRYPTION_KEY + value: "8By6udk4wn4VUQeHl_zvr8l6ZBEz77HKs_JkhwiC7FQ=" + volumeMounts: + - name: sqlite-data + mountPath: /app/data + livenessProbe: + httpGet: + path: /healthz/ + port: 8100 + httpHeaders: + - name: Host + value: localhost + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /healthz/ + port: 8100 + httpHeaders: + - name: Host + value: localhost + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "1024Mi" + cpu: "1000m" + volumes: + - name: sqlite-data + persistentVolumeClaim: + claimName: airgate-sqlite-pvc +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: airgate-sqlite-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: airgate-backend +spec: + selector: + app: airgate-backend + ports: + - protocol: TCP + port: 8100 + targetPort: 8100 diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml new file mode 100644 index 0000000..1216de3 --- /dev/null +++ b/k8s/ingress.yaml @@ -0,0 +1,34 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: airgate-ingress + annotations: + kubernetes.io/ingress.class: "traefik" + cert-manager.io/cluster-issuer: "letsencrypt-prod" +spec: + tls: + - hosts: + - airgate-api.airlabs.art + - airgate.airlabs.art + secretName: airgate-tls + rules: + - host: airgate-api.airlabs.art + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: airgate-backend + port: + number: 8100 + - host: airgate.airlabs.art + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: airgate-web + port: + number: 80 diff --git a/k8s/web-deployment.yaml b/k8s/web-deployment.yaml new file mode 100644 index 0000000..ac0dd28 --- /dev/null +++ b/k8s/web-deployment.yaml @@ -0,0 +1,59 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: airgate-web + labels: + app: airgate-web +spec: + replicas: 1 + selector: + matchLabels: + app: airgate-web + template: + metadata: + labels: + app: airgate-web + spec: + imagePullSecrets: + - name: swr-secret + containers: + - name: airgate-web + image: ${CI_REGISTRY_IMAGE}/airgate-web:latest + imagePullPolicy: Always + ports: + - containerPort: 80 + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "256Mi" + cpu: "250m" +--- +apiVersion: v1 +kind: Service +metadata: + name: airgate-web +spec: + selector: + app: airgate-web + ports: + - protocol: TCP + port: 80 + targetPort: 80