DevOps & giám sát

Khắc phục lỗi 502 của Node.js trong quá trình triển khai rolling deploy trên cloud

Các ứng dụng Node.js khi chuyển sang môi trường cloud thường gặp tình trạng gián đoạn ngắn hạn trong quá trình triển khai do xử lý tín hiệu và cấu hình kiểm tra sức khỏe (health check) chưa đúng cách.

Hình minh họa một máy chủ được bảo vệ khỏi lỗi trong quá trình triển khai
Ảnh minh họa tạo riêng cho bài viết

Bài được dịch tự động từ bản gốc tiếng Anh.

Các đội ngũ phát triển khi chuyển ứng dụng Node.js sang môi trường container hóa trên cloud thường xuyên gặp phải những đợt lỗi 502 ngắn nhưng gây rối loạn trong mỗi lần triển khai. Các sự cố này xảy ra vì các tiến trình Node.js tiêu chuẩn không tự động phối hợp với load balancer khi nhận tín hiệu tắt máy, dẫn đến việc các yêu cầu đang hoạt động bị ngắt. Việc triển khai các endpoint kiểm tra sức khỏe cụ thể và logic tắt máy nhẹ nhàng (graceful shutdown) sẽ giải quyết được những sai lệch về thời điểm giữa bộ điều phối (orchestrator) và hạ tầng.

Chuyện gì đã xảy ra

Khi một dịch vụ chuyển từ một máy ảo đơn lẻ sang môi trường container quản lý như Kubernetes hoặc ECS, chiến lược triển khai thường chuyển sang cập nhật dạng rolling (rolling updates). Trong mô hình này, các instance mới khởi động trong khi các instance cũ bị dừng lại. Vấn đề cốt lõi xuất phát từ một race condition: bộ điều phối gửi tín hiệu SIGTERM tới container cũ và đồng thời chỉ thị cho load balancer loại bỏ nó khỏi vòng quay phân phối. Tuy nhiên, load balancer thường chậm vài giây trong việc cập nhật bảng định tuyến. Vì mặc định Node.js thoát ngay lập tức khi nhận SIGTERM, tiến trình chết trước khi load balancer ngừng gửi traffic tới đó, dẫn đến các yêu cầu thất bại.

Vấn đề trở nên trầm trọng hơn bởi hành vi khởi động và quản lý kết nối. Các container mới có thể nhận traffic trước khi kết nối cơ sở dữ liệu được thiết lập hoàn toàn nếu không có kiểm tra sẵn sàng (readiness check). Ngoài ra, sự không khớp giữa các timeout keep-alive của Node.js và load balancer trên cloud gây ra thêm nhiều lỗi. Ví dụ, AWS Application Load Balancers giữ kết nối nhàn rỗi trong 60 giây, trong khi Node.js mặc định là 5 giây. Khi balancer cố gắng tái sử dụng socket mà Node đã đóng, người dùng gặp lỗi 502 ngay cả bên ngoài cửa sổ triển khai.

Chi tiết chính

  • Rolling deploy kích hoạt tín hiệu SIGTERM làm chết các tiến trình Node.js trước khi load balancer ngừng định tuyến traffic tới chúng.
  • Liveness probe chỉ nên kiểm tra xem event loop có phản hồi hay không, tuyệt đối không kiểm tra kết nối cơ sở dữ liệu để tránh các vòng lặp khởi động lại dây chuyền.
  • Readiness probe phải xác minh tính khả dụng của các dependency và trả về trạng thái 503 trong quá trình tắt máy để rút traffic một cách an toàn.
  • keepAliveTimeout của Node.js phải được đặt cao hơn timeout nhàn rỗi của load balancer để ngăn chặn lỗi tái sử dụng socket.
  • Sử dụng CMD npm start trong Docker ngăn cản việc chuyển tiếp tín hiệu đúng cách; các nhà phát triển nên sử dụng dạng exec hoặc một tiến trình init như tini.
  • Các pool cơ sở dữ liệu và tài nguyên khác chỉ nên được đóng sau khi HTTP server đã hoàn tất xử lý các yêu cầu đang bay (in-flight requests).

Bối cảnh

Hiểu rõ sự khác biệt giữa liveness và readiness là yếu tố then chốt cho các hoạt động ổn định trên cloud. Kiểm tra liveness trả lời câu hỏi liệu một tiến trình có bị kẹt và cần khởi động lại hay không, trong khi kiểm tra readiness xác định xem một instance có thể chấp nhận traffic hay chưa. Nhầm lẫn hai khái niệm này dẫn đến các bẫy phổ biến, chẳng hạn như khởi động lại tất cả các instance cùng lúc khi cơ sở dữ liệu chậm lại, biến một đỉnh độ trễ nhỏ thành sự cố mất điện toàn bộ. Tắt máy nhẹ nhàng (graceful shutdown) là thực hành dừng công việc mới, hoàn thành các tác vụ hiện có, và sau đó thoát ra, đảm bảo không có dữ liệu nào bị mất hoặc hỏng trong quá trình chuyển đổi.

Xử lý tín hiệu trong Linux containers cũng đòi hỏi sự chú ý đến Process ID (PID). Trong nhiều cấu hình Docker, shell hoặc package manager chạy dưới PID 1, vốn không chuyển tiếp đáng tin cậy các tín hiệu chấm dứt tới tiến trình con Node.js. Nếu không có sự lan truyền tín hiệu đúng cách, ứng dụng sẽ phớt lờ yêu cầu tắt máy cho đến khi bộ điều phối buộc phải kill sau khi hết thời gian gia hạn (grace period), bỏ qua hoàn toàn bất kỳ mã dọn dẹp nào.

Tại sao điều này quan trọng

Bỏ qua các lỗi triển khai ngắn hạn này tạo ra một cái thuế ẩn đối với tốc độ kỹ thuật và độ tin cậy hệ thống. Các đội ngũ thường chịu đựng những trục trặc nhỏ, dẫn đến việc triển khai ít thường xuyên hơn và các đợt phát hành lớn, rủi ro cao hơn. Sự do dự này làm chậm các vòng lặp phản hồi và khiến việc rollback trở nên khó khăn hơn. Hơn nữa, các thử lại phía client được kích hoạt bởi lỗi 502 ngẫu nhiên có thể nhân bản các hành động không idempotent, chẳng hạn như tạo nhiều đơn hàng hoặc tính phí khách hàng hai lần, biến một lỗi kỹ thuật thành một sự cố kinh doanh.

Đối với các trưởng nhóm IT và kỹ sư DevOps, các cảnh báo do nhiễu này làm giảm hiệu quả giám sát. Khi các sự cố thực sự bị chôn vùi dưới những thất bại triển khai thường lệ, các đội ngũ nới lỏng ngưỡng cảnh báo, làm giảm khả năng phát hiện các vấn đề thực tế. Đảm bảo triển khai zero-downtime khôi phục sự tự tin vào quy trình phát hành, cho phép các đội ngũ tung ra các thay đổi nhỏ hơn thường xuyên hơn mà không sợ làm gián đoạn người dùng hoặc làm hỏng dữ liệu.

Bạn có thể làm gì

  • Triển khai các endpoint /healthz và /readyz riêng biệt, đảm bảo các kiểm tra liveness vẫn nhẹ nhàng và độc lập với các dependency bên ngoài.
  • Cấu hình server Node.js để lắng nghe SIGTERM, đặt một cờ để từ chối các yêu cầu mới, và chờ delay deregistration của load balancer trước khi đóng kết nối.
  • Đặt server.keepAliveTimeout thành một giá trị vượt quá timeout nhàn rỗi của load balancer, ví dụ 65 giây cho AWS ALB.
  • Sử dụng dạng exec CMD ["node", "server.js"] trong Dockerfile của bạn hoặc bao gồm một tiến trình init để đảm bảo SIGTERM tới được ứng dụng của bạn.
  • Đóng các pool cơ sở dữ liệu và consumer job nền chỉ sau khi HTTP server đã xác nhận tất cả các yêu cầu in-flight đã hoàn tất.
  • Xác minh thiết lập của bạn bằng cách chạy load test trong quá trình rollout restart, nhắm tới mục tiêu không có phản hồi non-2xx nào trong suốt quá trình.

Tin khác

DevOps & giám sát

Chọn chính sách bù lịch phù hợp cho các tác vụ cron bị bỏ lỡ

Khi máy chủ khởi động lại trong lúc đang chạy các tác vụ theo lịch, việc bỏ qua âm thầm hoặc tạo ra backlog khổng lồ có thể dẫn đến mất dữ liệu hoặc bị cấm API. Tìm hiểu cách cấu hình systemd, Kubernetes và các script tùy chỉnh để xử lý các lần chạy bị bỏ lỡ m

Tất cả tin