# skillhub 部署架构与运维 ## 1 K8s 部署拓扑 ``` ┌─────────────┐ │ Ingress │ │ (Nginx) │ └──────┬──────┘ │ ┌────────────┴────────────┐ │ /api/* │ /* ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ │ Spring Boot │ │ Nginx / CDN │ │ replicas: 2+ │ │ 静态资源 │ └────────┬─────────┘ └──────────────────┘ │ ┌────────┴──────────────────────┐ │ │ │ ▼ ▼ ▼ ┌────────┐ ┌────────┐ ┌──────────────┐ │ PostgreSQL│ │ Redis │ │ S3 / MinIO │ │ (主从) │ │ │ │ │ └────────┘ └────────┘ └──────────────┘ ``` ## 2 服务配置 - 无状态设计,所有状态存储在 PostgreSQL / Redis / S3 - 健康检查:`/actuator/health`(liveness + readiness 分离) - 优雅停机:`spring.lifecycle.timeout-per-shutdown-phase=30s` - JVM:`-XX:MaxRAMPercentage=75.0` ## 3 环境 Profile | Profile | 用途 | 特点 | |---------|------|------| | `local` | 本地开发 | Docker Compose 一键启动(PostgreSQL/Redis/MinIO),Mock OAuth(见下方说明) | | `dev` | 开发环境 | 共享基础设施,GitHub OAuth 测试应用 | | `staging` | 预发布 | 与生产同构 | | `prod` | 生产 | 多 Pod,完整基础设施 | ### 本地开发 Mock 登录 `local` profile 下提供两种开发登录方式: 1. **MockAuthFilter**(默认):通过 `X-Mock-User-Id` Header 模拟登录,自动创建 Session,无需真实 OAuth 流程 2. **GitHub OAuth 测试应用**:配置 `OAUTH2_GITHUB_CLIENT_ID` / `OAUTH2_GITHUB_CLIENT_SECRET` 后可走真实 OAuth 流程(GitHub 支持 `http://localhost` 回调) MockAuthFilter 仅在 `local` profile 激活,通过 `@Profile("local")` 注解保证不会泄漏到其他环境。 ### Docker Compose 一键启动 项目提供两套 Docker Compose 配置,分别用于本地开发和完整部署。 #### docker-compose.yml — 本地开发(仅依赖服务) 本地开发时前后端在宿主机运行,Docker Compose 只拉起依赖服务: ```yaml # docker-compose.yml(项目根目录) services: postgres: image: postgres:16-alpine ports: - "5432:5432" environment: POSTGRES_DB: skillhub POSTGRES_USER: skillhub POSTGRES_PASSWORD: skillhub_dev volumes: - postgres_data:/var/lib/postgresql/data redis: image: redis:7-alpine ports: - "6379:6379" minio: image: minio/minio:latest ports: - "9000:9000" - "9001:9001" # MinIO Console environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin command: server /data --console-address ":9001" volumes: - minio_data:/data volumes: postgres_data: minio_data: ``` #### docker-compose.prod.yml — 完整部署(前后端 + 依赖服务) 开发完成后,通过 `docker compose -f docker-compose.prod.yml up -d` 一键打包并启动整个系统: ```yaml # docker-compose.prod.yml(项目根目录) services: postgres: image: postgres:16-alpine ports: - "5432:5432" environment: POSTGRES_DB: skillhub POSTGRES_USER: skillhub POSTGRES_PASSWORD: ${DB_PASSWORD:-skillhub_prod} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U skillhub"] interval: 5s timeout: 5s retries: 5 redis: image: redis:7-alpine ports: - "6379:6379" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 5s retries: 5 minio: image: minio/minio:latest ports: - "9000:9000" - "9001:9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin} command: server /data --console-address ":9001" volumes: - minio_data:/data healthcheck: test: ["CMD", "mc", "ready", "local"] interval: 5s timeout: 5s retries: 5 server: build: context: ./server dockerfile: Dockerfile ports: - "8080:8080" environment: SPRING_PROFILES_ACTIVE: prod DATABASE_URL: jdbc:postgresql://postgres:5432/skillhub DATABASE_USERNAME: skillhub DATABASE_PASSWORD: ${DB_PASSWORD:-skillhub_prod} REDIS_HOST: redis REDIS_PORT: 6379 S3_ENDPOINT: http://minio:9000 S3_ACCESS_KEY: ${MINIO_ROOT_USER:-minioadmin} S3_SECRET_KEY: ${MINIO_ROOT_PASSWORD:-minioadmin} S3_BUCKET: skillhub OAUTH2_GITHUB_CLIENT_ID: ${OAUTH2_GITHUB_CLIENT_ID} OAUTH2_GITHUB_CLIENT_SECRET: ${OAUTH2_GITHUB_CLIENT_SECRET} depends_on: postgres: condition: service_healthy redis: condition: service_healthy minio: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"] interval: 10s timeout: 5s retries: 10 start_period: 30s web: build: context: ./web dockerfile: Dockerfile ports: - "80:80" depends_on: server: condition: service_healthy volumes: postgres_data: minio_data: ``` #### 前后端 Dockerfile 后端 Dockerfile(`server/Dockerfile`): ```dockerfile FROM maven:3.9-eclipse-temurin-21 AS build WORKDIR /app COPY pom.xml . COPY skillhub-app/pom.xml skillhub-app/ COPY skillhub-domain/pom.xml skillhub-domain/ COPY skillhub-auth/pom.xml skillhub-auth/ COPY skillhub-search/pom.xml skillhub-search/ COPY skillhub-storage/pom.xml skillhub-storage/ COPY skillhub-infra/pom.xml skillhub-infra/ RUN mvn dependency:go-offline -B COPY . . RUN mvn package -DskipTests -B FROM eclipse-temurin:21-jre-alpine WORKDIR /app COPY --from=build /app/skillhub-app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"] ``` 前端 Dockerfile(`web/Dockerfile`): ```dockerfile FROM node:20-alpine AS build WORKDIR /app RUN corepack enable COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile COPY . . RUN pnpm build FROM nginx:alpine COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 ``` 前端 Nginx 配置(`web/nginx.conf`): ```nginx server { listen 80; root /usr/share/nginx/html; index index.html; # SPA 路由回退 location / { try_files $uri $uri/ /index.html; } # API 反向代理到后端 location /api/ { proxy_pass http://server:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # OAuth2 回调反向代理 location /oauth2/ { proxy_pass http://server:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /login/oauth2/ { proxy_pass http://server:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # Well-known 发现端点 location /.well-known/ { proxy_pass http://server:8080; proxy_set_header Host $host; } } ``` ### Spring Boot 配置文件分层 ``` server/skillhub-app/src/main/resources/ ├── application.yml # 公共配置(所有 profile 共享) ├── application-local.yml # 本地开发(Docker Compose 服务地址) ├── application-dev.yml # 开发环境 ├── application-staging.yml # 预发布 └── application-prod.yml # 生产 ``` `application.yml`(公共配置): ```yaml spring: application: name: skillhub jpa: open-in-view: false hibernate: ddl-auto: validate # 由 Flyway 管理 schema,Hibernate 仅校验 properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect flyway: enabled: true locations: classpath:db/migration server: shutdown: graceful spring.lifecycle.timeout-per-shutdown-phase: 30s ``` `application-local.yml`(本地开发,对应 Docker Compose): ```yaml spring: datasource: url: jdbc:postgresql://localhost:5432/skillhub username: skillhub password: skillhub_dev data: redis: host: localhost port: 6379 jpa: show-sql: true skillhub: storage: type: s3 endpoint: http://localhost:9000 access-key: minioadmin secret-key: minioadmin bucket: skillhub region: us-east-1 access-policy: mode: OPEN # 本地开发默认开放准入 ``` `application-prod.yml`(生产环境,凭证从环境变量/K8s Secret 注入): ```yaml spring: datasource: url: ${DATABASE_URL} username: ${DATABASE_USERNAME} password: ${DATABASE_PASSWORD} data: redis: host: ${REDIS_HOST} port: ${REDIS_PORT:6379} jpa: show-sql: false skillhub: storage: type: s3 endpoint: ${S3_ENDPOINT} access-key: ${S3_ACCESS_KEY} secret-key: ${S3_SECRET_KEY} bucket: ${S3_BUCKET:skillhub} region: ${S3_REGION:us-east-1} ``` ### 本地开发启动流程 ```bash # 1. 启动依赖服务 docker compose up -d # 2. 启动后端(自动执行 Flyway 迁移) cd server && ./mvnw spring-boot:run -Dspring-boot.run.profiles=local # 3. 启动前端 cd web && pnpm dev ``` ### 完整部署(一键打包 + 启动) ```bash # 构建并启动所有服务(前后端 + 依赖) docker compose -f docker-compose.prod.yml up -d --build # 仅重新构建并重启应用服务(依赖服务不重启) docker compose -f docker-compose.prod.yml up -d --build server web # 停止所有服务 docker compose -f docker-compose.prod.yml down # 停止并清除数据卷(慎用) docker compose -f docker-compose.prod.yml down -v ``` ### Makefile 命令 ```bash make dev # docker compose up -d + 后端 + 前端(本地开发) make dev-down # docker compose down make build # 构建后端 JAR + 前端 dist make docker # 构建前后端 Docker 镜像 make deploy # docker compose -f docker-compose.prod.yml up -d --build make deploy-down # docker compose -f docker-compose.prod.yml down make generate-api # 生成 OpenAPI 类型 ``` ## 4 配置管理 - 敏感配置:K8s Secret(数据库/Redis/S3 凭证、OAuth2 Client ID/Secret) - 非敏感配置:K8s ConfigMap(文件大小限制、Session TTL 等) ## 5 可观测性 | 维度 | 方案 | |------|------| | 日志 | JSON 格式 stdout,包含 traceId/requestId | | 指标 | Actuator + Micrometer → Prometheus | | 链路追踪 | 一期 requestId 透传,后续接 Jaeger/Zipkin | | 告警 | 基于 Prometheus(5xx 率、延迟 P99、Pod 重启) | requestId 透传:Ingress 注入 → Spring Filter 读取放入 MDC → 日志自动携带 → 响应 Header 回传。 ## 6 构建与发布 ### CI Pipeline 构建 ``` 代码提交 → CI Pipeline ├── server: mvn package → JAR └── web: pnpm build → dist/ │ ▼ Docker 多阶段构建 ├── server → eclipse-temurin:21-jre-alpine └── web → nginx:alpine │ ▼ 推送镜像 → K8s 滚动更新 ``` ### Docker Compose 完整部署 ``` make deploy │ ▼ docker compose -f docker-compose.prod.yml up -d --build │ ├── 构建 server 镜像(Maven 多阶段构建 → JRE 运行) ├── 构建 web 镜像(pnpm build → Nginx 静态服务 + 反向代理) ├── 拉起 PostgreSQL / Redis / MinIO ├── 等待依赖服务健康检查通过 ├── 启动 server(自动执行 Flyway 迁移) └── 启动 web(Nginx 代理 API 到 server) ``` Makefile 顶层命令:`make dev`, `make dev-down`, `make build`, `make docker`, `make deploy`, `make deploy-down`, `make generate-api` ## 7 数据库迁移 Flyway 管理 schema 变更: - 脚本路径:`server/skillhub-app/src/main/resources/db/migration/` - 命名:`V{version}__{description}.sql` - 多 Pod 安全:Flyway 自带数据库锁