Docker + Rust 构建问题分析

Docker + Rust 构建问题分析
SoniaDocker + Rust 构建问题分析
问题
- 容器启动后立即退出 (
exited with code 0) - 完全没有任何日志输出(连
main()第一行都没打印) - 改小版本号无效 (v3.1.9 → v3.2.0 都失败)
- 改大版本号就好了 (v3.x → v4.0.0 突然成功)
- 不同机器构建结果不一致(本地失败,服务器成功)
关键线索:二进制文件大小
正常启动的:22MB ~ 24MB
启动失败的:443KB ~ 450KB ← 这是损坏的!
根本原因
Cargo 增量编译 + Docker 缓存的潜在问题
Dockerfile 中常见的"依赖缓存优化"会导致问题:
# 步骤 1:缓存依赖
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release
# ↑ 这里生成了一个 450KB 的 dummy 二进制
# 步骤 2:删除假源码
RUN rm -rf src
# 步骤 3:复制真实源码
COPY . .
# 步骤 4:真正构建
RUN cargo build --release
# ↑ 问题出现在这里!
关键问题
- Cargo 检测到
target/release/已存在 dummy 二进制 - 增量编译判断:源码没变化(因为 Docker 缓存)
- Cargo 决定:不需要重新编译,直接复用旧二进制
- 结果:最终镜像里是那个 450KB 的废物二进制!
为什么版本号变更有时能解决问题?
v3.1.9 → v3.2.0 无效
Docker 认为这是"小改动"
→ 继续使用缓存层
→ 继续使用损坏的二进制
v3.x → v4.0.0 有效
Docker 认为这是"重大变更"(主版本号变化)
→ 某些缓存策略被重置
→ 意外触发了完全重新构建
→ 生成了正确的 24MB 二进制
但这不是可靠的修复!下次 v4.1.0 可能又会失败!
为什么这个问题如此隐蔽?
1. 编译成功,没有错误
[builder 8/8] RUN cargo build --release ✓ 0.6s
2. 容器启动成功,立即退出
solji_indexer_sonia exited with code 0
exit 0= “正常退出”(误导性结果!)- 实际:二进制损坏,连
main()都没执行
3. 完全没有日志
fn main() {
println!("Starting..."); // ← 这行永远不会执行
}
因为二进制根本就是假的 fn main() {}
4. 本地和服务器表现不一致
- 本地 Mac (arm64):缓存污染,生成 450KB
- 服务器 (amd64):重新构建,生成 107MB
- 造成错觉:“是不是架构问题?”
最终解决方案
核心思路:强制清理旧二进制,禁止增量编译复用
FROM rust:1.83-bookworm AS builder
RUN apt-get update && apt-get install -y \
pkg-config libssl-dev build-essential ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# ===== 步骤 1:缓存依赖(这个没问题)=====
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && \
echo "fn main() {}" > src/main.rs && \
cargo build --release --locked
# ===== 步骤 2:清理旧二进制(关键!)=====
RUN rm -rf target/release/solji-indexer \
target/release/deps/solji_indexer* \
target/release/.fingerprint/solji-indexer-*
# ===== 步骤 3:复制真实代码 =====
COPY . .
# ===== 步骤 4:完整构建 =====
RUN cargo build --release --locked
# ===== 步骤 5:验证二进制大小(重要!)=====
RUN SIZE=$(stat -c%s target/release/solji-indexer 2>/dev/null || stat -f%z target/release/solji-indexer) && \
echo "Binary size: $SIZE bytes" && \
if [ $SIZE -lt 5000000 ]; then \
echo "ERROR: Binary too small ($SIZE bytes), build failed!" && exit 1; \
fi
# ===== Runtime 镜像 =====
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y \
libssl3 ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY /app/target/release/solji-indexer /usr/local/bin/solji-indexer
COPY .env /usr/local/bin/.env
WORKDIR /usr/local/bin
EXPOSE 8080
CMD ["/usr/local/bin/solji-indexer"]
防御措施
1. 构建时验证二进制大小
RUN SIZE=$(stat -c%s target/release/solji-indexer) && \
[ $SIZE -gt 5000000 ] || (echo "Build failed: binary too small" && exit 1)
2. 使用 --locked 参数
RUN cargo build --release --locked
确保使用 Cargo.lock 中的精确版本
3. 构建后立即测试
# 在 builder 阶段测试二进制能否运行
RUN ./target/release/solji-indexer --version || \
(echo "Binary is broken!" && exit 1)
4. 清理 Docker 缓存
# 每次重要构建前执行
docker builder prune -af
docker build --no-cache -t myimage:tag .
5. CI/CD 中固定构建环境
# GitHub Actions
- name: Build Docker Image
run: |
docker build --no-cache \
--build-arg BUILDKIT_INLINE_CACHE=1 \
-t sonia831/solji-indexer:${{ github.sha }} .
检测清单
# 1. 检查镜像大小
docker images | grep solji-indexer
# 应该 > 100MB
# 2. 检查二进制大小
docker run --rm --entrypoint ls myimage:tag -lh /usr/local/bin/solji-indexer
# 应该 > 20MB
# 3. 测试启动
docker run --rm myimage:tag --version
# 应该有输出
# 4. 查看依赖库
docker run --rm --entrypoint ldd myimage:tag /usr/local/bin/solji-indexer
# 应该能看到 libssl, libc 等










