Chuyển các site trên minion từ pool PHP-FPM www dùng chung sang mỗi site một pool riêng là bước nên làm để chấm dứt lỗi max_children. Lý do và cách làm có trong bài Mỗi website một PHP-FPM pool: chấm dứt lỗi max_children. Tài liệu này nói về phần dễ bị bỏ qua: sau khi migrate PHP-FPM, thường lộ ra ba lỗi về đường dẫn log, giới hạn upload và múi giờ. Điểm chung của cả ba: php-fpm -t, nginx -t và cả health check HTTP đều báo xanh. Không lỗi nào làm site sập, nhưng cả ba đều làm site chạy sai một cách im lặng, và thường lộ ra lần lượt trong vài ngày đầu.
Bối cảnh: migrate PHP-FPM bằng script tự động
Việc tách pool thường được tự động hoá: với mỗi site trên minion, script sinh một file pool mới từ một danh sách directive, đổi fastcgi_pass của nginx sang socket riêng, kiểm tra cú pháp, reload PHP-FPM rồi gọi thử trang chủ. Cách làm này nhanh và lặp lại được, nhưng chất lượng của pool mới phụ thuộc hoàn toàn vào danh sách directive mà người viết script nghĩ tới.
Triệu chứng sau migrate PHP-FPM
- Log lỗi PHP nằm sai chỗ. Pool mới ghi lỗi PHP vào chính file error log mà nginx dùng cho site. Hai nguồn log trộn trong một file, và việc PHP có ghi được hay không phụ thuộc quyền của thư mục
/var/log/nginx/, vốn thuộcroot:adm. - Upload thất bại im lặng. File lớn hơn 2 MB không lên được, dù nginx trên balancer và minion đều cho phép body lớn hơn nhiều. Không có 413, không có 5xx.
- Giờ lệch và script bị cắt sớm. Site chạy theo giờ UTC, lệch 7 tiếng so với giờ Việt Nam. Script nặng vốn chạy được trong 60 giây nay có thể báo hết giờ ở mốc 30 giây.
Điều tra lỗi sau migrate PHP-FPM
Vì sao health check không bắt được lỗi sau migrate PHP-FPM
Health check điển hình gọi trang chủ từng site qua IP minion, kèm header Host, và coi mọi mã 5xx hoặc timeout là lỗi. Nó bắt tốt các lỗi về user chạy pool hay chủ socket, vì khi đó trang chủ trả 502. Ba lỗi sau migrate PHP-FPM thì khác: trang chủ vẫn trả 200. Log ghi sai chỗ không đổi mã trả về. Giới hạn upload chỉ có tác dụng khi có người upload. Múi giờ chỉ lộ ra khi có người đọc giờ trên trang. Health check chỉ trả lời "site có sống không", không trả lời "site có chạy như trước không".
So sánh giá trị, không so sánh file
Câu hỏi đúng sau một lần migrate PHP-FPM là: pool mới khác pool cũ ở directive nào? Lệnh dưới đây liệt kê mọi dòng php_value, php_admin_value của hai file cạnh nhau:
POOLD=/etc/php/7.4/fpm/pool.d
grep -hE '^\s*php_(admin_)?(value|flag)' $POOLD/www.conf | sort > /tmp/www.txt
grep -hE '^\s*php_(admin_)?(value|flag)' $POOLD/site-a-example.conf | sort > /tmp/site-a.txt
diff /tmp/www.txt /tmp/site-a.txtKết quả hay gặp: www.conf có php_admin_value[date.timezone] = Asia/Ho_Chi_Minh và php_admin_value[max_execution_time] = 60, còn pool mới không có. Pool mới cũng không khai upload_max_filesize và post_max_size.
Mỗi pool là một thế giới riêng
Theo tài liệu cấu hình PHP-FPM, mỗi khối [pool] độc lập: directive nào pool không khai thì lấy theo php.ini, không lấy theo pool khác. Khi migrate PHP-FPM, tức tách site ra khỏi www.conf, mọi giá trị từng được đặt riêng trong www.conf rơi về mặc định của php.ini:
| Directive | Trong www.conf | Pool mới thiếu khai báo | Hệ quả |
|---|---|---|---|
date.timezone | Asia/Ho_Chi_Minh | PHP dùng UTC | Lệch 7 tiếng |
max_execution_time | 60 | Mặc định 30 | Script nặng bị dừng sớm hơn |
upload_max_filesize / post_max_size | Giá trị riêng của site | Mặc định 2M / 8M | Upload lớn hơn 2 MB thất bại |
Có hai chi tiết làm mức ảnh hưởng khó đoán. Thứ nhất, site Laravel tự đặt múi giờ theo config('app.timezone') khi khởi động, nên lệch giờ chủ yếu ảnh hưởng site PHP thuần và những đoạn code dựa vào múi giờ mặc định của PHP. Thứ hai, theo tài liệu PHP về max_execution_time, trên Linux thời gian này không tính thời gian chờ truy vấn database, gọi mạng hay gọi hệ thống. Script chậm vì chờ I/O có thể không chạm giới hạn 30 giây, còn script nặng về CPU thì có.
Đường dẫn log: lỗi của chính bản thiết kế
Lỗi đường dẫn log không đến từ php.ini, mà từ lựa chọn ghi lỗi PHP vào /var/log/nginx/<domain>.error. Tiến trình PHP-FPM chạy bằng user của pool, trong khi thư mục log của nginx thuộc root:adm. User đó có ghi được hay không tuỳ quyền của từng file, và khi file bị xoay vòng thì quyền có thể đổi. Thêm vào đó, đọc một file trộn lẫn log của nginx và PHP làm việc điều tra chậm đi đúng lúc cần nhanh.
Nguyên nhân gốc
- Pool mới được sinh từ danh sách cứng, không kế thừa tường minh từ
www.conf. Mọi giá trị riêng củawww.confkhông có trong danh sách đều bị đánh rơi khi migrate PHP-FPM. - Health check chỉ kiểm tra "sống hay chết" ở trang chủ. Các thay đổi hành vi (upload, giờ, log) không đổi mã trả về nên lọt qua mọi bước kiểm tra.
- Không có bước so sánh cấu hình hiệu lực giữa trước và sau khi tách, nên lỗi chỉ lộ ra khi người dùng gặp phải.
Khắc phục
Pool mẫu đầy đủ khi migrate PHP-FPM
Pool mới nên được sinh với đủ các dòng sau. Lưu ý post_max_size phải lớn hơn hoặc bằng upload_max_filesize, theo tài liệu php.ini. Đặt ngược thì file nằm giữa hai giá trị vẫn không lên được:
request_terminate_timeout = 60s
php_admin_value[memory_limit] = 256M
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 72M
php_admin_value[error_log] = /var/log/php/site-a.example.error
php_admin_value[max_execution_time] = 60
php_admin_value[date.timezone] = Asia/Ho_Chi_MinhVá ngược cho pool đã migrate PHP-FPM trước đó
Với pool đã tách trước khi có bản vá, chỉ thêm dòng nào còn thiếu, không ghi đè giá trị mà một site đã tự chỉnh riêng:
POOL=/etc/php/7.4/fpm/pool.d/site-a-example.conf
grep -qE '^[;[:space:]]*php_admin_value\[date\.timezone\]' "$POOL" \
|| echo 'php_admin_value[date.timezone] = Asia/Ho_Chi_Minh' >> "$POOL"
grep -qE '^[;[:space:]]*php_admin_value\[max_execution_time\]' "$POOL" \
|| echo 'php_admin_value[max_execution_time] = 60' >> "$POOL"
# thư mục log riêng, thuộc đúng user chạy PHP-FPM
mkdir -p /var/log/php && chown www-data:www-data /var/log/php && chmod 755 /var/log/php
# pool nào vẫn thiếu giới hạn upload?
grep -L 'upload_max_filesize' /etc/php/*/fpm/pool.d/*.conf | grep -v www.conf
php-fpm7.4 -t && systemctl reload php7.4-fpmĐồng thời sửa luôn quy trình tạo site mới theo mô hình pool riêng, để site tạo sau không lặp lại ba lỗi này.
Cải tiến quy trình migrate PHP-FPM
- Kế thừa thay vì liệt kê. Khi migrate PHP-FPM, chép mọi dòng
php_value,php_admin_valuecủawww.confsang pool mới trước, rồi mới ghi đè những giá trị cần đổi. Danh sách cứng sẽ luôn thiếu thứ mà người viết script không biết là có. - Thống nhất chuỗi timeout. Thường có ba lớp:
max_execution_timevàrequest_terminate_timeoutở PHP-FPM,fastcgi_read_timeoutở nginx của minion, và timeout của balancer. Lớp trong cùng quyết định, các lớp ngoài chỉ nên dài hơn một chút. - Health check theo chức năng. Ngoài trang chủ, gọi thêm một endpoint kiểm tra trả về
date_default_timezone_get()và các giới hạnini_get()chính của pool, so với giá trị mong đợi.
<?php // health-ini.php, chỉ cho phép gọi từ mạng nội bộ
header('Content-Type: application/json');
echo json_encode([
'tz' => date_default_timezone_get(),
'upload' => ini_get('upload_max_filesize'),
'post' => ini_get('post_max_size'),
'met' => ini_get('max_execution_time'),
]);Checklist cho lần migrate PHP-FPM sau
- Trước khi migrate PHP-FPM: liệt kê mọi directive đang khác mặc định trong
www.confvàphp.inicủa minion. - Sau khi tách: chạy lại phép so sánh cho từng pool, kết quả phải rỗng hoặc chỉ gồm những khác biệt có chủ đích.
- Thử một lần upload file lớn và một lần in giờ hiện tại trên một site mẫu của mỗi minion.
- Đặt log của PHP và log của nginx ở hai thư mục riêng, thuộc đúng user ghi chúng.
- Migrate PHP-FPM theo từng minion, ghi ngày tách vào sổ thay đổi, để khi có báo lỗi biết ngay site nào vừa đổi cấu hình.
- Sau khi tách, theo dõi log
reached pm.max_childrenđể xác nhận mục tiêu chính của đợt chuyển đổi đã đạt.
Bài học
- Đổi cấu trúc cấu hình là đổi giá trị mặc định. Migrate PHP-FPM là ví dụ điển hình: tách một khối cấu hình ra không chỉ đổi chỗ nó nằm, mà còn đổi nơi nó lấy giá trị khi thiếu.
- "Xanh" chỉ nghĩa là đã kiểm tra những gì mình nghĩ tới. Health check HTTP bắt được lỗi sống chết, không bắt được lỗi hành vi.
- Vá thì phải vá ngược. Bản vá trong script chỉ có ích cho pool tạo sau đó. Bước bổ sung cho pool cũ mới thực sự đóng lỗi trên những site đã migrate PHP-FPM.
- Lỗi lộ ra lần lượt là dấu hiệu của một lỗ hổng quy trình, không phải nhiều sự cố riêng lẻ. Ở đây, lỗ hổng đó là thiếu bước so sánh trước và sau.
Bài tiếp theo
Right-sizing Kubernetes: đặt request CPU, RAM theo mức dùng thật. Chuyển sang cụm Kubernetes của Website AI: khi tổng request lớn gấp nhiều lần mức dùng thật, và mỗi báo cáo cho ra một bộ số khác nhau.
