
Sidecar注入后net.Dial突然超时或拒绝连接
根本原因不是数据库挂了,而是应用容器的网络栈被重定向到了Sidecar代理(比如Istio的istio-proxy),但Go默认的net.Dial没走代理,直连目标IP——而K8s里Service IP通常不响应直连请求。你看到的dial tcp 10.96.123.45:5432: i/o timeout,其实是发包进了黑洞。
解决办法不是改DNS或加host,是让Go明确走localhost代理端口:
- 确认Sidecar监听的本地出站端口(Istio默认
15001,Linkerd是4140) - 在
sql.Open前设置环境变量:export DB_HOST=localhost:15001,然后用这个地址建连接串 - 若用
pgx等驱动,避免硬编码host=db-svc,改用host=localhost port=15001 - 不要依赖
http.ProxyFromEnvironment——它对TCP连接无效
sql.DB的SetConnMaxLifetime在Sidecar下必须调小
Sidecar会主动断开空闲连接(Istio默认900秒),但Go的sql.DB默认ConnMaxLifetime是0(永不过期)。结果就是连接池里一堆“看着活着、实际已被proxy踢掉”的连接,下次db.Query直接报read: connection reset by peer。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 把
db.SetConnMaxLifetime(5 * time.Minute)写进初始化逻辑,比Sidecar的keepalive timeout至少短2分钟 - 同时设
db.SetMaxIdleConns(20)和db.SetMaxOpenConns(50),避免连接数震荡冲击Sidecar - 别信文档里“设为0性能最好”——在mesh里这是反模式
用pgxpool替代sql.DB时,健康检查必须绕过Sidecar
pgxpool默认用PING做连接健康检查,而这个PING走的是原始数据库地址(比如postgres://db-svc:5432),不会经过Sidecar,导致连接池反复驱逐“其实能用”的连接。
正确做法是关掉自动PING,改用手动探测:
- 创建pool时传
pgxpool.Config{HealthCheckPeriod: 0}禁用自动检查 - 单独起goroutine,用
net.Dial("tcp", "localhost:15001", nil)探测Sidecar端口是否存活 - 若Sidecar健康,再用
pool.Acquire(ctx)拿连接——此时连接已由proxy中转,自然可靠 - 注意:不要在Acquire前做DB级PING,那又绕回去了
Envoy日志里出现upstream_reset_before_response_started{connection_failure}
这说明Sidecar尝试把请求转发给数据库时,底层TCP连接失败了。常见于数据库Pod刚启动、liveness探针还没通过,但Sidecar已开始转发流量。Go客户端不会自动重试这种底层错误,db.Query直接panic或返回context deadline exceeded。
应对关键点:
- 数据库StatefulSet必须配
readinessProbe,且探针路径要真实连库(比如exec: psql -U test -c "SELECT 1") - Go代码里所有
db.Query都套retry.Do(用github.com/avast/retry-go),重试条件包含strings.Contains(err.Error(), "connection reset") - 禁止在init container里预热连接池——Sidecar此时可能还没就绪
Sidecar环境里,连接不是“建一次用到底”,而是“每次都要假设它刚断过”。最易忽略的是把数据库探针和应用探针解耦——它们的就绪节奏完全不同。










