目标:
https://rpc.myxingchen.xyz/api/echo返回 JSON,延迟 < 10ms 链路:浏览器 → nginx(443) → rpc-proxy(Flask, 8080) → echo-server(C++, 9000) 最终:4.18ms HTTPS / 0.63ms 直连
把 RPC 框架扔上公网,听起来简单——docker build && docker run 完事。我踩了 9 个坑,4 个 deploy 晚上,反复搞。这篇文把每个坑的现象、根因、修法、教训完整记录,下次别再重复。
一、坑 1:gcr.io/distroless 拉不到
现象
| |
根因
distroless 是 Google 维护的极简镜像,默认在 gcr.io 上。国内访问 gcr.io 不稳,有时通有时不通,IP 段被干扰。
修法
不用 distroless,改 debian:bookworm-slim——前者 ~20MB,后者 ~75MB,差 55MB 但100% 能拉。
| |
教训
国内生产不用 gcr.io。要小镜像,用 alpine (但 glibc 兼容差) 或自建 registry mirror。bookworm-slim 是最稳的中庸选择。
二、坑 2:git push 把 build/ 也传上 GitHub
现象
git push 后 GitHub 仓库多出 200MB 的 build/ 目录——里面是 CMake 中间文件、.o、可执行文件。
根因
部署到 server 时,我在 server 上 clone → build → docker build——build/ 目录不该 commit,但 .gitignore 漏了。
修法
- 立刻:
git rm -r --cached build/+ commit - 彻底:
git filter-repo --path build/ --invert-paths+ force push - 预防:
.gitignore加build/,*.o,*.a
教训
任何用 make / cmake 的项目,第一件事是写 .gitignore。build/ / *.o / .cache/ / compile_commands.json 都不该进仓库。
三、坑 3:cpp 文件用 uint32_t 但没 #include <cstdint>
现象
| |
根因
GCC 13 比 GCC 9/10 严格——uint32_t 必须显式 #include <cstdint>,以前能靠别的头文件间接 include,现在不行。
修法
Dockerfile 头注入:
| |
不优雅但work。正经修法是逐文件补,但 50+ 个文件太多了。
教训
升级 GCC 大版本要重扫头文件。CI 里加 -Wall -Wextra -Werror 能提前发现。
四、坑 4:protobuf 生成的 *.pb.cc 漂移
现象
| |
根因
我之前 commit 了一个旧版 *.pb.h,但 cmake 重跑时 protoc 重新生成了不同名字的——/build/rpc_service.pb.h 跟仓库里的 rpc_service.pb.h 内容不一致。
修法
删除仓库里所有 *.pb.cc / *.pb.h——这些是生成产物,不该 commit。CMake 每次 build 时 protoc 重新生成。
| |
教训
任何"由代码生成的代码"都不该 commit——*.pb.* / *_generated.* / *_grpc.pb.go / node_modules/ 全部 .gitignore。
五、坑 5:examples/echo/echo_server.cc 用过期 Codec API
现象
| |
根因
Codec 库之前是 1 参 API:decode(Buffer&) → DecodedPacket;重构后变 2 参:decode(Buffer&, DecodedPacket&)。examples 没同步更新。
修法
照新 API 重写 echo_server.cc,顺手用新的 encodeResponse():
| |
教训
库改 API 时,examples 是最后改的——但 examples 又是新人第一个看的代码,应该最干净。给 examples 配独立 CI(每次 cmake --build 后跑一遍 example)。
六、坑 6:CMake 找 binary 路径不固定
现象
| |
根因
CMake 默认 build 在 build/,但子目录里有自己的 CMakeLists 时,可能变成 build/<sub>/<sub>/binary——examples/echo/CMakeLists.txt 写到 build/echo/,但 binary 在 build/echo/echo_server,examples/ 前缀没了。
修法
用 find 探,不写死路径:
| |
教训
CI/CD 里的路径不要 hardcode——加一个 sub project 就崩。用 find / $(wildcard) / $(shell) 探真实路径。
七、坑 7:stage-2 base 缺 GLIBCXX_3.4.32
现象
镜像跑起来,程序秒崩:
| |
根因
GCC 13 编译的 C++ 代码用了新的 std::format / 一些 STL 内部 API,需要 GLIBCXX_3.4.32。debian:bookworm-slim 默认 libstdc++ 是 12 版本,不够新。
修法
静态链接 libstdc++ + libgcc——binary 自包含,不依赖 base image 的 libstdc++:
| |
教训
GCC 13 + 老 base image,必须 -static-libstdc++ -static-libgcc。要不就用 debian:sid (有新版 libstdc++) 配 apt 装,但镜像大 100MB+。
八、坑 8:docker-compose.yml 端口 vs binary listen 端口不一致
现象
502 Bad Gateway。docker compose ps 显示 echo-server Up,但 rpc-proxy 根本没起来。
根因
docker-compose.yml:echo-server.expose: "8888"docker-compose.yml:rpc-proxy.environment.ECHO_PORT: "8888"- 但 echo_server.cc 里
htons(9000)—— binary 听 9000,compose 配 8888
即使 rpc-proxy 起来,连 echo-server:8888 = 拒绝连接 → 502。
修法
两套改一套——我选改 compose(不改业务代码):
| |
教训
deploy 配置 double check——compose、nginx、env 三处端口必须对齐。用 EXPOSE 变量避免 hardcode。
九、坑 9:healthcheck 用 python3 但 debian-slim 没装
现象
容器 Up,但 STATUS 永远 (unhealthy):
| |
根因
compose healthcheck 写的是 python3 -c "import socket..."。debian:bookworm-slim 默认没装 python3——ExitCode 127 = command not found。
修法
用 bash /dev/tcp/host/port(纯 bash,无依赖):
| |
关键:exec 3<>/dev/tcp/... 立即 connect,然后 exec 3<&- 立即关——不要 cat < /dev/tcp/...,那是等数据,会 hang 到 timeout。
教训
healthcheck 命令要在目标镜像里"100% 可用"。debian-slim 没 python3 / curl / nc,只能 bash /dev/tcp。要装就要在 stage-2 apt 装,但会让镜像大 30MB+。
十、总结:9 个坑的根因分布
| 类型 | 坑 | 教训一句话 |
|---|---|---|
| 网络 | 1, 2 | 国内不用 gcr.io / build/ 必加 .gitignore |
| 编译 | 3, 4, 5, 6 | GCC 13 要 cstdint / 生成产物不 commit / examples 跟库同步 / 路径别写死 |
| 运行时 | 7, 9 | 静态链 / healthcheck 别用 python3 |
| 配置 | 8 | deploy 配置三处对齐 |
十一、最终文件清单(可照搬)
Dockerfile.echo(2 阶段)
| |
docker-compose.yml(关键片段)
| |
十二、这 9 坑复盘的最大教训
build 成功 ≠ 部署成功 ≠ 容器健康 ≠ 业务可用——四个 flag 都要看到。
[+] build N/N+✔ Image ... Built→ 镜像造好了docker compose ps没有(unhealthy)→ 容器没被 healthcheck 判死- 业务 log 有
listening on X→ 业务进程起来了 curl .../api/echo返回 200 + 正确 JSON → 真通了
任何只验其中一个就报"成功" = 浪费你时间。这 9 坑里我有 3 个坑是已经看到部分 flag 就说"通了",结果用户跟着部署踩了后面的坑。
相关阅读: