Website Laravel chạy trên nhiều pod, và để các pod dùng chung một thư mục, người ta mount thư mục đó từ một máy NFS. Mọi thứ chạy tốt cho tới khi web bắt đầu treo từng đợt: pod không đầy RAM, database không đầy, dung lượng đĩa còn dư, nhưng trang không mở. Chỉ cần ngắt mount NFS khỏi pod là web ổn định lại, và đó là dấu hiệu cho thấy NFS là nghi phạm. Bài này mô tả cách chẩn đoán từng lớp khi webroot trên NFS bị treo: đo đĩa bằng fio và ioping, đo mạng bằng iperf3, đọc đúng kết quả, những lỗi hay gặp khi đo, phần giám sát mà cảnh báo "đầy đĩa" không bắt được, và khi nào nên bỏ NFS cho webroot.
Nền tảng về volume và ReadWriteMany có ở bài Volume, PersistentVolume, PVC và StorageClass, trong đó có mục về NFS tĩnh bằng PersistentVolume. Phần giám sát máy NFS bằng node_exporter dựa trên Giám sát Kubernetes với kube-prometheus và Grafana.
Triệu chứng của webroot trên NFS bị treo
- Web treo hoặc rất chậm theo từng đợt.
- Pod web, database và dung lượng đĩa NFS đều bình thường, nhưng webroot trên NFS vẫn chậm.
- Khi cho pod chạy không mount NFS, web ổn định.
Ba dấu hiệu này chỉ cho thấy webroot trên NFS là nghi phạm hợp lý, chưa phải kết luận. Phần "chưa chốt" ở cuối bài nói rõ chỗ nào cần kiểm chứng thêm.
Vì sao webroot trên NFS làm PHP chậm
Khi code nằm trên NFS, mỗi request PHP thường chạm hàng chục đến hàng trăm file nhỏ: index.php, các file autoload, view, config. Trên đĩa cục bộ, metadata (stat, open) rẻ. Qua NFS, mỗi thao tác metadata là một vòng đi qua mạng tới máy chủ. Với opcache.validate_timestamps=1, PHP vẫn stat lại file theo chu kỳ opcache.revalidate_freq (mặc định 2 giây), và trên NFS mỗi lần stat là một vòng mạng. Client NFS có cache thuộc tính (acregmin, acdirmin) nhưng mỗi lần open vẫn kiểm tra lại với máy chủ theo ngữ nghĩa close-to-open.
Tăng realpath_cache_size và chạy composer dump-autoload -o giúp giảm số file phải chạm. Khi nhiều pod cùng đọc, một máy NFS nhỏ trở thành điểm nghẽn chung cho cả site. Người ta vẫn dùng NFS vì PVC khối thường chỉ hỗ trợ ReadWriteOnce.
Đo đĩa của webroot trên NFS bằng fio và ioping
Với webroot trên NFS, hãy đo trực tiếp trên máy NFS (đĩa cục bộ) và từ trong pod trên thư mục đã mount. Lệnh mẫu cho đọc ngẫu nhiên khối 4k:
fio --name=fiotest --filename=/mnt/data/testfio --size=2G \
--bs=4k --iodepth=64 --ioengine=libaio --direct=1 \
--rw=randread --runtime=60 --time_based
ioping -c 20 /mnt/dataioping cho biết độ trễ một yêu cầu nhỏ, thường là chỉ số dễ hiểu nhất với web: trên đĩa cục bộ thường ở mức vài trăm micro giây, qua NFS cao hơn rõ rệt (thường từ vài trăm micro giây tới cỡ mili giây), vì mỗi yêu cầu đi qua mạng. Với fio, IOPS trong pod trên NFS thường thấp hơn đĩa cục bộ; hãy đọc kết quả theo khoảng vì mỗi lần chạy dao động.
Những lỗi hay gặp khi đo
- So sánh khác bản chất khi đo webroot trên NFS. Đo trên máy NFS là đo đĩa cục bộ; đo trong pod là đo qua mạng, qua client NFS. Hiệu số giữa hai kết quả gồm cả mạng và giao thức, không chỉ "đĩa chậm".
direct=1bỏ qua page cache. Với web, cache của kernel và opcache đóng vai trò lớn, nên số đo O_DIRECT thấp hơn thực tế đọc file lặp lại. Nó trả lời "đĩa tối đa bao nhiêu", không trả lời "web trải nghiệm thế nào".rwmixreadchỉ có tác dụng với workload hỗn hợp (--rw=rwhoặcrandrw). Truyền--rwmixread=75cùng--rw=randreadthì tham số bị bỏ qua; đây là lỗi sao chép lệnh rất hay gặp.- Queue sâu không mô phỏng web. Khối 4k với
iodepth=64đo khả năng chịu hàng đợi của thiết bị, không mô phỏng việc mở hàng trăm file nhỏ song song. - Đừng nói "chậm gấp N lần" từ hai lần chạy khác điều kiện. Chỉ kết luận khi cùng tham số và lặp lại nhiều lần.
Để gần web hơn, hãy thêm một phép đo thực tế: time một lệnh duyệt nhiều file nhỏ (ví dụ find kèm stat trên thư mục code) ở cả hai nơi. Với NFS còn có công cụ đo đúng chỗ hơn fio:
nfsiostat 5
nfsstat -c
cat /proc/self/mountstatsnfsiostat (hoặc mountstats) cho RTT trung bình theo từng loại thao tác như GETATTR, LOOKUP, READ cùng số lần retransmit, nên phản ánh đúng chi phí metadata của webroot. Hãy ghi lại tuỳ chọn mount (noatime, nconnect, actimeo) và chế độ export sync hay async khi so sánh.
Đo mạng tới máy chủ NFS bằng iperf3
# trên máy NFS
iperf3 -s
# trong pod
iperf3 -c 10.0.0.31 # chiều pod vào NFS, một luồng
iperf3 -c 10.0.0.31 -R # chiều ngược lại
iperf3 -c 10.0.0.31 -P 4 # bốn luồng song songChạy mặc định chỉ đo một chiều, một luồng. Một kết quả cỡ vài Gbit/s cho thấy băng thông thô không phải nút cổ chai, nhưng đọc thêm cột Retr (số gói phải truyền lại): giá trị cao liên tục là tín hiệu cần điều tra thêm (mất gói, hàng đợi NIC, giới hạn CPU của pod, máy nhận quá tải), chưa đủ để kết luận. Có thể đối chiếu bằng một phép thử một luồng với netcat:
# trên máy NFS
nc -l -p 12345 > /dev/null
# trong pod
dd if=/dev/zero bs=1M count=1024 | nc 10.0.0.31 12345Số của netcat có thể khác iperf3 vì khác công cụ, chỉ một luồng, và địa chỉ đích khác (mạng riêng hay công khai) thì đường đi khác; đây là giả thuyết cần kiểm chứng. Băng thông không phải độ trễ: đường nhanh vẫn có thể chậm với hàng nghìn yêu cầu metadata nhỏ.
Phía máy chủ khi webroot trên NFS quá tải
Khi webroot trên NFS chậm, hai thông số hay bị bỏ quên là CPU của máy NFS và số luồng nfsd. Máy cấu hình nhỏ (1 core, 1 GB) dễ chạm 100% CPU khi chạy thử tải, trong khi biểu đồ trung bình vẫn trông bình thường vì đỉnh chỉ kéo dài vài giây. Số luồng mặc định (thường 8) cũng có thể ít cho nhiều client.
cat /proc/fs/nfsd/threadsĐể đặt bền vững, tạo file /etc/nfs.conf.d/local.conf với nội dung:
[nfsd]
threads=20Rồi áp dụng bằng sudo systemctl restart nfs-server. Restart nfs-server làm client NFS tạm treo I/O, nên hãy làm ngoài giờ cao điểm; hoặc thử nhanh bằng echo 20 | sudo tee /proc/fs/nfsd/threads (mất khi khởi động lại).
Với webroot trên NFS, hãy kiểm tra lại bằng /proc/fs/nfsd/threads thay vì tin vào ps. Ví dụ, một máy NFS 1 core, 1 GB dễ chạm 100% CPU khi chạy thử tải; nâng cấu hình và tăng luồng giúp thử tải hết full CPU nhưng web vẫn có thể treo. Vì vậy CPU máy NFS là nghi phạm mạnh nhất nhưng chưa được chứng minh trọn vẹn, và cũng không có cơ sở để kết luận "NFS không quá tải".
Giám sát webroot trên NFS: cảnh báo đầy đĩa không đủ
Khi giám sát webroot trên NFS, cảnh báo phần trăm đầy đĩa chỉ bắt sự cố hết dung lượng, không bắt được sự cố CPU hay IOPS. Với máy NFS, cài node_exporter (chạy bằng service systemd, không chạy nền bằng &) và thêm job scrape tĩnh vào Prometheus:
prometheus:
prometheusSpec:
additionalScrapeConfigs:
- job_name: nfs-server
static_configs:
- targets: ['10.0.0.31:9100']Rồi tạo các rule bổ sung sau; đây là đề xuất, cần kiểm chứng trên môi trường của bạn:
# đầy đĩa, lọc đúng mountpoint, có độ trễ để tránh báo giả
100 * (1 - node_filesystem_avail_bytes{job="nfs-server",mountpoint="/export"}
/ node_filesystem_size_bytes{job="nfs-server",mountpoint="/export"}) > 80 # for: 10m
# CPU cao kéo dài
100 - avg(rate(node_cpu_seconds_total{job="nfs-server",mode="idle"}[5m])) * 100 > 85 # for: 5m
# iowait cao
avg(rate(node_cpu_seconds_total{job="nfs-server",mode="iowait"}[5m])) * 100 > 20 # for: 5mTrong Grafana-managed alert, bỏ phần so sánh (> 80...) khỏi query, đặt ngưỡng ở bước Threshold và for ở Pending period; nếu giữ toán tử trong query thì đặt No data là OK, vì kết quả rỗng bị coi là thiếu dữ liệu.
Cảnh báo ở đây là Grafana-managed alert gửi qua webhook của kênh chat, không phải Alertmanager.Đừng dựa vào kubelet_volume_stats cho PV NFS tĩnh vì chưa được kiểm chứng. Rule log Loki dùng count_over_time sẽ báo lặp khi một lỗi lặp lại; hãy đặt pending period và gom nhóm theo nhãn như trong bài Grafana Alerting cho pod: dashboard RAM, cảnh báo và ServiceMonitor ingress-nginx.
Cổng rpcbind và tường lửa quanh webroot trên NFS
Máy chủ NFS phục vụ webroot trên NFS mà có địa chỉ công khai sẽ nhanh chóng bị quét cổng 111 (rpcbind). Log kiểu "request from unauthorized host" cho thấy quy tắc hosts.allow/hosts.deny hoặc tường lửa đang chặn, đó là điều tốt chứ không phải lỗi. Chỉ cho phép dải mạng nội bộ của cluster tới cổng 111 và 2049. Với NFSv4, mọi thứ đi qua cổng 2049, nên không cần mở dải cổng rộng. Về UID/GID khi pod không chạy bằng root, xem bài NFS và pod không chạy root: UID/GID, root_squash và anonuid.
Khi nào nên bỏ webroot trên NFS
- Đưa code vào image để thay webroot trên NFS. Code nằm trên đĩa cục bộ của node, nhanh và bất biến; mỗi lần deploy là một image mới.
- NFS chỉ cho thư mục thật sự ghi, ví dụ upload; không đặt cả
publicchứaindex.phplên NFS. - Session, cache ra Redis; ảnh và file tĩnh ra object storage (S3) qua CDN, thay vì file chung.
- Bật opcache với
validate_timestamps=0trong production để giảm vòng metadata, và reload khi deploy.
Nếu vẫn cần ReadWriteMany cho một thư mục nhỏ, đừng giữ cả webroot trên NFS; chỉ giữ NFS cho đúng thư mục đó và đo lại sau thay đổi.
Điều còn chưa chốt
- Nguyên nhân gốc của các đợt treo webroot trên NFS: chưa xác nhận; CPU máy NFS chỉ là nghi phạm mạnh nhất.
- Một số lần treo khác có thể do HPA chạm trần hoặc pod Pending thiếu CPU (xem bài Cluster Autoscaler trên LKE), tức là nhiễu cần loại ra trước khi đổ lỗi cho webroot trên NFS.
Tài liệu gốc: tài liệu fio và volume NFS trên kubernetes.io.
Bài tiếp theo
MySQL hết kết nối: pod 502 trên Kubernetes nhưng gốc nằm ở database. Khi NFS và lưu trữ chưa phải thủ phạm, lỗi 502 hàng loạt có thể xuất phát từ trần kết nối của MySQL.



