将c++程序部署到docker 在ubuntu下开发c程序捣鼓开发环境就花了不少时间。功能弄好以后怎么部署到安装了统信UOS)的服务器上让我犯难。虽然都是linux操作系统但极大概率将ubuntu下发布出来的程序原原本本拷贝到uos会跑不起来。一想到要再部署一大堆环境就心烦意乱。后来想到用docker部署。首先在开发机器上构建docker镜像然后导出该镜像拷贝到服务器导入镜像创建容器。半天工夫就搞好了。优势十分明显。对服务器来说无须安装运行环境污染少没有副作用对于程序来说它运行在容器里与宿主机有所隔离受外界影响也小管理起来也方便容器设置为开机即启动通过portainer在同一网络中任意一台机就能用浏览器重启和观察日志。一、构建docker镜像1、代码结构这是我C项目的代码结构。Dockerfile是手输的。2、CMakeLists.txtcmake_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 MATCHESx86_64|amd64)set(PLATFORM_X86_64 TRUE)elseif(CMAKE_SYSTEM_PROCESSOR MATCHESaarch64|arm64)set(PLATFORM_ARM64 TRUE)endif()endif()set(SOURCESUnderWtConn.cppcontroller/HttpRoutes.cppcontroller/HttpServer.cpputil/conf_util.cpputil/redis_util.cpputil/path_util.cpputil/time_util.cpputil/json_util.cpputil/hex_util.cpputil/mysqlconn.cpputil/general.cpputil/cbx_operate.cppglobal/pools_util.cppentity/ConnBoxSIIM.cppentity/Gateway.cppentity/LimDeviceR.cppentity/PowerHangYu.cppconfig/ConfigManager.cpplib/canClient.cpp)if(PLATFORM_ARM64)list(APPEND SOURCESlib/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.soINTERFACE_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_ERRORMySQL 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_ERRORMySQL 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 COMMENTCopying config file to build directoryVERBATIM)3、Dockerfile准备Dockerfile特别繁琐要反复试。我上个项目原本也想部署到docker但是开发机的CPU是x86的而服务器是ARM的没有时间在ARM的机器上捣鼓所以才放弃了docker。# 第一阶段构建阶段 FROM ubuntu:24.04AS builder # 设置工作目录 WORKDIR/ackctrl # 更新包列表并安装必要的工具和依赖项 RUN apt-get updateapt-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_LIBSON \ 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 --frombuilder /usr/local/lib/libhiredis.so* /usr/local/lib/ COPY --frombuilder /usr/lib/x86_64-linux-gnu/libmysqlcppconnx.so* /usr/local/lib/ COPY --frombuilder /usr/local/lib/libdrogon.so* /usr/local/lib/ COPY --frombuilder /usr/local/lib/libtrantor.so* /usr/local/lib/ COPY --frombuilder /usr/lib/x86_64-linux-gnu/libssl.so.3 /usr/local/lib/ COPY --frombuilder /usr/lib/x86_64-linux-gnu/libcrypto.so.3 /usr/local/lib/ COPY --frombuilder /usr/lib/x86_64-linux-gnu/libjsoncpp.so.25 /usr/local/lib/ # 从构建阶段复制可执行文件到运行时镜像 COPY --frombuilder /ackctrl/build/UnderwtConn . COPY --frombuilder /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\--restartalways-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或者容器概念的人。