1. סקירה כללית
שיעור ה-Lab הזה להדגים תכונות ויכולות שנועדו לייעל את תהליך הפיתוח של מהנדסי תוכנה שאחראים על פיתוח אפליקציות Java בסביבה בקונטיינרים. פיתוח אופייני של קונטיינרים מחייב את המשתמשים להבין את הפרטים של קונטיינרים ואת תהליך הפיתוח של הקונטיינרים. בנוסף, בדרך כלל מפתחים צריכים לפרוץ את התהליך ולצאת מסביבת הפיתוח המשולבת (IDE) שלהם כדי לבדוק את האפליקציות שלהם ולנפות באגים בסביבות מרוחקות. בעזרת הכלים והטכנולוגיות שמוזכרים במדריך הזה, מפתחים יכולים לעבוד ביעילות עם אפליקציות בקונטיינרים בלי לצאת מסביבת הפיתוח המשולבת (IDE).
מה תלמדו
בשיעור ה-Lab הזה תלמדו שיטות לפיתוח עם קונטיינרים ב-GCP, כולל:
- הגדרה ודרישות
- יצירת אפליקציה חדשה לתחילת העבודה עם Java
- להדרכה על תהליך הפיתוח
- פיתוח שירות CRUD מסוג מנוחה פשוט
- הסרת המשאבים
2. הגדרה ודרישות
הגדרת סביבה בקצב אישי
- נכנסים למסוף Google Cloud ויוצרים פרויקט חדש או עושים שימוש חוזר בפרויקט קיים. אם אין לכם עדיין חשבון Gmail או חשבון Google Workspace, עליכם ליצור חשבון.
- Project name הוא השם המוצג של המשתתפים בפרויקט. זו מחרוזת תווים שלא נעשה בה שימוש ב-Google APIs, ואפשר לעדכן אותה בכל שלב.
- Project ID חייב להיות ייחודי בכל הפרויקטים ב-Google Cloud ואי אפשר לשנות אותו (אי אפשר לשנות אותו אחרי שמגדירים אותו). מסוף Cloud יוצר מחרוזת ייחודית באופן אוטומטי; בדרך כלל לא מעניין אותך מה זה. ברוב ה-Codelabs תצטרכו להפנות אל מזהה הפרויקט (ובדרך כלל הוא מזוהה כ-
PROJECT_ID
), כך שאם הוא לא מוצא חן בעיניכם, תוכלו ליצור פרויקט אקראי אחר או לנסות בעצמכם ולבדוק אם הוא זמין. ואז המכשיר 'קפוא' לאחר יצירת הפרויקט. - יש ערך שלישי, Project Number, שחלק מממשקי ה-API משתמשים בו. מידע נוסף על כל שלושת הערכים האלה זמין במסמכי התיעוד.
- בשלב הבא צריך להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבים או בממשקי API של Cloud. מעבר ב-Codelab הזה לא אמור לעלות הרבה, אם בכלל. כדי להשבית את המשאבים ולא לצבור חיובים מעבר למדריך הזה, פועלים לפי ההנחיות למחיקת המשאבים. בסוף ה-Codelab. משתמשים חדשים ב-Google Cloud זכאים להצטרף לתוכנית תקופת ניסיון בחינם בשווי 1,200 ש"ח.
הפעלת Cloudshell Editor
שיעור ה-Lab הזה תוכנן ונבדק לשימוש עם Google Cloud Shell Editor. כדי לגשת לכלי העריכה:
- ניגשים לפרויקט Google בכתובת https://console.cloud.google.com.
- בפינה השמאלית העליונה, לוחצים על סמל העורך של Cloud Shell
- חלונית חדשה תיפתח בחלק התחתון של החלון
- לוחצים על הלחצן 'פתיחת העורך'.
- העורך ייפתח בצד שמאל עם כלי חקירה משמאל ועורך באזור המרכזי.
- חלונית טרמינל אמורה להיות זמינה גם בחלק התחתון של המסך
- אם הטרמינל לא פתוח, משתמשים בשילוב המקשים של 'Ctrl+' כדי לפתוח חלון טרמינל חדש
הגדרת gcloud
ב-Cloud Shell, מגדירים את מזהה הפרויקט ואת האזור שבו רוצים לפרוס את האפליקציה. שומרים אותם כמשתנים מסוג PROJECT_ID
ו-REGION
.
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. סקריפט ההגדרה שבהמשך מכין את התשתית הזו עבורכם. תהליך ההקצאה יימשך יותר מ-10 דקות. אפשר להמשיך בשלבים הבאים בזמן תהליך ההגדרה.
./setup.sh
3. יצירת אפליקציה חדשה לתחילת העבודה עם Java
בקטע זה תצרו אפליקציה חדשה מאפס של Java Spring Boot באמצעות אפליקציה לדוגמה שסופקה על ידי spring.io
שכפול האפליקציה לדוגמה
- יצירת אפליקציה לתחילת פעולה
curl https://start.spring.io/starter.zip -d dependencies=web -d type=maven-project -d javaVersion=11 -d packageName=com.example.springboot -o sample-app.zip
- מחלצים את תוכן האפליקציה
unzip sample-app.zip -d sample-app
- עוברים לספריית האפליקציה לדוגמה ופותחים את התיקייה בסביבת העבודה המשולבת של Cloud Shell בסביבת העבודה המשולבת (IDE) של Cloud Shell.
cd sample-app && cloudshell workspace .
הוספת ערכות פיתוח משולבות לאביב ו- ג'יב
כדי להפעיל את כלי הפיתוח לאתחול אביב, צריך לחפש את pom.xml בסייר בעורך ולפתוח אותו. לאחר מכן מדביקים את הקוד הבא אחרי שורת התיאור שבה כתוב <description>פרויקט הדגמה לאתחול אביב</description>
- הוספה של 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>
- הפעלת jib-maven-plugin ב-pom.xml
Jib הוא כלי קוד פתוח של 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>
אם מופיעה בקשה לשינוי קובץ build, בוחרים באפשרות Always
.
יצירת מניפסטים
ב-Skaffold יש כלים משולבים שעוזרים לפתח קונטיינרים בצורה פשוטה יותר. בשלב הזה מפעילים skaffold כדי ליצור באופן אוטומטי קובצי YAML בסיסיים ב-Kubernetes. התהליך מנסה לזהות ספריות עם הגדרות של קובץ אימג' בקונטיינר, כמו קובץ Docker, ואז יוצר מניפסט שירות ופריסה לכל אחת מהן.
כדי להתחיל את התהליך, מריצים את הפקודה הבאה.
- מריצים את הפקודה הבאה בטרמינל
skaffold init --generate-manifests
- כשמוצגת ההודעה:
- משתמשים בחצים כדי להזיז את הסמן אל
Jib Maven Plugin
- כדי לבחור את האפשרות, לוחצים על מקש הרווח.
- יש להקיש על Enter כדי להמשיך
- מזינים 8080 ליציאה
- מזינים y כדי לשמור את ההגדרה.
שני קבצים נוספו ל-viz של Workspace, skaffold.yaml
ו-deployment.yaml
עדכון שם האפליקציה
ערכי ברירת המחדל הכלולים בתצורה לא תואמים כרגע לשם האפליקציה שלך. מעדכנים את הקבצים כך שיפנו לשם האפליקציה, במקום לערכי ברירת המחדל.
- שינוי הערכים בהגדרות של Skaffold
- פתיחה של
skaffold.yaml
- בחירת שם התמונה שמוגדר כרגע בתור
pom-xml-image
- לוחצים לחיצה ימנית ובוחרים באפשרות 'שינוי כל המופעים'
- יש להקליד את השם החדש בתור
demo-app
- שינוי רשומות בהגדרות של Kubernetes
- פתיחת קובץ אחד (
deployment.yaml
) - בחירת שם התמונה שמוגדר כרגע בתור
pom-xml-image
- לוחצים לחיצה ימנית ובוחרים באפשרות 'שינוי כל המופעים'
- יש להקליד את השם החדש בתור
demo-app
הפעלת סנכרון חם
כדי לאפשר חוויית שימוש אופטימלית בטעינה מחדש מתוך הזיכרון, השתמשו בתכונת הסנכרון של Jib. בשלב הזה מגדירים את Skaffold להשתמש בתכונה הזו בתהליך ה-build.
שימו לב שה'סנכרון' הפרופיל שמגדירים בתצורה של skaffold משתמש ב'סנכרון' של Spring הפרופיל שהגדרתם בשלב הקודם, שבו הפעלתם תמיכה ל-Spring-dev-tools.
- עדכון ההגדרה של 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/java:debug
sync:
auto: true
הוספה של נתיב ברירת מחדל
יוצרים קובץ בשם HelloController.java בכתובת /src/main/java/com/example/springboot/
צריך להדביק את התכנים הבאים בקובץ כדי ליצור נתיב 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 לפיתוח מבוסס-קונטיינר.
פריסה ב-Kubernetes
- בחלונית שבחלק התחתון של Cloud Shell Editor, בוחרים באפשרות Cloud Code כוללת
- בחלונית שמופיעה בחלק העליון, בוחרים באפשרות 'ניפוי באגים ב-Kubernetes'. אם מוצגת הנחיה, בוחרים באפשרות 'כן' כדי להשתמש בהקשר הנוכחי של Kubernetes.
- בפעם הראשונה שמריצים את הפקודה, תופיע בחלק העליון של המסך הנחיה לשאול אם אתם רוצים את ההקשר הנוכחי של Kubernetes, יש לבחור באפשרות 'כן'. לקבל את ההקשר הנוכחי ולהשתמש בו.
- בשלב הבא תוצג הודעה שמבקשת באיזה מרשם להשתמש. צריך להקיש על Enter כדי לאשר את ערך ברירת המחדל שצוין
- צריך לבחור בכרטיסייה 'פלט' בחלונית התחתונה כדי לראות את ההתקדמות וההתראות
- בוחרים באפשרות Kubernetes: Run/Debug - Detailed (Kubernetes: הרצה/ניפוי באגים - מפורט). בתפריט הנפתח של הערוץ משמאל כדי להציג פרטים נוספים ויומנים בסטרימינג בשידור חי ממאגרי התגים
- כדי לחזור לתצוגה הפשוטה, בוחרים באפשרות 'Kubernetes: הרצה/ניפוי באגים'. מהתפריט הנפתח
- בסיום ה-build והבדיקות, תופיע בכרטיסייה 'פלט' ההודעה:
Resource deployment/demo-app status completed successfully
, ובכרטיסיית ה'פלט' תופיע כתובת ה-URL: 'כתובת ה-URL שהועברה מאפליקציית ההדגמה של השירות: http://localhost:8080' - בטרמינל של Cloud Code, מעבירים את העכבר מעל כתובת ה-URL שמופיעה בפלט (http://localhost:8080) ואז בטיפ הכלי שמופיע, בוחרים באפשרות Open Web Preview.
התגובה תהיה:
Hello from your local environment!
שימוש בנקודות עצירה (breakpoint)
- פותחים את האפליקציה HelloController.java בכתובת /src/main/java/com/example/springboot/HelloController.
- צריך לאתר את הצהרת ההחזרה של נתיב הבסיס –
return String.format("Hello from your %s environment!", target);
- כדי להוסיף נקודת עצירה לשורה הזו, לוחצים על הרווח הריק שמימין למספר השורה. יוצג אינדיקטור אדום כדי לציין שנקודת העצירה הוגדרה
- טוענים מחדש את הדפדפן ומציינים שהכלי לניפוי באגים עוצר את התהליך בנקודת העצירה (breakpoint) ומאפשר לכם לחקור את מצב החול של המשתנה של האפליקציה שפועלת מרחוק ב-GKE
- לוחצים למטה בקטע המשתנים עד שמוצאים את האפשרות 'Target' (יעד) מותאם אישית.
- בודקים את הערך הנוכחי כ'local'
- לוחצים לחיצה כפולה על שם המשתנה target (יעד) בחלון הקופץ, משנים את הערך למשהו אחר, כמו 'Cloud'.
- לוחצים על הלחצן 'המשך' בלוח הבקרה של ניפוי הבאגים.
- בודקים את התשובה בדפדפן, שמציגה עכשיו את הערך המעודכן שהזנתם.
טעינה מחדש מתוך הזיכרון (hot Reload)
- משנים את ההצהרה כך שתחזיר ערך אחר, כמו 'שלום מקוד %s'.
- הקובץ נשמר באופן אוטומטי ומסונכרן עם הקונטיינרים המרוחקים ב-GKE
- מרעננים את הדפדפן כדי לראות את התוצאות המעודכנות.
- כדי להפסיק את פעילות ניפוי הבאגים, לוחצים על הריבוע האדום בסרגל הכלים של ניפוי הבאגים
5. פיתוח שירות CRUD מסוג מנוחה פשוט
בשלב הזה האפליקציה מוגדרת במלואה לפיתוח בקונטיינרים, והסברתם על תהליך הפיתוח הבסיסי באמצעות Cloud Code. בחלקים הבאים תתרגלו את מה שלמדתם על ידי הוספת נקודות קצה של שירותי מנוחה שמתחברות למסד נתונים מנוהל ב-Google Cloud.
הגדרת יחסי תלות
הקוד של האפליקציה משתמש במסד נתונים כדי לשמור את הנתונים של שירות השאר. כדי לוודא שיחסי התלות זמינים, צריך להוסיף את הערכים הבאים ב-pom.xl
- פותחים את הקובץ
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>
יצירת קוד לשירות השאר
Quote.java
יוצרים קובץ בשם Quote.Java ב-/src/main/java/com/example/springboot/ ומעתיקים את הקוד שבהמשך. מגדיר את מודל הישות לאובייקט Quote שנעשה בו שימוש באפליקציה.
package com.example.springboot;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.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) {
try {
quoteRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (RuntimeException e) {
System.out.println(e.getMessage());
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
הוספת העברה של מסדי נתונים
אפשר ליצור תיקייה בכתובת src/main/resources/db/migration/
יוצרים קובץ 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
פריסה ואימות של אפליקציה
- בחלונית שבחלק התחתון של Cloud Shell Editor, בוחרים באפשרות Cloud Code ואז בוחרים באפשרות Debug ב-Kubernetes בחלק העליון של המסך.
- בסיום ה-build והבדיקות, תופיע בכרטיסייה 'פלט' ההודעה:
Resource deployment/demo-app status completed successfully
, ובכרטיסיית ה'פלט' תופיע כתובת ה-URL: 'כתובת ה-URL שהועברה מאפליקציית ההדגמה של השירות: http://localhost:8080' - הצגת ציטוטים אקראיים
מ-cloudshell Terminal, מריצים את הפקודה הבאה כמה פעמים מול נקודת הקצה במירכאות אקראיות. צפייה בשיחה חוזרת שמחזירה ציטוטים שונים
curl -v 127.0.0.1:8080/random-quote
- הוספת ציטוט
יוצרים הצעת מחיר חדשה, עם id=6 באמצעות הפקודה שלמטה ובודקים את הבקשה חוזרת
curl -v -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 127.0.0.1:8080/quotes
- מחיקת ציטוט
עכשיו מוחקים את הציטוט שהוספתם באמצעות שיטת המחיקה ובודקים את קוד התגובה HTTP/1.1 204
.
curl -v -X DELETE 127.0.0.1:8080/quotes/6
- שגיאה בחיבור לשרת
ניתן לראות מצב שגיאה על ידי הרצת הבקשה האחרונה לאחר שהרשומה כבר נמחקה
curl -v -X DELETE 127.0.0.1:8080/quotes/6
שימו לב שהתשובה מחזירה HTTP:500 Internal Server Error
.
ניפוי באגים באפליקציה
בקטע הקודם נמצא מצב שגיאה באפליקציה כשניסית למחוק רשומה שלא נמצאת במסד הנתונים. בקטע הזה תגדירו נקודת עצירה (breakpoint) לאתר את הבעיה. השגיאה אירעה בפעולה DELETE, לכן עליך לעבוד עם המחלקה QuoteController.
- פותחים את src.main.Java.com.example.springboot.QuoteController.Java
- חיפוש השיטה
deleteQuote()
- מחפשים את השורה שבה מוחקים פריט ממסד הנתונים:
quoteRepository.deleteById(id);
. - מגדירים נקודת עצירה בשורה הזו על ידי לחיצה על הרווח הריק שמימין למספר השורה.
- יופיע אינדיקטור אדום שמציין שנקודת העצירה (breakpoint) מוגדרת
- מריצים שוב את הפקודה
delete
curl -v -X DELETE 127.0.0.1:8080/quotes/6
- כדי לחזור לתצוגת ניפוי הבאגים, לוחצים על הסמל בעמודה הימנית.
- חשוב לוודא ששורת ניפוי הבאגים שנעצרה במחלקה QuoteController.
- בכלי לניפוי באגים, לוחצים על הסמל
step over
ובודקים שבוצעה חריגה - חשוב לשים לב ש-
RuntimeException was caught.
כללי מאוד מוחזר שגיאה בחיבור שרת 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
עדכון הקוד
הקוד שגוי וצריך לארגן מחדש את בלוק החריגים כדי לזהות את החריג EmptyResultDataAccessException
ולשלוח חזרה קוד סטטוס HTTP 404 לא נמצא.
מתקנים את השגיאה.
- כשסשן ניפוי הבאגים עדיין פועל, השלם את הבקשה על ידי לחיצה על הלחצן "המשך". בלוח הבקרה של ניפוי הבאגים.
- עכשיו מוסיפים לקוד את הבלוק הבא:
} catch (EmptyResultDataAccessException e){
return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
}
השיטה אמורה להיראות כך:
public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) { try { quoteRepository.deleteById(id); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } catch(EmptyResultDataAccessException e){ return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND); } catch (RuntimeException e) { System.out.println(e.getMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } }
- הרצה מחדש של פקודת המחיקה
curl -v -X DELETE 127.0.0.1:8080/quotes/6
- מבצעים את השלבים בכלי לניפוי באגים ובודקים אם
EmptyResultDataAccessException
זוהה והשגיאה 404 של HTTP שמוחזר למתקשר.
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
- כדי להפסיק את פעילות ניפוי הבאגים, לוחצים על הריבוע האדום בסרגל הכלים של ניפוי הבאגים
6. הסרת המשאבים
מעולה! בשיעור ה-Lab הזה יצרתם מאפס אפליקציית Java חדשה והגדרתם אותה לעבוד ביעילות עם קונטיינרים. לאחר מכן פרסתם את האפליקציה וביצעתם ניפוי באגים באשכול GKE מרוחק, בהתאם לתהליך הפיתוח של האפליקציה בסטאק אפליקציות מסורתי.
כדי לפנות מקום אחרי שמשלימים את שיעור ה-Lab:
- מחיקת הקבצים ששימשו בשיעור ה-Lab
cd ~ && rm -rf container-developer-workshop
- מחיקת הפרויקט כדי להסיר את כל התשתית והמשאבים הקשורים