【Docker多节点部署】基于“配置即身份“理念的 Docker 多节点 StarRocks 高可用集群自动化部署方案

文章目录

    • 核心观点
    • 一、设计理念:为什么"配置即身份"如此重要
    • [二、配置生成:从两个 IP 到完整配置的自动化转换](#二、配置生成:从两个 IP 到完整配置的自动化转换)
      • [2.1 角色判断:简单的比较,深远的影响](#2.1 角色判断:简单的比较,深远的影响)
      • [2.2 端口管理:冲突检测与自动分配](#2.2 端口管理:冲突检测与自动分配)
      • [2.3 配置文件的生成:环境变量的模板化](#2.3 配置文件的生成:环境变量的模板化)
    • 三、启动流程:自动化如何消除手动操作的复杂性
      • [3.1 Leader FE 的启动:直接而简单](#3.1 Leader FE 的启动:直接而简单)
      • [3.2 Follower FE 的启动:等待、检查、注册的自动化](#3.2 Follower FE 的启动:等待、检查、注册的自动化)
      • [3.3 BE 节点的启动:依赖 FE 集群的自动化处理](#3.3 BE 节点的启动:依赖 FE 集群的自动化处理)
    • [四、Docker Compose 配置:统一服务定义的力量](#四、Docker Compose 配置:统一服务定义的力量)
      • [4.1 Host 网络模式:为什么选择它](#4.1 Host 网络模式:为什么选择它)
      • [4.2 卷挂载策略:数据持久化与配置隔离](#4.2 卷挂载策略:数据持久化与配置隔离)
      • [4.3 健康检查:自动化监控的基础](#4.3 健康检查:自动化监控的基础)
    • 五、实际应用:从理论到实践的完整路径
      • [5.1 配置阶段:最小化的配置输入](#5.1 配置阶段:最小化的配置输入)
      • [5.2 生成阶段:一键生成所有配置](#5.2 生成阶段:一键生成所有配置)
      • [5.3 启动阶段:自动化的依赖处理](#5.3 启动阶段:自动化的依赖处理)
      • [5.4 扩展场景:添加新节点的简单性](#5.4 扩展场景:添加新节点的简单性)
    • 六、设计优势的深层分析
      • [6.1 消除人为错误的可能性](#6.1 消除人为错误的可能性)
      • [6.2 配置的一致性保证](#6.2 配置的一致性保证)
      • [6.3 扩展性的天然支持](#6.3 扩展性的天然支持)
    • 七、关键操作与故障排查
      • [7.1 集群状态监控](#7.1 集群状态监控)
      • [7.2 常见问题与解决方案](#7.2 常见问题与解决方案)
      • [7.3 手动操作命令](#7.3 手动操作命令)
    • 八、总结:设计思想的统一性

核心观点

StarRocks-HA 的核心价值在于:通过"配置即身份"的设计理念,将复杂的集群部署简化为两个 IP 地址的配置,同时通过自动化脚本实现了从配置生成到节点注册的完整自动化流程,使得任意规模的集群部署都变得简单可控。

这意味着,无论你需要部署 3 个节点还是 30 个节点,都只需要在每个节点上配置 MASTER_HOSTLOCAL_HOST 两个变量,然后运行 init-env.sh 脚本,系统就能自动识别节点角色、分配端口、生成配置,并在启动时自动完成节点注册。这种设计不仅降低了部署复杂度,更重要的是消除了人为错误的可能性,让集群扩展变得像添加一台新机器一样简单。

一、设计理念:为什么"配置即身份"如此重要

传统的集群部署方案通常需要为每个节点指定明确的角色(如 master、slave1、slave2),这种方式的局限性在于:当你需要扩展到第 4 个、第 5 个节点时,就必须修改部署脚本,增加新的节点类型定义。这不仅增加了维护成本,更重要的是,这种硬编码的方式违背了"配置驱动"的设计原则。

StarRocks-HA 采用了完全不同的思路:节点的身份不是通过显式的角色名称来定义,而是通过比较两个 IP 地址自动推断出来的 。具体来说,如果 LOCAL_HOST 等于 MASTER_HOST,那么这个节点就是 Leader;否则,它就是 Follower。这种设计的巧妙之处在于,它建立了一个简单的数学关系:节点角色 = f(MASTER_HOST, LOCAL_HOST),这个函数的结果只有两种可能,但足以支撑任意数量的节点。

复制代码
┌─────────────────────────────────────────────────────────┐
│              StarRocks 高可用集群架构                    │
├─────────────────────────────────────────────────────────┤
│                                                          │
│  FE 集群(元数据与控制层)                                │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ...         │
│  │ FE-0     │  │ FE-1     │  │ FE-2     │              │
│  │ (Leader) │  │(Follower)│  │(Follower)│              │
│  └──────────┘  └──────────┘  └──────────┘              │
│                                                          │
│  BE 集群(数据存储与计算层)                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ...         │
│  │ BE-0     │  │ BE-1     │  │ BE-2     │              │
│  │ (数据副本)│  │ (数据副本)│  │ (数据副本)│              │
│  └──────────┘  └──────────┘  └──────────┘              │
│                                                          │
└─────────────────────────────────────────────────────────┘

更重要的是,这种设计带来的不仅仅是配置的简化。当我们深入思考集群部署的本质时,会发现真正需要区分的是"谁是 Leader"和"谁是 Follower",而不是"这是第几个节点"。通过 IP 地址的比较,我们实际上是在回答一个更本质的问题:"这个节点是否应该承担 Leader 的责任?"这种设计哲学使得系统具备了天然的扩展性:添加新节点时,你不需要修改任何全局配置,只需要在新节点上设置自己的 LOCAL_HOST系统就能自动识别它的角色


二、配置生成:从两个 IP 到完整配置的自动化转换

理解了"配置即身份"的设计理念后,我们来看看系统是如何将这两个简单的 IP 地址转换为完整的节点配置的。这个过程由 init-env.sh 脚本完成,它执行了一系列自动化的决策和转换。

2.1 角色判断:简单的比较,深远的影响

脚本首先执行的是角色判断,这是整个配置生成流程的基础:

bash 复制代码
if [ "$LOCAL_HOST" == "$MASTER_HOST" ]; then
    FE_INDEX=0  # Leader
else
    FE_INDEX=1  # Follower
fi

这个看似简单的判断,实际上决定了后续所有的配置行为。对于 Leader 节点(FE_INDEX=0),系统会生成包含完整 Master 信息的配置;对于 Follower 节点(FE_INDEX=1),系统会生成指向 Master 的 helper 配置,以便在首次启动时能够从 Leader 同步元数据。

这种设计的好处是,每个节点的配置都是自包含的:它知道自己是谁,也知道应该连接到谁。这种自包含性使得节点可以独立启动,不需要依赖外部的配置中心或服务发现机制。

2.2 端口管理:冲突检测与自动分配

在确定了节点角色后,脚本需要为节点分配端口。这里的设计考虑是:虽然每个物理机上的端口是独立的(因为使用 host 网络模式),但在同一台物理机上可能存在多个服务实例,因此需要检测端口占用情况。

bash 复制代码
# 检查端口是否被占用
check_port_in_use() {
    # 使用 netstat/ss/lsof 检查端口占用
}

# 如果被占用,自动分配可用端口
if check_port_in_use $http_port; then
    http_port=$(find_available_port $http_port)
fi

这种自动端口分配机制确保了即使在复杂的部署环境中,也不会因为端口冲突导致启动失败。更重要的是,它使得同一台物理机上可以运行多个 StarRocks 实例,这对于测试环境或资源受限的场景特别有用。

2.3 配置文件的生成:环境变量的模板化

端口分配完成后,脚本会生成两个关键的环境变量文件:starrocks-fe.envstarrocks-be.env。这些文件包含了容器运行时所需的所有配置信息。

starrocks-fe.env 示例(Leader 节点):

bash 复制代码
HOST_IP=192.168.1.10
FE_INDEX=0
HTTP_PORT=8030
RPC_PORT=9020
FE_QUERY_PORT=9030
EDIT_LOG_PORT=9010
MASTER_FE_HOST=192.168.1.10
MASTER_FE_EDIT_LOG_PORT=9010
MASTER_FE_QUERY_PORT=9030
DEFAULT_REPLICATION_NUM=3

starrocks-be.env 示例

bash 复制代码
HOST_IP=192.168.1.10
BE_INDEX=0
BE_PORT=9060
BE_HTTP_PORT=8040
HEARTBEAT_PORT=9050
BRPC_PORT=8060
STARLET_PORT=9070
FE_HOST=192.168.1.10
FE_QUERY_PORT=9030

这些环境变量文件的设计遵循了一个重要原则:配置与代码分离 。所有的配置信息都通过环境变量传递给容器,而不是硬编码在脚本中。这种设计使得配置的修改变得简单:你只需要修改 .env 文件,而不需要修改 Docker Compose 文件或启动脚本。


三、启动流程:自动化如何消除手动操作的复杂性

配置生成完成后,真正的挑战在于启动流程。在传统的集群部署中,启动顺序是至关重要的:必须先启动 Leader,然后启动 Follower,最后启动 BE。如果顺序错误,或者某个节点启动失败,整个流程就需要重新开始。StarRocks-HA 通过 entrypoint 脚本中的自动化逻辑,完全消除了这种复杂性。

3.1 Leader FE 的启动:直接而简单

对于 Leader 节点,启动流程是直接的:它不需要等待任何其他节点,也不需要注册到任何集群,因为它本身就是集群的起点。

bash 复制代码
# fe-entrypoint.sh (Leader 路径)
configure_fe_for_host_mode  # 配置 host 网络模式
exec $STARROCKS_HOME/bin/start_fe.sh --logconsole  # 直接启动

这个简单的流程背后,实际上隐藏了一个重要的设计决策:Leader 节点是自举的。它不需要任何外部依赖就能启动,这确保了集群的初始化总是从一个确定的状态开始。

3.2 Follower FE 的启动:等待、检查、注册的自动化

Follower 节点的启动流程则复杂得多,但也正是这种复杂性体现了自动化的价值。Follower 需要完成三个关键步骤:等待 Leader 就绪、检查是否已注册、如果未注册则自动注册。

bash 复制代码
# fe-entrypoint.sh (Follower 路径)
configure_fe_for_host_mode

if [ $FE_INDEX -gt 0 ]; then
    # 1. 等待 Leader 就绪
    wait_for_master_fe
    
    # 2. 检查是否已注册
    if ! check_fe_registered; then
        # 3. 自动注册
        add_fe_to_cluster
    fi
    
    # 4. 启动服务
    if check_fe_initialized; then
        exec start_fe.sh --logconsole
    else
        exec start_fe.sh --helper $MASTER_FE_HOST:$MASTER_FE_EDIT_LOG_PORT --logconsole
    fi
fi

这个流程中的每个步骤都包含了重试机制和错误处理。例如,wait_for_master_fe 函数会循环检查 Leader 是否就绪,最多重试 60 次,每次等待 5 秒。这意味着即使 Leader 启动较慢,Follower 也会自动等待,而不是立即失败。

更重要的是,脚本会检查节点是否已经注册到集群。这个检查机制使得脚本具有了幂等性:即使脚本被多次执行,也不会导致重复注册。这种设计对于容器的重启场景特别重要:如果容器因为某种原因重启,脚本会检测到节点已经注册,直接启动服务,而不是尝试重新注册。

3.3 BE 节点的启动:依赖 FE 集群的自动化处理

BE 节点的启动流程与 Follower FE 类似,但有一个关键区别:BE 需要等待的是整个 FE 集群就绪,而不仅仅是 Leader。这是因为 BE 需要能够连接到 FE 集群来注册自己。

bash 复制代码
# be-entrypoint.sh
configure_be_for_host_mode
wait_for_fe  # 等待 FE 集群就绪
if ! check_be_registered; then
    register_be_to_cluster  # 自动注册
fi
exec start_be.sh --logconsole

这个流程的设计体现了另一个重要的原则:依赖关系的显式处理。BE 节点明确知道自己依赖于 FE 集群,因此会主动等待 FE 就绪。这种设计使得启动顺序变得灵活:你可以先启动所有 FE 节点,然后再启动所有 BE 节点,系统会自动处理依赖关系。


四、Docker Compose 配置:统一服务定义的力量

在理解了配置生成和启动流程后,我们来看看 Docker Compose 配置是如何支撑这种设计的。StarRocks-HA 采用了"统一服务定义"的策略:所有节点使用相同的服务名称(starrocks-festarrocks-be),但通过不同的环境变量文件来区分。

4.1 Host 网络模式:为什么选择它

Docker Compose 配置中最关键的设计决策是使用 host 网络模式:

yaml 复制代码
network_mode: "host"

这个决策的原因是多方面的。首先,在跨物理机部署的场景中,每个节点运行在不同的宿主机上,使用 host 网络模式可以让节点直接使用宿主机的 IP 地址,避免了 Docker 网络带来的复杂性。其次,StarRocks 的节点间通信需要知道对方的真实 IP 地址,使用 host 网络模式使得这个需求变得简单直接。

更重要的是,host 网络模式使得端口管理变得直观:每个物理机上的端口是独立的,因此可以使用相同的默认端口(如 8030、9030 等),而不需要为每个节点分配不同的端口。这大大简化了配置管理。

4.2 卷挂载策略:数据持久化与配置隔离

卷挂载策略的设计体现了数据持久化和配置隔离的平衡:

yaml 复制代码
volumes:
  - './run/bin:/opt/starrocks/bin:ro'  # FE: 只读
  - './run/bin:/opt/starrocks/bin:rwx' # BE: 读写
  - './data/fe/meta:/opt/starrocks/fe/meta:rw'
  - './data/be/storage:/opt/starrocks/be/storage:rw'
  - './conf/fe:/opt/starrocks/fe/conf:rw'
  - './conf/be:/opt/starrocks/be/conf:rw'

这里有几个关键点:首先,FE 的脚本目录是只读的,而 BE 的是读写的。这是因为 BE 的健康检查脚本需要执行权限,而 FE 不需要。其次,数据和配置目录都是读写的,这使得容器可以持久化数据和动态修改配置。

4.3 健康检查:自动化监控的基础

健康检查配置使得 Docker 能够自动监控服务的状态:

yaml 复制代码
# FE 健康检查
healthcheck:
  test: [ "CMD", "curl", "-f", "http://localhost:${HTTP_PORT:-8030}/api/bootstrap" ]
  interval: 10s
  timeout: 5s
  retries: 30
  start_period: 30s

# BE 健康检查
healthcheck:
  test: [ "CMD", "/opt/starrocks/bin/be-health-check.sh" ]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 40s

健康检查的设计考虑了服务的启动时间:FE 需要 30 秒的启动期,BE 需要 40 秒。这种设计确保了服务有足够的时间完成初始化,避免了启动期间的误报。


五、实际应用:从理论到实践的完整路径

理解了设计理念和实现机制后,我们来看看如何在实际场景中应用这些知识。整个部署流程可以概括为三个步骤:配置、生成、启动。

5.1 配置阶段:最小化的配置输入

在每个物理机上,你只需要创建一个 app.env 文件:

Master 节点(192.168.1.10)

bash 复制代码
KAFKA_HOST=127.0.0.1
DEFAULT_REPLICATION_NUM=3
MASTER_HOST=192.168.1.10
LOCAL_HOST=192.168.1.10

Follower 节点(192.168.1.11)

bash 复制代码
KAFKA_HOST=127.0.0.1
DEFAULT_REPLICATION_NUM=3
MASTER_HOST=192.168.1.10  # 所有节点指向同一个 Master
LOCAL_HOST=192.168.1.11   # 每个节点配置自己的 IP

这个配置文件的简洁性体现了"配置即身份"的设计理念:你不需要指定节点类型、不需要分配端口、不需要配置复杂的网络参数,只需要告诉系统"Master 是谁"和"我是谁"。

5.2 生成阶段:一键生成所有配置

配置完成后,在每个物理机上运行:

bash 复制代码
cd lakehouse/starrocks-HA
./init-env.sh

这个脚本会自动完成所有配置生成工作,包括角色判断、端口分配、配置文件生成等。整个过程是幂等的:即使多次运行,也不会产生冲突或错误。

5.3 启动阶段:自动化的依赖处理

启动阶段的关键在于理解自动化的依赖处理机制。你不需要严格按照"先 Leader 后 Follower"的顺序启动,因为 Follower 的启动脚本会自动等待 Leader 就绪。同样,BE 节点会自动等待 FE 集群就绪。

推荐的启动顺序(虽然不严格必需):

bash 复制代码
# 1. 启动 Leader FE
docker-compose up -d starrocks-fe  # 在 Master 节点上

# 2. 启动 Follower FE(可以并行启动)
docker-compose up -d starrocks-fe  # 在各个 Follower 节点上

# 3. 启动所有 BE(可以并行启动)
docker-compose up -d starrocks-be  # 在所有节点上

这种启动顺序的灵活性来自于 entrypoint 脚本中的自动化等待机制。即使你打乱了顺序,系统也会自动处理依赖关系。

5.4 扩展场景:添加新节点的简单性

当需要添加新节点时,整个流程的简单性就体现出来了。你只需要:

  1. 在新物理机上创建 app.env,设置 MASTER_HOSTLOCAL_HOST
  2. 运行 ./init-env.sh 生成配置
  3. 启动服务:docker-compose up -d starrocks-fe starrocks-be

新节点会自动识别自己的角色(Follower),自动注册到集群,整个过程无需任何手动干预。这种扩展的简单性正是"配置即身份"设计理念的最大价值体现。


六、设计优势的深层分析

通过前面的分析,我们已经看到了 StarRocks-HA 的设计如何简化了部署流程。现在让我们深入分析这些设计带来的更深层的优势。

6.1 消除人为错误的可能性

传统的集群部署中,人为错误是导致部署失败的主要原因之一。例如,错误地指定了节点角色、忘记了启动顺序、配置了错误的 IP 地址等。StarRocks-HA 通过自动化消除了这些错误的可能性:

  • 角色判断自动化:节点角色由 IP 地址比较自动确定,无法出错
  • 端口分配自动化:端口冲突自动检测和解决,无需手动分配
  • 节点注册自动化:启动脚本自动处理注册,无需手动执行 SQL 命令

这种自动化不仅提高了部署效率,更重要的是提高了部署的可靠性。

6.2 配置的一致性保证

在传统的部署方案中,不同节点的配置往往分散在多个文件中,容易出现不一致的情况。StarRocks-HA 通过统一的配置生成流程,确保了配置的一致性:

  • 统一的配置源 :所有配置都从 app.env 生成,确保来源一致
  • 统一的生成逻辑 :所有节点使用相同的 init-env.sh 脚本,确保逻辑一致
  • 统一的验证机制:所有配置都经过相同的验证流程,确保格式一致

这种一致性保证了集群中所有节点的配置都是可预测和可复现的。

6.3 扩展性的天然支持

"配置即身份"的设计使得系统天然支持扩展。当你需要添加新节点时,你不需要修改任何全局配置,只需要在新节点上设置自己的 LOCAL_HOST。这种设计使得集群的扩展变得像添加一台新机器一样简单,而不会影响现有节点。

更重要的是,这种扩展性是无上限的。理论上,你可以添加任意数量的节点,只要它们能够连接到 Master 节点。这种设计使得 StarRocks-HA 既适合小规模部署(3-5 个节点),也适合大规模生产环境(数十个节点)。


七、关键操作与故障排查

理解了设计理念和实现机制后,我们还需要掌握一些关键操作和故障排查技巧,以便在实际使用中能够快速解决问题。

7.1 集群状态监控

集群状态监控是运维的基础。StarRocks 提供了两个关键的 SQL 命令来查看集群状态:

查看 FE 集群状态

sql 复制代码
SHOW PROC '/frontends';

这个命令会显示所有 FE 节点的详细信息,包括 IP 地址、端口、角色(LEADER/FOLLOWER)、是否存活等。关键字段包括:

  • Role:节点角色,应该有一个 LEADER 和多个 FOLLOWER
  • Alive:是否存活,所有节点都应该是 true
  • ReplayedJournalId:已重放的日志 ID,Follower 应该与 Leader 接近

查看 BE 集群状态

sql 复制代码
SHOW PROC '/backends';

这个命令会显示所有 BE 节点的详细信息,包括 IP 地址、心跳端口、是否存活、存储容量等。关键字段包括:

  • Alive:是否存活,所有节点都应该是 true
  • SystemDecommissioned:是否系统下线,应该是 false
  • ClusterDecommissioned:是否集群下线,应该是 false

7.2 常见问题与解决方案

在实际使用中,可能会遇到一些常见问题。理解这些问题的原因和解决方案,有助于快速定位和解决问题。

问题 1:FE Follower 无法添加到集群

这个问题的常见原因包括:

  • Leader FE 未启动或不可访问:检查 Leader 节点的 FE 服务状态和网络连通性
  • helper 配置错误:检查 starrocks-fe.env 中的 MASTER_FE_HOSTMASTER_FE_EDIT_LOG_PORT
  • 网络不通:检查防火墙规则,确保 Follower 能够访问 Leader 的端口(9010、9020、9030)

问题 2:BE 节点无法注册

这个问题的常见原因包括:

  • FE 集群未就绪:确保所有 FE 节点都已启动并正常运行
  • FE_HOST 配置错误:检查 starrocks-be.env 中的 FE_HOSTFE_QUERY_PORT
  • 心跳端口被占用:检查 BE 节点的 9050 端口是否被占用

问题 3:端口冲突

虽然脚本会自动检测和分配端口,但在某些情况下仍可能出现端口冲突。解决方法:

  • 检查端口占用:ss -antp | grep <port>
  • 手动修改配置文件中的端口
  • 停止占用端口的服务

7.3 手动操作命令

虽然系统已经实现了自动化,但在某些特殊情况下,你可能需要手动执行一些操作:

手动添加 FE Follower

sql 复制代码
ALTER SYSTEM ADD FOLLOWER "192.168.1.13:9010";

手动添加 BE

sql 复制代码
ALTER SYSTEM ADD BACKEND "192.168.1.13:9050";

删除节点

sql 复制代码
-- 删除 FE Follower
ALTER SYSTEM DROP FOLLOWER "192.168.1.13:9010";

-- 删除 BE
ALTER SYSTEM DROP BACKEND "192.168.1.13:9050";

这些手动命令主要用于故障恢复或特殊场景,在正常情况下,自动化脚本已经处理了这些操作。


八、总结:设计思想的统一性

通过前面的深入分析,我们可以看到 StarRocks-HA 的设计思想具有高度的统一性。无论是"配置即身份"的理念,还是自动化的配置生成和启动流程,都服务于同一个目标:让集群部署变得简单、可靠、可扩展

这种统一性体现在多个层面:

  1. 配置层面的统一:所有节点使用相同的配置结构,通过 IP 地址比较区分角色
  2. 服务层面的统一:所有节点使用相同的 Docker Compose 服务定义,通过环境变量区分
  3. 流程层面的统一:所有节点遵循相同的启动流程,通过角色判断选择不同的执行路径
  4. 扩展层面的统一:添加新节点遵循相同的流程,无需修改全局配置

这种统一性使得系统具备了高度的可预测性和可维护性。无论是部署新集群,还是扩展现有集群,都遵循相同的模式和流程,降低了学习和维护成本。

更重要的是,这种设计思想可以应用到其他类似的集群部署场景中。无论是数据库集群、消息队列集群,还是其他分布式系统,都可以借鉴"配置即身份"的理念和自动化的流程设计,来实现简单、可靠、可扩展的部署方案。

因此,StarRocks-HA 不仅仅是一个 StarRocks 的部署工具,更是一个展示如何通过设计思想简化复杂系统部署的优秀案例。它证明了,通过深入思考问题的本质,我们可以将看似复杂的集群部署问题,转化为简单的配置和自动化的流程,从而大大提高系统的可用性和可维护性。

相关推荐
piepis2 小时前
Doris Docker 完整部署指南
数据仓库·docker·doris·容器部署
先知后行。3 小时前
C/C++八股文
java·开发语言
Yeats_Liao3 小时前
时序数据库系列(五):InfluxDB聚合函数与数据分析
java·后端·数据分析·时序数据库
又是忙碌的一天4 小时前
Java IO流
java·开发语言
程序员buddha4 小时前
springboot-mvc项目示例代码
java·spring boot·mvc
不懂英语的程序猿4 小时前
【Java 工具类】Java通过 TCP/IP 调用斑马打印机(完整实现)
java
多多*6 小时前
分布式系统中的CAP理论和BASE理论
java·数据结构·算法·log4j·maven
sg_knight6 小时前
Docker 实战:如何限制容器的内存使用大小
java·spring boot·spring·spring cloud·docker·容器·eureka
电棍2336 小时前
在docker a100云服务器运行vulkan->sapien->robotwin的经验(报错segmentation fault)
运维·docker·容器