将c++程序部署到docker
2026/8/5 14:54:44 网站建设 项目流程

在ubuntu下开发c++程序,捣鼓开发环境就花了不少时间。功能弄好以后,怎么部署到安装了统信(UOS)的服务器上让我犯难。虽然都是linux操作系统,但极大概率,将ubuntu下发布出来的程序,原原本本拷贝到uos会跑不起来。一想到要再部署一大堆环境就心烦意乱。

后来想到用docker部署。首先在开发机器上构建docker镜像;然后导出该镜像,拷贝到服务器;导入镜像;创建容器。半天工夫就搞好了。优势十分明显。对服务器来说,无须安装运行环境,污染少,没有副作用;对于程序来说,它运行在容器里,与宿主机有所隔离,受外界影响也小;管理起来也方便,容器设置为开机即启动,通过portainer,在同一网络中任意一台机就能用浏览器重启和观察日志。

一、构建docker镜像

1、代码结构

这是我C++项目的代码结构。Dockerfile是手输的。

2、CMakeLists.txt

cmake_minimum_required(VERSION3.25)set(CMAKE_EXPORT_COMPILE_COMMANDS ON)cmake_policy(SET CMP0153 OLD)project("UnderwtConn")# 检测当前架构if(CMAKE_SIZEOF_VOID_P EQUAL8)if(CMAKE_SYSTEM_PROCESSOR MATCHES"x86_64|amd64")set(PLATFORM_X86_64 TRUE)elseif(CMAKE_SYSTEM_PROCESSOR MATCHES"aarch64|arm64")set(PLATFORM_ARM64 TRUE)endif()endif()set(SOURCES"UnderWtConn.cpp""controller/HttpRoutes.cpp""controller/HttpServer.cpp""util/conf_util.cpp""util/redis_util.cpp""util/path_util.cpp""util/time_util.cpp""util/json_util.cpp""util/hex_util.cpp""util/mysqlconn.cpp""util/general.cpp""util/cbx_operate.cpp""global/pools_util.cpp""entity/ConnBoxSIIM.cpp""entity/Gateway.cpp""entity/LimDeviceR.cpp""entity/PowerHangYu.cpp""config/ConfigManager.cpp""lib/canClient.cpp")if(PLATFORM_ARM64)list(APPEND SOURCES"lib/controlcanArm.cpp")endif()add_executable(UnderwtConn ${SOURCES})find_package(Drogon CONFIG REQUIRED)find_package(Boost REQUIRED COMPONENTS system thread)if(PLATFORM_ARM64)if(NOT TARGET hiredis::hiredis)add_library(hiredis::hiredis UNKNOWN IMPORTED)set_target_properties(hiredis::hiredis PROPERTIES IMPORTED_LOCATION"/usr/local/lib/libhiredis.so"INTERFACE_INCLUDE_DIRECTORIES"/usr/local/include")endif()include_directories(/usr/include/mysql)set(MYSQLCPPCONN_DIR/mnt/sda/lib/mysql-connector)set(MYSQL_CONNECTOR_CPP_INCLUDE_DIR ${MYSQLCPPCONN_DIR}/include)set(MYSQL_CONNECTOR_CPP_LIBRARY ${MYSQLCPPCONN_DIR}/lib64/libmysqlcppconnx.so)if(NOT MYSQL_CONNECTOR_CPP_INCLUDE_DIR OR NOT MYSQL_CONNECTOR_CPP_LIBRARY)message(FATAL_ERROR"MySQL Connector/C++ not found! Please install libmysqlcppconn-dev.")endif()else()#告诉 CMake 尝试找到版本至少为1.1.0的 Hiredis 库,并且要求必须找到这个包才能继续执行后续的构建过程find_package(Hiredis1.1CONFIG REQUIRED)# 指定 hiredis 版本为1.1.0#CONFIG:指示 CMake 使用“配置”模式来查找包。这意味着 CMake 期望找到由该软件包提供的配置文件#REQUIRED:表示这是一个必需的依赖项,如果找不到就抛出错误# 如果不存在 hiredis::hiredis 目标,则创建一个if(NOT TARGET hiredis::hiredis)add_library(hiredis::hiredis UNKNOWN IMPORTED)set_target_properties(hiredis::hiredis PROPERTIES IMPORTED_LOCATION"${HIREDIS_LIBRARIES}"INTERFACE_INCLUDE_DIRECTORIES"${HIREDIS_INCLUDE_DIRS}")endif()# 指定额外的头文件搜索路径。 # 具体来说,它告诉编译器在编译过程中除了标准的头文件目录外,还需要检查/usr/include/mysql 目录下的头文件 # 类似于 target_include_directories,但后者更精细。目前推荐使用 target_include_directoriesinclude_directories(/usr/include/mysql)# 查找 MySQL Connector/C++头文件目录find_path(MYSQL_CONNECTOR_CPP_INCLUDE_DIR NAMES mysqlx/xdevapi.h PATHS/usr/include/usr/local/include/usr/include/mysql-cppconn)find_library(MYSQL_CONNECTOR_CPP_LIBRARY NAMES mysqlcppconnx # 直接指定具体版本 PATHS/usr/lib/x86_64-linux-gnu)if(NOT MYSQL_CONNECTOR_CPP_INCLUDE_DIR OR NOT MYSQL_CONNECTOR_CPP_LIBRARY)message(FATAL_ERROR"MySQL Connector/C++ not found! Please install libmysqlcppconn-dev.")endif()# 设置库文件路径set(CONTROLCAN_LIB_PATH ${CMAKE_SOURCE_DIR}/lib/libcontrolcan.so)endif()#类似于include_directoriestarget_include_directories(${PROJECT_NAME}PRIVATE ${MYSQL_CONNECTOR_CPP_INCLUDE_DIR})# 编译过程分为4个阶段,预处理、编译、汇编、链接。 # 所谓链接,是指将多个目标文件以及所需的库文件合并成最终的可执行文件或库target_link_libraries(UnderwtConn Boost::system Boost::thread Drogon::Drogon hiredis::hiredis ${MYSQL_CONNECTOR_CPP_LIBRARY}${CONTROLCAN_LIB_PATH}modbus-static-libstdc++-static-libgcc)if(CMAKE_VERSION VERSION_GREATER3.12)set_property(TARGET UnderwtConn PROPERTY CXX_STANDARD20)endif()# 将配置文件复制到构建目录add_custom_command(TARGET UnderwtConn POST_BUILD COMMAND ${CMAKE_COMMAND}-E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/config/sys.conf ${CMAKE_CURRENT_BINARY_DIR}/config/sys.conf COMMENT"Copying config file to build directory"VERBATIM)

3、Dockerfile

准备Dockerfile特别繁琐,要反复试。我上个项目原本也想部署到docker,但是开发机的CPU是x86的,而服务器是ARM的,没有时间在ARM的机器上捣鼓,所以才放弃了docker。

# 第一阶段:构建阶段 FROM ubuntu:24.04AS builder # 设置工作目录 WORKDIR/ackctrl # 更新包列表并安装必要的工具和依赖项 RUN apt-get update&&apt-get install-y \ gcc g++\ wget \ make \ cmake \ libboost-system-dev \ libboost-thread-dev \ libssl-dev \ zlib1g-dev \ uuid-dev \ libjsoncpp-dev \ doxygen \ graphviz \ dia \ pkg-config \ libvisa-dev \ libmodbus-dev \&&rm-rf/var/lib/apt/lists/* # 将工作目录设为tmp # 此命令有2个作用:1是之后的操作都从这里出发,而是如果没有这个目录的话则创建一个 WORKDIR /tmp # Hiredis # 复制解压后的 Hiredis 源码到容器中 COPY ./docker/hiredis-1.2.0 /tmp/hiredis # 编译并安装 Hiredis RUN cd /tmp/hiredis && \ mkdir build && cd build && \ cmake .. && \ make -j$(nproc) && \ make install && \ ldconfig && \ cd / && rm -rf /tmp/hiredis # 验证 Hiredis 安装 # RUN pkg-config --modversion hiredis && \ # find /usr/local -name "HiredisConfig.cmake" # MySQL # 复制 MySQL Connector/C++ 的 DEB 包到容器中 COPY ./docker/libmysqlcppconn10_9.2.0-1ubuntu24.04_amd64.deb /tmp/libmysqlcppconn10.deb COPY ./docker/libmysqlcppconn-dev_9.2.0-1ubuntu24.04_amd64.deb /tmp/libmysqlcppconn-dev.deb COPY ./docker/libmysqlcppconnx2_9.2.0-1ubuntu24.04_amd64.deb /tmp/libmysqlcppconnx2.deb # 安装 MySQL Connector/C++ 的运行时和开发库 RUN dpkg -i /tmp/libmysqlcppconn10.deb /tmp/libmysqlcppconn-dev.deb /tmp/libmysqlcppconnx2.deb && \ apt-get install -f -y && \ rm -rf /tmp/*.deb # 验证安装 # RUN dpkg -l | grep mysqlcppconn && \ # find /usr -name "mysql" && \ # ls /usr/lib/x86_64-linux-gnu | grep mysqlcppconn # Drogon # 将本地的 drogon 源码复制到容器中 COPY ./docker/drogon-master /tmp/drogon-src # 初始化子模块(如果需要) WORKDIR /tmp/drogon-src RUN mkdir -p build && cd build && \ cmake .. -DBUILD_SHARED_LIBS=ON && \ make -j$(nproc) && \ make install && \ cd / && rm -rf /tmp/drogon-src # 验证 Drogon 和 Trantor 是否安装成功 # RUN find /usr -name "libdrogon.so*" && \ # find /usr/local -name "libdrogon.so*" # RUN find /usr -name "libtrantor.so*" && \ # find /usr/local -name "libtrantor.so*" # 回到工作目录 WORKDIR /ackctrl # 将 AckCtrl 源码复制到容器中 COPY . /ackctrl # 创建构建目录并编译应用代码 RUN mkdir -p build && cd build && \ cmake -DCMAKE_PREFIX_PATH=/usr/local /ackctrl && \ make # 第二阶段:运行时(多阶段构建) #FROM debian:stable-slim FROM ubuntu:24.04 WORKDIR /ackctrl # 删除可能存在的旧版本动态库 RUN rm -f /usr/local/lib/libdrogon.so* && \ rm -f /usr/lib/x86_64-linux-gnu/libdrogon.so* # 从构建阶段复制动态链接库 COPY --from=builder /usr/local/lib/libhiredis.so* /usr/local/lib/ COPY --from=builder /usr/lib/x86_64-linux-gnu/libmysqlcppconnx.so* /usr/local/lib/ COPY --from=builder /usr/local/lib/libdrogon.so* /usr/local/lib/ COPY --from=builder /usr/local/lib/libtrantor.so* /usr/local/lib/ COPY --from=builder /usr/lib/x86_64-linux-gnu/libssl.so.3 /usr/local/lib/ COPY --from=builder /usr/lib/x86_64-linux-gnu/libcrypto.so.3 /usr/local/lib/ COPY --from=builder /usr/lib/x86_64-linux-gnu/libjsoncpp.so.25 /usr/local/lib/ # 从构建阶段复制可执行文件到运行时镜像 COPY --from=builder /ackctrl/build/UnderwtConn . COPY --from=builder /ackctrl/build/config ./config # 创建符号链接 RUN ln -sf libdrogon.so.1.9.10 /usr/local/lib/libdrogon.so.1 && \ ln -sf libdrogon.so.1 /usr/local/lib/libdrogon.so && \ ln -sf /usr/local/lib/libtrantor.so.1 /usr/local/lib/libtrantor.so # 更新动态链接库缓存 RUN ldconfig # 暴露默认端口 EXPOSE 10992 # 运行程序 CMD ["./UnderwtConn"] #CMD ["sh", "-c", "./UnderwtConn"]

4、构建命令

进入程序根目录,执行

如果Dockerfile在程序根目录,则

sudodockerbuild-tackctrl:1.0.

否则,应该指定Dockerfile的位置。比如Dockerfile位于<根目录/docker>目录里

sudodockerbuild-tackctrl:1.0-fdocker/Dockerfile.

二、导出镜像

在开发机器上导出刚构建好的镜像。

sudodockersave-oackctrl.tar ackctrl:1.0

修改权限,否则无法下载

sudochmod644ackctrl.tar

三、导入镜像

将开发机器上导出的镜像ackctrl.tar下载,拷贝到服务器,然后导入

sudodockerload-iackctrl.tar

四、创建容器

mkdir/home/docker/ackctrl/config-psudodockerrun--nameackctrl\--restart=always-d-it\-v/home/docker/ackctrl/config:/ackctrl/config\-p10992:10992\ackctrl:1.0

五、小结

这次部署比较顺利,很重要的一点是开发机和服务器的CPU都是x86结构,所以从开发机上导出的镜像,在服务器上创建容器就能跑,否则跑不起来。

其实我在上一个项目就想着用docker部署了。那个项目,也是要将C++部署到linux。但生产服务器的CPU是ARM架构的,而开发环境的CPU是X86的。这意味着无法使用开发机器构建镜像的方式,必须要拿到生产服务器上编译代码、构建镜像。项目时间赶,就在一台跟服务器配置相同的测试机上部署了开发环境,编译、发布,拷贝到其他服务器上运行。还要写脚本设置开机自动运行。查看日志还要敲命令。主要还是不熟悉docker,构建docker镜像时特别麻烦。

感谢发明了docker或者容器概念的人。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询