Mở đầu
Quá trình triển khai phổ biến hiện nay của các Single Page Application (SPA) là Code => Build => Package => Deploy.
Trong đó, build phase sẽ biên dịch code từ frame work thành code HTML, CSS, JS mà trình duyệt có thể hiểu được. Package phase đóng gói code đã được biên dịch vào một Web server image (Nginx, Apache) và sau đó Deploy phase sẽ triển khai Image lên các Container Engine Platform (Docker, K8S, Aws ECS, Sun Spinner…).
Trong phase Build, build command với tham số biến môi trường, các file config chứa biến môi trường tương ứng sẽ được biên dịch cùng với source code. Cách làm truyền thống này đáp ứng được hầu hết các yêu cầu trong quá trình phát triển nhưng vẫn tồn tại một số hạn chế:
- Tiêu tốn thời gian build không cần thiết: Do mỗi môi trường yêu cầu một phase build khác nhau mặc dù Source code sau khi biên dịch không có gì khác nhau ngoài các biến môi trường. Trên thực tế các dự án mình đã trải qua, quá trình build production của ứng dụng Angular mất khoảng 15 phút, thời gian build của reactjs thậm chí còn lên tới 30 phút hoặc hơn nữa. Thời gian build có thể thay đổi phụ thuộc khối lượng source code và cách optimize, nhưng rõ ràng những con số trên đáng để cân nhắc một giải pháp tối ưu hơn cho thời gian triển khai CICD.
- Không đủ linh hoạt cho các bài toán Cloud solution, dynamic config: Các bài toán về ứng dụng cloud, multi-tenant sẽ không khả thi nếu source code sau khi build chỉ có thể làm việc với một bộ configuration duy nhất
- Vấn đề bảo mật: Do dữ liệu cấu hình là một phần của source code, Mỗi thành viên đều có thể truy cập config của tất cả các môi trường, dẫn đến rủi ro về bảo mật và vận hành .
Các ứng dụng backend cũng gặp vấn đề này nhưng được giải quyết đơn giản bằng native environment của phía server, các biến môi trường có thể được inject qua phase Package nên một source code có thể làm việc với nhiều cấu hình khác nhau.
Cách giải quyết
Vậy giải pháp sẽ là phải làm sao để một bộ source code sau khi build có thể được inject các biến môi trường khác nhau tương tự như cách ứng dụng backend hoạt động. Sau đây là cách giải quyết vấn đề, nguyên lý chung này có thể áp dụng cho các web framework và ngôn ngữ khác nhau.
- Chuẩn bị giải pháp cho phép ứng dụng đọc cấu hình từ một nguồn độc lập với source code (File, API)
- Chỉnh sửa code của ứng dụng để thay thế các biến môi trường từ nguồn trên
- Đảm bảo cấu hình được load xong trước tiên, làm nguồn cho các render chức năng, giao diện về sau
Thực hành với Angular
Mặc định các file config tồn tại trong thư mục src/environments/ với format environment.<env name>.ts và chỉ dẫn build được đặt trong file angular.json. Với lệnh ng build –configuration=production Angular CLI sẽ thay thế file environment.ts bằng file environmen.prod.ts theo chỉ dẫn fileReplacement
Và các file logic code sẽ import và sử dụng biến môi trường từ file environment.ts.
Ta sẽ bỏ qua các cài đặt mặc định của Angular để thực hiện giải pháp theo các nguyên lý đã nêu ở trên.
Tạo một file cấu hình JSON có các key giống với file environment.ts trong src folder. Ví dụ env.json
Cấu hình location của file env.json vào phần assets trong angular.json để webpack giữ nguyên file này như một file assets
Tạo một service để đọc cấu hình từ file env.json, lưu ý vị trí tương đối của Service này với file env.json
Đảm bảo dữ liệu config được load đầu tiên khi khởi động ứng dụng
Kết quả
Vậy là ứng dụng đã đọc được nội dung từ một file hoặc từ một API độc lập. Nhưng từ từ đã, đến đây đã đạt được dự định ban đầu chưa, ta chưa thấy cách inject được giá trị configuration khác. Trên thực tế, sau khi tách file cấu hình ra khỏi phase Build là đã có thể giải quyết được vấn đề. Sau đây là một vài cách để thực hiện việc inject giá trị config.
- Inject value vào file env.json trong phase Package của quá trình CICD (thay thế giá trị của file này)
- Sử dụng API để lấy cấu hình từ phía backend không cần ghi đè file: Sử dụng API với các param đặc trưng cho phía client như IP, location, hostname… để làm parameter cho việc lấy cấu hình. Ví dụ một api với param /env?hostname=localhost:4200 sẽ trả về env với apiEndpoint là localhost:3000, hostname có thể lấy qua biến window.location của trình duyệt.
- Khi triển khai ứng dụng bằng các Container Engine Platform: các Engine này có hỗ trợ tính năng mount volume, lúc này trong phase Deploy, ta chỉ cần cấu hình mount các file chứa biến môi trường tương ứng từ hệ thống quản lý biến môi trường của Container Engine thay thế vào vị trí file env.json bên trong container chứa source code
Đọc thêm
Hướng dẫn cấu hình ConfigMap K8S
Hướng dẫn mount ConfigMap với Sun Spinner
Tạm kết
Với các thay đổi theo hướng dẫn ở trên, có thể triển khai ứng dụng web với các biến môi trường khác nhau gần như ngay lập tức, các sửa đổi biến môi trường cũng được apply gần như real time.
Tham khảo nhiều giải pháp cho hệ thống của bạn tại Sunteco Cloud
Trải nghiệm miễn phí hệ sinh thái điện toán đám mây Sunteco Cloud