这是一篇踩坑笔记,主要记录的是问题排查的过程。

问题: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/

需要修改的地方有两处:

  1. 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
    
  2. 修改/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连接远程数据库,可以正常连接就是成功了。

连接异常排查措施

  1. 查看容器网络:

    # middleware_net是我创建的容器网络,手动绑定在容器启动的docker-compose.yml中
    docker network inspect middleware_net
    
  2. 查看宿主机端口监听:

    # 在服务器执行,没有输出就是没被监听
    ss -tulpn | grep 13306
    
  3. 检查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 连接拒绝"
    
  4. 防火墙放行情况排查

    sudo firewall-cmd --list-ports
    
    # 本地电脑cmd测试服务器端口连通性
    telnet 你的服务器公网IP 13306
    
    # 上面那条不行,就使用powershell
    Test-NetConnection -ComputerName 你的服务器IP或域名 -Port 端口号
    
  5. 查看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为例,完整的工作流程:

  1. 监听端口

    Nginx 在宿主机监听 13306 端口,等待外网客户端发起 TCP 连接。

  2. 接收客户端 TCP 握手

    客户端完成三次握手连上 Nginx 的 13306 端口,Nginx 创建客户端连接 SocketA。

  3. 选择后端并建立上游连接

    读取 proxy_pass 配置(mysql:3306 ),利用 Docker 内部 DNS 解析容器名mysql 得到内网 IP,Nginx 扮演客户端,主动和 MySQL 容器的 3306 端口建立第二条 SocketB。

  4. 双向透明摆渡数据流(核心)

    • 客户端发来 MySQL 报文 → Nginx 原样转发给 MySQL;

    • MySQL 返回数据 → Nginx 原样转发回客户端;

      全程不会拆解、解析 MySQL 协议内容,纯粹做管道转发。

  5. 连接生命周期控制

    • proxy_connect_timeout:Nginx 连接 MySQL 的超时时间;
    • proxy_timeout:两端长时间无数据自动断开空闲连接;

客户端断开连接时,Nginx 同步断开与 MySQL 的连接。

本质:Nginx 充当「桥梁」,创建两条独立 TCP 连接,做数据双向拷贝,不是内核 NAT 端口映射