连通性 CLI:DNS、端口、TLS 与 HTTP 证据链工具手册
健康检查超时,先别改重试次数
发布刚结束,CI 的健康检查报 curl: (28) Connection timed out,开发者却说浏览器刚才还能打开首页。最危险的处理不是工具不够多,而是把“网络”当成一个开关:有人先把超时从 5 秒改成 30 秒,有人重启服务,还有人用 curl -k 跳过证书验证。这样会同时抹掉 DNS、TCP、TLS、代理和 HTTP 五层证据。
先固定观测位置。宿主机、WSL、容器和 CI runner 即使在同一台物理机上,也可能使用不同的 DNS、路由、代理环境和 CA 信任库。下面命令中的 api.example.test、192.0.2.10 和端口都是占位符,执行时替换为获准测试的目标;命令输出进入工单前应去掉内部域名、真实地址、凭证和业务响应体。
Windows 已提供 ping、tracert、nslookup 与 Test-NetConnection。Linux 常见包名是 iputils-ping、dnsutils 或 bind-utils、traceroute、netcat-openbsd 或 nmap-ncat、curl、openssl;macOS 可通过系统工具和包管理器补齐。优先使用操作系统或发行版的受支持软件源,不要用上游压缩包覆盖系统 TLS 库。上游发布中 curl 8.21.0 是当前稳定版;OpenSSL 同时维护多个分支,4.0.1 是当前新特性线,3.5.7 是支持周期更长的 LTS。企业镜像中的版本号可能较低但带有发行版回补,升级判断要同时看供应商支持状态和安全公告,不能只比较版本字符串。
nc 存在 OpenBSD netcat、Ncat 等不同实现,-z、-w、UDP 选项并不保证一致,所以团队脚本在使用前必须识别实现。PowerShell 中也要明确调用 curl.exe,避免旧环境里的别名行为混入证据。
curl.exe --version
openssl version
Get-Command Test-NetConnection, Resolve-DnsNamecurl --version
openssl version
nc -h 2>&1 | head -n 3
dig -v版本输出不只是安装证明。curl --version 会显示协议、TLS 后端和特性;同一条命令在 Schannel、OpenSSL 或其他 TLS 后端上可能读取不同的系统信任来源。脚本依赖某个参数时,应以本机 --help all、curl 手册 和 OpenSSL 下载与支持分支 为准,而不是从另一种 netcat 或另一平台复制参数。
第一轮实验:把成功链路逐层钉住
先在本机启动一个没有认证、没有 TLS 的临时 HTTP 服务。它只用于学习 TCP 与 HTTP 证据的差别,目录中不要放配置文件或密钥。
mkdir -p /tmp/connectivity-lab
printf 'ok\n' >/tmp/connectivity-lab/health
python3 -m http.server 18080 --bind 127.0.0.1 --directory /tmp/connectivity-lab另开终端执行:
ping -c 1 127.0.0.1
nc -vz -w 2 127.0.0.1 18080
curl --noproxy '*' --fail --silent --show-error \
--output /dev/null \
--write-out 'code=%{http_code} ip=%{remote_ip} dns=%{time_namelookup} tcp=%{time_connect} total=%{time_total}\n' \
http://127.0.0.1:18080/health预期先看到 ICMP 成功,再看到 TCP connection succeeded,最后得到 code=200。这里有三个不同事实:IP 栈能到目标、18080 上有进程接受连接、该进程能完成 HTTP 请求。time_namelookup、time_connect 和 time_total 是当前请求的阶段时间,不是跨环境通用阈值;它们的价值是同一负载、同一观测点下比较趋势。
现在故意把端口改为 18081:
nc -vz -w 2 127.0.0.1 18081
curl --noproxy '*' --connect-timeout 2 --max-time 4 \
http://127.0.0.1:18081/health本机空端口通常立即返回 Connection refused:内核收到 SYN 后用 RST 明确拒绝。若目标地址被防火墙静默丢弃,则表现更可能是超时。两者不能写成同一个“端口不通”:拒绝优先检查监听地址、进程和端口映射,超时优先检查路由、安全组、防火墙和网络策略。完成实验后在服务终端按 Ctrl+C,再删除 /tmp/connectivity-lab;若 nc 仍成功,说明临时服务没有真正退出或另有进程占用了端口。
Windows 可以用对象化结果完成同一判断:
$r = Test-NetConnection 127.0.0.1 -Port 18080 -InformationLevel Detailed
$r | Select-Object ComputerName, RemoteAddress, RemotePort, InterfaceAlias, SourceAddress, TcpTestSucceededTcpTestSucceeded=True 仍不代表 TLS、HTTP、认证或业务健康,只证明一次 TCP 建连成功。telnet 也只能做到近似的端口探测,而且 Windows Telnet Client 往往需要额外启用;自动化和结构化输出优先用 Test-NetConnection。
从 DNS 开始重放真实故障
真实目标不能直接从 curl -v 开始。先问客户端究竟把名字交给了哪个解析器,得到了哪些 A 与 AAAA 记录:
dig api.example.test A
dig api.example.test AAAA
dig @<approved-resolver> api.example.test A
getent hosts api.example.testResolve-DnsName api.example.test -Type A -DnsOnly
Resolve-DnsName api.example.test -Type AAAA -DnsOnly
Resolve-DnsName api.example.test -Server <approved-resolver> -DnsOnlydig 和 Resolve-DnsName -DnsOnly 是 DNS 询问;应用解析还可能受 hosts、搜索域、缓存或平台解析策略影响,所以要把 getent hosts、实际 curl 的 remote_ip 与 DNS 答案对照。NXDOMAIN 表示名字不存在,SERVFAIL 表示解析链路未能完成,超时表示没有及时收到答复。把三者都写成“DNS 挂了”会把修复责任交错团队。
若 A 成功而 AAAA 指向不可达网络,客户端可能先尝试 IPv6,形成“第一次慢、重试又好”的现象。用下面的对照实验确认,不要直接删 AAAA:
curl -4 --connect-timeout 3 -o /dev/null -sS -w 'v4 %{remote_ip} %{time_connect}\n' \
https://api.example.test/health
curl -6 --connect-timeout 3 -o /dev/null -sS -w 'v6 %{remote_ip} %{time_connect}\n' \
https://api.example.test/health如果只有容器失败,应进入容器重复查询并查看 /etc/resolv.conf,而不是拿宿主机结果替容器作证。VPN 分流、Docker 内置解析器、Kubernetes DNS 和宿主 hosts 都可能改变名字到地址的映射。
端口通过后,单独审问 TLS
TLS 故障经常被 TCP 成功掩盖。openssl s_client 中,-connect 决定连接地址,-servername 发送 SNI,-verify_hostname 校验证书身份,-verify_return_error 让验证错误终止握手;-showcerts 只是展示服务端发送的证书列表,不等于链验证通过。
openssl s_client \
-connect api.example.test:443 \
-servername api.example.test \
-verify_hostname api.example.test \
-verify_return_error \
-brief </dev/null成功时应看到协商出的 TLS 版本、密码套件和 verification OK。失败证据要保留错误类别,例如 hostname mismatch 指向 SAN/SNI/目标域名不一致,unable to get local issuer certificate 指向服务端链或客户端信任来源,TLS alert 则需要结合 alert 类型与服务端日志继续判断。不要把 curl -k 作为修复;它只能形成一次“跳过验证后能否继续”的反向对照,而且会同时关闭证书链和主机身份验证。
当 DNS 可能指错地址时,curl --resolve 能在不改 hosts 的情况下,把“连接哪个 IP”与“URL 中的主机名、SNI 和 Host 头”拆开:
curl --resolve api.example.test:443:192.0.2.10 \
--connect-timeout 3 --max-time 8 \
-o /dev/null -sS -w 'code=%{http_code} ip=%{remote_ip}\n' \
https://api.example.test/health若这个命令成功而默认解析失败,证据指向 DNS 或地址分流;若它仍报主机名错误,证书身份或虚拟主机配置仍有问题。--resolve 只影响这一条 curl 命令,实验结束即回滚,不会像修改 hosts 那样遗留机器级状态。
显式代理与直连必须成对验证
浏览器正常而 CLI 失败,常见原因是浏览器使用系统代理和企业证书,CLI 使用环境变量或直接连接。先记录环境,不要在截图中暴露带账号密码的代理 URL:
env | grep -iE '^(http|https|all|no)_proxy=' | sed -E 's#(://)[^/@]+@#\1***@#'
curl --noproxy '*' -I https://api.example.test/health
curl --proxy http://proxy.example.test:8080 -I https://api.example.test/health--noproxy '*' 强制本次请求不走代理,--proxy 强制使用给定代理。NO_PROXY 的匹配语义与变量大小写不能假定所有工具一致,尤其不能把 curl 的行为外推到 JVM、npm、Docker 或 IDE。代理返回 407 已经证明客户端到代理的 HTTP 交互成立,下一步应处理代理认证;目标返回 401 或 403 则说明网络与 TLS 大概率已经越过,问题转向业务凭证、mTLS、IP 白名单或授权策略。
路由追踪只在“目标地址正确但路径疑似异常”时补充。先查询内核会选择的源地址、接口和下一跳,再观察沿途 TTL 响应:
# Linux:只做 FIB 查询,不向目标发送探测包
ip route get 192.0.2.10
traceroute -n 192.0.2.10
# macOS:route 查询与 traceroute 的参数不同于 iproute2
route -n get 192.0.2.10
traceroute -n 192.0.2.10Find-NetRoute -RemoteIPAddress 192.0.2.10
tracert.exe -d 192.0.2.10
Test-NetConnection 192.0.2.10 -TraceRoute -InformationLevel Detailedip route get 与 Find-NetRoute 是本地路由选择证据,不证明远端可达。Linux 和 macOS 的 traceroute 通常默认发送 UDP 探测,Windows tracert 默认发送 ICMP Echo;ACL、ECMP 和策略路由可能让它们看到不同路径。三者都依赖 TTL 到期响应,中间设备可以不回答,因此某一跳显示 * 不能证明在那里丢包,最终业务端口结果仍由 TCP/TLS/HTTP 探针确认。不要在事故处理中直接执行 ip route add、route add 或 New-NetRoute 试错:机器级路由变更会影响其他进程,必须经过变更、回滚和来源验证。
把一次排障变成项目证据
项目可以保存不含凭证的探针脚本,目标、CA 文件与代理由运行环境注入。下面脚本会把 DNS、TCP、TLS、HTTP 分别落成状态,任何一层失败都保留自己的退出码;临时文件由 trap 清理。
#!/usr/bin/env bash
set -u
: "${TARGET_HOST:?set TARGET_HOST}"
: "${TARGET_PORT:=443}"
: "${TARGET_PATH:=/health}"
: "${CONNECT_TIMEOUT:=3}"
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT INT TERM
printf '[dns]\n'
dig +time="$CONNECT_TIMEOUT" +tries=1 +noall +comments +answer \
"$TARGET_HOST" A >"$tmp_dir/dns-a.txt" 2>&1 || true
dig +time="$CONNECT_TIMEOUT" +tries=1 +noall +comments +answer \
"$TARGET_HOST" AAAA >"$tmp_dir/dns-aaaa.txt" 2>&1 || true
cat "$tmp_dir/dns-a.txt" "$tmp_dir/dns-aaaa.txt"
if grep -Eq '^[^;].*[[:space:]]IN[[:space:]]+(A|AAAA)[[:space:]]' \
"$tmp_dir/dns-a.txt" "$tmp_dir/dns-aaaa.txt"; then
dns_rc=0
else
dns_rc=1
fi
printf '[tcp]\n'
nc -vz -w "$CONNECT_TIMEOUT" "$TARGET_HOST" "$TARGET_PORT" || tcp_rc=$?
: "${tcp_rc:=0}"
printf '[tls]\n'
openssl s_client -connect "${TARGET_HOST}:${TARGET_PORT}" \
-servername "$TARGET_HOST" -verify_hostname "$TARGET_HOST" \
-verify_return_error -brief </dev/null >"$tmp_dir/tls.txt" 2>&1 || tls_rc=$?
: "${tls_rc:=0}"
grep -E 'Protocol|Ciphersuite|Verification|verify error' "$tmp_dir/tls.txt" || true
printf '[http]\n'
curl --fail --silent --show-error --output /dev/null \
--connect-timeout "$CONNECT_TIMEOUT" --max-time "$((CONNECT_TIMEOUT * 3))" \
--write-out 'code=%{http_code} ip=%{remote_ip} dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect} total=%{time_total}\n' \
"https://${TARGET_HOST}:${TARGET_PORT}${TARGET_PATH}" || http_rc=$?
: "${http_rc:=0}"
printf 'result dns=%s tcp=%s tls=%s http=%s\n' \
"$dns_rc" "$tcp_rc" "$tls_rc" "$http_rc"
test "$dns_rc" -eq 0 && test "$tcp_rc" -eq 0 && \
test "$tls_rc" -eq 0 && test "$http_rc" -eq 0这个脚本适合受控 smoke endpoint,不适合对真实用户接口循环压测。dig 对 NOERROR 但无 A/AAAA 答案的查询仍可能正常退出,因此脚本还检查 Answer Section;输出中的 status 能继续区分 NXDOMAIN、SERVFAIL 与超时。至少得到一个地址才继续把 DNS 记为成功,双栈质量仍应分别观察 A、AAAA 和 curl -4/-6。
CONNECT_TIMEOUT 影响 DNS/TCP 建连等待,--max-time 限制整次 HTTP 传输;两者过短会制造假失败,过长会拖住 runner。生产值应来自该观测点的延迟基线和流水线预算,而不是照搬示例值。连续运行时记录每层成功率与分位趋势,不能只保留最终 exit code,否则 DNS 变慢和服务端变慢会再次混在一起。
仓库只保存脚本和脱敏后的输出格式,不保存 token、Cookie、代理密码、客户端私钥或真实响应体。若必须发送认证,优先从进程环境或受控 secret store 注入,并避免 --trace-ascii、-v 和 shell xtrace 把请求头写进日志。客户端证书的私钥文件应限制读取权限,命令行只引用路径,不把 PEM 内容或口令放进参数历史。
架构师怎样判断该停在哪一层
ping 成本低,但 ICMP 常被禁用,成功与失败都不能代表服务可用;nc 和 Test-NetConnection 快速证明 TCP,却不理解 TLS 与业务;openssl s_client 能把 SNI、证书链和协议协商拆开;curl 最接近真实 HTTP 客户端,但会带入代理、CA、重定向、认证和协议栈差异。工具不是互相替代,而是把失败边界逐步缩小。
长期治理的关键是限制探针的副作用与基数。不要让每个项目、每个实例、每个目标路径都生成独立高基数指标;按服务、观测点和失败层聚合,原始输出短期留存,凭 trace id 去服务端关联。探针频率乘以 runner 数、目标数和 TLS 握手成本,就是额外流量、CPU、日志与证书验证成本。健康端点应轻量、无用户数据、无破坏性操作,并由容量预算决定频率。
当证据已经指出 DNS 记录、网络策略、证书链、代理认证或业务授权中的某一层,就把修复交给该层的 owner,并在原观测点用同一命令复测。回滚同样按层执行:撤销临时 hosts 或代理覆盖,停止本地监听,删除临时 CA 与输出文件,恢复探针频率。最终记录应能回答从哪里测、解析到哪里、连到哪个地址、在哪一层停止、错误原文是什么;一句“网络恢复”无法防止同类故障再次发生。
