1. סקירה כללית
בקודלאב הזה תלמדו על הפרויקט Spring Native, תבינו איך יוצרים אפליקציה שמשתמשת בו ומפרסים אותה ב-Google Cloud.
נעבור על הרכיבים שלו, ההיסטוריה האחרונה של הפרויקט, כמה תרחישים לדוגמה וכמובן השלבים הנדרשים כדי להשתמש בו בפרויקטים שלכם.
הפרויקט Spring Native נמצא כרגע בשלב ניסיוני, ולכן כדי להתחיל לעבוד איתו תצטרכו לבצע הגדרות ספציפיות. עם זאת, כפי שפורסם ב-SpringOne 2021, Spring Native ישולב ב-Spring Framework 6.0 וב-Spring Boot 3.0 עם תמיכה ברמה הגבוהה ביותר, כך שזה הזמן המושלם לבחון לעומק את הפרויקט כמה חודשים לפני השקת הגרסה שלו.
הידור בזמן אמת עבר אופטימיזציה טובה מאוד לדברים כמו תהליכים ארוכים, אבל יש תרחישים לדוגמה שבהם אפליקציות שהוגדרו להידור מראש עובדות טוב יותר. נדבר על כך במהלך הקודלאב.
כאן תלמדו איך
- שימוש ב-Cloud Shell
- הפעלת Cloud Run API
- יצירה ופריסה של אפליקציה ב-Spring Native
- פריסת אפליקציה כזו ב-Cloud Run
מה צריך להכין
- פרויקט ב-Google Cloud Platform עם חשבון פעיל לחיוב ב-GCP
- CLI של gcloud מותקן, או גישה ל-Cloud Shell
- מיומנויות בסיסיות ב-Java וב-XML
- ידע בעבודה עם פקודות Linux נפוצות
סקר
איך תוכלו להשתמש במדריך הזה?
מה מידת שביעות הרצון שלך מהשימוש ב-Java?
מהו הדירוג שלך לגבי חוויית השימוש בשירותי Google Cloud?
2. רקע
בפרויקט Spring Native נעשה שימוש בכמה טכנולוגיות כדי לספק למפתחים ביצועים של אפליקציות מקוריות.
כדי להבין את Spring Native לעומק, כדאי להבין כמה מהטכנולוגיות של הרכיבים האלה, מה הן מאפשרות לנו ואיך הן פועלות יחד.
הידור AOT
כשמפתחים מריצים את javac באופן רגיל בזמן הידור, קובץ קובץ המקור בפורמט .java שלהם מומר לקובצי .class שנכתבים בקוד בייט. קוד הבייטקוק הזה מיועד להבנה רק על ידי Java Virtual Machine, כך ש-JVM תצטרך לפרש את הקוד הזה במכונות אחרות כדי שנוכל להריץ את הקוד שלנו.
התהליך הזה מאפשר לנו להעביר את החתימות של Java – כך שאפשר "לכתוב פעם אחת ולהריץ בכל מקום", אבל הוא יקר בהשוואה להרצת קוד מקורי.
למרבה המזל, ברוב הטמעות ה-JVM נעשה שימוש בתכנות בזמן אמת כדי לצמצם את עלות הפרשנות. כדי לעשות זאת, מערכת Android סופרת את הקריאות לפונקציה. אם הפונקציה נקראת בתדירות גבוהה מספיק כדי לעבור ערך סף ( 10,000 כברירת מחדל), היא מתורגמת לקוד מקורי בזמן הריצה כדי למנוע פעולות פרשנות יקרות נוספות.
בהידור מראש, נעשה שימוש בגישה ההפוכה: כל הקוד שאפשר להגיע אליו מקודד לקובץ הפעלה מקורי בזמן הידור. היתרון הוא שיפור הביצועים בזמן הריצה, תוך ויתור על ניידות.
כמובן, יש כאן פשרה, ולא תמיד כדאי להשתמש בה. עם זאת, הידור AOT יכול להניב תוצאות טובות בתרחישי שימוש מסוימים, כמו:
- אפליקציות לטווח קצר שבהן זמן ההפעלה חשוב
- סביבות עם מגבלות זיכרון גבוהות שבהן JIT עשוי להיות יקר מדי
עובדה מעניינת: הידור AOT הוצג כתכונה ניסיונית ב-JDK 9, אבל תחזוקת ההטמעה הזו הייתה יקרה והיא אף פעם לא תפסה תאוצה, ולכן היא הוסרה בשקט ב-Java 17 כדי לעודד את המפתחים להשתמש רק ב-GraalVM.
GraalVM
GraalVM היא הפצה של JDK בקוד פתוח שעברה אופטימיזציה רבה, ומציעה זמני הפעלה מהירים במיוחד, הידור של קובצי אימג' מקומיים מסוג AOT ויכולות פוליגלטיות שמאפשרות למפתחים לשלב כמה שפות באפליקציה אחת.
GraalVM נמצא בפיתוח פעיל, עם יכולות חדשות ושיפורים מתמידים ביכולות הקיימות. לכן, מומלץ למפתחים לעקוב אחרי העדכונים.
אלה כמה מהיעדים המשמעותיים שהשגנו לאחרונה:
- פלט חדש של build של קובץ אימג' מקורי ידידותי למשתמש ( 18 בינואר 2021)
- תמיכה ב-Java 17 ( 18 בינואר 2022)
- הפעלת הידור בכמה רמות כברירת מחדל כדי לשפר את זמני הידור בשפות שונות ( 20 באפריל 2021)
Spring Native
במילים פשוטות, Spring Native מאפשר להשתמש במהדר של GraalVM ליצירת קובצי אימג' מקומיים כדי להפוך אפליקציות Spring לקובצי הפעלה מקומיים.
התהליך הזה כולל ביצוע ניתוח סטטי של האפליקציה בזמן הידור כדי למצוא את כל השיטות באפליקציה שאפשר להגיע אליהן מנקודת הכניסה.
כך נוצרת למעשה תפיסה של 'עולם סגור' של האפליקציה, שבה כל הקוד ידוע כבר בזמן הידור, ואי אפשר לטעון קוד חדש בזמן הריצה.
חשוב לציין שיצירת קובץ אימג' מקורי היא תהליך שצורך הרבה זיכרון, שנמשך יותר זמן מאשר הידור של אפליקציה רגילה, ומטיל מגבלות על היבטים מסוימים של Java.
במקרים מסוימים, אין צורך לבצע שינויים בקוד כדי שהאפליקציה תפעל עם Spring Native. עם זאת, במצבים מסוימים נדרשת הגדרה מקורית ספציפית כדי שהתכונה תפעל כראוי. במקרים כאלה, בדרך כלל Spring Native מספק רמזים מקומיים כדי לפשט את התהליך.
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. אפשר לעשות זאת בכל עורך שבחרתם.
מוסיפים את הקטעים הבאים של המאגרים ו-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>
חשוב לזכור שתמונת tiny builder היא רק אחת מכמה אפשרויות. זו בחירה טובה לתרחיש השימוש שלנו כי יש בה מעט מאוד ספריות ושירותים נוספים, וכך היא עוזרת לצמצם את שטח ההתקפה.
לדוגמה, אם אתם מפתחים אפליקציה שצריכה גישה לספריות C נפוצות מסוימות, או שאתם עדיין לא בטוחים בדרישות של האפליקציה, יכול להיות שfull-builder יתאים לכם יותר.
5. פיתוח והרצה של אפליקציה מקורית
אחרי שכל זה יתבצע, נוכל ליצור את קובץ האימג' ולהריץ את האפליקציה המקורית והמאתחלת.
לפני שמריצים את ה-build, חשוב לזכור כמה דברים:
- הפעולה הזו תימשך יותר זמן מיצירת גרסה רגילה (כמה דקות)
- תהליך ה-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 מה האפליקציה תעשה בסביבת זמן הריצה. לכן, עומסי עבודה צפויים מסוימים (כמו משימות באצווה) עשויים להתאים מאוד למצב כזה, בעוד שאחרים עשויים לדרוש יותר מאמץ.
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 בשביל סדנת הקוד הזו או שאתם משתמשים מחדש בפרויקט קיים, חשוב להקפיד להימנע מחיובים מיותרים על המשאבים שבהם השתמשנו.
אתם יכולים למחוק או להשבית את שירותי Cloud Run שיצרנו, למחוק את התמונה שאירחנו או להשבית את הפרויקט כולו.
8. מקורות מידע נוספים
הפרויקט Spring Native הוא פרויקט חדש וניסיוני, אבל כבר יש שפע של מקורות מידע טובים שיעזרו למשתמשים הראשונים לפתור בעיות ולהשתתף בפרויקט:
מקורות מידע נוספים
בהמשך מופיעים מקורות מידע באינטרנט שעשויים להיות רלוונטיים למדריך הזה:
- מידע נוסף על טיפים מוטמעים
- מידע נוסף על GraalVM
- איך אפשר להצטרף
- שגיאת 'אין מספיק זיכרון' בזמן יצירת קובצי אימג' נייטיב
- שגיאה: לא ניתן להפעיל את האפליקציה
רישיון
העבודה הזו בשימוש במסגרת רישיון Creative Commons Attribution 2.0 Generic.