首页 / 资讯中心 / 文章详情

Docker Compose部署TiDB集群实战:从组件架构到踩坑排查

Docker Compose部署TiDB集群实战:从组件架构到踩坑排查 ★ FEATURED ARTICLE
前两天刚在本地用Docker把一套TiDB集群编排起来从拉镜像到最终用MySQL客户端连上4000端口整个过程比我预想中多踩了不少坑。现在把这套流程整理出来顺带把那些官方文档里写得云淡风轻、实际却能卡你半小时的细节都标记清楚给正在折腾docker部署TiDB的朋友做个参考。TiDB是一个兼容MySQL协议的分布式NewSQL数据库简单说就是“看着像MySQL但底层是分布式存储能横向扩展”。它拆成了TiDB Server、PD、TiKV三个核心组件Docker部署的核心思路就是把这三个组件分别容器化、用Compose编排起来让单机也能快速体验分布式数据库的架构。这篇文章适合两类人看一是刚接触TiDB、想用最低成本跑起来玩玩的开发者二是被各种“用K8s部署”“二进制部署”文档劝退、只想要一套能快速复现的本地演示环境的人。我尽量少讲大道理多写可以直接抄的配置和真实踩坑过程。1. 先搞清楚TiDB的三兄弟再动手1.1 三个核心组件各自干什么TiDB不是一个单体数据库而是一组配合工作的分布式组件。我习惯用一家餐厅来类比TiDB Server是前台点餐的收银员负责接收SQL请求、返回结果PD是店长负责掌握全局信息、决定哪桌客人去哪个窗口取餐TiKV是后厨真正把饭做熟、把数据存下来。具体到技术担当TiDB Server无状态SQL层负责解析SQL、生成执行计划、跨节点聚合数据。因为无状态所以可以随便扩缩容前面挂个负载均衡就能当MySQL用。PDPlacement Driver整个集群的“大脑”负责元数据管理、分片Region调度、时间戳分配TSO。PD通常部署3个节点通过Raft协议保证一致性。TiKV有状态的存储层数据以Region为单位打散在所有TiKV节点上每个Region默认3副本副本之间通过Raft协议自动同步。这是TiDB能支持弹性和高可用的根基。除了这三兄弟还有一个TiFlash属于列存引擎用于加速分析类查询。名字叫“TiFlash”实际上就是官方把列存和行存分开存储的HTAP方案。本地演示可以不部署后面进阶再玩。1.2 为什么用Docker而不是裸机装二进制我第一次折腾TiDB时也想过直接下载二进制包跑后来发现一个很现实的问题一套最少规模的集群至少是3个PD加3个TiKV加1个TiDB也就是7个进程。手动管理这么多进程启动顺序错了就会导致成员注册不上日志还分散在各个目录。用Docker的收益是明确的依赖打包干净TiDB组件没有乱七八糟的宿主机依赖一条命令就能起一个组件形象理解就是“扔进集装箱就跑”。网络模型统一Compose会自动创建内部网络组件之间用服务名互访不需要手动改hosts。环境隔离组件之间互不影响内存限制、日志采集、数据卷挂载都比裸进程好管理。我个人的观点是如果只是想快速体验TiDB、验证应用兼容性、跑个DemoDocker Compose是性价比最高的方式。但如果目标是学习TiDB运维、部署到生产那还是得看K8s因为Docker Compose不提供故障自愈和自动扩缩容。1.3 部署形态怎么选围绕“docker部署TiDB”实际有三种常见的玩法应用场景完全不一样玩法适用场景优点缺点单个docker run起容器临时测试TiDB不关心其他组件最快一条命令组件间要手动指定网络IP容易乱Docker Compose编排整套集群本机/单机演示环境配置清晰启停方便不适合跨多机部署K8sOperator生产环境、弹性扩缩容自愈、自动运维能力强学习成本高资源开销大这篇主要讲第二种它也是单机环境下最贴近“真实集群拓扑”的姿势。多机场景下Compose需要额外配置外部网络复杂度成倍上升不如直接用K8s。2. 部署前准备与配置文件设计2.1 环境准备在写配置之前先把基础环境确认好。我用的是一台8核16G内存的Linux服务器UbuntuDocker和Docker Compose插件都已经装好。实际跑起来后全套TiDB集群大概占用6G左右的空闲内存所以建议至少8G内存否则很容易出现OOM。环境要求汇总如下Docker Engine 20.10以上支持Compose v2可用磁盘空间至少15G镜像加数据卷很占空间TiKV镜像本身就超过1G服务器时间准确最好已配置NTP时间同步。TiDB对时钟偏移极为敏感PD和TiKV之间时钟偏差太大会直接拒绝工作端口可自行调整但默认规划要心里有数注意TiDB对时间同步的要求源自分布式一致性原理。Raft协议本身依赖于消息的先后顺序如果两个节点之间的时钟偏差过大会引发TSO时间戳乱序轻则报错重则选主异常。这个问题我后面细讲。2.2 端口规划要搞清楚每个组件暴露哪些端口不然端口冲突排查时毫无头绪。以一套最常规的部署为例组件端口用途TiDB Server4000SQL服务端口MySQL协议入口TiDB Server10080TiDB状态上报端口HTTPPD2379PD客户端端口TiKV/TiDB都连它PD2380PD集群节点间通信端口peerPD9090? 不是部分版本会用到具体按实际镜像TiKV20160TiKV客户端端口TiKV20180TiKV状态上报端口HTTP这里有个容易混淆的点PD的2379端口是让TiDB和TiKV来连接的2380端口是PD自己节点之间通信用的。如果你在Compose里没把2380暴露出来只是容器内部使用那其实也是可以的但为了能从宿主机查看健康状态我通常会暴露出来。2.3 docker-compose.yml怎么写、为什么这么写这是一个非常典型的Compose配置完整版我放在下面。先说几个关键判断镜像用官方镜像仓库的版本tag不要用latest裸跑因为大版本升级可能导致配置不兼容TiKV和PD都做了数据卷挂载防止容器删除后数据丢失启动顺序通过depends_on加healthcheck控制这一步几乎是必须的否则TiKV一启动就连接PDPD还没就绪经常出现注册失败。services: pd1: image: pingcap/pd:v8.1.0 container_name: pd1 ports: - 2379:2379 - 2380:2380 command: - --namepd1 - --client-urlshttp://0.0.0.0:2379 - --peer-urlshttp://0.0.0.0:2380 - --advertise-client-urlshttp://pd1:2379 - --advertise-peer-urlshttp://pd1:2380 - --initial-clusterpd1http://pd1:2380,pd2http://pd2:2380,pd3http://pd3:2380 volumes: - ./data/pd1:/data healthcheck: test: [CMD-SHELL, curl -f http://localhost:2379/pd/api/v1/health || exit 1] interval: 3s timeout: 3s retries: 20 pd2: image: pingcap/pd:v8.1.0 container_name: pd2 ports: - 12379:2379 - 12380:2380 command: - --namepd2 - --client-urlshttp://0.0.0.0:2379 - --peer-urlshttp://0.0.0.0:2380 - --advertise-client-urlshttp://pd2:2379 - --advertise-peer-urlshttp://pd2:2380 - --initial-clusterpd1http://pd1:2380,pd2http://pd2:2380,pd3http://pd3:2380 volumes: - ./data/pd2:/data healthcheck: test: [CMD-SHELL, curl -f http://localhost:2379/pd/api/v1/health || exit 1] interval: 3s timeout: 3s retries: 20 pd3: image: pingcap/pd:v8.1.0 container_name: pd3 ports: - 22379:2379 - 22380:2380 command: - --namepd3 - --client-urlshttp://0.0.0.0:2379 - --peer-urlshttp://0.0.0.0:2380 - --advertise-client-urlshttp://pd3:2379 - --advertise-peer-urlshttp://pd3:2380 - --initial-clusterpd1http://pd1:2380,pd2http://pd2:2380,pd3http://pd3:2380 volumes: - ./data/pd3:/data healthcheck: test: [CMD-SHELL, curl -f http://localhost:2379/pd/api/v1/health || exit 1] interval: 3s timeout: 3s retries: 20 tikv1: image: pingcap/tikv:v8.1.0 container_name: tikv1 ports: - 20160:20160 - 20180:20180 command: - --addr0.0.0.0:20160 - --advertise-addrtikv1:20160 - --status-addr0.0.0.0:20180 - --pd-endpointspd1:2379,pd2:2379,pd3:2379 - --data-dir/data volumes: - ./data/tikv1:/data depends_on: pd1: condition: service_healthy pd2: condition: service_healthy pd3: condition: service_healthy tikv2: image: pingcap/tikv:v8.1.0 container_name: tikv2 ports: - 30160:20160 - 30180:20180 command: - --addr0.0.0.0:20160 - --advertise-addrtikv2:20160 - --status-addr0.0.0.0:20180 - --pd-endpointspd1:2379,pd2:2379,pd3:2379 - --data-dir/data volumes: - ./data/tikv2:/data depends_on: pd1: condition: service_healthy pd2: condition: service_healthy pd3: condition: service_healthy tikv3: image: pingcap/tikv:v8.1.0 container_name: tikv3 ports: - 40160:20160 - 40180:20180 command: - --addr0.0.0.0:20160 - --advertise-addrtikv3:20160 - --status-addr0.0.0.0:20180 - --pd-endpointspd1:2379,pd2:2379,pd3:2379 - --data-dir/data volumes: - ./data/tikv3:/data depends_on: pd1: condition: service_healthy pd2: condition: service_healthy pd3: condition: service_healthy tidb: image: pingcap/tidb:v8.1.0 container_name: tidb ports: - 4000:4000 - 10080:10080 command: - --storetikv - --pathpd1:2379,pd2:2379,pd3:2379 - --advertise-addresstidb depends_on: tikv1: condition: service_started tikv2: condition: service_started tikv3: condition: service_started这个配置有几个设计上的讲究我展开说说。第一PD为什么要做healthcheck。TiKV启动的时候会去PD注册自己的Store信息如果PD还没就绪TiKV会一直重试。但TiKV的重试机制并不会在PD恢复后自动把所有事情都处理得很好某些极端情况下会出现Store状态为Offline或Tombstone。给PD加一个HTTP健康检查TiKV严格等待PD就绪后再启动是最简单可靠的顺序控制方式。第二advertise地址为什么不能用127.0.0.1。容器内部和宿主机是两个网络命名空间。如果TiKV的advertise-addr写成了127.0.0.1:20160PD拿到的Store地址就是错的客户端访问时就会连接失败。在Compose网络里容器之间通过服务名互相访问所以advertise地址必须是服务名。这就是为什么我不建议用docker run --network host来部署这套集群省事是省事但各个进程都抢宿主机端口后期管理很混乱。第三数据卷要挂到宿主机。TiDB组件的数据目录各不相同PD默认存元数据到/dataTiKV的--data-dir也指向/data。不挂卷的话docker-compose down之后数据就跟着容器一起没了。挂载之后即使容器被删掉重建数据也都还在。3. 从启动到连库完整实操记录3.1 启动集群配置文件保存为docker-compose.yml然后在同一目录执行docker-compose up -d首次启动会拉镜像几个镜像加起来体积不小耐心等就行。启动成功后先用docker-compose ps看一下状态docker-compose ps我那次启动后出现的情况是三个PD都处于healthy状态三个TiKV还在Restarting。这说明TiKV启动失败正在被Compose反复拉起。这时候别干等着赶紧看日志docker logs tikv1 --tail 50这类问题后面在踩坑实录里专门说。正常情况下等所有容器都显示Up之后再用tiup或mysql客户端连上去验证。3.2 用MySQL客户端验证TiDB的4000端口完全兼容MySQL协议所以直接用mysql客户端就能连mysql -h 127.0.0.1 -P 4000 -u rootTiDB默认没有密码进去后先看版本select version(); select tidb_version()\G再跑一个简单的建库建表测试create database if not exists test; use test; create table if not exists t1(id int primary key auto_increment, name varchar(20)); insert into t1(name) values(hello),(tidb),(docker); select * from t1;结果返回正常说明TiDB Server已经能成功读写TiKV了。这里尤其要确认一点SQL走到TiKV存储而不是走到了什么别的临时引擎。你可以在TiDB里执行explain analyze select * from t1如果执行计划中出现了tikv_xxx算子说明数据真的写到TiKV了。我习惯同时开两个窗口一个跑SQL一个用tidb的http状态接口看负载情况curl http://127.0.0.1:10080/status会返回一个JSON里面包含版本、当前连接数、语句数等。本地验证时这个接口比任何监控面板都直观。3.3 数据持久化容器删了也不怕Docker容器是脆弱的docker-compose down或者docker-compose rm都会把容器删掉。如果你没挂载数据卷TiKV里面存的数据会全部消失PD里的元数据也没了集群等于重新初始化。我强烈建议在任何一次正经操作之前都先做一次容器删除重建的验证docker-compose down docker-compose up -d利用挂载到宿主机data目录的数据目录PD启动时会从已有数据里恢复元数据TiKV也能从已有数据里恢复Region信息。重建之后之前的库表和数据依然还在。这个验证做完你对这套环境的信心会完全不同。不过要注意一点不要直接用宿主机上的同一个数据目录同时跑两套Compose集群。PD和TiKV的数据文件之间没有集群标识隔离你硬要用同一份数据同时启动两套集群很可能把集群元数据搞坏。3.4 关闭集群的正确姿势关闭集群也有讲究。最安全的顺序是先停TiDB再停TiKV最后停PD。因为TiDB会把正在执行的请求处理完或者优雅终止TiKV停的时候会向PD发送关闭请求把Region的心跳停下来PD最后停避免其他组件还依赖它的时候它先没了。实际操作中用docker-compose stop是最省心的它会按依赖倒序发送SIGTERM信号。我一般不会直接docker-compose kill或者docker stop某个容器因为那相当于强制断电极端情况下可能导致Raft日志没刷完恢复时数据校验要花很长时间。4. 踩坑实录部署TiDB最容易出的5个问题4.1 PD起来了TiKV却一直不可用这是我第一次部署遇到的最大问题。现象是PD容器都healthy了但tikv容器一直在Restarting日志里反复出现连接PD超时或者member信息获取失败。排查步骤确认PD地址在TiKV容器内可访问docker exec tikv1 ping pd1如果ping不通八成是Compose网络问题看看服务名是不是写错了。确认advertise-addr配置正确。如果写成127.0.0.1TiKV注册到PD的地址就是容器内部的回环地址PD和其他节点访问不到。查看PD日志确认TiKV有没有成功发起RegisterStore请求。最终我发现自己犯了一个低端错误——漏掉depends_on条件TiKV在PD健康检查通过之前就开始启动了。后来加上condition: service_healthy问题就消失了。4.2 内存不够的OOM地狱TiDB这套组件的资源占用不是说着玩的。3个PD、3个TiKV、1个TiDB裸跑大概吃掉6~8G内存。如果服务器只有4G内存TiKV启动后很快就会被内核OOM Kill掉此时用docker-compose ps看容器还显示Up但日志里其实已经打印了Killed。解决思路有两种。第一种是降低组件数量把3个PD减到1个TiKV也减到1个这种配置在纯学习场景够用但牺牲了高可用。第二种是给容器设置内存上限在Compose里给每个服务加mem_limitdeploy: resources: limits: memory: 2G注意mem_limit对TiKV的效果一般因为TiKV内部有RocksDB默认block cache会尽量吃内存。想压内存得给TiKV挂配置文件限制block cache大小。开发环境可以用最小配置凑合但生产环境千万别这么搞性能会缩水很严重。4.3 clock offset分布式系统的隐形杀手如果TiKV日志里出现clock offset相关错误核心原因就是节点时间不同步。TiDB的PD会校验节点上送的时间戳偏差过大会认为节点不可信。本地单机部署也会遇到这个问题吗会的尤其是宿主机没配置NTP且使用了时间跳跃的情况下。我印象最深的一次是宿主机重启后硬件时间漂移了十几秒整个集群突然全部异常。排查方法date然后在三个PD容器里分别执行docker exec pd1 date docker exec pd2 date正常应该是同一秒内。如果差了10秒以上先同步宿主机时间timedatectl set-ntp true如果容器时间和宿主机不一致检查docker run时有没有挂载/etc/localtime。Compose里可以加volumes: - /etc/localtime:/etc/localtime:ro这属于一个很容易被忽略但又非常关键的细节。4.4 端口冲突排查TiDB用4000端口MySQL默认也是3306一般不容易冲突。但如果宿主机本身装了别的应用4000、2379、2380这些端口就很容易撞车。Compose启动时如果端口被占用会直接报bind address already in use。我的排查命令netstat -tlnp | grep -E 4000|2379|2380|20160遇到冲突最简单的办法是把宿主机侧端口改掉容器内部端口保持不变。比如前面配置里PD2、PD3的宿主机端口就是12379、22379刻意避开了本机其他服务。容器内部的端口是组件之间的通信约定不能随便改但宿主机侧的端口只是映射入口随便改不影响集群工作。4.5 版本兼容和初始化连接问题镜像版本选不好同样会踩坑。比如PD和TiKV版本不一致TiKV连上PD后可能因为gRPC协议版本差异导致握手失败。所以必须统一版本号不要出现pd用v8.0.0、tikv用v8.1.0这类混搭。如果看到日志里出现unknown descriptor或者protobuf parse error优先检查各组件镜像版本是否一致。另一个容易犯的错是TiDB Server和TiKV都部署好了但用客户端连的时候报错比如host is not allowed to connect。这是TiDB的权限系统在捣乱。默认root用户只允许本机连接如果你从别的机器连过来需要手动创建用户并授权create user demo% identified by demo123; grant all privileges on *.* to demo%; flush privileges;如果不是必须用root我建议所有外部连接都用这种方式创建独立账号避免误操作把root密码改坏以后没法恢复。5. 从演示环境到正经环境下一步怎么玩5.1 常用维护指令这套Compose跑起来后日常维护就这么几条命令记住就行# 查看整体状态 docker-compose ps # 查看某个组件日志 docker-compose logs -f tikv1 # 进入TiDB容器执行SQL docker exec -it tidb mysql -h 127.0.0.1 -P 4000 -u root # 重启某个组件 docker-compose restart tidb # 停止所有容器但不删数据 docker-compose stop # 停止并删除所有容器数据卷保留 docker-compose down注意docker-compose down不会删除挂载到宿主机目录的数据卷所以数据安全。真正要清数据只能手动删除宿主机data目录下的文件。5.2 为什么生产环境不这么干这是很多人会问的问题。既然Compose能跑为什么生产环境不建议用核心原因有三个一是Docker Compose是单机编排工具没有跨节点调度能力TiDB的分布式能力完全发挥不出来二是TiDB的高可用依赖多副本和多节点单机部署的Compose环境即使起了3个TiKV本质上还是共用一台物理机硬件故障就全挂了三是生产TiDB需要很多细粒度的参数调优比如TiKV的线程池大小、RocksDB参数、PD的调度策略Compose不适合承担这种级别的配置管理。生产环境目前主流的部署方式是K8s TiDB Operator用K8s管理Pod、PV、网络、扩缩容Operator负责执行TiDB特有的运维动作。学习路线可以是从Compose起步理解组件交互再过渡到K8s。5.3 后续学习路径建议跑通这套集群只是第一步接下来我更推荐做这几件事第一把故障演练做一遍。用docker-compose stop tikv1模拟单节点故障观察TiDB是否还能正常读写。正常情况是PD会在几秒内感知到Region副本缺失把其他节点的副本提升为主副本业务侧只是轻微卡顿。这个实验能让你彻底理解Raft多副本的价值。第二玩一下TiFlash。在Compose文件里追加一个tiflash服务TiDB会自动把分析类查询路由到列存引擎。你可以在同一张表上跑普通OLTP查询和OLAP聚合观察执行计划里的算子在行存和列存之间如何选择。第三试着把Prometheus和Grafana加进来。TiDB的Prometheus指标接口非常丰富官方Grafana Dashboard模板也是现成的。本地把监控搭起来之后你能直观看到Region数、SQL QPS、TiKV的CPU内存变化排查问题会高效很多。我个人在实际操作中的一个体会是Docker部署TiDB这件事门槛不在命令本身而在你对分布式系统基本概念的把握。很多报错反推回去都是对“PD是核心协调者”“TiKV数据多副本”“Raft一致性”这些基础理解不到位。把Compose环境当成实验田多折腾几次比单纯看文档管用得多。下一步如果感兴趣可以试着把TiKV扩容到5节点、10节点观察Region的调度过程那时你会对“分布式数据库为什么这么设计”有更直观的理解。
阅读完成 · 觉得有帮助?
咨询建站