1. teacher-portal 登录 JSON 解析错误修复
- 根因:next.config.js rewrites /api/auth/* → gateway /api/auth/*,
但 gateway 只在 /api/v1 路由组下注册代理,/api/auth/* 返回 404 纯文本
- 修复:rewrites 映射 /api/auth/* → /api/v1/iam/*(对齐 iam controller @Controller("v1/iam"))
2. msg / data-ana 端口冲突修复
- 根因:msg env.ts PORT 默认 3006,与 data-ana(config.py http_port=3006)冲突
- 修复:msg PORT 默认 3006→3007(对齐端口分配表 msg=3007, data-ana=3006)
3. Prometheus classes-service 3001 DOWN 修复
- 根因:classes 已合并入 core-edu(P3 裁决 C1),但 prometheus.yml 仍有 classes-service scrape 配置
- 修复:从 prometheus.yml 删除 classes-service job
4. iam outbox 表 aggregate_id 列长度修复
- 根因:iam_outbox.aggregate_id VARCHAR(32),但 UUID 是 36 字符,导致 "Data too long" 错误
- 修复:DDL 中 VARCHAR(32) → VARCHAR(36);ALTER TABLE 修复现有数据库
5. 补充未搭建的监控基础设施
- docker-compose.yml 新增 Alertmanager(9093)/ Loki(3100)/ Promtail(observability profile)
- prometheus.yml alertmanager target 更新为 edu-alertmanager:9093
验证:
- TS typecheck 19 项目全部通过
- 登录链路 teacher-portal → gateway → iam 验证通过
- Prometheus targets 不再有 classes-service
- 3 个新基础设施容器(alertmanager/loki/promtail)启动成功
AI identity: trae-main(基础设施修复会话)
305 lines
10 KiB
YAML
305 lines
10 KiB
YAML
name: edu-full
|
||
services:
|
||
mysql:
|
||
# 镜像加速:通过 daocloud 镜像源绕过 docker.io 被墙问题
|
||
image: docker.m.daocloud.io/library/mysql:8.0
|
||
container_name: edu-mysql
|
||
restart: unless-stopped
|
||
environment:
|
||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-changeme}
|
||
MYSQL_DATABASE: ${MYSQL_DATABASE:-next_edu_cloud}
|
||
MYSQL_USER: ${MYSQL_USER:-edu}
|
||
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-changeme}
|
||
ports:
|
||
- "3306:3306"
|
||
volumes:
|
||
- mysql_data:/var/lib/mysql
|
||
- ./init-sql:/docker-entrypoint-initdb.d:ro
|
||
healthcheck:
|
||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 5
|
||
redis:
|
||
image: docker.m.daocloud.io/library/redis:7-alpine
|
||
container_name: edu-redis
|
||
restart: unless-stopped
|
||
ports:
|
||
- "6379:6379"
|
||
volumes:
|
||
- redis_data:/data
|
||
healthcheck:
|
||
test: ["CMD", "redis-cli", "ping"]
|
||
interval: 10s
|
||
timeout: 3s
|
||
retries: 5
|
||
kafka:
|
||
image: docker.m.daocloud.io/confluentinc/cp-kafka:7.6.0
|
||
container_name: edu-kafka
|
||
profiles: ["p3", "p4", "p5", "p6"]
|
||
restart: unless-stopped
|
||
depends_on:
|
||
zookeeper:
|
||
condition: service_healthy
|
||
environment:
|
||
KAFKA_BROKER_ID: 1
|
||
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
||
# 双 listener:INSIDE 容器间互访(kafka:29092),OUTSIDE 主机访问(localhost:9092)
|
||
# 必须用 INSIDE 作为 inter.broker.listener.name,否则 Debezium Connect 拿到 metadata
|
||
# 后会切回 advertised.listeners 中的 localhost,导致连接失败
|
||
KAFKA_LISTENERS: INSIDE://:29092,OUTSIDE://:9092
|
||
KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:29092,OUTSIDE://localhost:9092
|
||
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
|
||
KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE
|
||
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
||
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
|
||
ports:
|
||
- "9092:9092"
|
||
healthcheck:
|
||
test: ["CMD", "kafka-topics", "--bootstrap-server", "localhost:9092", "--list"]
|
||
interval: 15s
|
||
timeout: 10s
|
||
retries: 5
|
||
zookeeper:
|
||
image: docker.m.daocloud.io/confluentinc/cp-zookeeper:7.6.0
|
||
container_name: edu-zookeeper
|
||
profiles: ["p3", "p4", "p5", "p6"]
|
||
restart: unless-stopped
|
||
environment:
|
||
ZOOKEEPER_CLIENT_PORT: 2181
|
||
healthcheck:
|
||
test: ["CMD", "nc", "-z", "localhost", "2181"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 5
|
||
clickhouse:
|
||
image: docker.m.daocloud.io/clickhouse/clickhouse-server:24.3
|
||
container_name: edu-clickhouse
|
||
profiles: ["p4", "p5", "p6"]
|
||
restart: unless-stopped
|
||
ports:
|
||
- "8123:8123"
|
||
- "9000:9000"
|
||
volumes:
|
||
- clickhouse_data:/var/lib/clickhouse
|
||
# 覆盖默认 default-user.xml 限制(默认仅允许 127.0.0.1/::1 无密码访问)
|
||
- ./clickhouse/users.d/custom-users.xml:/etc/clickhouse-server/users.d/custom-users.xml:ro
|
||
healthcheck:
|
||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8123/ping"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 5
|
||
neo4j:
|
||
image: docker.m.daocloud.io/library/neo4j:5.20
|
||
container_name: edu-neo4j
|
||
profiles: ["p4", "p5", "p6"]
|
||
restart: unless-stopped
|
||
environment:
|
||
NEO4J_AUTH: neo4j/${NEO4J_PASSWORD:-changeme}
|
||
ports:
|
||
- "7474:7474"
|
||
- "7687:7687"
|
||
volumes:
|
||
- neo4j_data:/data
|
||
healthcheck:
|
||
test: ["CMD", "cypher-shell", "-u", "neo4j", "-p", "${NEO4J_PASSWORD:-changeme}", "RETURN 1"]
|
||
interval: 15s
|
||
timeout: 10s
|
||
retries: 5
|
||
elasticsearch:
|
||
# Elastic 官方镜像在 docker.elastic.co,非 Docker Hub,通常不被墙
|
||
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
|
||
container_name: edu-es
|
||
profiles: ["p5", "p6"]
|
||
restart: unless-stopped
|
||
environment:
|
||
discovery.type: single-node
|
||
xpack.security.enabled: "false"
|
||
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
|
||
ports:
|
||
- "9200:9200"
|
||
volumes:
|
||
- es_data:/usr/share/elasticsearch/data
|
||
healthcheck:
|
||
test: ["CMD", "curl", "-f", "http://localhost:9200/_cluster/health"]
|
||
interval: 15s
|
||
timeout: 10s
|
||
retries: 5
|
||
jaeger:
|
||
image: docker.m.daocloud.io/jaegertracing/all-in-one:1.57
|
||
container_name: edu-jaeger
|
||
profiles: ["observability"]
|
||
restart: unless-stopped
|
||
environment:
|
||
COLLECTOR_OTLP_ENABLED: "true"
|
||
ports:
|
||
- "16686:16686"
|
||
- "4318:4318"
|
||
# ============================================================
|
||
# Debezium Connect - CDC 链路核心
|
||
# 监听 MySQL binlog → 写入 Kafka topic
|
||
# topic 命名约定:<prefix>.<database>.<table>(如 edu-cdc.next_edu_cloud.grades)
|
||
# ============================================================
|
||
debezium-connect:
|
||
image: quay.io/debezium/connect:2.7
|
||
container_name: edu-debezium
|
||
profiles: ["p4", "p5", "p6"]
|
||
restart: unless-stopped
|
||
depends_on:
|
||
kafka:
|
||
condition: service_healthy
|
||
environment:
|
||
# Kafka Connect 基础配置(Debezium 2.x 容器映射规则:环境变量名大写 → connect 配置项)
|
||
# 必须用 INSIDE listener (kafka:29092),否则会拿到 OUTSIDE 的 localhost metadata 导致连不上
|
||
BOOTSTRAP_SERVERS: kafka:29092
|
||
GROUP_ID: edu-debezium
|
||
CONFIG_STORAGE_TOPIC: edu-connect-configs
|
||
OFFSET_STORAGE_TOPIC: edu-connect-offsets
|
||
STATUS_STORAGE_TOPIC: edu-connect-status
|
||
# 内部 converter 配置(必须与 Debezium 事件格式一致)
|
||
CONFIG_STORAGE_REPLICATION_FACTOR: "1"
|
||
OFFSET_STORAGE_REPLICATION_FACTOR: "1"
|
||
STATUS_STORAGE_REPLICATION_FACTOR: "1"
|
||
KEY_CONVERTER: org.apache.kafka.connect.json.JsonConverter
|
||
VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter
|
||
KEY_CONVERTER_SCHEMAS_ENABLE: "false"
|
||
VALUE_CONVERTER_SCHEMAS_ENABLE: "false"
|
||
# 监听端口
|
||
REST_PORT: 8083
|
||
REST_ADVERTISED_HOST_NAME: debezium-connect
|
||
# 日志级别
|
||
LOG_LEVEL: INFO
|
||
ports:
|
||
- "8083:8083"
|
||
healthcheck:
|
||
test: ["CMD", "curl", "-f", "http://localhost:8083/connectors"]
|
||
interval: 15s
|
||
timeout: 5s
|
||
start_period: 30s
|
||
retries: 10
|
||
networks:
|
||
- default
|
||
prometheus:
|
||
image: docker.m.daocloud.io/prom/prometheus:v2.51.0
|
||
container_name: edu-prometheus
|
||
profiles: ["observability"]
|
||
restart: unless-stopped
|
||
command:
|
||
- "--config.file=/etc/prometheus/prometheus.yml"
|
||
- "--storage.tsdb.path=/prometheus"
|
||
- "--storage.tsdb.retention.time=15d"
|
||
- "--web.enable-lifecycle"
|
||
volumes:
|
||
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||
- prometheus_data:/prometheus
|
||
ports:
|
||
- "9090:9090"
|
||
grafana:
|
||
image: docker.m.daocloud.io/grafana/grafana:10.4.0
|
||
container_name: edu-grafana
|
||
profiles: ["observability"]
|
||
restart: unless-stopped
|
||
environment:
|
||
GF_SECURITY_ADMIN_PASSWORD: admin
|
||
ports:
|
||
- "3030:3000"
|
||
volumes:
|
||
- grafana_data:/var/lib/grafana
|
||
# ============================================================
|
||
# Alertmanager - 告警路由与抑制(observability profile)
|
||
# ============================================================
|
||
alertmanager:
|
||
image: docker.m.daocloud.io/prom/alertmanager:v0.27.0
|
||
container_name: edu-alertmanager
|
||
profiles: ["observability"]
|
||
restart: unless-stopped
|
||
command:
|
||
- "--config.file=/etc/alertmanager/alertmanager.yml"
|
||
- "--storage.path=/alertmanager"
|
||
ports:
|
||
- "9093:9093"
|
||
volumes:
|
||
- ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
|
||
- alertmanager_data:/alertmanager
|
||
# ============================================================
|
||
# Loki - 日志聚合(observability profile)
|
||
# ============================================================
|
||
loki:
|
||
image: docker.m.daocloud.io/grafana/loki:3.2.1
|
||
container_name: edu-loki
|
||
profiles: ["observability"]
|
||
restart: unless-stopped
|
||
command: -config.file=/etc/loki/local-config.yaml
|
||
ports:
|
||
- "3100:3100"
|
||
volumes:
|
||
- loki_data:/loki
|
||
# ============================================================
|
||
# Promtail - 日志采集(observability profile)
|
||
# ============================================================
|
||
promtail:
|
||
image: docker.m.daocloud.io/grafana/promtail:3.2.1
|
||
container_name: edu-promtail
|
||
profiles: ["observability"]
|
||
restart: unless-stopped
|
||
command: -config.file=/etc/promtail/config.yml
|
||
volumes:
|
||
- ./promtail/config.yml:/etc/promtail/config.yml:ro
|
||
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||
depends_on:
|
||
- loki
|
||
# ============================================================
|
||
# Exporters(observability profile,与 Prometheus 同网络)
|
||
# ============================================================
|
||
node-exporter:
|
||
image: docker.m.daocloud.io/prom/node-exporter:v1.8.2
|
||
container_name: edu-node-exporter
|
||
profiles: ["observability"]
|
||
restart: unless-stopped
|
||
command:
|
||
- "--path.rootfs=/host"
|
||
ports:
|
||
- "9100:9100"
|
||
volumes:
|
||
- /proc:/host/proc:ro
|
||
- /sys:/host/sys:ro
|
||
- /:/host:ro
|
||
mysql-exporter:
|
||
image: docker.m.daocloud.io/prom/mysqld-exporter:v0.15.1
|
||
container_name: edu-mysql-exporter
|
||
profiles: ["observability"]
|
||
restart: unless-stopped
|
||
command:
|
||
- "--mysqld.address=edu-mysql:3306"
|
||
- "--mysqld.username=edu:changeme"
|
||
environment:
|
||
MYSQLD_EXPORTER_PASSWORD: "changeme"
|
||
ports:
|
||
- "9104:9104"
|
||
depends_on:
|
||
mysql:
|
||
condition: service_healthy
|
||
redis-exporter:
|
||
image: docker.m.daocloud.io/oliver006/redis_exporter:v1.67.0
|
||
container_name: edu-redis-exporter
|
||
profiles: ["observability"]
|
||
restart: unless-stopped
|
||
environment:
|
||
REDIS_ADDR: "redis://edu-redis:6379"
|
||
ports:
|
||
- "9121:9121"
|
||
depends_on:
|
||
redis:
|
||
condition: service_started
|
||
volumes:
|
||
mysql_data:
|
||
redis_data:
|
||
clickhouse_data:
|
||
neo4j_data:
|
||
es_data:
|
||
grafana_data:
|
||
prometheus_data:
|
||
alertmanager_data:
|
||
loki_data:
|