NestJS: disable incremental in 6 services tsconfig.json to fix dist not emitted when nest-cli deleteOutDir conflicts with tsc tsbuildinfo. classes/iam: import HealthModule in AppModule to fix /healthz 404. classes: rewrite HealthController to Drizzle getDb from TypeORM DI. teacher-bff: add /metrics endpoint for Prometheus scraping. infra: add node/mysql/redis exporters to observability profile. mysql-exporter v0.15.1 uses command-line flags not DATA_SOURCE_NAME. prometheus: enable web.enable-lifecycle for hot reload.
259 lines
8.3 KiB
YAML
259 lines
8.3 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
|
||
# ============================================================
|
||
# 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:
|