kiến thức Xây dựng Docker image tối ưu cho Spring Boot

tonghoangvu

Senior Member
Lần trước có post nhưng bị mod xóa vì backlink, nay đăng lại bài full luôn. Link gốc tại đây nhé https://blog.pocketo.org/build-optimized-docker-images-for-spring-boot-applications

Dockerize ứng dụng Spring Boot vừa đơn giản nhưng cũng vừa phức tạp. Tối ưu image build ra là một trong những vấn đề quan trọng cần tính đến.
Bài viết trình bày kinh nghiệm nhóm Pocketo có được sau thời gian tìm hiểu & áp dụng Docker cho project Pocketo.

1. Approaches​

Có các công cụ xây dựng Docker image cho Spring Boot mà không cần tự viết Dockerfile, như Cloud Native Buildpacks (CNB) hay JIB.
Spring Boot tích hợp sẵn CNB, giúp build image thông qua command mvn spring-boot:build-image hoặc gradle bootBuildImage. JIB, một công cụ dockerize của Google, lại có khả năng build image không cần Docker daemon.
https://ashishtechmill.com/comparing-modern-day-container-image-builders-jib-buildpacks-and-docker
Hạn chế chính của các công cụ dockerize này là việc cấu hình phức tạp. Ví dụ, CNB có rất nhiều cấu hình cần tìm hiểu nếu muốn build được image tối ưu. Do đó, nhóm Pocketo chọn cách viết Dockerfile truyền thống, tránh cấu hình phức tạp và kiểm soát image tốt hơn.

2. Write Dockerfile​

Choose base image​

Nhóm Pocketo chọn OpenJDK làm base image, nhưng đã chuyển sang Eclipse Temurin sau khi OpenJDK có thông báo deprecation chính thức.

6cb9ba61-5701-48c9-b698-f84c26d9071b.png


Eclipse Temurin đáp ứng các tiêu chí nhóm đặt ra, bao gồm:
  • Là official image trên Docker Hub
  • Hỗ trợ Java 17
  • Có bản build dựa trên Alpine Linux
Việc hỗ trợ JRE là một điểm cộng lớn khi so sánh với các nhà cung cấp khác. Nhóm nhận thấy image size giảm đến hơn 50% khi chuyển base image từ openjdk:17-alpine sang eclipse-temurin:17-jre-alpine.

d618d3b7-61c8-4154-a2d7-4de78ae3ffc6.png

Layered JAR​

Spring Boot mặc định build ra một Fat JAR chứa toàn bộ code, dependencies, resources,... có thể chạy độc lập. Việc dockerize một Fat JAR khá đơn giản, chỉ cần copy vào image là được.
Diff:
# syntax=docker/dockerfile:1
FROM eclipse-temurin:17-jre-alpine
COPY ./target/app.jar ./app.jar
ENTRYPOINT ["java", "-jar", "./app.jar"]
Tuy nhiên đây chưa phải cách làm tối ưu. Spring Boot 2.3.0 cung cấp một phương pháp tốt hơn, tách Fat JAR thành 4 thư mục riêng dựa theo tần suất thay đổi (Layered JAR). Các thư mục trên được copy vào image tạo ra 4 layer tương ứng.

68747470733a2f2f7777772e6261656c64756e672e636f6d2f77702d636f6e74656e742f75706c6f6164732f323032302f31312f737072696e672d626f6f742d6c61796572732e6a7067


Layered JAR tận dụng được Docker cache, các layer nào không đổi sẽ được cache lại, giúp tăng tốc quá trình build, tạo ra image nhẹ và tối ưu hơn. Hình bên dưới là cấu trúc các layer của một Spring Boot app tiêu chuẩn và kích thước từng layer.

233421086-0d4eb7f5-f08e-41b3-a338-97c11c18e946.png


Với cách làm này, khi code thay đổi chỉ có application layer được build lại, các layer trước giữ nguyên nhờ cache. Phiên bản image mới chỉ bổ sung vài chục KB so với image trước đó.
Cách làm Fat JAR ban đầu chỉ tạo duy nhất một JAR layer. Khi code thay đổi dù là nhỏ nhất, toàn bộ layer phải build lại, không tận dụng được cache. Như vậy, mỗi phiên bản image build ra đều có thêm hơn 50 MB không cần thiết.
https://spring.io/blog/2020/08/14/creating-efficient-docker-images-with-spring-boot-2-3/
https://www.baeldung.com/docker-layers-spring-boot
Quá trình build lúc này chia thành hai giai đoạn, extract JAR thành các thư mục layer và copy vào stage mới.
Code:
# syntax=docker/dockerfile:1
FROM eclipse-temurin:17-jre-alpine AS builder
COPY ./target/app.jar ./app.jar
RUN java -Djarmode=layertools -jar ./app.jar extract

FROM eclipse-temurin:17-jre-alpine
COPY --from=builder /dependencies/ ./
COPY --from=builder /spring-boot-loader/ ./
COPY --from=builder /snapshot-dependencies/ ./
COPY --from=builder /application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
Cần đảm bảo file JAR build ra là định dạng Layered, bằng cách cấu hình Spring Boot plugin cho MavenGradle (các version mới đã bật theo mặc định).

Custom JRE with JLink​

Java 9 cung cấp công cụ JLink, giúp xây dựng JRE tùy chỉnh chỉ bao gồm các module cần thiết cho ứng dụng. Do đó giúp làm giảm kích thước Java runtime hơn nữa so với JRE hoặc JDK.

8f89f6b2-9b3e-4c8c-b339-b7c87f2fc07a.png


Ứng dụng Spring Boot tiêu chuẩn chỉ yêu cầu module java.se, các tính năng khác (như JVM remote debugging) có thể yêu cầu thêm các module tương ứng.
Code:
# syntax=docker/dockerfile:1
FROM eclipse-temurin:17-jdk-jammy AS builder
RUN $JAVA_HOME/bin/jlink \
    --add-modules java.se \
    --strip-debug \
    --no-man-pages \
    --no-header-files \
    --compress=2 \
    --output /jre/

FROM ubuntu:jammy
ENV JAVA_HOME=/opt/java/jre
ENV PATH "${JAVA_HOME}/bin:${PATH}"
COPY --from=builder /jre/ $JAVA_HOME

# Use Fat JAR for simplicity
COPY ./target/app.jar ./app.jar
ENTRYPOINT ["java", "-jar", "./app.jar"]
Sử dụng custom JRE yêu cầu thư viện glibc, trong khi Alpine chỉ có sẵn musl. Do đó không thể dùng Alpine làm base image được. Việc chuyển đổi base image sang OS có hỗ trợ glibc (như Ubuntu) sẽ làm tăng kích thước image, tuy nhiên sẽ không quá ảnh hưởng (xem phần Conclusion).

3. Others​

Enable BuildKit

Khi tùy chọn BuildKit được bật, Docker sử dụng builder mới giúp xây dựng image nhanh và hiệu quả hơn. Trên Docker Desktop hoặc Docker v23.0 trở lên, BuildKit được bật theo mặc định.
Nên đặt biến môi trường DOCKER_BUILDKIT=1 trước khi chạy docker build để đảm bảo tùy chọn luôn được bật.

Add .dockerignore file​

Luôn nên bao gồm file .dockerignore khi xây dựng Docker image. Bài viết dưới đây mô tả chi tiết quá trình build image và lý do vì sao nên làm vậy.
https://codefresh.io/blog/not-ignore-dockerignore-2/
Dự án Pocketo thực hiện build source code thành JAR từ bên ngoài, sau đó mới copy vào image. Trong trường hợp này, build context chỉ cần bao gồm các output JAR là được.
Code:
# Excludes all files & folders by default
*.*
*/

# Includes necessary files & folders
!target/app.jar

Debugging​

Nhu cầu phát sinh khi chạy ứng dụng trong container là khả năng debug. Việc này yêu cầu điều chỉnh một số cấu hình trong IDE và Dockerfile.
Trong IDE cần setup tính năng Remote JVM debug. Chú ý chọn version JDK và copy lại tham số dòng lệnh hiện ra.

4eaf208d-0c01-43fc-9fc4-06ea874f891c.png


Ứng dụng Java trong container cần bật hỗ trợ debug với JDWP bằng cách thêm option đã copy khi chạy lệnh java.
Code:
# Use Fat JAR for simplicity
ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "./app.jar"]
EXPOSE 5005
Ứng dụng Java khi chạy sẽ listen trên port 5005, khi chạy debugger trong IDE sẽ kết nối đến port này và bắt đầu debug. Do đó, cần expose port 5005 ra ngoài container (chỉ định EXPOSE trong Dockerfile và dùng port mapping).
Cần thêm module jdk.jdwp.agent khi build custom JRE, bằng cách chạy jlink với tham số --add-modules java.se,jdk.jdwp.agent.

4. Conclusion​

Trên đây là các phương pháp xây dựng và tối ưu Docker image cho Spring Boot. Trong thực tế, việc lựa chọn mức độ tối ưu phụ thuộc vào từng trường hợp cụ thể.

Image size​

Trong nhiều trường hợp, image size không quá quan trọng nhờ có Docker cache. Các layer nặng như OS, Java runtime chỉ phải transfer một lần và được cache lại. Layer ứng dụng sẽ tận dụng được cache nếu build theo dạng Layered JAR.
Có nên dùng JLink để tạo ra image siêu nhỏ không?
Thông thường là không, việc dùng JLink tối ưu image size không có quá nhiều lợi ích. Bên cạnh đó, JLink cũng mang lại một số hạn chế, như không thể dùng Alpine base image và nguy cơ thiếu module.
Thay vào đó, chỉ cần sử dụng JRE (nếu có) thay vì JDK. Việc này giúp giảm image size mà không có nhược điểm nào.
Có nên dùng Alpine Linux không? Hay nên dùng các base image khác như Ubuntu?
Tùy vào từng trường hợp, cần cân nhắc thêm các yếu tố khác, thay vì chỉ chú trọng vào image size.
Alpine có dung lượng nhỏ hơn, tuy nhiên lại không đầy đủ tính năng như Ubuntu, độ phổ biến cũng kém hơn. Một số chương trình có nguy cơ suy giảm hiệu suất khi chạy trên Alpine, do Alpine sử dụng musl thay vì glibc.
https://superuser.com/questions/121...er-image-over-50-slower-than-the-ubuntu-image

Build time​

Build time đôi lúc cũng không quá quan trọng, khác biệt một vài giây có thể bỏ qua. Điều này lại phụ thuộc vào tần suất build image, nếu image phải build thường xuyên thì nên giảm thời gian build.
Ví dụ, một hệ thống thực hiện build image trong CI pipeline, việc build không diễn ra thường xuyên nên không cần quá chú trọng build time (tất nhiên cũng không được quá chậm).
Thêm một ví dụ khác, nếu bắt buộc chạy ứng dụng trong container ở môi trường local development, nghĩa là phải build thường xuyên, thì build time phải càng nhanh càng tốt, tránh ảnh hưởng năng suất làm việc.
Ví dụ 2 là một red flag của việc lạm dụng Docker quá mức. Dù image có build nhanh thế nào, việc build lại image với mỗi code change sẽ dẫn tới DX (development experience) cực kỳ tệ. Thay vào đó, nên làm cho ứng dụng chạy bình thường (không ở trong container) khi làm việc dưới local environment.
Nên chọn Fat JAR hay Layered JAR? Tôi biết Layered JAR tối ưu hơn, nhưng Fat JAR thì build nhanh hơn.
Luôn nên chọn Layered JAR. Như trên, build time chênh lệch 1, 2 giây sẽ không quá ảnh hưởng so với các lợi ích nhận được từ phương pháp Layered JAR.
Nên build image từ source code hay từ file JAR bên ngoài?
Với ứng dụng Java thì nên build source code bên ngoài và copy file JAR vào image. Các phương pháp tối ưu hóa ở trên sẽ được áp dụng dễ dàng hơn.

Source code: https://github.com/pocketo-app/spring-boot-dockerizing
Cover image: https://developer.okta.com/blog/2020/12/28/spring-boot-docker
Nguồn tham khảo:
 
Last edited:
Code:
# syntax=docker/dockerfile:1
FROM eclipse-temurin:17-jre-alpine AS builder
COPY ./target/app.jar ./app.jar
RUN java -Djarmode=layertools -jar ./app.jar extract

FROM eclipse-temurin:17-jre-alpine
COPY --from=builder /dependencies/ ./
COPY --from=builder /spring-boot-loader/ ./
COPY --from=builder /snapshot-dependencies/ ./
COPY --from=builder /application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

Đoạn này mình không hiểu tại sao lại phải build cái jar file ở bên ngoài rồi copy cái jar đó vào bên trong docker để chạy, mà không copy code vào docker rồi build jar ở trong môi trường docker luôn.

Môi trường build ở bên ngoài có thể là 1 jdk khác và môi trường docker chạy jar là 1 jdk khác thì có ảnh hưởng gì không nhỉ.
 
Đoạn này mình không hiểu tại sao lại phải build cái jar file ở bên ngoài rồi copy cái jar đó vào bên trong docker để chạy, mà không copy code vào docker rồi build jar ở trong môi trường docker luôn.

Môi trường build ở bên ngoài có thể là 1 jdk khác và môi trường docker chạy jar là 1 jdk khác thì có ảnh hưởng gì không nhỉ.
theo chút kinh nghiệm của mình (mình ko làm java) thì thường build docker image ngta sẽ chia nhiều stage nhằm tận dụng cache cũng như lược bớt 1 số thứ ko cần thiết ... ở trên ts build và runner nên để 2 chỗ thì cũng như mình nói trên là để lược bớt 1 số thứ - 1 số thứ ở trong trường hợp này có thể là raw code ko cần thiết chẳng hạn
 
Đoạn này mình không hiểu tại sao lại phải build cái jar file ở bên ngoài rồi copy cái jar đó vào bên trong docker để chạy, mà không copy code vào docker rồi build jar ở trong môi trường docker luôn.

Môi trường build ở bên ngoài có thể là 1 jdk khác và môi trường docker chạy jar là 1 jdk khác thì có ảnh hưởng gì không nhỉ.
build ở ngoài thì nhanh hơn chứ, build trong docker biết khi nào mới xong. Mình từng optimize của 1 ông build trong docker cỡ 20p, ra ngoài build cỡ 4p là xong rồi.
Thông thường ở local chạy jdk bao nhiêu thì người ta sẽ chọn image tương ứng với JDK đó
 
Đoạn này mình không hiểu tại sao lại phải build cái jar file ở bên ngoài rồi copy cái jar đó vào bên trong docker để chạy, mà không copy code vào docker rồi build jar ở trong môi trường docker luôn.

Môi trường build ở bên ngoài có thể là 1 jdk khác và môi trường docker chạy jar là 1 jdk khác thì có ảnh hưởng gì không nhỉ.
xử lý như thím thì cần gì cicd nữa ?
Hay trong container đấy lại tốn thêm 1 bước setup cicd bên trong ?
 
Đoạn này mình không hiểu tại sao lại phải build cái jar file ở bên ngoài rồi copy cái jar đó vào bên trong docker để chạy, mà không copy code vào docker rồi build jar ở trong môi trường docker luôn.

Môi trường build ở bên ngoài có thể là 1 jdk khác và môi trường docker chạy jar là 1 jdk khác thì có ảnh hưởng gì không nhỉ.
1: Tránh bị lỗ hổng bảo mật CVE khi build 1 đống dependencies mới chạy được những cái dưới đây
1687402892659.png

2: Từ (1) suy ra tránh phồng dung lượng của image khi không cần build những dependencies ko dùng tới, giảm thời gian build image
 
theo chút kinh nghiệm của mình (mình ko làm java) thì thường build docker image ngta sẽ chia nhiều stage nhằm tận dụng cache cũng như lược bớt 1 số thứ ko cần thiết ... ở trên ts build và runner nên để 2 chỗ thì cũng như mình nói trên là để lược bớt 1 số thứ - 1 số thứ ở trong trường hợp này có thể là raw code ko cần thiết chẳng hạn

Ý mình cũng là copy code vào dùng multi stage build đó, stage final thì cũng là compiled files để chạy thôi chứ không có readable code.

build ở ngoài thì nhanh hơn chứ, build trong docker biết khi nào mới xong. Mình từng optimize của 1 ông build trong docker cỡ 20p, ra ngoài build cỡ 4p là xong rồi.
Thông thường ở local chạy jdk bao nhiêu thì người ta sẽ chọn image tương ứng với JDK đó
Nếu như build ở ngoài và build ở trong khác biệt nhiều x5 lần như vậy có khi nào build ở trong không được cache dependencies dẫn đến phải tải trên maven về mỗi khi build không? Còn ở ngoài thì depencies có sẵn rồi nên không phải tải khi build.

xử lý như thím thì cần gì cicd nữa ?
Hay trong container đấy lại tốn thêm 1 bước setup cicd bên trong ?
Mình chưa hiểu ý bạn setup ci/cd bên trong docker là để làm gì. Nếu build bên ngoài bằng maven/gradle được thì bên trong docker vẫn build được bình thường ra jar file và tiếp tục các stage của chủ thớt để chạy.

1: Tránh bị lỗ hổng bảo mật CVE khi build 1 đống dependencies mới chạy được những cái dưới đây
View attachment 1909876
2: Từ (1) suy ra tránh phồng dung lượng của image khi không cần build những dependencies ko dùng tới, giảm thời gian build image
Mình nghĩ nếu dùng multi stage build thì kết quả build bên ngoài host và build bên trong docker vẫn ra jar file có kết qua như nhau mà nhỉ.
Build trong docker thì không cần môi trường host phải có jdk, nên ở CI/CD chẳng hạn thì không cần thiết phải có đúng phiên bản JDK mà project cần dùng để build được image chỉ cần docker.
-----
Trước giờ mình vẫn hay dùng multi-stage build để làm docker image, nghĩ nó là best practices mà hôm nay mới thấy nhiều bác lại có cách làm khác, chẳng lẽ trước giờ mình làm là bad practices sao :eek:
 
Ý mình cũng là copy code vào dùng multi stage build đó, stage final thì cũng là compiled files để chạy thôi chứ không có readable code.


Nếu như build ở ngoài và build ở trong khác biệt nhiều x5 lần như vậy có khi nào build ở trong không được cache dependencies dẫn đến phải tải trên maven về mỗi khi build không? Còn ở ngoài thì depencies có sẵn rồi nên không phải tải khi build.


Mình chưa hiểu ý bạn setup ci/cd bên trong docker là để làm gì. Nếu build bên ngoài bằng maven/gradle được thì bên trong docker vẫn build được bình thường ra jar file và tiếp tục các stage của chủ thớt để chạy.


Mình nghĩ nếu dùng multi stage build thì kết quả build bên ngoài host và build bên trong docker vẫn ra jar file có kết qua như nhau mà nhỉ.
Build trong docker thì không cần môi trường host phải có jdk, nên ở CI/CD chẳng hạn thì không cần thiết phải có đúng phiên bản JDK mà project cần dùng để build được image chỉ cần docker.
-----
Trước giờ mình vẫn hay dùng multi-stage build để làm docker image, nghĩ nó là best practices mà hôm nay mới thấy nhiều bác lại có cách làm khác, chẳng lẽ trước giờ mình làm là bad practices sao :eek:
ý bác là stage đầu build luôn jar file à, làm cái đó cũng dc hoặc nếu có file sẵn thì copy cho nó lẹ
 
Trước giờ mình vẫn hay dùng multi-stage build để làm docker image, nghĩ nó là best practices mà hôm nay mới thấy nhiều bác lại có cách làm khác, chẳng lẽ trước giờ mình làm là bad practices sao :eek:
Làm cách bác cũng được mà, có sao đâu. Mà lạ chỗ các tut docker cho nodejs hoặc FE thì đều copy source vào làm build stage, còn bên java toàn copy JAR đã build :( Hóng cao thủ vào thông não giúp.
 
Ý mình cũng là copy code vào dùng multi stage build đó, stage final thì cũng là compiled files để chạy thôi chứ không có readable code.


Nếu như build ở ngoài và build ở trong khác biệt nhiều x5 lần như vậy có khi nào build ở trong không được cache dependencies dẫn đến phải tải trên maven về mỗi khi build không? Còn ở ngoài thì depencies có sẵn rồi nên không phải tải khi build.


Mình chưa hiểu ý bạn setup ci/cd bên trong docker là để làm gì. Nếu build bên ngoài bằng maven/gradle được thì bên trong docker vẫn build được bình thường ra jar file và tiếp tục các stage của chủ thớt để chạy.


Mình nghĩ nếu dùng multi stage build thì kết quả build bên ngoài host và build bên trong docker vẫn ra jar file có kết qua như nhau mà nhỉ.
Build trong docker thì không cần môi trường host phải có jdk, nên ở CI/CD chẳng hạn thì không cần thiết phải có đúng phiên bản JDK mà project cần dùng để build được image chỉ cần docker.
-----
Trước giờ mình vẫn hay dùng multi-stage build để làm docker image, nghĩ nó là best practices mà hôm nay mới thấy nhiều bác lại có cách làm khác, chẳng lẽ trước giờ mình làm là bad practices sao :eek:
Mình vẫn viết Dockerfile multi-stage copy code build, tách layer và thấy như thế hợp lý hơn là build jar ở ngoài xong copy vào.
Phần tốc độ build ở ngoài và docker khác nhau 5 lần có thể do cache dependency, hiện tại cứ xài buildkit và cache lại .m2 là được
Code:
RUN --mount=type=cache,target=/root/.m2 ./mvnw install -DskipTests
 
Nếu như build ở ngoài và build ở trong khác biệt nhiều x5 lần như vậy có khi nào build ở trong không được cache dependencies dẫn đến phải tải trên maven về mỗi khi build không? Còn ở ngoài thì depencies có sẵn rồi nên không phải tải khi build.
Cũng có thể là do nguyên này để build chậm đến như vậy. Nếu tìm cách cache được dependencies có thể sẽ improve được thời gian build.
Mình thấy nhiều CICD của dự án Java của nhiều nước (Mình từng làm với Hàn, Trung, Nhật, Myanmar, Ấn...) thì họ hay dùng gitlabci để build rồi copy file jar để build image
 
Cũng có thể là do nguyên này để build chậm đến như vậy. Nếu tìm cách cache được dependencies có thể sẽ improve được thời gian build.
Mình thấy nhiều CICD của dự án Java của nhiều nước (Mình từng làm với Hàn, Trung, Nhật, Myanmar, Ấn...) thì họ hay dùng gitlabci để build rồi copy file jar để build image
Đương nhiên phải cache rồi bác, em cũng toàn copy toàn bộ src vào stage build xong build, có option để lưu lại dependencies để mỗi lần build k phải download lại nữa. Trước giờ nếu phải viết dockerfile đều làm vậy. Nay cũng mới biết nhiều ae làm ngược lại nên k biết cách mình làm có hợp lí k :sick:Nếu chọn option build ngoài rồi nhét file jar vào image thì runner chạy lệnh build lại phải cài đặt đúng môi trường so với dockerfile, e thấy nó k hợp lí lắm
 
Ý mình cũng là copy code vào dùng multi stage build đó, stage final thì cũng là compiled files để chạy thôi chứ không có readable code.


Nếu như build ở ngoài và build ở trong khác biệt nhiều x5 lần như vậy có khi nào build ở trong không được cache dependencies dẫn đến phải tải trên maven về mỗi khi build không? Còn ở ngoài thì depencies có sẵn rồi nên không phải tải khi build.


Mình chưa hiểu ý bạn setup ci/cd bên trong docker là để làm gì. Nếu build bên ngoài bằng maven/gradle được thì bên trong docker vẫn build được bình thường ra jar file và tiếp tục các stage của chủ thớt để chạy.


Mình nghĩ nếu dùng multi stage build thì kết quả build bên ngoài host và build bên trong docker vẫn ra jar file có kết qua như nhau mà nhỉ.
Build trong docker thì không cần môi trường host phải có jdk, nên ở CI/CD chẳng hạn thì không cần thiết phải có đúng phiên bản JDK mà project cần dùng để build được image chỉ cần docker.
-----
Trước giờ mình vẫn hay dùng multi-stage build để làm docker image, nghĩ nó là best practices mà hôm nay mới thấy nhiều bác lại có cách làm khác, chẳng lẽ trước giờ mình làm là bad practices sao :eek:
nghĩa là bước build ra file jar thì cicd đã làm rồi việc gì thím phải tự đi làm lại thêm 1 làn nữa .

via theNEXTvoz for iPhone
 
Cũng có thể là do nguyên này để build chậm đến như vậy. Nếu tìm cách cache được dependencies có thể sẽ improve được thời gian build.
Mình thấy nhiều CICD của dự án Java của nhiều nước (Mình từng làm với Hàn, Trung, Nhật, Myanmar, Ấn...) thì họ hay dùng gitlabci để build rồi copy file jar để build image
Chắc cũng tùy dự án khác nhau, và việc build ngoài hay trong cũng chưa có tạo ra vấn đề lớn nên mỗi project có 1 cách làm khác nhau. Có một số dự án mình biết thì kể cả ở gitlabci / azure devops thì build jar cũng nằm trong docker container luôn cho dù mục đích là không phải là deploy và chạy trong docker container.

Cách họ build là vẫn copy code vào trong docker, build ra jar file rồi copy jar file trong container đó ra ngoài sau đó dùng làm mục đích khác.

Mục đích đó có thể là copy file jar vào docker để build image deploy như bài viết ở bên trên. Hoặc file jar đó cũng để lưu ở artifact repository, hoặc dùng làm library nếu không phải là runnable jar... etc.

Việc build ở bên trong container đảm bảo 1 việc là ở bất kỳ môi trường nào cũng có thể build được, không bị những lỗi lạ như permission hoặc biến môi trường của máy host, pipelines. Kết quả build jar ở trong 1 docker container sẽ có tính nhất quán cao, không có sự khác biệt giữa việc build ở các máy khác nhau chỉ cần có docker là được.:love:

nghĩa là bước build ra file jar thì cicd đã làm rồi việc gì thím phải tự đi làm lại thêm 1 làn nữa .

via theNEXTvoz for iPhone
Nếu như có 1 bước trước đó trong pipelines có được file jar đó thì mình nghĩ việc copy jar đó vào docker để build tiếp image deploy như chủ thớt là hợp lý. Hoặc có thể pipelines build image deploy không cần build jar file mà pull từ 1 private artifact repository về ...etc lúc này không cần source code để có được jar file.

Còn build cách nào trực tiếp trên môi trường pipelines bên ngoài docker hay bên trong docker là 1 chuyện khác, có thể project phức tạp không thể build bên trong docker nên người ta phải build bên ngoài, nhưng trước giờ may mà mình chưa gặp project dạng đó. Nếu build được bằng docker thì mình luôn sẽ build bên trong docker.

Sau này nếu pipeline agent có upgrade thay đổi version linux, hay có mang nó qua các pipelines khác gitlab, github, azure devops thì môi trường của những CI đó sẽ không làm ảnh hưởng gì đến kết quả build. Và trong pipelines cũng không đảm bảo có sẵn toàn bộ ngôn ngữ + version của java/python/golang/node ... etc nên build trong docker vẫn là lựa chọn số 1 của mình. Chỉ sợ sau này docker hub đóng cửa thôi :byebye:
 
Back
Top