ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C++程序容器化实战:Dockerfile编写与镜像瘦身指南

C++程序容器化实战:Dockerfile编写与镜像瘦身指南 1. 为什么C程序必须考虑容器化说个实话很多写C的朋友对Docker的第一反应是“这不是后端Java/Python那帮人用的玩意儿吗”我当初也是这么想的直到有一次线上事故让我彻底改了态度。那是我负责的一个C编写的推荐服务本地跑得好好的代码编译通过、单测全绿结果一发到测试环境就崩——报找不到某个.so动态库。查了半天是测试机器上的glibc版本和我的开发机差了太多编译时用的新特性在旧环境里根本不认。后来换了台机器又是OpenSSL版本对不上。最离谱的一次是运维用了一个精简版系统镜像里面连libstdc.so.6都没有程序起来直接“error while loading shared libraries”。那一周我光顾着跟环境搏斗了业务代码一行没写。从那以后我得出一个结论C程序最大的部署隐患不是代码本身而是运行环境的不可控。编译产物对操作系统的内核版本、C标准库、C运行时、第三方动态库都高度敏感这跟Java“一次编译到处运行”完全是两个世界的故事。Docker解决的就是这个问题。你把编译好的二进制连同它需要的所有运行时依赖一起打包成镜像镜像在哪儿跑环境就原样复制到哪儿。这就像你搬家不是把家具运过去再找配套的螺丝而是直接把整个房间连同墙纸地板搬过去——里面的东西肯定不会掉。对C开发者来说Docker还有个Java/Python场景体会不到的额外好处它可以充当统一的构建环境。你不需要在新电脑上折腾CMake、GCC、依赖库的安装一个Dockerfile就能把整套构建环境固化下来不管谁拉下来构建产出的二进制都高度一致。团队里再也不会出现“我机器上能编过啊”这种话。这篇文章我不打算讲虚的直接分享一套可落地的方案怎么为C程序编写合适的Dockerfile、动态链接和静态链接到底怎么选、怎么把镜像体积从1G压到100M以内、实际部署时踩过的坑和排查思路。无论你只是想把个人小项目容器化还是要在团队里搭建标准化的C构建发布流程这篇文章都能直接用上。2. 部署方案选型先弄明白你的程序是怎么链接的2.1 动态链接与静态链接的取舍在写Dockerfile之前必须先想清楚一件事你的程序是动态链接还是静态链接这个决定直接影响了镜像基础镜像的选择和部署后的稳定性。大多数Linux下默认编译的C程序都是动态链接的也就是编译产物只是一个可执行文件实际运行时需要从系统里加载一堆.so动态库。常见的有libc.so.6、libstdc.so.6、libm.so.6如果你用了第三方库还会需要对应的运行时库。这种方式的好处是二进制文件小、内存占用低多个进程可以共享同一份库代码坏处就是我对部署最头疼的那种——目标机器上缺库或者库版本不对。静态链接则是把用到的库代码直接嵌入到最终的可执行文件里。编译出来的文件大不少几十MB到上百MB都正常但好处是运行时几乎不依赖宿主机的任何库拷到哪个Linux系统上都能直接跑——只要内核兼容就行。对容器化部署来说这其实是非常诱人的一个特性因为这意味着你甚至可以用一个几乎没有内容的空镜像来承载你的程序。我的建议是如果你的程序会部署到别人维护的服务器、或者要兼容多种Linux发行版优先考虑静态链接。反过来如果程序体积敏感、或者需要动态加载插件比如业务里用了dlopen那就老老实实用动态链接通过镜像来锁依赖。2.2 基础镜像怎么选Ubuntu、Debian还是Alpine确定了链接方式后基础镜像的选择就有方向了。动态链接的程序建议直接用ubuntu或debian系镜像。原因是这一类基础镜像自带的glibc、libstdc版本比较新和主流开发环境兼容度高不容易出现“编译时用的库比运行时还新”的情况。作为参考我习惯用ubuntu:22.04或debian:bookworm-slim后者体积更小一些。不太建议动态链接的程序跑在alpine上因为Alpine用的是musl libc而不是glibc这两个C标准库在行为上有细微差别你本地在glibc环境下编译的程序放到Alpine里很可能碰到二进制接口不兼容的问题到时候排查起来很痛苦。静态链接的程序就自由多了。理论上你可以直接用scratch一个完全空的镜像作为运行时基础镜像里面什么都没有只有一个你的可执行文件。这样镜像体积最小、攻击面也最小。但要注意如果你的程序需要读取系统证书比如发起HTTPS请求、需要时区数据、需要/etc/passwd来支撑用户权限管理那scratch就太“裸”了这时候可以用alpine作为基础它虽然也用musl但因为已经是全静态链接不会存在运行时找不对库的问题Alpine在这里只充当一个提供基本文件系统结构的壳。我实际最常用的一套组合是构建阶段用ubuntu:22.04运行阶段根据链接方式选ubuntu:22.04动态或alpine:3.19静态。这套方案既稳又兼顾体积下面会给出具体的Dockerfile示例。2.3 多阶段构建把开发环境和运行环境彻底分开这里必须提一下多阶段构建这是C容器化里性价比最高的一个技巧。早期我写Dockerfile都是一个镜像从头干到尾先装编译器、装CMake、装依赖然后编译然后——这个镜像就直接拿去跑了。结果一个简单服务镜像体积轻松超过1.5GB里面塞满了GCC、Makefile、头文件这些运行时根本用不到的东西。那些编译工具和中间产物不仅是磁盘空间的浪费更重要的是扩大了攻击面万一容器被攻破里面能找到全套编译链可以编译各种恶意工具。多阶段构建的思路是用第一个阶段builder装齐全套开发工具完成编译然后把编译好的产物和运行需要的动态库拷贝到第二个阶段一个干净的精简镜像里。第二个阶段里没有任何编译器、没有任何源代码只有能运行你程序的最小依赖集合。后面我将用一个实际项目作为案例来写完整的Dockerfile这里先记住一个核心原则镜像里不应该出现你的源码和编译器。3. 手把手编写Dockerfile以一个真实服务为例假设我有一个比较典型的C HTTP服务用到了CMake构建依赖了OpenSSL和libcurl编译命令大致是cmake -B build -DCMAKE_BUILD_TYPERelease cmake --build build -j$(nproc)产物是一个叫my_server的可执行文件。下面我分别给出动态链接和静态链接两套Dockerfile方案并解释每一步为什么要这么写。3.1 动态链接方案的完整Dockerfile# 第一阶段构建 FROM ubuntu:22.04 AS builder # 设置非交互模式避免apt等待键盘输入 ENV DEBIAN_FRONTENDnoninteractive # 安装编译工具链和依赖库头文件 RUN apt-get update apt-get install -y --no-install-recommends \ build-essential \ cmake \ libssl-dev \ libcurl4-openssl-dev \ rm -rf /var/lib/apt/lists/* # 把源码拷进容器 WORKDIR /src COPY . . # 构建 RUN cmake -B build -DCMAKE_BUILD_TYPERelease \ cmake --build build -j$(nproc) # 第二阶段运行 FROM ubuntu:22.04 # 安装运行时依赖只需要库不需要头文件和编译器 RUN apt-get update apt-get install -y --no-install-recommends \ libcurl4 \ libssl3 \ rm -rf /var/lib/apt/lists/* # 创建一个非root用户来运行服务安全考虑 RUN useradd --create-home --shell /bin/bash appuser WORKDIR /app # 从构建阶段只拷一个可执行文件出来 COPY --frombuilder /src/build/my_server . USER appuser EXPOSE 8080 CMD [./my_server]几个关键点说一下--no-install-recommends这个参数一定要加。不加的话apt会额外安装一大堆推荐包比如文档、调试工具之类的白白增加镜像体积。装完包之后还要执行rm -rf /var/lib/apt/lists/*这个动作是把apt的索引缓存删掉否则这些缓存也会被写进镜像层里。第二阶段为什么要单独再装一次libcurl4和libssl3因为运行时的库和编译时的头文件是两回事。编译阶段需要libssl-dev包含头文件和静态库而运行阶段只需要libssl3单纯的运行时动态库。如果图省事直接把libssl-dev装到运行镜像里那运行镜像里就多了一堆头文件和静态库文件这属于可以避免的体积浪费。为什么不直接复用第一阶段的镜像因为第一阶段里有GCC和CMake这些留在运行镜像里没有任何意义。多阶段构建就是为了让运行镜像尽可能干净。3.2 静态链接方案的Dockerfile如果你决定走静态链接路线Dockerfile可以精简到令人发指的程度# 第一阶段构建 FROM ubuntu:22.04 AS builder ENV DEBIAN_FRONTENDnoninteractive RUN apt-get update apt-get install -y --no-install-recommends \ build-essential \ cmake \ libssl-dev \ libcurl4-openssl-dev \ rm -rf /var/lib/apt/lists/* WORKDIR /src COPY . . # 注意这里编译参数需要调整 RUN cmake -B build -DCMAKE_BUILD_TYPERelease \ -DCMAKE_EXE_LINKER_FLAGS-static \ cmake --build build -j$(nproc) # 第二阶段运行 FROM alpine:3.19 RUN apk add --no-cache ca-certificates tzdata \ adduser -D appuser WORKDIR /app COPY --frombuilder /src/build/my_server . USER appuser EXPOSE 8080 CMD [./my_server]这里额外加了两样东西ca-certificates和tzdata。第一个是CA根证书如果你的程序要向外部发HTTPS请求没有证书文件会直接握手失败第二个是时区数据没有它程序里的时间函数可能输出UTC时间或者干脆报错。这两个都是静态链接场景中最容易漏的东西因为它们在编译时完全感知不到只有运行到对应功能时才会炸。静态链接的编译还有个大坑需要留意-static参数如果只加给C程序可能会出现链接错误提示找不到dlopen、pthread_create之类的东西。这通常是因为使用的库比如OpenSSL自身有一部分动态加载逻辑。一个通用的处理方式是额外加上-static-libgcc -static-libstdc让C/C运行时静态链接其他库动态链接——这是一种半静态方案实践中最常用。3.3 构建镜像并验证写好了Dockerfile接下来就是构建和验证。核心命令就三条# 构建镜像tag起个好记的名 docker build -t my_server:v1.0 . # 在开发机本地直接跑一下验证能不能启动 docker run --rm -p 8080:8080 my_server:v1.0 # 进入容器验证执行环境 docker run --rm -it my_server:v1.0 /bin/sh进入容器后我建议立刻做三件事一是ldd my_server看一下动态库依赖是否都能找到动态链接方案二是直接执行./my_server确认能起来三是确认当前用户是appuser而不是root。如果程序绑定的是低于1024的端口比如80、443就需要考虑端口映射或者给容器特殊权限否则非root用户会报权限不足——这是新手常踩的坑。有一条经验可以分享拿ldd检查是一个很好的习惯但是在容器里执行时不要只看有没有输出要关注有没有“not found”字样。有时候库存在但版本不对也会导致加载失败。4. 镜像瘦身与依赖管理从1.4GB到88MB的实操记录4.1 镜像体积为什么失控很多人的第一个Docker镜像往往会把自己吓一跳。我第一次给一个C项目做镜像时构建完一看镜像大小1.82GB人都傻了。查了一下原因一块是基础镜像本身就几百MB另一块是编译工具链膨胀得厉害还有一块是apt-get install时没有控制包范围连带把大量的文档、man手册、无用依赖都装了进来。镜像体积不是小事。它会拖慢构建与推送的速度也会影响容器的启动速度你自己的机器上可能问题不大但一旦涉及镜像仓库的存储配额或公网拉取体积就是实打实的成本。4.2 可落地的瘦身手段排名我按性价比从高到低排列实际操作中会用到的几个手段第一多阶段构建清理运行镜像。这个方案效果最明显直接从1.8GB降到300MB左右。运行镜像里只有运行时依赖没有编译器和源码。第二用--no-install-recommends和清理apt缓存控制基础镜像内部。这个组合能再省几十MB到一百多MB。有些-dev包会带来大量依赖如果运行阶段只是需要动态库而不是要再编译就宁可单独找对应的libxxx运行时包。第三考虑Alpine基础镜像或用scratch仅限静态链接。从300MB降到80MB左右主要是靠这一步。Alpine本身的镜像只有几MB加一个静态编译的二进制总大小通常不会超过100MB。第四编译选项优化。编译时使用-O2或-Os可以稍微缩小二进制体积。-Os是专门针对体积优化的级别如果你的程序对性能不是特别敏感值得一试。但老实说这个手段能有10%-20%的优化就不错了大头还是靠前面的镜像结构优化。第五用docker history检查镜像每一层的大小。这一条与其说是瘦身手段不如说是诊断手段。它会告诉我究竟是哪一层占的空间最大从而对症下药。执行docker history my_server:v1.0你会看到每一层的大小和创建命令比对很容易就能找到是哪一步出问题了。4.3 值得使用的编译和运行细节第一个细节CMake构建默认会生成Debug或RelWithDebInfo符号信息这些符号在容器里没有调试价值时纯属体积浪费。构建时加上-DCMAKE_BUILD_TYPEReleaseRelease模式的二进制会去掉大部分符号。第二个细节如果你用了strip命令对二进制进行裁剪能把可执行文件再减掉不少。GCC编译的Release默认还是带符号表的执行strip my_server可以去掉所有非调试符号。有些项目C异常消息可能依赖符号信息但绝大多数服务类程序strip完全没问题。第三个细节动态链接方案下你的程序依赖的所有共享库也可以通过手动拷贝的方式放到一个单独的目录并在运行时指定LD_LIBRARY_PATH这能让你彻底摆脱“基础镜像里有没有对应库”的束缚。但它和静态链接的权衡类似维护成本高而且需要精确跟踪库的传递依赖底层一点点的遗漏都会导致崩溃。我不建议作为首选方案除非你的运行环境连基础镜像都不能自由更换。我自己在实际项目中最终一个包含OpenSSL、libcurl、protobuf等功能完整的C服务镜像在静态链接Alpinestrip这套组合下能做到88MB启动耗时大约1秒。对多数业务场景这个数据已经够看了。5. 常见问题与排查技巧实录C程序部署到Docker有几个问题出现的频率高到值得单独写一节。每个问题都是我亲测踩过的坑直接给出排查思路和解决办法。5.1 启动报“error while loading shared libraries”这是C动态链接程序进容器后最经典的问题没有之一。报错信息类似./my_server: error while loading shared libraries: libssl.so.3: cannot open shared object file: No such file or directory这个问题说明运行镜像里缺少程序依赖的动态库。解决步骤是# 在容器里执行ldd看哪些依赖找不到 docker run --rm -it my_server:v1.0 /bin/sh ldd ./my_serverldd输出的每一行对应一个依赖库标着not found的就是缺的。找到缺哪个库名之后根据你的基础镜像类型安装对应的运行时包。Ubuntu/Debian用apt-file或者直接搜索在线包数据库Alpine用apk search。这里要特别提醒不要抱着“缺啥装啥编译包”的心态去装-dev版本。运行时只需要运行库不是开发库。装-dev包会把头文件和静态库都带进来又脏又大。如果你不想靠在线搜索也可以在宿主机上找到相应的.so文件后拷贝进容器但我不推荐这种手工作坊式的做法因为手工拷贝的库很可能会遗漏依赖的依赖——你解决了A.so又冒出B.so没完没了。5.2 容器内时钟/时区输出不正确程序打印出的时间比北京时间差了8小时或者日志里显示UTC。原因很直白容器镜像默认没有设置时区甚至根本没有时区数据库。如果你用的是Alpine静态链接方案解决办法很直接在运行阶段装一个tzdataRUN apk add --no-cache tzdata如果你想让容器的默认时区是北京时间可以接着加一条环境变量设置ENV TZAsia/ShanghaiUbuntu/Debian基础镜像通常自带时区数据库但默认时区是UTC同样设置一下TZ环境变量就能解决。我一般在Dockerfile里统一加上这个环境变量省得部署时才发现日志时间全错了。5.3 容器没权限访问某些资源程序的宿主目录、网络功能实际调用等会碰到权限问题。常见的表象是程序启动就报Permission denied。第一个原因是容器的默认用户是root时你可能已经绕过了很多权限限制但一旦切换到非root用户就会出现对某些目录没有写权限的问题。我建议程序的工作目录显式地设置好所有者。RUN useradd --create-home appuser \ mkdir -p /var/log/my_server \ chown -R appuser:appuser /var/log/my_server另一个是端口权限。非root用户默认不能绑定1024以下的端口。如果确实要服务80端口可以在宿主机上用端口映射把高位端口映射到80docker run -p 80:8080 ...这样容器内仍然是8080外部访问的80完全不需要特殊权限。还有一个属于网络层面的权限问题部分程序需要操作iptables或者原始套接字比如自己实现TCP/IP协议栈的服务这种场景下容器默认的网络隔离会导致操作被拒绝。最标准的解法是让容器以host网络模式运行或者使用更精细化的网络配置。考虑到安全因素这些都是要谨慎使用的操作除非你明确知道自己为什么需要否则不要盲目开启。5.4 构建时代码里用了OpenSSL运行时却出现handshake failure这个问题的直接现场是程序能启动日志也没有异常但只要一发HTTPS请求到外部就报SSL握手错误。第一个排查方向是证书文件缺失。容器镜像本身就轻量尤其是Alpine默认没有/etc/ssl/certs/ca-certificates.crt。解决办法就是上文中提到的在运行阶段安装ca-certificates包。第二个排查方向是OpenSSL版本差异。编译时的OpenSSL版本和运行时的版本如果差异较大可能表现在加密协议上比如编译时默认TLS 1.3运行时库只支持到TLS 1.2握手协议版本协商不上。这种情况最直接的应对是让编译环境和运行环境使用同一个基础镜像大版本比如都用ubuntu:22.04两边都是OpenSSL 3.0系列就不会出现这种错位。5.5 容器里/dev/urandom或随机数初始化阻塞凑巧的是我搜热词时看到你也在关注“c随机数”顺手把这个经验教训分享一下——C的std::random_device在某些实现里依赖操作系统的熵源容器里/dev/urandom通常是可用的但如果你用了/dev/random阻塞式熵源在高并发或低熵环境里有可能会卡住。你多半不会直接写/dev/random但一些老旧的加密库默认用/dev/random。排查标志是程序在启动早期就挂住不崩溃也没日志输出。解决办法不要试图去给容器注入熵源那太麻烦了直接在代码里改掉随机数的熵源指定就行或者确认库里是否可以配置使用urandom。多数现代库和操作系统都会用urandom遇到这类阻塞的概率已经很低了但既然遇到过就写出来提醒一下。5.6 容器启动秒退日志一片空白这可以说是最让人摸不着头脑的问题docker run之后容器立刻退出docker logs什么都没有。一个常见的坑是你的程序是个后台守护进程启动后fork出一个子进程然后父进程退出。在容器里这种方式基本必死——容器是以PID 1的进程生命周期为界的main进程一退容器就结束了不管你fork了什么子进程。解决办法是把程序改成前台运行模式或者在命令行里显式地执行./my_server而不是./my_server 。如果你用的启动命令被写成了CMD [/bin/sh, -c, ./my_server ]那就把去掉让进程保持在PID 1的位置。还有一个很容易被忽略的原因动态链接的程序如果在容器里找不到任何依赖ldd会提示但在极少数情况下比如动态库缺失属于未定义符号级别程序会在运行早期就段错误退出甚至来不及打印任何日志。这时候可以用docker run --rm -it --entrypoint /bin/sh my_server:v1.0进入容器手动执行观察有没有报错信息。5.7 排查工具与一条龙思路如果排查了半天一头雾水我会给你一套组合拳# 1. 先看容器的退出状态码 docker inspect --format{{.State.ExitCode}} 容器ID # 2. 看完整日志加上时间戳 docker logs --timestamps 容器ID # 3. 用debug模式进入容器手动执行程序 docker run --rm -it --entrypoint /bin/sh 镜像名 # 4. 检查程序的动态依赖 ldd ./my_server # 5. 看系统调用容器内通常没有strace但可以在宿主机上对PID执行 strace -f -o /tmp/trace.log -p 容器进程PID前四步能覆盖90%的启动类问题最后一步strace通常能定位到是缺文件、缺网络还是权限问题。如果strace输出显示ENOENT注意看是哪个系统调用触发——open一个不存在的库文件还是bind一个不存在的网络地址这会快速定位到环境问题还是代码问题。6. 构建与发布的实战流程从本地代码到远程仓库的一条龙6.1 一个完整的部署脚本理论讲了这么多最后分享一个我实际项目中在用的完整流程。它不复杂但每一步都有明确意图。# 1. 构建镜像 DOCKER_IMAGEregistry.example.com/myteam/my_server DOCKER_TAGv1.0.0-$(git rev-parse --short HEAD) docker build -t ${DOCKER_IMAGE}:${DOCKER_TAG} . # 2. 本地冒烟测试 docker run --rm -d --name smoke_test -p 8080:8080 ${DOCKER_IMAGE}:${DOCKER_TAG} sleep 3 # 3. 用一个请求验证服务正常 curl -fsS http://localhost:8080/health || { echo smoke test failed; docker logs smoke_test; exit 1; } # 4. 清理测试容器 docker rm -f smoke_test # 5. 推送镜像到仓库 docker push ${DOCKER_IMAGE}:${DOCKER_TAG} # 6. 打上一个latest的tag便于回滚 docker tag ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:latest docker push ${DOCKER_IMAGE}:latest用Git commit hash作为镜像tag的一部分可以保证每个镜像和源码提交能对应起来。以后线上出了任何问题只要看镜像tag就知道对应的是哪个commit可以直接回去看代码。这对故障排查的帮助大得惊人。冒烟测试里那个sleep 3是给服务留出启动时间。如果你的服务启动比较慢可以根据实际情况调整也可以改成循环等待健康检查通过不要盲目用固定sleep。6.2 需要注意的权限与交互问题把镜像推到私有仓库时你会遇到登录认证的问题。执行docker login registry.example.com后生成的凭证默认存放在~/.docker/config.json。在CI或自动化脚本里推荐使用专用的部署账号而不是个人账号避免密钥泄露时波及面太大。如果你的程序涉及数据库连接。在容器里连接宿主机上的MySQL/Redis时注意不要用localhost因为容器内的localhost是容器自己。要用宿主机在局域网中的IP或者在启动容器的时候加--network host让容器直接共享宿主机的网络栈。如果数据库和容器都在同一个Docker网络中直接用服务名Docker DNS解析更方便。我早期在这个问题上栽过一次查了半天发现是连接地址写错了。6.3 用Docker Compose编排C服务和依赖当你部署的不只是一个C程序还包括它依赖的MySQL、Redis时写一个docker-compose.yml是省心省力的做法。version: 3.8 services: my_server: image: registry.example.com/myteam/my_server:v1.0.0 ports: - 8080:8080 environment: - TZAsia/Shanghai - DB_HOSTmysql - REDIS_HOSTredis depends_on: - mysql - redis restart: unless-stopped mysql: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORD${MYSQL_ROOT_PASSWORD} - MYSQL_DATABASEmyapp volumes: - mysql_data:/var/lib/mysql ports: - 3306:3306 restart: unless-stopped redis: image: redis:7-alpine volumes: - redis_data:/data restart: unless-stopped volumes: mysql_data: redis_data:在这个编排里my_server访问数据库时直接用服务名mysql和redis不需要关心它们的IP地址。Docker内部的DNS会负责解析这让配置变得非常简单。depends_on保证了容器启动顺序只不过它只保证MySQL和Redis的容器先启动不那么严格地等它们内部就绪——如果你的程序启动太快会在连接数据库时碰到短暂的重试失败这属于正常现象程序里做好重试就行。6.4 Docker Desktop下的注意事项很多开发者在本地用的是Docker DesktopWindows/Mac部署到真正的Linux服务器上时可能会遇到一些差异。一个常见的问题是本地一切正常推到服务器上之后发现容器启动不了。典型原因之一是架构不同。如果你在Mac的Apple SiliconARM64上构建镜像默认生成的是ARM64架构的镜像推到x86_64的服务器上就完全没办法运行。解决办法是构建时显式指定平台docker build --platform linux/amd64 -t my_server:v1.0 .另外一个差异是换行符。Windows下写的Dockerfile如果存在CRLF换行符在某些版本的Docker里会报错或行为异常。处理办法很简单在项目根目录加一个.gitattributes文件强制所有文本文件用LF换行。* textauto eollf Dockerfile text eollf *.sh text eollf这对团队协作尤其重要否则某个同事在Windows上提交一个Dockerfile的修改其他人在Linux上构建时会莫名踩坑。7. 从一个踩坑者的角度多说几句关于C程序容器化我最后还想分享几点没法归到某个章节、但价值极高的体会。第一点容器化不是把程序塞进镜像就完了它是一个持续优化的过程。第一版能跑、第二版稳定、第三版才抠体积和安全。不要指望第一次就把所有事情做完美先把整套流程跑通再逐步迭代这是最务实的路径。第二点Dockerfile本身就是一份可追溯、可审查的环境文档。以前交接一个C项目新人光配环境就要折腾两天现在有了标准Dockerfile拉下来一条命令构建所有依赖清清楚楚。从这个角度看Docker对团队协作的节省不比你写的业务代码少。第三点静态链接虽然让人省心也别无脑用。如果你的程序用到了涉及系统底层的库比如GPU相关、内核模块交互静态链接往往不可行甚至不可能。遇到这种情况老老实实用动态链接Docker镜像锁环境效果一样好。方案是死的人是活的适合自己的实际场景才是最优的。如果你刚上手建议先拿一个现有的、规模不大的C程序练手按照上面的步骤一步步走下来。第一次可能花两三个小时但做完一次之后你就拥有了对“环境问题”免疫力。那种不用再跟“我机子上能跑”这个魔咒作斗争的感觉真心值得体会一次。
返回列表