Docker容器内应用经SSH隧道连接PostgreSQL报连接拒绝排查
故障原因
- Docker容器使用独立的网络命名空间,拥有自己的回环地址、路由表和端口监听范围。容器内部的
localhost/127.0.0.1指向容器本身,而非运行容器的宿主机。你在业务应用连接串中填写localhost:5442,实际是在尝试访问容器自身的5442端口,该端口没有任何服务监听,自然会抛出连接拒绝错误。 - 你执行的
ssh -L 5442:localhost:5442 root@s1命令建立的本地端口转发,默认只会绑定宿主机的127.0.0.1回环地址,仅宿主机本机上运行的进程可以访问这个隧道端口,Docker网桥网段的流量默认无法触达这个监听端口。 - 你本机运行的Beekeeper客户端能正常连接,是因为它直接运行在宿主机环境中,访问宿主机127.0.0.1:5442正好命中SSH隧道的监听范围,因此连通正常。
可行解决方法
按推荐优先级排序:
方案一:调整SSH隧道监听配置,通过宿主机网关访问(改动最小)
- 终止当前运行的SSH隧道进程,重新建立隧道时增加绑定参数,让隧道监听宿主机所有网卡地址:
ssh -N -b 0.0.0.0 -L 5442:localhost:5442 root@s1注意:如果执行后仍无法从Docker网段访问,需要检查本机SSH客户端配置文件
/etc/ssh/ssh_config,将GatewayPorts参数设置为yes后重启SSH隧道,否则0.0.0.0绑定不会生效。 - 调整业务容器的启动配置和数据库连接串:
- 启动容器时增加宿主机映射参数,让容器可以通过固定域名访问宿主机:
docker run --add-host=host.docker.internal:host-gateway <你的业务镜像名> - 将数据库连接串中的
localhost替换为host.docker.internal,最终连接串格式为:postgresql://postgres:<你的数据库密码>@host.docker.internal:5442/jackie
- 启动容器时增加宿主机映射参数,让容器可以通过固定域名访问宿主机:
- 检查宿主机本地防火墙规则(ufw/firewalld/iptables),放通5442端口对Docker网桥网段的访问权限,避免流量被防火墙拦截。
- 终止当前运行的SSH隧道进程,重新建立隧道时增加绑定参数,让隧道监听宿主机所有网卡地址:
方案二:在业务容器内部直接建立SSH隧道(隔离性最佳)
如果你不希望数据库隧道对宿主机上所有进程/容器开放,可以直接把隧道建在业务容器内部:- 调整业务镜像的Dockerfile,安装SSH客户端依赖:
RUN apt update && apt install -y --no-install-recommends openssh-client && rm -rf /var/lib/apt/lists/* - 通过Docker secret或者挂载卷的方式,将s1服务器的SSH免密私钥传入容器(禁止将私钥明文打包到镜像层,避免泄露),配置容器到s1的免密登录。
- 修改容器的
entrypoint.sh启动脚本,先启动SSH隧道再拉起uwsgi服务:#!/bin/bash # 静默启动SSH本地端口转发,绑定容器内回环地址 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -Nf -L 5442:localhost:5442 root@s1 # 启动原有业务服务 exec uwsgi
该方案下你原来的数据库连接串不需要做任何修改,因为隧道直接监听在容器自身的127.0.0.1:5442端口。
- 调整业务镜像的Dockerfile,安装SSH客户端依赖:
方案三(不推荐,安全风险高):公网暴露数据库端口
直接修改s1服务器上PostgreSQL容器的启动参数,将原来的127.0.0.1:5442->5432/tcp端口映射改为0.0.0.0:5442->5432/tcp,同时在s1服务器的防火墙配置严格的IP白名单,仅允许你业务服务器的公网IP访问5442端口,之后业务容器直接通过s1服务器公网IP:5442连接数据库。该方案会将数据库端口暴露在公网,若白名单配置疏漏很容易发生数据库被爆破、数据泄露的风险,非特殊场景不建议使用。
内容的提问来源于stack exchange,提问作者Nemo
相关产品推荐
相关产品推荐

