Tối ưu Spring Container với Spring Native

Spring Native Beta ra mắt vào ngày 11/3/2021, bạn đọc có thể thử nghiệm thông qua start.spring.io. Bài viết này sẽ khám phá các lợi ích của Spring Native trong việc xây dựng container khi so sánh với cách xây dựng ứng dụng truyền thống với Spring Boot.

TL;DR

  • Kích thước của container thật sự quan trọng và nên được tối ưu thu nhỏ
  • Spring Native là dự án hợp tác giữa đội ngũ phát triển Spring và GraalVM nhằm mục tiêu biên dịch các ứng dụng Java Spring thành các tệp thực thi độc lập (gọi là native image)
  • Lợi ích của Spring Native là giúp giảm kích thước của container và cho phép khởi chạy gần như tức thì (<100ms) với hiệu suất cao và tiêu thụ bộ nhớ thấp hơn, phù hợp với loại workload như microservices, cloud function và Kubernetes.
  • Ví dụ bên dưới cho kết quả tối ưu hơn 50% kích thước và bộ nhớ tiêu thụ, đặc biệt thời gian khởi chạy ứng dụng Spring giảm từ 5 giây về 0.09 giây.

Tại sao kích thước của container lại quan trọng?

Khi so sánh các đặc tính của container với virtual machine, rõ ràng container có nhiều ưu điểm hơn về tính portability và tính lightweight. Điều này làm cho các nhà phát triển đôi khi lại quên mất việc phải tối ưu hóa kích thước của container.

Trên thực tế, với sự bùng nổ của điện toán đám mây, dung lượng lưu trữ ngày càng rẻ và tạo ra cảm giác rằng việc tiết kiệm một vài MB là không quan trọng. Với các bạn tìm hiểu sâu về công nghệ container thì kỹ thuật Copy-On-Write, một tính năng quan trọng giúp cải thiện thời gian khởi động container đánh đổi bằng việc yêu cầu sử dụng thêm không gian lưu trữ cho các lớp. Do đó, khi thiết kế container, người viết được yêu cầu tránh tạo ra các container lớn.

Bỏ qua yếu tố về kích thước và giá cả lưu trữ, một container kích thước nhỏ có các lợi thế như sau:

  • Bảo mật: Container nhỏ hơn có nghĩa là số lượng thư viện bên trong ít hơn, ít lỗ hồng phần mềm hơn và giảm bớt các nguy cơ tấn công bề mặt, đồng thời minh bạch hơn về các thành phần bên trong.
  • Hiệu năng: Không chỉ thời gian khởi động nhanh hơn, các container nhỏ hơn cũng giúp cải thiện quá trình xây dựng và triển khai, dễ dàng di chuyển một cách nhanh chóng bên trong cluster.
  • Dễ bảo trì: Kích thước nhỏ hơn đồng nghĩa với việc có ít thư viện phụ thuộc, điều này giúp ích rất nhiều cho công việc cập nhật các bản vá.

Ứng dụng Spring Boot chạy trong Docker trước khi có Spring Native

Hãy tạo một ứng dụng Spring Boot đơn giản điển hình với JDK11, Docker dùng alpine làm base image. Bạn dùng tool nào cũng được: Spring Boot CLI, Maven Archetype, start.spring.io, v.v.

Một ứng dụng đơn giản với Spring và Docker

Kích thước của container là khoảng 167MB, sử dụng câu lệnh docker history để khảo sát các lớp có ở bên trong container này. Bạn đọc có thể dùng tham số --no-trunc để xem chi tiết.

Khảo sát các lớp bên trong của container chứa ứng dụng Spring Boot

Trong khi gói jar chỉ chiếm 17.1 MB, alpine base image là 5.58 MB, rất nhiều thứ như JRE và các thư viện cần thiết chiếm 144.2 MB!!!

Thử nghiệm Spring Native

Để giải quyết những nhược điểm trên, các nhà phát triển từ Spring và GraalVM đã cùng nhau xây dựng dự án Spring Native với mục tiêu chính là biên dịch các ứng dụng Spring trên nền tảng runtime của GraalVM để giảm kích thước và đem lại các lợi ích khác như: khởi chạy gần như tức thì (dưới 100ms), hiệu suất cao, tiêu thụ bộ nhớ thấp hơn đổi lại cần thời gian biên dịch lâu hơn và it các tùy chọn tối ưu hóa thời gian chạy.

Hiện tại, có 2 cách chính để biên dịch một ứng dụng Spring Boot Native:

  • Dùng Spring Boot Buildpacks để tạo container chứa tệp thực thi native
  • Dùng plugin GraalVM native image của Maven để tạo tệp thực thi native

Trong bài viết này, chúng ta bắt đầu với start.spring.io (chọn thêm Spring Native) và sử dụng Spring Boot Buildpacks với câu lệnh mvn spring-boot:build-image để tạo container.

Lưu ý: Chỉ phiên bản 2.4.3 của Spring Boot được hỗ trợ nên bạn sẽ cần sửa tệp POM.

Thời gian biên dịch sẽ khá lâu tùy cấu hình của máy, đặc biệt là trong lần đầu tiên cần cho phép Docker sử dụng nhiều hơn 8GB RAM. Container image được tạo với tên theo cú pháp: docker.io/library/[artifact]:[version]

Ứng dụng đơn giản với Spring Native và Docker

Kích thước của container chỉ 86MB và khi xem bên trong ta chỉ thấy các lớp đã được tối ưu hóa và tinh gọn với chỉ những gì thật sự cần thiết.

Khảo sát các lớp bên trong của container chứa ứng dụng Spring Boot Native

Đừng lo về việc sử dụng công nghệ của 41 năm trước, Buildpacks tạo các lớp với timestamp cố định. Không chỉ có kích thước nhỏ, thời gian khởi chạy cũng được tối ưu: ứng dụng chỉ mất 0.09 giây (so với Spring Boot mất trung bình từ 5 đến 6 giây). Nếu sử dụng câu lệnh docker stats chúng ta thấy ứng dụng chỉ tiêu thụ 59MB bộ nhớ (so với Spring Boot là 140MB), nghĩa là chúng ta có thể tiết kiệm tài nguyên cho các workload khác, điều này tạo ra khác biệt rất lớn!

Kết luận

Chúng ta sẽ cần chờ thêm một chút thời gian để đội dự án hoàn thành công việc của họ và phát hành bản ổn định của Spring Native, hỗ trợ các ứng dụng Spring sẵn mà không đòi hỏi sửa đổi nhiều. Native images giúp tối ưu chi phí lưu trữ và phù hợp cho nhiều loại workload, trong đó có microservices, cloud function và các ứng dụng chạy trên nền tảng Kubernetes. Trong thời gian đó, chúng ta có thể tìm hiểu kỹ hơn về những khác biệt lớn giữa JVM và nền tảng native này trên các phương diện về ưu - khuyết điểm, các đánh đổi và hạn chế khi sử dụng.

Atekco - Home for Authentic Technical Consultants