# .gitea/workflows/deploy.yml name: Deploy to K3s on: push: branches: - master jobs: deploy: runs-on: ubuntu-latest container: image: localhost:5000/tiny-ci-runner:latest env: IMAGE_TAG: ${{ github.sha }} KUBECONFIG: /tmp/.kube/config NODE_ENV: production steps: - name: Write kubeconfig run: | mkdir -p /tmp/.kube cat << 'EOF' > /tmp/.kube/config ${{ secrets.KUBECONFIG_DATA }} EOF chmod 600 /tmp/.kube/config - name: Verify Kubernetes access run: | kubectl cluster-info kubectl get nodes - name: Checkout code run: | git clone --depth=1 --branch master \ https://git.tonesc.cn/tone/tonePage.git \ /workspace/tone/tonePage cd /workspace/tone/tonePage git log -1 --oneline - name: Build and push backend image run: | cd /workspace/tone/tonePage/apps/backend docker build -t localhost:5000/backend:${IMAGE_TAG} . docker push localhost:5000/backend:${IMAGE_TAG} - name: Build and push frontend image run: | cd /workspace/tone/tonePage/apps/frontend docker build \ --build-arg API_BASE="http://backend-service:3001" \ -t localhost:5000/frontend:${IMAGE_TAG} . docker push localhost:5000/frontend:${IMAGE_TAG} - name: Run database migrations with Node.js Job run: | cd /workspace/tone/tonePage/apps/deploy echo "Running database migrations using Node.js image and backend content." # 生成一个唯一的 Job 名称,避免冲突 JOB_NAME="node-migrate-$(echo ${IMAGE_TAG} | cut -c1-8)-$(date +%s)" # 从后端镜像中提取编译后的代码 (dist) 和 package.json # 创建一个临时容器来复制文件 TEMP_CONTAINER_NAME="temp-extract-${IMAGE_TAG}" docker create --name $TEMP_CONTAINER_NAME localhost:5000/backend:${IMAGE_TAG} /bin/true # 创建临时目录 TEMP_DIR=$(mktemp -d) echo "Using temporary directory: $TEMP_DIR" # 复制 package.json 和 dist 目录 docker cp $TEMP_CONTAINER_NAME:/app/package.json $TEMP_DIR/ docker cp $TEMP_CONTAINER_NAME:/app/dist $TEMP_DIR/ # 清理临时容器 docker rm -v $TEMP_CONTAINER_NAME > /dev/null # 创建一个 ConfigMap 来存储迁移文件内容 # 将 dist 目录打包成 tar 并 base64 编码 cd $TEMP_DIR tar czf dist.tar.gz dist DIST_TAR_B64=$(base64 -w 0 dist.tar.gz) # 也获取 package.json 内容 PKG_JSON_B64=$(base64 -w 0 package.json) # 创建一个临时的 ConfigMap YAML 文件 cat << EOF > /tmp/migration-cm-${IMAGE_TAG}.yaml apiVersion: v1 kind: ConfigMap metadata: name: migration-files-$JOB_NAME namespace: default data: dist.tar.gz.b64: | $DIST_TAR_B64 package.json.b64: | $PKG_JSON_B64 EOF # 应用 ConfigMap kubectl apply -f /tmp/migration-cm-${IMAGE_TAG}.yaml # 创建一个临时的 Job YAML 文件 cat << EOF > /tmp/migration-job-${IMAGE_TAG}.yaml apiVersion: batch/v1 kind: Job metadata: name: $JOB_NAME namespace: default spec: template: spec: restartPolicy: Never initContainers: # Init container to decode and extract the files from ConfigMap - name: setup-migration image: busybox:1.35 command: ['sh', '-c'] args: - | echo "Decoding package.json and dist archive..." echo "\$PKG_JSON_B64" | base64 -d > /app/package.json echo "\$DIST_TAR_B64" | base64 -d > /app/dist.tar.gz echo "Extracting dist archive..." cd /app && tar xzf dist.tar.gz echo "Listing /app contents:" ls -la /app env: - name: PKG_JSON_B64 valueFrom: configMapKeyRef: name: migration-files-$JOB_NAME key: package.json.b64 - name: DIST_TAR_B64 valueFrom: configMapKeyRef: name: migration-files-$JOB_NAME key: dist.tar.gz.b64 volumeMounts: - name: shared-data mountPath: /app containers: - name: migrator image: node:22-alpine # 使用包含 Node.js 和 npm 的标准镜像 command: ["/bin/sh", "-c"] args: - | echo "Installing dependencies..." npm ci --only=production echo "Running database migration..." npx typeorm migration:run -d dist/data-source.js env: - name: NODE_ENV value: "production" - name: DATABASE_HOST value: "postgres-service" - name: DATABASE_PORT value: "5432" - name: DATABASE_NAME value: "tone_page" - name: DATABASE_USERNAME value: "tone_page" # 从 Secret 中获取敏感信息 - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: backend-secret key: DATABASE_PASSWORD - name: JWT_SECRET valueFrom: secretKeyRef: name: backend-secret key: JWT_SECRET - name: JWT_EXPIRES_IN value: "1d" - name: ALIYUN_ACCESS_KEY_ID valueFrom: secretKeyRef: name: backend-secret key: ALIYUN_ACCESS_KEY_ID - name: ALIYUN_ACCESS_KEY_SECRET valueFrom: secretKeyRef: name: backend-secret key: ALIYUN_ACCESS_KEY_SECRET - name: ALIYUN_OSS_STS_ROLE_ARN valueFrom: secretKeyRef: name: backend-secret key: ALIYUN_OSS_STS_ROLE_ARN - name: WEBAUTHN_RP_ID valueFrom: secretKeyRef: name: backend-secret key: WEBAUTHN_RP_ID - name: WEBAUTHN_ORIGIN valueFrom: secretKeyRef: name: backend-secret key: WEBAUTHN_ORIGIN - name: WEBAUTHN_RP_NAME valueFrom: secretKeyRef: name: backend-secret key: WEBAUTHN_RP_NAME volumeMounts: - name: shared-data mountPath: /app workingDir: /app volumes: - name: shared-data emptyDir: {} backoffLimit: 1 # 如果 Job 失败,只重试 1 次 EOF # 应用 Job 配置 kubectl apply -f /tmp/migration-job-${IMAGE_TAG}.yaml echo "Waiting for job $JOB_NAME to complete..." # 等待 Job 完成,最多等待 5 分钟 (300秒) kubectl wait --for=condition=complete job/$JOB_NAME --timeout=300s # 检查 Job 是否成功 FAILED_COUNT=$(kubectl get job $JOB_NAME -o jsonpath='{.status.failed}' 2>/dev/null || echo "null") if [ "$FAILED_COUNT" = "null" ] || [ "$FAILED_COUNT" -eq 0 ]; then echo "Migration job $JOB_NAME completed successfully." else echo "Migration job $JOB_NAME failed. Failed pod count: $FAILED_COUNT" kubectl describe job $JOB_NAME echo "Logs from the failed pod:" FAILED_POD_NAME=$(kubectl get pods --selector=job-name=$JOB_NAME --field-selector=status.phase=Failed -o jsonpath='{.items[0].metadata.name}') if [ ! -z "$FAILED_POD_NAME" ]; then kubectl logs $FAILED_POD_NAME -c migrator kubectl logs $FAILED_POD_NAME -c setup-migration # Also check init container logs else echo "Could not find the failed pod name." fi exit 1 fi # 清理临时资源 (可选) kubectl delete job $JOB_NAME kubectl delete configmap migration-files-$JOB_NAME rm /tmp/migration-job-${IMAGE_TAG}.yaml rm /tmp/migration-cm-${IMAGE_TAG}.yaml rm -rf $TEMP_DIR echo "Database migrations completed successfully." - name: Deploy to K3s run: | cd /workspace/tone/tonePage/apps/deploy # 基础资源 kubectl apply -f postgres-deployment.yaml kubectl apply -f backend-deployment.yaml kubectl apply -f frontend-deployment.yaml # 更新镜像(触发滚动更新) kubectl set image deployment/backend \ backend=localhost:5000/backend:${IMAGE_TAG} kubectl set image deployment/frontend \ frontend=localhost:5000/frontend:${IMAGE_TAG} # 等待滚动完成 kubectl rollout status deployment/backend --timeout=120s kubectl rollout status deployment/frontend --timeout=120s - name: Post-deploy sanity check run: | kubectl get pods kubectl get svc