这是一篇踩坑笔记,主要记录的是问题排查的过程。
问题:docker-compose部署Postgres通过nginx的TCP转发机制避免对外暴露接口而一直报错:Connection attempt time out、Connection Refused、Connection Reset。
场景说明
使用docker-compose分开独立部署posetgres和nginx容器,借助nginx的TCP转发机制暴露15432端口给外部,隐藏5432端口,达到保护数据库连接安全的目的。
使用的自定义目录是/dev-ops,手动创建容器共用网络:
# 基础创建
docker network create middleware_net
创建mysql容器
/dev-ops目录下新建mysql的docker-compose-mysql.yml
version: "3.8"
services:
mysql:
image: mysql:8.0-debian
container_name: mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: Root@123456
MYSQL_DATABASE: test_db
MYSQL_ROOT_HOST: '%'
volumes:
- ./mysql/data:/var/lib/mysql:z
- ./mysql/conf/my.cnf:/etc/mysql/my.cnf:ro
networks:
middleware_net:
networks:
middleware_net:
external: true
/dev-ops目录下新建文件夹用于容器内部路径绑定:
mkdir -p /dev-ops/mysql/data
mkdir -p /dev-ops/mysql/conf
/dev-ops/mysql/conf下新建文件my.cnf:
[mysqld]
# 字符集修正
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
# 基础配置
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /var/lib/mysql
secure-file-priv = /var/lib/mysql-files
# 关闭严格报错可选
sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
# 最大连接
max_connections = 1000
default-storage-engine = INNODB
# innodb优化
innodb_buffer_pool_size = 512M
innodb_log_file_size = 256M
# 时区
default_time_zone = '+8:00'
[mysql]
default-character-set=utf8mb4
启动镜像,进入/dev-ops目录:
docker-compose -f docker-compose-mysql.yml up -d
查看镜像运行状态:
docker ps
# 查看日志确认正常启动
docker-compose -f docker-compose-mysql.yml logs
创建nginx容器配置TCP转发
Docker部署nginx技能书掉落:http://qiubuer.cn/posts/install-nginx-ssl/
需要修改的地方有两处:
-
docker-compose-nginx.yml文件:绑定公共网络,监听13306端口
version: '3.8' services: nginx: image: nginx:1.25.1 container_name: nginx restart: always ports: - '443:443' - '80:80' - '13306:13306' volumes: - ./nginx/logs:/var/log/nginx - ./nginx/html:/usr/share/nginx/html - ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf - ./nginx/conf/conf.d:/etc/nginx/conf.d - ./nginx/ssl:/etc/nginx/ssl/ privileged: true networks: middleware_net: networks: middleware_net: external: true -
修改/dev-ops/nginx/conf/nginx.conf,主要是添加
stream {}部分user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; events { worker_connections 1024; } stream { # limit_conn_zone $binary_remote_addr zone=tcp_conn_zone:10m; server { listen 13306; proxy_pass mysql:3306; proxy_connect_timeout 15s; proxy_timeout 3600s; } } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; include /etc/nginx/conf.d/*.conf; }
云服务器放行13306端口,防火墙放行13306端口:
friewall-cmd --permanent --port-add=13306/tcp
本地启用DBeaver或者Navicat连接远程数据库,可以正常连接就是成功了。
连接异常排查措施
-
查看容器网络:
# middleware_net是我创建的容器网络,手动绑定在容器启动的docker-compose.yml中 docker network inspect middleware_net -
查看宿主机端口监听:
# 在服务器执行,没有输出就是没被监听 ss -tulpn | grep 13306 -
检查Nginx的Stream配置:
# success或者ok代表没有语法错误,但是正确的参数放入错误的块配置中这种检查不出来,而且会导致nginx反复重启,一直restarting docker exec nginx nginx -t重载Nginx
docker exec nginx nginx -s reload进入Nginx确认mysql连通性
docker exec -it nginx bash # 检测 mysql:3306 timeout 2 bash -c "</dev/tcp/mysql/3306 && echo 连通成功 || echo 连接拒绝" -
防火墙放行情况排查
sudo firewall-cmd --list-ports # 本地电脑cmd测试服务器端口连通性 telnet 你的服务器公网IP 13306 # 上面那条不行,就使用powershell Test-NetConnection -ComputerName 你的服务器IP或域名 -Port 端口号 -
查看Mysql运转情况
# 查看mysql状态是否 running docker compose -f docker-compose-mysql.yml ps # 看日志是否启动成功 docker logs mysql
Mysql本地连接报错:Public Key Retrieval is not allowed
MySQL8.0 默认认证插件是 `caching_sha2_password`,密码传输依靠服务器 RSA 公钥加密;
数据库客户端(DBeaver、Navicat、JDBC 等)**默认禁止自动拉取 MySQL 公钥**,于是抛出 `Public Key Retrieval is not allowed`。
### DBeaver
1. 编辑连接 → **驱动属性**
2. 找到:`allowPublicKeyRetrieval` = `true`
3. `useSSL` = `false`
4. 重新测试连接即可。
### Navicat
1. 编辑连接 → 高级
2. 勾选 **允许公钥检索**,取消勾选「使用 SSL」。
Nginx中TCP转发机制
还是以mysql为例,完整的工作流程:
-
监听端口
Nginx 在宿主机监听 13306 端口,等待外网客户端发起 TCP 连接。
-
接收客户端 TCP 握手
客户端完成三次握手连上 Nginx 的 13306 端口,Nginx 创建客户端连接 SocketA。
-
选择后端并建立上游连接
读取 proxy_pass 配置(mysql:3306 ),利用 Docker 内部 DNS 解析容器名mysql 得到内网 IP,Nginx 扮演客户端,主动和 MySQL 容器的 3306 端口建立第二条 SocketB。
-
双向透明摆渡数据流(核心)
-
客户端发来 MySQL 报文 → Nginx 原样转发给 MySQL;
-
MySQL 返回数据 → Nginx 原样转发回客户端;
全程不会拆解、解析 MySQL 协议内容,纯粹做管道转发。
-
-
连接生命周期控制
proxy_connect_timeout:Nginx 连接 MySQL 的超时时间;- proxy_timeout:两端长时间无数据自动断开空闲连接;
客户端断开连接时,Nginx 同步断开与 MySQL 的连接。
本质:Nginx 充当「桥梁」,创建两条独立 TCP 连接,做数据双向拷贝,不是内核 NAT 端口映射。