chore(deploy): add replication deployment assets
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 5m52s
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 5m52s
This commit is contained in:
parent
0ec0e2b9d2
commit
68c7b0a8ec
20
.env.example
20
.env.example
@ -3,6 +3,26 @@
|
|||||||
|
|
||||||
# MySQL root 密码(同时需要在 configs/ppanel.yaml 的 MySQL.Password 中填写相同的值)
|
# MySQL root 密码(同时需要在 configs/ppanel.yaml 的 MySQL.Password 中填写相同的值)
|
||||||
MYSQL_ROOT_PASSWORD=CHANGE_ME_TO_STRONG_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 管理员密码
|
||||||
GRAFANA_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD
|
GRAFANA_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -61,6 +61,12 @@ package-lock.json
|
|||||||
# ==================== 脚本 ====================
|
# ==================== 脚本 ====================
|
||||||
*.sh
|
*.sh
|
||||||
script/*.sh
|
script/*.sh
|
||||||
|
!mysql/*.sh
|
||||||
|
!redis/*.sh
|
||||||
|
|
||||||
|
# ==================== 部署本地密钥 ====================
|
||||||
|
mysql/.my.cnf
|
||||||
|
redis/sentinel.conf
|
||||||
|
|
||||||
# ==================== CI/CD 本地运行配置 ====================
|
# ==================== CI/CD 本地运行配置 ====================
|
||||||
.run/
|
.run/
|
||||||
|
|||||||
17
config/nginx-ppanel-upstream.conf.example
Normal file
17
config/nginx-ppanel-upstream.conf.example
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
717
doc/remote-replication-zh.md
Normal file
717
doc/remote-replication-zh.md
Normal file
@ -0,0 +1,717 @@
|
|||||||
|
# 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" <<SQL
|
||||||
|
CREATE USER IF NOT EXISTS '$MYSQL_REPLICATION_USER'@'%' IDENTIFIED WITH mysql_native_password BY '$MYSQL_REPLICATION_PASSWORD';
|
||||||
|
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO '$MYSQL_REPLICATION_USER'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
SQL
|
||||||
|
```
|
||||||
|
|
||||||
|
从从机测试主机端口:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mysqladmin ping -hMASTER_PUBLIC_IP -P3306 -u"$MYSQL_REPLICATION_USER" -p"$MYSQL_REPLICATION_PASSWORD"
|
||||||
|
redis-cli -h MASTER_PUBLIC_IP -p 6379 -a "$REDIS_PASSWORD" ping
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 主库已有数据的备份
|
||||||
|
|
||||||
|
已有生产数据时,先从主库导出一致性 GTID 备份:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-mysql mysqldump \
|
||||||
|
-uroot -p"$MYSQL_ROOT_PASSWORD" \
|
||||||
|
--single-transaction \
|
||||||
|
--routines \
|
||||||
|
--triggers \
|
||||||
|
--events \
|
||||||
|
--set-gtid-purged=ON \
|
||||||
|
--databases "$MYSQL_DATABASE" \
|
||||||
|
| gzip > 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" <<SQL
|
||||||
|
SET GLOBAL super_read_only = OFF;
|
||||||
|
SET GLOBAL read_only = OFF;
|
||||||
|
SQL
|
||||||
|
```
|
||||||
|
|
||||||
|
导入备份:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gunzip -c /root/ppanel-gtid.sql.gz | docker exec -i ppanel-mysql-replica mysql -uroot -p"$MYSQL_ROOT_PASSWORD"
|
||||||
|
```
|
||||||
|
|
||||||
|
如果从库是全新空库,只能导入一次。重复导入前需要确认是否要重建 `mysql_replica_data`,避免覆盖或重复数据。
|
||||||
|
|
||||||
|
## 12. 配置 MySQL GTID 复制
|
||||||
|
|
||||||
|
执行复制配置 profile:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.replica.yml --profile replication-setup up mysql-replica-setup
|
||||||
|
```
|
||||||
|
|
||||||
|
该步骤会:
|
||||||
|
|
||||||
|
- 等待主库可连接。
|
||||||
|
- 等待本机从库可连接。
|
||||||
|
- 执行 `CHANGE REPLICATION SOURCE TO ... SOURCE_AUTO_POSITION=1`。
|
||||||
|
- 启动复制。
|
||||||
|
- 重新开启 `read_only` 和 `super_read_only`。
|
||||||
|
|
||||||
|
检查状态:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-mysql-replica mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW REPLICA STATUS\\G"
|
||||||
|
```
|
||||||
|
|
||||||
|
必须确认:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Replica_IO_Running: Yes
|
||||||
|
Replica_SQL_Running: Yes
|
||||||
|
Last_IO_Error:
|
||||||
|
Last_SQL_Error:
|
||||||
|
```
|
||||||
|
|
||||||
|
`Seconds_Behind_Source` 可以短暂为非 0,但应逐渐回到较低值。
|
||||||
|
|
||||||
|
## 13. 验证 MySQL 主从同步
|
||||||
|
|
||||||
|
在主库创建测试数据:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -i ppanel-mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" <<SQL
|
||||||
|
CREATE TABLE IF NOT EXISTS replication_check (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
message VARCHAR(64) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
INSERT INTO replication_check (message) VALUES ('gtid-ok');
|
||||||
|
SQL
|
||||||
|
```
|
||||||
|
|
||||||
|
在从库查询:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-mysql-replica mysql -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" -e "SELECT * FROM replication_check ORDER BY id DESC LIMIT 3;"
|
||||||
|
```
|
||||||
|
|
||||||
|
确认能看到 `gtid-ok`。
|
||||||
|
|
||||||
|
## 14. 验证 Redis 主从/Sentinel
|
||||||
|
|
||||||
|
主机写入测试 key:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-redis redis-cli -a "$REDIS_PASSWORD" SET replication:check redis-ok
|
||||||
|
```
|
||||||
|
|
||||||
|
从机读取:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-redis-replica redis-cli -a "$REDIS_PASSWORD" GET replication:check
|
||||||
|
```
|
||||||
|
|
||||||
|
检查从库角色:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-redis-replica redis-cli -a "$REDIS_PASSWORD" INFO replication
|
||||||
|
```
|
||||||
|
|
||||||
|
应看到:
|
||||||
|
|
||||||
|
```text
|
||||||
|
role:slave
|
||||||
|
master_host:MASTER_PUBLIC_IP
|
||||||
|
master_link_status:up
|
||||||
|
```
|
||||||
|
|
||||||
|
检查 Sentinel:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-redis-sentinel redis-cli -p 26379 SENTINEL masters
|
||||||
|
```
|
||||||
|
|
||||||
|
## 15. 启动从机业务实例
|
||||||
|
|
||||||
|
确认从机 `configs/ppanel.yaml` 指向主库和主 Redis 后启动:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.replica.yml up -d ppanel-server
|
||||||
|
```
|
||||||
|
|
||||||
|
检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker logs --tail=100 ppanel-server
|
||||||
|
curl -I http://127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
如果从机业务实例报 MySQL/Redis 连接失败,先检查:
|
||||||
|
|
||||||
|
- 主机安全组/防火墙是否放行从机 IP。
|
||||||
|
- `configs/ppanel.yaml` 是否写主机公网 IP。
|
||||||
|
- `Redis.Pass` 是否和 `REDIS_PASSWORD` 一致。
|
||||||
|
|
||||||
|
## 16. Nginx 多实例转发
|
||||||
|
|
||||||
|
Nginx 只转发 HTTP 流量,不负责数据库读写分离。可参考 `config/nginx-ppanel-upstream.conf.example`:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
upstream ppanel_backend {
|
||||||
|
server MASTER_PUBLIC_IP:8080 max_fails=3 fail_timeout=10s;
|
||||||
|
server REPLICA_PUBLIC_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
应用配置后检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nginx -t
|
||||||
|
systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
建议先只把从机加入 upstream,但设置较低权重或观察日志,确认登录、验证码、订单、支付回调等写操作正常后再正式承载流量。
|
||||||
|
|
||||||
|
## 17. 监控访问
|
||||||
|
|
||||||
|
Grafana 默认绑定主机本地端口 `3333`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -L 3333:localhost:3333 root@MASTER_PUBLIC_IP
|
||||||
|
```
|
||||||
|
|
||||||
|
本地浏览器打开:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://localhost:3333
|
||||||
|
```
|
||||||
|
|
||||||
|
Prometheus 默认绑定主机本地端口 `9090`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -L 9090:localhost:9090 root@MASTER_PUBLIC_IP
|
||||||
|
```
|
||||||
|
|
||||||
|
本地浏览器打开:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://localhost:9090
|
||||||
|
```
|
||||||
|
|
||||||
|
常用检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker ps
|
||||||
|
docker logs --tail=100 ppanel-prometheus
|
||||||
|
docker logs --tail=100 ppanel-grafana
|
||||||
|
docker logs --tail=100 ppanel-loki
|
||||||
|
docker logs --tail=100 ppanel-tempo
|
||||||
|
```
|
||||||
|
|
||||||
|
## 18. 本地 Grafana MCP 排障入口
|
||||||
|
|
||||||
|
`mcp-grafana` 不部署在服务器 compose 中。它安装在本地电脑的 MCP 客户端环境里,通过 Grafana URL 和 Service Account Token 远程查询指标、日志、trace 和 dashboard。
|
||||||
|
|
||||||
|
先通过 SSH 隧道让本地能访问 Grafana:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -L 3333:localhost:3333 root@MASTER_PUBLIC_IP
|
||||||
|
```
|
||||||
|
|
||||||
|
在 Grafana 中创建 Service Account Token,然后在本地 MCP 客户端配置:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"grafana": {
|
||||||
|
"command": "mcp-grafana",
|
||||||
|
"env": {
|
||||||
|
"GRAFANA_URL": "http://localhost:3333",
|
||||||
|
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "glsa_xxx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如果本地使用 Docker 跑 MCP server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm -p 127.0.0.1:8000:8000 \
|
||||||
|
-e GRAFANA_URL=http://host.docker.internal:3333 \
|
||||||
|
-e GRAFANA_SERVICE_ACCOUNT_TOKEN=glsa_xxx \
|
||||||
|
grafana/mcp-grafana
|
||||||
|
```
|
||||||
|
|
||||||
|
客户端 SSE 地址:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://localhost:8000/sse
|
||||||
|
```
|
||||||
|
|
||||||
|
不要把 Grafana 或本地 MCP SSE 暴露公网。
|
||||||
|
|
||||||
|
## 19. 日常巡检命令
|
||||||
|
|
||||||
|
MySQL 主库:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-mysql mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW MASTER STATUS\\G"
|
||||||
|
```
|
||||||
|
|
||||||
|
MySQL 从库:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-mysql-replica mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW REPLICA STATUS\\G"
|
||||||
|
```
|
||||||
|
|
||||||
|
Redis 主库:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-redis redis-cli -a "$REDIS_PASSWORD" INFO replication
|
||||||
|
```
|
||||||
|
|
||||||
|
Redis 从库:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-redis-replica redis-cli -a "$REDIS_PASSWORD" INFO replication
|
||||||
|
```
|
||||||
|
|
||||||
|
业务实例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker logs --tail=100 ppanel-server
|
||||||
|
curl -I http://127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
Compose 状态:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.cloud.yml ps
|
||||||
|
docker compose -f docker-compose.replica.yml ps
|
||||||
|
```
|
||||||
|
|
||||||
|
## 20. 手动故障切换
|
||||||
|
|
||||||
|
### MySQL 主库故障
|
||||||
|
|
||||||
|
在从机确认复制尽量追平后执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -i ppanel-mysql-replica mysql -uroot -p"$MYSQL_ROOT_PASSWORD" <<SQL
|
||||||
|
STOP REPLICA;
|
||||||
|
RESET REPLICA ALL;
|
||||||
|
SET GLOBAL super_read_only = OFF;
|
||||||
|
SET GLOBAL read_only = OFF;
|
||||||
|
SQL
|
||||||
|
```
|
||||||
|
|
||||||
|
然后把所有 `ppanel-server` 的 `configs/ppanel.yaml` 改为:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
MySQL:
|
||||||
|
Addr: REPLICA_PUBLIC_IP:3307
|
||||||
|
```
|
||||||
|
|
||||||
|
如果要让新主库对远程服务开放,需要把从机 compose 的 MySQL 端口从 `127.0.0.1:3307:3306` 改成内网/公网白名单可访问的地址,并更新防火墙。
|
||||||
|
|
||||||
|
重启业务实例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.cloud.yml restart ppanel-server
|
||||||
|
docker compose -f docker-compose.replica.yml restart ppanel-server
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redis 主库故障
|
||||||
|
|
||||||
|
在从机提升 Redis:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-redis-replica redis-cli -a "$REDIS_PASSWORD" REPLICAOF NO ONE
|
||||||
|
```
|
||||||
|
|
||||||
|
然后把所有 `ppanel-server` 的 `configs/ppanel.yaml` 改为:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Redis:
|
||||||
|
Host: REPLICA_PUBLIC_IP:6380
|
||||||
|
```
|
||||||
|
|
||||||
|
如果要让新 Redis 主库对远程服务开放,需要把从机 compose 的 Redis 端口从 `127.0.0.1:6380:6379` 改成内网/公网白名单可访问的地址,并更新防火墙。
|
||||||
|
|
||||||
|
重启业务实例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.cloud.yml restart ppanel-server
|
||||||
|
docker compose -f docker-compose.replica.yml restart ppanel-server
|
||||||
|
```
|
||||||
|
|
||||||
|
## 21. 常见问题
|
||||||
|
|
||||||
|
### `Replica_IO_Running: No`
|
||||||
|
|
||||||
|
检查:
|
||||||
|
|
||||||
|
- 主机防火墙是否允许从机 IP 访问 `3306`。
|
||||||
|
- `MYSQL_REPLICATION_USER` 和 `MYSQL_REPLICATION_PASSWORD` 是否一致。
|
||||||
|
- 主库是否已经创建复制账号。
|
||||||
|
- `MASTER_MYSQL_HOST` 是否写错。
|
||||||
|
|
||||||
|
查看错误:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-mysql-replica mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW REPLICA STATUS\\G" | egrep "Last_IO_Error|Last_SQL_Error"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `Replica_SQL_Running: No`
|
||||||
|
|
||||||
|
通常是导入备份或 GTID 状态不一致。先看错误:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec ppanel-mysql-replica mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "SHOW REPLICA STATUS\\G" | egrep "Last_SQL_Error|Retrieved_Gtid_Set|Executed_Gtid_Set"
|
||||||
|
```
|
||||||
|
|
||||||
|
不要直接跳过事务。生产环境应先备份当前从库,再决定是否重建从库数据卷重新导入。
|
||||||
|
|
||||||
|
### 从机业务登录异常
|
||||||
|
|
||||||
|
检查从机 `configs/ppanel.yaml`:
|
||||||
|
|
||||||
|
- `MySQL.Addr` 必须是主机 MySQL。
|
||||||
|
- `Redis.Host` 必须是主机 Redis。
|
||||||
|
- `Redis.Pass` 必须填写。
|
||||||
|
|
||||||
|
### Redis 从库不同步
|
||||||
|
|
||||||
|
检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker logs --tail=100 ppanel-redis-replica
|
||||||
|
docker exec ppanel-redis-replica redis-cli -a "$REDIS_PASSWORD" INFO replication
|
||||||
|
```
|
||||||
|
|
||||||
|
常见原因是主机 `6379` 未对白名单放行,或 `REDIS_PASSWORD` 不一致。
|
||||||
|
|
||||||
|
### Grafana MCP 连接失败
|
||||||
|
|
||||||
|
检查:
|
||||||
|
|
||||||
|
- SSH 隧道是否仍在运行。
|
||||||
|
- 本地是否能打开 `http://localhost:3333`。
|
||||||
|
- Grafana Service Account Token 是否有效。
|
||||||
|
- 本地 MCP 配置中的 `GRAFANA_URL` 是否是 `http://localhost:3333`。
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# PPanel 服务部署 (云端/无源码版)
|
# PPanel 服务部署 (云端/无源码版)
|
||||||
# 使用方法:
|
# 使用方法:
|
||||||
# 1. 确保已将 docker-compose.cloud.yml, configs/, loki/, grafana/, prometheus/, tempo/ 目录上传到服务器同一目录
|
# 1. 确保已将 docker-compose.cloud.yml, configs/, mysql/, redis/, loki/, grafana/, prometheus/, tempo/ 目录上传到主服务器同一目录
|
||||||
# 2. 确保 configs/ 目录下有 ppanel.yaml 配置文件(参考 etc/ppanel.yaml)
|
# 2. 确保 configs/ 目录下有 ppanel.yaml 配置文件(参考 etc/ppanel.yaml)
|
||||||
# 3. 确保 logs/ cache/ tempo_data/ 目录存在 (mkdir -p logs cache tempo_data)
|
# 3. 确保 logs/ cache/ tempo_data/ 目录存在 (mkdir -p logs cache tempo_data)
|
||||||
# 4. 运行: docker-compose -f docker-compose.cloud.yml up -d
|
# 4. 运行: docker-compose -f docker-compose.cloud.yml up -d
|
||||||
@ -8,7 +8,8 @@
|
|||||||
# 网络说明:
|
# 网络说明:
|
||||||
# ppanel-server 使用 host 网络(可出外网,访问 MySQL/Redis/Tempo 用 127.0.0.1)
|
# ppanel-server 使用 host 网络(可出外网,访问 MySQL/Redis/Tempo 用 127.0.0.1)
|
||||||
# 监控服务(MySQL/Redis/Loki/Tempo/Grafana/Prometheus)在 ppanel_net bridge 网络中
|
# 监控服务(MySQL/Redis/Loki/Tempo/Grafana/Prometheus)在 ppanel_net bridge 网络中
|
||||||
# MySQL(3306)/Redis(6379)/Tempo(4317) 将端口映射到 127.0.0.1,ppanel-server 通过 host 网络访问
|
# MySQL(3306)/Redis(6379)/Tempo(4317) 映射到宿主机,ppanel-server 通过 host 网络访问
|
||||||
|
# 远程从库/远程业务实例访问 MySQL/Redis 时,必须在宿主机防火墙只放行从机公网 IP
|
||||||
# 监控端口绑定 127.0.0.1,需通过 SSH 隧道或 Nginx 反代访问
|
# 监控端口绑定 127.0.0.1,需通过 SSH 隧道或 Nginx 反代访问
|
||||||
#
|
#
|
||||||
# 未来多开 ppanel-server 时:
|
# 未来多开 ppanel-server 时:
|
||||||
@ -50,17 +51,19 @@ services:
|
|||||||
max-file: "3"
|
max-file: "3"
|
||||||
|
|
||||||
# ----------------------------------------------------
|
# ----------------------------------------------------
|
||||||
# 2. MySQL Database
|
# 2. MySQL Database (主库)
|
||||||
# ----------------------------------------------------
|
# ----------------------------------------------------
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:8.0
|
image: mysql:8.0
|
||||||
container_name: ppanel-mysql
|
container_name: ppanel-mysql
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "3306:3306" # 仅宿主机可访问,ppanel-server(host网络)通过127.0.0.1连接
|
- "${MYSQL_PUBLISH_ADDR:-0.0.0.0}:3306:3306" # 远程从库需要访问;请用防火墙只放行从机公网 IP
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?请在 .env 文件中设置 MYSQL_ROOT_PASSWORD}"
|
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?请在 .env 文件中设置 MYSQL_ROOT_PASSWORD}"
|
||||||
MYSQL_DATABASE: "ppanel"
|
MYSQL_DATABASE: "${MYSQL_DATABASE:-ppanel}"
|
||||||
|
MYSQL_REPLICATION_USER: "${MYSQL_REPLICATION_USER:?请在 .env 文件中设置 MYSQL_REPLICATION_USER}"
|
||||||
|
MYSQL_REPLICATION_PASSWORD: "${MYSQL_REPLICATION_PASSWORD:?请在 .env 文件中设置 MYSQL_REPLICATION_PASSWORD}"
|
||||||
TZ: Asia/Shanghai
|
TZ: Asia/Shanghai
|
||||||
command:
|
command:
|
||||||
- --default-authentication-plugin=mysql_native_password
|
- --default-authentication-plugin=mysql_native_password
|
||||||
@ -70,7 +73,15 @@ services:
|
|||||||
- --innodb_flush_log_at_trx_commit=2
|
- --innodb_flush_log_at_trx_commit=2
|
||||||
- --innodb_io_capacity=5000
|
- --innodb_io_capacity=5000
|
||||||
- --max_connections=5000
|
- --max_connections=5000
|
||||||
|
- --server-id=1
|
||||||
|
- --log-bin=mysql-bin
|
||||||
|
- --binlog-format=ROW
|
||||||
|
- --gtid-mode=ON
|
||||||
|
- --enforce-gtid-consistency=ON
|
||||||
|
- --log-replica-updates=ON
|
||||||
volumes:
|
volumes:
|
||||||
|
- ./mysql/master.cnf:/etc/mysql/conf.d/replication.cnf:ro
|
||||||
|
- ./mysql/init-master.sh:/docker-entrypoint-initdb.d/10-init-master.sh:ro
|
||||||
- mysql_data:/var/lib/mysql
|
- mysql_data:/var/lib/mysql
|
||||||
ulimits:
|
ulimits:
|
||||||
nproc: 65535
|
nproc: 65535
|
||||||
@ -97,12 +108,22 @@ services:
|
|||||||
image: redis:8.2.1
|
image: redis:8.2.1
|
||||||
container_name: ppanel-redis
|
container_name: ppanel-redis
|
||||||
restart: always
|
restart: always
|
||||||
|
environment:
|
||||||
|
REDIS_PASSWORD: "${REDIS_PASSWORD:?请在 .env 文件中设置 REDIS_PASSWORD}"
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:6379:6379" # 仅宿主机可访问,ppanel-server(host网络)通过127.0.0.1连接
|
- "${REDIS_PUBLISH_ADDR:-0.0.0.0}:6379:6379" # 远程业务实例/Redis从库需要访问;请用防火墙只放行从机公网 IP
|
||||||
command:
|
command:
|
||||||
- redis-server
|
- redis-server
|
||||||
- --tcp-backlog 65535
|
- --tcp-backlog
|
||||||
- --maxmemory-policy allkeys-lru
|
- "65535"
|
||||||
|
- --maxmemory-policy
|
||||||
|
- allkeys-lru
|
||||||
|
- --requirepass
|
||||||
|
- "${REDIS_PASSWORD:?请在 .env 文件中设置 REDIS_PASSWORD}"
|
||||||
|
- --masterauth
|
||||||
|
- "${REDIS_PASSWORD:?请在 .env 文件中设置 REDIS_PASSWORD}"
|
||||||
|
- --appendonly
|
||||||
|
- "yes"
|
||||||
volumes:
|
volumes:
|
||||||
- redis_data:/data
|
- redis_data:/data
|
||||||
ulimits:
|
ulimits:
|
||||||
@ -113,7 +134,7 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- ppanel_net
|
- ppanel_net
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
test: ["CMD-SHELL", "redis-cli -a \"$${REDIS_PASSWORD}\" ping | grep -q PONG"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@ -255,6 +276,7 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
- REDIS_ADDR=redis://redis:6379
|
- REDIS_ADDR=redis://redis:6379
|
||||||
|
- REDIS_PASSWORD=${REDIS_PASSWORD:?请在 .env 文件中设置 REDIS_PASSWORD}
|
||||||
networks:
|
networks:
|
||||||
- ppanel_net
|
- ppanel_net
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
200
docker-compose.replica.yml
Normal file
200
docker-compose.replica.yml
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
# PPanel 从机部署:远程 MySQL 从库 + Redis 从库/Sentinel + 额外业务实例
|
||||||
|
#
|
||||||
|
# 使用方式:
|
||||||
|
# 1. 将 docker-compose.replica.yml, configs/, mysql/, redis/, tempo/ 上传到从机同一目录
|
||||||
|
# 2. .env 中填写主机公网 IP: MASTER_MYSQL_HOST, MASTER_REDIS_HOST
|
||||||
|
# 3. 先按 doc/remote-replication-zh.md 导入主库备份,再运行 replication-setup profile
|
||||||
|
# 4. 业务实例的 configs/ppanel.yaml 必须指向主 MySQL 和主 Redis,不要指向本机从库
|
||||||
|
|
||||||
|
services:
|
||||||
|
ppanel-server:
|
||||||
|
image: registry.kxsw.us/vpn-server:${PPANEL_SERVER_TAG:-latest}
|
||||||
|
container_name: ppanel-server
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./configs:/app/etc
|
||||||
|
- ./logs:/app/logs
|
||||||
|
- ./cache:/app/cache
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
network_mode: host
|
||||||
|
ulimits:
|
||||||
|
nproc: 65535
|
||||||
|
nofile:
|
||||||
|
soft: 65535
|
||||||
|
hard: 65535
|
||||||
|
depends_on:
|
||||||
|
tempo:
|
||||||
|
condition: service_started
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
mysql-replica:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: ppanel-mysql-replica
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:3307:3306"
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?请在 .env 文件中设置 MYSQL_ROOT_PASSWORD}"
|
||||||
|
MYSQL_DATABASE: "${MYSQL_DATABASE:-ppanel}"
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
command:
|
||||||
|
- --default-authentication-plugin=mysql_native_password
|
||||||
|
- --innodb_buffer_pool_size=16G
|
||||||
|
- --innodb_buffer_pool_instances=16
|
||||||
|
- --innodb_log_file_size=2G
|
||||||
|
- --innodb_flush_log_at_trx_commit=2
|
||||||
|
- --innodb_io_capacity=5000
|
||||||
|
- --max_connections=5000
|
||||||
|
volumes:
|
||||||
|
- ./mysql/replica.cnf:/etc/mysql/conf.d/replication.cnf:ro
|
||||||
|
- mysql_replica_data:/var/lib/mysql
|
||||||
|
ulimits:
|
||||||
|
nproc: 65535
|
||||||
|
nofile:
|
||||||
|
soft: 65535
|
||||||
|
hard: 65535
|
||||||
|
networks:
|
||||||
|
- ppanel_net
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
mysql-replica-setup:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: ppanel-mysql-replica-setup
|
||||||
|
profiles:
|
||||||
|
- replication-setup
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?请在 .env 文件中设置 MYSQL_ROOT_PASSWORD}"
|
||||||
|
MASTER_MYSQL_HOST: "${MASTER_MYSQL_HOST:?请在 .env 文件中设置 MASTER_MYSQL_HOST}"
|
||||||
|
MASTER_MYSQL_PORT: "${MASTER_MYSQL_PORT:-3306}"
|
||||||
|
MYSQL_REPLICATION_USER: "${MYSQL_REPLICATION_USER:?请在 .env 文件中设置 MYSQL_REPLICATION_USER}"
|
||||||
|
MYSQL_REPLICATION_PASSWORD: "${MYSQL_REPLICATION_PASSWORD:?请在 .env 文件中设置 MYSQL_REPLICATION_PASSWORD}"
|
||||||
|
REPLICA_MYSQL_HOST: mysql-replica
|
||||||
|
REPLICA_MYSQL_PORT: "3306"
|
||||||
|
volumes:
|
||||||
|
- ./mysql/configure-replica.sh:/scripts/configure-replica.sh:ro
|
||||||
|
entrypoint:
|
||||||
|
- /bin/sh
|
||||||
|
- /scripts/configure-replica.sh
|
||||||
|
networks:
|
||||||
|
- ppanel_net
|
||||||
|
depends_on:
|
||||||
|
mysql-replica:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
redis-replica:
|
||||||
|
image: redis:8.2.1
|
||||||
|
container_name: ppanel-redis-replica
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
REDIS_PASSWORD: "${REDIS_PASSWORD:?请在 .env 文件中设置 REDIS_PASSWORD}"
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:6380:6379"
|
||||||
|
command:
|
||||||
|
- redis-server
|
||||||
|
- --replicaof
|
||||||
|
- "${MASTER_REDIS_HOST:?请在 .env 文件中设置 MASTER_REDIS_HOST}"
|
||||||
|
- "${MASTER_REDIS_PORT:-6379}"
|
||||||
|
- --masterauth
|
||||||
|
- "${REDIS_PASSWORD:?请在 .env 文件中设置 REDIS_PASSWORD}"
|
||||||
|
- --requirepass
|
||||||
|
- "${REDIS_PASSWORD:?请在 .env 文件中设置 REDIS_PASSWORD}"
|
||||||
|
- --appendonly
|
||||||
|
- "yes"
|
||||||
|
- --tcp-backlog
|
||||||
|
- "65535"
|
||||||
|
- --maxmemory-policy
|
||||||
|
- allkeys-lru
|
||||||
|
volumes:
|
||||||
|
- redis_replica_data:/data
|
||||||
|
ulimits:
|
||||||
|
nproc: 65535
|
||||||
|
nofile:
|
||||||
|
soft: 65535
|
||||||
|
hard: 65535
|
||||||
|
networks:
|
||||||
|
- ppanel_net
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "redis-cli -a \"$${REDIS_PASSWORD}\" ping | grep -q PONG"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
redis-sentinel:
|
||||||
|
image: redis:8.2.1
|
||||||
|
container_name: ppanel-redis-sentinel
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:26379:26379"
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
printf '%s\n' \
|
||||||
|
'port 26379' \
|
||||||
|
'dir /tmp' \
|
||||||
|
'sentinel monitor ppanel-redis-master ${MASTER_REDIS_HOST} ${MASTER_REDIS_PORT:-6379} 2' \
|
||||||
|
'sentinel auth-pass ppanel-redis-master ${REDIS_PASSWORD}' \
|
||||||
|
'sentinel down-after-milliseconds ppanel-redis-master 10000' \
|
||||||
|
'sentinel failover-timeout ppanel-redis-master 60000' \
|
||||||
|
'sentinel parallel-syncs ppanel-redis-master 1' \
|
||||||
|
> /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
|
||||||
5
mysql/.my.cnf.example
Normal file
5
mysql/.my.cnf.example
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[client]
|
||||||
|
host=mysql
|
||||||
|
port=3306
|
||||||
|
user=root
|
||||||
|
password=CHANGE_ME_TO_MYSQL_ROOT_PASSWORD
|
||||||
41
mysql/configure-replica.sh
Normal file
41
mysql/configure-replica.sh
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#!/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}" <<SQL
|
||||||
|
STOP REPLICA;
|
||||||
|
RESET REPLICA ALL;
|
||||||
|
SET GLOBAL super_read_only = OFF;
|
||||||
|
SET GLOBAL read_only = OFF;
|
||||||
|
CHANGE REPLICATION SOURCE TO
|
||||||
|
SOURCE_HOST='${MASTER_MYSQL_HOST}',
|
||||||
|
SOURCE_PORT=${MASTER_MYSQL_PORT},
|
||||||
|
SOURCE_USER='${MYSQL_REPLICATION_USER}',
|
||||||
|
SOURCE_PASSWORD='${MYSQL_REPLICATION_PASSWORD}',
|
||||||
|
SOURCE_AUTO_POSITION=1,
|
||||||
|
GET_SOURCE_PUBLIC_KEY=1;
|
||||||
|
START REPLICA;
|
||||||
|
SET GLOBAL read_only = ON;
|
||||||
|
SET GLOBAL super_read_only = ON;
|
||||||
|
SQL
|
||||||
|
|
||||||
|
mysql -h"${REPLICA_MYSQL_HOST}" -P"${REPLICA_MYSQL_PORT}" -uroot -p"${MYSQL_ROOT_PASSWORD}" -e "SHOW REPLICA STATUS\\G"
|
||||||
14
mysql/init-master.sh
Normal file
14
mysql/init-master.sh
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
: "${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD is required}"
|
||||||
|
: "${MYSQL_REPLICATION_USER:?MYSQL_REPLICATION_USER is required}"
|
||||||
|
: "${MYSQL_REPLICATION_PASSWORD:?MYSQL_REPLICATION_PASSWORD is required}"
|
||||||
|
|
||||||
|
mysql_note "Creating MySQL replication user '${MYSQL_REPLICATION_USER}'"
|
||||||
|
|
||||||
|
mysql --protocol=socket -uroot -p"${MYSQL_ROOT_PASSWORD}" <<SQL
|
||||||
|
CREATE USER IF NOT EXISTS '${MYSQL_REPLICATION_USER}'@'%' IDENTIFIED WITH mysql_native_password BY '${MYSQL_REPLICATION_PASSWORD}';
|
||||||
|
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO '${MYSQL_REPLICATION_USER}'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
SQL
|
||||||
9
mysql/master.cnf
Normal file
9
mysql/master.cnf
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[mysqld]
|
||||||
|
server-id=1
|
||||||
|
log-bin=mysql-bin
|
||||||
|
binlog-format=ROW
|
||||||
|
gtid-mode=ON
|
||||||
|
enforce-gtid-consistency=ON
|
||||||
|
log-replica-updates=ON
|
||||||
|
binlog-expire-logs-seconds=604800
|
||||||
|
relay-log-recovery=ON
|
||||||
10
mysql/replica.cnf
Normal file
10
mysql/replica.cnf
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[mysqld]
|
||||||
|
server-id=2
|
||||||
|
relay-log=mysql-relay-bin
|
||||||
|
read-only=ON
|
||||||
|
super-read-only=ON
|
||||||
|
gtid-mode=ON
|
||||||
|
enforce-gtid-consistency=ON
|
||||||
|
log-replica-updates=ON
|
||||||
|
relay-log-recovery=ON
|
||||||
|
skip-replica-start=ON
|
||||||
7
redis/sentinel.conf.example
Normal file
7
redis/sentinel.conf.example
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
port 26379
|
||||||
|
dir /tmp
|
||||||
|
sentinel monitor ppanel-redis-master MASTER_REDIS_HOST MASTER_REDIS_PORT 2
|
||||||
|
sentinel auth-pass ppanel-redis-master CHANGE_ME_TO_STRONG_REDIS_PASSWORD
|
||||||
|
sentinel down-after-milliseconds ppanel-redis-master 10000
|
||||||
|
sentinel failover-timeout ppanel-redis-master 60000
|
||||||
|
sentinel parallel-syncs ppanel-redis-master 1
|
||||||
Loading…
x
Reference in New Issue
Block a user