1. סקירה כללית
ב-Codelab הזה נלמד על פרויקט Spring Native, ניצור אפליקציה שמשתמשת בו ונפרוס אותה ב-Google Cloud.
נסביר על הרכיבים שלו, על ההיסטוריה האחרונה של הפרויקט, על כמה תרחישים לדוגמה ועל השלבים שצריך לבצע כדי להשתמש בו בפרויקטים שלכם.
פרויקט Spring Native נמצא כרגע בשלב ניסיוני, ולכן נדרש לבצע הגדרה ספציפית כדי להתחיל להשתמש בו. עם זאת, כפי שהודע בכנס SpringOne 2021, Spring Native עתיד להשתלב ב-Spring Framework 6.0 וב-Spring Boot 3.0 עם תמיכה ברמה גבוהה, ולכן זה הזמן המושלם לבחון את הפרויקט כמה חודשים לפני ההשקה שלו.
הידור JIT עבר אופטימיזציה טובה מאוד לפעולות כמו תהליכים ארוכים, אבל יש תרחישי שימוש מסוימים שבהם אפליקציות שעברו הידור AOT פועלות אפילו טוב יותר. נדון בכך במהלך ה-Codelab.
כאן אפשר להבין איך
- שימוש ב-Cloud Shell
- הפעלת Cloud Run API
- יצירה ופריסה של אפליקציית נייטיב של Spring
- פריסת אפליקציה כזו ב-Cloud Run
מה תצטרכו
- פרויקט ב-Google Cloud Platform עם חשבון לחיוב פעיל ב-GCP
- gcloud CLI מותקן, או שיש גישה אל Cloud Shell
- מיומנויות בסיסיות ב-Java וב-XML
- ידע מעשי בפקודות נפוצות של Linux
סקר
איך תשתמשו במדריך הזה?
איך היית מדרג את החוויה שלך עם Java?
איזה דירוג מתאים לדעתך לחוויית השימוש שלך בשירותי Google Cloud?
2. רקע
בפרויקט Spring Native נעשה שימוש בכמה טכנולוגיות כדי לספק למפתחים ביצועים של אפליקציות נייטיב.
כדי להבין את Spring Native באופן מלא, כדאי להכיר כמה מהטכנולוגיות של הרכיבים האלה, מה הן מאפשרות לנו ואיך הן פועלות יחד.
הידור AOT
כשמפתחים מריצים את javac בדרך כלל משך הזמן לקימפול, קוד המקור שלנו ב- .java עובר קומפילציה לקבצים מסוג .class שנכתבים בבייטקוד. הבייטקוד הזה מיועד להבנה רק על ידי מכונת Java וירטואלית, ולכן ה-JVM יצטרך לפרש את הקוד הזה במכונות אחרות כדי שנוכל להריץ את הקוד שלנו.
התהליך הזה הוא מה שמאפשר ל-Java ניידות ייחודית – אפשר "לכתוב פעם אחת ולהריץ בכל מקום", אבל הוא יקר בהשוואה להרצת קוד Native.
למזלנו, ברוב ההטמעות של ה-JVM נעשה שימוש בהידור בזמן ריצה כדי לצמצם את עלות הפרשנות הזו. הדבר נעשה על ידי ספירת ההפעלות של פונקציה. אם הפונקציה מופעלת מספיק פעמים כדי לעבור סף מסוים ( 10,000 כברירת מחדל), היא עוברת קומפילציה לקוד Native בזמן הריצה כדי למנוע פרשנות יקרה נוספת.
הידור מראש (AOT) הוא גישה הפוכה, שבה כל הקוד שאפשר להגיע אליו עובר הידור לקובץ הפעלה מקורי משך הזמן לקימפול. הפשרה הזו מאפשרת להשיג יעילות בזיכרון ושיפורים אחרים בביצועים בזמן הריצה, אבל פוגעת בניידות.

כמובן שיש כאן פשרה, ולא תמיד כדאי לעשות אותה. עם זאת, קומפילציה של AOT יכולה להיות שימושית בתרחישי שימוש מסוימים, כמו:
- אפליקציות לשימוש קצר טווח שבהן זמן ההפעלה חשוב
- סביבות עם מגבלות זיכרון מחמירות שבהן יכול להיות ש-JIT יקר מדי
עובדה מעניינת: קומפילציה של AOT הוצגה כתכונה ניסיונית ב-JDK 9, אבל היה יקר לתחזק את ההטמעה הזו, והיא לא זכתה לפופולריות. לכן היא הוסרה בשקט ב-Java 17, כדי שמפתחים יוכלו פשוט להשתמש ב-GraalVM.
GraalVM
GraalVM היא הפצה של JDK בקוד פתוח שעברה אופטימיזציה גבוהה, עם זמני הפעלה מהירים במיוחד, קומפילציה של תמונות מקוריות של AOT ויכולות פוליגלוטיות שמאפשרות למפתחים לשלב כמה שפות באפליקציה אחת.
פיתוח של GraalVM נמצא בעיצומו, והיכולות החדשות והקיימות משתפרות כל הזמן, לכן מומלץ למפתחים להתעדכן.
הנה כמה דוגמאות לפעולות משמעותיות שביצענו לאחרונה:
- פלט חדש של בניית תמונות מקוריות, ידידותי למשתמש ( 2021-01-18)
- תמיכה ב-Java 17 ( 2022-01-18)
- הפעלנו כברירת מחדל קומפילציה רב-שכבתית כדי לשפר את זמני הקומפילציה של פוליגלוט ( 2021-04-20)
Spring Native
במילים פשוטות – Spring Native מאפשרת להשתמש בקומפיילר native-image של GraalVM כדי להפוך אפליקציות Spring לקבצים הפעלה מקוריים.
התהליך הזה כולל ביצוע ניתוח סטטי של האפליקציה בזמן ההידור כדי למצוא את כל השיטות באפליקציה שאפשר להגיע אליהן מנקודת הכניסה.
המשמעות היא שנוצרת תפיסה של "עולם סגור" של האפליקציה, שבה כל הקוד ידוע בזמן ההידור, ולא ניתן לטעון קוד חדש בזמן הריצה.
חשוב לציין שיצירת תמונות מקוריות היא תהליך שדורש הרבה זיכרון ונמשך זמן רב יותר מהידור של אפליקציה רגילה, ושיש מגבלות על היבטים מסוימים של Java.
במקרים מסוימים, לא נדרשים שינויים בקוד כדי שאפליקציה תפעל עם Spring Native. עם זאת, במצבים מסוימים נדרשת הגדרה ספציפית של מודעות מותאמות כדי שהן יפעלו בצורה תקינה. במקרים כאלה, Spring Native מספקת לעיתים קרובות Native Hints כדי לפשט את התהליך הזה.
3. הגדרה/עבודה מקדימה
לפני שמתחילים להטמיע את Spring Native, צריך ליצור את האפליקציה ולפרוס אותה כדי ליצור בסיס להשוואה עם הגרסה המקורית בהמשך.
1. יצירת הפרויקט
נתחיל בהורדה של האפליקציה מהאתר start.spring.io:
curl https://start.spring.io/starter.zip -d dependencies=web \
-d javaVersion=11 \
-d bootVersion=2.6.4 -o io-native-starter.zip
אפליקציה לתחילת הדרך זו משתמשת ב-Spring Boot 2.6.4, שהיא הגרסה העדכנית שפרויקט spring-native תומך בה בזמן הכתיבה.
הערה: מאז שחרור GraalVM 21.0.3, אפשר להשתמש גם ב-Java 17 לדוגמה הזו. במדריך הזה אנחנו עדיין משתמשים ב-Java 11 כדי לצמצם את ההגדרות הנדרשות.
אחרי שיש לנו קובץ ZIP בשורת הפקודה, אנחנו יכולים ליצור ספריית משנה לפרויקט ולחלץ את התיקייה לתוכה:
mkdir spring-native cd spring-native unzip ../io-native-starter.zip
2. שינויים בקוד
אחרי שנפתח את הפרויקט, נוסיף במהירות סימן חיים ונציג את הביצועים של Spring Native אחרי שנריץ אותו.
עורכים את הקובץ DemoApplication.java כך שיתאים לתוכן הבא:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.time.Instant;
@RestController
@SpringBootApplication
public class DemoApplication {
private static Instant startTime;
private static Instant readyTime;
public static void main(String[] args) {
startTime = Instant.now();
SpringApplication.run(DemoApplication.class, args);
}
@GetMapping("/")
public String index() {
return "Time between start and ApplicationReadyEvent: "
+ Duration.between(startTime, readyTime).toMillis()
+ "ms";
}
@EventListener(ApplicationReadyEvent.class)
public void ready() {
readyTime = Instant.now();
}
}
בשלב הזה, אפליקציית הבסיס שלנו מוכנה לשימוש, אז אתם יכולים ליצור אימג' ולהריץ אותו באופן מקומי כדי לקבל מושג לגבי זמן ההפעלה לפני שנעביר אותו לאפליקציית נייטיב.
כדי לבנות את התמונה שלנו:
mvn spring-boot:build-image
אפשר גם להשתמש ב-docker images demo כדי לקבל מושג לגבי הגודל של תמונת הבסיס: 
כדי להריץ את האפליקציה שלנו:
docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT
3. פריסת אפליקציה בסיסית
עכשיו, אחרי שיצרנו את האפליקציה, נבצע פריסה שלה ונרשום את הזמנים, כדי שנוכל להשוות אותם בהמשך לזמני ההפעלה של אפליקציית נייטיב.
בהתאם לסוג האפליקציה שאתם יוצרים, יש כמה אפשרויות שונות לאירוח הדברים שלכם.
עם זאת, מכיוון שהדוגמה שלנו היא אפליקציית אינטרנט פשוטה מאוד, אנחנו יכולים להסתמך על Cloud Run.
אם אתם מבצעים את הפעולות במחשב שלכם, ודאו שכלי ה-CLI של gcloud מותקן ומעודכן.
אם אתם ב-Cloud Shell, הכול יטופל אוטומטית ותוכלו פשוט להריץ את הפקודה הבאה בספריית קובצי המקור:
gcloud run deploy
4. הגדרת אפליקציה
1. הגדרת מאגרי Maven שלנו
הפרויקט הזה עדיין בשלב הניסוי, ולכן נצטרך להגדיר את האפליקציה כדי שנוכל למצוא ארטיפקטים ניסיוניים שלא זמינים במאגר המרכזי של Maven.
כדי לעשות זאת, צריך להוסיף את הרכיבים הבאים לקובץ pom.xml. אפשר לעשות את זה בעורך שתבחרו.
מוסיפים את הקטעים הבאים: repositories ו-pluginRepositories לקובץ ה-pom:
<repositories>
<repository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</pluginRepository>
</pluginRepositories>
2. הוספת יחסי התלות
לאחר מכן מוסיפים את התלות spring-native, שנדרשת להרצת אפליקציית Spring כתמונה מקורית. הערה: השלב הזה לא הכרחי אם משתמשים ב-Gradle
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>0.11.2</version>
</dependency>
</dependencies>
3. הוספה או הפעלה של הפלאגינים שלנו
עכשיו מוסיפים את הפלאגין AOT כדי לשפר את התאימות של התמונות המקוריות ואת גודלן ( מידע נוסף):
<plugins>
<!-- ... -->
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>0.11.2</version>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
עכשיו נעדכן את התוסף spring-boot-maven-plugin כדי להפעיל תמיכה בקובץ אימג' מקורי, ונשתמש ב-paketo builder כדי ליצור את קובץ האימג' המקורי:
<plugins>
<!-- ... -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
</plugins>
שימו לב: תמונת הכלי הקטן לבניית אתרים היא רק אחת מכמה אפשרויות. זו בחירה טובה לתרחיש השימוש שלנו כי יש לה מעט מאוד ספריות וכלי עזר נוספים, מה שעוזר למזער את שטח הפנים להתקפה.
לדוגמה, אם אתם מפתחים אפליקציה שצריכה גישה לכמה ספריות נפוצות של C, או שאתם עדיין לא בטוחים מה הדרישות של האפליקציה, יכול להיות שהכלי המלא לבנייה יתאים לכם יותר.
5. פיתוח והרצה של אפליקציית נייטיב
אחרי שכל זה יוגדר, נוכל ליצור את קובץ האימג' ולהריץ את האפליקציה המותאמת המקומפלת.
לפני שמריצים את הבנייה, חשוב לזכור:
- התהליך יימשך יותר זמן מבנייה רגילה (כמה דקות)

- תהליך build זה יכול לצרוך הרבה זיכרון (כמה ג'יגה-בייט)

- תהליך build זה מחייב גישה לדימון (daemon) של Docker
- בדוגמה הזו אנחנו עוברים על התהליך באופן ידני, אבל אפשר גם להגדיר את שלבי ה-build כך שפרופיל build מקורי יופעל באופן אוטומטי.
כדי לבנות את התמונה שלנו:
mvn spring-boot:build-image
אחרי שאפליקציית הנייטיב מוכנה, אפשר לראות אותה בפעולה.
כדי להריץ את האפליקציה שלנו:
docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT
בשלב הזה אנחנו יכולים לראות את שני הצדדים של המשוואה של אפליקציית נייטיב.
השקענו קצת יותר זמן והשתמשנו ביותר שימוש בזיכרון משך הזמן לקימפול, אבל בתמורה קיבלנו אפליקציה שיכולה להתחיל לפעול הרבה יותר מהר, וצורכת הרבה פחות זיכרון (בהתאם לעומס העבודה).
אם מריצים את הפקודה docker images demo כדי להשוות בין הגודל של התמונה המקורית לבין הגודל של התמונה המקורית, אפשר לראות שהגודל קטן באופן משמעותי:

חשוב גם לציין שבמקרים מורכבים יותר של שימוש, צריך לבצע שינויים נוספים כדי ליידע את מהדר ה-AOT לגבי הפעולות שהאפליקציה תבצע בזמן הריצה. לכן, עומסי עבודה צפויים מסוימים (כמו עבודות אצווה) עשויים להתאים מאוד לשימוש ב-Spot, בעוד שאחרים עשויים להיות מורכבים יותר.
6. פריסת אפליקציית הנייטיב שלנו
כדי לפרוס את האפליקציה שלנו ב-Cloud Run, נצטרך להכניס את קובץ האימג' המקורי שלנו למנהל חבילות כמו Artifact Registry.
1. הכנה של מאגר Docker
כדי להתחיל בתהליך הזה, צריך ליצור מאגר:
gcloud artifacts repositories create native-image-docker-repo --repository-format=docker \
--location=us-central1 --description="Repository for our native images"
בשלב הבא, נרצה לוודא שהאימות שלנו מאפשר לנו לבצע push למאגר החדש.
אפשר לפשט את התליך הזה באמצעות ה-CLI של gcloud:
gcloud auth configure-docker us-central1-docker.pkg.dev
2. שליחת התמונה שלנו אל Artifact Registry
בשלב הבא נתייג את התמונה:
export PROJECT_ID=$(gcloud config list --format 'value(core.project)')
docker tag demo:0.0.1-SNAPSHOT \
us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
ואז אפשר להשתמש ב-docker push כדי לשלוח אותו ל-Artifact Registry:
docker push us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
3. פריסה ב-Cloud Run
עכשיו אפשר לפרוס את האימג' שאחסנו ב-Artifact Registry ב-Cloud Run:
gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
מאחר שבנינו ופרסנו את האפליקציה שלנו כתמונה מקורית, אנחנו יכולים להיות בטוחים שהאפליקציה שלנו עושה שימוש מצוין בעלויות התשתית שלנו בזמן שהיא פועלת.
אתם מוזמנים להשוות בעצמכם את זמני ההפעלה של אפליקציית הבסיס שלנו לאפליקציה החדשה הזו.

7. סיכום/ניקוי
מזל טוב על פיתוח ופריסה של אפליקציית נייטיב של Spring ב-Google Cloud!
אנחנו מקווים שהמדריך הזה יעודד אתכם להכיר טוב יותר את פרויקט Spring Native, ולזכור אותו למקרה שהוא יתאים לצרכים שלכם בעתיד.
אופציונלי: פינוי נפח אחסון או השבתה של שירות
בין אם יצרתם פרויקט בענן ב-Google Cloud בשביל ה-Codelab הזה או שאתם משתמשים בפרויקט קיים, חשוב להיזהר כדי לא לצבור חיובים מיותרים על המשאבים שבהם השתמשנו.
אתם יכולים למחוק או להשבית את שירותי Cloud Run שיצרנו, למחוק את התמונה שאירחנו או לכבות את כל הפרויקט.
8. מקורות מידע נוספים
פרויקט Spring Native הוא פרויקט חדש וניסיוני, אבל כבר יש הרבה מקורות מידע טובים שיכולים לעזור למשתמשים הראשונים לפתור בעיות ולהשתתף בפרויקט:
מקורות מידע נוספים
בהמשך מפורטים מקורות מידע באינטרנט שעשויים להיות רלוונטיים למדריך הזה:
- מידע נוסף על רמזים מותאמים
- מידע נוסף על GraalVM
- איך משתתפים
- שגיאת 'אין מספיק זיכרון' כשיוצרים תמונות נייטיב
- שגיאה בהפעלת האפליקציה
רישיון
העבודה הזו בשימוש במסגרת רישיון Creative Commons שמותנה בייחוס כללי מגרסה 2.0.