Cách Chúng Tôi Khắc Phục Lỗi Gunicorn Worker trong Ứng Dụng Flask: Hành Trình Khắc Phục Sự Cố Thực Sự
By hientd, at: 22:20 Ngày 03 tháng 11 năm 2024
Thời gian đọc ước tính: __READING_TIME__ minutes


Khi bạn quản lý các máy chủ sản xuất, luôn có lúc xảy ra sự cố ngay cả khi bạn nghĩ mọi thứ đang hoạt động trơn tru. Gần đây, chúng tôi đã gặp phải chính xác điều đó—một vấn đề thực sự trong ứng dụng Python/Flask của chúng tôi đang chạy trên Gunicorn. Khách hàng của chúng tôi báo cáo sự cố gián đoạn hoạt động của ứng dụng, và chúng tôi nhanh chóng nhận ra đã đến lúc cần tìm hiểu kỹ vấn đề. Đây là câu chuyện về cách chúng tôi giải quyết nó, những gì chúng tôi đã học được và các bước đơn giản mà chúng tôi sẽ thực hiện trong tương lai để tránh vấn đề tương tự.
Cấu hình ban đầu
Cấu hình của chúng tôi có một bộ cân bằng tải với hai máy chủ xử lý các yêu cầu của khách hàng. Trong điều kiện bình thường, bộ cân bằng tải giúp lưu lượng truy cập hoạt động trơn tru, nhưng khách hàng của chúng tôi bắt đầu gặp phải sự cố khả dụng bật/tắt này.
[2024-11-03 13:25:42 +0000] [26747] [INFO] Booting worker with pid: 26747
--- Logging error ---
Traceback (most recent call last):
File "/usr/lib/python3.6/logging/__init__.py", line 998, in emit
self.flush()
File "/usr/lib/python3.6/logging/__init__.py", line 978, in flush
self.stream.flush()
RuntimeError: reentrant call inside <_io.BufferedWriter name=''>
Call stack:
File "/home/ubuntu/sample-app/venv/bin/gunicorn", line 8, in
sys.exit(run())
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 67, in run
WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/app/base.py", line 231, in run
super().run()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/app/base.py", line 72, in run
Arbiter(self).run()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/arbiter.py", line 211, in run
self.manage_workers()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/arbiter.py", line 551, in manage_workers
self.spawn_workers()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/arbiter.py", line 623, in spawn_workers
time.sleep(0.1 * random.random())
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/arbiter.py", line 242, in handle_chld
self.reap_workers()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/arbiter.py", line 533, in reap_workers
os.WTERMSIG(status)
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/glogging.py", line 261, in warning
self.error_log.warning(msg, *args, **kwargs)
File "/usr/lib/python3.6/logging/__init__.py", line 1320, in warning
self._log(WARNING, msg, args, **kwargs)
File "/usr/lib/python3.6/logging/__init__.py", line 1444, in _log
self.handle(record)
File "/usr/lib/python3.6/logging/__init__.py", line 1454, in handle
self.callHandlers(record)
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/newrelic/hooks/logger_logging.py", line 87, in wrap_callHandlers
return wrapped(*args, **kwargs)
File "/usr/lib/python3.6/logging/__init__.py", line 1516, in callHandlers
hdlr.handle(record)
File "/usr/lib/python3.6/logging/__init__.py", line 865, in handle
self.emit(record)
File "/usr/lib/python3.6/logging/__init__.py", line 998, in emit
self.flush()
File "/usr/lib/python3.6/logging/__init__.py", line 978, in flush
self.stream.flush()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/arbiter.py", line 242, in handle_chld
self.reap_workers()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/arbiter.py", line 533, in reap_workers
os.WTERMSIG(status)
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/glogging.py", line 261, in warning
self.error_log.warning(msg, *args, **kwargs)
File "/usr/lib/python3.6/logging/__init__.py", line 1320, in warning
self._log(WARNING, msg, args, **kwargs)
File "/usr/lib/python3.6/logging/__init__.py", line 1444, in _log
self.handle(record)
File "/usr/lib/python3.6/logging/__init__.py", line 1454, in handle
self.callHandlers(record)
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/newrelic/hooks/logger_logging.py", line 87, in wrap_callHandlers
return wrapped(*args, **kwargs)
Message: 'Worker with pid %s was terminated due to signal %s'
Arguments: (26469, 9)
[2024-11-03 13:25:42 +0000] [26748] [INFO] Booting worker with pid: 26748
--- Logging error ---
Traceback (most recent call last):
File "/usr/lib/python3.6/logging/__init__.py", line 998, in emit
self.flush()
File "/usr/lib/python3.6/logging/__init__.py", line 978, in flush
self.stream.flush()
RuntimeError: reentrant call inside <_io.BufferedWriter name=''>
Call stack:
File "/home/ubuntu/sample-app/venv/bin/gunicorn", line 8, in
sys.exit(run())
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/app/wsgiapp.py", line 67, in run
WSGIApplication("%(prog)s [OPTIONS] [APP_MODULE]").run()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/app/base.py", line 231, in run
super().run()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/app/base.py", line 72, in run
Arbiter(self).run()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/arbiter.py", line 211, in run
self.manage_workers()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/arbiter.py", line 551, in manage_workers
self.spawn_workers()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/arbiter.py", line 623, in spawn_workers
time.sleep(0.1 * random.random())
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/arbiter.py", line 242, in handle_chld
self.reap_workers()
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/arbiter.py", line 533, in reap_workers
os.WTERMSIG(status)
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/gunicorn/glogging.py", line 261, in warning
self.error_log.warning(msg, *args, **kwargs)
File "/usr/lib/python3.6/logging/__init__.py", line 1320, in warning
self._log(WARNING, msg, args, **kwargs)
File "/usr/lib/python3.6/logging/__init__.py", line 1444, in _log
self.handle(record)
File "/usr/lib/python3.6/logging/__init__.py", line 1454, in handle
self.callHandlers(record)
File "/home/ubuntu/sample-app/venv/lib/python3.6/site-packages/newrelic/hooks/logger_logging.py", line 87, in wrap_callHandlers
return wrapped(*args, **kwargs)
Message: 'Worker with pid %s was terminated due to signal %s'
Arguments: (26469, 9)
Đây là cách chúng tôi bắt đầu khắc phục sự cố:
-
Xác minh quy trình: Đầu tiên, chúng tôi đã kiểm tra tất cả các quy trình máy chủ để xác nhận rằng chúng đang chạy. Mọi thứ dường như ổn ở cấp độ này—không có sự cố hoặc quy trình bị treo rõ ràng.
-
Kiểm tra nhật ký: Vì tất cả các quy trình dường như đang hoạt động trơn tru, chúng tôi chuyển sang các nhật ký được quản lý bởi Supervisord. Đó là nơi chúng tôi thấy một mô hình lặp lại: ứng dụng đang bị chấm dứt một cách ngẫu nhiên. Và trong nhật ký lỗi, chúng tôi đã tìm thấy các mục cụ thể này:
RuntimeError: reentrant call inside <_io.BufferedWriter name='
'>
Cùng với đó, chúng tôi cứ thấy:Worker with pid %s was terminated due to signal %s
Với sự kết hợp của các lỗi "reentrant" và việc chấm dứt worker này, có vẻ như chúng tôi gặp phải sự pha trộn giữa các vấn đề về bộ nhớ, xung đột Gunicorn và lỗi ghi nhật ký. Nhưng chúng tôi cần thêm thông tin.
Tìm hiểu sâu hơn: Nghiên cứu lỗi
Với những manh mối này, chúng tôi bắt đầu tìm kiếm trực tuyến, tìm kiếm các thuật ngữ như "reentrant call inside <_io.BufferedWriter>"
và "Gunicorn RuntimeError.”
Rất may, chúng tôi đã tìm thấy các cuộc thảo luận trên GitHub từ những người khác đã gặp phải vấn đề tương tự. Dưới đây là một vài cuộc thảo luận chính mà chúng tôi đã tham khảo:
Đọc qua những điều này, một vấn đề trở nên rõ ràng: chúng tôi đang chạy một phiên bản lỗi thời của Gunicorn—20.0.1. Phiên bản phát hành mới nhất là 23.0.0, và nó bao gồm các bản sửa lỗi cho việc ghi nhật ký và quản lý quy trình giải quyết trực tiếp các vấn đề này. Vì vậy, chúng tôi quyết định thử nâng cấp.
Kiểm tra giải pháp
-
Nâng cấp đầu tiên: Thay vì nhảy ngay vào phiên bản mới nhất, chúng tôi đã nâng cấp lên 21.2.0. Chúng tôi muốn thận trọng, vì vậy chúng tôi đã theo dõi ứng dụng sát sao. Sau vài giờ, chúng tôi thấy sự ổn định—không còn chấm dứt ngẫu nhiên nữa, và mọi thứ đều hoạt động như mong đợi.
-
Sử dụng phiên bản mới nhất: Khi chúng tôi cảm thấy tự tin, chúng tôi đã nâng cấp lên 23.0.0, phiên bản Gunicorn mới nhất (điều này yêu cầu nâng cấp phiên bản Python mới hơn - khó hơn nhưng đáng giá). Và kể từ đó, ứng dụng đã ổn định mà không có một lỗi reentrant nào. Khả năng truy cập của khách hàng vào ứng dụng đã trở lại bình thường, và rõ ràng việc nâng cấp đã giải quyết được vấn đề.
Những gì chúng tôi đã học được
Trải nghiệm này đã làm nổi bật một vài bài học:
-
Nhật ký là manh mối đầu tiên của bạn: Luôn bắt đầu bằng cách xác nhận các quy trình đang chạy và kiểm tra kỹ lưỡng nhật ký. Nhật ký thường chứa các gợi ý quan trọng.
-
Tìm kiếm các vấn đề tương tự: Khi bạn thấy một lỗi lạ, những người khác có thể cũng đã gặp phải nó. Tìm kiếm trên GitHub các thuật ngữ cụ thể có thể chỉ cho bạn các cuộc thảo luận hữu ích và đôi khi thậm chí là các giải pháp chính xác.
-
Thực hiện nâng cấp từ từ: Thay vì nhảy ngay vào phiên bản mới nhất, nên nâng cấp và kiểm tra từng bước. Chúng tôi đã thấy sự cải thiện ngay lập tức với 21.2.0, và cách tiếp cận từng bước đó cho phép chúng tôi đảm bảo sự ổn định trước khi chuyển sang 23.0.0.
-
Luôn cập nhật các bản phát hành: Đăng ký vào các kho lưu trữ GitHub cho các thư viện mà bạn phụ thuộc vào—như Gunicorn—giúp bạn cập nhật thông tin về các bản cập nhật quan trọng. Đây là một cách đơn giản để tránh những vấn đề đột ngột này.
Kết luận
Khắc phục sự cố sản xuất có thể gây căng thẳng, nhưng việc áp dụng phương pháp từng bước thực sự đã mang lại hiệu quả ở đây. Trải nghiệm này đã nhắc nhở chúng tôi về tầm quan trọng của việc giám sát các phụ thuộc và cập nhật chúng. Hy vọng, câu chuyện của chúng tôi sẽ cung cấp cho bạn một vài hiểu biết để giải quyết những thách thức tương tự—hoặc tránh chúng hoàn toàn.