Khi chỉ có vài website, mỗi site một máy chủ là cách đơn giản nhất. Khi số site lên hàng trăm, phần lớn là PHP/Laravel và mỗi site cần một phiên bản PHP khác nhau, cách đó vừa tốn kém vừa khó quản lý. Tài liệu này mô tả cách tổ chức Cloud VPS theo mô hình ba tầng Balancer – Minion – Database: vai trò của từng tầng, cấu hình cốt lõi, các lỗi hay gặp khi vận hành và những đánh đổi mà mô hình này chấp nhận.

1. Khi nào cần mô hình ba tầng trên Cloud VPS

Mô hình phù hợp khi bài toán có các đặc điểm sau:

  • Nhiều website của nhiều khách hàng, đa số Laravel, một phần PHP thuần hoặc Node.js.
  • Nhiều thế hệ code cùng tồn tại: có site chỉ chạy được trên PHP 7.2, có site đã lên PHP 7.4 hoặc 8.x.
  • Mỗi site cần SSL, chặn được bot và request xấu, có backup database hằng ngày.
  • Ngân sách có hạn, chủ yếu dùng Cloud VPS cấu hình nhỏ (vài vCPU, vài GB RAM).

Mục tiêu là gom nhiều site lên ít Cloud VPS, nhưng vẫn tách được những thứ không nên dùng chung: cổng ra Internet, phiên bản PHP và dữ liệu.

2. Kiến trúc tổng quan: ba tầng, ba vai trò

                Internet
                   │  HTTPS :443
                   ▼
   ┌───────────────────────────────────┐
   │ BALANCER (có IP public)           │  SSL, chặn request xấu, rate limit,
   │ nginx reverse proxy               │  định tuyến domain → minion
   └───────────────┬───────────────────┘
                   │  HTTP :80 qua mạng private
                   ▼
   ┌───────────────────────────────────┐
   │ MINION (chỉ IP private)           │  nginx + PHP-FPM theo phiên bản,
   │ mỗi nhóm minion một phiên bản PHP │  code ở /var/www/<domain>,
   └───────────────┬───────────────────┘  supervisor cho queue worker
                   │  TCP 3306 qua mạng private
                   ▼
   ┌───────────────────────────────────┐
   │ DATABASE (chỉ IP private)         │  MySQL, mỗi máy chứa nhiều schema
   └───────────────────────────────────┘
        + máy vận hành (jump host): client MySQL, dump/backup, công cụ

Nguyên tắc quan trọng nhất: trong cả cụm Cloud VPS, chỉ balancer có IP public. Minion và database chỉ có IP private, nên từ Internet không thể chạm thẳng vào PHP-FPM hay cổng 3306.

Nên đặt tên máy theo vai trò, ví dụ theo mẫu <vai-trò>-<phiên-bản>-<số> như lb-1, app-php74-1, db-mysql80-1. Nhìn tên là biết Cloud VPS đó làm gì và chạy phiên bản nào. Các tên này được khai báo trong DNS nội bộ để cấu hình và script gọi máy bằng tên thay vì IP. Bài sau trình bày chi tiết phần này.

3. Tầng Balancer: cổng vào duy nhất

Balancer là một Cloud VPS chạy nginx ở vai trò reverse proxy (module ngx_http_proxy). Mỗi domain có một file riêng trong /etc/nginx/conf.d/<domain>.conf, nên thêm, sửa hay gỡ một site chỉ đụng tới đúng một file. Cấu hình mẫu (đã lược bớt):

upstream example_com {
    server 10.0.0.21:80;          # minion đang chứa site
}

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    include snippets/security-blocking.conf;   # chặn đường dẫn nhạy cảm, bot xấu

    location / {
        limit_req zone=dynamic burst=50;

        proxy_pass http://example_com;
        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_read_timeout 300;
    }
}

Balancer đảm nhận bốn việc:

  1. SSL termination: chứng chỉ Let's Encrypt cấp và gia hạn tự động bằng certbot ngay trên balancer. Minion phía sau chỉ cần chạy HTTP trong mạng private.
  2. Chặn sớm: snippet dùng chung trả 403 cho các đường dẫn như .env, .git, phpmyadmin, wp-login và một số User-Agent xấu. Request rác bị chặn từ đây, không tốn worker PHP nào.
  3. Rate limit: limit_req giới hạn số request mỗi IP, để một client hung hăng không làm nghẽn cả minion.
  4. Định tuyến: domain nào đi về minion nào. Khi chuyển site sang Cloud VPS khác, chỉ cần sửa dòng server trong khối upstream rồi chạy nginx -t && systemctl reload nginx.

Lưu ý về tên gọi: trong mô hình này, mỗi khối upstream thường chỉ có một minion. Cái tên "balancer" mô tả vị trí hơn là chức năng: nó là cổng vào, chưa phải bộ cân bằng tải giữa nhiều máy. Một site chạy trên đúng một minion, minion đó gặp sự cố thì site đó ngừng. Đây là đánh đổi có chủ đích để giữ mô hình đơn giản.

4. Tầng Minion: nơi code chạy

Các Cloud VPS ở tầng minion được chia theo phiên bản PHP: một nhóm chứa site cũ cần PHP 7.2, một nhóm chứa site PHP 7.4, một nhóm cho PHP 8.x. Cách chia này giúp:

  • Không phải cài, vá và theo dõi nhiều phiên bản PHP trên cùng một Cloud VPS.
  • Nâng cấp PHP cho một site chỉ cần chuyển site đó sang nhóm minion mới, không ảnh hưởng các site còn lại.

Mỗi minion chạy nginx riêng, chỉ lắng nghe trên IP private, nhận request từ balancer và chuyển cho PHP-FPM qua unix socket:

server {
    listen 10.0.0.21:80;
    server_name example.com www.example.com;
    root /var/www/example.com/public;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param HTTPS on;          # SSL đã kết thúc ở balancer
        fastcgi_read_timeout 180;
    }
}

Dòng fastcgi_param HTTPS on nhỏ nhưng quan trọng. Request tới minion là HTTP thuần, nếu thiếu dòng này Laravel sẽ sinh link http:// và có thể rơi vào vòng redirect vô hạn.

Quy ước thư mục trên Cloud VPS minion

Nên thống nhất một đường dẫn cho mọi site, ví dụ /var/www/<domain>/, do user chạy web sở hữu. Trong thư mục đó là repo Laravel đầy đủ, gồm .env, artisan và public/. Khi mọi Cloud VPS cùng một quy ước, các script vận hành (deploy, backup, đọc log) chỉ cần biết tên domain là đủ.

Queue worker: mỗi site tự mang cấu hình supervisor

Laravel cần queue:work chạy nền. Thay vì sửa file supervisor chung mỗi lần thêm site, có thể cấu hình supervisor đọc thêm file supervisord.ini nằm ngay trong thư mục từng site:

# /etc/supervisor/supervisord.conf
[include]
files = /etc/supervisor/conf.d/*.conf /var/www/*/supervisord.ini
# /var/www/example.com/supervisord.ini
[program:example-com-queue]
command=php /var/www/example.com/artisan queue:work --sleep=2 --tries=2 --timeout=300
user=www-data
numprocs=1
autostart=true
autorestart=true
stdout_logfile=/var/www/example.com/storage/logs/queue.log

Cấu hình worker được deploy cùng code. Gỡ site thì xoá thư mục, worker mất theo sau lần supervisorctl reread && supervisorctl update kế tiếp.

5. Tầng Database: tách dữ liệu khỏi code

MySQL chạy trên các Cloud VPS riêng, mỗi máy chứa nhiều schema của nhiều site. Tách database khỏi minion mang lại ba lợi ích:

  • Tài nguyên không giành nhau: PHP-FPM tăng đột biến không làm MySQL bị OOM, và ngược lại.
  • Chuyển minion dễ: đổi site sang minion khác không cần đụng tới dữ liệu, chỉ cần DB_HOST trong .env vẫn trỏ đúng máy database.
  • Backup tập trung: dump theo máy database, không phải đi từng minion.

Trên Cloud VPS cấu hình nhỏ, cấu hình MySQL thường phải tiết kiệm, và mỗi lựa chọn đều có cái giá:

Lựa chọnĐượcMất
Không bật binlog (skip-log-bin)Tiết kiệm I/O và dung lượng đĩaKhông có replica, không khôi phục được về một thời điểm bất kỳ (point-in-time recovery). Backup chỉ là bản dump định kỳ
innodb_buffer_pool_size nhỏ trên máy RAM thấpChạy được trên VPS vài GB RAMDữ liệu lớn hơn buffer pool thì đọc đĩa nhiều, query chậm
Nhiều schema trên một máyÍt máy, dễ quản lýMột site có query tệ có thể kéo chậm cả máy

6. Máy vận hành: không SSH thẳng vào database

Thao tác với database (truy vấn kiểm tra, dump, import) nên đi qua một Cloud VPS vận hành riêng (jump host), cài sẵn MySQL client và công cụ đẩy file lên object storage. Thông tin đăng nhập từng máy database được lưu bằng mysql_config_editor, nên không cần gõ mật khẩu trên dòng lệnh và mật khẩu không lọt vào lịch sử shell:

# lưu thông tin đăng nhập một lần (được mã hoá trong ~/.mylogin.cnf)
mysql_config_editor set --login-path=db-1 \
    --host=db-1.internal.example --user=admin --password

# từ đó về sau
mysql --login-path=db-1 -e "SHOW DATABASES;"
mysqldump --login-path=db-1 --single-transaction ten_db | gzip > ten_db.sql.gz

7. Lỗi thường gặp và cách xử lý

Triệu chứngNguyên nhân hay gặpCách xử lý
Redirect vô hạn, link sinh ra là http://Minion không biết request gốc là HTTPSThêm fastcgi_param HTTPS on, cấu hình TrustProxies trong Laravel
502 Bad Gateway ở balancerCloud VPS minion tắt, nginx minion chưa lắng nghe đúng IP, PHP-FPM chếtTừ balancer curl -H "Host: example.com" http://10.0.0.21/ để tách lỗi theo tầng
504 Gateway TimeoutRequest PHP chạy lâu hơn proxy_read_timeout hoặc fastcgi_read_timeoutTìm query chậm; chỉ nới timeout khi tác vụ thật sự cần
Một site chậm kéo cả minionBot cào hoặc query nặng dùng hết worker PHP-FPMRate limit ở balancer, tách pool PHP-FPM theo site, xem slow log MySQL
Queue không chạy sau khi thêm siteChưa nạp file supervisord.ini mớisupervisorctl reread && supervisorctl update

8. Ưu, nhược điểm và checklist cho cụm Cloud VPS ba tầng

Ưu điểmNhược điểm
Đơn giản: toàn công cụ quen thuộc (nginx, PHP-FPM, MySQL, supervisor)Mỗi site chạy trên đúng một minion, không có failover tự động
Rẻ: nhiều site trên ít Cloud VPS nhỏCác site cùng minion dùng chung tài nguyên
Nhiều phiên bản PHP sống chung mà không xung độtThêm site, đổi domain, chuyển máy làm tay thì dễ sai, cần tự động hoá
Bề mặt tấn công nhỏ: chỉ balancer lộ ra InternetBalancer là điểm lỗi đơn của mọi site phía sau nó
Dễ debug: đi theo đúng ba tầng là tìm ra lỗiDatabase không replica, an toàn dữ liệu dựa hoàn toàn vào backup

Checklist khi dựng một cụm theo mô hình này:

  • Chỉ balancer có IP public; firewall chặn mọi cổng của minion và database từ Internet.
  • Tên từng Cloud VPS theo vai trò, khai báo trong DNS nội bộ.
  • Mỗi domain một file cấu hình trên balancer, luôn chạy nginx -t trước khi reload.
  • Minion chia theo phiên bản PHP, đường dẫn site thống nhất.
  • Database đặt trên Cloud VPS riêng, có backup định kỳ và đã thử restore.
  • Thao tác database đi qua máy vận hành, không để mật khẩu trên dòng lệnh.

Không có mô hình nào hoàn hảo. Cách tổ chức Cloud VPS này hợp với giai đoạn cần nhiều site, chi phí thấp và đội vận hành nhỏ. Các bài tiếp theo đi sâu từng tầng, kể cả cách xử lý khi các nhược điểm trên biến thành sự cố. Toàn bộ series được tập hợp tại chuyên mục blog kỹ thuật của Sudo.

Bài tiếp theo

DNS nội bộ: đặt tên và quản lý hàng chục máy chủ. Vì sao nên gọi máy bằng tên theo vai trò thay vì nhớ IP, và DNS nội bộ giúp gì cho mọi script vận hành phía sau.