部署WireGuard,打通服务器之间通讯让数据库无需端口暴露
讲故事
最近因为新购了网络不错的服务器,三网优化,就想着把之前部署的服务都迁移到新的服务器上。本来都已经搞定了,但是新服务器内存比较小,导致占用比较高,另外随着时间增长,内存会越战越多。于是有了优化的想法,下面分享下我折腾的故事。
内存优化
服务迁移
ssh-tunnel转发
WireGuard
第一步 内存优化
刚开始只是想着怎么减少内存,于是MySQL 进行动刀,设置了一堆限制:
# 端口
port=3306
bind-address=0.0.0.0
# 关闭性能模式
performance_schema = 0
# InnoDB 缓冲池大小,默认通常是 128MB
innodb_buffer_pool_size = 64M
# 减少日志缓冲区
innodb_log_buffer_size = 4M
# 最大连接数
max_connections=100
# 线程缓存,缓存多少个线程用于重用
thread_cache_size = 4
# 表定义缓存,限制打开的表定义数量,节省内存。
table_definition_cache = 400
# 打开表缓存
table_open_cache = 400
# 限制临时表内存
tmp_table_size = 16M
max_heap_table_size = 16M
# MySQL8 的密码认证插件 如果不设置低版本navicat无法连接
mysql_native_password=ON
# 服务端使用的字符集默认为8比特编码的latin1字符集
character-set-server=utf8mb4
# 设置时区
default-time_zone='+8:00'虽说内存是控制下来了,体验也变差了。这也不是个事啊。
第二步 迁移服务
既然服务器放不下那么多服务,我就决定把一部分服务放到老服务器上。其实在这之前我是准备不续费老服务器了的。现在看来还是得爆金币。
于是我把一些不那么吃延迟的服务移到了老服务器上,比如镜像转发umami等服务。
但其实大的服务依然还在新服务器上,比如博客和数据库以NPM等服务。
第三步 ssh-tunnel转发
最终还是决定把数据库迁移到老服务器,这MySQL就可以不用限制参数了,这样性能也能更好。
但是有个问题是,如果新服务器想要连接老服务器的数据库,就要在老服务器上暴露端口,这样很容易被扫,之前我的老服务器就曾经被中了挖矿病毒,只能重装系统,至今还是有点后怕,还好只是挖矿。
这里提个醒,如果有用`nps`的还是换其他开源项目吧,这个项目已经很久没维护了,目前怀疑是被中病毒,就是这货的问题。
所以,我不太想暴露端口。
之前有使ssh隧道进行转发连接数据库,就在想能不能直接通ssh隧道,将两台服务器连接在一起。
于是就萌生了创建一个ssh隧道的想法,让新服务器通ssh隧道去连MySQL。
数据库设置
首先对MySQL的容器做限制,如果直接端口映3306:3306就会导docker绕ufw直接去改底层iptable,导致端口直接暴露,公网直接可以访问,这也是之前踩过的坑。[Docker踩坑:Docker容器端口不受防火墙限制](https://blog.bitskyline.com/archives/docker-pitfall-portslimitbroken)
services:
mysql:
image: mysql:8.4
container_name: mysql
restart: unless-stopped
# 这里设置了一个network,方便其他容器访问
networks:
- mysql-net
# 这里3306:3306改为127.0.0.1:3306:3306,这样就只监听本地,不会再绕过ufw了
ports:
- "127.0.0.1:3306:3306"
logging:
driver: 'json-file'
options:
max-size: '1g'
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_general_ci
- --explicit_defaults_for_timestamp=true
volumes:
- ./data:/var/lib/mysql
- ./backup:/data/mysqlBackup
- ./config:/etc/mysql/conf.d
- ./logs:/var/log/mysql
- /etc/localtime:/etc/localtime:ro
environment:
- MYSQL_ROOT_PASSWORD=YOUR_PASSWORD
- TZ=Asia/Shanghai
# 这里设置了一个network,方便其他容器访问
networks:
mysql-net:
name: mysql-net
driver: bridgeMySQL启动起来后,现在就要去新服务器设ssh隧道了。
ssh隧道详解
这里建立隧道的命令是:
ssh -L 127.0.0.1:3306:3306 user@service_ip:port上述命令只监听本地127.0.0.1:3306,如果我想要容器访问,这样设置肯定是不行的。
于是,来个双通道吧。
ssh -L 127.0.0.1:3306:3306 -L 172.17.0.1:3306:3306 user@service_ip:port但是使用命令直接去连接,虽然可以连上,但是一旦断了又要手动再去连接,太麻烦了,必须搞成自动的。
ssh隧道自启+重启
于是问了下AI可以设置一个systemd文件,用于自动启动和重连:
进/etc/systemd/system目录,新建文ssh-tunnel-mysql.service
[Unit]
Description=Persistent SSH Tunnel for MySQL
After=network.target
[Service]
User=root
# -N: 不执行远程命令
# -T: 不分配终端
# ExitOnForwardFailure=yes: 端口绑定失败时退出,触发systemd重启
# ServerAliveInterval=30: 每30秒发一下心跳包,防止路由器静默断开
# ServerAliveCountMax=3: 连续3次心跳无响应就判定死线,触发退出重启
ExecStart=/usr/bin/ssh -NT -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -L 127.0.0.1:3306:127.0.0.1:3306 -L 172.17.0.1:3306:127.0.0.1:2206 user@ip_server
# 如果服务挂了,总是自动重启
Restart=always
# 重启前等待 5 秒
RestartSec=5
[Install]
WantedBy=multi-user.target然后使用下面命令自启运行:
systemctl enable ssh-tunnel-mysql.service --now但是启动有报错,这里是因为使用脚本运行,没法输入密码,需要设置成免密才可以。
在老服务器上使ssh-keygen -t rsa -C生成密钥和公钥,执行命令后直接点回车即可。会~/.ssh/目录下生.id_rsa.id_rsa.pub
到新服务器~/.ssh目录中新建authorized_keys文件,将老服务器的公钥粘贴进去即可。如authorized_keys文件已经存在,修改这个文件再加一行即可。
这样就可以免密运行了。
# 重启
systemctl restart ssh-tunnel-mysql.service
# 查看状态
systemctl status ssh-tunnel-mysql.service状态如果Active: active (running),就说明已经成功运行起来了。
在新服务器上:
本地连接
127.0.0.1:3306容器连接
172.17.0.1:3306
就可以连上远程的数据库了。
第四步 WireGuard
最终上面的方式试用下来还是蛮不错的,可以稳定运行。因为我两台服务器都在一个地区,所以还是蛮稳定的。
但是我这颗折腾的心吧,总是无处安放。
跟AI进行了多轮对话,我这种方案只能是临时方案,所以还需要升级。
AI给了我两条方案
TailscaleWireGuard
选择WireGuard的原因很简单,因为Tailscale要登录账号使用别人的服务。虽然可以自headscale加卫星节点,但是太麻烦了,这些我都试过,现在配置文件等还躺backup目录下。
于是,一条路走到黑,在跟AI深入交流后,我决定WireGuard来连接。
本WireGuard是没有服务器/客户端之分的,反正就是互相连接,UDP。
但是其实我这个中心化还挺明显的,就是想让其他服务器来连接我的数据库。
于是,方案变成了服务端+客户端的方式:
wg-easy作为服务端wireguard作为客户端
配置服务端
服务端配置可以说也是踩了很多坑wg-easy有很多版本,AI给到的版本14,但是我去wg-easy的github,读了文档后,看样子15改进了很多,本着要用就用最新的,于是踩了一堆坑。
再跟着AI使用他推荐的配置后,发现根本不行,只能看官方文档进行部署。
在github上docker-compose.yaml文件中,官方使用的bridge网络模式,猜测是因为大多数使用者都是为VPN,但本文是为了服务器间的连接,需要使host模式,这样才能在本地创建虚拟网卡,供服务器之间连接。
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
restart: unless-stopped
network_mode: "host"
environment:
- PORT=51821 # WebUI端口
# wg-easy-15 现在仅支持在webui进行设置以下信息
# 服务器的公网 IP 或域名
# - WG_HOST=1.1.1.1
# - WG_PORT=34429 # 通信端口
# - WG_DEFAULT_ADDRESS=10.1.8.0/24 # 虚拟局域网网段
volumes:
- ./data:/etc/wireguard
- /lib/modules:/lib/modules:ro
cap_add:
- NET_ADMIN # 赋予容器修改网络接口的权限
- SYS_MODULE # 赋予容器加载内核模块的权限(WireGuard需要)折腾很多次后,发现环境变量只能设置个web-ui的端口。
14版本wg-easy只要docker-compose.yaml文件中配置环境变量即可。15版本需要启web-ui后进行设置。
直接运行,然后通web-ui进行配置:
docker-compose up -d进web-ui后根据他的提示进行设置即可。
需要注意的是:
换虚拟局域网网段:需要到右上角Administrator的管理面板中设置,点击接口配置,下面有个选项是
修改CIDR,只有修改了这里才会改变虚拟局域网网段。设置WireGuard通讯端口:需要到右上角Administrator的管理面板中设置,点击网络配置和接口配置,将端口都设置为你想要设置的端口。
数据库设置
这里需要修改数据库ports,因为需要监听虚拟局域网,否则无法连接到数据库。
services:
mysql:
image: mysql:8.4
container_name: mysql
restart: unless-stopped
# 这里设置了一个network,方便其他容器访问
networks:
- mysql-net
# 这里3306:3306改为127.0.0.1:3306:3306,这样就只监听本地,不会再绕过ufw了
ports:
- "127.0.0.1:3306:3306"
- "10.1.8.1:3306:3306"
logging:
driver: 'json-file'
options:
max-size: '1g'
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_general_ci
- --explicit_defaults_for_timestamp=true
volumes:
- ./data:/var/lib/mysql
- ./backup:/data/mysqlBackup
- ./config:/etc/mysql/conf.d
- ./logs:/var/log/mysql
- /etc/localtime:/etc/localtime:ro
environment:
- MYSQL_ROOT_PASSWORD=YOUR_PASSWORD
- TZ=Asia/Shanghai
# 这里设置了一个network,方便其他容器访问
networks:
mysql-net:
name: mysql-net
driver: bridge这里增加了一Ports设置10.1.8.1:3306:3306,表示数据库也监WireGuard的虚拟局域网。
10.1.8.1代表当wg-easy运行WireGuard,因为我们设置127.0.0.1:3306在本地,所以客户端访问10.1.8.1:3306也可以访问到数据库了。
开放ufw
这里需要开放WireGuard的端口,使用的协议是UDP。
ufw allow 51820/udp这里可能有人觉得,既然都要开放端口,为什么还要那么麻烦,直接开放数据库的端口不就行了。
这个我也详细跟AI聊了聊,AI的回答是:
WireGuard 本身就是自带“隐形的”
很多人的顾虑是:“暴露 51820 端口到公网上,会被黑客拿扫描器扫出来然后爆破吗?”
答案是:绝对不会!
WireGuard 被誉为现代最强 VPN,有一个核心设计哲学叫做 “在未建立连接前保持绝对静默(Silent Drop)”。
如果你的 MySQL 或 SSH 暴露了,黑客的扫描器一扫,它们会回复:“Hello,我是 MySQL”。
但如果你扫描 WireGuard 的端口,WireGuard 不会给出任何回应。只要发来的数据包没有正确的私钥加密,WireGuard 直接将包丢弃,无声无息。
在黑客的扫描器(比如 Nmap)看来,扫描你的 51820 端口和扫描一个根本没开放的废弃端口,结果是一模一样的,看起来都是“无响应(Filtered)”。这也是我ssh隧道最终选WireGuard的原因。
客户端配置
因为服务wg-easy的原因,所以客户端配置就很简单了。
我们只需要wg-easy提供web-ui上新建客户端,然后设置客户端IP等信息,必要设置是允许IP10.1.8.0/24即可(这里需要注意,先要新增一个客户端,然后新增完成后,才能在编辑里修改)。
设置完成后,只需要点击客户端右边的下载图标,即可将配置文件下载下来,给客户端使用。
在客户docker部署WireGuard
前置动作
创建一个目wireguard,然后在该目录下创config目录,将刚刚下载下来的配置文件放config目录下,重命名wg0.conf。
docker-compose配置文件
services:
wireguard-client:
# 使用 linuxserver 出品的经典稳定镜像
image: lscr.io/linuxserver/wireguard:latest
container_name: wg_client
restart: unless-stopped
# 必须是 host 模式,这样隧道才会建在宿主机上
network_mode: "host"
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
volumes:
# 将本机目录挂载进容器的配置目录
- ./config:/config
cap_add:
- NET_ADMIN
- SYS_MODULE启动
docker-compose up -d客户端连接
配置需要连接的数据库的服务,将地址改10.1.8.1:3306即可访问数据库。
总结
因为强迫症的缘故,导致整个优化过程大大加长,不过总算是学到了很多。但我只想说,不折腾其实也能用...