Ứng dụng báo SQLSTATE[HY000] [2002] Connection refused trong khi Service của MySQL vẫn có địa chỉ, ping được, còn CPU và RAM của pod hoàn toàn bình thường. Đây là dạng sự cố điển hình khi MySQL đầy volume: mysqld không khởi động được vì đĩa dữ liệu đã hết chỗ, nên không có socket nào để kết nối. Nếu bạn nghi MySQL đầy volume, hãy làm theo các bước dưới đây. Bài này đi theo thứ tự chẩn đoán và xử lý: nhận biết, tìm thủ phạm là binlog, cứu MySQL chạy lại, dựng lại volume lớn hơn, rồi những bẫy khi nới PVC trực tiếp.
Bài Volume, PersistentVolume, PVC và StorageClass giải thích các khái niệm lưu trữ mà bài này dùng. Nếu MySQL của bạn chạy theo mô hình StatefulSet, xem thêm MySQL replication trong StatefulSet.
Triệu chứng khi MySQL đầy volume: Connection refused
Lỗi 2002 thường được hiểu là sai địa chỉ hoặc MySQL chưa khởi động. Hai phép thử đầu tiên là kiểm tra mạng từ pod ứng dụng tới Service, và vào thẳng pod MySQL để đăng nhập:
kubectl exec -it -n db-1-0 -- bash
mysql -u root -p
# ERROR 2002 (HY000): Can't connect to local MySQL server
# through socket '/var/lib/mysql/mysql.sock' (2) Mạng ổn mà socket không tồn tại nghĩa là mysqld không chạy, và MySQL đầy volume là nghi vấn hàng đầu. Grafana cho thấy CPU và RAM bình thường, nên hướng điều tra phải chuyển sang đĩa.
Chẩn đoán MySQL đầy volume bằng df và du
df -h /var/lib/mysql
# Filesystem Size Used Avail Use% Mounted on
# /dev/... 9.8G 9.8G 0 100% /var/lib/mysql
du -hsx /var/lib/mysql/* | sort -h | tail
# 1.1G /var/lib/mysql/binlog.000001
# 1.1G /var/lib/mysql/binlog.000002
# ...Dòng 100% xác nhận volume dữ liệu đã đầy. Lệnh du cho thấy phần lớn dung lượng thuộc về một chuỗi file binlog.NNNNNN, mỗi file khoảng 1 GB, trong khi dữ liệu thật chỉ chiếm phần nhỏ. Lưu ý du -x giữ nguyên trong một filesystem, nên không đếm nhầm phần của overlay hay mount khác.
Vì sao binlog làm MySQL đầy volume
Binary log ghi lại mọi thay đổi dữ liệu, cần cho replication và khôi phục theo thời điểm. Từ MySQL 8.0, binary log được bật mặc định, trong khi các image cũ hơn thường tắt. max_binlog_size (mặc định 1 GB) chỉ quyết định kích thước từng file; một transaction rất lớn vẫn có thể làm file vượt mức này.
Thời gian giữ do binlog_expire_logs_seconds quyết định (mặc định 2592000 giây, tức 30 ngày), tức giới hạn theo tuổi chứ không theo số file. Tổng dung lượng binlog phụ thuộc lượng ghi nhân với thời gian giữ, nên với volume 10 GB và một đợt ghi lớn như nhập hàng loạt sản phẩm thì volume đầy rất nhanh, còn 30 ngày giữ lại là quá dài. Nguyên nhân binlog tăng nhanh trong một sự cố cụ thể chưa được chứng minh; thường chỉ nghi ngờ từ việc import lượng lớn. Hai giá trị mặc định trên là kiến thức từ tài liệu MySQL, bạn nên đối chiếu với phiên bản đang chạy.
Cứu MySQL khởi động lại khi đĩa hết chỗ
Với MySQL đầy volume, khi mysqld không lên được, bạn không thể chạy PURGE vì chưa có kết nối. Quy trình gồm hai bước: tạo chỗ trống tối thiểu để mysqld lên, rồi dọn đúng chuẩn bằng lệnh của MySQL.
Bước 1, tạo chỗ trống. Có ba hướng, ưu tiên từ trên xuống:
- Nới PVC trước nếu StorageClass cho phép (xem phần nới PVC bên dưới).
- Scale StatefulSet về 0, mount PVC vào một pod tạm, xoá file binlog cũ nhất và sửa
binlog.indexcho khớp (bỏ dòng của file đã xoá), rồi scale lại. - Cách tạm nhất:
mvmột file binlog ra ngoài volume. File đó nằm trên overlay của container và sẽ mất khi container khởi động lại;binlog.indexkhông được cập nhật nên MySQL vẫn tin file còn tồn tại. Chỉ dùng khi chưa có cách nào khác.
Bước 2, dọn đúng chuẩn khi mysqld đã chạy:
-- sau khi mysqld đã chạy lại
PURGE BINARY LOGS TO 'binlog.000006';
-- hoặc theo thời gian
PURGE BINARY LOGS BEFORE NOW() - INTERVAL 1 DAY;Nếu có replica, hãy kiểm tra vị trí đọc của replica bằng SHOW REPLICA STATUS trước khi PURGE, vì xoá binlog replica chưa đọc sẽ làm replication hỏng. Nên thử quy trình này trên môi trường thử trước.
Trước mọi thao tác xoá, hãy dump dữ liệu và kiểm tra dump. Dùng mật khẩu từ biến môi trường có sẵn trong pod (lấy từ Secret) thay vì gõ trên dòng lệnh:
kubectl exec -i -n <namespace> db-1-0 -- sh -c \
'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" mysqldump -u root --single-transaction \
--routines --triggers --events app_db' > app_db.sql
ls -lh app_db.sql
tail -n 3 app_db.sql # phải thấy dòng "Dump completed"Biến $MYSQL_ROOT_PASSWORD nằm trong dấu nháy đơn nên do shell trong pod bung ra, mật khẩu không xuất hiện trong đối số của kubectl. Nếu image đặt tên biến khác, hãy dùng tên đó, hoặc dùng --defaults-extra-file mount từ Secret. Dòng cuối của file dump không phải -- Dump completed nghĩa là dump hỏng. Xoá PVC khi dump chưa được kiểm tra là rủi ro mất dữ liệu thật, nhất là khi MySQL đầy volume và bạn đang vội. Đừng gõ mật khẩu thẳng trên dòng lệnh: nó hiện trong lịch sử shell và danh sách tiến trình.
Dựng lại volume lớn hơn khi MySQL đầy volume
Một cách xử lý chắc chắn khi MySQL đầy volume là dựng lại từ đầu: dump, xoá StatefulSet, xoá PVC, xoá PV, tăng dung lượng, apply, restore. Các bước theo thứ tự:
- Dump và kiểm tra dump như trên.
- Xoá StatefulSet, rồi PVC.
- Xem PV: nếu StorageClass có
reclaimPolicy: Retain, PV chuyển sang trạng tháiReleased(đây là trạng thái của PV, không phải của PVC). - Xoá PV. Lệnh này chỉ xoá object trong cluster, đĩa ở nhà cung cấp với Retain vẫn còn và vẫn bị tính phí, nên cần dọn riêng.
- Sửa
volumeClaimTemplatesvới dung lượng mới, apply lại StatefulSet, rồi restore dump.
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 30Gi
storageClassName: block-storage-retain # tên ví dụTên StorageClass ở trên chỉ là ví dụ; trên LKE hãy xem tên thật bằng kubectl get storageclass.
Khi MySQL đầy volume, đây là cách chắc chắn nhất nhưng có downtime, và cần chắc chắn dữ liệu đã nằm an toàn ngoài PVC.
Giới hạn của StatefulSet khi MySQL đầy volume cần nới dung lượng
Thử sửa storage trong volumeClaimTemplates của StatefulSet đang chạy sẽ nhận lỗi Forbidden: updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy'... are forbidden. Đây là giới hạn chung của StatefulSet (xem cách dựng ở bài Manifest Kubernetes đầu tiên), không phải điều mới. Cách khác là nới PVC trực tiếp, nhưng cần StorageClass có allowVolumeExpansion: true:
kubectl get storageclass -o custom-columns=NAME:.metadata.name,EXPAND:.allowVolumeExpansion
kubectl patch pvc data-db-1-0 -n -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}' Dùng kubectl patch hoặc kubectl edit trên đúng đối tượng PVC. Một lỗi hay gặp là sửa dung lượng bằng cách dán manifest kind PersistentVolumeClaim vào file StatefulSet rồi apply; cách đó không nới được PVC đã tồn tại.
Phần hai: nới PVC cho MySQL đầy volume không như mong đợi
Phần này mô tả một lần thử nới PVC trên volume đã được làm đầy có chủ ý bằng cách copy file lớn. Sau khi sửa PVC lên 20Gi, df -h trong pod vẫn báo khoảng 9.8G. Việc nới PVC gồm hai pha: pha một là mở rộng volume ở phía nhà cung cấp, pha hai là mở rộng filesystem, do CSI driver thực hiện khi volume được mount. Vì thế cần kiểm tra điều kiện của PVC thay vì suy đoán:
kubectl describe pvc data-db-1-0 -n <namespace>
# Conditions:
# FileSystemResizePending True
kubectl get pvc data-db-1-0 -n <namespace> -o jsonpath='{.status.capacity.storage}'
kubectl get volumeattachmentĐiều kiện FileSystemResizePending nghĩa là pha hai đang chờ. Trong describe pvc, hãy xem thêm các event Resizing và ExternalExpanding để biết pha nào đang chậm, còn get volumeattachment cho biết volume đang gắn vào node nào. Hướng chạy resize2fs trực tiếp trong container là suy luận sai: tool không có sẵn trong image và việc đó thuộc về CSI driver, không phải ứng dụng.
Trong lần thử đó, xoá pod để ép mount lại dẫn đến ContainerCreating kéo dài với sự kiện volume attachment is being deleted; pod mất khá lâu mới lên và df vẫn báo dung lượng cũ, khởi động lại node cũng không đổi. Đó chỉ là kết quả của một lần thử với một nhà cung cấp, không nên coi là hành vi thường gặp, và kết quả cuối cùng chưa được xác nhận.
Lệnh sửa PVC lên dung lượng lớn hơn đã được API server chấp nhận; nếu StorageClass không bật allowVolumeExpansion thì thường bị từ chối ngay, nên có thể suy ra cờ này đã bật, nhưng vẫn nên kiểm bằng lệnh kubectl get storageclass ở trên. Với các lỗi gắn volume tương tự, xem bài Pod kẹt ContainerCreating trên LKE.
Phòng ngừa MySQL đầy volume
- Để tránh MySQL đầy volume, đặt
binlog_expire_logs_secondsphù hợp: tham số này quyết định thời gian giữ, cònmax_binlog_sizechỉ quyết định kích thước từng file (giảm đi giúp dọn mịn hơn, không giảm tổng dung lượng). Ví dụ trongmy.cnf:binlog_expire_logs_seconds=259200(3 ngày). Đây là giá trị ví dụ, chưa thử trên cluster; hãy kiểm tra trước khi áp dụng. - Tắt binary log nếu không dùng replication hay khôi phục theo thời điểm (
skip-log-bin). - Cảnh báo từ sớm bằng Prometheus (xem Giám sát Kubernetes với kube-prometheus), ví dụ khi dung lượng trống dưới 20%:
kubelet_volume_stats_available_bytes / kubelet_volume_stats_capacity_bytes < 0.2. - Chọn StorageClass có
allowVolumeExpansion: truengay từ đầu để MySQL đầy volume còn có đường nới nhanh. - Diễn tập quy trình restore định kỳ, vì chỉ dump chưa kiểm tra thì chưa phải bản sao lưu.
Checklist khi MySQL đầy volume
- Nghi MySQL đầy volume khi Connection refused mà CPU và RAM bình thường: chạy
df -htrên volume dữ liệu trước. - Khi mysqld đã lên, dùng
PURGE BINARY LOGS(kiểm tra replica trước); chỉmvtạm khi chưa có cách nào khác. - Kiểm tra dump trước khi xoá StatefulSet, PVC hoặc PV.
- Sau khi xoá PV Retain, dọn đĩa phía nhà cung cấp để khỏi bị tính phí.
- Sau khi xử lý xong MySQL đầy volume, đặt cảnh báo dung lượng để lần sau biết sớm.
- Nới PVC thì kiểm tra
describe pvcvà điều kiệnFileSystemResizePending.
Bài tiếp theo
Cài mysqld_exporter trên Kubernetes: ba lỗi im lặng khiến Prometheus trống số liệu. Cảnh báo dung lượng volume lấy từ kubelet, còn bài này đi tiếp sang số liệu bên trong MySQL: cách cài mysqld_exporter và ba lỗi khiến Prometheus không có số liệu.



