Docker 部署 OceanBase 异常关机后恢复实战

一句话总结:OceanBase 内部系统表(__all_ls_replica)持久化了容器 IP,Docker 网络重建后分配新 IP 导致 RPC 通信失败。解法是固定 Docker 网段与静态 IP,并绕过 obd 入口脚本直接启动 observer。

背景

生产环境使用 docker-compose 部署 OceanBase CE 4.2.1,服务器异常关机后容器启动失败。本文记录完整排查与修复过程。

环境信息

  • OceanBase CE 4.2.1.10
  • docker-compose 部署,数据通过 bind mount 持久化
  • 单节点集群 obcluster

原始配置(有缺陷)

version: '3.0'
services:
    oceanbase:
        image: oceanbase/oceanbase-ce:latest
        container_name: oceanbase
        environment:
            - OB_SYS_PASSWORD=123456
            - OB_CLUSTER_NAME=obcluster
        ports:
            - "2881:2881"
            - "2882:2882"
        volumes:
            - ./ob:/root/ob
        networks:
            - oceanbase_network
networks:
    oceanbase_network:
        driver: bridge

排查过程

第一步:docker-compose up -d 失败

[ERROR] OBD-1002: Fail to init 172.20.0.2 home path: /root/ob is not empty.
[ERROR] OBD-1027: run `obd cluster deploy -f obcluster` to perform forced deployment.

容器入口脚本检测到 /root/ob 已有旧数据,拒绝重新部署。

第二步:docker start oceanbase 同样失败

[ERROR] Deploy "obcluster" is configured. You could not start an configured cluster.

obd 内部状态卡在 configured,拒绝执行 start 命令。

第三步:进入容器手动排查

docker run -it --rm \
  --network tszb-oceanbase_oceanbase_network \
  -v "$(pwd)/ob:/root/ob" \
  -p 2881:2881 -p 2882:2882 \
  --entrypoint /bin/bash \
  oceanbase/oceanbase-ce:latest
obd cluster list
# demo      | stopped
# obcluster | configured   ← 状态卡死

第四步:发现根因 —— IP 不匹配

# 容器当前 IP
hostname -I
# 172.20.0.2

# observer 内部配置记录的 IP
cat /root/ob/etc/observer.config.bin | strings | grep "172\."
# local_ip=172.28.0.2
# all_server_list=172.28.0.2:2882
# rootservice_list=172.28.0.2:2882:2881

容器 IP 是 172.20.0.2,但 observer.config.bin 内部记录的 IP 是 172.28.0.2

这是因为 docker-compose 重建 network 时分配了新的子网段,而 OceanBase 内部表(__all_ls_replica 等)持久化存储了旧的 member_list IP。observer 启动后一直尝试向 172.28.0.2:2882 发 RPC,永远无法完成 Leader 选举。

第五步:错误尝试 —— 删除 config.bin

删除 observer.config.bin 后直接启动 observer,observer 认为这是全新集群尝试 bootstrap,与已有 datafile 冲突,直接崩溃:

ERROR ... errcode=-4018 ... info="observer start fail"

教训:不要删除已有集群的 observer.config.bin。

第六步:错误尝试 —— 用 -o local_ip 覆盖 IP

observer ... -o "local_ip=172.20.0.2"

虽然进程 IP 变成了 172.20.0.2,但 OceanBase 内部系统表 __all_ls_replicamember_list 字段里存的仍然是 172.28.0.2,RPC 通信依然失败,永远卡在 Server is initializing


最终方案

思路

既然内部表中已经持久化了 172.28.0.2,最安全的做法是让 Docker 网络分配同样的 IP 段,而不是修改 OceanBase 内部数据。

修复后的 docker-compose.yml

version: '3.0'

services:
    oceanbase:
        image: oceanbase/oceanbase-ce:latest
        container_name: oceanbase
        environment:
            - OB_SYS_PASSWORD=123456
            - OB_CLUSTER_NAME=obcluster
        ports:
            - "2881:2881"
            - "2882:2882"
        volumes:
            - ./ob:/root/ob
        networks:
            oceanbase_network:
                ipv4_address: 172.28.0.2

networks:
    oceanbase_network:
        driver: bridge
        ipam:
            config:
                - subnet: 172.28.0.0/16

操作步骤

# 1. 停止并清理
docker stop oceanbase 2>/dev/null
docker rm oceanbase 2>/dev/null
docker network rm tszb-oceanbase_oceanbase_network 2>/dev/null

# 2. 重建(注意:数据不丢,在 ./ob 目录里)
docker-compose up -d

# 3. 进入容器手动启动(obd 状态可能仍卡在 configured)
docker exec -it oceanbase bash

# 4. 清理运行时锁文件
rm -f /root/ob/run/*.pid /root/ob/run/*.lock
find /root/.obd -name "*.lock" -delete 2>/dev/null

# 5. 直接启动 observer
export LD_LIBRARY_PATH=/root/ob/lib:$LD_LIBRARY_PATH
cd /root/ob && /root/ob/bin/observer \
  -p 2881 -P 2882 \
  -z zone1 -n obcluster -c 1 \
  -d /root/ob/store -i eth0 -l INFO \
  -o "memory_limit=6G,system_memory=1G,datafile_size=5G,log_disk_size=5G,root_password='yunjin@9527'"

# 6. 等 3-5 分钟后验证
obclient -h127.0.0.1 -P2881 -uroot@sys -p'yunjin@9527'

验证成功的标志

# 进程存活
ps aux | grep observer | grep -v grep

# IP 一致
grep "self.*172.28.0.2" /root/ob/log/observer.log | tail -1

# 连接成功
obclient -h127.0.0.1 -P2881 -uroot@sys -p'yunjin@9527'
# 进入后执行:SELECT * FROM oceanbase.__all_tenant;

问题根因总结

问题原因修复
OBD-1002: /root/ob is not empty入口脚本不允许重复部署已有数据目录跳过 obd,直接启动 observer
Deploy is configured, cannot startobd 内部状态持久化在 SQLite,异常关机后状态未更新跳过 obd,直接启动 observer
observer 启动后 crashed (-4018)删除 config.bin 导致 observer 尝试重新 bootstrap 与已有数据冲突保留 config.bin
永远卡在 Server is initializing内部表 member_list 记录的旧 IP 与容器新 IP 不匹配,RPC 通信失败固定 Docker 网段与静态 IP

最终结果

经过上述排查与修复后,observer 进程成功启动,数据库连接正常,所有租户数据和业务表完好无损。

关键经验

  1. 不要把 /root/ob/store 看作"能删"的数据——那是生产数据库的全部。
  2. observer.config.bin 不能删——它记录着集群元数据,删掉后 observer 会尝试重新 bootstrap。
  3. OceanBase 内部表会持久化 IP 地址——仅靠 -o local_ip 无法覆盖所有记录。
  4. Docker 静态 IP + 固定子网是 OceanBase 容器化的最佳实践——避免容器重建后 IP 漂移。
  5. 30G+ 数据量的 crash recovery 预计 5-10 分钟,期间 Server is initializing 是正常状态。

对 docker-compose.yml 的改进建议

version: '3.0'
services:
    oceanbase:
        image: oceanbase/oceanbase-ce:latest
        container_name: oceanbase
        environment:
            - OB_SYS_PASSWORD=123456
            - OB_CLUSTER_NAME=obcluster
        ports:
            - "2881:2881"
            - "2882:2882"
        volumes:
            - ./ob:/root/ob
        networks:
            oceanbase_network:
                ipv4_address: 172.28.0.2
        restart: unless-stopped  # 新增:宿主机重启后自动恢复

networks:
    oceanbase_network:
        driver: bridge
        ipam:
            config:
                - subnet: 172.28.0.0/16

加了 restart: unless-stopped 后,容器崩溃或宿主机重启时 Docker 会自动尝试重启容器。不过注意:首次部署或 Docker 网络重建后,仍可能需要手动进入容器启动 observer,因为 obd 入口脚本对"已有数据目录"的检查逻辑比较严格。

更多推荐