
一、这件事为什么值得写下来
课程要求装 OpenTenBase。我想顺手验证一个念头:把系统换到 Ubuntu 26.04.1(resolute),能不能装成功。
结论先放这里:能,但过程和「装」几乎没关系。
OpenTenBase 的官方打包项目(OpenTenBase-Packages,由电子科技大学开源社团维护)给出的支持矩阵是 20.04 / 22.04 / 24.04 / 25.04,26.04 不在里面。这意味着官方那条「一条命令装完」的路,在我这台机器上走不通——不是命令跑不了,而是它依赖的运行时假设全部错位了:预编译二进制链接的 OpenSSL 是老版本,系统里已经是新的;部署脚本默认能读到的 home 骨架文件不存在;包管理器里没有它要的那个 so 版本。
更麻烦的是报错信息经常指错方向。工具会打印 Installation completed successfully,然后集群一个进程都起不来;initdb 崩了但不告诉你为什么;一个看起来无害的 sed: 无法读取 xxx 警告,才是端口号变成垃圾值 3536603216 的真凶。
所以这篇只记技术:装的是什么、错在哪、怎么验证它真的好了。整个过程我是用一个 AI 助手做「看报错 → 改方案 → 再跑」的循环推进的,但下面不会花篇幅夸工具——报错原文和根因才是能复用的部分。
二、先搞清楚要装的是什么
在敲任何命令之前,得先明白 OpenTenBase 和 PostgreSQL 的区别:它不是一个数据库,而是一个集群。最小形态下,同一台机器上跑三个进程,靠端口区分,各管一件事。
组件 | 端口 | 角色 | 职责 |
|---|---|---|---|
GTM | 11000 | 全局事务管理器 | 不存业务数据,只分配全局事务号与全局对象(如序列)。分布式事务的跨节点一致性靠它发号。 |
CN | 11003 | 协调节点(Coordinator) | 客户端唯一入口。解析 SQL、决定发给哪些 DN、把结果汇总回来。只存全局元数据。 |
DN | 11006 | 数据节点(Datanode) | 真正存数据的地方。生产环境主备成对,单机学习用一个。 |
这个结构解释了后面几乎所有现象:
· 客户端连不上时,问题很可能不在 CN,而在它背后的 DN——CN 只是把请求转过去。 · GTM 挂了,整个集群都不正常,哪怕 DN 进程还活着,因为拿不到事务号就开不了事务。 · 三个进程共用一套二进制、一份配置文件模板,只靠端口和数据目录区分——所以「端口配错」在这套体系里是致命错误,不是小毛病。
装完以后,集群的「户口本」在 CN 上的系统表里:
psql -h 127.0.0.1 -p 11003 -U opentenbase postgres -c "select node_name,node_type,node_port,node_host from pgxc_node;"
如果这里的 node_port 不是 11000 / 11003 / 11006,那就不用往下查了,先修配置。
三、环境与三个硬门槛
项目 | 实际值 | 说明 |
|---|---|---|
系统 | Ubuntu 26.04.1 LTS(resolute) | 不在官方支持矩阵内,本文所有坑的源头 |
虚拟化 | VMware Workstation,NAT | 单机三节点,同一 IP 靠端口区分 |
CPU / 内存 | 4 核 / 7.7 GiB | 内存是硬门槛,见下 |
磁盘 | 约 20 GB | deb 包路线够用;源码编译会紧张 |
安装形态 | 1 GTM + 1 CN + 1 DN | 具备分布式语义的最小配置,无高可用 |
门槛一:内存不低于 4 GB。 CN 节点需要约 4 GB 共享内存,而且这个值官方明确说不可调优。2 GB 的机器不是「慢」,是直接起不来。这一条我当硬校验用,不达标就别折腾。
门槛二:OpenTenBase 不能用 root 运行。 必须普通用户(deb 包会自动建 opentenbase 用户),而且这个用户要能被 SSH 登录、要有能读写的 home 目录。很多「启动即挂」其实是权限问题穿了个马甲。
门槛三:系统版本决定二进制能不能跑。 这一条最容易被忽略,因为它是静默的:预编译包是在 OpenSSL 1.1 时代构建的,Ubuntu 22.04 之后系统只带 OpenSSL 3,动态加载器按文件名找不到 libssl.so.1.1,进程直接起不来。26.04 把这个问题放得更大,下面第五节会展开。
四、三条安装路线,为什么我选 deb 包
路线 | 优点 | 代价 | 结论 |
|---|---|---|---|
源码编译 | 最贴近生产,可以针对新系统重新链接 OpenSSL 3 | 依赖十几项、编译 30 分钟以上、吃磁盘、坑在 CMake/contrib 阶段 | 不选(磁盘和时间都不划算) |
Docker 镜像 | 几分钟起来,零依赖烦恼 | 看不到进程树、端口绑定、数据目录结构 | 不选(我来学架构的,不是来跑 demo 的) |
官方 deb 包(OpenTenBase-Packages) | 几分钟装完,占用小,能直接看进程和配置 | 要配第三方 apt 源,且预编译二进制带老依赖 | 选它 |
选 deb 包的理由很具体:我想看到 pgxc_node 里真实的节点记录,想看到 CN 和 DN 各自的进程树,想亲手改 config.ini。这些在容器里会被封装掉,而封装掉的部分恰好是我要学的部分。
反过来说,如果你只是想「跑一个分布式数据库看看 SQL 能不能跑」,Docker 是最优解,本文后面的坑你一个都不会遇到。
五、九个坑:表象与根因
先给速览表。后面挑最值得讲的五条展开——不是因为别的坑不重要,而是这五条能迁移到别的部署场景里。
# | 表象 | 根因 |
|---|---|---|
1 | 六个步骤全绿,status 却全 Stopped | 部署工具用「目录是否创建」判断 initdb 成功,而 initdb 已经先把目录建好了 |
2 | initdb 无声崩溃,只有一个非零退出码 | 缺 libssl.so.1.1 / libcrypto.so.1.1,报错被 &>/dev/null 吞掉 |
3 | 日志里 CN 端口是 3536603216 | 部署工具靠 .bashrc 记录端口,deb 建用户时没生成 home 骨架 |
4 | 报 libpqxx-7.9.so not found | 随包附带的 libpqxx.so 内部 SONAME 带版本号,文件名不带 |
5 | psql 起不来,缺 libreadline.so.7 | 26.04 只提供 .so.8,而 psql 链接的是 .so.7 |
6 | ssh 传二进制过去,文件 0 字节且不报错 | 用了 ssh -n,stdin 被接到 /dev/null |
7 | 长任务日志读一半就断,后面全丢 | 管道直读受缓冲影响,进程结束前被截断 |
8 | opentenbase_ctl 报权限/环境类错误 | 用 root 执行,或 package= 指向了目录而不是 tar.gz 文件 |
9 | 怀疑 locale 不对,花几小时验证一个不存在的 bug | 真实报错被隐藏时,任何猜测都「看起来合理」 |
5.1 「安装成功」是最危险的四个字
装完 deb 包我就跑了部署:
sudo su - opentenbase -c "./opentenbase_ctl install -c config.ini"
输出 ====== Installation completed successfully ======,六个步骤逐个 successfully,退出码 0。然后 start 报告 Success: 3, Failed: 0,但 status 显示 CN 和 DN 都是 Stopped,只有 GTM 活着。
怎么定位的?不看工具自己的话,看三个独立证据:
· 日志第二行,Node port : 3536603216——正常应该是 11003。3536603216 是未初始化内存被当整数打印出来的样子。 · [ERROR]: Could not open the file ".../cn0001/data/postgresql.conf"——配置文件根本没生成。 · 数据目录里只有 pg_hba.conf、postgresql.conf.user、pg_ctl_start.log 三个文件,没有 PG_VERSION。
最后这条是判据。PostgreSQL 系(OpenTenBase 基于 PG 内核)的 initdb 成功后会写一个 PG_VERSION 文件记录数据格式版本,并建出 base/、global/ 一堆子目录。目录里没有它,就说明 initdb 压根没跑成——目录只是被 mkdir 出来了。
根因在部署日志里:它执行 initdb 时带了一句 &>/dev/null,把 stdout 和 stderr 全丢进了黑洞;紧接着下一行却打印 Successfully initdb for node cn0001。它的成功判据是「目录有没有被创建」,而不是 initdb 的退出码。
这条坑的通用形式是:自动化工具说的「成功」,只是它自己的判断标准。你要知道它的标准是什么。 所以验收必须独立做:PG_VERSION 在不在、pgxc_node 端口对不对、数据是否真的落到 DN。
5.2 缺 libssl.so.1.1:为什么软链一定崩
绕开工具手动跑一次 initdb,真凶立刻显形:
initdb: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory
这里最容易犯的错,是把三个版本号混为一谈:
名称 | 是什么 | 属于哪个维度 |
|---|---|---|
OpenTenBase 5.x | 数据库产品版本 | 产品版本 |
PostgreSQL 10 | 它采用的 PG 内核版本 | 内核版本 |
OpenSSL 1.1 / 3.0 | 它依赖的加密库版本 | 依赖库版本 |
「我装的是 5.0,凭什么要 1.1 的库」——因为这两个数字之间没有任何换算关系。二进制在构建时就把「我要 libssl.so.1.1」写进了 ELF 的 SONAME 记录,动态加载器只按这个确切文件名去找。libssl.so.3 名字就不一样,加载器根本不会看它。
那软链行不行?ln -s libssl.so.3 libssl.so.1.1 能骗过前两步:文件存在了,加载器同意加载了。它死在第 3 步——调用库里的函数。OpenSSL 3 把大量结构体改成不透明类型、重写了一批 ERR_ 函数、废弃了 engine 机制。程序按 1.1 的约定去调,对不上就是 Segmentation fault。
这种失败比「报缺少文件」更恶心:没有明确报错名、没有缺失提示,只有一个崩溃的退出码——再叠上第 1 条坑里的 &>/dev/null,你连崩溃都看不见。
正确做法是从 Ubuntu 22.04(jammy)的安全源补这个 deb 包:
cd /tmp
sudo curl -sSL -o libssl1.1.deb http://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.24_amd64.deb
sudo dpkg -i libssl1.1.deb || sudo apt-get install -y -f
三个补充说明:
· 版本号 1.1.1f-1ubuntu2.24 是写死的,从 jammy 源下架后 URL 会失效,届时去 packages.ubuntu.com 查当前可用版本。 · 国内 security.ubuntu.com 慢,换清华镜像同一路径即可。 · 这不算给系统降级:系统自带的 libssl.so.3 一点没动,apt、curl 继续用它;你只是额外补了一个不同文件名的文件。同机共存多版本库是 Linux 的常规设计。 · 但 libssl1.1 已停止安全更新。本地一次性虚拟机可以接受;要上生产,正解是源码编译一版链接 OpenSSL 3 的 OpenTenBase,而不是长期赖着老库。
5.3 一个 sed 警告,换来了一个垃圾端口号
第一次启动时有一行警告,我当时归类为噪音:
sed: 无法读取 /var/lib/opentenbase/.bashrc: No such file or directory
它不是噪音。deb 包创建 opentenbase 用户时没有生成 home 目录骨架,而这个 .bashrc 正是部署工具用来记录各节点端口的载体。文件不存在 → 端口写不进去 → 日志里出现 3536603216 → 进程按垃圾端口启动 → 立刻挂掉。
一条完整传导链:.bashrc 缺失 → sed 失败(只有一行警告)→ 端口丢失 → 启动即挂。
所以这一条我记的是方法论:不要因为某个报错看起来无害就跳过它,尤其当它出现在你还没跑通的主流程里。
5.4 同样是「缺个库」,处理方式完全不同
还有两个缺库,但判断标准不是「差多少版本」,而是 ABI 兼不兼容。
libpqxx-7.9.so → 软链就够。 程序自带 libpqxx.so,但文件内部记录的 SONAME 是 libpqxx-7.9.so(objdump -p 能看到)。库存量是同一个,只是文件名少了个版本后缀,加载器按名字找不到。
sudo ln -sf /var/lib/opentenbase/install/opentenbase/5.21.8/lib/libpqxx.so /var/lib/opentenbase/install/opentenbase/5.21.8/lib/libpqxx-7.9.so
echo "/var/lib/opentenbase/install/opentenbase/5.21.8/lib" | sudo tee /etc/ld.so.conf.d/opentenbase.conf
sudo ldconfig
libreadline.so.7 → 软链也可接受。 26.04 只给 libreadline.so.8,而 psql 和 pgxc_ctl 链接 .so.7。readline 只用于交互式行编辑(退格、翻历史),版本间接口变动极小。
sudo ln -sf /usr/lib/x86_64-linux-gnu/libreadline.so.8 /usr/lib/x86_64-linux-gnu/libreadline.so.7
sudo ldconfig
对照 5.2 的 OpenSSL:那里是纯 ABI 不兼容(结构体布局、函数签名全变),软链必崩,必须装真库。三个「缺库」,两种处理,一条判断标准。
5.5 其余四条,一句话一条
ssh -n 会静默吃掉你的 stdin。-n 让 ssh 从 /dev/null 读 stdin,于是「ssh 到目标机执行 tar 解包」这类需要喂数据的操作,传了 0 字节还不报错。传二进制时这一条能让人卡很久——因为退出码是 0。
长耗时任务的日志必须落文件。 管道直读会被缓冲截断,进程结束前最后一段输出经常整段丢失。正确姿势是 nohup ... > install.log 2>&1 & 落盘,再另开一条连接 tail -f 读。
两个「必须」:不能用 root 跑 opentenbase_ctl;package= 必须指向 .tar.gz 文件,不能是目录。 前者是安全设计,后者是工具实现细节,都只报一行错,不解释。
报错被隐藏时,你的猜测全会「看起来合理」。 我一度怀疑是 --locale=zh_CN.utf8 不存在导致的,因为 initdb 参数里确实有它。去系统里一查,locale 明明在。根因和 locale 毫无关系,但那几个小时是真的花掉了。这一条和 5.1 是同一个病:先让错误可见,再谈定位。
六、可复制的正确顺序
把上面所有坑按「必须发生在什么之前」重排,就是下面这七步。顺序本身就是信息:依赖修复必须发生在第一次 install 之前,否则你会拿到一次假成功,然后花两小时怀疑人生。
步骤 1:环境预检
lsb_release -a
nproc && free -h
df -h /
hostname -I
ping -c 2 github.com
前三项决定「能不能装」,第四项后面写 config.ini 要用,最后一项决定「下不下载得动」。国内访问 GitHub 常常超时,先解决虚拟机网络(NAT / DNS / 换源),不然每一步都卡在下载。
步骤 2:配 apt 源
curl -sSL https://raw.githubusercontent.com/CDUESTC-OpenAtom-Open-Source-Club/OpenTenBase-Packages/main/scripts/setup-apt.sh | sudo bash
脚本会探测系统版本代号、写入源、装 GPG 公钥。这里有个容易误判的点:源域名是 repo.blackevil217.com,看着完全不像官方。我一度怀疑被投毒,去翻了官方仓库才确认它确实是官方 CDN 镜像,而且脚本内置了指纹固定校验:
EXPECTED_FINGERPRINT=D8B2E316E1FF88EE178703549D8FA46F3A55D5F0
判断一个第三方源能不能用,看它有没有密钥校验机制,而不是看域名好不好听。反过来也要清醒:往系统里加第三方源的风险是真实的,apt install 触发的 postinst 以 root 执行,能干任何事。不放心就去核实指纹,别靠猜。
步骤 3:装包,然后立刻修依赖(最关键)
sudo apt-get update
sudo apt-get install -y opentenbase sshpass
然后是全量依赖体检,这一段建议直接抄——它一次把所有缺库都列出来,而不是让你跑一次挂一次:
BIN=/var/lib/opentenbase/install/opentenbase/5.21.8/bin
for f in "$BIN"/*; do
[ -f "$f" ] && [ -x "$f" ] || continue
miss=$(ldd "$f" 2>/dev/null | grep "not found" | awk '{print $1}' | tr '\n' ' ')
[ -n "$miss" ] && printf "%-28s MISSING: %s\n" "$(basename "$f")" "$miss"
done
没有输出才叫干净。按 5.2 / 5.4 的方法补齐:libssl1.1 装真包,libpqxx-7.9 与 libreadline.so.7 建软链,最后 sudo ldconfig。
步骤 4:用户、SSH 免密、home 骨架
sudo passwd opentenbase
sudo usermod -aG sudo opentenbase
sudo touch /var/lib/opentenbase/.bashrc
sudo chown opentenbase:opentenbase /var/lib/opentenbase/.bashrc
sudo su - opentenbase -c 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa -q && cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && ssh -o StrictHostKeyChecking=no localhost "echo ok"'
最后那句自连测试必须回 ok。部署工具靠 SSH 把安装包分发到各节点,单机场景就是连自己——免密没通,install 一定失败,而且报错信息不会直说。
步骤 5:打包成 tar.gz
sudo mkdir -p /data/opentenbase
cd /var/lib/opentenbase/install/opentenbase/5.21.8
sudo tar -zcf /data/opentenbase/opentenbase-5.21.8-i.x86_64.tar.gz *
package= 指向的必须是这个 .tar.gz 文件本身,不是目录。
步骤 6:config.ini(单机的关键:同一个 IP,靠端口区分)
[instance]
name=otb_single
type=distributed
package=/data/opentenbase/opentenbase-5.21.8-i.x86_64.tar.gz
[gtm]
master=192.168.74.134
[coordinators]
master=192.168.74.134
nodes-per-server=1
conf=/data/opentenbase/postgres.conf
[datanodes]
master=192.168.74.134
nodes-per-server=1
conf=/data/opentenbase/postgres.conf
[server]
ssh-user=opentenbase
ssh-password=你的密码
ssh-port=22
[log]
level=INFO
两个容易错的点:ssh-password 必须和 opentenbase 用户密码一致;postgres.conf 必须存在,哪怕是空文件。
步骤 7:install,然后停止相信它
sudo su - opentenbase -c "cd /data/opentenbase && ./opentenbase_ctl install -c config.ini"
sudo su - opentenbase -c "cd /data/opentenbase && ./opentenbase_ctl status"
看到这一坨才算数:
Node cn0001(192.168.74.134:11003) is Running
Node gtm0001(192.168.74.134:11000) is Running
Node dn0001(192.168.74.134:11006) is Running
[Result] Total: 3, Running: 3, Stopped: 0, Unknown: 0
三个端口 11000 / 11003 / 11006 全对上,才算真的起来了。
七、验收:什么才算「装好了」
Running 3/3 只能说明进程活着,不能说明分布生效了。我用四步验收,缺一步都不算完。
① 数据目录有 PG_VERSION。 三个节点各查一次,这是 initdb 真的跑过的唯一硬证据。
② 建一张分片表,指定分布键。
create table t_order (id bigint, uid bigint, amt numeric(12,2)) distribute by shard(uid);
③ 看数据到底落在哪个节点。xc_node_id 是分布式表的隐藏系统列,标记这行属于哪个 DN:
insert into t_order select g, g % 100, g * 1.5 from generate_series(1,10000) g;
select xc_node_id, count(*) from t_order group by 1 order by 1;
返回里能看到 xc_node_id 对应 dn0001,行数加起来等于 10000。这一列能查出来,说明表真的按节点切开了,不是建在 CN 上的普通表。
④ 看执行计划。explain 里出现 Remote Fast Query Execution,说明查询被下推到了 DN 侧执行,CN 只做汇总。
这里必须诚实一个边界:单 DN 环境下,按分布键查和按非分布键查,执行计划长得一模一样。 因为只有一个数据节点,无论怎么切都只有一个目标可去。所以「计划没差别」不能当成分片失效的证据,也不能当成配置成功的证据——它只是这个环境给不出结论。真想验证分片,得至少两个 DN。
类似的边界还有:单 DN 下你测不出数据倾斜,测不出跨节点事务的两阶段提交,测不出 GTM 故障转移。这些都是「1 GTM + 1 CN + 1 DN」这个形态天生给不了的,别用它的表现去推断生产环境的行为。
八、沉淀下来的三样东西
装完之后我把整个过程压缩成了可直接复用的资产,而不是留在聊天记录里。
一份部署手册:7 章正文 + 11 条排错条目。结构是「按顺序的步骤」+「按症状索引的坑」,前者给第一次装的人,后者给挂掉之后来查的人。
一份速查手册:端口对照表(GTM 11000 / CN 11003 / DN 11006)、目录对照表、常用命令(status / start / stop / psql 连接串)、以及验收 SQL。这一页是给「已经装好、但三个月后忘了怎么登录」的自己。
一套图形化启动器,三个脚本都放在虚拟机桌面上,双击可用:
脚本 | 作用 |
|---|---|
otb-ctl | 有序启停:先 GTM,再 CN / DN;停止反序。手动启停最容易犯的错就是顺序错 |
otb-launcher | zenity 面板,把 status / start / stop / 打开 psql 做成按钮 |
otb-sql | 一键开 psql,自动带上端口、用户、库名和 LD_LIBRARY_PATH |
顺带记一条版本差异,因为网上教程混着写,很容易抄错:
维度 | V2(老路) | V5(deb 包这条路) |
|---|---|---|
部署工具 | pgxc_ctl | opentenbase_ctl |
配置载体 | pgxc_ctl.conf(手写节点段) | config.ini(分节声明 master / nodes-per-server) |
节点定义 | 逐节点写 IP、端口、数据目录 | 只写 master,端口与目录由工具按实例规划生成 |
常见混淆 | 端口随手填 6666 / 5432 | 默认 11000 / 11003 / 11006,别混着用 |
九、控制终端是怎么构建的
第八节里提到的“一套图形化启动器”,就是桌面上那两个图标。这一节把它拆开:装了哪几个文件、权限怎么给、启停顺序怎么保证、GUI 在 26.04 上踩了哪些坑。
先看实物。四个脚本都在 /usr/local/bin,root 所有、0755:
文件 | 运行身份 | 作用 |
|---|---|---|
otb-ctl | root | 唯一的特权入口。9 个子命令:start / stop / restart / status / nodes / conn / info / log / kill |
otb-launcher | 桌面用户 | zenity 面板,只负责调 otb-ctl,不直接碰数据库 |
otb-sql | 桌面用户 | 开一个 ptyxis 窗口连 CN |
otb-launcher-fix | root | 写 sudoers 免密规则并自检 |
9.1 为什么手打命令不够用
四个理由,每条都是实际卡过的:
① opentenbase_ctl 必须带 -c <ini>。漏了它直接报 Failed to extract version from package name——这个报错跟“配置”看起来毫无关系,足够查半天。
② 必须以 opentenbase 身份执行。root 直接跑,数据目录属主会变成 root,下次切过去就写不进去。
③ start 报成功不等于起来了。上一节已经证明过:它只管拉起进程,不负责告诉你端口是否就绪、CN 能不能连上 GTM。
④ 顺序不能错。GTM 没起来就启 CN,CN 里的 gtm_proxy 连不上,psql 会挂在那里不动——不报错,只是没反应。
所以中间需要一层很薄的中间件,只做三件事:按顺序、等就绪、给结论。
9.2 权限模型:只放行一条命令
面板要能用,就得让桌面用户能启停集群;但“能启停集群”几乎等于“能给 opentenbase 当 root 用”。做法是把权限收在一根针尖上——只授权 otb-ctl 这一个可执行文件,参数不限,其它一律不放:
cat > /etc/sudoers.d/opentenbase-launcher <<EOF
# 允许 $TARGET_USER 免密码调用 OpenTenBase 集群控制脚本。
# 只放开这一个命令,不构成通用提权。
$TARGET_USER ALL=(root) NOPASSWD: /usr/local/bin/otb-ctl
EOF
落盘后还有两步不能省:
chmod 440 /etc/sudoers.d/opentenbase-launcher
chown root:root /etc/sudoers.d/opentenbase-launcher
visudo -c
echo "已写入:"
echo " $TARGET_USER ALL=(root) NOPASSWD: /usr/local/bin/otb-ctl"
if sudo -u "$TARGET_USER" sudo -n /usr/local/bin/otb-ctl nodes >/dev/null 2>&1; then
echo "自检通过:控制面板现在可以正常调用了。"
else
echo "自检失败,请检查上面的 visudo 输出。" >&2
exit 1
fi
sudoers 文件权限一旦宽松(比如 644),sudo 会直接忽略它,而且忽略时不给你明显提示;visudo -c 则负责在写坏语法时立刻报错,而不是等到下次 sudo 全盘失效。
这里有个特别容易写错的自检:判断“免密是否生效”,必须用真正要调用的那条命令去试。
# 自检:真正需要免密的是 otb-ctl 本身而不是任意命令,
# 因此这里必须用将要调用的那条命令来探测,不能用 `sudo -n true`(必然失败)。
if ! sudo -n "$CTL" nodes >/dev/null 2>&1; then
zenity --error --no-wrap \
--text="当前用户无法免密调用 otb-ctl,控制面板无法工作。\n\n请执行以下命令修复:\n\n sudo bash /usr/local/bin/otb-launcher-fix\n\n这会在 /etc/sudoers.d/opentenbase-launcher 中写入:\n $USER ALL=(root) NOPASSWD: /usr/local/bin/otb-ctl"
exit 1
fi
写成 sudo -n true 一定失败——因为白名单里只有 otb-ctl,true 不在其中。于是脚本判定授权没配好,面板一打开就弹错误框,而实际上一切正常。免密授权是“逐命令”的,自检也必须“逐命令”,不能拿一个通用命令去探。
9.3 otb-ctl 的三个硬设计
第一个:路径和身份全部写死在头部,脚本不接受“当前目录”“当前用户”这类隐含前提。
set -uo pipefail
OTB_USER="opentenbase"
RUN_HOME="/var/lib/opentenbase/install/opentenbase/5.0" # 实例实际使用的二进制
SYS_HOME="/usr/lib/opentenbase/5.0" # 官方安装路径
BIN="$RUN_HOME/bin"
CTL="$SYS_HOME/bin/opentenbase_ctl"
CONF="/var/lib/opentenbase/opentenbase_config.ini"
RUN_ROOT="/var/lib/opentenbase/run/instance"
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
echo "错误:otb-ctl 需要 root 权限,请使用 sudo 运行。" >&2
exit 1
fi
注意这里有两条路径并存:BIN 指向实例实际使用的二进制(/var/lib/opentenbase/install/opentenbase/5.0/bin),CTL 却指向系统包路径(/usr/lib/opentenbase/5.0/bin/opentenbase_ctl)。启停节点用前者,查询状态用后者——不是笔误,是两边各自能跑通的那一份。
第二个:节点不写死,靠目录和前缀自己认。实例目录下每个子目录就是一个节点,前缀决定类型,端口从该节点的 postgresql.conf 里反查:
as_otb() { su - "$OTB_USER" -c "$1" 2>&1; }
list_nodes() { ls -1 "$BASE" 2>/dev/null; }
# 节点类型:gtm0001 -> gtm ; cn0001 -> coordinator ; dn0001 -> datanode
node_kind() {
case "$1" in
gtm*) echo gtm ;;
cn*) echo coordinator ;;
dn*) echo datanode ;;
*) echo unknown ;;
esac
}
node_dir() { echo "$BASE/$1/data"; }
node_port() {
local d; d="$(node_dir "$1")"
awk '/^[[:space:]]*port[[:space:]]*=/{gsub(/[^0-9]/,"");print;exit}' \
"$d/postgresql.conf" 2>/dev/null
}
这样一来,以后加第二个 DN(dn0002)不需要改脚本,它会自己出现在 status、start、stop 的循环里。
第三个:启停一个节点是一对函数,就绪判定独立于“命令是否返回 0”。
start_one() {
local n="$1" d k
d="$(node_dir "$n")"; k="$(node_kind "$n")"
if [[ "$k" == "gtm" ]]; then
# GTM 重启前必须清掉陈旧 pid / 注册记录,否则会出现端口占用
rm -f "$d/gtm.pid" "$d/register.node"
as_otb "$BIN/gtm_ctl start -Z gtm -D $d -l $d/gtm_start.log"
else
as_otb "$BIN/pg_ctl start -Z $k -D $d -l $d/pg_start.log"
fi
}
stop_one() {
local n="$1" d k
d="$(node_dir "$n")"; k="$(node_kind "$n")"
if [[ "$k" == "gtm" ]]; then
as_otb "$BIN/gtm_ctl stop -Z gtm -D $d -m fast"
else
as_otb "$BIN/pg_ctl stop -Z $k -D $d -m fast"
fi
}
is_node_up() {
local n="$1" d
d="$(node_dir "$n")"
if [[ "$(node_kind "$n")" == "gtm" ]]; then
[[ -s "$d/gtm.pid" ]] && kill -0 "$(head -n1 "$d/gtm.pid")" 2>/dev/null
else
[[ -s "$d/postmaster.pid" ]] && kill -0 "$(head -n1 "$d/postmaster.pid")" 2>/dev/null
fi
}
is_node_up 才是关键:GTM 看 gtm.pid,CN/DN 看 postmaster.pid,再 kill -0 验一下进程还活着。因为 pg_ctl start 启动完就退出,退出码 0 只说明“我发过启动指令”,不说明进程真的活着。
9.4 顺序、就绪、自检:start 的三层循环
do_start 的骨架很简单:外层按 gtm → coordinator → datanode 分组遍历,内层遍历该类型的全部节点。顺序靠外层的数组决定,与目录名无关。
for kind in gtm coordinator datanode; do
for n in $(list_nodes); do
k="$(node_kind "$n")"
[[ "$k" == "$kind" ]] || continue
echo ">> 启动 $n ($k) ..."
out="$(start_one "$n")"
echo "$out" | sed 's/^/ /'
if is_node_up "$n"; then
echo " [OK] $n 已就绪"
else
echo " [失败] $n 未起来,日志:"
echo "$(sudo -u "$OTB_USER" tail -n 15 "$(log_file "$n" 2>/dev/null)" 2>/dev/null)" | sed 's/^/ /'
fail="$fail $n"
fi
echo
done
[[ -n "$(filter_kind $kind)" ]] && sleep 2
done
三个细节值得单独说:
· GTM 启动前先清陈旧状态。旧 gtm.pid 和 register.node 不清,新进程会因为登记信息对不上而占不到端口——这是“CN 连不上 GTM”的头号原因,也是我在重装阶段反复被打回的地方。
if [[ "$k" == "gtm" ]]; then
# GTM 重启前必须清掉陈旧 pid / 注册记录,否则会出现端口占用
rm -f "$d/gtm.pid" "$d/register.node"
as_otb "$BIN/gtm_ctl start -Z gtm -D $d -l $d/gtm_start.log"
else
as_otb "$BIN/pg_ctl start -Z $k -D $d -l $d/pg_start.log"
fi
· 每组之间 sleep 2。给 GTM 一点时间把节点列表广播出去,再启 CN;省掉这 2 秒,在慢一点的机器上就是随机失败。
· 失败时直接把日志尾巴摊出来,而不是丢一句“启动失败”让你自己找路径。日志文件的查找也做了兜底:先看 pg_start.log、gtm_start.log,找不到再取 pg_log/ 下最新的 csv。
启动完的自检分三级,一级比一级硬:进程级(pidfile + kill -0)、端口级(/dev/tcp 探测)、SQL 级(psql 跑一句 select 1)。
echo "---------------- 自检 ----------------"
local p
for n in $(list_nodes); do
p="$(node_port "$n")"
if [[ -n "$p" ]]; then
if timeout 5 bash -c "exec 3<>/dev/tcp/127.0.0.1/$p" 2>/dev/null; then
echo " 端口 $p ($n) : OPEN"
else
echo " 端口 $p ($n) : CLOSED"
fi
fi
done
# Coordinator 连通性测试
local cn="" cport=""
for n in $(list_nodes); do
[[ "$(node_kind "$n")" == "coordinator" ]] && { cn="$n"; cport="$(node_port "$n")"; }
done
if [[ -n "$cn" && -n "$cport" ]]; then
echo
echo ">> Coordinator SQL 连通性测试 ..."
if res="$(as_otb "$BIN/psql -h 127.0.0.1 -p $cport -U opentenbase -d postgres -Atc 'select 1'")" \
&& [[ "$res" == "1" ]]; then
echo " [OK] psql 连接正常,集群可用。"
ok=1
else
echo " [失败] psql 无法返回正确结果:$res"
fail="$fail $cn"
fi
fi
为什么非要做到 SQL 级?因为这台机器现在就有一个现成的反例:status 显示 Running 2/3——GTM 是 Stopped,CN 和 DN 两个进程都还在跑。这时连上去,psql 会卡住,不报错也不返回。进程活着,集群不可用。所以 otb-ctl start 的最后一关永远是 select 1 返回 1,而不是看 status 里的计数。
9.5 otb-launcher:zenity 4 的面板怎么写
26.04 自带的是 zenity 4.2.1(GTK4 + libadwaita),和网上大量 GTK3 时代的示例有实打实的差别:--list 的每一行必须自己拆成 KEY 操作 说明 三个参数,再用 --hide-column=1 --print-column=1 让返回值只有 KEY。
choice="$(zenity --list --title="$TITLE_BASE" \
--text="OpenTenBase 5.0 · 单集群 (1 GTM + 1 CN + 1 DN)\n请选择要执行的操作:" \
--column="KEY" --column="操作" --column="说明" \
--hide-column=1 --print-column=1 --hide-header \
--width=640 --height=440 \
status "查看集群状态" "列出 GTM / CN / DN 各节点运行情况" \
start "启动集群" "按 GTM → CN → DN 顺序启动并完成自检" \
stop "停止集群" "按 DN → CN → GTM 顺序安全停止" \
restart "重启集群" "常用于清理异常状态" \
sql "打开 SQL 控制台" "在终端中连接 Coordinator (psql)" \
log "查看节点日志" "排查启动失败、GTM 连接等问题" \
info "连接信息" "端口、数据目录、连接命令一览" \
quit "退出" "关闭控制面板" 2>/dev/null)"
用 KEY 而不是中文标题做返回值,是为了后面 case 好写,也避免改了文案就改不动分支。
其次是“长任务不能干等”:启动集群十几秒,面板得先弹脉动进度条,命令在后台跑,结束后把完整输出丢进文本窗口:
run_with_progress() {
local title="$1"; shift
local tmp; tmp="$(mktemp)"
( sudo -n "$CTL" "$@" >"$tmp" 2>&1; echo "EXIT_CODE=$?" >>"$tmp" ) &
local pid=$!
( while kill -0 "$pid" 2>/dev/null; do sleep 0.25; done ) |
zenity --progress --pulsate --auto-close \
--title="$title" --text="正在执行,请稍候…" \
--width=460 --no-cancel 2>/dev/null
wait "$pid" 2>/dev/null
local rc
rc="$(grep '^EXIT_CODE=' "$tmp" | tail -n 1 | cut -d= -f2)"
grep -v '^EXIT_CODE=' "$tmp" |
zenity --text-info --title="$title — 执行结果" \
--width=780 --height=520 --font="Monospace 11" 2>/dev/null
rm -f "$tmp"
return "${rc:-0}"
}
这段里有两个小心思:后台子 shell 把退出码 echo "EXIT_CODE=$?" 追加到临时文件末尾——因为跨子进程拿不到退出码,只能从文件里读;进度条靠 --pulsate --auto-close 加一个 while kill -0 的喂数循环,否则它不会自己消失。
状态页要把 otb-ctl status 的文本解析成表格,只认 Node ... is XXX 这种行,用 bash 的前缀/后缀裁剪拆出名字、地址、状态,不引外部解析器:
zenity --list --title="集群状态" \
--text="$summary\n$conn" \
--column="节点" --column="地址" --column="状态" \
--width=560 --height=340 --hide-header=0 \
"${rows[@]}" >/dev/null 2>&1
图形会话检查放在最前面:DISPLAY 和 WAYLAND_DISPLAY 都没有,就直接打印命令行用法并退出——通过 SSH 跑 GUI 是最常见的误用,与其抛一堆 GTK 警告,不如把正确用法摆在脸上。
9.6 otb-sql:为什么是 ptyxis
26.04(GNOME 50)的默认终端换成了 ptyxis,系统里既没有 gnome-terminal,也没有 xterm。所以任何 gnome-terminal -- bash -c 的老写法在这里都是直接失败。脚本做的是逐级 fallback:
launch() {
if command -v ptyxis >/dev/null 2>&1; then
ptyxis --new-window -T "OpenTenBase SQL 控制台" -- bash -c "$inner"
elif command -v xterm >/dev/null 2>&1; then
xterm -bg "#1a1a1a" -fg "#f0f0f0" -fa "Monospace" -fs 12 -geometry 100x32 \
-title "OpenTenBase SQL 控制台" -e bash -c "$inner"
elif command -v kgx >/dev/null 2>&1; then
kgx --title "OpenTenBase SQL 控制台" -- bash -c "$inner"
elif command -v gnome-terminal >/dev/null 2>&1; then
gnome-terminal --title "OpenTenBase SQL 控制台" -- bash -c "$inner"
else
printf '未找到可用的图形终端(ptyxis/xterm/kgx/gnome-terminal)。\n请直接在终端中执行:\n %s -h 127.0.0.1 -p %s -U %s -d %s\n' \
"$PSQL" "$port" "$USER_" "$DB" >&2
return 1
fi
ptyxis 的参数形式是 ptyxis --new-window -T "标题" -- bash -c "命令",-- 之后的参数原样交给要执行的程序。xterm 那条分支还顺手设了深色背景和对数等宽字体,因为它的默认外观在 GNOME 里实在突兀。
内层命令拼装时要显式带上库路径,这是整套东西里最容易被忽略的一行:
inner="echo 'OpenTenBase SQL 控制台 —— 输入 \\\\q 退出,\\\\c otb_demo 切换到演示库'
echo
export LD_LIBRARY_PATH=$RUN_HOME/lib
export PATH=$RUN_HOME/bin:\$PATH
export PSQL_HISTORY=~/.otb_psql_history
exec psql -h 127.0.0.1 -p $port -U $USER_ -d $DB"
psql 依赖 libpq.so.5、libreadline.so.7 这些装在非标准路径下的库;不设 LD_LIBRARY_PATH,双击图标的结果就是一个闪退的黑窗。
外层还有 setsid launch & disown:把终端窗口放进独立会话,否则面板一退出,ptyxis 跟着被回收,用户刚打开的 SQL 窗口瞬间消失。
9.7 两个 .desktop:菜单里能搜到,桌面上能双击
同一份 .desktop 放两处:~/.local/share/applications 进应用菜单、可被搜索;~/桌面 供双击。注意 26.04 中文环境的桌面目录是 ~/桌面,不是 ~/Desktop——第一版装到了不存在的 ~/Desktop,图标自然不会出现,而命令一行都没报错。
[Desktop Entry]
Type=Application
Version=1.0
Name=OpenTenBase 控制面板
GenericName=数据库集群管理
Comment=启动、停止与监控 OpenTenBase 单集群(1 GTM + 1 协调节点 + 1 数据节点)
Exec=/usr/local/bin/otb-launcher
Icon=opentenbase
Terminal=false
Categories=Development;Database;System;
Keywords=OpenTenBase;opentenbase;GTM;Coordinator;Datanode;cluster;数据库;集群;
StartupWMClass=zenity
X-GNOME-UsesNotifications=false
四个字段值得单独说:Terminal=false(脚本自己会开终端,不能再套一个黑框);StartupWMClass=zenity(把 zenity 的窗口归到这个图标下,否则任务栏多一个无名项);Icon=opentenbase(图标主题里的名字,装上包之后才有);Exec 指绝对路径,不依赖 PATH。
可执行位必须有——chmod 0755,否则双击只会打开一个文本编辑器。GNOME 另有“信任”标记 gio set <文件> metadata::trusted true;本机实测这个属性读出来是空的、图标仍然可用,是否必需取决于文件系统有没有启用 gvfs metadata,别把它当成唯一判据。
9.8 开机自启:一个 oneshot 单元
deb 包部署完之后默认没有任何自启机制:没有 systemd 单元、crontab 是空的、也没有 rc.local。每次开机都得手动 start,所以补一个单元:
[Unit]
Description=OpenTenBase Cluster (1 GTM + 1 CN + 1 DN)
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
User=opentenbase
Group=opentenbase
WorkingDirectory=/var/lib/opentenbase
Environment=HOME=/var/lib/opentenbase
Environment=OTB_HOME=/usr/lib/opentenbase/5.0
Environment=LD_LIBRARY_PATH=/usr/lib/opentenbase/5.0/lib
ExecStart=/usr/lib/opentenbase/5.0/bin/opentenbase_ctl start -c /var/lib/opentenbase/opentenbase_config.ini
ExecStop=/usr/lib/opentenbase/5.0/bin/opentenbase_ctl stop -c /var/lib/opentenbase/opentenbase_config.ini
TimeoutStartSec=300
TimeoutStopSec=300
Restart=no
[Install]
五个字段都是踩出来的,不是抄模板:
字段 | 为什么必须这样写 |
|---|---|
Type=oneshot + RemainAfterExit=yes | opentenbase_ctl 拉起三个子进程后自己就退出了;用 Type=forking 会被 systemd 判为“未 fork”而失败 |
Environment=LD_LIBRARY_PATH=... | systemd 不加载 shell 配置,不显式给就是 libpq.so.5 not found |
-c /var/lib/opentenbase/opentenbase_config.ini | 不带 ini 就报 Failed to extract version from package name |
User=opentenbase | 用 root 启会把数据目录属主搞乱 |
TimeoutStartSec=300 | 三节点冷启动要几十秒,默认 90 秒偏紧;超时被 kill 会留下半启动状态 |
还有一个隐蔽点:机器上同时存在两份二进制。systemd 单元用的是系统包路径 /usr/lib/opentenbase/5.0,而 otb-ctl 里的 BIN 指向实例拷贝。因此 otb-ctl 和 systemctl 不要混用:手动启停过之后,systemd 那边仍会显示 active,两边的状态认知就不一致了。混用之后补一次 systemctl restart opentenbase 归位。
9.9 没有桌面时,怎么把 GUI 拉起来看
全程只有 SSH 也能验证面板——用 Xvfb 造一个假的 X 服务器,再用 xdotool 点它:
sudo apt-get install -y xvfb matchbox-window-manager xdotool scrot
Xvfb :99 -screen 0 1280x900x24 &
DISPLAY=:99 matchbox-window-manager &
DISPLAY=:99 GDK_BACKEND=x11 /usr/local/bin/otb-launcher &
xdotool search --name "OpenTenBase 控制面板"
xdotool mousemove 320 200; xdotool click --repeat 2 1
scrot -o /tmp/shot.png
三个前提:必须有窗口管理器(matchbox),否则 xdotool 查到的窗口 class 是空的;GTK4 应用要显式 GDK_BACKEND=x11,否则它会去连不存在的 Wayland;别用键盘事件,key Down/Return 在 GTK4 的 ColumnView 上时序不可靠,用鼠标坐标点击才稳。
如果桌面已经在跑 Wayland,还可以把面板“送”进现有会话。直接 DISPLAY=:0 nohup ... & 起的进程会随 SSH 会话结束被回收;正确姿势是从用户会话里取环境,再用 systemd-run --user 脱离:
systemctl --user show-environment | grep -E 'WAYLAND|DISPLAY'
systemd-run --user --collect --unit=otb-panel \
--setenv=WAYLAND_DISPLAY=wayland-0 --setenv=DISPLAY=:0 \
/usr/local/bin/otb-launcher
截图优先走 VMware 自己的接口:vmrun -T ws -gu ling -gp *** captureScreen <vmx> out.png。GNOME 的 D-Bus 截图接口在 26.04 上会被直接拒绝(Screenshot is not allowed),而且 vmrun 调截图必须带 -gu/-gp,否则报“此虚拟机不允许执行匿名客户机操作”。
9.10 用起来是什么样
三条入口,指向同一套逻辑:桌面双击「OpenTenBase 控制面板」、应用菜单里搜 OpenTenBase、命令行 otb-ctl status。
打开面板是七个操作:查看状态、启动、停止、重启、打开 SQL 控制台、查看节点日志、连接信息。启动全程有进度条,失败会把该节点的日志尾巴直接摊在面前;SQL 控制台一个窗口连上 CN,提示符里带端口,不用再记 LD_LIBRARY_PATH 和那一串 -h -p -U -d。
这套东西的价值不在“好看”。它把前面九条坑固化成了默认行为:你不会忘记清 gtm.pid,不会忘记 -c ini,也不会在 GTM 没起来的时候去启 CN——因为它按 gtm → coordinator → datanode 的顺序走,并且每一步都要拿到 PID 才算数。
十、最后
三句话收尾:
一,不要在官方支持矩阵之外硬装却不做依赖体检。系统版本一变,预编译二进制的运行时假设就全变了。
二,不要相信自动化工具的成功提示。它的判据往往比你想的宽松得多,独立验收成本很低,PG_VERSION、pgxc_node、xc_node_id 三个点就够。
三,踩完坑立刻写成文档。坑是有时效的——今天记得清清楚楚,三个月后一定忘;而这台机器(以及任何一台 Ubuntu)再要重来,我只需要说一句「帮我部署 OpenTenBase」,那 9 条坑、端口表、验收 SQL 全都在。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。