פיתוח באמצעות Cloud Workstations ו-Cloud Code

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

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

מה תלמדו

בשיעור ה-Lab הזה תלמדו שיטות לפיתוח עם קונטיינרים ב-GCP, כולל:

  • פיתוח InnerLoop באמצעות Cloud Workstations
  • יצירת אפליקציה חדשה לתחילת העבודה עם Java
  • להדרכה על תהליך הפיתוח
  • פיתוח שירות CRUD מסוג מנוחה פשוט
  • אפליקציה לניפוי באגים באשכול GKE
  • קישור האפליקציה למסד הנתונים של CloudSQL

58a4cdd3ed7a123a.png

2. הגדרה ודרישות

הגדרת סביבה בקצב אישי

  1. נכנסים למסוף Google Cloud ויוצרים פרויקט חדש או עושים שימוש חוזר בפרויקט קיים. אם אין לכם עדיין חשבון Gmail או חשבון Google Workspace, עליכם ליצור חשבון.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Project name הוא השם המוצג של המשתתפים בפרויקט. זו מחרוזת תווים שלא משמשת את Google APIs. אפשר לעדכן אותו בכל שלב.
  • Project ID הוא ייחודי בכל הפרויקטים ב-Google Cloud ואי אפשר לשנות אותו (אי אפשר לשנות אותו אחרי שמגדירים אותו). מסוף Cloud יוצר מחרוזת ייחודית באופן אוטומטי; בדרך כלל לא מעניין אותך מה זה. ברוב ה-Codelabs תצטרכו להפנות אל מזהה הפרויקט (בדרך כלל הוא מזוהה כ-PROJECT_ID). אם המזהה שנוצר לא מוצא חן בעיניך, יש לך אפשרות ליצור מזהה אקראי אחר. לחלופין, אפשר לנסות תבנית משלך ולבדוק אם היא זמינה. לא ניתן לשנות אותו אחרי השלב הזה, והוא יישאר למשך הפרויקט.
  • לידיעתך, יש ערך שלישי – Project Number (מספר פרויקט), שחלק מממשקי ה-API משתמשים בו. מידע נוסף על כל שלושת הערכים האלה זמין במסמכי התיעוד.
  1. בשלב הבא צריך להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבים או בממשקי API של Cloud. מעבר ב-Codelab הזה לא אמור לעלות הרבה, אם בכלל. כדי להשבית את המשאבים ולא לצבור חיובים מעבר למדריך הזה, אתם יכולים למחוק את המשאבים שיצרתם או למחוק את הפרויקט כולו. משתמשים חדשים ב-Google Cloud זכאים להצטרף לתוכנית תקופת ניסיון בחינם בשווי 1,200 ש"ח.

הפעלת Cloudshell Editor

שיעור ה-Lab הזה תוכנן ונבדק לשימוש עם Google Cloud Shell Editor. כדי לגשת לכלי העריכה:

  1. ניגשים לפרויקט Google בכתובת https://console.cloud.google.com.
  2. בפינה השמאלית העליונה, לוחצים על סמל העורך של Cloud Shell

8560cc8d45e8c112.png

  1. חלונית חדשה תיפתח בחלק התחתון של החלון
  2. לוחצים על הלחצן 'פתיחת העורך'.

9e504cb98a6a8005.png

  1. העורך ייפתח בצד שמאל עם כלי חקירה משמאל ועורך באזור המרכזי.
  2. חלונית טרמינל אמורה להיות זמינה גם בחלק התחתון של המסך
  3. אם הטרמינל לא פתוח, משתמשים בשילוב המקשים של 'Ctrl+' כדי לפתוח חלון טרמינל חדש

הגדרת gcloud

ב-Cloud Shell, מגדירים את מזהה הפרויקט ואת האזור שבו רוצים לפרוס את האפליקציה. שומרים אותם כמשתנים מסוג PROJECT_ID ו-REGION.

export REGION=us-central1
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')

שכפול קוד המקור

קוד המקור של שיעור ה-Lab הזה נמצא בסדנה למפתחים של קונטיינרים ב-GoogleCloudPlatform ב-GitHub. משכפלים באמצעות הפקודה שלמטה ומשנים את שם הספרייה.

git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git
cd container-developer-workshop/labs/spring-boot

הקצאת התשתית שבה נעשה שימוש בשיעור ה-Lab הזה

בשיעור ה-Lab הזה תפרסו קוד ל-GKE ותגשו לנתונים שמאוחסנים במסד נתונים של CloudSQL. סקריפט ההגדרה שבהמשך מכין את התשתית הזו עבורכם. תהליך ההקצאה יימשך יותר מ-25 דקות. צריך להמתין להשלמת הסקריפט לפני שעוברים לקטע הבא.

./setup_with_cw.sh &

אשכול Cloud Workstations

פותחים את Cloud Workstations במסוף Cloud. ממתינים שהאשכול יהיה בסטטוס READY.

305e1a3d63ac7ff6.png

יצירת תצורה של תחנות עבודה

אם הסשן של Cloud Shell התנתק, צריך ללחוץ על Reconnect (התחברות מחדש). ואז מריצים את הפקודה cli של gcloud כדי להגדיר את מזהה הפרויקט. לפני הרצת הפקודה, צריך להחליף את מזהה הפרויקט לדוגמה שבהמשך במזהה הפרויקט ב-qwiklabs.

gcloud config set project qwiklabs-gcp-project-id

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

cd ~/container-developer-workshop/labs/spring-boot
./workstation_config_setup.sh

מאמתים את התוצאות בקטע 'הגדרות אישיות'. המעבר לסטטוס READY יימשך 2 דקות.

7a6af5aa2807a5f2.png

פותחים את Cloud Workstations במסוף ויוצרים מכונה חדשה.

a53adeeac81a78c8.png

שינוי השם ל-my-workstation ובחירה בהגדרה הקיימת: codeoss-java.

f21c216997746097.png

מאמתים את התוצאות בקטע 'תחנות עבודה'.

66a9fc8b20543e32.png

הפעלה של תחנת העבודה

מפעילים את תחנת העבודה.

c91bb69b61ec8635.png

אפשר להשתמש בקובצי Cookie של צד שלישי על ידי לחיצה על הסמל בסרגל הכתובות. 1b8923e2943f9bc4.png

fcf9405b6957b7d7.png

לוחצים על "האתר לא פועל?".

36a84c0e2e3b85b.png

לוחצים על "אפשר להשתמש בקובצי Cookie".

2259694328628fba.png

ברגע שתושק תחנת העבודה, יופיע סביבת פיתוח משולבת (IDE) של Code OSS. לוחצים על 'סימון כ'בוצע''. בדף 'תחילת העבודה', סביבת הפיתוח המשולבת (IDE) של תחנת העבודה

94874fba9b74cc22.png

3. יצירת אפליקציה חדשה לתחילת העבודה עם Java

בקטע זה תיצרו אפליקציה חדשה לגמרי מסוג Java Spring Boot באמצעות אפליקציה לדוגמה שסופקה על ידי spring.io. פותחים חלון Terminal חדש.

c31d48f2e4938c38.png

שכפול האפליקציה לדוגמה

  1. יצירת אפליקציה לתחילת פעולה
curl  https://start.spring.io/starter.zip -d dependencies=web -d type=maven-project -d javaVersion=17 -d packageName=com.example.springboot -o sample-app.zip

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

58149777e5cc350a.png

  1. מחלצים את תוכן האפליקציה
unzip sample-app.zip -d sample-app
  1. פתיחת האפליקציה לדוגמה תיקייה
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"

הוספת כלי פיתוח לעונת האביב ג'יב

כדי להפעיל את כלי הפיתוח לאתחול אביב, צריך לחפש את pom.xml בסייר בעורך ולפתוח אותו. עכשיו צריך להדביק את הקוד הבא אחרי שורת התיאור שבה כתוב <description>Demo project for Spring Boot</description>

  1. הוספה של spring-boot-devtools ב-pom.xml

פותחים את הקובץ pom.xml ברמה הבסיסית (root) של הפרויקט. צריך להוסיף את ההגדרות האישיות הבאות אחרי הרשומה Description.

pom.xml

  <!--  Spring profiles-->
  <profiles>
    <profile>
      <id>sync</id>
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
        </dependency>
      </dependencies>
    </profile>
  </profiles>
  1. הפעלת jib-maven-plugin ב-pom.xml

Jib הוא כלי בקוד פתוח של Google ליצירת קונטיינרים של Java שמאפשר למפתחי Java לבנות קונטיינרים באמצעות כלי Java שהם מכירים. Jib הוא כלי מהיר ופשוט ליצירת קובצי אימג' של קונטיינר שמטפל בכל השלבים של אריזת האפליקציה שלכם בקובץ אימג' של קונטיינר. לא צריך לכתוב קובץ Docker או להתקין Docker, והוא משולב ישירות ב-Maven וב-Gradle.

גוללים למטה בקובץ pom.xml ומעדכנים את הקטע Build כך שיכלול את הפלאגין של Jib. קטע ה-build צריך להתאים לקטע הבא בסיום התהליך.

pom.xml

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <!--  Jib Plugin-->
      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>3.2.0</version>
      </plugin>
       <!--  Maven Resources Plugin-->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>3.1.0</version>
      </plugin>
    </plugins>
  </build>

יצירת מניפסטים

Skaffold מספק כלים משולבים שעוזרים לפשט את פיתוח הקונטיינרים. בשלב הזה מפעילים את Skaffold כדי ליצור באופן אוטומטי קובצי YAML בסיסיים ב-Kubernetes. התהליך מנסה לזהות ספריות עם הגדרות של קובץ אימג' בקונטיינר, כמו קובץ Docker, ואז יוצר מניפסט שירות ופריסה לכל אחת מהן.

כדי להתחיל את התהליך, מריצים את הפקודה הבאה ב-Terminal.

d869e0cd38e983d7.png

  1. מריצים את הפקודה הבאה בטרמינל
skaffold init --generate-manifests
  1. כשמוצגת ההודעה:
  • משתמשים בחצים כדי להזיז את הסמן אל Jib Maven Plugin
  • כדי לבחור את האפשרות, לוחצים על מקש הרווח.
  • יש להקיש על Enter כדי להמשיך
  1. מזינים 8080 ליציאה
  2. מזינים y כדי לשמור את ההגדרה.

שני קבצים נוספו לסביבת העבודה skaffold.yaml ו-deployment.yaml

פלט Skaffold:

b33cc1e0c2077ab8.png

עדכון שם האפליקציה

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

  1. שינוי הערכים בהגדרות של Skaffold
  • פתיחה של skaffold.yaml
  • בחירת שם התמונה שמוגדר כרגע בתור pom-xml-image
  • לוחצים לחיצה ימנית ובוחרים באפשרות 'שינוי כל המופעים'
  • יש להקליד את השם החדש בתור demo-app
  1. שינוי רשומות בהגדרות של Kubernetes
  • פתיחת קובץ אחד (deployment.yaml)
  • בחירת שם התמונה שמוגדר כרגע בתור pom-xml-image
  • לוחצים לחיצה ימנית ובוחרים באפשרות 'שינוי כל המופעים'
  • יש להקליד את השם החדש בתור demo-app

הפעלת מצב סנכרון אוטומטי

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

שימו לב שה'סנכרון' הפרופיל שמגדירים בתצורה של Skaffold משתמש ב'סנכרון' של Spring הפרופיל שהגדרתם בשלב הקודם, שבו הפעלתם תמיכה ל-Spring-dev-tools.

  1. עדכון ההגדרה של Skaffold

בקובץ skaffold.yaml, צריך להחליף את כל קטע ה-build של הקובץ במפרט הבא. אין לשנות קטעים אחרים בקובץ.

skaffold.yaml

build:
  artifacts:
  - image: demo-app
    jib:
      project: com.example:demo
      type: maven
      args: 
      - --no-transfer-progress
      - -Psync
      fromImage: gcr.io/distroless/java17-debian11:debug
    sync:
      auto: true

הוספה של נתיב ברירת מחדל

יוצרים קובץ בשם HelloController.java בתיקייה /src/main/java/com/example/springboot/.

a624f5dd0c477c09.png

מדביקים את התכנים הבאים בקובץ כדי ליצור נתיב http שמוגדר כברירת מחדל.

HelloController.java

package com.example.springboot;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Value;

@RestController
public class HelloController {

    @Value("${target:local}")
    String target;

    @GetMapping("/") 
    public String hello()
    {
        return String.format("Hello from your %s environment!", target);
    }
}

4. להדרכה על תהליך הפיתוח

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

פלטפורמת Cloud Code עובדת בשילוב עם Skaffold כדי לייעל את תהליך הפיתוח. כשפורסים ב-GKE התהליך הזה מתרחש מאחורי הקלעים ומסיר את הפרטים מהתהליך למפתחים. Cloud Code גם משפר את תהליך הפיתוח באמצעות יכולות מסורתיות של ניפוי באגים ו-hotsync לפיתוח מבוסס-קונטיינר.

כניסה ל-Google Cloud

לוחצים על הסמל של Cloud Code ובוחרים באפשרות Sign in to Google Cloud (כניסה ל-Google Cloud):

1769afd39be372ff.png

לוחצים על 'המשך לכניסה'.

923bb1c8f63160f9.png

בודקים את הפלט ב-Terminal ופותחים את הקישור:

517fdd579c34aa21.png

מתחברים עם פרטי הכניסה של התלמידים ב-Qwiklabs.

db99b345f7a8e72c.png

בוחרים באפשרות 'יש אישור':

a5376553c430ac84.png

מעתיקים את קוד האימות וחוזרים לכרטיסייה 'תחנת עבודה'.

6719421277b92eac.png

מדביקים את קוד האימות ומקישים על Enter.

e9847cfe3fa8a2ce.png

הוספת אשכול Kubernetes

  1. הוספת אשכול

62a3b97bdbb427e5.png

  1. בחירת Google Kubernetes Engine:

9577de423568bbaa.png

  1. בוחרים פרויקט.

c5202fcbeebcd41c.png

  1. בוחרים באפשרות 'אשכול ציטוטים' שנוצר בהגדרה הראשונית.

366cfd8bc27cd3ed.png

9d68532c9bc4a89b.png

הגדרת מזהה הפרויקט הנוכחי באמצעות cli של gcloud

מעתיקים את מזהה הפרויקט לשיעור ה-Lab הזה מהדף qwiklabs.

fcff2d10007ec5bc.png

מריצים את הפקודה cli של gcloud כדי להגדיר את מזהה הפרויקט. לפני הרצת הפקודה, צריך להחליף את מזהה הפרויקט לדוגמה.

gcloud config set project qwiklabs-gcp-project-id

פלט לדוגמה:

f1c03d01b7ac112c.png

ניפוי באגים ב-Kubernetes

  1. בחלונית שמימין למטה, בוחרים ב-Cloud Code.

60b8e4e95868b561.png

  1. בחלונית שמופיעה בקטע 'משימות פיתוח', בוחרים באפשרות 'ניפוי באגים ב-Kubernetes'.

גוללים למטה אם האפשרות לא מוצגת.

7d30833d96632ca0.png

  1. צריך לבחור באפשרות 'כן' כדי להשתמש בהקשר הנוכחי.

a024a69b64de7e9e.png

  1. בוחרים באפשרות 'אשכול ציטוטים' שנוצר במהלך ההגדרה הראשונית.

faebabf372e3caf0.png

  1. בוחרים באפשרות Container Repository.

fabc6dce48bae1b4.png

  1. צריך לבחור בכרטיסייה 'פלט' בחלונית התחתונה כדי לראות את ההתקדמות וההתראות
  2. בוחרים באפשרות 'Kubernetes: הרצה/ניפוי באגים – פירוט'. בתפריט הנפתח של הערוץ משמאל כדי להציג פרטים נוספים ויומנים בסטרימינג בשידור חי ממאגרי התגים

86b44c59db58f8f3.png

ממתינים לפריסת האפליקציה.

9f37706a752829fe.png

  1. לבדוק את האפליקציה שנפרסה ב-GKE ב-Cloud Console.

6ad220e5d1980756.png

  1. כדי לחזור לתצוגה הפשוטה, בוחרים באפשרות Kubernetes: Run/Debug. מהתפריט הנפתח בכרטיסייה OUTPUT.
  2. בסיום ה-build והבדיקות, תופיע בכרטיסייה 'פלט' ההודעה: Resource deployment/demo-app status completed successfully, ובכרטיסיית ה'פלט' תופיע כתובת ה-URL: 'כתובת ה-URL שהועברה מאפליקציית ההדגמה של השירות: http://localhost:8080'
  3. בטרמינל של Cloud Code, מעבירים את העכבר מעל כתובת ה-URL שמופיעה בפלט (http://localhost:8080) ואז בטיפ הכלי שמופיע בוחרים באפשרות 'מעקב אחר קישור'.

28c5539880194a8e.png

תיפתח כרטיסייה חדשה ותוכלו לראות את הפלט למטה:

d67253ca16238f49.png

שימוש בנקודות עצירה (breakpoint)

  1. פתיחת האפליקציה HelloController.java שנמצאת בכתובת /src/main/java/com/example/springboot/HelloController.java
  2. צריך לאתר את הצהרת ההחזרה של נתיב הבסיס – return String.format("Hello from your %s environment!", target);
  3. כדי להוסיף נקודת עצירה לשורה הזו, לוחצים על הרווח הריק שמימין למספר השורה. יוצג אינדיקטור אדום כדי לציין שנקודת העצירה הוגדרה

5027dc6da2618a39.png

  1. טוענים מחדש את הדפדפן ומציינים שהכלי לניפוי באגים עוצר את התהליך בנקודת העצירה (breakpoint) ומאפשר לכם לחקור את המשתנים והמצב של האפליקציה שרצה מרחוק ב-GKE

71acfb426623cec2.png

  1. לוחצים למטה בקטע המשתנים עד שמוצאים את האפשרות 'Target' (יעד) מותאם אישית.
  2. בודקים את הערך הנוכחי כ'local'

a1160d2ed2bb5c82.png

  1. לוחצים לחיצה כפולה על שם המשתנה target (יעד) ובחלון הקופץ,

משנים את הערך ל-Cloud Workstations.

e597a556a5c53f32.png

  1. לוחצים על הלחצן 'המשך' בלוח הבקרה של ניפוי הבאגים.

ec17086191770d0d.png

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

6698a9db9e729925.png

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

טעינה מחדש מתוך הזיכרון (hot Reload)

  1. משנים את ההצהרה כך שתחזיר ערך אחר, כמו 'שלום מקוד %s'.
  2. הקובץ נשמר באופן אוטומטי ומסונכרן עם הקונטיינרים המרוחקים ב-GKE
  3. מרעננים את הדפדפן כדי לראות את התוצאות המעודכנות.
  4. כדי להפסיק את פעילות ניפוי הבאגים, לוחצים על הריבוע האדום בסרגל הכלים של ניפוי הבאגים

a541f928ec8f430e.png c2752bb28d82af86.png

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

984eb2fa34867d70.png

5. פיתוח שירות CRUD מסוג מנוחה פשוט

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

הגדרת יחסי תלות

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

  1. פותחים את הקובץ pom.xml ומוסיפים את הפרטים הבאים לקטע של יחסי התלות בתצורה

pom.xml

    <!--  Database dependencies-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.flywaydb</groupId>
      <artifactId>flyway-core</artifactId>
    </dependency>
    <dependency>
      <groupId>javax.persistence</groupId>
      <artifactId>javax.persistence-api</artifactId>
      <version>2.2</version>
    </dependency>

שירות REST בפורמט REST

Quote.java

יוצרים קובץ בשם Quote.java ב-/src/main/java/com/example/springboot/ ומעתיקים את הקוד שבהמשך. מגדיר את מודל הישות לאובייקט Quote שנעשה בו שימוש באפליקציה.

package com.example.springboot;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import java.util.Objects;

@Entity
@Table(name = "quotes")
public class Quote
{
    @Id
    @Column(name = "id")
    private Integer id;

    @Column(name="quote")
    private String quote;

    @Column(name="author")
    private String author;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getQuote() {
        return quote;
    }

    public void setQuote(String quote) {
        this.quote = quote;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
      if (o == null || getClass() != o.getClass()) {
        return false;
      }
        Quote quote1 = (Quote) o;
        return Objects.equals(id, quote1.id) &&
                Objects.equals(quote, quote1.quote) &&
                Objects.equals(author, quote1.author);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, quote, author);
    }
}

QuoteRepository.java

יוצרים קובץ בשם QuoteRepository.java בכתובת src/main/java/com/example/springboot ומעתיקים את הקוד הבא

package com.example.springboot;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface QuoteRepository extends JpaRepository<Quote,Integer> {

    @Query( nativeQuery = true, value =
            "SELECT id,quote,author FROM quotes ORDER BY RANDOM() LIMIT 1")
    Quote findRandomQuote();
}

הקוד הזה משתמש ב-JPA לשמירת הנתונים. הכיתה מרחיבה את ממשק Spring JPARepository ומאפשרת ליצור קוד בהתאמה אישית. בקוד שהוספת, שיטה מותאמת אישית findRandomQuote.

QuoteController.java

כדי לחשוף את נקודת הקצה של השירות, מחלקה QuoteController תספק את הפונקציונליות הזו.

יצירת קובץ בשם QuoteController.java בכתובת src/main/java/com/example/springboot והעתקה של התוכן הבא

package com.example.springboot;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class QuoteController {

    private final QuoteRepository quoteRepository;

    public QuoteController(QuoteRepository quoteRepository) {
        this.quoteRepository = quoteRepository;
    }

    @GetMapping("/random-quote") 
    public Quote randomQuote()
    {
        return quoteRepository.findRandomQuote();  
    }

    @GetMapping("/quotes") 
    public ResponseEntity<List<Quote>> allQuotes()
    {
        try {
            List<Quote> quotes = new ArrayList<Quote>();
            
            quoteRepository.findAll().forEach(quotes::add);

            if (quotes.size()==0 || quotes.isEmpty()) 
                return new ResponseEntity<List<Quote>>(HttpStatus.NO_CONTENT);
                
            return new ResponseEntity<List<Quote>>(quotes, HttpStatus.OK);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<List<Quote>>(HttpStatus.INTERNAL_SERVER_ERROR);
        }        
    }

    @PostMapping("/quotes")
    public ResponseEntity<Quote> createQuote(@RequestBody Quote quote) {
        try {
            Quote saved = quoteRepository.save(quote);
            return new ResponseEntity<Quote>(saved, HttpStatus.CREATED);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<Quote>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }     

    @PutMapping("/quotes/{id}")
    public ResponseEntity<Quote> updateQuote(@PathVariable("id") Integer id, @RequestBody Quote quote) {
        try {
            Optional<Quote> existingQuote = quoteRepository.findById(id);
            
            if(existingQuote.isPresent()){
                Quote updatedQuote = existingQuote.get();
                updatedQuote.setAuthor(quote.getAuthor());
                updatedQuote.setQuote(quote.getQuote());

                return new ResponseEntity<Quote>(updatedQuote, HttpStatus.OK);
            } else {
                return new ResponseEntity<Quote>(HttpStatus.NOT_FOUND);
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<Quote>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }     

    @DeleteMapping("/quotes/{id}")
    public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
        Optional<Quote> quote = quoteRepository.findById(id);
        if (quote.isPresent()) {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } else {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

}

הוספת הגדרות של מסד נתונים

application.yaml

הוספת תצורה למסד הנתונים בקצה העורפי שהשירות ניגש אליו. עורכים (או יוצרים, אם לא קיים) את הקובץ בשם application.yaml בקטע src/main/resources ומוסיפים הגדרת Spring עם פרמטרים לקצה העורפי.

target: local

spring:
  config:
    activate:
      on-profile: cloud-dev
  datasource:
    url: 'jdbc:postgresql://${DB_HOST:127.0.0.1}/${DB_NAME:quote_db}'
    username: '${DB_USER:user}'
    password: '${DB_PASS:password}'
  jpa:
    properties:
      hibernate:
        jdbc:
          lob:
            non_contextual_creation: true
        dialect: org.hibernate.dialect.PostgreSQLDialect
    hibernate:
      ddl-auto: update

הוספת העברה של מסדי נתונים

יצירת תיקיות db/migration תחת src/main/resources

יוצרים קובץ SQL: V1__create_quotes_table.sql

מדביקים בקובץ את התכנים הבאים

V1__create_quotes_table.sql

CREATE TABLE quotes(
   id INTEGER PRIMARY KEY,
   quote VARCHAR(1024),
   author VARCHAR(256)
);

INSERT INTO quotes (id,quote,author) VALUES (1,'Never, never, never give up','Winston Churchill');
INSERT INTO quotes (id,quote,author) VALUES (2,'While there''s life, there''s hope','Marcus Tullius Cicero');
INSERT INTO quotes (id,quote,author) VALUES (3,'Failure is success in progress','Anonymous');
INSERT INTO quotes (id,quote,author) VALUES (4,'Success demands singleness of purpose','Vincent Lombardi');
INSERT INTO quotes (id,quote,author) VALUES (5,'The shortest answer is doing','Lord Herbert');

הגדרת Kubernetes

התוספות הבאות לקובץ deployment.yaml מאפשרות לאפליקציה להתחבר למכונות של CloudSQL.

  • TARGET – מגדיר את המשתנה כדי לציין את הסביבה שבה האפליקציה מופעלת
  • SPRING_PROFILES_ACTIVE – הצגת פרופיל ה-Spring הפעיל, שיוגדר כ-cloud-dev
  • DB_HOST – כתובת ה-IP הפרטית של מסד הנתונים, שצוינה כאשר מופע מסד הנתונים נוצר או על ידי לחיצה על SQL בתפריט הניווט של מסוף Google Cloud – יש לשנות את הערך !
  • DB_USER ו-DB_PASS – כפי שהוגדרו בתצורה של מכונת CloudSQL, מאוחסנים כסוד ב-GCP

מעדכנים אתDeploy.yaml בתוכן הבא.

deployment.yaml

apiVersion: v1
kind: Service
metadata:
  name: demo-app
  labels:
    app: demo-app
spec:
  ports:
  - port: 8080
    protocol: TCP
  clusterIP: None
  selector:
    app: demo-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-app
  labels:
    app: demo-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo-app
  template:
    metadata:
      labels:
        app: demo-app
    spec:
      containers:
      - name: demo-app
        image: demo-app
        env:
          - name: PORT
            value: "8080"
          - name: TARGET
            value: "Local Dev - CloudSQL Database - K8s Cluster"
          - name: SPRING_PROFILES_ACTIVE
            value: cloud-dev
          - name: DB_HOST
            value: ${DB_INSTANCE_IP}   
          - name: DB_PORT
            value: "5432"  
          - name: DB_USER
            valueFrom:
              secretKeyRef:
                name: gke-cloud-sql-secrets
                key: username
          - name: DB_PASS
            valueFrom:
              secretKeyRef:
                name: gke-cloud-sql-secrets
                key: password
          - name: DB_NAME
            valueFrom:
              secretKeyRef:
                name: gke-cloud-sql-secrets
                key: database

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

export DB_INSTANCE_IP=$(gcloud sql instances describe quote-db-instance \
    --format=json | jq \
    --raw-output ".ipAddresses[].ipAddress")

envsubst < deployment.yaml > deployment.new && mv deployment.new deployment.yaml

פותחים אתDeploy.yaml ומוודאים שערך DB_HOST עודכן בכתובת IP של מכונה.

fd63c0aede14beba.png

פריסה ואימות של אפליקציה

  1. בחלונית שבחלק התחתון של Cloud Shell Editor, בוחרים ב-Cloud Code ואז בוחרים באפשרות Debug ב-Kubernetes בחלק העליון של המסך.

33a5cf41aae91adb.png

  1. בסיום ה-build והבדיקות, בכרטיסייה 'פלט' מופיע הכיתוב: Resource deployment/demo-app status completed successfully, וכתובת ה-URL מופיעה: 'כתובת ה-URL שהועברה מאפליקציית ההדגמה של השירות: http://localhost:8080'. שימו לב שלפעמים השקע עשוי להיות שונה כמו 8081. במקרה כזה, מגדירים את הערך המתאים. הגדרת הערך של כתובת URL בטרמינל
export URL=localhost:8080
  1. הצגת ציטוטים אקראיים

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

curl $URL/random-quote | jq
  1. הוספת ציטוט

יוצרים הצעת מחיר חדשה, עם id=6 באמצעות הפקודה שלמטה ובודקים את הבקשה חוזרת

curl -H 'Content-Type: application/json' -d '{"id":"6","author":"Henry David Thoreau","quote":"Go confidently in the direction of your dreams! Live the life you have imagined"}' -X POST $URL/quotes
  1. מחיקת ציטוט

עכשיו מוחקים את הציטוט שהוספתם באמצעות שיטת המחיקה ובודקים את קוד התגובה HTTP/1.1 204.

curl -v -X DELETE $URL/quotes/6
  1. שגיאה בחיבור לשרת

ניתן לראות מצב שגיאה על ידי הרצת הבקשה האחרונה לאחר שהרשומה כבר נמחקה

curl -v -X DELETE $URL/quotes/6

שימו לב שהתשובה מחזירה HTTP:500 Internal Server Error.

ניפוי באגים באפליקציה

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

  1. פתיחה של src/main/java/com/example/springboot/QuoteController.java
  2. חיפוש השיטה deleteQuote()
  3. מאתרים את הקו: Optional<Quote> quote = quoteRepository.findById(id);
  4. מגדירים נקודת עצירה בשורה הזו על ידי לחיצה על הרווח הריק שמימין למספר השורה.
  5. יופיע אינדיקטור אדום שמציין שנקודת העצירה (breakpoint) מוגדרת
  6. מריצים שוב את הפקודה delete
curl -v -X DELETE $URL/quotes/6
  1. כדי לחזור לתצוגת ניפוי הבאגים, לוחצים על הסמל בעמודה הימנית.
  2. חשוב לוודא ששורת ניפוי הבאגים שנעצרה במחלקה QuoteController.
  3. בכלי לניפוי באגים, לוחצים על הסמל step over b814d39b2e5f3d9e.png
  4. שימו לב שקוד מחזיר ללקוח שגיאת שרת פנימית (HTTP 500) שאינה אידיאלית.
   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> DELETE /quotes/6 HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500
< Content-Length: 0
< Date: 
<
* Connection #0 to host 127.0.0.1 left intact

עדכון הקוד

הקוד שגוי ויש לארגן מחדש את הבלוק else כדי לשלוח חזרה קוד סטטוס HTTP 404 לא נמצא.

מתקנים את השגיאה.

  1. כשסשן ניפוי הבאגים עדיין פועל, השלם את הבקשה על ידי לחיצה על הלחצן "המשך". בלוח הבקרה של ניפוי הבאגים.
  2. בשלב הבא, משנים את הבלוק else לקוד:
       else {
                return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
            }

השיטה אמורה להיראות כך:

@DeleteMapping("/quotes/{id}")
public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
        Optional<Quote> quote = quoteRepository.findById(id);
        if (quote.isPresent()) {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } else {
            return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
        }
    }
  1. הרצה מחדש של פקודת המחיקה
curl -v -X DELETE $URL/quotes/6
  1. עוברים על הכלי לניפוי באגים ובודקים את השגיאה 'HTTP 404 Not Found' שהוחזרה למתקשר.
   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> DELETE /quotes/6 HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 404
< Content-Length: 0
< Date: 
<
* Connection #0 to host 127.0.0.1 left intact
  1. כדי להפסיק את פעילות ניפוי הבאגים, לוחצים על הריבוע האדום בסרגל הכלים של ניפוי הבאגים

12bc3c82f63dcd8a.png

6f19c0f855832407.png

6. מזל טוב

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

מה למדת

  • פיתוח InnerLoop באמצעות Cloud Workstations
  • יצירת אפליקציה חדשה לתחילת העבודה עם Java
  • להדרכה על תהליך הפיתוח
  • פיתוח שירות CRUD REST פשוט
  • אפליקציה לניפוי באגים באשכול GKE
  • קישור האפליקציה למסד הנתונים של CloudSQL