ci: 添加 Gitea Actions CI/CD 和 Knative 部署配置
- 添加 CI workflow(PR 构建检查) - 添加 Deploy workflow(main 分支自动部署) - 添加 Web/API 多阶段 Dockerfile - 添加 Knative Service 配置(自动扩缩容) - 添加 K8s ConfigMap、Secret、Namespace 配置 - 添加 .dockerignore 优化构建 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
46
.dockerignore
Normal file
46
.dockerignore
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
.next
|
||||||
|
dist
|
||||||
|
.turbo
|
||||||
|
|
||||||
|
# Development
|
||||||
|
.env.local
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
**/Dockerfile*
|
||||||
|
docker-compose*.yml
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
docs
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
**/*.test.ts
|
||||||
|
**/*.spec.ts
|
||||||
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
38
.gitea/workflows/ci.yaml
Normal file
38
.gitea/workflows/ci.yaml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
env:
|
||||||
|
PNPM_VERSION: 9
|
||||||
|
NODE_VERSION: 20
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: ${{ env.PNPM_VERSION }}
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Generate Prisma Client
|
||||||
|
run: pnpm db:generate
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build
|
||||||
101
.gitea/workflows/deploy.yaml
Normal file
101
.gitea/workflows/deploy.yaml
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
env:
|
||||||
|
PNPM_VERSION: 9
|
||||||
|
NODE_VERSION: 20
|
||||||
|
REGISTRY: gitea.tegical.world
|
||||||
|
IMAGE_PREFIX: tegical/seclusion
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
name: Build and Push Images
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
image_tag: ${{ steps.meta.outputs.tag }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Generate image tag
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
echo "tag=$(date +%Y%m%d%H%M%S)-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Login to Gitea Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and push Web image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./apps/web/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web:${{ steps.meta.outputs.tag }}
|
||||||
|
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web:latest
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
- name: Build and push API image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./apps/api/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api:${{ steps.meta.outputs.tag }}
|
||||||
|
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api:latest
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
name: Deploy to Knative
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build-and-push
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup kubectl
|
||||||
|
uses: azure/setup-kubectl@v4
|
||||||
|
|
||||||
|
- name: Configure kubeconfig
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.kube
|
||||||
|
echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config
|
||||||
|
chmod 600 ~/.kube/config
|
||||||
|
|
||||||
|
- name: Update image tags in manifests
|
||||||
|
run: |
|
||||||
|
IMAGE_TAG=${{ needs.build-and-push.outputs.image_tag }}
|
||||||
|
sed -i "s|IMAGE_TAG_PLACEHOLDER|${IMAGE_TAG}|g" deploy/k8s/*.yaml
|
||||||
|
|
||||||
|
- name: Deploy to Knative
|
||||||
|
run: |
|
||||||
|
kubectl apply -f deploy/k8s/namespace.yaml
|
||||||
|
kubectl apply -f deploy/k8s/configmap.yaml
|
||||||
|
kubectl apply -f deploy/k8s/secret.yaml
|
||||||
|
kubectl apply -f deploy/k8s/web-ksvc.yaml
|
||||||
|
kubectl apply -f deploy/k8s/api-ksvc.yaml
|
||||||
|
|
||||||
|
- name: Wait for services to be ready
|
||||||
|
run: |
|
||||||
|
kubectl wait --for=condition=Ready ksvc/seclusion-web -n seclusion --timeout=300s
|
||||||
|
kubectl wait --for=condition=Ready ksvc/seclusion-api -n seclusion --timeout=300s
|
||||||
|
|
||||||
|
- name: Get service URLs
|
||||||
|
run: |
|
||||||
|
echo "Web URL: $(kubectl get ksvc seclusion-web -n seclusion -o jsonpath='{.status.url}')"
|
||||||
|
echo "API URL: $(kubectl get ksvc seclusion-api -n seclusion -o jsonpath='{.status.url}')"
|
||||||
89
apps/api/Dockerfile
Normal file
89
apps/api/Dockerfile
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Base stage: Install dependencies
|
||||||
|
# ============================================
|
||||||
|
FROM node:20-alpine AS base
|
||||||
|
RUN corepack enable && corepack prepare pnpm@9 --activate
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Dependencies stage: Install all dependencies
|
||||||
|
# ============================================
|
||||||
|
FROM base AS deps
|
||||||
|
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
||||||
|
COPY apps/api/package.json ./apps/api/
|
||||||
|
COPY apps/api/prisma ./apps/api/prisma/
|
||||||
|
COPY packages/shared/package.json ./packages/shared/
|
||||||
|
COPY packages/eslint-config/package.json ./packages/eslint-config/
|
||||||
|
COPY packages/typescript-config/package.json ./packages/typescript-config/
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Generate Prisma Client
|
||||||
|
WORKDIR /app/apps/api
|
||||||
|
RUN pnpm db:generate
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Builder stage: Build the application
|
||||||
|
# ============================================
|
||||||
|
FROM base AS builder
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules
|
||||||
|
COPY --from=deps /app/packages ./packages
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build shared package first
|
||||||
|
WORKDIR /app/packages/shared
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
# Build API application
|
||||||
|
WORKDIR /app/apps/api
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Production dependencies stage
|
||||||
|
# ============================================
|
||||||
|
FROM base AS prod-deps
|
||||||
|
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
||||||
|
COPY apps/api/package.json ./apps/api/
|
||||||
|
COPY apps/api/prisma ./apps/api/prisma/
|
||||||
|
COPY packages/shared/package.json ./packages/shared/
|
||||||
|
COPY packages/eslint-config/package.json ./packages/eslint-config/
|
||||||
|
COPY packages/typescript-config/package.json ./packages/typescript-config/
|
||||||
|
RUN pnpm install --frozen-lockfile --prod
|
||||||
|
|
||||||
|
# Generate Prisma Client for production
|
||||||
|
WORKDIR /app/apps/api
|
||||||
|
RUN pnpm db:generate
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Runner stage: Production image
|
||||||
|
# ============================================
|
||||||
|
FROM node:20-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nestjs
|
||||||
|
|
||||||
|
# Copy production dependencies
|
||||||
|
COPY --from=prod-deps /app/node_modules ./node_modules
|
||||||
|
COPY --from=prod-deps /app/apps/api/node_modules ./apps/api/node_modules
|
||||||
|
COPY --from=prod-deps /app/packages/shared/node_modules ./packages/shared/node_modules
|
||||||
|
|
||||||
|
# Copy built application
|
||||||
|
COPY --from=builder /app/apps/api/dist ./apps/api/dist
|
||||||
|
COPY --from=builder /app/packages/shared/dist ./packages/shared/dist
|
||||||
|
COPY --from=builder /app/packages/shared/package.json ./packages/shared/
|
||||||
|
|
||||||
|
# Copy Prisma schema for migrations (optional)
|
||||||
|
COPY --from=builder /app/apps/api/prisma ./apps/api/prisma
|
||||||
|
|
||||||
|
USER nestjs
|
||||||
|
|
||||||
|
EXPOSE 4000
|
||||||
|
|
||||||
|
WORKDIR /app/apps/api
|
||||||
|
|
||||||
|
CMD ["node", "dist/main"]
|
||||||
63
apps/web/Dockerfile
Normal file
63
apps/web/Dockerfile
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Base stage: Install dependencies
|
||||||
|
# ============================================
|
||||||
|
FROM node:20-alpine AS base
|
||||||
|
RUN corepack enable && corepack prepare pnpm@9 --activate
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Dependencies stage: Install all dependencies
|
||||||
|
# ============================================
|
||||||
|
FROM base AS deps
|
||||||
|
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
||||||
|
COPY apps/web/package.json ./apps/web/
|
||||||
|
COPY packages/shared/package.json ./packages/shared/
|
||||||
|
COPY packages/eslint-config/package.json ./packages/eslint-config/
|
||||||
|
COPY packages/typescript-config/package.json ./packages/typescript-config/
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Builder stage: Build the application
|
||||||
|
# ============================================
|
||||||
|
FROM base AS builder
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY --from=deps /app/apps/web/node_modules ./apps/web/node_modules
|
||||||
|
COPY --from=deps /app/packages ./packages
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build shared package first
|
||||||
|
WORKDIR /app/packages/shared
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
# Build web application
|
||||||
|
WORKDIR /app/apps/web
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Runner stage: Production image
|
||||||
|
# ============================================
|
||||||
|
FROM node:20-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
|
# Copy standalone output
|
||||||
|
COPY --from=builder /app/apps/web/.next/standalone ./
|
||||||
|
COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static
|
||||||
|
COPY --from=builder /app/apps/web/public ./apps/web/public
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ENV PORT=3000
|
||||||
|
ENV HOSTNAME="0.0.0.0"
|
||||||
|
|
||||||
|
CMD ["node", "apps/web/server.js"]
|
||||||
270
deploy/k8s/README.md
Normal file
270
deploy/k8s/README.md
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
# Knative 部署指南
|
||||||
|
|
||||||
|
本项目使用 Knative Serving 在 Kubernetes 上部署应用。
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
.gitea/workflows/
|
||||||
|
├── ci.yaml # PR 构建检查
|
||||||
|
└── deploy.yaml # 主分支部署
|
||||||
|
|
||||||
|
apps/
|
||||||
|
├── web/Dockerfile # Web 应用 Dockerfile
|
||||||
|
└── api/Dockerfile # API 应用 Dockerfile
|
||||||
|
|
||||||
|
deploy/k8s/
|
||||||
|
├── namespace.yaml # 命名空间
|
||||||
|
├── configmap.yaml # 环境变量配置
|
||||||
|
├── secret.yaml # 敏感信息配置
|
||||||
|
├── web-ksvc.yaml # Web Knative Service
|
||||||
|
└── api-ksvc.yaml # API Knative Service
|
||||||
|
```
|
||||||
|
|
||||||
|
## 前置要求
|
||||||
|
|
||||||
|
1. **Kubernetes 集群**:需要安装 Knative Serving
|
||||||
|
2. **Gitea Container Registry**:镜像仓库地址 `gitea.tegical.world`
|
||||||
|
3. **kubectl**:本地安装 kubectl 用于手动部署
|
||||||
|
|
||||||
|
## Gitea Secrets 配置
|
||||||
|
|
||||||
|
在 Gitea 仓库设置 → Secrets 中配置:
|
||||||
|
|
||||||
|
| Secret 名称 | 说明 | 生成方式 |
|
||||||
|
|------------|------|---------|
|
||||||
|
| `REGISTRY_USERNAME` | Gitea 容器镜像仓库用户名 | - |
|
||||||
|
| `REGISTRY_PASSWORD` | Gitea 容器镜像仓库密码 | Token 或密码 |
|
||||||
|
| `KUBECONFIG` | Base64 编码的 kubeconfig | `cat ~/.kube/config \| base64` |
|
||||||
|
|
||||||
|
## 配置修改
|
||||||
|
|
||||||
|
### 1. ConfigMap (`deploy/k8s/configmap.yaml`)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
data:
|
||||||
|
# 修改为实际的 API 地址
|
||||||
|
NEXT_PUBLIC_API_URL: "https://api.example.com"
|
||||||
|
|
||||||
|
# 是否启用加密
|
||||||
|
NEXT_PUBLIC_ENABLE_ENCRYPTION: "false"
|
||||||
|
ENABLE_ENCRYPTION: "false"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Secret (`deploy/k8s/secret.yaml`)
|
||||||
|
|
||||||
|
**方式一:直接编辑文件(不推荐)**
|
||||||
|
```yaml
|
||||||
|
stringData:
|
||||||
|
DATABASE_URL: "postgresql://user:pass@host:5432/db"
|
||||||
|
REDIS_URL: "redis://host:6379"
|
||||||
|
JWT_SECRET: "your-secret-here"
|
||||||
|
# ... 其他配置
|
||||||
|
```
|
||||||
|
|
||||||
|
**方式二:使用 kubectl 创建(推荐)**
|
||||||
|
```bash
|
||||||
|
kubectl create secret generic seclusion-secret -n seclusion \
|
||||||
|
--from-literal=DATABASE_URL='postgresql://user:pass@host:5432/db' \
|
||||||
|
--from-literal=REDIS_URL='redis://host:6379' \
|
||||||
|
--from-literal=JWT_SECRET='your-jwt-secret' \
|
||||||
|
--from-literal=JWT_EXPIRES_IN='7d' \
|
||||||
|
--from-literal=JWT_REFRESH_SECRET='your-refresh-secret' \
|
||||||
|
--from-literal=JWT_REFRESH_EXPIRES_IN='30d' \
|
||||||
|
--from-literal=ENCRYPTION_KEY='' \
|
||||||
|
--from-literal=NEXT_PUBLIC_ENCRYPTION_KEY=''
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Knative Service 配置
|
||||||
|
|
||||||
|
**自动扩缩容配置** (`deploy/k8s/web-ksvc.yaml` 和 `deploy/k8s/api-ksvc.yaml`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
# 最小实例数(0 允许缩容到零)
|
||||||
|
autoscaling.knative.dev/min-scale: "1"
|
||||||
|
# 最大实例数
|
||||||
|
autoscaling.knative.dev/max-scale: "10"
|
||||||
|
# 每个实例的目标并发请求数
|
||||||
|
autoscaling.knative.dev/target: "100"
|
||||||
|
```
|
||||||
|
|
||||||
|
**资源限制**:
|
||||||
|
```yaml
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
limits:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 部署流程
|
||||||
|
|
||||||
|
### 自动部署(推荐)
|
||||||
|
|
||||||
|
1. **创建 PR** → 触发 CI workflow 进行构建检查
|
||||||
|
2. **合并到 main** → 自动构建镜像并部署到 Knative
|
||||||
|
|
||||||
|
### 手动部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 构建镜像
|
||||||
|
docker build -f apps/web/Dockerfile -t gitea.tegical.world/tegical/seclusion-web:latest .
|
||||||
|
docker build -f apps/api/Dockerfile -t gitea.tegical.world/tegical/seclusion-api:latest .
|
||||||
|
|
||||||
|
# 2. 推送镜像
|
||||||
|
docker push gitea.tegical.world/tegical/seclusion-web:latest
|
||||||
|
docker push gitea.tegical.world/tegical/seclusion-api:latest
|
||||||
|
|
||||||
|
# 3. 部署到 Knative
|
||||||
|
kubectl apply -f deploy/k8s/namespace.yaml
|
||||||
|
kubectl apply -f deploy/k8s/configmap.yaml
|
||||||
|
kubectl apply -f deploy/k8s/secret.yaml
|
||||||
|
kubectl apply -f deploy/k8s/web-ksvc.yaml
|
||||||
|
kubectl apply -f deploy/k8s/api-ksvc.yaml
|
||||||
|
|
||||||
|
# 4. 等待服务就绪
|
||||||
|
kubectl wait --for=condition=Ready ksvc/seclusion-web -n seclusion --timeout=300s
|
||||||
|
kubectl wait --for=condition=Ready ksvc/seclusion-api -n seclusion --timeout=300s
|
||||||
|
|
||||||
|
# 5. 查看服务 URL
|
||||||
|
kubectl get ksvc -n seclusion
|
||||||
|
```
|
||||||
|
|
||||||
|
## 查看部署状态
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看 Knative Service 状态
|
||||||
|
kubectl get ksvc -n seclusion
|
||||||
|
|
||||||
|
# 查看 Pod 状态
|
||||||
|
kubectl get pods -n seclusion
|
||||||
|
|
||||||
|
# 查看服务日志
|
||||||
|
kubectl logs -n seclusion -l serving.knative.dev/service=seclusion-web
|
||||||
|
kubectl logs -n seclusion -l serving.knative.dev/service=seclusion-api
|
||||||
|
|
||||||
|
# 查看服务详情
|
||||||
|
kubectl describe ksvc seclusion-web -n seclusion
|
||||||
|
kubectl describe ksvc seclusion-api -n seclusion
|
||||||
|
```
|
||||||
|
|
||||||
|
## 域名配置
|
||||||
|
|
||||||
|
Knative 会自动为每个 Service 分配一个 URL,格式通常为:
|
||||||
|
- `http://seclusion-web.seclusion.{knative-domain}`
|
||||||
|
- `http://seclusion-api.seclusion.{knative-domain}`
|
||||||
|
|
||||||
|
### 配置自定义域名
|
||||||
|
|
||||||
|
创建 `deploy/k8s/domain-mapping.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: serving.knative.dev/v1alpha1
|
||||||
|
kind: DomainMapping
|
||||||
|
metadata:
|
||||||
|
name: seclusion.example.com
|
||||||
|
namespace: seclusion
|
||||||
|
spec:
|
||||||
|
ref:
|
||||||
|
name: seclusion-web
|
||||||
|
kind: Service
|
||||||
|
apiVersion: serving.knative.dev/v1
|
||||||
|
---
|
||||||
|
apiVersion: serving.knative.dev/v1alpha1
|
||||||
|
kind: DomainMapping
|
||||||
|
metadata:
|
||||||
|
name: api.seclusion.example.com
|
||||||
|
namespace: seclusion
|
||||||
|
spec:
|
||||||
|
ref:
|
||||||
|
name: seclusion-api
|
||||||
|
kind: Service
|
||||||
|
apiVersion: serving.knative.dev/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
应用配置:
|
||||||
|
```bash
|
||||||
|
kubectl apply -f deploy/k8s/domain-mapping.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 故障排查
|
||||||
|
|
||||||
|
### 镜像拉取失败
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查镜像仓库凭证
|
||||||
|
kubectl get secret -n seclusion
|
||||||
|
|
||||||
|
# 如需要私有镜像,创建 pull secret
|
||||||
|
kubectl create secret docker-registry gitea-registry \
|
||||||
|
--docker-server=gitea.tegical.world \
|
||||||
|
--docker-username=<username> \
|
||||||
|
--docker-password=<password> \
|
||||||
|
-n seclusion
|
||||||
|
|
||||||
|
# 在 Knative Service 中引用
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: gitea-registry
|
||||||
|
```
|
||||||
|
|
||||||
|
### 服务无法就绪
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看事件
|
||||||
|
kubectl get events -n seclusion --sort-by='.lastTimestamp'
|
||||||
|
|
||||||
|
# 查看 Revision 状态
|
||||||
|
kubectl get revision -n seclusion
|
||||||
|
|
||||||
|
# 查看详细日志
|
||||||
|
kubectl logs -n seclusion -l serving.knative.dev/service=seclusion-web --tail=100
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据库连接失败
|
||||||
|
|
||||||
|
确认 `DATABASE_URL` 和 `REDIS_URL` 配置正确,且集群内可访问数据库。
|
||||||
|
|
||||||
|
## 回滚
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看历史 Revision
|
||||||
|
kubectl get revision -n seclusion
|
||||||
|
|
||||||
|
# 回滚到指定 Revision
|
||||||
|
kubectl patch ksvc seclusion-web -n seclusion --type='json' \
|
||||||
|
-p='[{"op": "replace", "path": "/spec/traffic", "value": [{"revisionName": "seclusion-web-00001", "percent": 100}]}]'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
|
||||||
|
### 1. 调整并发限制
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
# 0 表示无限制,建议设置合理值
|
||||||
|
containerConcurrency: 100
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 调整扩缩容策略
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
# 缩容窗口期(秒)
|
||||||
|
autoscaling.knative.dev/scale-down-delay: "30s"
|
||||||
|
# 稳定窗口期(秒)
|
||||||
|
autoscaling.knative.dev/window: "60s"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 启用缓存优化
|
||||||
|
|
||||||
|
在 Dockerfile 中已使用多阶段构建和 BuildKit 缓存优化构建速度。
|
||||||
42
deploy/k8s/api-ksvc.yaml
Normal file
42
deploy/k8s/api-ksvc.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
apiVersion: serving.knative.dev/v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: seclusion-api
|
||||||
|
namespace: seclusion
|
||||||
|
labels:
|
||||||
|
app: seclusion-api
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
# 最小实例数(API 服务建议保持至少 1 个实例)
|
||||||
|
autoscaling.knative.dev/min-scale: "1"
|
||||||
|
# 最大实例数
|
||||||
|
autoscaling.knative.dev/max-scale: "10"
|
||||||
|
# 每个实例的并发请求数
|
||||||
|
autoscaling.knative.dev/target: "100"
|
||||||
|
spec:
|
||||||
|
containerConcurrency: 0
|
||||||
|
containers:
|
||||||
|
- name: api
|
||||||
|
image: gitea.tegical.world/tegical/seclusion-api:IMAGE_TAG_PLACEHOLDER
|
||||||
|
ports:
|
||||||
|
- containerPort: 4000
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: seclusion-config
|
||||||
|
- secretRef:
|
||||||
|
name: seclusion-secret
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
limits:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /api/health
|
||||||
|
port: 4000
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
14
deploy/k8s/configmap.yaml
Normal file
14
deploy/k8s/configmap.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: seclusion-config
|
||||||
|
namespace: seclusion
|
||||||
|
data:
|
||||||
|
# Web 配置
|
||||||
|
NEXT_PUBLIC_API_URL: "https://api.example.com"
|
||||||
|
NEXT_PUBLIC_ENABLE_ENCRYPTION: "false"
|
||||||
|
|
||||||
|
# API 配置
|
||||||
|
NODE_ENV: "production"
|
||||||
|
PORT: "4000"
|
||||||
|
ENABLE_ENCRYPTION: "false"
|
||||||
6
deploy/k8s/namespace.yaml
Normal file
6
deploy/k8s/namespace.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: seclusion
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: seclusion
|
||||||
27
deploy/k8s/secret.yaml
Normal file
27
deploy/k8s/secret.yaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 注意:此文件仅作为模板,实际部署时需要手动创建 Secret 或使用外部密钥管理
|
||||||
|
# kubectl create secret generic seclusion-secret -n seclusion \
|
||||||
|
# --from-literal=DATABASE_URL='postgresql://...' \
|
||||||
|
# --from-literal=REDIS_URL='redis://...' \
|
||||||
|
# --from-literal=JWT_SECRET='your-jwt-secret' \
|
||||||
|
# --from-literal=ENCRYPTION_KEY='your-encryption-key'
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: seclusion-secret
|
||||||
|
namespace: seclusion
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
# 数据库连接
|
||||||
|
DATABASE_URL: "postgresql://user:password@postgres:5432/seclusion"
|
||||||
|
REDIS_URL: "redis://redis:6379"
|
||||||
|
|
||||||
|
# JWT 配置
|
||||||
|
JWT_SECRET: "change-me-in-production"
|
||||||
|
JWT_EXPIRES_IN: "7d"
|
||||||
|
JWT_REFRESH_SECRET: "change-me-in-production-refresh"
|
||||||
|
JWT_REFRESH_EXPIRES_IN: "30d"
|
||||||
|
|
||||||
|
# 加密密钥(如启用加密)
|
||||||
|
ENCRYPTION_KEY: ""
|
||||||
|
NEXT_PUBLIC_ENCRYPTION_KEY: ""
|
||||||
42
deploy/k8s/web-ksvc.yaml
Normal file
42
deploy/k8s/web-ksvc.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
apiVersion: serving.knative.dev/v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: seclusion-web
|
||||||
|
namespace: seclusion
|
||||||
|
labels:
|
||||||
|
app: seclusion-web
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
# 最小实例数(设为 1 避免冷启动,设为 0 允许缩容到零)
|
||||||
|
autoscaling.knative.dev/min-scale: "1"
|
||||||
|
# 最大实例数
|
||||||
|
autoscaling.knative.dev/max-scale: "10"
|
||||||
|
# 每个实例的并发请求数
|
||||||
|
autoscaling.knative.dev/target: "100"
|
||||||
|
spec:
|
||||||
|
containerConcurrency: 0
|
||||||
|
containers:
|
||||||
|
- name: web
|
||||||
|
image: gitea.tegical.world/tegical/seclusion-web:IMAGE_TAG_PLACEHOLDER
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: seclusion-config
|
||||||
|
- secretRef:
|
||||||
|
name: seclusion-secret
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
limits:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 3000
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
Reference in New Issue
Block a user