13-快速部署
本文档提供完整的一键部署脚本,可在各节点直接执行,快速完成整个集群的部署。
部署概述
-
Node1:Master节点,运行所有7个容器
-
Node2:Backup节点,运行所有6个容器(无mysql-01的init.sql)
-
Node3:Backup节点,运行所有7个容器(含backup-server)
前置条件
在开始部署前,确保:
-
所有节点已安装Docker和Docker Compose
docker --version docker compose version -
所有节点已创建5个macvlan网络
docker network ls | grep -E "frontend|backend|cache|database|manage" -
已配置内核参数
# 检查macvlan模块 lsmod | grep macvlan -
网络互通
ping -c 1 192.168.64.128 ping -c 1 192.168.64.129 ping -c 1 192.168.64.130
Node1部署脚本
将以下脚本保存到Node1(192.168.64.128)执行:
#!/bin/bash
# Node1部署脚本
set -e
echo "============================================"
echo " Node1 部署脚本 (192.168.64.128)"
echo "============================================"
# 1. 创建目录结构
echo "[1/7] 创建目录结构..."
mkdir -p /opt/cluster-deploy/{config/{nginx-lb/conf.d,keepalived,php,redis,mysql},scripts}
# 2. 创建nginx-lb配置
echo "[2/7] 创建nginx-lb配置..."
cat > /opt/cluster-deploy/config/nginx-lb/nginx.conf << 'EOF'
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'upstream: $upstream_addr '
'upstream_status: $upstream_status '
'request_time: $request_time '
'upstream_response_time: $upstream_response_time';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100M;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml font/truetype font/opentype
application/vnd.ms-fontobject image/svg+xml;
upstream web_backend {
least_conn;
server 172.20.2.11:80 max_fails=3 fail_timeout=30s;
server 172.20.2.12:80 max_fails=3 fail_timeout=30s;
server 172.20.2.13:80 max_fails=3 fail_timeout=30s;
}
include /etc/nginx/conf.d/*.conf;
}
EOF
cat > /opt/cluster-deploy/config/nginx-lb/conf.d/upstream.conf << 'EOF'
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://web_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;
proxy_connect_timeout 10s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
EOF
cat > /opt/cluster-deploy/config/nginx-lb/ssl.conf << 'EOF'
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
EOF
# 3. 创建keepalived配置
echo "[3/7] 创建keepalived配置..."
cat > /opt/cluster-deploy/config/keepalived/keepalived_master.conf << 'EOF'
global_defs {
router_id LVS_MASTER
script_user root
enable_script_security
}
vrrp_script check_nginx {
script "/etc/keepalived/check_nginx.sh"
interval 3
weight -20
fall 2
rise 1
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 100
priority 100
advert_int 1
nopreempt
unicast_peer {
172.20.1.12
172.20.1.13
}
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
172.20.1.100/24 dev eth0
}
track_script {
check_nginx
}
notify_master "/etc/keepalived/notify.sh master"
notify_backup "/etc/keepalived/notify.sh backup"
notify_fault "/etc/keepalived/notify.sh fault"
}
EOF
cat > /opt/cluster-deploy/config/keepalived/keepalived_backup.conf << 'EOF'
global_defs {
router_id LVS_BACKUP1
script_user root
enable_script_security
}
vrrp_script check_nginx {
script "/etc/keepalived/check_nginx.sh"
interval 3
weight -20
fall 2
rise 1
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 100
priority 90
advert_int 1
nopreempt
unicast_peer {
172.20.1.11
172.20.1.13
}
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
172.20.1.100/24 dev eth0
}
track_script {
check_nginx
}
notify_master "/etc/keepalived/notify.sh master"
notify_backup "/etc/keepalived/notify.sh backup"
notify_fault "/etc/keepalived/notify.sh fault"
}
EOF
cat > /opt/cluster-deploy/config/keepalived/keepalived_backup2.conf << 'EOF'
global_defs {
router_id LVS_BACKUP2
script_user root
enable_script_security
}
vrrp_script check_nginx {
script "/etc/keepalived/check_nginx.sh"
interval 3
weight -20
fall 2
rise 1
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 100
priority 80
advert_int 1
nopreempt
unicast_peer {
172.20.1.11
172.20.1.12
}
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
172.20.1.100/24 dev eth0
}
track_script {
check_nginx
}
notify_master "/etc/keepalived/notify.sh master"
notify_backup "/etc/keepalived/notify.sh backup"
notify_fault "/etc/keepalived/notify.sh fault"
}
EOF
cat > /opt/cluster-deploy/config/keepalived/check_nginx.sh << 'EOF'
#!/bin/bash
A=$(ps -C nginx --no-headers | wc -l)
if [ "$A" -eq 0 ];then
exit 1
fi
EOF
cat > /opt/cluster-deploy/config/keepalived/notify.sh << 'EOF'
#!/bin/bash
LOGFILE=/var/log/keepalived-notify.log
echo "[$(date '+%Y-%m-%d %H:%M:%S')] State changed to: $1" >> $LOGFILE
EOF
chmod +x /opt/cluster-deploy/config/keepalived/check_nginx.sh
chmod +x /opt/cluster-deploy/config/keepalived/notify.sh
# 4. 创建php配置
echo "[4/7] 创建php配置..."
cat > /opt/cluster-deploy/config/php/php-node1.conf << 'EOF'
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'request_time: $request_time';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100M;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml font/truetype font/opentype
application/vnd.ms-fontobject image/svg+xml;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass 172.20.2.21:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
include fastcgi_params;
}
location /health {
access_log off;
return 200 "php-healthy\n";
add_header Content-Type text/plain;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
}
}
EOF
cat > /opt/cluster-deploy/config/php/php-node2.conf << 'EOF'
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'request_time: $request_time';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100M;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml font/truetype font/opentype
application/vnd.ms-fontobject image/svg+xml;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass 172.20.2.22:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
include fastcgi_params;
}
location /health {
access_log off;
return 200 "php-healthy\n";
add_header Content-Type text/plain;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
}
}
EOF
cat > /opt/cluster-deploy/config/php/php-node3.conf << 'EOF'
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'request_time: $request_time';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100M;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml font/truetype font/opentype
application/vnd.ms-fontobject image/svg+xml;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass 172.20.2.23:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
include fastcgi_params;
}
location /health {
access_log off;
return 200 "php-healthy\n";
add_header Content-Type text/plain;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
}
}
EOF
cat > /opt/cluster-deploy/config/php/php.ini << 'EOF'
[PHP]
upload_max_filesize = 50M
post_max_size = 50M
max_execution_time = 60
memory_limit = 256M
display_errors = Off
log_errors = On
error_log = /var/log/php/error.log
date.timezone = Asia/Shanghai
[Session]
session.save_handler = redis
session.save_path = "tcp://172.20.3.11:6379?auth=YourStr0ng!Pass"
[opcache]
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 4000
EOF
# 5. 创建redis配置
echo "[5/7] 创建redis配置..."
cat > /opt/cluster-deploy/config/redis/redis-master.conf << 'EOF'
bind 0.0.0.0
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize no
supervised no
pidfile /var/run/redis/redis-server.pid
loglevel notice
logfile ""
databases 16
always-show-logo no
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /data
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
replica-priority 100
maxmemory 256mb
maxmemory-policy allkeys-lru
maxmemory-samples 5
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
EOF
cat > /opt/cluster-deploy/config/redis/redis-slave.conf << 'EOF'
bind 0.0.0.0
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize no
supervised no
pidfile /var/run/redis/redis-server.pid
loglevel notice
logfile ""
databases 16
always-show-logo no
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /data
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
replica-priority 100
maxmemory 256mb
maxmemory-policy allkeys-lru
maxmemory-samples 5
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
EOF
cat > /opt/cluster-deploy/config/redis/sentinel.conf << 'EOF'
bind 0.0.0.0
port 26379
daemonize no
supervised no
pidfile /var/run/redis-sentinel.pid
logfile ""
loglevel notice
sentinel monitor mymaster 172.20.3.11 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes
sentinel auth-pass mymaster 'YourStr0ng!Pass'
EOF
# 6. 创建mysql配置
echo "[6/7] 创建mysql配置..."
cat > /opt/cluster-deploy/config/mysql/my-node1.cnf << 'EOF'
[mysqld]
server-id=1
bind-address=0.0.0.0
port=3306
basedir=/usr
datadir=/var/lib/mysql
socket=/var/run/mysqld/mysqld.sock
pid-file=/var/run/mysqld/mysqld.pid
log-error=/var/log/mysql/error.log
report_host=172.20.4.11
gtid_mode=ON
enforce_gtid_consistency=ON
log_slave_updates=ON
binlog_checksum=NONE
skip-name-resolve
loose-group_replication_group_name="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
loose-group_replication_start_on_boot=OFF
loose-group_replication_local_address="172.20.4.11:33061"
loose-group_replication_group_seeds="172.20.4.11:33061,172.20.4.12:33061,172.20.4.13:33061"
loose-group_replication_ip_allowlist="172.20.4.0/24"
loose-group_replication_single_primary_mode=ON
loose-group_replication_enforce_update_everywhere_checks=OFF
loose-group_replication_poll_spin_loops=100
loose-group_replication_recovery_reconnect_interval=10
loose-group_replication_member_weight=70
master_info_repository=TABLE
relay_log_info_repository=TABLE
transaction_write_set_extraction=XXHASH64
binlog_format=ROW
[client]
socket=/var/run/mysqld/mysqld.sock
[mysql]
socket=/var/run/mysqld/mysqld.sock
EOF
cat > /opt/cluster-deploy/config/mysql/my-node2.cnf << 'EOF'
[mysqld]
server-id=2
bind-address=0.0.0.0
port=3306
basedir=/usr
datadir=/var/lib/mysql
socket=/var/run/mysqld/mysqld.sock
pid-file=/var/run/mysqld/mysqld.pid
log-error=/var/log/mysql/error.log
report_host=172.20.4.12
gtid_mode=ON
enforce_gtid_consistency=ON
log_slave_updates=ON
binlog_checksum=NONE
skip-name-resolve
loose-group_replication_group_name="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
loose-group_replication_start_on_boot=OFF
loose-group_replication_local_address="172.20.4.12:33061"
loose-group_replication_group_seeds="172.20.4.11:33061,172.20.4.12:33061,172.20.4.13:33061"
loose-group_replication_ip_allowlist="172.20.4.0/24"
loose-group_replication_single_primary_mode=ON
loose-group_replication_enforce_update_everywhere_checks=OFF
loose-group_replication_poll_spin_loops=100
loose-group_replication_recovery_reconnect_interval=10
loose-group_replication_member_weight=60
master_info_repository=TABLE
relay_log_info_repository=TABLE
transaction_write_set_extraction=XXHASH64
binlog_format=ROW
[client]
socket=/var/run/mysqld/mysqld.sock
[mysql]
socket=/var/run/mysqld/mysqld.sock
EOF
cat > /opt/cluster-deploy/config/mysql/my-node3.cnf << 'EOF'
[mysqld]
server-id=3
bind-address=0.0.0.0
port=3306
basedir=/usr
datadir=/var/lib/mysql
socket=/var/run/mysqld/mysqld.sock
pid-file=/var/run/mysqld/mysqld.pid
log-error=/var/log/mysql/error.log
report_host=172.20.4.13
gtid_mode=ON
enforce_gtid_consistency=ON
log_slave_updates=ON
binlog_checksum=NONE
skip-name-resolve
loose-group_replication_group_name="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
loose-group_replication_start_on_boot=OFF
loose-group_replication_local_address="172.20.4.13:33061"
loose-group_replication_group_seeds="172.20.4.11:33061,172.20.4.12:33061,172.20.4.13:33061"
loose-group_replication_ip_allowlist="172.20.4.0/24"
loose-group_replication_single_primary_mode=ON
loose-group_replication_enforce_update_everywhere_checks=OFF
loose-group_replication_poll_spin_loops=100
loose-group_replication_recovery_reconnect_interval=10
loose-group_replication_member_weight=50
master_info_repository=TABLE
relay_log_info_repository=TABLE
transaction_write_set_extraction=XXHASH64
binlog_format=ROW
[client]
socket=/var/run/mysqld/mysqld.sock
[mysql]
socket=/var/run/mysqld/mysqld.sock
EOF
cat > /opt/cluster-deploy/config/mysql/init.sql << 'EOF'
CREATE DATABASE IF NOT EXISTS app_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE app_db;
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO users (username, email) VALUES
('admin', 'admin@example.com'),
('user1', 'user1@example.com'),
('user2', 'user2@example.com');
EOF
# 7. 创建docker-compose文件
echo "[7/7] 创建docker-compose-node1.yml..."
cat > /opt/cluster-deploy/docker-compose-node1.yml << 'EOF'
services:
nginx-lb:
image: nginx:alpine
container_name: nginx-lb
networks:
frontend-net:
ipv4_address: 172.20.1.11
backend-net:
ipv4_address: 172.20.2.100
volumes:
- ./config/nginx-lb/nginx.conf:/etc/nginx/nginx.conf:ro
- ./config/nginx-lb/conf.d:/etc/nginx/conf.d:ro
- ./config/nginx-lb/ssl.conf:/etc/nginx/ssl.conf:ro
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/health > /dev/null 2>&1 || exit 1"]
interval: 10s
timeout: 5s
retries: 3
keepalived:
image: ednxzu/keepalived:2.3.4
container_name: keepalived
network_mode: service:nginx-lb
privileged: true
entrypoint: ["/usr/sbin/keepalived", "-f", "/etc/keepalived/keepalived.conf", "--dont-fork", "--log-console"]
volumes:
- ./config/keepalived/keepalived_master.conf:/etc/keepalived/keepalived.conf:ro
- ./config/keepalived/check_nginx.sh:/etc/keepalived/check_nginx.sh:ro
- ./config/keepalived/notify.sh:/etc/keepalived/notify.sh:ro
restart: unless-stopped
php:
image: nginx:alpine
container_name: php
networks:
backend-net:
ipv4_address: 172.20.2.11
volumes:
- ./config/php/php-node1.conf:/etc/nginx/nginx.conf:ro
- web-data:/usr/share/nginx/html
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/index.php > /dev/null 2>&1 || exit 1"]
interval: 10s
timeout: 5s
retries: 3
php-fpm:
build: ./config/php-fpm
container_name: php-fpm
networks:
backend-net:
ipv4_address: 172.20.2.21
volumes:
- ./config/php/php.ini:/usr/local/etc/php/conf.d/custom.ini:ro
- web-data:/var/www/html
restart: unless-stopped
healthcheck:
test: ["CMD", "php-fpm", "-t"]
interval: 10s
timeout: 5s
retries: 3
redis-master:
image: redis:7-alpine
container_name: redis-master
networks:
cache-net:
ipv4_address: 172.20.3.11
command: redis-server /etc/redis/redis.conf
volumes:
- redis-master-data:/data
- ./config/redis/redis-master.conf:/etc/redis/redis.conf:ro
environment:
- REDIS_PASSWORD=YourStr0ng!Pass
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "-a", "YourStr0ng!Pass", "ping"]
interval: 10s
timeout: 5s
retries: 3
sentinel-01:
image: redis:7-alpine
container_name: sentinel-01
networks:
cache-net:
ipv4_address: 172.20.3.31
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./config/redis/sentinel.conf:/etc/redis/sentinel.conf
depends_on:
- redis-master
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "-p", "26379", "ping"]
interval: 10s
timeout: 5s
retries: 3
mysql-01:
image: mysql:8.0
container_name: mysql-01
hostname: mysql-node1
networks:
database-net:
ipv4_address: 172.20.4.11
volumes:
- mysql-01-data:/var/lib/mysql
- ./config/mysql/my-node1.cnf:/etc/mysql/conf.d/my.cnf:ro
- ./config/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
environment:
- MYSQL_ROOT_PASSWORD=YourStr0ng!Pass
- MYSQL_DATABASE=app_db
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "-uroot", "-pYourStr0ng!Pass", "ping", "-h", "127.0.0.1"]
interval: 15s
timeout: 10s
retries: 5
networks:
frontend-net:
driver: macvlan
driver_opts:
parent: ens33
ipam:
config:
- subnet: 172.20.1.0/24
gateway: 172.20.1.1
backend-net:
driver: macvlan
driver_opts:
parent: ens33
ipam:
config:
- subnet: 172.20.2.0/24
gateway: 172.20.2.1
cache-net:
driver: macvlan
driver_opts:
parent: ens33
ipam:
config:
- subnet: 172.20.3.0/24
gateway: 172.20.3.1
database-net:
driver: macvlan
driver_opts:
parent: ens33
ipam:
config:
- subnet: 172.20.4.0/24
gateway: 172.20.4.1
volumes:
redis-master-data:
mysql-01-data:
web-data:
EOF
# 启动服务
echo ""
echo "============================================"
echo "启动Node1服务..."
echo "============================================"
cd /opt/cluster-deploy
docker compose -f docker-compose-node1.yml up -d
echo ""
echo "Node1部署完成!"
echo ""
echo "下一步:"
echo "1. 在Node2执行 node2-deploy.sh"
echo "2. 在Node3执行 node3-deploy.sh"
echo "3. 等待MySQL就绪后执行MGR初始化"
Node2部署脚本
将以下脚本保存到Node2(192.168.64.129)执行:
#!/bin/bash
# Node2部署脚本
set -e
echo "============================================"
echo " Node2 部署脚本 (192.168.64.129)"
echo "============================================"
# 1. 创建目录结构
echo "[1/6] 创建目录结构..."
mkdir -p /opt/cluster-deploy/{config/{nginx-lb/conf.d,keepalived,php,redis,mysql},scripts}
# 2. 复制配置文件(从共享位置或手动复制)
echo "[2/6] 请先从Node1复制配置文件到 /opt/cluster-deploy/config/"
echo "完成后按Enter继续..."
read
# 3. 创建docker-compose-node2.yml
echo "[3/6] 创建docker-compose-node2.yml..."
cat > /opt/cluster-deploy/docker-compose-node2.yml << 'EOF'
services:
nginx-lb:
image: nginx:alpine
container_name: nginx-lb
networks:
frontend-net:
ipv4_address: 172.20.1.12
backend-net:
ipv4_address: 172.20.2.101
volumes:
- ./config/nginx-lb/nginx.conf:/etc/nginx/nginx.conf:ro
- ./config/nginx-lb/conf.d:/etc/nginx/conf.d:ro
- ./config/nginx-lb/ssl.conf:/etc/nginx/ssl.conf:ro
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/health > /dev/null 2>&1 || exit 1"]
interval: 10s
timeout: 5s
retries: 3
keepalived:
image: ednxzu/keepalived:2.3.4
container_name: keepalived
network_mode: service:nginx-lb
privileged: true
entrypoint: ["/usr/sbin/keepalived", "-f", "/etc/keepalived/keepalived.conf", "--dont-fork", "--log-console"]
volumes:
- ./config/keepalived/keepalived_backup.conf:/etc/keepalived/keepalived.conf:ro
- ./config/keepalived/check_nginx.sh:/etc/keepalived/check_nginx.sh:ro
- ./config/keepalived/notify.sh:/etc/keepalived/notify.sh:ro
restart: unless-stopped
php:
image: nginx:alpine
container_name: php
networks:
backend-net:
ipv4_address: 172.20.2.12
volumes:
- ./config/php/php-node2.conf:/etc/nginx/nginx.conf:ro
- web-data:/usr/share/nginx/html
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/index.php > /dev/null 2>&1 || exit 1"]
interval: 10s
timeout: 5s
retries: 3
php-fpm:
build: ./config/php-fpm
container_name: php-fpm
networks:
backend-net:
ipv4_address: 172.20.2.22
volumes:
- ./config/php/php.ini:/usr/local/etc/php/conf.d/custom.ini:ro
- web-data:/var/www/html
restart: unless-stopped
healthcheck:
test: ["CMD", "php-fpm", "-t"]
interval: 10s
timeout: 5s
retries: 3
redis-slave:
image: redis:7-alpine
container_name: redis-slave
networks:
cache-net:
ipv4_address: 172.20.3.12
command: redis-server /etc/redis/redis.conf --replicaof 172.20.3.11 6379
volumes:
- redis-slave-data:/data
- ./config/redis/redis-slave.conf:/etc/redis/redis.conf:ro
environment:
- REDIS_PASSWORD=YourStr0ng!Pass
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "-a", "YourStr0ng!Pass", "ping"]
interval: 10s
timeout: 5s
retries: 3
sentinel-02:
image: redis:7-alpine
container_name: sentinel-02
networks:
cache-net:
ipv4_address: 172.20.3.32
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./config/redis/sentinel.conf:/etc/redis/sentinel.conf
depends_on:
- redis-slave
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "-p", "26379", "ping"]
interval: 10s
timeout: 5s
retries: 3
mysql-02:
image: mysql:8.0
container_name: mysql-02
hostname: mysql-node2
networks:
database-net:
ipv4_address: 172.20.4.12
volumes:
- mysql-02-data:/var/lib/mysql
- ./config/mysql/my-node2.cnf:/etc/mysql/conf.d/my.cnf:ro
environment:
- MYSQL_ROOT_PASSWORD=YourStr0ng!Pass
- MYSQL_DATABASE=app_db
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "-uroot", "-pYourStr0ng!Pass", "ping", "-h", "127.0.0.1"]
interval: 15s
timeout: 10s
retries: 5
networks:
frontend-net:
driver: macvlan
driver_opts:
parent: ens33
ipam:
config:
- subnet: 172.20.1.0/24
gateway: 172.20.1.1
backend-net:
driver: macvlan
driver_opts:
parent: ens33
ipam:
config:
- subnet: 172.20.2.0/24
gateway: 172.20.2.1
cache-net:
driver: macvlan
driver_opts:
parent: ens33
ipam:
config:
- subnet: 172.20.3.0/24
gateway: 172.20.3.1
database-net:
driver: macvlan
driver_opts:
parent: ens33
ipam:
config:
- subnet: 172.20.4.0/24
gateway: 172.20.4.1
volumes:
web-data:
redis-slave-data:
mysql-02-data:
EOF
# 4. 启动服务
echo "[4/6] 启动Node2服务..."
cd /opt/cluster-deploy
docker compose -f docker-compose-node2.yml up -d
# 5. 等待Redis Slave同步
echo "[5/6] 等待Redis Slave同步..."
sleep 10
# 6. 验证
echo "[6/6] 验证Node2服务..."
docker ps --filter "name=nginx-lb" --filter "name=keepalived" --filter "name=php" --filter "name=redis" --filter "name=sentinel" --filter "name=mysql"
echo ""
echo "Node2部署完成!"
echo ""
echo "下一步:"
echo "1. 在Node3执行 node3-deploy.sh"
echo "2. 等待MySQL就绪后执行MGR初始化"
Node3部署脚本
将以下脚本保存到Node3(192.168.64.130)执行:
#!/bin/bash
# Node3部署脚本
set -e
echo "============================================"
echo " Node3 部署脚本 (192.168.64.130)"
echo "============================================"
# 1. 创建目录结构
echo "[1/6] 创建目录结构..."
mkdir -p /opt/cluster-deploy/{config/{nginx-lb/conf.d,keepalived,php,redis,mysql},scripts}
# 2. 复制配置文件(从共享位置或手动复制)
echo "[2/6] 请先从Node1复制配置文件到 /opt/cluster-deploy/config/"
echo "完成后按Enter继续..."
read
# 3. 创建docker-compose-node3.yml
echo "[3/6] 创建docker-compose-node3.yml..."
cat > /opt/cluster-deploy/docker-compose-node3.yml << 'EOF'
services:
nginx-lb:
image: nginx:alpine
container_name: nginx-lb
networks:
frontend-net:
ipv4_address: 172.20.1.13
backend-net:
ipv4_address: 172.20.2.102
volumes:
- ./config/nginx-lb/nginx.conf:/etc/nginx/nginx.conf:ro
- ./config/nginx-lb/conf.d:/etc/nginx/conf.d:ro
- ./config/nginx-lb/ssl.conf:/etc/nginx/ssl.conf:ro
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/health > /dev/null 2>&1 || exit 1"]
interval: 10s
timeout: 5s
retries: 3
keepalived:
image: ednxzu/keepalived:2.3.4
container_name: keepalived
network_mode: service:nginx-lb
privileged: true
entrypoint: ["/usr/sbin/keepalived", "-f", "/etc/keepalived/keepalived.conf", "--dont-fork", "--log-console"]
volumes:
- ./config/keepalived/keepalived_backup2.conf:/etc/keepalived/keepalived.conf:ro
- ./config/keepalived/check_nginx.sh:/etc/keepalived/check_nginx.sh:ro
- ./config/keepalived/notify.sh:/etc/keepalived/notify.sh:ro
restart: unless-stopped
php:
image: nginx:alpine
container_name: php
networks:
backend-net:
ipv4_address: 172.20.2.13
volumes:
- ./config/php/php-node3.conf:/etc/nginx/nginx.conf:ro
- web-data:/usr/share/nginx/html
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/index.php > /dev/null 2>&1 || exit 1"]
interval: 10s
timeout: 5s
retries: 3
php-fpm:
build: ./config/php-fpm
container_name: php-fpm
networks:
backend-net:
ipv4_address: 172.20.2.23
volumes:
- ./config/php/php.ini:/usr/local/etc/php/conf.d/custom.ini:ro
- web-data:/var/www/html
restart: unless-stopped
healthcheck:
test: ["CMD", "php-fpm", "-t"]
interval: 10s
timeout: 5s
retries: 3
redis-slave:
image: redis:7-alpine
container_name: redis-slave
networks:
cache-net:
ipv4_address: 172.20.3.13
command: redis-server /etc/redis/redis.conf --replicaof 172.20.3.11 6379
volumes:
- redis-slave-data:/data
- ./config/redis/redis-slave.conf:/etc/redis/redis.conf:ro
environment:
- REDIS_PASSWORD=YourStr0ng!Pass
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "-a", "YourStr0ng!Pass", "ping"]
interval: 10s
timeout: 5s
retries: 3
sentinel-03:
image: redis:7-alpine
container_name: sentinel-03
networks:
cache-net:
ipv4_address: 172.20.3.33
command: redis-sentinel /etc/redis/sentinel.conf
volumes:
- ./config/redis/sentinel.conf:/etc/redis/sentinel.conf
depends_on:
- redis-slave
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "-p", "26379", "ping"]
interval: 10s
timeout: 5s
retries: 3
mysql-03:
image: mysql:8.0
container_name: mysql-03
hostname: mysql-node3
networks:
database-net:
ipv4_address: 172.20.4.13
volumes:
- mysql-03-data:/var/lib/mysql
- ./config/mysql/my-node3.cnf:/etc/mysql/conf.d/my.cnf:ro
environment:
- MYSQL_ROOT_PASSWORD=YourStr0ng!Pass
- MYSQL_DATABASE=app_db
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "-uroot", "-pYourStr0ng!Pass", "ping", "-h", "127.0.0.1"]
interval: 15s
timeout: 10s
retries: 5
backup-server:
image: alpine:latest
container_name: backup-server
networks:
manage-net:
ipv4_address: 172.20.5.13
volumes:
- ./scripts/backup.sh:/backup.sh:ro
- mysql-backup:/backup/mysql
command: ["tail", "-f", "/dev/null"]
restart: unless-stopped
networks:
frontend-net:
driver: macvlan
driver_opts:
parent: ens33
ipam:
config:
- subnet: 172.20.1.0/24
gateway: 172.20.1.1
backend-net:
driver: macvlan
driver_opts:
parent: ens33
ipam:
config:
- subnet: 172.20.2.0/24
gateway: 172.20.2.1
cache-net:
driver: macvlan
driver_opts:
parent: ens33
ipam:
config:
- subnet: 172.20.3.0/24
gateway: 172.20.3.1
database-net:
driver: macvlan
driver_opts:
parent: ens33
ipam:
config:
- subnet: 172.20.4.0/24
gateway: 172.20.4.1
manage-net:
driver: macvlan
driver_opts:
parent: ens33
ipam:
config:
- subnet: 172.20.5.0/24
gateway: 172.20.5.1
volumes:
web-data:
redis-slave-data:
mysql-03-data:
mysql-backup:
EOF
# 4. 启动服务
echo "[4/6] 启动Node3服务..."
cd /opt/cluster-deploy
docker compose -f docker-compose-node3.yml up -d
# 5. 等待Redis Slave同步
echo "[5/6] 等待Redis Slave同步..."
sleep 10
# 6. 验证
echo "[6/6] 验证Node3服务..."
docker ps --filter "name=nginx-lb" --filter "name=keepalived" --filter "name=php" --filter "name=redis" --filter "name=sentinel" --filter "name=mysql" --filter "name=backup"
echo ""
echo "Node3部署完成!"
echo ""
echo "下一步:执行MySQL MGR初始化"
MySQL MGR初始化脚本
在Node1上执行(在所有MySQL容器就绪后):
#!/bin/bash
# MySQL MGR初始化脚本
set -e
MYSQL_ROOT="root"
MYSQL_PASS="YourStr0ng!Pass"
REPL_USER="repl_user"
REPL_PASS="YourStr0ng!Pass"
echo "============================================"
echo " MySQL MGR 初始化脚本"
echo "============================================"
echo ""
echo "[1/5] 等待所有MySQL容器就绪..."
for container in mysql-01 mysql-02 mysql-03; do
echo "Waiting for $container..."
until docker exec $container mysqladmin -u$MYSQL_ROOT -p"$MYSQL_PASS" ping -h127.0.0.1 &>/dev/null; do
sleep 2
done
echo "✓ $container is ready"
done
echo ""
echo "[2/5] 创建复制用户..."
docker exec mysql-01 mysql -u$MYSQL_ROOT -p"$MYSQL_PASS" -h127.0.0.1 << EOF
CREATE USER IF NOT EXISTS '$REPL_USER'@'%' IDENTIFIED WITH mysql_native_password BY '$REPL_PASS';
GRANT BACKUP_ADMIN ON *.* TO '$REPL_USER'@'%';
GRANT REPLICATION SLAVE ON *.* TO '$REPL_USER'@'%';
GRANT GROUP_REPLICATION_STREAM ON *.* TO '$REPL_USER'@'%';
FLUSH PRIVILEGES;
EOF
echo "✓ 复制用户创建完成"
echo ""
echo "[3/5] 启动Node1 MGR..."
docker exec mysql-01 mysql -u$MYSQL_ROOT -p"$MYSQL_PASS" -h127.0.0.1 << EOF
CHANGE MASTER TO MASTER_USER='$REPL_USER', MASTER_PASSWORD='$REPL_PASS' FOR CHANNEL 'group_replication_recovery';
START GROUP_REPLICATION;
EOF
sleep 5
echo "✓ Node1 MGR启动"
echo ""
echo "[4/5] 添加Node2到MGR..."
docker exec mysql-02 mysql -u$MYSQL_ROOT -p"$MYSQL_PASS" -h127.0.0.1 << EOF
CHANGE MASTER TO MASTER_USER='$REPL_USER', MASTER_PASSWORD='$REPL_PASS' FOR CHANNEL 'group_replication_recovery';
START GROUP_REPLICATION;
EOF
sleep 5
echo "✓ Node2已加入"
echo ""
echo "[5/5] 添加Node3到MGR..."
docker exec mysql-03 mysql -u$MYSQL_ROOT -p"$MYSQL_PASS" -h127.0.0.1 << EOF
CHANGE MASTER TO MASTER_USER='$REPL_USER', MASTER_PASSWORD='$REPL_PASS' FOR CHANNEL 'group_replication_recovery';
START GROUP_REPLICATION;
EOF
sleep 5
echo "✓ Node3已加入"
echo ""
echo "============================================"
echo "验证MGR状态..."
echo "============================================"
docker exec mysql-01 mysql -u$MYSQL_ROOT -p"$MYSQL_PASS" -h127.0.0.1 -e "
SELECT
member_host,
member_port,
member_role,
member_state
FROM performance_schema.replication_group_members
WHERE channel_name = 'group_replication_applier';
"
echo ""
echo "============================================"
echo " MGR初始化完成!"
echo "============================================"
常见问题排查
问题1:容器启动失败
# 查看容器日志
docker logs <container_name>
# 常见原因:
# 1. 配置文件未创建(挂载成了目录)
# 2. 网络冲突
# 3. 端口被占用
问题2:网络创建失败
# 检查网卡名称
ip link show
# 如果网卡名称不是ens33,修改配置
sed -i 's/ens33/你的网卡名/g' /opt/cluster-deploy/docker-compose-*.yml
问题3:VIP无法绑定
# 检查Keepalived日志
docker logs keepalived
# 检查网卡状态
ip link show ens33
下一步
-
12-验证测试.md - 完整验证
-
14-运维手册.md - 日常运维