Docker容器化高可用架构部署方案(十四)

13-快速部署

本文档提供完整的一键部署脚本,可在各节点直接执行,快速完成整个集群的部署。

部署概述

  • Node1:Master节点,运行所有7个容器

  • Node2:Backup节点,运行所有6个容器(无mysql-01的init.sql)

  • Node3:Backup节点,运行所有7个容器(含backup-server)

前置条件

在开始部署前,确保:

  1. 所有节点已安装Docker和Docker Compose

    复制代码
    docker --version
    docker compose version
  2. 所有节点已创建5个macvlan网络

    复制代码
    docker network ls | grep -E "frontend|backend|cache|database|manage"
  3. 已配置内核参数

    复制代码
    # 检查macvlan模块
    lsmod | grep macvlan
  4. 网络互通

    复制代码
    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

下一步

相关推荐
2601_957786779 小时前
多平台矩阵系统的反脆弱架构:如何用技术解耦对抗平台规则的不确定性
人工智能·矩阵·架构·平台解耦
日取其半万世不竭9 小时前
OpenCost:Kubernetes 成本监控,开源的云资源费用分析
容器·kubernetes·开源
虎冯河9 小时前
Nano Banana Pro生图逻辑详解—— 从底层架构到实践指南
架构·aigc
万里侯10 小时前
Ansible自动化运维实战:从入门到生产级应用
微服务·容器·k8s
Cat_Rocky10 小时前
k8s zabbix7学习-设置告警
学习·容器·kubernetes
啷里格啷10 小时前
第三章 Fast-DDS核心源码导读与流程拆解-Discovery机制
后端·架构
什么半岛铁盒10 小时前
LangChain 入门与架构:快速搭建你的第一个 AI 应用
人工智能·架构·langchain
mirror_zAI10 小时前
C++ 仿 QQ 聊天室项目:Qt 客户端 + epoll 服务端 + Reactor 架构(含源码)
c++·qt·架构
啷里格啷10 小时前
第三章 Fast-DDS核心源码导读与流程拆解
后端·架构