
前阵子帮客户交付一套数据分析底座对方指定要 Apache Doris又明确只给了三台云主机Kubernetes 不在规划范围内。我当时的判断是docker-compose 能在一个清晰目录里编排 FE、BE又能把元数据落盘到宿主机关机重启数据不丢这几乎是“不上 K8s 又要三节点高可用”场景下最务实的一条路。这篇文章会把整套部署方案完整记录下来从 compose 文件怎么写、FE/BE 的注册顺序、配置文件的坑到故障转移实测以及最后怎么把单机演练改成三机生产。适合正在评估 Doris 做 OLAP 分析、又不想直接上 K8s 的团队参考。1. 先说结论三节点上为什么要分别部署 FE 和 BE1.1 架构定位3 个 FE 3 个 BE 混合部署Doris 将集群角色分为 FEFrontend和 BEBackend。FE 负责元数据管理、SQL 解析与查询计划生成BE 负责数据存储与计算。两套服务既可以混部在同一台机器上也可以单独规划但有一个硬性规则FE 元数据用 Paxos 协议做副本只有奇数个 FE 才能稳定选主。换句话说2 个 FE 不如 3 个 FE4 个 FE 在故障时可能被投票撕裂。所以三节点机器我首选每台同时部署一个 FE 和一个 BE最终形成 3 FE 3 BE。如果你只有一台机器做验证也可以写成“一个物理节点上跑 6 个容器”但在生产上必须把容器分散到三台宿主机上否则 FE 和 BE 都堆在同一块物理磁盘上断电或宿主机故障时整个集群仍然不可用。本文后面的 compose 文件统一按“单机多容器验证 多机迁移”两个角度来给因为你要真正上生产必须理解 Docker DNS 和节点注册之间的配合。1.2 容器化部署的边界生产级的前提条件Doris 官方文档里曾明确不推荐在生产环境用 Docker原因是容器网络和存储 IO 会带来额外损耗尤其 BE 的磁盘顺序写和大量网络同步在 overlay 网络下性能衰减明显。但在实际交付中私有化环境经常只有云主机没有 K8s我们也不可能因为一个分析引擎单独建一套容器调度平台。这时候用 docker-compose 也能达到我认可的生产级底线元数据目录和 BE 数据目录全部挂载到宿主机物理卷容器删了重建不丢数据配置好 restart 策略宿主机重启后容器自动拉起FE 和 BE 都在同一个自定义 bridge 网络内通过容器名注册IP 变化不影响集群感知3 副本建表即使一个 BE 宕机查询也不中断。这不是追求最大性能的方案但它保证了可用性和数据可靠性。如果你的业务对查询延迟极其敏感我再强调一遍别用容器跑 BE用裸金属或者虚机挂本地盘更合适。下面这套东西适用于“想用三节点快速搭一个高可用的分析集群并且后续愿意接受运维迁移”的场景。2. Docker Compose 文件逐行拆解2.1 文件目录组织我习惯把配置文件放在 compose 工程目录下统一管理方便回滚和版本化。下面的目录结构是实战里比较清晰的一种doris-cluster/ ├── docker-compose.yml ├── conf/ │ ├── fe1.conf │ ├── fe2.conf │ ├── fe3.conf │ ├── be1.conf │ ├── be2.conf │ └── be3.conf └── data/ ├── fe1/meta ├── fe2/meta ├── fe3/meta ├── be1/storage ├── be2/storage └── be3/storage每个 FE 的元数据、每个 BE 的数据都分开挂载是为了复制到多机时仍然保持一一对应的关系。不要多个 FE 共用同一个 meta 目录也不要多个 BE 共用 storage一旦误操作元数据损坏的恢复成本会非常高。2.2 docker-compose.yml 完整示例以单机多容器为例我用的是一个doris-net桥接网络compose 服务名同时作为容器名。官方镜像入口支持在 command 指定启动角色例如fe或者be。如果你使用的镜像入口脚本不同先不带 command 跑一下看容器日志里是否有启停帮助提示。version: 3.8 networks: doris-net: driver: bridge services: fe1: image: apache/doris:2.1.7 container_name: doris-fe1 hostname: doris-fe1 command: [fe] restart: unless-stopped networks: - doris-net ports: - 18030:8030 - 19030:9030 volumes: - ./conf/fe1.conf:/opt/apache-doris/fe/conf/fe.conf:ro - ./data/fe1/meta:/opt/apache-doris/fe/doris-meta - ./data/fe1/log:/opt/apache-doris/fe/log environment: - JAVA_OPTS-Xmx4g -Xms4g fe2: image: apache/doris:2.1.7 container_name: doris-fe2 hostname: doris-fe2 command: [fe] restart: unless-stopped networks: - doris-net ports: - 28030:8030 - 29030:9030 volumes: - ./conf/fe2.conf:/opt/apache-doris/fe/conf/fe.conf:ro - ./data/fe2/meta:/opt/apache-doris/fe/doris-meta - ./data/fe2/log:/opt/apache-doris/fe/log environment: - JAVA_OPTS-Xmx4g -Xms4g fe3: image: apache/doris:2.1.7 container_name: doris-fe3 hostname: doris-fe3 command: [fe] restart: unless-stopped networks: - doris-net ports: - 38030:8030 - 39030:9030 volumes: - ./conf/fe3.conf:/opt/apache-doris/fe/conf/fe.conf:ro - ./data/fe3/meta:/opt/apache-doris/fe/doris-meta - ./data/fe3/log:/opt/apache-doris/fe/log environment: - JAVA_OPTS-Xmx4g -Xms4g be1: image: apache/doris:2.1.7 container_name: doris-be1 hostname: doris-be1 command: [be] restart: unless-stopped networks: - doris-net ports: - 18040:8040 - 19060:9060 volumes: - ./conf/be1.conf:/opt/apache-doris/be/conf/be.conf:ro - ./data/be1/storage:/opt/apache-doris/be/storage - ./data/be1/log:/opt/apache-doris/be/log be2: image: apache/doris:2.1.7 container_name: doris-be2 hostname: doris-be2 command: [be] restart: unless-stopped networks: - doris-net ports: - 28040:8040 - 29060:9060 volumes: - ./conf/be2.conf:/opt/apache-doris/be/conf/be.conf:ro - ./data/be2/storage:/opt/apache-doris/be/storage - ./data/be2/log:/opt/apache-doris/be/log be3: image: apache/doris:2.1.7 container_name: doris-be3 hostname: doris-be3 command: [be] restart: unless-stopped networks: - doris-net ports: - 38040:8040 - 39060:9060 volumes: - ./conf/be3.conf:/opt/apache-doris/be/conf/be.conf:ro - ./data/be3/storage:/opt/apache-doris/be/storage - ./data/be3/log:/opt/apache-doris/be/log这个文件有几个细节要解释我把 FE 的 8030 和 9030 映射到了宿主机的不同端口是为了在同一台机器上同时起 3 个 FE 时不冲突。多机部署时可以全部映射成标准端口 8030/9030hostname和容器名保持一致不要在 compose 里乱起别名否则注册 FE 时用的地址和容器实际地址对不上BE 的 9060 是 BE 与 BE 之间数据传输、也用于 FE 调度副本时访问的数据端口挂载配置文件用只读方式:ro防止容器内某个初始化脚本意外改写所有 data 目录需要先创建如果目录缺失且权限不正确BE 启动后会无法写 storage报错信息通常很隐晦。2.3 端口与关键参数速查下面是 FE 和 BE 最常用的端口排错时一定会用到角色端口用途FE8030Web UI同时提供部分 HTTP 接口FE9030MySQL 协议端口客户端连接 SQL 使用FE9010FE 之间 edit log 同步与选主通信FE9020FE Thrift RPC 端口BE9050BE 向 FE 发送心跳的端口BE9060BE 数据服务端口BE8040BE 的 HTTP 端口可查看健康状态BE8060BRPC 端口用于 BE 间数据传输端口映射到宿主机之后记得在防火墙里把对应的查询端口和 HTTP 端口放通。生产环境建议不要对外暴露 8030只暴露给运维网段。3. 首启编排FE 必须一个一个引导不能一次性 up all3.1 为什么 FE 不能一起启动我最初懒直接docker compose up -d把所有 FE 一起拉起结果 fe2、fe3 日志里反复出现无法找到 master FE 地址的报错。原因是 Doris 的新 FE 节点在启动时会尝试从已有节点同步元数据快照而它必须先被集群手动“点名”加入这个动作 SQL 是ALTER SYSTEM ADD FOLLOWER。如果 fe2 还没被 fe1 “承认”就直接启动它不知道自己的 master 是谁也无法加入 Paxos 组。所以第一次部署必须严格按下面顺序执行先启动 fe1初始化元数据在 fe1 上把 fe2 添加为 FOLLOWER再启动 fe2等 fe2 正常注册后把 fe3 添加为 FOLLOWER最后启动 fe3。3.2 启动 fe1 并逐步添加 FE先把 fe1 单独拉起来docker compose up -d fe1 docker compose ps等doris-fe1状态变成 healthy如果你在 compose 里配了 healthcheck或者日志里出现FE service is started之后我建议先在宿主机确认 MySQL 协议端口能通mysql -h127.0.0.1 -P19030 -uroot此时 root 密码默认是空。如果mysql命令没有装可以直接在容器里执行docker exec -it doris-fe1 mysql -uroot -P9030 -h127.0.0.1然后依次执行两条 SQLALTER SYSTEM ADD FOLLOWER doris-fe2:9010; ALTER SYSTEM ADD FOLLOWER doris-fe3:9010;注意这里务必填doris-fe2和doris-fe3因为我们是在 bridge 网络内互联而不是用宿主机 IP。如果这里填了宿主机 IP容器重启后 IP 一旦变化集群之间就断开了。这是 Docker 部署 Doris 最容易踩的坑。接着再启动 fe2、fe3docker compose up -d fe2 fe3 docker compose ps全部起来后在任意一个 FE 节点执行SHOW FRONTENDS;你会看到三行结果正常状态是Alive为 trueRole为 FOLLOWERJoin为 true。里面有且仅有一个节点的IsMaster为 true这就是当前 leader。3.3 FE 配置文件里需要确认的三个项我给三个 FE 配置的是不同的 conf 文件但核心参数基本一样# fe1.conf / fe2.conf / fe3.conf meta_dir /opt/apache-doris/fe/doris-meta edit_log_port 9010 http_port 8030 rpc_port 9020 query_port 9030 priority_networks 172.20.0.0/16重点解释一下priority_networks。容器环境里往往有多个网卡如果不指定 FE 绑定哪个网段它可能把集群通信地址绑定到 docker 内网的某个非预期地址上甚至绑定到回环地址导致其它 FE 和 BE 无法连接。正因为我们是在 compose 网络内这里可以填这个 bridge 网络的网段。如果你不确定网段先docker network inspect doris-cluster_doris-net查一下容器所在子网再填进去。meta_dir必须和 compose 里的挂载目标一致否则容器重启后元数据仍然落在容器可写层里删容器就丢。这个参数不能靠环境变量覆盖必须写在 conf 文件里。3.4 注册 BE 节点FE 集群就绪后接着启动三个 BEdocker compose up -d be1 be2 be3然后继续用 mysql 客户端执行ALTER SYSTEM ADD BACKEND doris-be1:9050; ALTER SYSTEM ADD BACKEND doris-be2:9050; ALTER SYSTEM ADD BACKEND doris-be3:9050;同样后面跟的是容器名而不是 IP。执行后看SHOW BACKENDS;等几秒钟到心跳周期如果Alive列都是 true说明 BE 注册成功。这里9050是 BE 的心跳服务端口不是 BE 的数据端口9060千万别写错。4. BE 与表级副本让数据真正分布到三机4.1 BE 必须检查的三个配置项BE 容器起来后最怕两件事一是 storage 目录权限不对二是storage_root_path和挂载点不一致。我的 be1.conf 内容如下# be1.conf / be2.conf / be3.conf storage_root_path /opt/apache-doris/be/storage be_port 9060 webserver_port 8040 heartbeat_service_port 9050 brpc_port 8060 priority_networks 172.20.0.0/16重点说storage_root_path。Doris 支持多盘存储多个路径用分号隔开比如/data1/be/storage;/data2/be/storage。每个路径后面可以配置medium和capacity例如storage_root_path /data1/be/storage,medium:HDD,capacity:1T;/data1/be/storage,medium:SSD,capacity:500G但我们容器里只挂了一个目录就保持简单。如果 BE 一直处于Alivefalse我建议先看容器日志docker logs doris-be1 --tail 100大概率是storage_root_path对应的目录权限不够或者宿主机挂载目录没提前创建。此时只需要创建目录并放开权限然后重启容器mkdir -p ./data/be1/storage chmod -R 755 ./data/be1/storage docker compose up -d --force-recreate be14.2 建表replication_num 必须显式设置成 3很多新手建表时不写replication_num默认值可能是 1或者 FE 自动按照当前 BE 数量生成。为了确保三节点高可用我建表时永远显式写 3CREATE DATABASE demo; CREATE TABLE demo.user_click ( uid BIGINT, event VARCHAR(32), ts DATETIME ) DUPLICATE KEY(uid) DISTRIBUTED BY HASH(uid) BUCKETS 10 PROPERTIES(replication_num 3);这里用的是 DUPLICATE KEY 模型适合做明细查询。如果你需要做聚合可以换 AGGREGATE KEY。分桶数量BUCKETS 10在数据量不大时够用生产环境一般建议每个桶的数据量在 100MB 到 1GB 之间可以先粗略估算数据量后期再扩桶。插入几条数据验证写入INSERT INTO demo.user_click VALUES (1001, click, 2025-06-01 10:00:00), (1002, view, 2025-06-01 10:05:00), (1003, click, 2025-06-01 10:10:00);4.3 验证副本分布Doris 没有直接在 SHOW 命令里显示“某行数据的物理副本在哪”但可以通过 tablet 信息确认。执行SHOW TABLET FROM demo.user_click;输出里会看到每个 tablet 有多个副本ReplicaCount应该是 3Healthy为 true。如果某节点挂了这里会出现ReplicaMissing说明 Doris 正在后台自动修复。另外我习惯在多机上分别执行curl http://127.0.0.1:8040/api/health正常情况下返回{status: OK, msg: To Be Added}这个接口是 BE 自己汇报健康状态和 FE 视角的SHOW BACKENDS略有区别但排错时很有用。5. 故障转移实测停 FE、停 BE、重启宿主机后的表现5.1 停掉 Leader FE在验证阶段我把 FE1 停掉模拟 leader 故障docker stop doris-fe1停掉后立刻在 fe2 上尝试查询docker exec -it doris-fe2 mysql -uroot -P9030 -h127.0.0.1 -e SHOW FRONTENDS;你会看到 fe1 的Alive变成 falsefe2 或 fe3 中有一个节点的IsMaster变为 true。Doris 的 FE 选主在 10 秒级别不会立刻发生。如果你用 JDBC 直连 fe1 的 9030 端口写语句或查询语句时连接会在一段时间后报错应用需要重连到新 leader 才能继续。生产环境我给客户的方案是在连接串里配置多个 FE 地址例如jdbc:mysql:loadbalance://192.168.1.11:9030,192.168.1.12:9030,192.168.1.13:9030/demo这样客户端 JDBC 会自动做 failover不需要应用关心当前 leader 是谁。如果用的是 MySQL 命令行或脚本则需要手动切到新 FE 节点。恢复 FE1docker start doris-fe1等几十秒后再次查询SHOW FRONTENDSfe1 会回到 Alivetrue并且元数据从 leader 同步补齐Role仍然是 FOLLOWER。5.2 停掉一个 BE把 be1 停掉模拟存储节点故障docker stop doris-be1此时立刻用 SQL 查数据SELECT * FROM demo.user_click;查询仍然正常返回因为表有三个副本be1 上的副本缺失不影响另外两个副本继续服务。你可以继续执行SHOW BACKENDS;看到 be1 的Alive变成 false。再执行SHOW TABLET FROM demo.user_click;有些 tablet 的ReplicaCount永远是 3这个数字代表期望副本数需要看Healthy和ReplicaMissing这些指标会发现有缺失。Doris 会立刻把副本调度到其它 BE 上优先补满 3 副本。再启动 be1docker start doris-be1等心跳恢复后be1 会自动重新加入集群后台会把之前调走的副本慢慢迁回也会把自己节点上已经缺失的副本补充完整。这个修复过程通常需要几分钟到几十分钟取决于数据量。如果你等不及自动修复可以手动触发ADMIN REPAIR TABLE demo.user_click;这个命令只加速调度不能绕过 Doris 的副本机制真正写入还是异步完成的。5.3 宿主机整体重启后的恢复生产上更常见的场景是整台宿主机断电或者重启。在单机演示环境下restart: unless-stopped会把所有容器重新拉起顺序由 Docker daemon 自己控制。只要 FE 和 BE 注册时使用的是容器名Doris 集群内部通过 Docker DNS 重新解析新 IP仍然可以通信。我建议在宿主机重启后执行一次统一检查docker compose ps确认所有容器都是 Up 状态然后进入任意 FE 执行SHOW FRONTENDS; SHOW BACKENDS;如果 FE 有多个节点同时重启可能出现短暂选主延迟但不需要人工干预。唯一要注意的是如果宿主机重启时 Docker 服务还没起来可能所有容器处于 Exited 状态等 Docker 起来后会自动拉起。除非写了restart: no否则不需要手工逐个启动。6. 生产级落地监控、权限、备份与多机迁移6.1 接入 Prometheus 指标Doris 的 FE 和 BE 都暴露了 Prometheus 指标接口FE 指标地址http://fe_host:8030/metricsBE 指标地址http://be_host:8040/metrics单机演练时可以用宿主机映射端口直接刮取多机部署时在 Prometheus 配置里加入下面一段scrape_configs: - job_name: doris_fe static_configs: - targets: [192.168.1.11:8030, 192.168.1.12:8030, 192.168.1.13:8030] - job_name: doris_be static_configs: - targets: [192.168.1.11:8040, 192.168.1.12:8040, 192.168.1.13:8040]指标里比较值得关注的有doris_be_tablet_count、doris_fe_query_err_rate、doris_be_disk_usage这类与健康度和容量相关的指标。虽然 Doris 自带的 Web UI 已经能看很多运行状态但告警还是得靠 Prometheus Alertmanager 做。6.2 创建业务账号与权限生产环境不要再随便用 root 空密码连库了。建议在初始化完成后立刻执行CREATE USER app_rw IDENTIFIED BY 强密码; GRANT SELECT_PRIV, LOAD_PRIV, ALTER_PRIV, CREATE_PRIV, DROP_PRIV ON demo.* TO app_rw; CREATE USER app_read IDENTIFIED BY 只读密码; GRANT SELECT_PRIV ON demo.* TO app_read;Doris 的权限模型和 MySQL 接近但有些细节不同。比如CREATE_PRIV只对库级授权有效如果你要允许用户在指定库下建表要授权到对应库。只读账号建议不要给SHOW相关高级权限避免误操作。6.3 备份方案不能省容器化部署的持久化卷虽然保证了单机重启不丢数据但如果整机磁盘损坏或者误删表卷也不可靠。Doris 官方备份恢复命令是BACKUP/RESTORE它需要先创建一个仓库仓库可以挂到对象存储或者 HDFS。如果环境里没有对象存储退而求其次我会周期性用EXPORT把关键表导出到外部目录EXPORT TABLE demo.user_click TO file:///tmp/export PROPERTIES(label export_20250601);这个方案适合保护业务核心表不适合做全库秒级恢复。完全体的备份方案应该是在每台宿主机上对./data/fe*/meta和./data/be*/storage做快照并且定时把快照传到独立存储。磁盘快照是兜底BACKUP命令才能做表级恢复两者都要做。6.4 从单机演练改造成三机部署当你准备把单机演练的方案迁到三台物理机时不需要改太多东西单机演练配置三机生产配置所有服务都写在同一个 docker-compose.yml每台宿主机只写本机角色例如 node1 只定义 fe1 和 be18030/9030 映射到不同端口每台宿主机都映射标准端口 8030/9030容器网络是 bridge三台机器之间必须能通过主机名或固定内网 IP 互通compose 里hostname改成实际主机名注册 FE/BE 时用容器名注册时用每台宿主机的主机名或内网 IP例如192.168.1.11:9010多机迁移最常踩的坑是priority_networks配置单机演练时我填的是 bridge 网段多机部署时要改成宿主机内网网段否则 FE/BE 会把地址绑定到 docker0 网桥地址上导致跨机器无法识别。这条切记。6.5 几个非常实在的避坑总结最后我把这套部署里真正让我花过时间排查的问题列一下给后来者参考容器名注册还是 IP 注册表面看都能通但容器重建后 IP 会变所以要用容器名或主机名FE 首次启动不要一次性把所有节点都起起来否则新节点不知道该跟谁同步元数据meta_dir和storage_root_path必须与 compose 挂载目标一致不一致等于白挂BE 磁盘格式化很慢第一次启动时SHOW BACKENDS看到 Alivetrue 不代表 tablet 副本马上建好要等后台任务完成不要在宿主机直接 kill BE 进程来模拟故障Doris 检测到 BE 进程异常退出后会迅速标记下线而docker stop会触发优雅退出这和生产环境断电更接近JDBC 连接串一定要配多个 FE否则 FE 主节点切换时应用会报连接错误。用 docker-compose 跑 Doris 三节点从配置文件设计到故障转移验证核心就是把“节点注册”“持久化”“自动拉起”“三副本建表”这四件事做到位。尤其是注册地址的选择几乎决定了容器化集群在宿主机重启后还能不能自愈。我的建议是无论演示环境还是生产交付都先按本文流程完整演练一遍故障转移再真正上生产。只有亲手停过 FE、停过 BE、重启过宿主机你才敢说这个集群“高可用”。