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 Tế
By hientd, at: 22:20 Ngày 03 tháng 11 năm 2024
Thời gian đọc ước tính: __READING_TIME__ minutes


Cách Chúng Tôi Khắc Phục Lỗi Worker Gunicorn trong Ứng Dụng Flask của Chúng Tôi: Một Hành Trình Khắc Phục Sự Cố Thực Sự
Khi bạn quản lý các máy chủ sản xuất, luôn có lúc mọi thứ không diễn ra như ý muốn ngay cả khi bạn nghĩ rằng 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 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 định kỳ với ứ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 đề này. Đâ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 gặp phải 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ữ cho 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='<stderr>'>
Call stack:
File "/home/ubuntu/sample-app/venv/bin/gunicorn", line 8, in <module>
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='<stderr>'>
Call stack:
File "/home/ubuntu/sample-app/venv/bin/gunicorn", line 8, in <module>
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)</module></stderr></module></stderr>
Dưới đây là cách chúng tôi bắt đầu khắc phục sự cố:
-
Xác minh các tiến trình: Đầu tiên, chúng tôi kiểm tra tất cả các tiến 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 tiến trình treo rõ ràng nào.
-
Kiểm tra nhật ký: Vì tất cả các tiến trình dường như đang chạy 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 bị chấm dứt một cách ngẫu nhiên. Và trong nhật ký lỗi của chúng tôi, chúng tôi đã tìm thấy các mục cụ thể này:
RuntimeError: reentrant call inside <_io.BufferedWriter name='<stderr>'> </stderr>
Cùng với đó, chúng tôi liên tụ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ự kết hợp củ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.”
May mắn thay, 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 Gunicorn lỗi thời - 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ý tiến 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 một cách chặt chẽ. Sau vài giờ, chúng tôi thấy sự ổn định - không còn sự 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 (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 ứng dụng của khách hàng của chúng tôi đã trở lại bình thường, và rõ ràng là 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 tiến trình đang chạy và kiểm tra kỹ lưỡng các 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ể đã gặp phải nó. Tìm kiếm trên GitHub với 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 chậm rãi: Thay vì nhảy 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 dần dần đó 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ý 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 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ự đã được đền đáp ở đâ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à giữ cho chúng được cập nhật. Hy vọng câu chuyện của chúng tôi mang lại 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.