אופטימיזציה של אפליקציות Java ל-Cloud Run

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

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

הרבה מהשיטות המומלצות והאופטימיזציות באפליקציות רגילות מבוססות-אינטרנט של Java מתמקדות ב:

  • טיפול בבקשות מקבילות (גם כאלה שמבוססות על שרשור וגם כאלה שלא חוסמות קלט/פלט)
  • הפחתת זמן האחזור של התגובה באמצעות איגום חיבורים (connection pooling) ועיבוד באצווה (batching) של פונקציות לא קריטיות, למשל שליחת עקבות ומדדים למשימות ברקע.

הרבה מהאופטימיזציות המקובלות האלה פועלות היטב באפליקציות שפועלות לאורך זמן, אבל יכול להיות שהן לא יפעלו טוב בשירות Cloud Run, שפועל רק כשהוא משרת בקשות באופן פעיל. בדף הזה מפורטות כמה אופטימיזציות שונות ופשרות שקשורות ל-Cloud Run, שבעזרתן אפשר לקצר את זמן ההפעלה ולצמצם את השימוש בזיכרון.

שימוש בהגברת מהירות המעבד (CPU) בזמן ההפעלה כדי להפחית את זמן האחזור של ההפעלה

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

המדדים של Google הראו שאפליקציות Java נהנות משימוש בהגברת מהירות המעבד בהפעלה, שיכולה לקצר את זמני ההפעלה בעד 50%.

אופטימיזציה של קובץ אימג' של קונטיינר של אפליקציית Java

אופטימיזציה של קובץ אימג' של קונטיינר יכולה לקצר את זמני הטעינה וההפעלה. אפשר לבצע אופטימיזציה של התמונה על ידי:

  • מזעור של קובץ אימג' של קונטיינר
  • הימנעות משימוש בקובצי JAR של ארכיון ספריות מקוננות
  • שימוש ב-Jib

מזעור קובץ האימג' של הקונטיינר

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

  • קוד מקור
  • פריטי מידע שנוצרו בתהליך פיתוח (Artifact) של Maven
  • כלי בנייה
  • ספריות Git
  • קובצי הפעלה או כלי עזר שלא בשימוש

אם אתם יוצרים את הקוד מתוך Dockerfile, השתמשו ב-Docker multi-stage build כדי שקובץ האימג' הסופי של הקונטיינר יכלול רק את JRE ואת קובץ ה-JAR של האפליקציה עצמה.

כדאי להימנע מקובצי JAR של ספריות מקוננות

חלק מהמסגרות הפופולריות, כמו Spring Boot, יוצרות קובץ ארכיון של אפליקציה (JAR) שמכיל קובצי JAR נוספים של ספריות (JARs מוטבעים). צריך לפתוח את הקבצים האלה (לפרוס אותם) בזמן ההפעלה, וזה עלול לפגוע במהירות ההפעלה ב-Cloud Run. לכן, כשזה אפשרי, כדאי ליצור קובץ JAR דק עם ספריות חיצוניות: אפשר לבצע את הפעולה הזו באופן אוטומטי באמצעות Jib כדי להכניס את האפליקציה למאגר.

שימוש ב-Jib

אפשר להשתמש בתוסף Jib כדי ליצור קונטיינר מינימלי ולשטח את ארכיון האפליקציה באופן אוטומטי. ‫Jib פועל עם Maven ועם Gradle, ופועל עם אפליקציות Spring Boot ללא צורך בהגדרה. יכול להיות שחלק ממסגרות האפליקציות ידרשו הגדרות נוספות של Jib.

אופטימיזציות של JVM לאפליקציות Java ב-Cloud Run

אופטימיזציה של ה-JVM בשביל שירות Cloud Run יכולה לשפר את הביצועים ואת השימוש בזיכרון.

שימוש בגרסאות JVM שמודעות לקונטיינרים

במכונות וירטואליות ובמכונות, כשמדובר בהקצאות של מעבד וזיכרון, ה-JVM מבין את המעבד והזיכרון שהוא יכול להשתמש בהם ממיקומים ידועים, למשל, ב-Linux,‏ /proc/cpuinfo ו-/proc/meminfo. עם זאת, כשמריצים את התהליך בקונטיינר, מגבלות המעבד והזיכרון מאוחסנות ב-/proc/cgroups/.... גרסאות ישנות יותר של JDK ממשיכות לחפש ב-/proc במקום ב-/proc/cgroups, מה שיכול לגרום לשימוש רב יותר במעבד (CPU) ובשימוש בזיכרון ממה שהוקצה. המצב הזה יכול לגרום ל:

  • מספר מוגזם של שרשורים כי גודל מאגר השרשורים מוגדר על ידי Runtime.availableProcessors()
  • גודל ערימה מקסימלי שחורג ממגבלת הזיכרון של המאגר. מכונת ה-JVM משתמשת בזיכרון באופן אגרסיבי לפני שהיא מבצעת איסוף אשפה. במקרים כאלה, קל מאוד לחרוג ממגבלת הזיכרון של הקונטיינר, והקונטיינר יופסק על ידי OOMKilled.

לכן, צריך להשתמש בגרסת JVM שמודעת לקונטיינר. גרסאות OpenJDK גדולות או שוות לגרסה 8u192 מודעות לקונטיינרים כברירת מחדל.

איך מבינים את השימוש בזיכרון של JVM

השימוש בזיכרון של JVM מורכב משימוש בזיכרון מקורי ומשימוש בערימה. זיכרון העבודה של האפליקציה נמצא בדרך כלל ב-heap. הגודל של ה-heap מוגבל על ידי ההגדרה Max Heap. במופע של Cloud Run עם זיכרון RAM של 256MB, אי אפשר להקצות את כל הזיכרון הזה ל-Max Heap, כי גם JVM ומערכת ההפעלה דורשים זיכרון מקומי, למשל, מחסנית של שרשור, מטמוני קוד, ידיות קבצים, מאגרי נתונים וכו'. אם האפליקציה נסגרת בגלל חוסר זיכרון (OOMKilled) ואתם צריכים לדעת מה השימוש בזיכרון של JVM (זיכרון מקומי + Heap), אתם יכולים להפעיל את Native Memory Tracking כדי לראות את השימוש אחרי שהאפליקציה נסגרת בהצלחה. אם האפליקציה שלכם נסגרת בגלל חוסר זיכרון (OOM), היא לא תוכל להדפיס את המידע. במקרה כזה, צריך להריץ את האפליקציה עם יותר זיכרון כדי שהיא תוכל ליצור את הפלט בהצלחה.

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

java -XX:NativeMemoryTracking=summary \
  -XX:+UnlockDiagnosticVMOptions \
  -XX:+PrintNMTStatistics \
  ...

כדאי להשתמש במחשבון זיכרון Java בקוד פתוח כדי להעריך את צורכי הזיכרון.

השבתה של קומפיילר האופטימיזציה

כברירת מחדל, ל-JVM יש כמה שלבים של הידור JIT. למרות שהשלבים האלה משפרים את היעילות של האפליקציה לאורך זמן, הם גם יכולים להגדיל את התקורה של השימוש בזיכרון ולהאריך את זמן ההפעלה.

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

בשירות Cloud Run, מגדירים את משתנה הסביבה:

JAVA_TOOL_OPTIONS="-XX:+TieredCompilation -XX:TieredStopAtLevel=1"

שימוש בשיתוף נתונים של מחלקות אפליקציות

כדי לצמצם עוד יותר את זמן ה-JIT ואת השימוש בזיכרון, אפשר להשתמש בשיתוף נתוני מחלקות של אפליקציות (AppCDS) כדי לשתף את המחלקות של Java שעברו הידור מראש כארכיון. אפשר לעשות שימוש חוזר בארכיון AppCDS כשמפעילים מופע נוסף של אותה אפליקציית Java. מכונת ה-JVM יכולה לעשות שימוש חוזר בנתונים שחושבו מראש מהארכיון, וכך לקצר את זמן ההפעלה.

השיקולים הבאים רלוונטיים לשימוש ב-AppCDS:

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

דוגמה ל-Spring Boot באמצעות קובץ JAR מוצלל

אפליקציות Spring Boot משתמשות ב-uber JAR מוטמע כברירת מחדל, שלא יפעל ב-AppCDS. לכן, אם אתם משתמשים ב-AppCDS, אתם צריכים ליצור JAR מוצלל. לדוגמה, באמצעות Maven ותוסף Maven Shade:

<build>
  <finalName>helloworld</finalName>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-shade-plugin</artifactId>
      <configuration>
        <keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
        <createDependencyReducedPom>true</createDependencyReducedPom>
        <filters>
          <filter>
            <artifact>*:*</artifact>
            <excludes>
              <exclude>META-INF/*.SF</exclude>
              <exclude>META-INF/*.DSA</exclude>
              <exclude>META-INF/*.RSA</exclude>
            </excludes>
          </filter>
        </filters>
      </configuration>
      <executions>
        <execution>
          <phase>package</phase>
          <goals><goal>shade</goal></goals>
          <configuration>
            <transformers>
              <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                <resource>META-INF/spring.handlers</resource>
              </transformer>
              <transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
                <resource>META-INF/spring.factories</resource>
              </transformer>
              <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                <resource>META-INF/spring.schemas</resource>
              </transformer>
              <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
              <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                <mainClass>${mainClass}</mainClass>
              </transformer>
            </transformers>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

אם קובץ ה-JAR המוצלל מכיל את כל התלויות, אפשר ליצור ארכיון פשוט במהלך בניית מאגר התגים באמצעות Dockerfile:

# Use Docker's multi-stage build
FROM eclipse-temurin:11-jre as APPCDS

COPY target/helloworld.jar /helloworld.jar

# Run the application, but with a custom trigger that exits immediately.
# In this particular example, the application looks for the '--appcds' flag.
# You can implement a similar flag in your own application.
RUN java -XX:DumpLoadedClassList=classes.lst -jar helloworld.jar --appcds=true

# From the captured list of classes (based on execution coverage),
# generate the AppCDS archive file.
RUN java -Xshare:dump -XX:SharedClassListFile=classes.lst -XX:SharedArchiveFile=appcds.jsa --class-path helloworld.jar

FROM eclipse-temurin:11-jre

# Copy both the JAR file and the AppCDS archive file to the runtime container.
COPY --from=APPCDS /helloworld.jar /helloworld.jar
COPY --from=APPCDS /appcds.jsa /appcds.jsa

# Enable Application Class-Data sharing
ENTRYPOINT java -Xshare:on -XX:SharedArchiveFile=appcds.jsa -jar helloworld.jar

הקטנת גודל מחסנית השרשור

רוב אפליקציות האינטרנט של Java מבוססות על שרשור לכל חיבור. כל שרשור Java צורך זיכרון מקומי (לא ב-heap). האזור הזה נקרא מחסנית השרשור, וגודל ברירת המחדל שלו הוא 1MB לכל שרשור. אם האפליקציה מטפלת ב-80 בקשות בו-זמניות, יכול להיות שיש לה לפחות 80 שרשורים, כלומר 80MB של שטח מחסנית שרשורים בשימוש. הזיכרון הזה הוא בנוסף לגודל הערימה. יכול להיות שערך ברירת המחדל גדול יותר מהנדרש. אפשר להקטין את גודל מחסנית השרשור.

אם תצמצמו יותר מדי, תראו את java.lang.StackOverflowError. אפשר ליצור פרופיל של האפליקציה ולמצוא את גודל מחסנית השרשור האופטימלי להגדרה.

בשירות Cloud Run, מגדירים את משתנה הסביבה:

JAVA_TOOL_OPTIONS="-Xss256k"

הפחתת מספר ה-threads לשיפור הביצועים של אפליקציות Java

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

צמצום מספר השרשורים

כל שרשור Java עשוי להגדיל את השימוש בזיכרון בגלל Thread Stack. ב-Cloud Run אפשר להשתמש במקסימום 1,000 בקשות בו-זמנית. במודל של שרשור לכל חיבור, צריך לכל היותר 1,000 שרשורים כדי לטפל בכל הבקשות בו-זמנית. ברוב שרתי האינטרנט ומסגרות העבודה אפשר להגדיר את המספר המקסימלי של השרשורים והחיבורים. לדוגמה, ב-Spring Boot, אפשר להגביל את מספר החיבורים המקסימלי בקובץ applications.properties:

server.tomcat.max-threads=80

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

כדי להפחית באמת את מספר השרשורים, כדאי לאמץ מודל תגובתי של תכנות לא חוסם, כך שאפשר יהיה להפחית באופן משמעותי את מספר השרשורים תוך טיפול ביותר בקשות בו-זמניות. מסגרות אפליקציות כמו Spring Boot עם Webflux,‏ Micronaut ו-Quarkus תומכות באפליקציות אינטרנט תגובתיות.

בדרך כלל, למסגרות תגובתיות כמו Spring Boot עם Webflux, ‏ Micronaut ו-Quarkus יש זמני הפעלה מהירים יותר.

אם תמשיכו לכתוב קוד חוסם במסגרת לא חוסמת, התפוקה ושיעורי השגיאות יהיו גרועים משמעותית בשירות Cloud Run. הסיבה לכך היא שבמסגרות לא חוסמות יש רק כמה שרשורים, למשל 2 או 4. אם הקוד שלכם חוסם, הוא יכול לטפל במספר קטן מאוד של בקשות בו-זמניות.

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

אם אתם משתמשים בפעילויות ברקע, אתם צריכים להגדיר חיוב לפי מופע

פעילות ברקע היא כל פעילות שמתרחשת אחרי שהתגובה של HTTP נמסרת. כשמריצים ב-Cloud Run עומסי עבודה מסורתיים שיש להם משימות ברקע, צריך לקחת בחשבון שיקולים מיוחדים.

הגדרת חיוב לפי מופע

אם רוצים לתמוך בפעילויות ברקע בשירות Cloud Run, צריך להגדיר את שירות Cloud Run לחיוב לפי מופע כדי להריץ פעילויות ברקע מחוץ לבקשות ועדיין לקבל גישה למעבד.

אם משתמשים בחיוב לפי בקשה, מומלץ להימנע מפעילויות ברקע

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

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

  • מאגרי חיבורים של JDBC – ניקויים ובדיקות חיבור מתבצעים בדרך כלל ברקע
  • שולחי מעקב מבוזר – בדרך כלל, מעקבים מבוזרים נשלחים בקבוצות באופן תקופתי או כשהמאגר מלא ברקע.
  • שולחי מדדים – בדרך כלל המדדים נשלחים בקבוצות באופן תקופתי ברקע.
  • ב-Spring Boot, כל השיטות שמסומנות בהערה @Async
  • טיימרים – יכול להיות שהפעלות שמבוססות על טיימרים (למשל, ScheduledThreadPoolExecutor,‏ Quartz או @Scheduled Spring annotation) לא יפעלו כשהחיוב מבוסס על בקשות.
  • מקבלים של הודעות – לדוגמה, לקוחות של Pub/Sub streaming pull, לקוחות של JMS או לקוחות של Kafka, בדרך כלל פועלים בשרשורים ברקע בלי צורך בבקשות. הפעולות האלה לא יפעלו אם לא יהיו בקשות באפליקציה. לא מומלץ לקבל הודעות בדרך הזו ב-Cloud Run.

אופטימיזציה של אפליקציות

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

הפחתת משימות ההפעלה

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

‫Cloud Run שולח בקשה ממשתמש אמיתי כדי להפעיל מופע של הפעלה קרה. יכול להיות שמשתמשים שהוקצתה להם בקשה במופע שהופעל לאחרונה יחוו עיכובים ארוכים.

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

שימוש במאגר חיבורים

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

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

אם משתמשים ב-Spring Boot

אם אתם משתמשים ב-Spring Boot, כדאי לשקול את האופטימיזציות הבאות

שימוש ב-Spring Boot מגרסה 2.2 ואילך

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

שימוש באתחול מדורג

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

אפשר להפעיל את האתחול המדורג בapplication.properties:

spring.main.lazy-initialization=true

או באמצעות משתנה סביבה:

SPRING_MAIN_LAZY_INITIALIZATIION=true

עם זאת, אם אתם משתמשים ב-min-instances, אתחול עצל לא יעזור, כי האתחול אמור להתרחש כש-min-instance מתחיל.

איך להימנע מסריקה של כיתה

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

שימוש בכלים למפתחים של Spring Boot שלא בסביבת ייצור

אם אתם משתמשים ב-Spring Boot Developer Tool במהלך הפיתוח, ודאו שהוא לא נארז בתמונת קונטיינר הייצור. יכול להיות שהבעיה הזו מתרחשת אם יצרתם את אפליקציית Spring Boot בלי התוספים של Spring Boot build (לדוגמה, באמצעות התוסף Shade או באמצעות Jib ליצירת קונטיינר).

במקרים כאלה, צריך לוודא שכלי ה-build מחריג במפורש את כלי הפיתוח של Spring Boot. אפשרות נוספת היא להשבית את הכלי למפתחים של Spring Boot באופן מפורש).

המאמרים הבאים

טיפים נוספים