Files
Edu/infra/docker-compose.yml
SpecialX a6b0c54fba fix: 修复登录路由 + 端口冲突 + Prometheus 配置 + outbox 表结构 + 补充监控基础设施
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(基础设施修复会话)
2026-07-11 09:22:09 +08:00

305 lines
10 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
# 双 listenerINSIDE 容器间互访kafka:29092OUTSIDE 主机访问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
# ============================================================
# Exportersobservability 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: