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:
- 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.
- Chặn sớm: snippet dùng chung trả 403 cho các đường dẫn như
.env,.git,phpmyadmin,wp-loginvà một số User-Agent xấu. Request rác bị chặn từ đây, không tốn worker PHP nào. - Rate limit:
limit_reqgiới hạn số request mỗi IP, để một client hung hăng không làm nghẽn cả minion. - Đị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
servertrong khối upstream rồi chạynginx -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.logCấ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_HOSTtrong.envvẫ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 | Được | Mất |
|---|---|---|
Không bật binlog (skip-log-bin) | Tiết kiệm I/O và dung lượng đĩa | Khô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ấp | Chạy được trên VPS vài GB RAM | Dữ 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.gz7. Lỗi thường gặp và cách xử lý
| Triệu chứng | Nguyên nhân hay gặp | Cách xử lý |
|---|---|---|
Redirect vô hạn, link sinh ra là http:// | Minion không biết request gốc là HTTPS | Thêm fastcgi_param HTTPS on, cấu hình TrustProxies trong Laravel |
| 502 Bad Gateway ở balancer | Cloud VPS minion tắt, nginx minion chưa lắng nghe đúng IP, PHP-FPM chết | Từ balancer curl -H "Host: example.com" http://10.0.0.21/ để tách lỗi theo tầng |
| 504 Gateway Timeout | Request PHP chạy lâu hơn proxy_read_timeout hoặc fastcgi_read_timeout | Tì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ả minion | Bot cào hoặc query nặng dùng hết worker PHP-FPM | Rate limit ở balancer, tách pool PHP-FPM theo site, xem slow log MySQL |
| Queue không chạy sau khi thêm site | Chưa nạp file supervisord.ini mới | supervisorctl reread && supervisorctl update |
8. Ưu, nhược điểm và checklist cho cụm Cloud VPS ba tầng
| Ưu điểm | Nhượ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 đột | Thê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 Internet | Balancer 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ỗi | Database 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 -ttrướ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.
