בניית תמונות Docker אופטימליות עבור יישום Spring Boot

קונטיינרים הפכו לאמצעי המועדף לאריזת אפליקציה עם כל התלות בתוכנה ובמערכת ההפעלה שלה ולאחר מכן להעביר אותן לסביבות שונות.

מאמר זה מכסה דרכים שונות להכיל יישום Spring Boot במכולה:

  • בניית תמונת docker באמצעות dockerfile,
  • בניית תמונת OCI ממקור באמצעות Cloud-Native Buildpack,
  • ואופטימיזציה של תמונה בזמן ריצה על ידי הפרדת חלקי JAR לרמות שונות באמצעות כלים שכבות.

 דוגמה לקוד

מאמר זה מלווה בדוגמה של קוד עבודה ב-GitHub .

טרמינולוגיה של מיכל

נתחיל בטרמינולוגיית המכולה המשמשת לאורך המאמר:

  • תמונת מיכל: קובץ בפורמט מסוים. אנו ממירים את האפליקציה שלנו לתמונת מכיל על ידי הפעלת כלי הבנייה.
  • מכולה: מופע בר הפעלה של תמונת המכולה.
  • מנוע מיכל: תהליך הדמון שאחראי להפעלת הקונטיינר.
  • מארח מיכל: המכונה המארח שבה פועל מנוע המכולה.
  • רישום מיכל: המיקום הכללי המשמש לפרסום והפצה של תמונת המכולה.
  • תקן OCI: יוזמת מיכל פתוחה (OCI) - הוא מבנה ניהולי קל ופתוח שנוצר במסגרת של Linux יסוד. מפרט התמונות של OCI מגדיר סטנדרטים בתעשייה עבור פורמטים וזמני ריצה של תמונות קונטיינרים כדי להבטיח שכל מנועי הקונטיינרים יוכלו להריץ תמונות קונטיינרים שנוצרו על ידי כל כלי בנייה.

כדי לאכלס אפליקציה, אנו עוטפים את האפליקציה שלנו בתמונת מכיל ומפרסמים את התמונה הזו לרישום הציבורי. זמן הריצה של מיכל מאחזר תמונה זו מהרישום, פורק אותה ומריץ את היישום בתוכה.

גרסה 2.3 של Spring Boot מספקת תוספים לבניית תמונות OCI.

סַוָר הוא המימוש הנפוץ ביותר של קונטיינר, ואנו משתמשים ב-Docker בדוגמאות שלנו, כך שכל הפניות הקונטיינר הבאות במאמר זה יתייחסו ל-Docker.

בניית תמונת מיכל בדרך המסורתית

בניית תמונות Docker עבור יישומי Spring Boot היא קלה מאוד על ידי הוספת כמה הוראות ל- Dockerfile שלך.

אנו יוצרים תחילה JAR בר הפעלה וכחלק מהוראות Dockerfile, מעתיקים את ה-JAR להפעלה על גבי תמונת JRE הבסיסית לאחר החלת ההתאמות הנחוצות.

בואו ניצור את אפליקציית האביב שלנו ב- ראשוני האביב עם תלות web, lombokи actuator. אנחנו גם מוסיפים בקר מנוחה כדי לספק API עם GETשיטה.

יצירת Dockerfile

לאחר מכן אנו מניחים את היישום הזה במיכל על ידי הוספה Dockerfile:

FROM adoptopenjdk:11-jre-hotspot
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/application.jar"]

Dockerfile שלנו מכיל תמונת בסיס, מ adoptopenjdk, שעליו אנו מעתיקים את קובץ ה-JAR שלנו ואז פותחים את היציאה, 8080אשר יקשיב לבקשות.

הרכבת יישום

ראשית עליך ליצור יישום באמצעות Maven או Gradle. כאן אנו משתמשים ב-Maven:

mvn clean package

זה יוצר קובץ JAR בר הפעלה עבור היישום. אנחנו צריכים להמיר את JAR ההפעלה הזה לתמונת Docker כדי לפעול על מנוע Docker.

צור תמונת מיכל

לאחר מכן הכנסנו את קובץ ההפעלה של JAR לתמונה של Docker על ידי הפעלת הפקודה docker buildמספריית השורש של הפרויקט המכילה את ה-Dockerfile שנוצר קודם לכן:

docker build  -t usersignup:v1 .

אנו יכולים לראות את התמונה שלנו ברשימה עם הפקודה:

docker images 

הפלט של הפקודה לעיל כולל את התמונה שלנו usersignupיחד עם תמונת הבסיס, adoptopenjdkהמצוין ב- Dockerfile שלנו.

REPOSITORY          TAG                 SIZE
usersignup          v1                  249MB
adoptopenjdk        11-jre-hotspot      229MB

הצג שכבות בתוך תמונת מיכל

בואו נסתכל על ערימת השכבות בתוך התמונה. אנחנו נשתמש כלי  לִצְלוֹל, כדי להציג את השכבות האלה:

dive usersignup:v1

הנה חלק מהפלט של פקודת הצלילה: 

בניית תמונות Docker אופטימליות עבור יישום Spring Boot

כפי שאנו יכולים לראות, שכבת היישום מהווה חלק ניכר מגודל התמונה. אנו רוצים להקטין את גודל השכבה הזו בסעיפים הבאים כחלק מהאופטימיזציה שלנו.

בניית תמונת מיכל עם Buildpack

חבילות הרכבה (Buildpacks) הוא מונח כללי המשמש הצעות שונות של Platform as a Service (PAAS) ליצירת תמונת מיכל מקוד המקור. הוא הושק על ידי Heroku בשנת 2011 ומאז אומץ על ידי Cloud Foundry, Google App Engine, Gitlab, Knative ועוד כמה.

בניית תמונות Docker אופטימליות עבור יישום Spring Boot

היתרון של חבילות בניית ענן

אחד היתרונות העיקריים של השימוש ב-Buildpack לבניית תמונות הוא זה ניתן לנהל שינויים בתצורת תמונה באופן מרכזי (בונה) ולהפיץ אותם לכל היישומים המשתמשים בבונה.

חבילות הבנייה היו קשורות קשר הדוק לפלטפורמה. Cloud-Native Buildpacks מספקים סטנדרטיזציה בין פלטפורמות על ידי תמיכה בפורמט התמונה OCI, מה שמבטיח שניתן להפעיל את התמונה על ידי מנוע Docker.

שימוש בתוסף Spring Boot

התוסף Spring Boot בונה תמונות OCI ממקור באמצעות Buildpack. תמונות נוצרות באמצעות bootBuildImageמשימות (Gradle) או spring-boot:build-imageיעד (Maven) והתקנה מקומית של Docker.

אנו יכולים להתאים אישית את שם התמונה שאנו צריכים לדחוף ל-Docker Registry על ידי ציון השם image tag:

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    <image>
      <name>docker.io/pratikdas/${project.artifactId}:v1</name>
    </image>
  </configuration>
</plugin>

בואו נשתמש ב-Maven לביצוע build-imageמטרות ליצירת אפליקציה ויצירת תמונת מיכל. אנחנו לא משתמשים כרגע בשום Dockerfiles.

mvn spring-boot:build-image

התוצאה תהיה משהו כזה:

[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) @ usersignup ---
[INFO] Building image 'docker.io/pratikdas/usersignup:v1'
[INFO] 
[INFO]  > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
.
.
.. [creator]     Adding label 'org.springframework.boot.version'
.. [creator]     *** Images (c311fe74ec73):
.. [creator]           docker.io/pratikdas/usersignup:v1
[INFO] 
[INFO] Successfully built image 'docker.io/pratikdas/usersignup:v1'

מהפלט, אנחנו רואים את זה paketo Cloud-Native buildpackמשמש ליצירת תמונת OCI עובדת. כמו בעבר, אנו יכולים לראות את התמונה רשומה כתמונת Docker על ידי הפעלת הפקודה:

docker images 

מסקנה:

REPOSITORY                             SIZE
paketobuildpacks/run                  84.3MB
gcr.io/paketo-buildpacks/builder      652MB
pratikdas/usersignup                  257MB

יצירת תמונת מיכל עם Jib

Jib הוא תוסף ליצירת תמונות מגוגל המספק שיטה חלופית ליצירת תמונת מיכל מהמקור.

הגדרת תצורה jib-maven-pluginב-pom.xml:

      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>2.5.2</version>
      </plugin>

לאחר מכן, אנו מפעילים את הפלאגין Jib באמצעות הפקודה Maven כדי לבנות את האפליקציה וליצור את תמונת המיכל. כמו קודם, אנחנו לא משתמשים ב-Dockerfiles כאן:

mvn compile jib:build -Dimage=<docker registry name>/usersignup:v1

לאחר ביצוע פקודת Maven לעיל, אנו מקבלים את הפלט הבא:

[INFO] Containerizing application to pratikdas/usersignup:v1...
.
.
[INFO] Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, io.pratik.users.UsersignupApplication]
[INFO] 
[INFO] Built and pushed image as pratikdas/usersignup:v1
[INFO] Executing tasks:
[INFO] [==============================] 100.0% complete

הפלט מראה שתמונת המיכל נוצרה והוצבה ברישום.

מניעים ושיטות ליצירת תמונות מותאמות

יש לנו שתי סיבות עיקריות לאופטימיזציה:

  • פרודוקטיביות: במערכת תזמור קונטיינר, תמונת קונטיינר נמשכת ממרשם התמונות אל המארח המריץ את מנוע המכולה. תהליך זה נקרא תכנון. משיכת תמונות גדולות מהרישום מביאה לזמני תזמון ארוכים במערכות תזמור קונטיינרים וזמני בנייה ארוכים בצינורות CI.
  • אבטחה: לתמונות גדולות יש גם שטח גדול לפגיעות.

תמונת Docker מורכבת מערימה של שכבות, שכל אחת מהן מייצגת הצהרה ב-Dockerfile שלנו. כל שכבה מייצגת את הדלתא של השינויים בשכבה הבסיסית. כאשר אנו שולפים תמונת Docker מהרישום, היא נמשכת בשכבות ונשמרת במטמון במארח.

Spring Boot שימושים "Fat JAR" ב כפורמט האריזה המוגדר כברירת מחדל. כאשר אנו מסתכלים על JAR שמן, אנו רואים שהאפליקציה היא חלק קטן מאוד מכל ה-JAR. זה החלק שהכי משתנה. השאר מורכב מתלות של Spring Framework.

נוסחת האופטימיזציה מתמקדת בבידוד האפליקציה ברמה נפרדת מהתלות של Spring Framework.

שכבת התלות שמהווה את החלק הארי של קובץ ה-JAR העבה יורדת פעם אחת בלבד ונשמרת במטמון במערכת המארחת.

רק שכבה דקה של האפליקציה נמשכת במהלך עדכוני אפליקציה ותזמון מיכל, כפי שמוצג בתרשים זה:

בניית תמונות Docker אופטימליות עבור יישום Spring Boot

בסעיפים הבאים, נבחן כיצד ליצור תמונות אופטימליות אלה עבור יישום Spring Boot.

בניית תמונת מיכל אופטימלית עבור יישום Spring Boot עם Buildpack

Spring Boot 2.3 תומך בשכבות על ידי חילוץ חלקים מקובץ JAR עבה לשכבות נפרדות. תכונת השכבות מושבתת כברירת מחדל וצריכה להיות מופעלת במפורש באמצעות הפלאגין Spring Boot Maven:

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    <layers>
      <enabled>true</enabled>
    </layers>
  </configuration> 
</plugin>

אנו נשתמש בתצורה זו כדי לבנות את תמונת המכולה שלנו תחילה עם Buildpack ולאחר מכן עם Docker בסעיפים הבאים.

בוא נרוץ build-imageמייבן מטרה ליצור תמונת מיכל:

mvn spring-boot:build-image

אם נריץ את Dive כדי לראות את השכבות בתמונה המתקבלת, נוכל לראות ששכבת האפליקציה (מוקפת באדום) קטנה בהרבה בטווח הקילו-בייט בהשוואה למה שקיבלנו בפורמט JAR העבה:

בניית תמונות Docker אופטימליות עבור יישום Spring Boot

בניית תמונת מיכל אופטימלית עבור יישום Spring Boot עם Docker

במקום להשתמש בתוסף Maven או Gradle, נוכל גם ליצור תמונת Docker JAR שכבתית עם קובץ Docker.

כאשר אנו משתמשים ב-Docker, עלינו לנקוט בשני שלבים נוספים כדי לחלץ את השכבות ולהעתיק אותן לתמונה הסופית.

התוכן של ה-JAR שנוצר לאחר בנייה עם Maven עם שכבות מופעלת ייראה כך:

META-INF/
.
BOOT-INF/lib/
.
BOOT-INF/lib/spring-boot-jarmode-layertools-2.3.3.RELEASE.jar
BOOT-INF/classpath.idx
BOOT-INF/layers.idx

הפלט מציג JAR נוסף בשם spring-boot-jarmode-layertoolsи layersfle.idxקוֹבֶץ. קובץ JAR נוסף זה מספק יכולות שכבות, כמתואר בסעיף הבא.

חלץ תלות בשכבות נפרדות

כדי להציג ולחלץ שכבות מה-JAR השכבתי שלנו, אנו משתמשים במאפיין המערכת -Djarmode=layertoolsלהתחלה spring-boot-jarmode-layertoolsJAR במקום יישום:

java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar

הפעלת פקודה זו מייצרת פלט המכיל את אפשרויות הפקודה הזמינות:

Usage:
  java -Djarmode=layertools -jar usersignup-0.0.1-SNAPSHOT.jar

Available commands:
  list     List layers from the jar that can be extracted
  extract  Extracts layers from the jar for image creation
  help     Help about any command

הפלט מציג את הפקודות list, extractи helpс helpלהיות ברירת המחדל. בוא נריץ את הפקודה עם listאוֹפְּצִיָה:

java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar list
dependencies
spring-boot-loader
snapshot-dependencies
application

אנו רואים רשימה של תלות שניתן להוסיף כשכבות.

שכבות כברירת מחדל:

שם השכבה

תוכן

dependencies

כל תלות שהגרסה שלה אינה מכילה SNAPSHOT

spring-boot-loader

שיעורי מטעני JAR

snapshot-dependencies

כל תלות שהגרסה שלה מכילה SNAPSHOT

application

מחלקות ומשאבים של יישומים

שכבות מוגדרות ב layers.idxהקובץ בסדר שבו יש להוסיף אותם לתמונת Docker. שכבות אלו נשמרות במטמון במארח לאחר האחזור הראשון מכיוון שהן אינן משתנות. רק שכבת האפליקציה המעודכנת יורדת למארח, שהיא מהירה יותר בגלל הגודל המופחת .

בניית תמונה עם תלות שחולצה לשכבות נפרדות

נבנה את התמונה הסופית בשני שלבים באמצעות שיטה שנקראת הרכבה רב-שלבית . בשלב הראשון נחלץ את התלות ובשלב השני נעתיק את התלות שחולצו לגמר.

בואו נשנה את Dockerfile שלנו לבנייה מרובה שלבים:

# the first stage of our build will extract the layers
FROM adoptopenjdk:14-jre-hotspot as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

# the second stage of our build will copy the extracted layers
FROM adoptopenjdk:14-jre-hotspot
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

אנו שומרים את התצורה הזו בקובץ נפרד - Dockerfile2.

אנו בונים את תמונת Docker באמצעות הפקודה:

docker build -f Dockerfile2 -t usersignup:v1 .

לאחר ביצוע פקודה זו, נקבל את הפלט הבא:

Sending build context to Docker daemon  20.41MB
Step 1/12 : FROM adoptopenjdk:14-jre-hotspot as builder
14-jre-hotspot: Pulling from library/adoptopenjdk
.
.
Successfully built a9ebf6970841
Successfully tagged userssignup:v1

אנו יכולים לראות שתמונת Docker נוצרת עם מזהה תמונה ולאחר מכן מתויגת.

לבסוף, אנו מפעילים את פקודת הצלילה כמו קודם כדי לבדוק את השכבות בתוך תמונת Docker שנוצרה. אנו יכולים לספק מזהה תמונה או תג כקלט לפקודת הצלילה:

dive userssignup:v1

כפי שניתן לראות מהפלט, השכבה המכילה את האפליקציה היא כעת רק 11 KB והתלות נשמרות בשכבות נפרדות. 

בניית תמונות Docker אופטימליות עבור יישום Spring Boot

חילוץ תלות פנימית בשכבות נפרדות

אנו יכולים להקטין עוד יותר את גודל שכבת היישום על ידי חילוץ כל אחת מהתלות המותאמות אישית שלנו לשכבה נפרדת במקום לארוז אותן עם היישום על ידי הצהרתן ב ymlקובץ דומה בשם layers.idx:

- "dependencies":
  - "BOOT-INF/lib/"
- "spring-boot-loader":
  - "org/"
- "snapshot-dependencies":
- "custom-dependencies":
  - "io/myorg/"
- "application":
  - "BOOT-INF/classes/"
  - "BOOT-INF/classpath.idx"
  - "BOOT-INF/layers.idx"
  - "META-INF/"

בקובץ הזה layers.idxהוספנו תלות מותאמת אישית בשם, io.myorgהמכיל תלות בארגון שאוחזר מהמאגר המשותף.

פלט

במאמר זה, בדקנו את השימוש ב-Cloud-Native Buildpacks לבניית תמונת מיכל ישירות מהמקור. זוהי אלטרנטיבה לשימוש ב-Docker ליצירת תמונת קונטיינר בדרך הרגילה: ראשית, נוצר קובץ JAR בר הפעלה עבה ולאחר מכן נארז לתוך תמונת קונטיינר על ידי ציון ההוראות ב-Dockerfile.

בדקנו גם אופטימיזציה של המיכל שלנו על ידי הכללת תכונת שכבות המחלצת תלות לשכבות נפרדות שמאוחסנות במטמון במארח ושכבת אפליקציה דקה נטענת בזמן התזמון במנועי הביצוע של המכולה.

אתה יכול למצוא את כל קוד המקור המשמש במאמר בכתובת GitHub .

הפניה לפקודה

להלן סיכום של הפקודות בהן השתמשנו במאמר זה לעיון מהיר.

ניקוי הקשר:

docker system prune -a

בניית תמונת מיכל עם Dockerfile:

docker build -f <Docker file name> -t <tag> .

בנה תמונת מיכל ממקור (ללא Dockerfile):

mvn spring-boot:build-image

הצג שכבות תלות. לפני בניית קובץ צנצנת היישום, ודא שתכונת השכבות מופעלת בתוסף spring-boot-maven-:

java -Djarmode=layertools -jar application.jar list

חלץ שכבות תלות. לפני בניית קובץ צנצנת היישום, ודא שתכונת השכבות מופעלת בתוסף spring-boot-maven-:

 java -Djarmode=layertools -jar application.jar extract

הצגת רשימה של תמונות מיכל

docker images

הצג משמאל בתוך תמונת המיכל (ודא שכלי הצלילה מותקן):

dive <image ID or image tag>

מקור: www.habr.com

קנה אירוח אמין לאתרים עם הגנת DDoS, שרתי VPS VDS 🔥 קנה אחסון אתרים אמין עם הגנת DDoS, שרתי VPS VDS | ProHoster