Files
Edu/infra/docker-compose.yml
SpecialX 1a5fa78fa6 feat(config-service): split config-service from iam for plugin/layout config
- new NestJS service on port 3011/gRPC 50059 (ADR-026)
- owns 6 config_ tables (plugin/role-mapping/role-layout/layout-tpl/user-override/outbox)
- GraphQL Federation 2 subgraph with DataLoader + RouterAuthGuard
- gRPC ConfigService + admin REST CRUD + user REST API
- three-layer merge: registry.defaultProps + roleMapping.widget_props + userOverride.props
- Redis cache with 5min TTL
- registered in apollo-router supergraph + docker-compose + port-allocation

Implements M3 of v2.1 migration plan.
2026-07-15 02:13:03 +08:00

359 lines
12 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 链路核心v2.1Outbox 表监听 + 自动投递)
# 监听 MySQL binlog → 写入 Kafka topic
# v2.1 设计ADR-032业务代码只写 outbox 表Debezium 监听 binlog 自动投递
# 废弃 OutboxPublisher 轮询线程(见 M0.5 / M8
# 注册脚本bash infra/debezium/register-outbox-connectors.sh
# ============================================================
debezium-connect:
image: quay.io/debezium/connect:2.7
container_name: edu-debezium
profiles: ["default", "p3", "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
config-service:
build:
context: ../services/config-service
container_name: edu-config-service
profiles: ["p3", "p4", "p5", "p6"]
restart: unless-stopped
environment:
DATABASE_URL: mysql://edu:${MYSQL_PASSWORD:-changeme}@edu-mysql:3306/next_edu_cloud
REDIS_URL: redis://edu-redis:6379
KAFKA_BROKERS: kafka:29092
ROUTER_AUTH_SECRET: ${ROUTER_AUTH_SECRET:-dev-router-secret}
DEV_MODE: ${DEV_MODE:-false}
PORT: "3011"
GRPC_PORT: "50059"
ports:
- "3011:3011"
- "50059:50059"
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3011/healthz"]
interval: 15s
timeout: 5s
retries: 5
apollo-router:
build:
context: ./apollo-router
container_name: edu-apollo-router
profiles: ["p3", "p4", "p5", "p6"]
restart: unless-stopped
environment:
ROUTER_AUTH_SECRET: ${ROUTER_AUTH_SECRET:-dev-router-secret}
APOLLO_ELV2_LICENSE: accept
ports:
- "3000:3000"
- "8088:8088"
depends_on:
- iam
- core-edu
- content
- msg
- ai
- data-ana
- config-service
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8088/health"]
interval: 15s
timeout: 5s
retries: 5
volumes:
mysql_data:
redis_data:
clickhouse_data:
neo4j_data:
es_data:
grafana_data:
prometheus_data:
alertmanager_data:
loki_data: