diff --git a/.env.example b/.env.example index f3c1fe1..b8fc93a 100644 --- a/.env.example +++ b/.env.example @@ -3,26 +3,6 @@ # MySQL root 密码(同时需要在 configs/ppanel.yaml 的 MySQL.Password 中填写相同的值) MYSQL_ROOT_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD -MYSQL_DATABASE=hifast - -# 主库对外监听地址。远程从库需要访问时可保持 0.0.0.0,并在防火墙只放行从机公网 IP。 -MYSQL_PUBLISH_ADDR=0.0.0.0 - -# MySQL GTID 主从复制账号 -MYSQL_REPLICATION_USER=replicator -MYSQL_REPLICATION_PASSWORD=CHANGE_ME_TO_STRONG_REPLICATION_PASSWORD - -# Redis 密码。多实例/主从部署时,configs/ppanel.yaml 的 Redis.Pass 也需要填写相同值。 -REDIS_PASSWORD=CHANGE_ME_TO_STRONG_REDIS_PASSWORD - -# Redis 对外监听地址。远程业务实例或 Redis 从库需要访问时可保持 0.0.0.0,并在防火墙只放行从机公网 IP。 -REDIS_PUBLISH_ADDR=0.0.0.0 - -# 从机 docker-compose.replica.yml 使用:主机公网 IP 或内网/VPN IP -MASTER_MYSQL_HOST=CHANGE_ME_MASTER_MYSQL_HOST -MASTER_MYSQL_PORT=3306 -MASTER_REDIS_HOST=CHANGE_ME_MASTER_REDIS_HOST -MASTER_REDIS_PORT=6379 # Grafana 管理员密码 GRAFANA_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD diff --git a/.gitignore b/.gitignore index 7218612..ce9f7ff 100644 --- a/.gitignore +++ b/.gitignore @@ -61,12 +61,6 @@ package-lock.json # ==================== 脚本 ==================== *.sh script/*.sh -!mysql/*.sh -!redis/*.sh - -# ==================== 部署本地密钥 ==================== -mysql/.my.cnf -redis/sentinel.conf # ==================== CI/CD 本地运行配置 ==================== .run/ diff --git a/config/nginx-ppanel-upstream.conf.example b/config/nginx-ppanel-upstream.conf.example deleted file mode 100644 index bd75d65..0000000 --- a/config/nginx-ppanel-upstream.conf.example +++ /dev/null @@ -1,17 +0,0 @@ -upstream ppanel_backend { - server MASTER_SERVER_IP:8080 max_fails=3 fail_timeout=10s; - server REPLICA_SERVER_IP:8080 max_fails=3 fail_timeout=10s; -} - -server { - listen 443 ssl http2; - server_name api.example.com; - - location / { - proxy_pass http://ppanel_backend; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} diff --git a/doc/remote-replication-zh.md b/doc/remote-replication-zh.md deleted file mode 100644 index 4bb576c..0000000 --- a/doc/remote-replication-zh.md +++ /dev/null @@ -1,717 +0,0 @@ -# PPanel 双机部署:远程 MySQL 主从、Redis 主从/Sentinel、多实例与监控 - -本文档说明如何部署两台机器: - -- 主机:运行主 MySQL、主 Redis、PPanel Server、Grafana、Prometheus、Loki、Tempo。 -- 从机:运行 MySQL 从库、Redis 从库、Redis Sentinel、额外 PPanel Server。 -- Nginx:只做 HTTP 负载均衡,不做数据库读写分离。 -- MCP Grafana:安装在本地电脑,不部署到服务器,用于后续排查线上 bug。 - -重要原则: - -- 所有 `ppanel-server` 实例都连接主 MySQL 和主 Redis。 -- 从机 MySQL 只做复制/灾备,不给业务直接写入。 -- 当前后端只有单个 `MySQL.Addr` 和单个 `Redis.Host`,不要让业务实例连接本机从库。 -- 故障切换采用人工确认,避免自动切换造成脑裂。 - -## 1. 拓扑与端口 - -示例变量: - -```text -主机公网 IP: MASTER_PUBLIC_IP -从机公网 IP: REPLICA_PUBLIC_IP -业务域名: api.example.com -数据库名: ppanel -``` - -主机端口: - -| 端口 | 用途 | 建议暴露范围 | -| --- | --- | --- | -| `8080` | 主机 PPanel Server | Nginx 所在机器 | -| `3306` | 主 MySQL | 只允许从机 IP | -| `6379` | 主 Redis | 只允许从机 IP | -| `3333` | Grafana | 默认 `127.0.0.1`,通过 SSH 隧道 | -| `9090` | Prometheus | 默认 `127.0.0.1` | -| `4317` | Tempo OTLP | 默认 `127.0.0.1` | - -从机端口: - -| 端口 | 用途 | 建议暴露范围 | -| --- | --- | --- | -| `8080` | 从机 PPanel Server | Nginx 所在机器 | -| `3307` | 本机 MySQL 从库调试端口 | 默认 `127.0.0.1` | -| `6380` | 本机 Redis 从库调试端口 | 默认 `127.0.0.1` | -| `26379` | 本机 Redis Sentinel | 默认 `127.0.0.1` | - -## 2. 文件准备 - -主机上传: - -```text -docker-compose.cloud.yml -.env -configs/ -mysql/ -redis/ -loki/ -grafana/ -prometheus/ -tempo/ -config/ -``` - -从机上传: - -```text -docker-compose.replica.yml -.env -configs/ -mysql/ -redis/ -tempo/ -cache/ -``` - -两台机器都创建运行目录: - -```bash -mkdir -p configs logs cache tempo_data mysql redis -``` - -主机如果使用监控 exporter,还需要准备 MySQL exporter 配置: - -```bash -cp mysql/.my.cnf.example mysql/.my.cnf -``` - -编辑 `mysql/.my.cnf`,把密码改成真实 `MYSQL_ROOT_PASSWORD`。该文件已被 `.gitignore` 忽略,不要提交。 - -## 3. 主机 `.env` - -在主机复制 `.env.example`: - -```bash -cp .env.example .env -``` - -主机 `.env` 示例: - -```dotenv -MYSQL_ROOT_PASSWORD=CHANGE_ME_STRONG_MYSQL_ROOT_PASSWORD -MYSQL_DATABASE=ppanel -MYSQL_PUBLISH_ADDR=0.0.0.0 - -MYSQL_REPLICATION_USER=replicator -MYSQL_REPLICATION_PASSWORD=CHANGE_ME_STRONG_REPLICATION_PASSWORD - -REDIS_PASSWORD=CHANGE_ME_STRONG_REDIS_PASSWORD -REDIS_PUBLISH_ADDR=0.0.0.0 - -GRAFANA_PASSWORD=CHANGE_ME_STRONG_GRAFANA_PASSWORD -PPANEL_SERVER_TAG=latest -``` - -如果只想让 MySQL/Redis 监听内网 IP,把 `MYSQL_PUBLISH_ADDR` 和 `REDIS_PUBLISH_ADDR` 改成内网地址。 - -## 4. 主机 `configs/ppanel.yaml` - -主机业务实例连接本机主库和主 Redis: - -```yaml -MySQL: - Addr: 127.0.0.1:3306 - Username: root - Password: CHANGE_ME_STRONG_MYSQL_ROOT_PASSWORD - Dbname: ppanel - Config: charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai - MaxIdleConns: 10 - MaxOpenConns: 100 - SlowThreshold: 1000 - -Redis: - Host: 127.0.0.1:6379 - Pass: CHANGE_ME_STRONG_REDIS_PASSWORD - DB: 0 -``` - -注意:启用当前 compose 后 Redis 有密码,`Redis.Pass` 必须填写。 - -## 5. 主机防火墙 - -主机必须只允许从机 IP 访问 MySQL 和 Redis。 - -UFW 示例: - -```bash -ufw allow from REPLICA_PUBLIC_IP to any port 3306 proto tcp -ufw allow from REPLICA_PUBLIC_IP to any port 6379 proto tcp -ufw deny 3306/tcp -ufw deny 6379/tcp -ufw reload -``` - -firewalld 示例: - -```bash -firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="REPLICA_PUBLIC_IP" port protocol="tcp" port="3306" accept' -firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="REPLICA_PUBLIC_IP" port protocol="tcp" port="6379" accept' -firewall-cmd --reload -``` - -如果使用云厂商安全组,也要同步设置白名单。 - -## 6. 启动主机 - -检查 compose: - -```bash -docker compose -f docker-compose.cloud.yml config -``` - -启动: - -```bash -docker compose -f docker-compose.cloud.yml up -d -``` - -检查服务: - -```bash -docker ps -docker logs --tail=100 ppanel-mysql -docker logs --tail=100 ppanel-redis -docker logs --tail=100 ppanel-server -``` - -检查 MySQL GTID: - -```bash -docker exec ppanel-mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW VARIABLES LIKE 'gtid_mode';" -docker exec ppanel-mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW MASTER STATUS\\G" -``` - -检查 Redis: - -```bash -docker exec ppanel-redis redis-cli -a "$REDIS_PASSWORD" ping -``` - -如果主库已有数据,`mysql/init-master.sh` 不会自动重跑。手动创建复制账号: - -```bash -docker exec -i ppanel-mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" < ppanel-gtid.sql.gz -``` - -把备份复制到从机: - -```bash -scp ppanel-gtid.sql.gz root@REPLICA_PUBLIC_IP:/root/ppanel-gtid.sql.gz -``` - -## 8. 从机 `.env` - -从机也复制 `.env.example`: - -```bash -cp .env.example .env -``` - -从机 `.env` 示例: - -```dotenv -MYSQL_ROOT_PASSWORD=CHANGE_ME_STRONG_MYSQL_ROOT_PASSWORD -MYSQL_DATABASE=ppanel - -MYSQL_REPLICATION_USER=replicator -MYSQL_REPLICATION_PASSWORD=CHANGE_ME_STRONG_REPLICATION_PASSWORD - -REDIS_PASSWORD=CHANGE_ME_STRONG_REDIS_PASSWORD - -MASTER_MYSQL_HOST=MASTER_PUBLIC_IP -MASTER_MYSQL_PORT=3306 -MASTER_REDIS_HOST=MASTER_PUBLIC_IP -MASTER_REDIS_PORT=6379 - -GRAFANA_PASSWORD=CHANGE_ME_STRONG_GRAFANA_PASSWORD -PPANEL_SERVER_TAG=latest -``` - -`MYSQL_ROOT_PASSWORD` 建议和主机一致,方便备份导入和后续切换。 - -## 9. 从机 `configs/ppanel.yaml` - -从机业务实例仍然连接主机 MySQL 和主机 Redis: - -```yaml -MySQL: - Addr: MASTER_PUBLIC_IP:3306 - Username: root - Password: CHANGE_ME_STRONG_MYSQL_ROOT_PASSWORD - Dbname: ppanel - Config: charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai - MaxIdleConns: 10 - MaxOpenConns: 100 - SlowThreshold: 1000 - -Redis: - Host: MASTER_PUBLIC_IP:6379 - Pass: CHANGE_ME_STRONG_REDIS_PASSWORD - DB: 0 - -Trace: - Endpoint: "127.0.0.1:4317" -``` - -不要写成: - -```yaml -MySQL: - Addr: 127.0.0.1:3307 -Redis: - Host: 127.0.0.1:6380 -``` - -这样会让业务连接本机从库,写请求会失败或造成状态不一致。 - -## 10. 启动从机基础服务 - -检查 compose: - -```bash -docker compose -f docker-compose.replica.yml config -``` - -启动 MySQL 从库、Redis 从库、Sentinel 和 Tempo: - -```bash -docker compose -f docker-compose.replica.yml up -d mysql-replica redis-replica redis-sentinel tempo -``` - -查看日志: - -```bash -docker logs --tail=100 ppanel-mysql-replica -docker logs --tail=100 ppanel-redis-replica -docker logs --tail=100 ppanel-redis-sentinel -``` - -## 11. 导入主库备份到从库 - -导入前临时关闭从库只读: - -```bash -docker exec -i ppanel-mysql-replica mysql -uroot -p"$MYSQL_ROOT_PASSWORD" < /tmp/sentinel.conf - exec redis-server /tmp/sentinel.conf --sentinel - networks: - - ppanel_net - depends_on: - - redis-replica - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "3" - - tempo: - image: grafana/tempo:2.4.1 - container_name: ppanel-tempo - user: root - restart: always - command: - - "-config.file=/etc/tempo.yaml" - - "-target=all" - volumes: - - ./tempo/tempo-config.yaml:/etc/tempo.yaml - - ./tempo_data:/var/tempo - ports: - - "127.0.0.1:4317:4317" - networks: - - ppanel_net - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "3" - -volumes: - mysql_replica_data: - redis_replica_data: - tempo_data: - -networks: - ppanel_net: - name: ppanel_net - driver: bridge diff --git a/mysql/.my.cnf.example b/mysql/.my.cnf.example deleted file mode 100644 index 9233af8..0000000 --- a/mysql/.my.cnf.example +++ /dev/null @@ -1,5 +0,0 @@ -[client] -host=mysql -port=3306 -user=root -password=CHANGE_ME_TO_MYSQL_ROOT_PASSWORD diff --git a/mysql/configure-replica.sh b/mysql/configure-replica.sh deleted file mode 100644 index 34a76f8..0000000 --- a/mysql/configure-replica.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh -set -eu - -: "${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD is required}" -: "${MASTER_MYSQL_HOST:?MASTER_MYSQL_HOST is required}" -: "${MYSQL_REPLICATION_USER:?MYSQL_REPLICATION_USER is required}" -: "${MYSQL_REPLICATION_PASSWORD:?MYSQL_REPLICATION_PASSWORD is required}" - -MASTER_MYSQL_PORT="${MASTER_MYSQL_PORT:-3306}" -REPLICA_MYSQL_HOST="${REPLICA_MYSQL_HOST:-mysql-replica}" -REPLICA_MYSQL_PORT="${REPLICA_MYSQL_PORT:-3306}" - -echo "Waiting for MySQL source ${MASTER_MYSQL_HOST}:${MASTER_MYSQL_PORT}..." -until mysqladmin ping -h"${MASTER_MYSQL_HOST}" -P"${MASTER_MYSQL_PORT}" -u"${MYSQL_REPLICATION_USER}" -p"${MYSQL_REPLICATION_PASSWORD}" --silent; do - sleep 2 -done - -echo "Waiting for local replica ${REPLICA_MYSQL_HOST}:${REPLICA_MYSQL_PORT}..." -until mysqladmin ping -h"${REPLICA_MYSQL_HOST}" -P"${REPLICA_MYSQL_PORT}" -uroot -p"${MYSQL_ROOT_PASSWORD}" --silent; do - sleep 2 -done - -echo "Configuring GTID replication..." -mysql -h"${REPLICA_MYSQL_HOST}" -P"${REPLICA_MYSQL_PORT}" -uroot -p"${MYSQL_ROOT_PASSWORD}" <