一篇 6 月 16 日发布的技术笔记,把一个容器排障里的小技巧重新带回视野:当镜像里没有 curl、wget,也不方便临时安装工具时,Bash 的 /dev/tcp/host/port 重定向仍可能打开一个 TCP socket,再由工程师手写一段 HTTP 请求,检查内网服务的 /health 是否可达。
这件事真正重要的地方,是它贴近今天容器镜像“越做越瘦”的现实。Distroless、Alpine、最小 Debian 镜像常把调试工具移出运行时环境,安全面更小,排障成本也更高。/dev/tcp 不是银弹,只是一根备用火柴:能照亮问题是不是网络连通性,却不能替你完成完整 HTTP 客户端该做的事。
Bash 打开的不是 HTTP,而是一个原始 TCP 连接
原文给出的核心命令很短:exec 3<>/dev/tcp/service/8642 打开到主机和端口的连接;printf 'GET /health HTTP/1.1\r\nHost: service\r\nConnection: close\r\n\r\n' >&3 写入请求;cat <&3 读取响应。输出会包括状态行、响应头、空行和正文。
这里最容易误读的一点,是 /dev/tcp 并不是 Linux 内核提供的真实设备文件。你去 ls /dev/tcp,通常什么也看不到。它是 Bash 在处理重定向时识别的特殊路径,由 Bash 自己做 DNS 解析和 connect(2) 调用,再把 socket 交给文件描述符 3。
| 场景 | 常规做法 | 无工具时的做法 | 判断 |
|---|---|---|---|
| 检查 HTTP 健康接口 | curl http://service:8642/health | Bash /dev/tcp 加手写 GET | 可用于快速定位连通性 |
| 调试 HTTPS/API | curl、httpie 或 SDK | 需另引入 openssl s_client 等工具 | 不适合临时拼凑 |
| 生产脚本 | 成熟 HTTP 客户端 | 不建议依赖 | 错误处理太薄 |
最该记住的限制,是 HTTP/1.1 默认不会主动断开
这类技巧对 DevOps、SRE 和后端工程师有现实意义。凌晨排障时,问题往往不是“接口语义是否正确”,而是“这个 Pod 到那个服务名到底通不通”。如果容器没有包管理器,重建镜像又要走流水线,能在已有 Bash 里发出一次明文 GET,就能少绕一圈。
但它也有几个硬边界。HTTP/1.1 请求最好带上 Connection: close,否则服务端可能保持连接,cat 会一直等 EOF,看起来像命令挂死。稳妥做法是再包一层 timeout 6 bash -c '...',避免排障命令本身变成新问题。
更大的限制在协议层。这个办法不支持 TLS、重定向、压缩、chunked 响应解析、重试、Cookie 管理,也不会像 curl 那样给出清晰错误码。用于 HTTPS 场景时,除非额外引入 openssl 等工具,否则 /dev/tcp 只能拿到一条原始加密连接,读不懂内容。
容器越精简,临时排障越依赖基本功
行业惯例正在把运行时镜像做得更小。Google 推过 distroless 镜像,Docker 官方镜像也长期鼓励多阶段构建,把编译器、调试器和包管理器留在构建阶段。这降低了攻击面,却把线上排障推向两条路:要么准备 sidecar、ephemeral container、专用 debug 镜像;要么掌握少数不依赖外部二进制的底层技巧。
/dev/tcp 属于后者,但还要看环境。它是 Bash 的非 POSIX 特性,不属于 /bin/sh、dash 或 zsh 的通用能力;Bash 构建时还必须启用 --enable-net-redirections。主流发行版大多可用,但旧系统或极简构建不能默认假设。
接下来真正该观察的,不是大家会不会用 Bash 替代 curl,而是团队是否把调试能力前置设计好:生产镜像保持克制,排障入口另行准备。临时技巧能救急,工程制度才减少救急。
