Người thực hiện: Tôn Thất Bách – Technical Leader, Mynavi TechTus Vietnam
Mở đầu
Biến môi trường (Environment Variables – ENV) là nơi các lập trình viên thường lưu trữ những thông tin cấu hình quan trọng như mật khẩu cơ sở dữ liệu, khóa API hay địa chỉ máy chủ. Nhiều người mặc định rằng: một khi đã khai báo ENV thì chương trình sẽ luôn đọc được đúng giá trị đó. Thực tế thì không phải lúc nào cũng đơn giản như vậy. Trong quá trình phát triển phần mềm, chúng tôi đã gặp không ít tình huống mà biến ENV vẫn tồn tại, nhưng ứng dụng lại đọc sai, đọc không được hoặc sử dụng một giá trị hoàn toàn khác so với mong đợi. Hai câu chuyện dưới đây là những ví dụ điển hình.
Vấn đề đặt ra là “Sao request đầu đọc được, request sau lại không?”
Trong một dự án Laravel, Thành – kỹ sư Backend – gặp một lỗi rất khó hiểu. Ở lần gửi yêu cầu đầu tiên, ứng dụng vẫn đọc được giá trị từ biến ENV thông qua hàm env(). Thế nhưng ngay từ request thứ hai, giá trị này lại biến mất. Trong khi đó, Hùng – kỹ sư Frontend – cũng phát hiện một vấn đề khác. Dù đã thay đổi cấu hình trên máy cá nhân, ứng dụng vẫn sử dụng các giá trị cũ như thể chưa từng được cập nhật. Ban đầu, hai lỗi này có vẻ không liên quan. Nhưng nguyên nhân thực chất lại rất giống nhau.
Điều gì đã xảy ra?
Laravel không khuyến khích gọi trực tiếp env() trong quá trình ứng dụng đang hoạt động. Thay vào đó, framework mong muốn tất cả giá trị từ ENV được đọc ngay khi ứng dụng khởi động và lưu vào các tệp cấu hình (config). Đặc biệt trên môi trường Production, Laravel còn cung cấp lệnh: “php artisan config:cache”. Lệnh này sẽ gom toàn bộ cấu hình thành một bộ nhớ đệm để ứng dụng khởi động nhanh hơn. Đổi lại, sau khi cache được tạo, việc gọi env() trực tiếp trong mã nguồn sẽ không còn đáng tin cậy. Có lúc chương trình vẫn đọc được giá trị, có lúc lại trả về null hoặc dữ liệu không như mong đợi. Đó cũng chính là nguyên nhân khiến Thành gặp lỗi. Thay vì lấy dữ liệu từ file cấu hình, đoạn mã lại gọi env() ngay trong lúc chương trình đang chạy.
Một năm trước, khi phát triển một dự án chạy trên AWS Lambda bằng Node.js, nhóm chúng tôi cũng gặp một tình huống tương tự. Sau khi nâng cấp Node.js từ phiên bản 6 lên 8, toàn bộ thời gian hệ thống trả về đều là UTC, trong khi ứng dụng phải sử dụng múi giờ Nhật Bản (JST). Kiểm tra lại mã nguồn, mọi thứ đều có vẻ đúng. Ngay đầu chương trình đã có dòng: “process.env.TZ = “Asia/Tokyo”; Nhưng kết quả vẫn sai. Sau quá trình điều tra, chúng tôi phát hiện Node.js chỉ đọc biến TZ khi khởi tạo đối tượng thời gian lần đầu tiên. Trong môi trường Serverless như AWS Lambda, quá trình khởi tạo này đôi khi đã diễn ra trước khi đoạn mã của ứng dụng được thực thi. Điều đó có nghĩa là dù chương trình đã gán lại process.env.TZ, hệ thống vẫn tiếp tục sử dụng múi giờ cũ. Giải pháp là cấu hình biến TZ trực tiếp trong phần Environment Variables của AWS Lambda thay vì thiết lập trong mã nguồn.
Bài học rút ra
Không phải lúc nào thay đổi ENV cũng đồng nghĩa với việc ứng dụng sẽ sử dụng giá trị mới ngay lập tức. Mỗi framework hoặc nền tảng đều có cách đọc và lưu trữ biến môi trường khác nhau. Có nơi chỉ đọc một lần khi khởi động, có nơi lưu vào bộ nhớ đệm, cũng có nơi giá trị đã được “đóng băng” trước khi mã nguồn của bạn được thực thi. Nếu không hiểu cơ chế này, bạn rất dễ gặp những lỗi khó tái hiện và mất nhiều thời gian để tìm nguyên nhân.
Một vài kinh nghiệm khi làm việc với ENV
Để hạn chế những lỗi tương tự, nhóm chúng tôi luôn tuân theo một số nguyên tắc:
- Chỉ đọc biến ENV ngay khi ứng dụng khởi động, sau đó sử dụng lại thông qua các tệp cấu hình.
- Không gọi
env()rải rác trong nhiều nơi của mã nguồn. - Nếu framework hỗ trợ cache cấu hình, hãy bật tính năng này trên môi trường Production để vừa tăng hiệu năng vừa đảm bảo tính nhất quán.
- Với Docker, AWS hoặc các hệ thống triển khai khác, nên cấu hình ENV trực tiếp trên môi trường chạy thay vì phụ thuộc vào các tệp
.env.
Một lưu ý cuối cùng
Ngay cả khi bạn không thay đổi biến ENV, vẫn có khả năng giá trị của nó bị tác động bởi các thư viện bên thứ ba. Ví dụ, AWS SDK dành cho Greengrass IoT từng có cơ chế tự động thay đổi giá trị của biến NODE_TLS_REJECT_UNAUTHORIZED, khiến hành vi của ứng dụng khác với kỳ vọng. Đó là lý do vì sao việc kiểm tra môi trường chạy (Runtime) luôn quan trọng không kém việc kiểm tra chính mã nguồn.
Kết luận
Biến môi trường giúp việc quản lý cấu hình trở nên đơn giản và an toàn hơn, nhưng chúng không phải lúc nào cũng hoạt động theo cách chúng ta nghĩ. Để tránh những lỗi khó phát hiện, lập trình viên cần hiểu rõ cách framework và nền tảng mình sử dụng xử lý ENV trong quá trình khởi động và vận hành. Chỉ khi nắm được cơ chế đó, bạn mới có thể xây dựng một hệ thống ổn định, dễ bảo trì và tránh được những “cú lừa” đến từ chính môi trường Runtime.