背景
随着 OceanBase 的商业化及社区的推广,OceanBase 的客户群体数量越来越多、类型也越来越丰富,有关键行业的头部企业,也有社区的开发者及中小客户,更有与 OceanBase 形成良好合作的生态厂商。传统以 OCP 为中心,一体化管理和维护 OceanBase 的方式对于社区开发者和中小客户而言,管理成本高,平台依赖性强,并且缺乏在 OCP 故障不可用时的兜底管理方案。
此外,国内外的数据库厂大多有原厂开发的黑屏命令行管理工具,而 OceanBase 现有的黑屏工具,如obd、obctl、ob_admin等,目前还处于在诸如像社区开源、交付运维这样的独立场景应用,未完全推向用户侧,因独立发展的关系,安装及使用方式各异,暂未从整体上形成对外统一方案,为客户输出统一的价值。
为了解决上述痛点,免安装、成本低、易上手、运维手段丰富的 Oceanbase 内置开源管控工具 obshell 应运而生。
概述
obshell(OceanBase Shell)是 OceanBase 社区为运维人员 & 开发人员提供的免安装、开箱即用的本地集群命令行工具。obshell 支持集群运维,同时基于 OBServer 节点对外提供运维管理 API,实现不同生态产品对同一集群统一管理,从而方便第三方产品工具对接 OceanBase 数据库,同时降低了 OceanBase 集群管理难度和成本。
obshell 不需要额外安装。您通过任何方式安装 OceanBase-CE 数据库都可以在任何一个节点的工作目录的 bin
目录下看到 obshell 可执行文件。
此外,obshell 还提供了基于 Web 的交互式管理页面 —— OB-Dashboard,专为 OceanBase 集群和租户资源的管理而设计。它提供了关键资源的监控功能,帮助用户高效管理 OceanBase 集群。
OB-Dashboard 服务完全集成在 obshell 进程中,用户可以通过访问任何部署了 obshell 的服务地址来管理 OceanBase 集群。这种设计不仅简化了管理流程,还确保了极低的资源开销,为用户提供简单易用的管控体验。
obshell 通过本地 sqlite 数据库和其所管理的 OB 集群进行元数据的存放,以极低的额外成本引入实现对单个集群的部署和管控,提供丰富的白屏 + 黑屏的运维手段,供用户自由选择。
系统架构
上图展示了一个由 obshell 管控的 OB 集群的系统架构图。每个 obshell Agent 管理一个 observer,整个集群的运维动作由一个 obshell Agent 发起,多个 obshell Agent 共同协调推进。
负责管理同一 OB 集群中 observer 的 obshell Agent,构成 obshell 集群。每个 obshell 节点的元数据分为两部分,一部分存放在工作目录下的本地数据库中,这一部分元数据主要是 OB 集群部署和启动前,obshell 所需的信息,例如节点的身份信息和配置信息等。另一部分存放在所管理的 OB 集群中,这一部分元数据主要包括集群的拓扑信息,运维任务信息等。
由于 obshell 集群的元数据信息存放在所管理的 OB 集群中,避免了额外成本的引入。同时 obshell 运维的可用性仅依赖于 OB 集群可用,而当 OB 集群因多数节点宕机变得不可用时,obshell 还提供了应急启动的功能用于拉起 OB 集群,并且该运维动作不依赖 OB 集群,进一步提升了其可用性。
在 obshell 中,每个运维动作都由对应的任务完成,任务的调用时序图如下:
功能支持
截止到 obshell 4.3.0.1 为止,obshell 具备一下功能,囊括绝大部分 OceanBase 基础运维功能,能够满足日常管控所需:
模块 | 功能 | OB-Dashboard 是否支持 |
---|---|---|
集群管理 | 集群创建 | |
集群启停 | ![]() |
|
集群升级 | ![]() |
|
集群拓扑变更 | ||
参数管理 | ![]() |
|
集群资源统计信息 | ![]() |
|
租户管理 | 租户创建/删除 | ![]() |
租户锁定/解锁 | ![]() |
|
副本管理 | ![]() |
|
租户信息(primary zone、名称)修改 | ![]() |
|
租户参数管理 | ![]() |
|
资源规格管理 | ![]() |
|
合并管理 | ![]() |
|
租户信息查询 | ![]() |
|
备份恢复 | ![]() |
|
OBProxy 管理 | OBProxy 添加/启停/升级/销毁 |
系统集成
运维手段
除了可以通过直接请求 API 使用 obshell 管控 OB 集群外,还可以通过一下方式创建并管控 OB 集群。
- 命令行
- 官方文档介绍:https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000003382039
- obshell 提供包括但不限于 集群命令组、租户命令组、备份恢复命令组、任务命令组等命令,用于对 obshell 和 OB 集群进行管控。
- Python SDK
- github 仓库地址:https://github.com/oceanbase/OBShell-SDK-Python
- 针对于 obshell 的主要开放 API,提供了对应的同步方法和异步方法,您可以通过使用提供的方法,编写代码,实现自动化运维。此外,sdk 中还提供了一些聚合方法,使得您在引用 SDK 方法时更加的便捷。
- GO SDK
- github 仓库地址:https://github.com/oceanbase/OBShell-SDK-GO
- obshell-sdk-go 同样也提供了大量的运维方法,您可以通过在 GO 项目中使用合适的方法,实现独属于您自己的简易 OB 集群管控平台。
内部集成进展
除了使用 obshell 创建 OB 集群外,通过 OBD 或 OCP 部署并启动的 OB 集群,也会自动拉起 obshell 进程并接管 OB 集群。对于手动部署的 OB 集群,可通过命令行或 SDK 拉起 obshell 节点进行接管。
- OBD
V 2.6.0 开始,OBD 开始对 obshell 进程生命周期进行维护,目前,obd 已经通过集成 obshell-sdk-python 实现备份恢复功能,后续将逐步继续通过 obshell 替换原有的运维功能。对于已经支持 OB-Dashboard 的 OB 版本,OBD 在部署成功之后,会展示出 OB-Dashboard 的访问地址,复制到浏览器即可体验。
- OCP
V 4.3.3 开始,OCP 开始对 obshell 进程生命周期进行维护,后续将逐步通过 obshell 替换原用的运维功能。
- YUM INSTALL
在使用 systemctl 启动 OceanBase 服务时,会先启动 obshell 进程,再通过 obshell 拉起 OB 集群。
- OB-Dashboard
从 obshell 4.3.0.0 开始提供白屏运维 OB-Dashboard,访问集群中任一 obshell 的 web 服务端口即可访问。
工具对比
和其他的开源管控工具对比
obshell | obd | OCP 社区版 | |
---|---|---|---|
运维手段 | 命令行 + SDK + 白屏 | 命令行 | 白屏 |
功能 | OB 集群运维和租户运维以及 OBProxy 运维 | 支持 OB 集群和周边组件的安装和部署 | 支持多集群运维和主机运维 |
额外成本 | 本地 sqlite | / | meta db |
可用性 | 只要所管理的 OB 集群可用即可。 提供应急启动功能。 |
依赖于中控机可用 | 依赖于 meta db 可用 |
部署难度 | 无需单独安装,跟随 observer。可单独升级。 | 通过 ob-deploy rpm 安装 | 通过 ocp-all-in-one 进行安装 |
适用场景 | 集群规模小/低成本/自动化运维 | 集群统一安装部署 | 集群规模大 |
实践操作
下面将通过不同方式展示如何从 0 开始通过 obshell 部署一个 OB 集群。
Python SDK:
from obshell import initialize_nodes, start_obshell, NodeConfig, download_package, ClientSet
from obshell.auth import PasswordAuth
ips = [
"10.10.10.1",
"10.10.10.2",
"10.10.10.3",
]
work_dir = "/data/ob" # OBServer 的工作目录,不需要提前创建,初始化 OBServer 时会自动创建
def init_nodes(pkgs):
nodes_config = []
for _, ip in enumerate(ips):
node = NodeConfig(ip, work_dir, username="root")
nodes_config.append(node)
# # sdk 会自动判断是否使用 rsync,如果不想使用 rsync,传输效率比较低
# # 使用 rsync 传输文件时,需要在 SDK 执行机器和目标机器之间配置免密登录,同时两边都需要安装 rsync.
# # 你可以通过以下方法关闭 rsync 传输,但是不推荐这么做
# from obshell.ssh import USE_RSYNC
# USE_RSYNC = False
# 初始化节点
# 参数说明:
# rpm_packages: 所需要安装的软件包路径
# force_clean: 是否强制清理 OBServer 工作目录,如果为 True,则工作目录会被清空并 kill 掉所有相关进程
# configs: 集群配置项, 必须是 NodeConfig 类型的列表
initialize_nodes(rpm_packages=pkgs, force_clean=True, configs=nodes_config)
# 启动 obshell
start_obshell(nodes_config)
def create_cluster():
work_dir = "/data/ob" # OBServer 的工作目录,不需要提前创建,初始化 OBServer 时会自动创建
# 创建 sdk 客户端
client = ClientSet(ips[0])
# 填充各节点的配置
configs = {}
i = 0
for ip in ips:
i += 1
configs["%s:2886" % ip] = {
"zone": "zone%s" % (i % 3 + 1), # 打散到三个 zone
"home_path": work_dir,
# "data_dir": data_dir, # 数据目录
# "redo_dir": redo_dir, # 日志目录
"datafile_size": "24G", "cpu_count": "6",
"memory_limit": "16G", "system_memory": "4G", "log_disk_size": "24G",
"enable_syslog_recycle": "true", "enable_syslog_wf": "true"
}
# 创建集群
client.v1.agg_create_cluster(
configs,
"demo", # 集群名
1, # 集群ID
"demo", # root@sys 密码
agent_passwords={},
clear_if_failed=True
)
if __name__ == "__main__":
# 下载需要的包到本地
oceanbase_ce_libs = download_package("/data/download", "oceanbase-ce-libs")
oceanbase_ce = download_package("/data/download", "oceanbase-ce")
# 初始化各节点并拉起 obshell
init_nodes([oceanbase_ce_libs, oceanbase_ce])
# 创建 oceanbase 集群
create_cluster()
# 创建 obshell 客户端
client = ClientSet(ips[0], auth=PasswordAuth("demo"))
# 查询集群状态
print(client.v1.get_ob_info())
# 查询 obshell 节点状态
print(client.v1.get_status())
GO SDK:
package main
import (
"fmt"
"github.com/oceanbase/obshell-sdk-go/services"
v1 "github.com/oceanbase/obshell-sdk-go/services/v1"
"github.com/oceanbase/obshell-sdk-go/util"
)
var ips = []string{
"10.10.10.1",
"10.10.10.2",
"10.10.10.3",
}
var pkgs = []string{}
func initNode() error {
fmt.Println("initNode")
workDir := "/data/ob" // OBServer 的工作目录,不需要提前创建,初始化 OBServer 时会自动创建
nodeConfigs := make([]util.NodeConfig, 0)
for _, ip := range ips {
nodeConfigs = append(nodeConfigs, util.NewNodeConfig(ip, workDir, 2886))
}
// // util.UseRsync 标识是否使用 rsync 传输文件,默认为 false.
// // 使用 rsync 传输文件时,需要在 SDK 执行机器和目标机器之间配置免密登录,同时两边都需要安装 rsync.
// // 默认会使用 scp 和 并行sftp 传输文件, 速度和 rsync 差不多.
// // 你可以通过 util.UseRsync = true 开启 rsync 传输.
// util.UseRsync = false
// // util.CHUNK_SIZE 用来控制并行 sftp 传输文件的 chunk 大小,默认为 64M
// // util.CHUNK_SIZE 越小并行度越高,传输速度越快,但是会占用更多的 ssh 信道
// // 当前 OB 中单个文件的最大大小约为450M,其中64M可分为7-8个块.
// // 这将需要7-8个并发连接,由于sshd配置中的默认MaxSessions为10,因此此值 64M 是一个合理的默认值.
// // 如果你想提高sftp分块传输的性能,你可以减小这个值以增加并发连接的数量.
// // 但是需要相应地增加目标机器上 sshd config 的 MaxSessions 配置.
// // 同时还需要调大 util.PARALLEL_SFTP_MAX 的值,以保证并行度的上限.
// // util.PARALLEL_SFTP_MAX 使用来保护并行 sftp 的最大并发数不要超过目标机器的 MaxSessions, 默认为 8
// util.CHUNK_SIZE = 64 * 1024 * 1024
// util.PARALLEL_SFTP_MAX = 8
// // util.SCP_THRESHOLD 用来控制 scp 传输文件大小,大于该值的文件会使用 scp 传输,默认为 1M
// // 因为小文件使用内存备份批量传输效率更高,所以只有大于 util.SCP_THRESHOLD 的文件才会使用 scp 传输
// // 可以通过设置 util.SCP_THRESHOLD = 0 来关闭 scp
// util.SCP_THRESHOLD = 1 * 1024 * 1024
// 初始化节点
// 参数说明:
// rpmPackagePaths: 所需要安装的软件包路径
// forceClean: 是否强制清理 OBServer 工作目录,如果为 True,则工作目录会被清空并 kill 掉所有相关进程
// configs: 节点配置信息
err := util.InitNodes(pkgs, true, nodeConfigs...)
if err != nil {
return err
}
// 启动 obshell
return util.StartObshell(nodeConfigs...)
}
func CreateCluster() error {
fmt.Println("CreateCluster")
// 创建 client 实例,节点地址为 '10.10.10.1',端口为 2886。
// 所在集群的 root@sys 密码为 '****'。
client, err := services.NewClient(ips[0], 2886)
if err != nil {
return err
}
configs := map[string]string{
"datafile_size": "24G", "log_disk_size": "24G",
"cpu_count": "16", "memory_limit": "16G", "system_memory": "8G",
"enable_syslog_recycle": "true", "enable_syslog_wf": "true"}
// 创建请求
createClusteRequest := client.V1().NewCreateClusterRequest().
ConfigObserver(configs, v1.SCOPE_GLOBAL).
ConfigCluster("obshell-sdk-go", 12358).SetPassword("demo")
for idx, ip := range ips {
zone := fmt.Sprintf("zone%d", idx+1) // 打散到三个 zone
createClusteRequest.AddServer(ip, 2886, zone)
}
// 根据请求创建集群
return client.V1().CreateClusterWithRequest(createClusteRequest)
}
func main() {
// 下载与执行机器同架构的最新的 oceanbase-ce 和 oceanbase-ce-libs 包到本地
fmt.Println("DownloadPackage")
oceanbaseRpmDest, err := util.DownloadPackage("/data/download", util.PackageEntry{
Name: "oceanbase-ce",
})
if err != nil {
fmt.Println(err)
return
}
pkgs = append(pkgs, oceanbaseRpmDest)
oceanbaseLibsDest, err := util.DownloadPackage("/data/download", util.PackageEntry{
Name: "oceanbase-ce-libs",
})
if err != nil {
fmt.Println(err)
return
}
pkgs = append(pkgs, oceanbaseLibsDest)
err = initNode()
if err != nil {
fmt.Println(err)
return
}
err = CreateCluster()
if err != nil {
fmt.Println(err)
return
}
client, err := services.NewClientWithPassword(ips[0], 2886, "demo")
if err != nil {
fmt.Println(err)
return
}
// 查询集群状态
clusterStatus, err := client.V1().GetObInfo()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(clusterStatus)
// 查询节点状态
status, err := client.V1().GetStatus()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(status)
}
cli:
#!/bin/bash
WORK_DIR=/data/ob
ips=(
"10.10.10.1"
"10.10.10.2"
"10.10.10.3"
)
# 函数:处理单个IP的所有操作
process_ip() {
local ip=$1
echo "Processing $ip..."
# 清理和创建目录
if ! ssh root@$ip "rm -rf $WORK_DIR && mkdir -p $WORK_DIR" > /dev/null 2>&1; then
echo "Error: Failed to setup directory on $ip"
return 1
fi
# 下载RPM包
if ! ssh root@$ip "wget -q -P $WORK_DIR https://mirrors.aliyun.com/oceanbase/community/stable/el/7/x86_64/oceanbase-ce-4.3.5.2-102020032025070315.el7.x86_64.rpm" > /dev/null 2>&1; then
echo "Error: Failed to download oceanbase-ce RPM on $ip"
return 1
fi
if ! ssh root@$ip "wget -q -P $WORK_DIR https://mirrors.aliyun.com/oceanbase/community/stable/el/7/x86_64/oceanbase-ce-libs-4.3.5.2-102020032025070315.el7.x86_64.rpm" > /dev/null 2>&1; then
echo "Error: Failed to download oceanbase-ce-libs RPM on $ip"
return 1
fi
# 解压RPM包
if ! ssh root@$ip "cd $WORK_DIR && rpm2cpio oceanbase-ce-4.3.5.2-102020032025070315.el7.x86_64.rpm | cpio -idmv" > /dev/null 2>&1; then
echo "Error: Failed to extract oceanbase-ce RPM on $ip"
return 1
fi
if ! ssh root@$ip "cd $WORK_DIR && rpm2cpio oceanbase-ce-libs-4.3.5.2-102020032025070315.el7.x86_64.rpm | cpio -idmv" > /dev/null 2>&1; then
echo "Error: Failed to extract oceanbase-ce-libs RPM on $ip"
return 1
fi
echo "Successfully processed $ip"
return 0
}
# 并行处理所有IP
echo "Starting parallel processing of all IPs..."
pids=()
for ip in "${ips[@]}"; do
process_ip "$ip" &
pids+=($!)
done
# 等待所有进程完成并检查结果
failed_count=0
for i in "${!pids[@]}"; do
if ! wait "${pids[$i]}"; then
echo "Process for IP ${ips[$i]} failed"
((failed_count++))
fi
done
if [ $failed_count -gt 0 ]; then
echo "Error: $failed_count out of ${#ips[@]} nodes failed"
exit 1
fi
echo "All nodes processed successfully!"
echo "OceanBase deployment preparation completed."
# 启动obshell
for i in {0..2}; do
ssh root@${ips[$i]} "$WORK_DIR/home/admin/oceanbase/bin/obshell agent start"
ssh root@${ips[$i]} "$WORK_DIR/home/admin/oceanbase/bin/obshell cluster join -s ${ips[0]}:2886 -z zone$((i+1))"
done
# 初始化集群
ssh root@${ips[0]} "$WORK_DIR/home/admin/oceanbase/bin/obshell cluster init -n demo --rootpassword demo -o 'datafile_size=24G,cpu_count=6,memory_limit=16G,system_memory=4G,log_disk_size=24G,enable_syslog_recycle=true,enable_syslog_wf=true' "
在集群部署且初始化成功之后,可以通过请求任一节点 obshell 的 TCP 监听端口进入白屏运维页面,登陆密码即 sys 租户的 root 密码:
后续迭代计划
obshell 未来将持续演进运维能力,提升运维稳定性。
在下一迭代中,obshell 将支持 ORACLE 租户的创建和管理,白屏工具 OB-Dashboard 也将新增 监控/告警、备份/恢复等功能。同时, SDK 功能也将进一步完善,例如支持 client 容灾(自动切换请求的目标 Agent 节点),增强 SDK 的可靠性。
对于 obshell 的最终产品形态,有一个美好的展望。所有的开源工具都能够建立在 obshell 集群之上,各个生态产品通过 obshell 对同一集群进行统一管理,用户也能够通过集成 obshell 打造独属的 OceanBase 的运维平台。这一目标任重而道远,希望有更多的人加入到开源社区和 ob-operation sig 中,共建 obshell。