מודעות מותאמות לאביב ב-Google Cloud

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

מה תצטרכו

סקר

איך תשתמשו במדריך הזה?

רק קוראים את המידע קוראים את המידע ומבצעים את התרגילים

איך היית מדרג את החוויה שלך עם Java?

מתחילים ביניים מומחים

איזה דירוג מתאים לדעתך לחוויית השימוש שלך בשירותי Google Cloud?

מתחילים ביניים מומחים

2. רקע

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

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

הידור AOT

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

התהליך הזה הוא מה שמאפשר ל-Java ניידות ייחודית – אפשר "לכתוב פעם אחת ולהריץ בכל מקום", אבל הוא יקר בהשוואה להרצת קוד Native.

למזלנו, ברוב ההטמעות של ה-JVM נעשה שימוש בהידור בזמן ריצה כדי לצמצם את עלות הפרשנות הזו. הדבר נעשה על ידי ספירת ההפעלות של פונקציה. אם הפונקציה מופעלת מספיק פעמים כדי לעבור סף מסוים ( 10,000 כברירת מחדל), היא עוברת קומפילציה לקוד Native בזמן הריצה כדי למנוע פרשנות יקרה נוספת.

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

5042e8e62a05a27.png

כמובן שיש כאן פשרה, ולא תמיד כדאי לעשות אותה. עם זאת, קומפילציה של 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 כדי לקבל מושג לגבי הגודל של תמונת הבסיס: 6ecb403e9af1475e.png

כדי להריץ את האפליקציה שלנו:

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. פיתוח והרצה של אפליקציית נייטיב

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

לפני שמריצים את הבנייה, חשוב לזכור:

  • התהליך יימשך יותר זמן מבנייה רגילה (כמה דקות) d420322893640701.png
  • תהליך build זה יכול לצרוך הרבה זיכרון (כמה ג'יגה-בייט) cda24e1eb11fdbea.png
  • תהליך build זה מחייב גישה לדימון (daemon) של Docker
  • בדוגמה הזו אנחנו עוברים על התהליך באופן ידני, אבל אפשר גם להגדיר את שלבי ה-build כך שפרופיל build מקורי יופעל באופן אוטומטי.

כדי לבנות את התמונה שלנו:

mvn spring-boot:build-image

אחרי שאפליקציית הנייטיב מוכנה, אפשר לראות אותה בפעולה.

כדי להריץ את האפליקציה שלנו:

docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT

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

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

אם מריצים את הפקודה docker images demo כדי להשוות בין הגודל של התמונה המקורית לבין הגודל של התמונה המקורית, אפשר לראות שהגודל קטן באופן משמעותי:

e667f65a011c1328.png

חשוב גם לציין שבמקרים מורכבים יותר של שימוש, צריך לבצע שינויים נוספים כדי ליידע את מהדר ה-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

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

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

6dde63d35959b1bb.png

7. סיכום/ניקוי

מזל טוב על פיתוח ופריסה של אפליקציית נייטיב של Spring ב-Google Cloud!

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

אופציונלי: פינוי נפח אחסון או השבתה של שירות

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

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

8. מקורות מידע נוספים

פרויקט Spring Native הוא פרויקט חדש וניסיוני, אבל כבר יש הרבה מקורות מידע טובים שיכולים לעזור למשתמשים הראשונים לפתור בעיות ולהשתתף בפרויקט:

מקורות מידע נוספים

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

רישיון

העבודה הזו בשימוש במסגרת רישיון Creative Commons שמותנה בייחוס כללי מגרסה 2.0.