Bốn dòng JavaScript được vá đã tạo ra một stack tự động hóa mong manh như thế nào
Một đội ngũ kỹ thuật dựa vào bốn cron job và watchdog để duy trì các bản vá nhỏ trong một dependency của Node.js, dẫn đến sự cố hàng tuần cho đến khi thay đổi kiến trúc mạng loại bỏ hoàn toàn nhu cầu này.
Bài được dịch tự động từ bản gốc tiếng Anh.
Một cụm phần mềm (software fleet) đã gặp phải các lỗi kết nối lặp đi lặp lại trong nhiều tháng do một giải pháp tạm bợ mong manh liên quan đến bốn dòng JavaScript. Đội ngũ kỹ thuật phải duy trì một stack phức tạp gồm các cron job, bộ đếm thời gian khởi động lại và firewall watchdog để giữ cho các bản vá này hoạt động trên một ứng dụng tự cập nhật. Vấn đề chỉ được giải quyết triệt để khi chuyển sang một máy ảo định tuyến (router virtual machine), loại bỏ hoàn toàn nhu cầu về các bản vá.
Chuyện gì đã xảy ra
Nguyên nhân gốc rễ nằm ở một dependency của Node.js có tên @runonflux/nat-upnp, cụ thể là trong tệp ssdp.js. Ứng dụng yêu cầu hai sửa đổi nhỏ để hoạt động chính xác trên hạ tầng mạng hiện tại. Bản vá đầu tiên thay đổi cách client phát hiện gateway, chuyển từ tin nhắn multicast M-SEARCH sang các yêu cầu unicast hướng tới địa chỉ LAN của firewall. Điều này cần thiết vì daemon miniupnpd của firewall biên không tham gia nhóm multicast bắt buộc, khiến việc khám phá thất bại mà không báo lỗi. Bản vá thứ hai lọc các giao diện mạng trong quá trình tạo socket, ngăn client thử ánh xạ cổng trên các giao diện bridge của Docker, vốn trả về lỗi 718 và làm sập tiến trình.
Bốn dòng mã này rất quan trọng để giữ cho cụm bảy nút (seven-node fleet) có thể truy cập được từ internet. Tuy nhiên, ứng dụng thường xuyên tự cập nhật, và mỗi lần cập nhật đều ghi đè phiên bản ssdp.js đã sửa đổi bằng phiên bản sạch từ upstream. Để khắc phục, đội ngũ đã triển khai một chiến lược tự động hóa theo lớp. Một cron job chạy mỗi phút để kiểm tra và áp dụng lại các bản vá. Bộ đếm thời gian @reboot với độ trễ 30 giây đảm bảo các bản vá được áp dụng trước khi ứng dụng khởi tạo trong quá trình khởi động. Ngoài ra, một watchdog trên firewall khởi động lại daemon UPnP mỗi hai phút để ngăn chặn sự trôi trạng thái (state drift), trong khi một bước xác minh riêng biệt chạy mỗi ba mươi phút để xác nhận các ánh xạ cổng vẫn nguyên vẹn.
Mặc dù có những biện pháp này, cụm máy chủ vẫn gặp khoảng một sự cố mỗi tuần. Chế độ lỗi chính liên quan đến các điều kiện tranh chấp (race conditions) trong quá trình cập nhật hoặc khởi động lại ứng dụng. Nếu ứng dụng nạp module trước khi cron job kịp áp dụng lại các bản vá, Node.js sẽ cache phiên bản lỗi trong bộ nhớ. Việc vá tệp trên đĩa sau đó không ảnh hưởng đến tiến trình đang chạy, đòi hỏi phải khởi động lại hoàn toàn để khắc phục. Sự không ổn định này kéo dài cho đến khi đội ngũ di chuyển sang một kiến trúc mạng mới giúp loại bỏ các lỗi cơ bản.
Chi tiết chính
- Giải pháp tạm bợ bao gồm bốn dòng JavaScript trong dependency
@runonflux/nat-upnpđể sửa các vấn đề về khám phá SSDP và ràng buộc socket. - Cần bốn cơ chế tự động hóa riêng biệt: cron job áp dụng lại mỗi phút, bộ đếm thời gian sleep
@reboot, firewall watchdog mỗi hai phút và bước xác minh mỗi ba mươi phút. - Các sự cố xảy ra khoảng một lần mỗi tuần, thường do cơ chế cache module của Node.js khóa mã chưa được vá trước khi cron job kịp can thiệp.
miniupnpdcủa firewall biên được cấu hình đúng nhưng không tham gia nhóm multicast, dẫn đến nhu cầu phải vá để sử dụng khám phá unicast.- Giải pháp cuối cùng là chuyển chức năng Internet Gateway Device sang một router VM với hỗ trợ multicast phù hợp, loại bỏ tất cả các bản vá và script tự động hóa.
- Các quy tắc cố định ngăn cản việc vá các tệp trong
ZelBack/src/do các kiểm tra tính toàn vẹn, buộc đội ngũ phải vá dependency bên ngoài thay thế.
Bối cảnh
Universal Plug and Play (UPnP) cho phép các thiết bị trên mạng cục bộ tự động cấu hình chuyển tiếp cổng (port forwarding) trên gateway. Client thường phát hiện gateway bằng cách gửi tin nhắn M-SEARCH tới một địa chỉ multicast cụ thể. Nếu gateway không lắng nghe nhóm multicast này, việc khám phá sẽ thất bại. Trong các môi trường container hóa như Docker, tồn tại nhiều giao diện mạng, bao gồm cả các bridge ảo. Các ứng dụng ràng buộc với tất cả các giao diện có thể thử đàm phán UPnP trên các bridge không thể định tuyến, dẫn đến lỗi hoặc sập. Module của Node.js được cache trong bộ nhớ sau lệnh require() đầu tiên, nghĩa là những thay đổi đối với tệp nguồn trên đĩa không ảnh hưởng đến các tiến trình đã chạy trừ khi chúng được khởi động lại.
Tại sao điều này quan trọng
Trường hợp này minh họa chi phí ẩn của việc duy trì các bản vá chịu tải (load-bearing patches) trên các dependency của bên thứ ba. Mặc dù các thay đổi mã rất đơn giản, nhưng gánh nặng vận hành lại đáng kể. Đội ngũ phải quản lý bốn thành phần tự động hóa riêng biệt chỉ để giữ cho ứng dụng hoạt động. Sự phức tạp này giới thiệu các chế độ lỗi mới, chẳng hạn như điều kiện tranh chấp giữa trình cập nhật và trình vá, khó gỡ lỗi hơn so với vấn đề mạng ban đầu. Đối với các đội ngũ chạy phần mềm tự lưu trữ (self-hosted software), điều này nhấn mạnh rủi ro khi dựa vào các giải pháp tạm bợ mong manh phải sống sót qua các bản cập nhật tự động.
Hơn nữa, sự cố này chứng minh những hạn chế của việc vá cấp độ tệp trong các môi trường runtime động. Vì Node.js cache các module, việc sửa tệp trên đĩa là không đủ nếu tiến trình đã nạp phiên bản lỗi. Điều này đòi hỏi sự phối hợp cẩn thận các lần khởi động lại và thời điểm, trở nên ngày càng khó khăn khi mở rộng quy mô. Các sự cố hàng tuần tiêu tốn thời gian kỹ thuật và giảm niềm tin vào độ tin cậy của cụm máy chủ, chứng minh rằng nợ kỹ thuật trong các script vận hành có thể gây hại ngang với nợ trong mã ứng dụng.
Bạn có thể làm gì
- Kiểm toán các cron job và script tự động của bạn để xác định bất kỳ cái nào tồn tại chỉ nhằm duy trì các bản vá thủ công hoặc giải pháp tạm bợ.
- Xác minh xem các dịch vụ mạng của bạn, chẳng hạn như daemon UPnP, có được cấu hình đúng để xử lý lưu lượng multicast hay không trước khi áp dụng các hack phía client.
- Kiểm tra xem runtime ứng dụng của bạn có cache các module hoặc cấu hình hay không, đảm bảo rằng các sửa đổi cấp độ tệp kích hoạt các lần khởi động lại tiến trình cần thiết.
- Cân nhắc các thay đổi kiến trúc, chẳng hạn như VM gateway chuyên dụng hoặc reverse proxy, để giải quyết các vấn đề tương thích mạng, loại bỏ nhu cầu vá.



