Docker容器中PHP-FPM通过SSH隧道连接远程MySQL数据库问题
Docker中PHP API通过SSH隧道连接远程MySQL的解决方案
我刚帮朋友处理过类似的场景,Docker容器里跑PHP API连需要SSH隧道的MySQL,用密码验证的话确实有几个容易踩的坑,给你梳理下可行的方案:
第一步:确保容器内有必要的工具
默认的PHP Docker镜像(比如php:fpm)是不带sshpass和openssh-client的,所以得先在Dockerfile里安装它们:
# 假设用Debian系的PHP镜像 RUN apt-get update && apt-get install -y --no-install-recommends sshpass openssh-client \ && rm -rf /var/lib/apt/lists/*
--no-install-recommends是为了减少镜像体积,用完清理apt缓存也很重要。
方案一:容器启动时预先建立SSH隧道(推荐用于API服务)
这种方式最稳定,适合长期运行的API服务,思路是先启动SSH隧道后台运行,再启动PHP服务。
1. 编写启动脚本
创建一个start.sh脚本,放在项目根目录:
#!/bin/bash set -e # 遇到错误就退出 # 配置参数,建议用环境变量传递,不要硬编码! SSH_PASSWORD=${SSH_PASSWORD:-"你的默认SSH密码"} SSH_USER=${SSH_USER:-"ssh_username"} SSH_HOST=${SSH_HOST:-"ssh_server_ip"} REMOTE_MYSQL_HOST=${REMOTE_MYSQL_HOST:-"mysql_server_ip"} LOCAL_MYSQL_PORT=${LOCAL_MYSQL_PORT:-3307} # 建立SSH隧道:把远程MySQL的3306端口映射到容器本地的3307端口 # -f:后台运行;-N:不执行远程命令;-L:端口映射;-o:跳过主机指纹验证(第一次连接必备) sshpass -p "$SSH_PASSWORD" ssh -f -N -L 127.0.0.1:$LOCAL_MYSQL_PORT:$REMOTE_MYSQL_HOST:3306 \ -o StrictHostKeyChecking=no \ $SSH_USER@$SSH_HOST # 启动PHP服务(这里以php-fpm为例,根据你的环境调整) exec php-fpm
注意:绝对不要在生产环境硬编码密码,用Docker环境变量传递更安全,比如启动容器时加-e SSH_PASSWORD=xxx。
2. 修改Dockerfile
把脚本复制到容器里,设置为启动命令:
COPY start.sh /usr/local/bin/start.sh RUN chmod +x /usr/local/bin/start.sh CMD ["start.sh"]
3. 配置Aura/SQL连接
因为隧道已经把远程MySQL映射到容器的127.0.0.1:3307,所以Aura/SQL的连接配置要指向这个本地端口:
use Aura\Sql\Connector\MysqlConnector; $connector = new MysqlConnector(); $connection = $connector->connect([ 'host' => '127.0.0.1', 'port' => 3307, // 和脚本里的LOCAL_MYSQL_PORT一致 'dbname' => 'your_database_name', 'username' => 'mysql_username', // 远程MySQL的用户名 'password' => 'mysql_password', // 远程MySQL的密码 ]);
方案二:PHP代码中动态建立隧道(适合脚本/低流量场景)
如果你的服务是脚本或者低流量的API,可以在代码里直接启动隧道,但要注意性能和权限问题:
// 用环境变量读取配置,避免硬编码 $sshPassword = getenv('SSH_PASSWORD'); $sshUser = getenv('SSH_USER'); $sshHost = getenv('SSH_HOST'); $remoteMysqlHost = getenv('REMOTE_MYSQL_HOST'); $localPort = 3307; // 启动SSH隧道 $descriptorspec = [ 0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'], ]; $process = proc_open( "sshpass -p '$sshPassword' ssh -N -L $localPort:$remoteMysqlHost:3306 $sshUser@$sshHost -o StrictHostKeyChecking=no", $descriptorspec, $pipes ); // 给隧道一点时间建立连接 sleep(2); // 检查隧道是否成功启动 if (!is_resource($process)) { die('Failed to establish SSH tunnel'); } // 初始化Aura/SQL连接 $connector = new \Aura\Sql\Connector\MysqlConnector(); $connection = $connector->connect([ 'host' => '127.0.0.1', 'port' => $localPort, 'dbname' => 'your_database_name', 'username' => 'mysql_username', 'password' => 'mysql_password', ]); // 执行你的数据库操作... // $result = $connection->fetchAll('SELECT * FROM your_table'); // 脚本结束时关闭隧道 proc_terminate($process); proc_close($process);
⚠️ 注意:这种方式每次请求都会建立新隧道,性能开销大,而且要确保PHP的proc_open函数没被禁用(检查php.ini里的disable_functions)。
关键注意事项
- 安全性优化:生产环境绝对不要用明文密码的sshpass,建议改用SSH密钥对:
- 生成密钥对:
ssh-keygen -t rsa -b 4096 - 把公钥传到SSH服务器的
~/.ssh/authorized_keys - 把私钥挂载到容器的
/root/.ssh/id_rsa,然后修改ssh命令去掉sshpass:ssh -i /root/.ssh/id_rsa ...
- 生成密钥对:
- 隧道存活检测:如果SSH隧道意外断开,你的API会连不上MySQL,可以在启动脚本里加个循环检测,比如每隔5分钟检查隧道进程,不存在就重启:
把这段加到start.sh里,后台运行检测循环。while true; do # 检查隧道进程是否存在 pgrep -f "ssh -f -N -L" || sshpass -p "$SSH_PASSWORD" ssh -f -N -L ... sleep 300 done & - 主机指纹验证:生产环境不要用
-o StrictHostKeyChecking=no,可以预先把SSH服务器的指纹加到容器的/root/.ssh/known_hosts里,比如在Dockerfile里添加:RUN mkdir -p /root/.ssh && echo "ssh_server_ip ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ..." > /root/.ssh/known_hosts
内容的提问来源于stack exchange,提问作者donfifty
相关产品推荐
相关产品推荐

