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

1. סקירה כללית

ב-Codelab הזה, נלמד על הפרויקט Spring Native, ניצור אפליקציה שמשתמשת בה ולפרוס אותה ב-Google Cloud.

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

הפרויקט Spring Native נמצא כרגע בשלב ניסיוני, לכן יש צורך בהגדרות ספציפיות כדי להתחיל. עם זאת, כפי שהודענו ב-SpringOne 2021, Spring Native מוגדר לשילוב ב-Spring Framework 6.0 וב-Spring Boot 3.0 עם תמיכה ברמה הראשונה, כך שזו הדרך המושלמת לבחון את הפרויקט לעומק כמה חודשים לפני השקתו.

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

כאן אפשר להבין איך

  • שימוש ב-Cloud Shell
  • הפעלת Cloud Run API
  • יצירה ופריסה של אפליקציית Spring Native
  • פריסת אפליקציה כזו ב-Cloud Run

מה צריך להכין

סקר

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

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

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

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

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

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

2. רקע

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

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

אוסף AOT

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

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

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

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

5042e8e62a05a27.png

זו כמובן בעיה, ולא תמיד כדאי לעשות זאת. עם זאת, הידור מסוג AOT יכול להתאים למקרים מסוימים, למשל:

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

עובדה משעשעת: אומנם הוספנו את אוסף AOT כתכונה ניסיונית ב-JDK 9, למרות שהתחזוקה של ההטמעה הזו הייתה יקרה, ואף פעם לא תפסו אותו, ולכן הוסרה באופן חלק ב-Java 17 לטובת המפתחים שרק השתמשו ב-GraalVM.

GraalVM

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

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

אלה חלק מאבני הדרך האחרונות:

  • פלט חדש של גרסת build מותאמת אישית וידידותית למשתמש ( 18.01.2021)
  • תמיכה ב-Java 17 ( 18.01.2022)
  • הפעלת הידור רב-שכבתי כברירת מחדל כדי לשפר את זמני ההידור של polyglot ( 20.04.2021)

מודעות מותאמות לאביב

במילים אחרות, 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 בזמן הכתיבה.

חשוב לשים לב שמאז ההשקה של GraalVM 21.0.3, אפשר להשתמש ב-Java 17 גם בדוגמה הזו. אנחנו עדיין נשתמש ב-Java 11 עבור המדריך הזה כדי לצמצם את ההגדרות המעורבות.

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

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:

<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 כתמונה מקורית. הערה: אין צורך לבצע את השלב הזה אם משתמשים ב-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 כדי לאפשר תמיכה בתמונות נייטיב ונשתמש ב-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>

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

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

5. יצירה והפעלה של אפליקציית נייטיב

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

יש כמה דברים שחשוב לזכור לפני הפעלת ה-build:

  • הפעולה תימשך יותר זמן מאשר build רגיל (כמה דקות) d420322893640701.png
  • תהליך ה-build הזה יכול לצרוך הרבה זיכרון (כמה ג'יגה-בייט) cda24e1eb11fdbea.png
  • לתהליך ה-build הזה נדרשת גישה לדימון של 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 לגבי מה שהאפליקציה שלך תבצע בזמן הריצה. מהסיבה הזו, יכול להיות שעומסי עבודה צפויים מסוימים (כמו משימות באצווה) יתאימו מאוד לכך, ואילו אחרים עשויים להתאים לעלייה גדולה יותר.

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"

בשלב הבא, עלינו לוודא שאנחנו מאומתים כדי להעביר אותם למרשם החדש שלנו.

ה-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

עכשיו אנחנו מוכנים לפרוס ב-Cloud Run את התמונה שאחסנו ב-Artifact Registry:

gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2

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

מומלץ להשוות בין זמני ההפעלה של האפליקציה הבסיסית שלנו לבין האפליקציה המקורית החדשה הזו בעצמך!

6dde63d35959b1bb.png

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

כל הכבוד על היצירה והפריסה של אפליקציית Spring Native ב-Google Cloud!

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

אופציונלי: ניקוי ו/או השבתה של השירות

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

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

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

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

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

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

רישיון

היצירה הזו בשימוש ברישיון Creative Commons Attribution 2.0 גנרי.