本文基于 Fast DDS QoS 工程实践提炼而成,适用于任何 C/C++、Go、Rust 等编译型语言项目的容器化。核心思路:先理解产物 → 确认环境 → 分析依赖 → 编译验证 → 收集库 → 编写 Dockerfile → 构建检查 → 运行测试 → 打包部署


一、整体思路:容器化的"八步走"战略

容器化不是简单地写个 Dockerfile,而是一条分层递进的工程流水线。每一层都依赖上一层正确完成,任何一层有缺口,后面都会出问题。

┌─────────────────────────────────────────────────┐
│  第 8 层:打包 & 部署(docker save / push)      │
├─────────────────────────────────────────────────┤
│  第 7 层:容器内运行 & 验收                      │
├─────────────────────────────────────────────────┤
│  第 6 层:构建后检查(架构 / 依赖 / 启动)       │
├─────────────────────────────────────────────────┤
│  第 5 层:编写 Dockerfile                        │
├─────────────────────────────────────────────────┤
│  第 4 层:收集运行时动态库(runtime-libs/)      │
├─────────────────────────────────────────────────┤
│  第 3 层:主机上编译 & 验证                      │
├─────────────────────────────────────────────────┤
│  第 2 层:分析依赖(file / ldd / 头文件)        │
├─────────────────────────────────────────────────┤
│  第 1 层:确认环境(架构 / Docker / 基础镜像)    │
└─────────────────────────────────────────────────┘

核心原则:程序必须在目标架构上编译,或者使用与目标架构一致的交叉编译环境。 不能在 x86 机器上编译 ARM 程序,然后塞进 ARM 镜像。


二、第一步:确认环境 —— 一切的起点

2.1 确认目标机器架构

uname -m
输出 含义
aarch64 / arm64 ARM 64 位(如 Kylin ARM、飞腾、鲲鹏)
x86_64 Intel/AMD 64 位
riscv64 RISC-V 64 位

⚠️ 关键: 后续编译、基础镜像、动态库全部必须与目标架构一致。

2.2 确认 Docker 可用

docker version

检查 Client 和 Server 都正常,重点关注 Server 端版本。

2.3 确认基础镜像

# 列出已有镜像
docker images --format '{{.Repository}}:{{.Tag}}'

# 检查特定镜像的架构
docker image inspect <镜像名> --format '{{.Os}}/{{.Architecture}}'

选择基础镜像的原则:

  • 优先使用目标环境已有的内网镜像(避免构建时联网超时)
  • 确认架构匹配(linux/arm64linux/amd64 等)
  • 不要盲目写 FROM ubuntu:20.04,先看看本地有什么
# ✅ 好的做法:使用内网 ARM64 镜像
FROM 192.168.16.61:30443/library/eclipse-temurin:17.0.19_10-jre-noble

# ❌ 不好的做法:假设能访问 Docker Hub
FROM ubuntu:20.04

三、第二步:分析依赖 —— 搞清楚"需要什么"

C/C++ 程序的依赖分三类,缺一不可:

类型 说明 示例
编译依赖 头文件、CMake 配置、静态库 fastddsConfig.cmake*.h
运行依赖 程序启动时动态加载的 .so libssl.so.1.1libfastdds.so
环境依赖 网络、共享内存、权限、配置文件 --network host/dev/shm

3.1 确认可执行文件架构

编译完成后,第一件事就是检查架构:

file build/MyProgram

期望输出:

ELF 64-bit LSB pie executable, ARM aarch64
# 或
ELF 64-bit LSB pie executable, x86-64

如果架构不对(比如在 x86 上编译了程序却要放进 ARM 镜像),立即停止,重新选择构建环境。

3.2 查看动态库依赖(最重要的一步)

ldd build/MyProgram

输出示例:

libfastdds.so.3 => /home/user/opt/fastdds/lib/libfastdds.so.3
libtinyxml2.so.6 => not found          ❌ 缺失!
libssl.so.1.1 => not found             ❌ 缺失!
libstdc++.so.6 => /lib/aarch64-linux-gnu/libstdc++.so.6

重点看 not found 每个 not found 都是一颗定时炸弹。

核心洞察: “目标主机上能运行” ≠ “基础镜像里也能运行”。主机可能已经装了这些库,但容器是独立的文件系统,看不到主机的 /lib

3.3 确认第三方库安装位置

# 以 Fast DDS 为例
export MYLIB_HOME=/path/to/install/prefix
ls -l "$MYLIB_HOME/include"    # 头文件
ls -l "$MYLIB_HOME/lib"        # 库文件

3.4 确认 CMake 能找到依赖

export CMAKE_PREFIX_PATH="$MYLIB_HOME:${CMAKE_PREFIX_PATH}"
cmake -S . -B build
  • CMAKE_PREFIX_PATH:告诉 CMake 去哪里找 xxxConfig.cmake、头文件和库
  • -S .:源代码目录
  • -B build:构建输出目录(不污染源代码)

四、第三步:编译 & 主机验证

4.1 设置编译环境

export MYLIB_HOME=/path/to/install/prefix
export CMAKE_PREFIX_PATH="$MYLIB_HOME:${CMAKE_PREFIX_PATH}"
export LD_LIBRARY_PATH="$MYLIB_HOME/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"

LD_LIBRARY_PATH 只影响当前 Shell,不会自动写入镜像,Dockerfile 里要单独设置。

4.2 编译

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j"$(nproc)"
参数 含义
-DCMAKE_BUILD_TYPE=Release 发布版本(优化编译)
-j"$(nproc)" 按 CPU 核心数并行编译

4.3 在主机上先验证(容器化之前必做!)

# 终端 1:启动服务端/订阅端
./build/MyServer --config ./config.xml

# 终端 2:启动客户端/发布端
./build/MyClient --config ./config.xml

验收原则:以接收端为准! 发送端显示"成功"只能说明本地调用成功,不代表对端收到。

经验法则: 如果主机上都跑不通,容器里一定也跑不通。先修好主机上的问题,再谈容器化。


五、第四步:收集运行时动态库

这是整个流程中最容易出错的环节。

5.1 创建干净的库目录

rm -rf runtime-libs && mkdir -p runtime-libs

5.2 复制第三方库

cp -a "$MYLIB_HOME/lib"/*.so* runtime-libs/

cp -a 保留符号链接关系,动态库通常是一串软链接(libxxx.solibxxx.so.3libxxx.so.3.6.1),不能只复制一个。

5.3 补齐系统库

根据 ldd 的结果,逐个补齐 not found 的库:

# 示例:补齐 TinyXML2 和 OpenSSL
cp -a /lib/aarch64-linux-gnu/libtinyxml2.so.6* runtime-libs/
cp -a /lib/aarch64-linux-gnu/libssl.so.1.1* runtime-libs/
cp -a /lib/aarch64-linux-gnu/libcrypto.so.1.1* runtime-libs/

通用方法(换项目时照做):

  1. 对每个可执行文件运行 ldd
  2. 找出所有 not found 或基础镜像中没有的库
  3. find / dpkg -S / rpm -qf 定位真实文件
  4. 复制库文件及其所有符号链接
  5. 构建镜像后再次在容器内 ldd 验证
# 辅助定位命令
find /lib /usr/lib "$MYLIB_HOME/lib" -name 'lib*.so*' 2>/dev/null | grep -E '关键词'
dpkg -S libxxx.so.6          # Debian/Ubuntu
rpm -qf /lib/libxxx.so.6     # CentOS/RHEL

六、第五步:编写 Dockerfile

6.1 Dockerfile 模板

# ========================================
# 多阶段构建(推荐)
# ========================================

# ---- 阶段 1:构建阶段(如果支持在容器内编译)----
# FROM <base> AS builder
# WORKDIR /build
# COPY . .
# RUN cmake -S . -B build && cmake --build build -j4

# ---- 阶段 2:运行阶段(精简)----
ARG BASE_IMAGE=<你的内网基础镜像>
FROM ${BASE_IMAGE}

WORKDIR /opt/myapp

# 复制编译好的可执行文件
COPY build/MyProgram /opt/myapp/bin/MyProgram

# 复制配置文件
COPY config/*.xml /opt/myapp/config/

# 复制运行时库
COPY runtime-libs/ /opt/myapp/lib/

# 确保可执行权限
RUN chmod 0755 /opt/myapp/bin/MyProgram

# 设置库搜索路径
ENV LD_LIBRARY_PATH=/opt/myapp/lib:/usr/local/lib

# 默认启动命令(按需修改)
CMD ["/bin/sh"]

6.2 逐项说明

指令 作用
ARG BASE_IMAGE 允许构建时替换基础镜像
FROM 指定基础镜像和架构
WORKDIR 设置工作目录
COPY 将文件从构建上下文复制到镜像内
chmod 0755 确保程序有执行权限
ENV LD_LIBRARY_PATH 让程序找到动态库
CMD 默认启动命令

6.3 检查清单

  • FROM 的架构是否与目标机一致
  • COPY 的源文件是否都真实存在
  • WORKDIR、程序路径、配置路径是否一致
  • chmod 是否覆盖所有可执行文件
  • ENV LD_LIBRARY_PATH 是否包含实际库目录
  • 是否需要 --network host--ipc=host 等特殊权限
  • 是否需要在 Dockerfile 中安装额外包(apt-get / yum

七、第六步:构建镜像

7.1 构建前检查上下文

ls -l Dockerfile build/MyProgram config/*.xml runtime-libs/

确保所有 COPY 引用的文件都存在。

7.2 执行构建

docker build --pull=false -t myapp:1.0.0 .
参数 含义
--pull=false 不尝试从远程更新基础镜像(离线环境必备)
-t myapp:1.0.0 设置镜像名和不可变版本号
. 当前目录为构建上下文

7.3 常见错误

错误 原因 解决
Dockerfile: no such file 当前目录不对或文件名大小写错误 pwd + ls -l Dockerfile
Client.Timeout exceeded 无法访问 Docker Hub 改用内网基础镜像 + --pull=false
COPY failed: no source 源文件不存在 检查文件路径和构建上下文

八、第七步:构建后检查(逐层验证)

8.1 检查镜像架构

docker image inspect myapp:1.0.0 --format '{{.Os}}/{{.Architecture}}'
# 必须输出:linux/arm64(或对应架构)

8.2 检查文件是否到位

docker run --rm --entrypoint /bin/sh myapp:1.0.0 -c \
  'ls -l /opt/myapp/bin /opt/myapp/config /opt/myapp/lib'

8.3 检查动态库解析

docker run --rm --entrypoint /bin/sh myapp:1.0.0 -c \
  'ldd /opt/myapp/bin/MyProgram'

如果还有 not found,回到第 5 步补齐库,重新构建。

8.4 检查程序能否启动

docker run --rm myapp:1.0.0 /opt/myapp/bin/MyProgram --help

九、第八步:容器内运行 & 验收

9.1 同一主机上的多容器

# 终端 1:启动服务端
docker run --rm --network host --ipc=host myapp:1.0.0 \
  /opt/myapp/bin/MyServer --config /opt/myapp/config/server.xml

# 终端 2:启动客户端
docker run --rm --network host --ipc=host myapp:1.0.0 \
  /opt/myapp/bin/MyClient --config /opt/myapp/config/client.xml
参数 含义
--network host 使用主机网络(UDP 发现、数据传输)
--ipc=host 共享主机 IPC(共享内存通信必需)
--rm 退出后自动清理容器

9.2 常见"假通过"陷阱

现象 真正原因
Writer write() 成功,Reader 收到 0 条 缺少 --ipc=host,共享内存通道隔离
端点匹配但无数据 检查 Domain/Topic/数据类型是否一致
连接超时 检查防火墙、端口、网络策略

记住:验收必须以接收端实际数据为准!

9.3 跨主机部署要点

  • --ipc=host 不能跨主机
  • 必须明确使用 UDP/TCP,不能依赖共享内存
  • 防火墙放通发现端口和数据端口
  • 在真实双机环境重新完整测试

十、第九步:打包 & 部署

10.1 导出镜像为 tar

docker save -o myapp-1.0.0.tar myapp:1.0.0
ls -lh myapp-1.0.0.tar

docker save 导出的是完整镜像包(含所有层),可直接上传 Harbor 或离线传输。

10.2 直接推送到 Harbor(如果网络可达)

docker tag myapp:1.0.0 <harbor-host>/<project>/myapp:1.0.0
docker push <harbor-host>/<project>/myapp:1.0.0

10.3 版本管理原则

  • ✅ 使用不可变版本号1.0.01.0.1
  • ❌ 不要使用 latest(无法追溯)
  • 任何变更(代码、库、配置、基础镜像)都应递增版本号

十一、运行时配置文件(runtime.yaml)注意事项

如果你的平台使用 runtime.yaml(非 Kubernetes Pod YAML),注意:

apiVersion: runtime.jfounder.io/v1
kind: RuntimeConfig
ports:
  - name: management
    port: 8080
    protocol: TCP
    expose: true
management:
  portName: management
env:
  - name: LD_LIBRARY_PATH
    value: "/opt/myapp/lib:/usr/local/lib"

不要写 Kubernetes 字段: metadataspeccontainersimagecommandargs

重要: YAML 通过 Schema 校验 ≠ 平台能正确运行你的程序。如果程序不实现平台管理接口,需要额外包装。


十二、通用排错手册

12.1 快速定位流程图

问题出现
  │
  ├─ 构建失败?
  │    ├─ Dockerfile 找不到 → 检查 pwd + 文件名
  │    ├─ 基础镜像拉不到 → 改用内网镜像 + --pull=false
  │    └─ COPY 失败 → 检查源文件是否存在于构建上下文
  │
  ├─ 启动报错?
  │    ├─ 动态库缺失 → ldd 查缺 + 补齐 runtime-libs
  │    ├─ 配置文件找不到 → 检查容器内路径 vs 宿主机路径
  │    └─ 权限不足 → chmod + 检查用户/设备权限
  │
  ├─ 网络通信异常?
  │    ├─ 收不到数据 → 检查 --network host + --ipc=host
  │    ├─ 端点不匹配 → 检查 Domain/Topic/数据类型
  │    └─ 端口不通 → 检查防火墙 + 安全组
  │
  └─ 架构不匹配?
       └─ uname -m + file + docker inspect 三连确认

12.2 错误速查表

错误信息 根因 解决方案
Dockerfile: no such file 目录不对或大小写错误 pwd + ls Dockerfile
Client.Timeout exceeded 无法访问公网仓库 内网镜像 + --pull=false
libxxx.so: cannot open 动态库缺失 ldd → 找库 → 复制 → 重建
write() 成功但收到 0 条 --ipc=host 加上 --ipc=host
架构不匹配 x86 程序放进 ARM 镜像 在 ARM 机器重新编译
XML 找不到 用了宿主机路径 改用容器内路径 /opt/...

十三、换项目时的完整检查清单

编译前

  • 目标机器架构已确认(uname -m
  • 基础镜像已存在且架构匹配
  • 确认工程生成的可执行文件名称(看 CMakeLists.txt / Makefile)
  • 确认配置文件清单
  • 设置正确的 CMake 前缀路径

编译后

  • file 确认架构正确
  • ldd 没有 not found
  • 程序在主机上运行通过
  • 退出码和业务输出符合预期

构建镜像前

  • Dockerfile 位于工程根目录
  • 所有 COPY 源文件真实存在
  • 所有动态库已放入 runtime-libs/
  • ENV LD_LIBRARY_PATH 包含库目录
  • 不依赖不可用的公网仓库

构建镜像后

  • 镜像架构正确(docker inspect
  • 容器内文件路径正确
  • 容器内 lddnot found
  • 程序可在容器内启动
  • 网络、IPC、权限均已验证
  • 接收端实际结果为准

部署前

  • 使用不可变版本号(非 latest
  • 关键业务测试已重新执行
  • docker save 导出 tar 或 Harbor 推送成功
  • 镜像名和 Tag 与部署配置一致
  • runtime.yaml 使用平台真实 Schema

十四、一条命令流走完全程

以下是换项目时的最短完整流程模板:

# ===== 1. 环境确认 =====
cd /path/to/project
uname -m
docker version

# ===== 2. 设置依赖环境 =====
export MYLIB_HOME=/path/to/install
export CMAKE_PREFIX_PATH="$MYLIB_HOME:${CMAKE_PREFIX_PATH}"
export LD_LIBRARY_PATH="$MYLIB_HOME/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"

# ===== 3. 编译 =====
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j"$(nproc)"

# ===== 4. 架构 & 依赖检查 =====
file build/MyProgram
ldd build/MyProgram

# ===== 5. 主机验证 =====
./build/MyProgram --test

# ===== 6. 收集动态库 =====
rm -rf runtime-libs && mkdir -p runtime-libs
cp -a "$MYLIB_HOME/lib"/*.so* runtime-libs/
# 根据 ldd 结果补齐系统库
cp -a /lib/aarch64-linux-gnu/libxxx.so* runtime-libs/

# ===== 7. 构建镜像 =====
docker build --pull=false -t myapp:1.0.0 .

# ===== 8. 构建后检查 =====
docker image inspect myapp:1.0.0 --format '{{.Os}}/{{.Architecture}}'
docker run --rm --entrypoint /bin/sh myapp:1.0.0 -c 'ldd /opt/myapp/bin/MyProgram'
docker run --rm myapp:1.0.0 /opt/myapp/bin/MyProgram --help

# ===== 9. 容器内运行 =====
docker run --rm --network host --ipc=host myapp:1.0.0 \
  /opt/myapp/bin/MyProgram --config /opt/myapp/config/app.xml

# ===== 10. 打包 =====
docker save -o myapp-1.0.0.tar myapp:1.0.0

附录:核心命令速查卡

命令 用途
uname -m 查看 CPU 架构
file <binary> 查看可执行文件架构
ldd <binary> 查看动态库依赖
docker image inspect <img> 查看镜像元数据(架构等)
docker build --pull=false -t name:tag . 离线构建镜像
docker run --rm -it --network host --ipc=host 运行容器(同机通信)
docker save -o file.tar image:tag 导出镜像为 tar
docker tag + docker push 推送到 Harbor
find / -name 'lib*.so*' 定位动态库文件
dpkg -S <file> / rpm -qf <file> 查询文件属于哪个包

总结: 容器化的本质是依赖管理 + 环境复现。搞清楚"程序需要什么",然后"把所有需要的东西装进一个隔离的文件系统",最后"确保运行时环境和主机打通"。掌握这套思路,任何 C/C++ 工程的容器化都能举一反三。# 从零到交付:通用 C/C++ 项目容器镜像搭建完全指南

本文基于 Fast DDS QoS 工程实践提炼而成,适用于任何 C/C++、Go、Rust 等编译型语言项目的容器化。核心思路:先理解产物 → 确认环境 → 分析依赖 → 编译验证 → 收集库 → 编写 Dockerfile → 构建检查 → 运行测试 → 打包部署


一、整体思路:容器化的"八步走"战略

容器化不是简单地写个 Dockerfile,而是一条分层递进的工程流水线。每一层都依赖上一层正确完成,任何一层有缺口,后面都会出问题。

┌─────────────────────────────────────────────────┐
│  第 8 层:打包 & 部署(docker save / push)      │
├─────────────────────────────────────────────────┤
│  第 7 层:容器内运行 & 验收                      │
├─────────────────────────────────────────────────┤
│  第 6 层:构建后检查(架构 / 依赖 / 启动)       │
├─────────────────────────────────────────────────┤
│  第 5 层:编写 Dockerfile                        │
├─────────────────────────────────────────────────┤
│  第 4 层:收集运行时动态库(runtime-libs/)      │
├─────────────────────────────────────────────────┤
│  第 3 层:主机上编译 & 验证                      │
├─────────────────────────────────────────────────┤
│  第 2 层:分析依赖(file / ldd / 头文件)        │
├─────────────────────────────────────────────────┤
│  第 1 层:确认环境(架构 / Docker / 基础镜像)    │
└─────────────────────────────────────────────────┘

核心原则:程序必须在目标架构上编译,或者使用与目标架构一致的交叉编译环境。 不能在 x86 机器上编译 ARM 程序,然后塞进 ARM 镜像。


二、第一步:确认环境 —— 一切的起点

2.1 确认目标机器架构

uname -m
输出 含义
aarch64 / arm64 ARM 64 位(如 Kylin ARM、飞腾、鲲鹏)
x86_64 Intel/AMD 64 位
riscv64 RISC-V 64 位

⚠️ 关键: 后续编译、基础镜像、动态库全部必须与目标架构一致。

2.2 确认 Docker 可用

docker version

检查 Client 和 Server 都正常,重点关注 Server 端版本。

2.3 确认基础镜像

# 列出已有镜像
docker images --format '{{.Repository}}:{{.Tag}}'

# 检查特定镜像的架构
docker image inspect <镜像名> --format '{{.Os}}/{{.Architecture}}'

选择基础镜像的原则:

  • 优先使用目标环境已有的内网镜像(避免构建时联网超时)
  • 确认架构匹配(linux/arm64linux/amd64 等)
  • 不要盲目写 FROM ubuntu:20.04,先看看本地有什么
# ✅ 好的做法:使用内网 ARM64 镜像
FROM 192.168.16.61:30443/library/eclipse-temurin:17.0.19_10-jre-noble

# ❌ 不好的做法:假设能访问 Docker Hub
FROM ubuntu:20.04

三、第二步:分析依赖 —— 搞清楚"需要什么"

C/C++ 程序的依赖分三类,缺一不可:

类型 说明 示例
编译依赖 头文件、CMake 配置、静态库 fastddsConfig.cmake*.h
运行依赖 程序启动时动态加载的 .so libssl.so.1.1libfastdds.so
环境依赖 网络、共享内存、权限、配置文件 --network host/dev/shm

3.1 确认可执行文件架构

编译完成后,第一件事就是检查架构:

file build/MyProgram

期望输出:

ELF 64-bit LSB pie executable, ARM aarch64
# 或
ELF 64-bit LSB pie executable, x86-64

如果架构不对(比如在 x86 上编译了程序却要放进 ARM 镜像),立即停止,重新选择构建环境。

3.2 查看动态库依赖(最重要的一步)

ldd build/MyProgram

输出示例:

libfastdds.so.3 => /home/user/opt/fastdds/lib/libfastdds.so.3
libtinyxml2.so.6 => not found          ❌ 缺失!
libssl.so.1.1 => not found             ❌ 缺失!
libstdc++.so.6 => /lib/aarch64-linux-gnu/libstdc++.so.6

重点看 not found 每个 not found 都是一颗定时炸弹。

核心洞察: “目标主机上能运行” ≠ “基础镜像里也能运行”。主机可能已经装了这些库,但容器是独立的文件系统,看不到主机的 /lib

3.3 确认第三方库安装位置

# 以 Fast DDS 为例
export MYLIB_HOME=/path/to/install/prefix
ls -l "$MYLIB_HOME/include"    # 头文件
ls -l "$MYLIB_HOME/lib"        # 库文件

3.4 确认 CMake 能找到依赖

export CMAKE_PREFIX_PATH="$MYLIB_HOME:${CMAKE_PREFIX_PATH}"
cmake -S . -B build
  • CMAKE_PREFIX_PATH:告诉 CMake 去哪里找 xxxConfig.cmake、头文件和库
  • -S .:源代码目录
  • -B build:构建输出目录(不污染源代码)

四、第三步:编译 & 主机验证

4.1 设置编译环境

export MYLIB_HOME=/path/to/install/prefix
export CMAKE_PREFIX_PATH="$MYLIB_HOME:${CMAKE_PREFIX_PATH}"
export LD_LIBRARY_PATH="$MYLIB_HOME/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"

LD_LIBRARY_PATH 只影响当前 Shell,不会自动写入镜像,Dockerfile 里要单独设置。

4.2 编译

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j"$(nproc)"
参数 含义
-DCMAKE_BUILD_TYPE=Release 发布版本(优化编译)
-j"$(nproc)" 按 CPU 核心数并行编译

4.3 在主机上先验证(容器化之前必做!)

# 终端 1:启动服务端/订阅端
./build/MyServer --config ./config.xml

# 终端 2:启动客户端/发布端
./build/MyClient --config ./config.xml

验收原则:以接收端为准! 发送端显示"成功"只能说明本地调用成功,不代表对端收到。

经验法则: 如果主机上都跑不通,容器里一定也跑不通。先修好主机上的问题,再谈容器化。


五、第四步:收集运行时动态库

这是整个流程中最容易出错的环节。

5.1 创建干净的库目录

rm -rf runtime-libs && mkdir -p runtime-libs

5.2 复制第三方库

cp -a "$MYLIB_HOME/lib"/*.so* runtime-libs/

cp -a 保留符号链接关系,动态库通常是一串软链接(libxxx.solibxxx.so.3libxxx.so.3.6.1),不能只复制一个。

5.3 补齐系统库

根据 ldd 的结果,逐个补齐 not found 的库:

# 示例:补齐 TinyXML2 和 OpenSSL
cp -a /lib/aarch64-linux-gnu/libtinyxml2.so.6* runtime-libs/
cp -a /lib/aarch64-linux-gnu/libssl.so.1.1* runtime-libs/
cp -a /lib/aarch64-linux-gnu/libcrypto.so.1.1* runtime-libs/

通用方法(换项目时照做):

  1. 对每个可执行文件运行 ldd
  2. 找出所有 not found 或基础镜像中没有的库
  3. find / dpkg -S / rpm -qf 定位真实文件
  4. 复制库文件及其所有符号链接
  5. 构建镜像后再次在容器内 ldd 验证
# 辅助定位命令
find /lib /usr/lib "$MYLIB_HOME/lib" -name 'lib*.so*' 2>/dev/null | grep -E '关键词'
dpkg -S libxxx.so.6          # Debian/Ubuntu
rpm -qf /lib/libxxx.so.6     # CentOS/RHEL

六、第五步:编写 Dockerfile

6.1 Dockerfile 模板

# ========================================
# 多阶段构建(推荐)
# ========================================

# ---- 阶段 1:构建阶段(如果支持在容器内编译)----
# FROM <base> AS builder
# WORKDIR /build
# COPY . .
# RUN cmake -S . -B build && cmake --build build -j4

# ---- 阶段 2:运行阶段(精简)----
ARG BASE_IMAGE=<你的内网基础镜像>
FROM ${BASE_IMAGE}

WORKDIR /opt/myapp

# 复制编译好的可执行文件
COPY build/MyProgram /opt/myapp/bin/MyProgram

# 复制配置文件
COPY config/*.xml /opt/myapp/config/

# 复制运行时库
COPY runtime-libs/ /opt/myapp/lib/

# 确保可执行权限
RUN chmod 0755 /opt/myapp/bin/MyProgram

# 设置库搜索路径
ENV LD_LIBRARY_PATH=/opt/myapp/lib:/usr/local/lib

# 默认启动命令(按需修改)
CMD ["/bin/sh"]

6.2 逐项说明

指令 作用
ARG BASE_IMAGE 允许构建时替换基础镜像
FROM 指定基础镜像和架构
WORKDIR 设置工作目录
COPY 将文件从构建上下文复制到镜像内
chmod 0755 确保程序有执行权限
ENV LD_LIBRARY_PATH 让程序找到动态库
CMD 默认启动命令

6.3 检查清单

  • FROM 的架构是否与目标机一致
  • COPY 的源文件是否都真实存在
  • WORKDIR、程序路径、配置路径是否一致
  • chmod 是否覆盖所有可执行文件
  • ENV LD_LIBRARY_PATH 是否包含实际库目录
  • 是否需要 --network host--ipc=host 等特殊权限
  • 是否需要在 Dockerfile 中安装额外包(apt-get / yum

七、第六步:构建镜像

7.1 构建前检查上下文

ls -l Dockerfile build/MyProgram config/*.xml runtime-libs/

确保所有 COPY 引用的文件都存在。

7.2 执行构建

docker build --pull=false -t myapp:1.0.0 .
参数 含义
--pull=false 不尝试从远程更新基础镜像(离线环境必备)
-t myapp:1.0.0 设置镜像名和不可变版本号
. 当前目录为构建上下文

7.3 常见错误

错误 原因 解决
Dockerfile: no such file 当前目录不对或文件名大小写错误 pwd + ls -l Dockerfile
Client.Timeout exceeded 无法访问 Docker Hub 改用内网基础镜像 + --pull=false
COPY failed: no source 源文件不存在 检查文件路径和构建上下文

八、第七步:构建后检查(逐层验证)

8.1 检查镜像架构

docker image inspect myapp:1.0.0 --format '{{.Os}}/{{.Architecture}}'
# 必须输出:linux/arm64(或对应架构)

8.2 检查文件是否到位

docker run --rm --entrypoint /bin/sh myapp:1.0.0 -c \
  'ls -l /opt/myapp/bin /opt/myapp/config /opt/myapp/lib'

8.3 检查动态库解析

docker run --rm --entrypoint /bin/sh myapp:1.0.0 -c \
  'ldd /opt/myapp/bin/MyProgram'

如果还有 not found,回到第 5 步补齐库,重新构建。

8.4 检查程序能否启动

docker run --rm myapp:1.0.0 /opt/myapp/bin/MyProgram --help

九、第八步:容器内运行 & 验收

9.1 同一主机上的多容器

# 终端 1:启动服务端
docker run --rm --network host --ipc=host myapp:1.0.0 \
  /opt/myapp/bin/MyServer --config /opt/myapp/config/server.xml

# 终端 2:启动客户端
docker run --rm --network host --ipc=host myapp:1.0.0 \
  /opt/myapp/bin/MyClient --config /opt/myapp/config/client.xml
参数 含义
--network host 使用主机网络(UDP 发现、数据传输)
--ipc=host 共享主机 IPC(共享内存通信必需)
--rm 退出后自动清理容器

9.2 常见"假通过"陷阱

现象 真正原因
Writer write() 成功,Reader 收到 0 条 缺少 --ipc=host,共享内存通道隔离
端点匹配但无数据 检查 Domain/Topic/数据类型是否一致
连接超时 检查防火墙、端口、网络策略

记住:验收必须以接收端实际数据为准!

9.3 跨主机部署要点

  • --ipc=host 不能跨主机
  • 必须明确使用 UDP/TCP,不能依赖共享内存
  • 防火墙放通发现端口和数据端口
  • 在真实双机环境重新完整测试

十、第九步:打包 & 部署

10.1 导出镜像为 tar

docker save -o myapp-1.0.0.tar myapp:1.0.0
ls -lh myapp-1.0.0.tar

docker save 导出的是完整镜像包(含所有层),可直接上传 Harbor 或离线传输。

10.2 直接推送到 Harbor(如果网络可达)

docker tag myapp:1.0.0 <harbor-host>/<project>/myapp:1.0.0
docker push <harbor-host>/<project>/myapp:1.0.0

10.3 版本管理原则

  • ✅ 使用不可变版本号1.0.01.0.1
  • ❌ 不要使用 latest(无法追溯)
  • 任何变更(代码、库、配置、基础镜像)都应递增版本号

十一、运行时配置文件(runtime.yaml)注意事项

如果你的平台使用 runtime.yaml(非 Kubernetes Pod YAML),注意:

apiVersion: runtime.jfounder.io/v1
kind: RuntimeConfig
ports:
  - name: management
    port: 8080
    protocol: TCP
    expose: true
management:
  portName: management
env:
  - name: LD_LIBRARY_PATH
    value: "/opt/myapp/lib:/usr/local/lib"

不要写 Kubernetes 字段: metadataspeccontainersimagecommandargs

重要: YAML 通过 Schema 校验 ≠ 平台能正确运行你的程序。如果程序不实现平台管理接口,需要额外包装。


十二、通用排错手册

12.1 快速定位流程图

问题出现
  │
  ├─ 构建失败?
  │    ├─ Dockerfile 找不到 → 检查 pwd + 文件名
  │    ├─ 基础镜像拉不到 → 改用内网镜像 + --pull=false
  │    └─ COPY 失败 → 检查源文件是否存在于构建上下文
  │
  ├─ 启动报错?
  │    ├─ 动态库缺失 → ldd 查缺 + 补齐 runtime-libs
  │    ├─ 配置文件找不到 → 检查容器内路径 vs 宿主机路径
  │    └─ 权限不足 → chmod + 检查用户/设备权限
  │
  ├─ 网络通信异常?
  │    ├─ 收不到数据 → 检查 --network host + --ipc=host
  │    ├─ 端点不匹配 → 检查 Domain/Topic/数据类型
  │    └─ 端口不通 → 检查防火墙 + 安全组
  │
  └─ 架构不匹配?
       └─ uname -m + file + docker inspect 三连确认

12.2 错误速查表

错误信息 根因 解决方案
Dockerfile: no such file 目录不对或大小写错误 pwd + ls Dockerfile
Client.Timeout exceeded 无法访问公网仓库 内网镜像 + --pull=false
libxxx.so: cannot open 动态库缺失 ldd → 找库 → 复制 → 重建
write() 成功但收到 0 条 --ipc=host 加上 --ipc=host
架构不匹配 x86 程序放进 ARM 镜像 在 ARM 机器重新编译
XML 找不到 用了宿主机路径 改用容器内路径 /opt/...

十三、换项目时的完整检查清单

编译前

  • 目标机器架构已确认(uname -m
  • 基础镜像已存在且架构匹配
  • 确认工程生成的可执行文件名称(看 CMakeLists.txt / Makefile)
  • 确认配置文件清单
  • 设置正确的 CMake 前缀路径

编译后

  • file 确认架构正确
  • ldd 没有 not found
  • 程序在主机上运行通过
  • 退出码和业务输出符合预期

构建镜像前

  • Dockerfile 位于工程根目录
  • 所有 COPY 源文件真实存在
  • 所有动态库已放入 runtime-libs/
  • ENV LD_LIBRARY_PATH 包含库目录
  • 不依赖不可用的公网仓库

构建镜像后

  • 镜像架构正确(docker inspect
  • 容器内文件路径正确
  • 容器内 lddnot found
  • 程序可在容器内启动
  • 网络、IPC、权限均已验证
  • 接收端实际结果为准

部署前

  • 使用不可变版本号(非 latest
  • 关键业务测试已重新执行
  • docker save 导出 tar 或 Harbor 推送成功
  • 镜像名和 Tag 与部署配置一致
  • runtime.yaml 使用平台真实 Schema

十四、一条命令流走完全程

以下是换项目时的最短完整流程模板:

# ===== 1. 环境确认 =====
cd /path/to/project
uname -m
docker version

# ===== 2. 设置依赖环境 =====
export MYLIB_HOME=/path/to/install
export CMAKE_PREFIX_PATH="$MYLIB_HOME:${CMAKE_PREFIX_PATH}"
export LD_LIBRARY_PATH="$MYLIB_HOME/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"

# ===== 3. 编译 =====
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j"$(nproc)"

# ===== 4. 架构 & 依赖检查 =====
file build/MyProgram
ldd build/MyProgram

# ===== 5. 主机验证 =====
./build/MyProgram --test

# ===== 6. 收集动态库 =====
rm -rf runtime-libs && mkdir -p runtime-libs
cp -a "$MYLIB_HOME/lib"/*.so* runtime-libs/
# 根据 ldd 结果补齐系统库
cp -a /lib/aarch64-linux-gnu/libxxx.so* runtime-libs/

# ===== 7. 构建镜像 =====
docker build --pull=false -t myapp:1.0.0 .

# ===== 8. 构建后检查 =====
docker image inspect myapp:1.0.0 --format '{{.Os}}/{{.Architecture}}'
docker run --rm --entrypoint /bin/sh myapp:1.0.0 -c 'ldd /opt/myapp/bin/MyProgram'
docker run --rm myapp:1.0.0 /opt/myapp/bin/MyProgram --help

# ===== 9. 容器内运行 =====
docker run --rm --network host --ipc=host myapp:1.0.0 \
  /opt/myapp/bin/MyProgram --config /opt/myapp/config/app.xml

# ===== 10. 打包 =====
docker save -o myapp-1.0.0.tar myapp:1.0.0

附录:核心命令速查卡

命令 用途
uname -m 查看 CPU 架构
file <binary> 查看可执行文件架构
ldd <binary> 查看动态库依赖
docker image inspect <img> 查看镜像元数据(架构等)
docker build --pull=false -t name:tag . 离线构建镜像
docker run --rm -it --network host --ipc=host 运行容器(同机通信)
docker save -o file.tar image:tag 导出镜像为 tar
docker tag + docker push 推送到 Harbor
find / -name 'lib*.so*' 定位动态库文件
dpkg -S <file> / rpm -qf <file> 查询文件属于哪个包

总结: 容器化的本质是依赖管理 + 环境复现。搞清楚"程序需要什么",然后"把所有需要的东西装进一个隔离的文件系统",最后"确保运行时环境和主机打通"。掌握这套思路,任何 C/C++ 工程的容器化都能举一反三。# 从零到交付:通用 C/C++ 项目容器镜像搭建完全指南

本文基于 Fast DDS QoS 工程实践提炼而成,适用于任何 C/C++、Go、Rust 等编译型语言项目的容器化。核心思路:先理解产物 → 确认环境 → 分析依赖 → 编译验证 → 收集库 → 编写 Dockerfile → 构建检查 → 运行测试 → 打包部署


一、整体思路:容器化的"八步走"战略

容器化不是简单地写个 Dockerfile,而是一条分层递进的工程流水线。每一层都依赖上一层正确完成,任何一层有缺口,后面都会出问题。

┌─────────────────────────────────────────────────┐
│  第 8 层:打包 & 部署(docker save / push)      │
├─────────────────────────────────────────────────┤
│  第 7 层:容器内运行 & 验收                      │
├─────────────────────────────────────────────────┤
│  第 6 层:构建后检查(架构 / 依赖 / 启动)       │
├─────────────────────────────────────────────────┤
│  第 5 层:编写 Dockerfile                        │
├─────────────────────────────────────────────────┤
│  第 4 层:收集运行时动态库(runtime-libs/)      │
├─────────────────────────────────────────────────┤
│  第 3 层:主机上编译 & 验证                      │
├─────────────────────────────────────────────────┤
│  第 2 层:分析依赖(file / ldd / 头文件)        │
├─────────────────────────────────────────────────┤
│  第 1 层:确认环境(架构 / Docker / 基础镜像)    │
└─────────────────────────────────────────────────┘

核心原则:程序必须在目标架构上编译,或者使用与目标架构一致的交叉编译环境。 不能在 x86 机器上编译 ARM 程序,然后塞进 ARM 镜像。


二、第一步:确认环境 —— 一切的起点

2.1 确认目标机器架构

uname -m
输出 含义
aarch64 / arm64 ARM 64 位(如 Kylin ARM、飞腾、鲲鹏)
x86_64 Intel/AMD 64 位
riscv64 RISC-V 64 位

⚠️ 关键: 后续编译、基础镜像、动态库全部必须与目标架构一致。

2.2 确认 Docker 可用

docker version

检查 Client 和 Server 都正常,重点关注 Server 端版本。

2.3 确认基础镜像

# 列出已有镜像
docker images --format '{{.Repository}}:{{.Tag}}'

# 检查特定镜像的架构
docker image inspect <镜像名> --format '{{.Os}}/{{.Architecture}}'

选择基础镜像的原则:

  • 优先使用目标环境已有的内网镜像(避免构建时联网超时)
  • 确认架构匹配(linux/arm64linux/amd64 等)
  • 不要盲目写 FROM ubuntu:20.04,先看看本地有什么
# ✅ 好的做法:使用内网 ARM64 镜像
FROM 192.168.16.61:30443/library/eclipse-temurin:17.0.19_10-jre-noble

# ❌ 不好的做法:假设能访问 Docker Hub
FROM ubuntu:20.04

三、第二步:分析依赖 —— 搞清楚"需要什么"

C/C++ 程序的依赖分三类,缺一不可:

类型 说明 示例
编译依赖 头文件、CMake 配置、静态库 fastddsConfig.cmake*.h
运行依赖 程序启动时动态加载的 .so libssl.so.1.1libfastdds.so
环境依赖 网络、共享内存、权限、配置文件 --network host/dev/shm

3.1 确认可执行文件架构

编译完成后,第一件事就是检查架构:

file build/MyProgram

期望输出:

ELF 64-bit LSB pie executable, ARM aarch64
# 或
ELF 64-bit LSB pie executable, x86-64

如果架构不对(比如在 x86 上编译了程序却要放进 ARM 镜像),立即停止,重新选择构建环境。

3.2 查看动态库依赖(最重要的一步)

ldd build/MyProgram

输出示例:

libfastdds.so.3 => /home/user/opt/fastdds/lib/libfastdds.so.3
libtinyxml2.so.6 => not found          ❌ 缺失!
libssl.so.1.1 => not found             ❌ 缺失!
libstdc++.so.6 => /lib/aarch64-linux-gnu/libstdc++.so.6

重点看 not found 每个 not found 都是一颗定时炸弹。

核心洞察: “目标主机上能运行” ≠ “基础镜像里也能运行”。主机可能已经装了这些库,但容器是独立的文件系统,看不到主机的 /lib

3.3 确认第三方库安装位置

# 以 Fast DDS 为例
export MYLIB_HOME=/path/to/install/prefix
ls -l "$MYLIB_HOME/include"    # 头文件
ls -l "$MYLIB_HOME/lib"        # 库文件

3.4 确认 CMake 能找到依赖

export CMAKE_PREFIX_PATH="$MYLIB_HOME:${CMAKE_PREFIX_PATH}"
cmake -S . -B build
  • CMAKE_PREFIX_PATH:告诉 CMake 去哪里找 xxxConfig.cmake、头文件和库
  • -S .:源代码目录
  • -B build:构建输出目录(不污染源代码)

四、第三步:编译 & 主机验证

4.1 设置编译环境

export MYLIB_HOME=/path/to/install/prefix
export CMAKE_PREFIX_PATH="$MYLIB_HOME:${CMAKE_PREFIX_PATH}"
export LD_LIBRARY_PATH="$MYLIB_HOME/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"

LD_LIBRARY_PATH 只影响当前 Shell,不会自动写入镜像,Dockerfile 里要单独设置。

4.2 编译

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j"$(nproc)"
参数 含义
-DCMAKE_BUILD_TYPE=Release 发布版本(优化编译)
-j"$(nproc)" 按 CPU 核心数并行编译

4.3 在主机上先验证(容器化之前必做!)

# 终端 1:启动服务端/订阅端
./build/MyServer --config ./config.xml

# 终端 2:启动客户端/发布端
./build/MyClient --config ./config.xml

验收原则:以接收端为准! 发送端显示"成功"只能说明本地调用成功,不代表对端收到。

经验法则: 如果主机上都跑不通,容器里一定也跑不通。先修好主机上的问题,再谈容器化。


五、第四步:收集运行时动态库

这是整个流程中最容易出错的环节。

5.1 创建干净的库目录

rm -rf runtime-libs && mkdir -p runtime-libs

5.2 复制第三方库

cp -a "$MYLIB_HOME/lib"/*.so* runtime-libs/

cp -a 保留符号链接关系,动态库通常是一串软链接(libxxx.solibxxx.so.3libxxx.so.3.6.1),不能只复制一个。

5.3 补齐系统库

根据 ldd 的结果,逐个补齐 not found 的库:

# 示例:补齐 TinyXML2 和 OpenSSL
cp -a /lib/aarch64-linux-gnu/libtinyxml2.so.6* runtime-libs/
cp -a /lib/aarch64-linux-gnu/libssl.so.1.1* runtime-libs/
cp -a /lib/aarch64-linux-gnu/libcrypto.so.1.1* runtime-libs/

通用方法(换项目时照做):

  1. 对每个可执行文件运行 ldd
  2. 找出所有 not found 或基础镜像中没有的库
  3. find / dpkg -S / rpm -qf 定位真实文件
  4. 复制库文件及其所有符号链接
  5. 构建镜像后再次在容器内 ldd 验证
# 辅助定位命令
find /lib /usr/lib "$MYLIB_HOME/lib" -name 'lib*.so*' 2>/dev/null | grep -E '关键词'
dpkg -S libxxx.so.6          # Debian/Ubuntu
rpm -qf /lib/libxxx.so.6     # CentOS/RHEL

六、第五步:编写 Dockerfile

6.1 Dockerfile 模板

# ========================================
# 多阶段构建(推荐)
# ========================================

# ---- 阶段 1:构建阶段(如果支持在容器内编译)----
# FROM <base> AS builder
# WORKDIR /build
# COPY . .
# RUN cmake -S . -B build && cmake --build build -j4

# ---- 阶段 2:运行阶段(精简)----
ARG BASE_IMAGE=<你的内网基础镜像>
FROM ${BASE_IMAGE}

WORKDIR /opt/myapp

# 复制编译好的可执行文件
COPY build/MyProgram /opt/myapp/bin/MyProgram

# 复制配置文件
COPY config/*.xml /opt/myapp/config/

# 复制运行时库
COPY runtime-libs/ /opt/myapp/lib/

# 确保可执行权限
RUN chmod 0755 /opt/myapp/bin/MyProgram

# 设置库搜索路径
ENV LD_LIBRARY_PATH=/opt/myapp/lib:/usr/local/lib

# 默认启动命令(按需修改)
CMD ["/bin/sh"]

6.2 逐项说明

指令 作用
ARG BASE_IMAGE 允许构建时替换基础镜像
FROM 指定基础镜像和架构
WORKDIR 设置工作目录
COPY 将文件从构建上下文复制到镜像内
chmod 0755 确保程序有执行权限
ENV LD_LIBRARY_PATH 让程序找到动态库
CMD 默认启动命令

6.3 检查清单

  • FROM 的架构是否与目标机一致
  • COPY 的源文件是否都真实存在
  • WORKDIR、程序路径、配置路径是否一致
  • chmod 是否覆盖所有可执行文件
  • ENV LD_LIBRARY_PATH 是否包含实际库目录
  • 是否需要 --network host--ipc=host 等特殊权限
  • 是否需要在 Dockerfile 中安装额外包(apt-get / yum

七、第六步:构建镜像

7.1 构建前检查上下文

ls -l Dockerfile build/MyProgram config/*.xml runtime-libs/

确保所有 COPY 引用的文件都存在。

7.2 执行构建

docker build --pull=false -t myapp:1.0.0 .
参数 含义
--pull=false 不尝试从远程更新基础镜像(离线环境必备)
-t myapp:1.0.0 设置镜像名和不可变版本号
. 当前目录为构建上下文

7.3 常见错误

错误 原因 解决
Dockerfile: no such file 当前目录不对或文件名大小写错误 pwd + ls -l Dockerfile
Client.Timeout exceeded 无法访问 Docker Hub 改用内网基础镜像 + --pull=false
COPY failed: no source 源文件不存在 检查文件路径和构建上下文

八、第七步:构建后检查(逐层验证)

8.1 检查镜像架构

docker image inspect myapp:1.0.0 --format '{{.Os}}/{{.Architecture}}'
# 必须输出:linux/arm64(或对应架构)

8.2 检查文件是否到位

docker run --rm --entrypoint /bin/sh myapp:1.0.0 -c \
  'ls -l /opt/myapp/bin /opt/myapp/config /opt/myapp/lib'

8.3 检查动态库解析

docker run --rm --entrypoint /bin/sh myapp:1.0.0 -c \
  'ldd /opt/myapp/bin/MyProgram'

如果还有 not found,回到第 5 步补齐库,重新构建。

8.4 检查程序能否启动

docker run --rm myapp:1.0.0 /opt/myapp/bin/MyProgram --help

九、第八步:容器内运行 & 验收

9.1 同一主机上的多容器

# 终端 1:启动服务端
docker run --rm --network host --ipc=host myapp:1.0.0 \
  /opt/myapp/bin/MyServer --config /opt/myapp/config/server.xml

# 终端 2:启动客户端
docker run --rm --network host --ipc=host myapp:1.0.0 \
  /opt/myapp/bin/MyClient --config /opt/myapp/config/client.xml
参数 含义
--network host 使用主机网络(UDP 发现、数据传输)
--ipc=host 共享主机 IPC(共享内存通信必需)
--rm 退出后自动清理容器

9.2 常见"假通过"陷阱

现象 真正原因
Writer write() 成功,Reader 收到 0 条 缺少 --ipc=host,共享内存通道隔离
端点匹配但无数据 检查 Domain/Topic/数据类型是否一致
连接超时 检查防火墙、端口、网络策略

记住:验收必须以接收端实际数据为准!

9.3 跨主机部署要点

  • --ipc=host 不能跨主机
  • 必须明确使用 UDP/TCP,不能依赖共享内存
  • 防火墙放通发现端口和数据端口
  • 在真实双机环境重新完整测试

十、第九步:打包 & 部署

10.1 导出镜像为 tar

docker save -o myapp-1.0.0.tar myapp:1.0.0
ls -lh myapp-1.0.0.tar

docker save 导出的是完整镜像包(含所有层),可直接上传 Harbor 或离线传输。

10.2 直接推送到 Harbor(如果网络可达)

docker tag myapp:1.0.0 <harbor-host>/<project>/myapp:1.0.0
docker push <harbor-host>/<project>/myapp:1.0.0

10.3 版本管理原则

  • ✅ 使用不可变版本号1.0.01.0.1
  • ❌ 不要使用 latest(无法追溯)
  • 任何变更(代码、库、配置、基础镜像)都应递增版本号

十一、运行时配置文件(runtime.yaml)注意事项

如果你的平台使用 runtime.yaml(非 Kubernetes Pod YAML),注意:

apiVersion: runtime.jfounder.io/v1
kind: RuntimeConfig
ports:
  - name: management
    port: 8080
    protocol: TCP
    expose: true
management:
  portName: management
env:
  - name: LD_LIBRARY_PATH
    value: "/opt/myapp/lib:/usr/local/lib"

不要写 Kubernetes 字段: metadataspeccontainersimagecommandargs

重要: YAML 通过 Schema 校验 ≠ 平台能正确运行你的程序。如果程序不实现平台管理接口,需要额外包装。


十二、通用排错手册

12.1 快速定位流程图

问题出现
  │
  ├─ 构建失败?
  │    ├─ Dockerfile 找不到 → 检查 pwd + 文件名
  │    ├─ 基础镜像拉不到 → 改用内网镜像 + --pull=false
  │    └─ COPY 失败 → 检查源文件是否存在于构建上下文
  │
  ├─ 启动报错?
  │    ├─ 动态库缺失 → ldd 查缺 + 补齐 runtime-libs
  │    ├─ 配置文件找不到 → 检查容器内路径 vs 宿主机路径
  │    └─ 权限不足 → chmod + 检查用户/设备权限
  │
  ├─ 网络通信异常?
  │    ├─ 收不到数据 → 检查 --network host + --ipc=host
  │    ├─ 端点不匹配 → 检查 Domain/Topic/数据类型
  │    └─ 端口不通 → 检查防火墙 + 安全组
  │
  └─ 架构不匹配?
       └─ uname -m + file + docker inspect 三连确认

12.2 错误速查表

错误信息 根因 解决方案
Dockerfile: no such file 目录不对或大小写错误 pwd + ls Dockerfile
Client.Timeout exceeded 无法访问公网仓库 内网镜像 + --pull=false
libxxx.so: cannot open 动态库缺失 ldd → 找库 → 复制 → 重建
write() 成功但收到 0 条 --ipc=host 加上 --ipc=host
架构不匹配 x86 程序放进 ARM 镜像 在 ARM 机器重新编译
XML 找不到 用了宿主机路径 改用容器内路径 /opt/...

十三、换项目时的完整检查清单

编译前

  • 目标机器架构已确认(uname -m
  • 基础镜像已存在且架构匹配
  • 确认工程生成的可执行文件名称(看 CMakeLists.txt / Makefile)
  • 确认配置文件清单
  • 设置正确的 CMake 前缀路径

编译后

  • file 确认架构正确
  • ldd 没有 not found
  • 程序在主机上运行通过
  • 退出码和业务输出符合预期

构建镜像前

  • Dockerfile 位于工程根目录
  • 所有 COPY 源文件真实存在
  • 所有动态库已放入 runtime-libs/
  • ENV LD_LIBRARY_PATH 包含库目录
  • 不依赖不可用的公网仓库

构建镜像后

  • 镜像架构正确(docker inspect
  • 容器内文件路径正确
  • 容器内 lddnot found
  • 程序可在容器内启动
  • 网络、IPC、权限均已验证
  • 接收端实际结果为准

部署前

  • 使用不可变版本号(非 latest
  • 关键业务测试已重新执行
  • docker save 导出 tar 或 Harbor 推送成功
  • 镜像名和 Tag 与部署配置一致
  • runtime.yaml 使用平台真实 Schema

十四、一条命令流走完全程

以下是换项目时的最短完整流程模板:

# ===== 1. 环境确认 =====
cd /path/to/project
uname -m
docker version

# ===== 2. 设置依赖环境 =====
export MYLIB_HOME=/path/to/install
export CMAKE_PREFIX_PATH="$MYLIB_HOME:${CMAKE_PREFIX_PATH}"
export LD_LIBRARY_PATH="$MYLIB_HOME/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"

# ===== 3. 编译 =====
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j"$(nproc)"

# ===== 4. 架构 & 依赖检查 =====
file build/MyProgram
ldd build/MyProgram

# ===== 5. 主机验证 =====
./build/MyProgram --test

# ===== 6. 收集动态库 =====
rm -rf runtime-libs && mkdir -p runtime-libs
cp -a "$MYLIB_HOME/lib"/*.so* runtime-libs/
# 根据 ldd 结果补齐系统库
cp -a /lib/aarch64-linux-gnu/libxxx.so* runtime-libs/

# ===== 7. 构建镜像 =====
docker build --pull=false -t myapp:1.0.0 .

# ===== 8. 构建后检查 =====
docker image inspect myapp:1.0.0 --format '{{.Os}}/{{.Architecture}}'
docker run --rm --entrypoint /bin/sh myapp:1.0.0 -c 'ldd /opt/myapp/bin/MyProgram'
docker run --rm myapp:1.0.0 /opt/myapp/bin/MyProgram --help

# ===== 9. 容器内运行 =====
docker run --rm --network host --ipc=host myapp:1.0.0 \
  /opt/myapp/bin/MyProgram --config /opt/myapp/config/app.xml

# ===== 10. 打包 =====
docker save -o myapp-1.0.0.tar myapp:1.0.0

附录:核心命令速查卡

命令 用途
uname -m 查看 CPU 架构
file <binary> 查看可执行文件架构
ldd <binary> 查看动态库依赖
docker image inspect <img> 查看镜像元数据(架构等)
docker build --pull=false -t name:tag . 离线构建镜像
docker run --rm -it --network host --ipc=host 运行容器(同机通信)
docker save -o file.tar image:tag 导出镜像为 tar
docker tag + docker push 推送到 Harbor
find / -name 'lib*.so*' 定位动态库文件
dpkg -S <file> / rpm -qf <file> 查询文件属于哪个包

总结: 容器化的本质是依赖管理 + 环境复现。搞清楚"程序需要什么",然后"把所有需要的东西装进一个隔离的文件系统",最后"确保运行时环境和主机打通"。掌握这套思路,任何 C/C++ 工程的容器化都能举一反三。

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐