讲故事

最近因为新购了网络不错的服务器,三网优化,就想着把之前部署的服务都迁移到新的服务器上。本来都已经搞定了,但是新服务器内存比较小,导致占用比较高,另外随着时间增长,内存会越战越多。于是有了优化的想法,下面分享下我折腾的故事。

  • 内存优化

  • 服务迁移

  • 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就会导dockerufw直接去改底层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: bridge

MySQL启动起来后,现在就要去新服务器设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给了我两条方案

  • Tailscale

  • WireGuard

选择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即可访问数据库。

总结

因为强迫症的缘故,导致整个优化过程大大加长,不过总算是学到了很多。但我只想说,不折腾其实也能用...

文章作者: Will
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Will's Blog
Linux WireGuard Debian Halo MySQL Linux
喜欢就支持一下吧