Apache Doris 容器化实战指南:从Docker镜像构建到Kubernetes集群部署

张开发
2026/4/12 14:06:46 15 分钟阅读

分享文章

Apache Doris 容器化实战指南:从Docker镜像构建到Kubernetes集群部署
1. 为什么要把Apache Doris塞进容器里大家好我是老张在数据平台和基础架构这块摸爬滚打了十来年。这些年从物理机到虚拟机再到现在的容器化我算是亲眼见证了技术栈的变迁。今天想和大家聊聊一个特别具体的事儿怎么把Apache Doris这个高性能的MPP分析型数据库给“装”进Docker和Kubernetes里。你可能要问Doris本身部署也不算太复杂为啥非要折腾容器化呢我刚开始也有这个疑问直到我们团队要同时维护好几个不同版本、服务于不同业务线的Doris集群时头就大了。开发同学想快速搭个环境测试新功能运维同学要保证线上集群的稳定和可扩展测试同学则需要一个和生产环境尽可能一致的沙箱。这时候容器化的优势就太明显了环境标准化、快速拉起、资源隔离、弹性伸缩。简单说容器化能让Doris的部署和运维从“手工作坊”升级到“自动化工厂”。举个例子以前新同事入职配一个本地开发环境光是装依赖、配网络、初始化集群可能就得折腾一两天还容易因为环境差异出各种奇葩问题。现在我直接给他一个docker-compose文件他只要机器上有Docker一条命令几分钟就能得到一个完整的、可用的Doris测试集群1个FE1个BE立马就能开始干活。这种效率的提升对于追求快速迭代的团队来说价值巨大。当然容器化也不是银弹尤其是对于有状态的服务。Doris的FEFrontend有元数据要持久化BEBackend有数据存储这些在容器生命周期的管理上需要格外小心。所以这篇实战指南我会结合我踩过的坑和总结的经验带你从最基础的Docker镜像定制构建开始一步步走到生产可用的Kubernetes集群部署。我们的目标不是简单地“能跑起来”而是构建一个健壮、可维护、可扩展的容器化Doris方案。2. 从零开始定制你的Doris Docker镜像官方其实提供了Doris的Docker镜像但说实话直接用在生产环境或者有定制化需求的场景下往往不够用。比如你可能需要集成特定的监控Agent、调整默认的JVM参数、或者使用自己编译的二进制版本。所以掌握自己构建镜像的能力是容器化实践的第一步。2.1 构建前的准备思路与目录结构构建镜像首先得把“原料”和“配方”准备好。我的习惯是建立一个清晰的目录结构这能让后续的维护和自动化脚本编写轻松很多。下面是我常用的一个结构doris-docker-build/ ├── fe/ │ ├── Dockerfile │ └── resources/ │ ├── init_fe.sh │ └── apache-doris-2.0.3-bin-fe.tar.gz ├── be/ │ ├── Dockerfile │ └── resources/ │ ├── init_be.sh │ └── apache-doris-2.0.3-bin-be-x86_64.tar.gz └── broker/ ├── Dockerfile └── resources/ ├── init_broker.sh └── apache-doris-2.0.3-bin-broker.tar.gz关键点解析分组件构建FE、BE、Broker分开构建镜像这是微服务架构的思想解耦后更灵活也方便独立升级。资源隔离每个组件自己的resources目录下放它所需的启动脚本和二进制包。这样做Dockerfile写起来清晰ADD命令不会混乱。二进制包来源你可以从Apache Doris官网下载官方编译好的稳定版二进制包这是最省事的方式。如果你团队对Doris源码有定制化修改那就需要自己编译打包了。记住务必从可信源获取安全是第一位的。2.2 编写Dockerfile不止是COPY和RUNDockerfile是镜像的蓝图。很多人觉得就是“基础镜像-复制文件-设置启动命令”三步走但对于Doris这种复杂服务细节决定成败。以构建FE镜像的Dockerfile为例我通常会这样写# 使用官方OpenJDK 8镜像作为基础这是经过Docker Hub认证的更安全稳定。 # 注意标签选择比如8u342-jdk明确版本号避免因基础镜像更新导致意外问题。 FROM openjdk:8u342-jdk # 设置环境变量JAVA_HOME必须正确指向容器内的JDK路径。 ENV JAVA_HOME/usr/local/openjdk-8 \ PATH/opt/apache-doris/fe/bin:$PATH \ # 设置容器时区避免日志时间错乱这是个很容易忽略的坑。 TZAsia/Shanghai # 复制二进制包并解压。使用ADD命令会自动解压tar.gz文件。 # 这里有个技巧先复制到临时目录再移动到最终位置可以让镜像层更清晰。 ADD ./resources/apache-doris-2.0.3-bin-fe.tar.gz /opt/ # 安装必要的系统工具。Doris FE需要mysql-client来执行一些初始化操作。 RUN apt-get update \ apt-get install -y --no-install-recommends default-mysql-client \ apt-get clean \ rm -rf /var/lib/apt/lists/* \ # 创建统一的Doris安装目录并移动解压后的文件 mkdir -p /opt/apache-doris \ mv /opt/apache-doris-fe-2.0.3-bin /opt/apache-doris/fe # 复制自定义的启动脚本并赋予执行权限。 COPY ./resources/init_fe.sh /opt/apache-doris/fe/bin/ RUN chmod x /opt/apache-doris/fe/bin/init_fe.sh # 声明数据卷这是一个好习惯提示用户哪些目录需要持久化。 VOLUME [/opt/apache-doris/fe/doris-meta, /opt/apache-doris/fe/log] # 使用ENTRYPOINT而不是CMD确保启动脚本是容器的主进程。 # 在K8s环境下主进程不以后台方式运行至关重要否则Pod会不断重启。 ENTRYPOINT [/opt/apache-doris/fe/bin/init_fe.sh]几个我踩过的坑时区问题不设置TZ容器内默认是UTC时间查询日志时会很困惑。镜像层优化把多个RUN指令用连接并在最后清理apt缓存能显著减少镜像层数和最终大小。进程前台运行init_fe.sh脚本里绝对不能用nohup ... 这种方式启动Doris进程。必须让进程在前台运行这样Docker/K8s才能正确监控进程状态。这是容器化有状态服务最关键的准则之一。BE和Broker的Dockerfile思路类似主要区别在于二进制包名称、环境变量和启动脚本。BE同样需要JDK环境从1.2版本支持Java UDF开始所以基础镜像选择是一样的。2.3 灵魂所在编写启动脚本init_fe.shDockerfile管“构建”启动脚本管“运行”。这个脚本是镜像的灵魂它负责在容器启动时根据环境变量动态配置并启动Doris进程。一个健壮的init_fe.sh脚本需要处理以下事情#!/bin/bash set -e # 遇到错误立即退出方便排错 # 1. 从环境变量读取配置这些变量会在docker run或K8s YAML中设置 FE_SERVERS${FE_SERVERS:-} # FE集群列表格式 fe1:ip:9010,fe2:ip:9010 FE_ID${FE_ID:-} # 当前FE的ID1代表Master # 其他如FE_IP, FE_EDIT_LOG_PORT等也可以从这里传入 # 2. 检查必要环境变量是否设置 if [[ -z $FE_SERVERS || -z $FE_ID ]]; then echo ERROR: Environment variables FE_SERVERS and FE_ID must be set. exit 1 fi # 3. 解析FE_SERVERS生成fe.conf配置文件 # 这里需要写一些逻辑将FE_SERVERS字符串拆解并判断当前节点是Master还是Follower # 如果是MasterFE_ID1可能需要执行初始化元数据的操作 # 如果是Follower则需要等待Master可用后执行ADD FOLLOWER或ADD OBSERVER命令加入集群 # 4. 处理元数据目录 # 检查doris-meta目录是否为空如果是全新的容器需要初始化。 # 如果目录已存在比如从持久化卷挂载的则跳过初始化。 # 5. 启动FE进程 # 关键必须使用前台模式启动 /opt/apache-doris/fe/bin/start_fe.sh --daemonfalse # 或者直接运行java进程 # 6. 可选健康检查循环 # 可以添加一个循环定期检查FE进程端口9030是否就绪并输出日志。BE的init_be.sh脚本逻辑也类似核心是等待FE Master节点可用通过mysql客户端连接测试。使用ALTER SYSTEM ADD BACKEND命令将自身注册到FE。前台启动BE进程。把这些脚本写好你的自定义镜像就具备了“智能启动”的能力而不是一个简单的二进制包空壳。2.4 执行构建与推送万事俱备开始构建。进入fe目录执行docker build -t your-registry.com/doris-fe:2.0.3-custom .-t参数指定镜像标签建议包含仓库地址、组件名、版本号和自定义标识。构建BE和Broker镜像同理。构建成功后用docker images查看。接下来需要把镜像推送到私有仓库如Harbor或公共仓库这样K8s集群才能拉取到。docker login your-registry.com docker push your-registry.com/doris-fe:2.0.3-custom3. 单机编排用Docker Compose快速搭建测试集群有了镜像我们先在单机环境下玩起来。Docker Compose是定义和运行多容器应用的神器特别适合本地开发、测试和快速原型验证。3.1 理解Doris容器的网络模式在写Compose文件前得先搞清楚网络。Doris集群内部节点FE之间、FE和BE之间需要频繁通信。容器网络有两种常见选择host模式(network_mode: host)容器直接使用宿主机的网络栈IP和端口都是宿主机级别的。优点是网络性能最好零损耗配置简单。缺点是端口容易冲突且不利于跨主机编排因为容器IP就是宿主机IP在多主机环境下需要复杂的服务发现。bridge模式Docker为容器创建一个虚拟子网容器有独立的内部IP。优点是隔离性好端口映射灵活。缺点是网络性能有轻微损耗并且需要解决容器间通过服务名或自定义网络互相发现的问题。对于单机部署测试集群我强烈推荐使用host模式简单粗暴没烦恼。下面就是一个典型的单机1FE1BE的docker-compose.yml3.2 编写docker-compose.ymlversion: 3.8 services: doris-fe: image: your-registry.com/doris-fe:2.0.3-custom # 使用你刚构建的镜像 container_name: doris-fe hostname: doris-fe # 容器主机名 environment: - FE_SERVERSfe1:192.168.1.100:9010 # 关键这里填宿主机的真实IP - FE_ID1 - FE_IP192.168.1.100 volumes: # 将元数据和日志持久化到宿主机避免容器重启数据丢失 - ./data/fe/doris-meta:/opt/apache-doris/fe/doris-meta - ./data/fe/log:/opt/apache-doris/fe/log network_mode: host # 使用host网络 restart: unless-stopped # 设置重启策略 # 健康检查确保服务真正就绪后才接受流量 healthcheck: test: [CMD, mysql, -h127.0.0.1, -P9030, -uroot, -e, SELECT 1] interval: 30s timeout: 10s retries: 3 start_period: 60s doris-be: image: your-registry.com/doris-be:2.0.3-custom container_name: doris-be hostname: doris-be environment: - FE_SERVERSfe1:192.168.1.100:9010 - BE_ADDR192.168.1.100:9050 # BE自身的地址也是宿主机IP volumes: # BE的数据存储目录必须持久化 - ./data/be/storage:/opt/apache-doris/be/storage - ./data/be/log:/opt/apache-doris/be/log network_mode: host restart: unless-stopped depends_on: doris-fe: condition: service_healthy # 等待FE健康后再启动BE healthcheck: test: [CMD-SHELL, curl -sf http://127.0.0.1:8040/api/health || exit 1] interval: 30s timeout: 5s retries: 3重要参数解读FE_SERVERS告诉所有节点FE集群的地址列表。即使只有一个FE也要按格式写。FE_ID1表示这是Master FE。如果部署多个FE其他节点可以是2,3...FE_IP/BE_ADDR在host模式下必须设置为宿主机的IP地址不能是127.0.0.1或容器内部IP否则其他机器无法访问。volumes这是数据持久化的生命线。一定要把doris-meta和storage目录挂载出来。我习惯挂载到当前目录下的data文件夹方便管理。depends_oncondition: service_healthy确保启动顺序和依赖关系BE必须等FE就绪了再去注册。healthcheck定义容器健康状态Docker Compose和K8s都会依赖这个来判断Pod是否就绪。3.3 启动与验证在docker-compose.yml所在目录一键启动docker-compose up -d用docker-compose ps查看状态用docker-compose logs -f doris-fe查看日志。集群启动后用MySQL客户端连接测试mysql -h 192.168.1.100 -P 9030 -uroot连接成功后执行SHOW FRONTENDS;和SHOW BACKENDS;应该能看到FE和BE节点都是Alive状态。这个基于Docker Compose的集群已经可以满足大部分功能测试和开发联调的需求了。它把部署时间从小时级降到了分钟级。4. 进阶生产级Kubernetes部署全攻略单机Compose虽好但无法满足生产环境的高可用、弹性伸缩和自动化运维需求。接下来我们进入正题看看如何在Kubernetes上部署一个健壮的Doris集群。4.1 设计理念StatefulSet Service ConfigMap在K8s中部署有状态服务首选StatefulSet。它为每个Pod提供稳定的、唯一的网络标识符主机名和持久化存储这完美契合了Doris FE和BE节点的需求。FE通常部署3个节点1 Master 2 Follower组成高可用集群。使用一个StatefulSet每个Pod有独立的元数据PVCPersistentVolumeClaim。BE可以部署多个同样使用StatefulSet每个Pod有独立的数据存储PVC。Service为FE和BE创建对应的Service提供稳定的集群内访问域名。例如FE的Service可以让BE Pod通过doris-fe-service这个域名访问到FE集群。ConfigMap将启动脚本、配置文件如fe.conf,be.conf的模板放入ConfigMap以卷的形式挂载到容器内实现配置与镜像分离。4.2 关键配置解析以FE StatefulSet为例下面是一个简化但核心的FE StatefulSet YAML片段我加了详细注释apiVersion: apps/v1 kind: StatefulSet metadata: name: doris-fe namespace: doris spec: serviceName: doris-fe-service # 必须对应Headless Service replicas: 3 # 3个节点实现高可用 selector: matchLabels: app: doris-fe template: metadata: labels: app: doris-fe spec: # 可以设置亲和性/反亲和性让Pod分散在不同节点上 affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - doris-fe topologyKey: kubernetes.io/hostname containers: - name: fe image: your-registry.com/doris-fe:2.0.3-custom imagePullPolicy: IfNotPresent env: - name: FE_SERVERS # 这是关键利用StatefulSet的Pod域名规则。 # $(POD_NAME) 会被替换为 doris-fe-0, doris-fe-1, doris-fe-2 # 每个Pod都知道整个集群的列表。 value: fe1$(POD_NAME).doris-fe-service.$(NAMESPACE).svc.cluster.local:9010,fe2$(POD_NAME).doris-fe-service.$(NAMESPACE).svc.cluster.local:9010,fe3$(POD_NAME).doris-fe-service.$(NAMESPACE).svc.cluster.local:9010 - name: FE_ID # 通过Pod序号生成FE_IDdoris-fe-0对应ID 1以此类推。 valueFrom: fieldRef: fieldPath: metadata.name # 需要在启动脚本中处理提取出末尾数字并1如果从0开始 - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace ports: - containerPort: 9030 # 查询端口 name: query - containerPort: 9010 # 编辑日志端口FE间通信 name: edit-log - containerPort: 8030 # HTTP端口 name: http volumeMounts: - name: fe-meta-pvc mountPath: /opt/apache-doris/fe/doris-meta - name: fe-log-pvc mountPath: /opt/apache-doris/fe/log - name: fe-config mountPath: /opt/apache-doris/fe/conf # 可以挂载自定义配置 resources: requests: memory: 4Gi cpu: 2 limits: memory: 8Gi cpu: 4 livenessProbe: tcpSocket: port: 9030 initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: # 使用mysql客户端进行更精确的就绪检查 exec: command: - /bin/sh - -c - mysql -h127.0.0.1 -P9030 -uroot -e SELECT 1 initialDelaySeconds: 90 periodSeconds: 10 failureThreshold: 3 volumeClaimTemplates: # StatefulSet的精髓为每个Pod自动创建PVC - metadata: name: fe-meta-pvc spec: accessModes: [ ReadWriteOnce ] storageClassName: ssd-fast # 指定存储类元数据对IOPS要求高 resources: requests: storage: 100Gi - metadata: name: fe-log-pvc spec: accessModes: [ ReadWriteOnce ] storageClassName: standard resources: requests: storage: 50Gi --- # 对应的Headless Service用于Pod间直接通信 apiVersion: v1 kind: Service metadata: name: doris-fe-service namespace: doris spec: clusterIP: None # Headless Service ports: - port: 9010 name: edit-log - port: 9030 name: query selector: app: doris-fe核心要点服务发现利用StatefulSet的稳定网络标识$(POD_NAME).$(SERVICE_NAME).$(NAMESPACE).svc.cluster.local每个Pod都能解析出其他Pod的地址。FE_SERVERS环境变量通过这个机制动态生成完整的集群列表。动态FE_ID通过fieldRef获取Pod名称如doris-fe-0在启动脚本中解析出序号“0”并映射为Doris所需的FE_ID1。这保证了Pod无论在哪台节点上重建其ID是稳定的。持久化存储volumeClaimTemplates是StatefulSet的杀手级特性它为每个Pod自动创建独立的PVC。务必为元数据选择高性能的存储类如SSD。探针readinessProbe使用mysql命令检查比简单的端口检查更准确确保FE完全启动并能接受查询后才标记为就绪。资源限制一定要设置resources.requests和limits避免单个Pod吃光节点资源也方便K8s调度。BE的StatefulSet配置类似但环境变量主要是FE_SERVERS和BE_ADDR。BE_ADDR可以设置为$(POD_NAME).doris-be-service.$(NAMESPACE).svc.cluster.local:9050。4.3 部署流程与运维操作准备K8s环境确保有可用的Kubernetes集群可以是云托管的也可以是自建的如KubeSphere、Rancher并配置好存储类StorageClass。创建命名空间kubectl create ns doris部署FEkubectl apply -f doris-fe-statefulset.yaml -n doris等待FE就绪kubectl get pods -n doris -w观察所有FE Pod变成Running和Ready状态。查看日志确认Master选举成功。部署BEkubectl apply -f doris-be-statefulset.yaml -n doris验证集群通过Port-forward或Ingress将FE的9030端口暴露出来用MySQL客户端连接执行SHOW BACKENDS;查看BE注册情况。扩缩容操作扩容BE非常简单直接修改StatefulSet的replicas数量或者用命令kubectl scale statefulset doris-be --replicas5 -n doris。新的BE Pod会自动启动并注册到FE。缩容BE不能直接缩容必须先通过MySQL客户端连接到Doris对要下线的BE执行ALTER SYSTEM DECOMMISSION BACKEND “be_host:heartbeat_port”;命令。等待数据迁移完成后该BE会显示为Decommissioned状态此时再在K8s中缩容Pod才是安全的。FE扩缩容FE的扩容增加Follower/Observer和缩容也需要通过Doris的SQL命令ALTER SYSTEM ADD FOLLOWER/OBSERVER和DROP FOLLOWER/OBSERVER来操作并同步更新所有Pod的FE_SERVERS环境变量。自动化这一步需要额外的Operator或初始化脚本支持。4.4 监控与日志收集生产环境离不开监控。你需要指标暴露Doris FE和BE都提供了Prometheus格式的Metrics接口FE:http://fe_host:8030/metrics BE:http://be_host:8040/metrics。在K8s中可以通过PodMonitor或ServiceMonitor配置让Prometheus自动抓取。日志收集将挂载到宿主机或PVC的日志目录/opt/apache-doris/*/log通过DaemonSet部署的Filebeat或Fluentd收集并发送到Elasticsearch或Loki等日志中心。告警规则针对关键指标如FE/BE节点是否存活、查询延迟、磁盘使用率等在Prometheus Alertmanager中配置告警。5. 避坑指南与高级技巧走完上面的流程一个基本的容器化Doris集群就搭起来了。但想在生产环境跑得稳还得注意下面这些我亲身踩过的坑。5.1 存储性能与数据持久化这是最关键的环节。Doris BE的存储性能直接决定了查询速度。BE存储一定要用本地SSD盘或高性能的云盘如AWS的io2GCP的pd-ssd阿里云的ESSD PL云盘。通过K8s的Local PersistentVolume或高性能的StorageClass来提供。切勿使用网络延迟高的存储否则BE的读写性能会成为瓶颈。FE元数据FE的元数据doris-meta虽然数据量不大但读写频繁对延迟敏感同样建议使用SSD存储。备份策略定期对FE的元数据目录进行备份。可以利用K8s的CronJob执行mysqldump备份Doris的系统表同时结合存储卷的快照功能进行物理备份。5.2 网络性能优化在K8s中Pod间的网络通信通常经过一层Overlay如Calico的IPIP或VxLAN会带来轻微开销。对于Doris这种内部通信密集的集群可以考虑使用HostNetwork像Docker Compose那样在K8s Pod Spec中设置hostNetwork: true。这能获得最佳网络性能但牺牲了端口管理的灵活性且要求端口在宿主机上不冲突。需谨慎评估。CNI插件选择如果集群使用Calico可以启用IPIP模式或直接路由模式BGP以减少封装开销。或者考虑像Cilium这样的高性能CNI插件。拓扑感知路由在云服务商的环境下启用Service的拓扑感知路由可以确保流量优先在同一可用区内转发降低延迟。5.3 资源管理与调度资源请求与限制务必准确设置。BE对内存和CPU消耗都很大。一个经验值是BE Pod的内存limits至少是requests的1.5倍给JVM一些缓冲空间。CPU的requests和limits可以设成一致避免被限制。节点亲和性与反亲和性使用podAntiAffinity确保同一个Doris服务的多个Pod如3个FE尽量分散在不同的物理节点上提高容灾能力。BE Pod也可以考虑分散部署避免单个节点故障导致数据副本全部丢失。容忍度如果节点有特殊污点记得给Pod加上对应的tolerations。5.4 版本升级与回滚容器化带来了便捷的升级方式但数据库升级仍需谨慎。镜像升级准备好新版本的Doris镜像。滚动升级修改StatefulSet的image字段K8s会按顺序逐个更新Pod。务必先升级BE再升级FE因为新版本的BE通常可以兼容旧版本FE反之则可能不行。灰度验证升级完一个Pod后观察日志和监控指标确认无误后再继续下一个。回滚方案如果升级失败快速将image字段改回旧版本K8s会自动回滚。前提是数据存储的格式是向后兼容的。因此升级前务必查阅官方Release Note确认升级路径和注意事项。最后我想说容器化Apache Doris是一个系统工程它不仅仅是把进程放进容器里那么简单。它涉及到镜像构建、服务编排、网络、存储、监控、运维等方方面面。本文提供的是一套经过实践验证的可行路径和核心要点但每个团队的实际环境云厂商、网络架构、存储设施都不同需要你在此基础上进行调整和深化。多测试多观察监控理解每个参数背后的含义你就能驾驭好这个强大的分析型数据库让它在你云原生的数据平台上稳定高效地运行。

更多文章