Cách Chúng Tôi Khắc Phục Lỗi Ghi Nhật Ký Gunicorn Đệ Quy Trong Môi Trường Sản Xuất
By hientd, at: 15:59 Ngày 10 tháng 6 năm 2025
Thời gian đọc ước tính: __READING_TIME__ minutes


Tại Glinteco, gần đây chúng tôi đã gặp phải một sự cố sản xuất khó hiểu, trong đó một trong những ứng dụng Flask của khách hàng của chúng tôi bị gián đoạn, mặc dù tất cả các dịch vụ đều đang chạy và log không hiển thị lỗi ngay từ cái nhìn đầu tiên. Sau khi tìm hiểu kỹ, chúng tôi đã phát hiện ra một vấn đề nghiêm trọng:
RuntimeError: reentrant call inside <_io.BufferedWriter name='
Trong bài viết này, chúng tôi sẽ hướng dẫn bạn cách:
-
Nguyên nhân gây ra lỗi
-
Cách chúng tôi gỡ lỗi
-
Bản vá lỗi mà chúng tôi đã áp dụng
-
Những điểm mấu chốt dành cho bất kỳ nhóm nào sử dụng Gunicorn + logging trong Python
Vấn đề
Cấu hình của khách hàng khá phổ biến:
-
Ứng dụng Flask chạy phía sau Gunicorn
-
Triển khai trên hai máy chủ phía sau một bộ cân bằng tải
-
Đã cài đặt giám sát New Relic
-
Phiên bản Gunicorn: 20.0.1
-
Python phiên bản: 3.6
Đột nhiên, người dùng bắt đầu báo cáo việc truy cập liên tục và gián đoạn vào trang web. Nhưng số liệu hệ thống cho thấy sử dụng CPU và bộ nhớ bình thường. Các tiến trình vẫn hoạt động. Điều này không hợp lý, cho đến khi chúng tôi kiểm tra lỗi này trong stderr:
RuntimeError: reentrant call inside <_io.BufferedWriter name=''>
Worker with pid 12345 was terminated due to signal 9
Gỡ lỗi sự cố
Lỗi này rất khó theo dõi. Dưới đây là những gì chúng tôi đã tìm hiểu được thông qua quá trình điều tra:
-
Lỗi liên quan đến cơ chế ghi nhật ký của Gunicorn, đặc biệt là khi nhiều tiến trình worker cố gắng ghi vào stderr cùng một lúc.
-
Đây là một vấn đề đã biết trong các phiên bản cũ của Gunicorn, trong đó ghi nhật ký lặp lại hoặc đệ quy có thể gây ra sự cố.
-
Tín hiệu 9 (SIGKILL) có nghĩa là tiến trình worker đã bị hệ thống buộc phải kết thúc — có thể là do bế tắc ghi nhật ký hoặc bộ nhớ bị cạn kiệt.
Chúng tôi đã kiểm tra:
-
Nhật ký Supervisord → không có dấu vết của việc sử dụng tài nguyên cao
-
Nhật ký ứng dụng → gián đoạn và không đầy đủ
-
Các vấn đề trên GitHub của Gunicorn → Đã tìm thấy vấn đề liên quan (từ năm 2021)
Bản vá lỗi: Nâng cấp từng giai đoạn
Chúng tôi không chuyển sang phiên bản mới nhất ngay lập tức. Thay vào đó, chúng tôi đã thực hiện một quá trình nâng cấp an toàn:
-
Nâng cấp Gunicorn lên 21.2.0 — vì cộng đồng đã xác nhận phiên bản này đã giải quyết nhiều vấn đề về ghi nhật ký.
-
Giám sát hệ thống trong vài ngày. Lỗi đã biến mất và hệ thống trở nên ổn định trở lại.
-
Sau khi xác nhận sự ổn định, chúng tôi đã chuyển sang Gunicorn 23.0.0, phiên bản ổn định mới nhất.
-
Cũng đã cập nhật Python lên 3.10 để cải thiện hiệu suất và khả năng tương thích.
Không có vấn đề nào kể từ đó.
Bản vá lỗi bổ sung (Tùy chọn nhưng được khuyến nghị)
-
Chúng tôi đã thay thế việc ghi nhật ký stderr trực tiếp bằng bộ xử lý ghi nhật ký thích hợp trong Python bằng cách sử dụng logging.StreamHandler(sys.stdout).
-
Giảm nhẹ số lượng worker của Gunicorn để hạn chế đồng thời trong giờ cao điểm.
-
Thêm cài đặt max_requests trong Gunicorn để tái chế worker sau N yêu cầu.
gunicorn app:app --workers=4 --max-requests=1000 --max-requests-jitter=100
Những điểm mấu chốt
-
Gunicorn < 20.1 không còn an toàn cho sản xuất nữa — hãy nâng cấp càng sớm càng tốt.
-
Ghi nhật ký có thể lặng lẽ làm hỏng ứng dụng của bạn nếu không được cấu hình đúng cách.
-
Luôn giám sát stderr và tín hiệu worker, ngay cả khi nhật ký ứng dụng trông ổn.
-
Thực hiện theo một con đường nâng cấp an toàn và đăng ký theo dõi các vấn đề trên GitHub hoặc nhật ký thay đổi của các phụ thuộc quan trọng.
Suy nghĩ cuối cùng
Vấn đề này nhắc nhở chúng tôi rằng tại sao ngay cả các thư viện trưởng thành như Gunicorn cũng cần được bảo trì và giám sát tích cực. Tại Glinteco, chúng tôi giúp khách hàng không chỉ xây dựng ứng dụng mà còn chạy và mở rộng chúng một cách an toàn.
Nếu bạn đang gặp phải hành vi lạ trong ứng dụng Flask/Django của mình, hoặc cần ý kiến thứ hai về đường dẫn triển khai của mình, hãy liên hệ với chúng tôi. Chúng tôi đã từng trải qua điều đó.