feat(infra): p6 hardening - observability and deploy compose

- 5 NestJS services add /metrics endpoint via app.getHttpAdapter()
- prometheus.yml scales to 8 services with rule_files and alertmanager
- monitoring compose replaces blackbox with Loki+Promtail
- Grafana datasource adds Loki
- docker-compose.deploy.yml scales to 11 services
- deploy.env.example completes Neo4j/ES/ClickHouse/LLM vars
- teacher-bff adds health.controller
- CI removes continue-on-error on lint step
- teacher-portal lint script changed to eslint src
This commit is contained in:
SpecialX
2026-07-09 10:21:06 +08:00
parent 3ca654619f
commit 566060fade
21 changed files with 554 additions and 59 deletions

View File

@@ -18,6 +18,9 @@
name: edu
services:
# ============================================================
# 应用服务10 个api-gateway + 3 Go/Python + 6 NestJS
# ============================================================
api-gateway:
build:
context: ./repo/services/api-gateway
@@ -34,6 +37,11 @@ services:
CLASSES_SERVICE_URL: http://classes:3001
IAM_SERVICE_URL: http://iam:3002
TEACHER_BFF_URL: http://teacher-bff:3003
CORE_EDU_SERVICE_URL: http://core-edu:3004
CONTENT_SERVICE_URL: http://content:3005
DATA_ANA_SERVICE_URL: http://data-ana:3006
MSG_SERVICE_URL: http://msg:3007
AI_SERVICE_URL: http://ai:3008
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
LOG_LEVEL: ${LOG_LEVEL:-info}
ports:
@@ -59,7 +67,6 @@ services:
restart: unless-stopped
environment:
PORT: 3001
# 连接服务器已有的 MySQL容器名 edu-mysql需在 edu-shared 网络)
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL}
KAFKA_BROKERS: ${KAFKA_BROKERS:-}
@@ -75,6 +82,214 @@ services:
- edu-net
- edu-shared
iam:
build:
context: ./repo
dockerfile: services/iam/Dockerfile
container_name: edu-iam
restart: unless-stopped
environment:
PORT: 3002
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL}
JWT_SECRET: ${JWT_SECRET}
JWT_ISSUER: ${JWT_ISSUER:-next-edu-cloud}
JWT_AUDIENCE: ${JWT_AUDIENCE:-next-edu-cloud}
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
LOG_LEVEL: ${LOG_LEVEL:-info}
NODE_ENV: production
healthcheck:
test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3002/healthz"]
interval: 30s
timeout: 5s
start_period: 30s
retries: 5
networks:
- edu-net
- edu-shared
teacher-bff:
build:
context: ./repo
dockerfile: services/teacher-bff/Dockerfile
container_name: edu-teacher-bff
restart: unless-stopped
environment:
PORT: 3003
IAM_SERVICE_URL: http://iam:3002
CLASSES_SERVICE_URL: http://classes:3001
CORE_EDU_SERVICE_URL: http://core-edu:3004
LOG_LEVEL: ${LOG_LEVEL:-info}
NODE_ENV: production
depends_on:
iam:
condition: service_healthy
classes:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3003/healthz"]
interval: 30s
timeout: 5s
start_period: 20s
retries: 3
networks:
- edu-net
- edu-shared
core-edu:
build:
context: ./repo
dockerfile: services/core-edu/Dockerfile
container_name: edu-core-edu
restart: unless-stopped
environment:
PORT: 3004
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL}
KAFKA_BROKERS: ${KAFKA_BROKERS:-}
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
LOG_LEVEL: ${LOG_LEVEL:-info}
NODE_ENV: production
DEV_MODE: "false"
healthcheck:
test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3004/healthz"]
interval: 30s
timeout: 5s
start_period: 30s
retries: 5
networks:
- edu-net
- edu-shared
content:
build:
context: ./repo
dockerfile: services/content/Dockerfile
container_name: edu-content
restart: unless-stopped
environment:
PORT: 3005
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL}
NEO4J_URL: ${NEO4J_URL:-}
NEO4J_PASSWORD: ${NEO4J_PASSWORD:-}
ES_URL: ${ES_URL:-}
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
LOG_LEVEL: ${LOG_LEVEL:-info}
NODE_ENV: production
DEV_MODE: "false"
healthcheck:
test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3005/healthz"]
interval: 30s
timeout: 5s
start_period: 30s
retries: 5
networks:
- edu-net
- edu-shared
msg:
build:
context: ./repo
dockerfile: services/msg/Dockerfile
container_name: edu-msg
restart: unless-stopped
environment:
PORT: 3007
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL}
KAFKA_BROKERS: ${KAFKA_BROKERS:-}
ES_URL: ${ES_URL:-}
PUSH_GATEWAY_URL: http://push-gateway:8081
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
LOG_LEVEL: ${LOG_LEVEL:-info}
NODE_ENV: production
DEV_MODE: "false"
depends_on:
push-gateway:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3007/healthz"]
interval: 30s
timeout: 5s
start_period: 30s
retries: 5
networks:
- edu-net
- edu-shared
ai:
build:
context: ./repo/services/ai
dockerfile: Dockerfile
container_name: edu-ai
restart: unless-stopped
environment:
PORT: 3008
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.openai.com/v1}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
OTEL_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
LOG_LEVEL: ${LOG_LEVEL:-info}
DEV_MODE: "false"
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:3008/healthz')"]
interval: 30s
timeout: 5s
start_period: 20s
retries: 3
networks:
- edu-net
- edu-shared
data-ana:
build:
context: ./repo/services/data-ana
dockerfile: Dockerfile
container_name: edu-data-ana
restart: unless-stopped
environment:
PORT: 3006
CLICKHOUSE_HOST: ${CLICKHOUSE_HOST:-}
CLICKHOUSE_PORT: ${CLICKHOUSE_PORT:-8123}
CLICKHOUSE_DATABASE: ${CLICKHOUSE_DATABASE:-edu_analytics}
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-}
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-}
OTEL_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://otel-collector:4318}
LOG_LEVEL: ${LOG_LEVEL:-info}
DEV_MODE: "false"
KAFKA_BROKERS: ${KAFKA_BROKERS:-}
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:3006/healthz')"]
interval: 30s
timeout: 5s
start_period: 20s
retries: 3
networks:
- edu-net
- edu-shared
push-gateway:
build:
context: ./repo/services/push-gateway
dockerfile: Dockerfile
container_name: edu-push-gateway
restart: unless-stopped
environment:
PUSH_GATEWAY_PORT: 8081
JWT_SECRET: ${JWT_SECRET}
REDIS_URL: ${REDIS_URL}
DEV_MODE: "false"
healthcheck:
test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:8081/healthz"]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
networks:
- edu-net
- edu-shared
teacher-portal:
build:
context: ./repo