1. ภาพรวม
แล็บนี้จะสาธิตฟีเจอร์และความสามารถที่ออกแบบมาเพื่อเพิ่มประสิทธิภาพเวิร์กโฟลว์การพัฒนาสำหรับวิศวกรซอฟต์แวร์ที่ได้รับมอบหมายให้พัฒนาแอปพลิเคชัน Java ในสภาพแวดล้อมที่มีคอนเทนเนอร์ การพัฒนาคอนเทนเนอร์โดยทั่วไปกำหนดให้ผู้ใช้ต้องเข้าใจรายละเอียดของคอนเทนเนอร์และกระบวนการบิลด์คอนเทนเนอร์ นอกจากนี้ โดยปกติแล้ว นักพัฒนาซอฟต์แวร์จะต้องหยุดโฟลว์การทำงาน ย้ายออกจาก IDE เพื่อทดสอบและแก้ไขข้อบกพร่องของแอปพลิเคชันในสภาพแวดล้อมระยะไกล เครื่องมือและเทคโนโลยีที่กล่าวถึงในบทแนะนำนี้ช่วยให้นักพัฒนาแอปทำงานกับแอปพลิเคชันที่อยู่ในคอนเทนเนอร์ได้อย่างมีประสิทธิภาพโดยไม่ต้องออกจาก IDE
สิ่งที่คุณจะได้เรียนรู้
ในแล็บนี้ คุณจะได้เรียนรู้วิธีการพัฒนาด้วยคอนเทนเนอร์ใน GCP ซึ่งรวมถึง
- การตั้งค่าและข้อกำหนด
- การสร้างแอปพลิเคชันเริ่มต้น Java ใหม่
- การเดินผ่านกระบวนการพัฒนา
- การพัฒนาบริการ REST แบบ CRUD อย่างง่าย
- ล้างข้อมูล
2. การตั้งค่าและข้อกำหนด
การตั้งค่าสภาพแวดล้อมแบบเรียนรู้ด้วยตนเอง
- ลงชื่อเข้าใช้ Google Cloud Console แล้วสร้างโปรเจ็กต์ใหม่หรือใช้โปรเจ็กต์ที่มีอยู่ซ้ำ หากยังไม่มีบัญชี Gmail หรือ Google Workspace คุณต้องสร้างบัญชี



- ชื่อโปรเจ็กต์คือชื่อที่แสดงสำหรับผู้เข้าร่วมโปรเจ็กต์นี้ โดยเป็นสตริงอักขระที่ Google APIs ไม่ได้ใช้ และคุณสามารถอัปเดตได้ทุกเมื่อ
- รหัสโปรเจ็กต์ต้องไม่ซ้ำกันในโปรเจ็กต์ Google Cloud ทั้งหมดและเปลี่ยนแปลงไม่ได้ (เปลี่ยนไม่ได้หลังจากตั้งค่าแล้ว) Cloud Console จะสร้างสตริงที่ไม่ซ้ำกันโดยอัตโนมัติ ซึ่งโดยปกติแล้วคุณไม่จำเป็นต้องสนใจว่าสตริงนั้นคืออะไร ใน Codelab ส่วนใหญ่ คุณจะต้องอ้างอิงรหัสโปรเจ็กต์ (และโดยทั่วไปจะระบุเป็น
PROJECT_ID) ดังนั้นหากไม่ชอบรหัสนี้ ให้สร้างรหัสแบบสุ่มอีกรหัส หรือคุณจะลองใช้รหัสของคุณเองและดูว่ามีรหัสนั้นหรือไม่ก็ได้ จากนั้นจะ "หยุด" หลังจากสร้างโปรเจ็กต์แล้ว - นอกจากนี้ยังมีค่าที่ 3 คือหมายเลขโปรเจ็กต์ที่ API บางตัวใช้ ดูข้อมูลเพิ่มเติมเกี่ยวกับค่าทั้ง 3 นี้ได้ในเอกสารประกอบ
- จากนั้นคุณจะต้องเปิดใช้การเรียกเก็บเงินใน Cloud Console เพื่อใช้ทรัพยากร/API ของ Cloud การทำตาม Codelab นี้ไม่ควรมีค่าใช้จ่ายมากนัก หรืออาจไม่มีเลย หากต้องการปิดแหล่งข้อมูลเพื่อไม่ให้มีการเรียกเก็บเงินนอกเหนือจากบทแนะนำนี้ ให้ทำตามวิธีการ "ล้างข้อมูล" ที่ตอนท้ายของ Codelab ผู้ใช้ Google Cloud รายใหม่มีสิทธิ์เข้าร่วมโปรแกรมช่วงทดลองใช้ฟรีมูลค่า$300 USD
เริ่มโปรแกรมแก้ไข Cloud Shell
Lab นี้ออกแบบและทดสอบเพื่อใช้กับ Google Cloud Shell Editor วิธีเข้าถึงเครื่องมือแก้ไข
- เข้าถึงโปรเจ็กต์ Google ที่ https://console.cloud.google.com
- คลิกไอคอนโปรแกรมแก้ไข Cloud Shell ที่มุมขวาบน

- แผงใหม่จะเปิดขึ้นที่ด้านล่างของหน้าต่าง
- คลิกปุ่ม "เปิดเครื่องมือแก้ไข"

- เครื่องมือแก้ไขจะเปิดขึ้นพร้อมกับ Explorer ทางด้านขวาและเครื่องมือแก้ไขในพื้นที่ส่วนกลาง
- นอกจากนี้ ควรมีแผงเทอร์มินัลที่ด้านล่างของหน้าจอด้วย
- หากเทอร์มินัลไม่ได้เปิดอยู่ ให้ใช้ชุดค่าผสมของปุ่ม `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)')
รับซอร์สโค้ด
ซอร์สโค้ดสำหรับแล็บนี้อยู่ใน container-developer-workshop ใน GoogleCloudPlatform บน GitHub โคลนด้วยคำสั่งด้านล่าง แล้วเปลี่ยนเป็นไดเรกทอรี
git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git
cd container-developer-workshop/labs/spring-boot
จัดสรรโครงสร้างพื้นฐานที่ใช้ใน Lab นี้
ในแล็บนี้ คุณจะได้ติดตั้งใช้งานโค้ดใน GKE และเข้าถึงข้อมูลที่จัดเก็บไว้ในฐานข้อมูล Cloud SQL สคริปต์การตั้งค่าด้านล่างจะเตรียมโครงสร้างพื้นฐานนี้ให้คุณ กระบวนการจัดสรรจะใช้เวลามากกว่า 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
- เปลี่ยนเป็นไดเรกทอรี sample-app แล้วเปิดโฟลเดอร์ในพื้นที่ทำงานของ Cloud Shell IDE
cd sample-app && cloudshell workspace .
เพิ่ม spring-boot-devtools และ Jib
หากต้องการเปิดใช้ Spring Boot DevTools ให้ค้นหาและเปิด pom.xml จาก Explorer ในโปรแกรมแก้ไข จากนั้นวางโค้ดต่อไปนี้หลังบรรทัดรายละเอียดที่ระบุว่า <description>Demo project for Spring Boot</description>
- เพิ่ม spring-boot-devtools ใน pom.xml
เปิด pom.xml ในรูทของโปรเจ็กต์ เพิ่มการกำหนดค่าต่อไปนี้หลังจากรายการ 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 ใน pom.xml
Jib เป็นเครื่องมือสร้างคอนเทนเนอร์ Java แบบโอเพนซอร์สจาก Google ที่ช่วยให้นักพัฒนาซอฟต์แวร์ Java สร้างคอนเทนเนอร์ได้โดยใช้เครื่องมือ Java ที่คุ้นเคย Jib เป็นเครื่องมือสร้างอิมเมจคอนเทนเนอร์ที่รวดเร็วและเรียบง่าย ซึ่งจะจัดการขั้นตอนทั้งหมดในการแพ็กเกจแอปพลิเคชันของคุณลงในอิมเมจคอนเทนเนอร์ โดยไม่จำเป็นต้องเขียน Dockerfile หรือติดตั้ง Docker และผสานรวมเข้ากับ Maven และ Gradle โดยตรง
เลื่อนลงในไฟล์ pom.xml และอัปเดตส่วน Build เพื่อรวมปลั๊กอิน Jib ส่วนบิลด์ควรตรงกับส่วนต่อไปนี้เมื่อเสร็จสมบูรณ์
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>
เลือก Always หากได้รับข้อความแจ้งเกี่ยวกับการเปลี่ยนแปลงไฟล์บิลด์

สร้างไฟล์ Manifest
Skaffold มีเครื่องมือแบบผสานรวมที่จะช่วยให้การพัฒนาคอนเทนเนอร์เป็นเรื่องง่าย ในขั้นตอนนี้ คุณจะเริ่มต้น skaffold ซึ่งจะสร้างไฟล์ YAML ของ Kubernetes ฐานโดยอัตโนมัติ กระบวนการนี้จะพยายามระบุไดเรกทอรีที่มีคำจำกัดความของอิมเมจคอนเทนเนอร์ เช่น Dockerfile จากนั้นจะสร้างไฟล์ Manifest ของการทำให้ใช้งานได้และบริการสำหรับแต่ละรายการ
เรียกใช้คำสั่งด้านล่างเพื่อเริ่มกระบวนการ
- เรียกใช้คำสั่งต่อไปนี้ในเทอร์มินัล
skaffold init --generate-manifests
- เมื่อได้รับข้อความแจ้ง ให้ทำดังนี้
- ใช้ลูกศรเพื่อเลื่อนเคอร์เซอร์ไปที่
Jib Maven Plugin - กดแป้นเว้นวรรคเพื่อเลือกตัวเลือก
- กด Enter เพื่อดำเนินการต่อ
- ป้อน 8080 สำหรับพอร์ต
- ป้อน y เพื่อบันทึกการกำหนดค่า
ระบบจะเพิ่มไฟล์ 2 ไฟล์ไปยังพื้นที่ทำงาน ได้แก่ skaffold.yaml และ deployment.yaml
อัปเดตชื่อแอป
ปัจจุบันค่าเริ่มต้นที่รวมอยู่ในการกำหนดค่าไม่ตรงกับชื่อแอปพลิเคชันของคุณ อัปเดตไฟล์เพื่ออ้างอิงชื่อแอปพลิเคชันแทนค่าเริ่มต้น
- เปลี่ยนรายการในการกำหนดค่า Skaffold
- เปิด
skaffold.yaml - เลือกชื่อรูปภาพที่ตั้งเป็น
pom-xml-imageในปัจจุบัน - คลิกขวาแล้วเลือก "เปลี่ยนทุกรายการ"
- พิมพ์ชื่อใหม่เป็นภาษา
demo-app
- เปลี่ยนรายการในการกำหนดค่า Kubernetes
- เปิดไฟล์
deployment.yaml - เลือกชื่อรูปภาพที่ตั้งเป็น
pom-xml-imageในปัจจุบัน - คลิกขวาแล้วเลือก "เปลี่ยนทุกรายการ"
- พิมพ์ชื่อใหม่เป็นภาษา
demo-app
เปิดใช้การซิงค์ด่วน
หากต้องการมอบประสบการณ์การโหลดซ้ำด่วนที่ได้รับการเพิ่มประสิทธิภาพ คุณจะต้องใช้ฟีเจอร์ซิงค์ที่ Jib มีให้ ในขั้นตอนนี้ คุณจะกำหนดค่า Skaffold เพื่อใช้ฟีเจอร์ดังกล่าวในกระบวนการบิลด์
โปรดทราบว่าโปรไฟล์ "sync" ที่คุณกําลังกําหนดค่าในการกําหนดค่า Skaffold จะใช้ประโยชน์จากโปรไฟล์ "sync" ของ Spring ที่คุณกําหนดค่าไว้ในขั้นตอนก่อนหน้า ซึ่งคุณได้เปิดใช้การรองรับ spring-dev-tools
- อัปเดตการกำหนดค่า Skaffold
ในไฟล์ skaffold.yaml ให้แทนที่ทั้งส่วนบิลด์ของไฟล์ด้วยข้อกำหนดต่อไปนี้ อย่าแก้ไขส่วนอื่นๆ ของไฟล์
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. การเดินผ่านกระบวนการพัฒนา
ในส่วนนี้ คุณจะได้ทำตามขั้นตอน 2-3 ขั้นตอนโดยใช้ปลั๊กอิน Cloud Code เพื่อเรียนรู้กระบวนการพื้นฐาน รวมถึงตรวจสอบการกำหนดค่าและการตั้งค่าของแอปพลิเคชันเริ่มต้น
Cloud Code ผสานรวมกับ Skaffold เพื่อเพิ่มประสิทธิภาพกระบวนการพัฒนา เมื่อคุณทำให้ใช้งานได้ใน GKE ในขั้นตอนต่อไปนี้ Cloud Code และ Skaffold จะบิลด์อิมเมจคอนเทนเนอร์ พุชไปยัง Container Registry แล้วทำให้ใช้งานได้แอปพลิเคชันใน GKE โดยอัตโนมัติ ซึ่งจะเกิดขึ้นเบื้องหลังโดยซ่อนรายละเอียดจากขั้นตอนการทำงานของนักพัฒนาแอป นอกจากนี้ Cloud Code ยังช่วยเพิ่มประสิทธิภาพกระบวนการพัฒนาด้วยการมอบความสามารถในการแก้ไขข้อบกพร่องและการซิงค์ด่วนแบบเดิมให้กับการพัฒนาที่อิงตามคอนเทนเนอร์
ทําให้ใช้งานได้ใน Kubernetes
- เลือก Cloud Code  ในบานหน้าต่างที่ด้านล่างของ Cloud Shell Editor

- เลือก "แก้ไขข้อบกพร่องใน Kubernetes" ในแผงที่ปรากฏขึ้นที่ด้านบน หากได้รับข้อความแจ้ง ให้เลือก "ใช่" เพื่อใช้บริบท Kubernetes ปัจจุบัน

- เมื่อเรียกใช้คำสั่งเป็นครั้งแรก ข้อความแจ้งจะปรากฏที่ด้านบนของหน้าจอเพื่อถามว่าคุณต้องการใช้บริบท Kubernetes ปัจจุบันหรือไม่ ให้เลือก "ใช่" เพื่อยอมรับและใช้บริบทปัจจุบัน

- จากนั้นระบบจะแสดงข้อความแจ้งเพื่อถามว่าควรใช้รีจิสทรีคอนเทนเนอร์ใด กด Enter เพื่อยอมรับค่าเริ่มต้นที่ระบุ

- เลือกแท็บเอาต์พุตในบานหน้าต่างด้านล่างเพื่อดูความคืบหน้าและการแจ้งเตือน

- เลือก "Kubernetes: เรียกใช้/แก้ไขข้อบกพร่อง - โดยละเอียด" ในเมนูแบบเลื่อนลงของช่องทางทางด้านขวาเพื่อดูรายละเอียดเพิ่มเติมและบันทึกที่สตรีมแบบสดจากคอนเทนเนอร์

- กลับไปที่มุมมองแบบง่ายโดยเลือก "Kubernetes: เรียกใช้/แก้ไขข้อบกพร่อง" จากเมนูแบบเลื่อนลง
- เมื่อการสร้างและการทดสอบเสร็จสมบูรณ์ แท็บเอาต์พุตจะแสดง
Resource deployment/demo-app status completed successfullyและ URL ที่ระบุคือ "Forwarded URL from service demo-app: http://localhost:8080" - ในเทอร์มินัล Cloud Code ให้วางเมาส์เหนือ URL ในเอาต์พุต (http://localhost:8080) จากนั้นเลือก "เปิดตัวอย่างเว็บ" ในเคล็ดลับเครื่องมือที่ปรากฏขึ้น
การตอบกลับจะเป็นดังนี้
Hello from your local environment!
ใช้เบรกพอยท์
- เปิดแอปพลิเคชัน HelloController.java ที่ /src/main/java/com/example/springboot/HelloController.java
- ค้นหาคำสั่ง "return" สำหรับเส้นทางรูทซึ่งอ่านว่า
return String.format("Hello from your %s environment!", target); - เพิ่มเบรกพอยต์ในบรรทัดนั้นโดยคลิกพื้นที่ว่างทางด้านซ้ายของหมายเลขบรรทัด ตัวบ่งชี้สีแดงจะแสดงขึ้นเพื่อระบุว่าได้ตั้งค่าเบรกพอยต์แล้ว
- โหลดเบราว์เซอร์ซ้ำและสังเกตว่าโปรแกรมแก้ไขข้อบกพร่องจะหยุดกระบวนการที่เบรกพอยต์และให้คุณตรวจสอบตัวแปรและสถานะของแอปพลิเคชันที่ทำงานจากระยะไกลใน GKE
- คลิกส่วนตัวแปรลงไปจนกว่าจะพบตัวแปร "เป้าหมาย"
- สังเกตค่าปัจจุบันเป็น "local"
- ดับเบิลคลิกชื่อตัวแปร "target" แล้วเปลี่ยนค่าในป๊อปอัปเป็นค่าอื่น เช่น "Cloud"
- คลิกปุ่ม "ดำเนินการต่อ" ในแผงควบคุมการแก้ไขข้อบกพร่อง
- ตรวจสอบการตอบกลับในเบราว์เซอร์ ซึ่งตอนนี้จะแสดงค่าที่อัปเดตที่คุณเพิ่งป้อน
Hot Reload
- เปลี่ยนคำสั่งให้แสดงค่าอื่น เช่น "สวัสดีจาก %s Code"
- ระบบจะบันทึกและซิงค์ไฟล์ไปยังคอนเทนเนอร์ระยะไกลใน GKE โดยอัตโนมัติ
- โปรดรีเฟรชเบราว์เซอร์เพื่อดูผลลัพธ์ที่อัปเดต
- หยุดเซสชันการแก้ไขข้อบกพร่องโดยคลิกสี่เหลี่ยมสีแดงในแถบเครื่องมือการแก้ไขข้อบกพร่อง

5. การพัฒนาบริการ REST แบบ CRUD อย่างง่าย
ตอนนี้แอปพลิเคชันของคุณได้รับการกำหนดค่าอย่างเต็มรูปแบบสำหรับการพัฒนาแบบคอนเทนเนอร์แล้ว และคุณได้ทำตามเวิร์กโฟลว์การพัฒนาพื้นฐานด้วย Cloud Code แล้ว ในส่วนต่อไปนี้ คุณจะได้ฝึกฝนสิ่งที่ได้เรียนรู้โดยการเพิ่มปลายทางของบริการ REST ที่เชื่อมต่อกับฐานข้อมูลที่มีการจัดการใน Google Cloud
กำหนดค่าการอ้างอิง
โค้ดของแอปพลิเคชันใช้ฐานข้อมูลเพื่อจัดเก็บข้อมูลบริการ REST ตรวจสอบว่าทรัพยากร Dependency พร้อมใช้งานโดยเพิ่มข้อมูลต่อไปนี้ใน 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>
เขียนโค้ดบริการ REST
Quote.java
สร้างไฟล์ชื่อ Quote.java ใน /src/main/java/com/example/springboot/ แล้วคัดลอกโค้ดด้านล่าง ซึ่งกำหนดโมเดลเอนทิตีสำหรับออบเจ็กต์ใบเสนอราคาที่ใช้ในแอปพลิเคชัน
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 ซึ่งจัดเก็บเป็น Secret ใน GCP
อัปเดต deployment.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 แล้วเลือกแก้ไขข้อบกพร่องใน Kubernetes ที่ด้านบนของหน้าจอ
- เมื่อการสร้างและการทดสอบเสร็จสมบูรณ์ แท็บเอาต์พุตจะแสดง
Resource deployment/demo-app status completed successfullyและ URL ที่ระบุคือ "Forwarded URL from service demo-app: http://localhost:8080" - ดูคำคมแบบสุ่ม
จากเทอร์มินัล Cloud Shell ให้เรียกใช้คำสั่งด้านล่างหลายครั้งกับปลายทาง random-quote สังเกตการเรียกซ้ำที่แสดงราคาที่แตกต่างกัน
curl -v 127.0.0.1:8080/random-quote
- เพิ่มคำพูด
สร้างใบเสนอราคาใหม่โดยมีรหัส=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
แก้ไขข้อบกพร่องของแอปพลิเคชัน
ในส่วนก่อนหน้า คุณพบสถานะข้อผิดพลาดในแอปพลิเคชันเมื่อพยายามลบรายการที่ไม่ได้อยู่ในฐานข้อมูล ในส่วนนี้ คุณจะตั้งค่าเบรกพอยต์เพื่อค้นหาปัญหา ข้อผิดพลาดเกิดขึ้นในการดำเนินการ DELETE ดังนั้นคุณจะต้องทำงานกับคลาส QuoteController
- เปิด src.main.java.com.example.springboot.QuoteController.java
- ค้นหาวิธี
deleteQuote() - ค้นหาบรรทัดที่ลบรายการออกจากฐานข้อมูล:
quoteRepository.deleteById(id); - ตั้งค่าเบรกพอยต์ในบรรทัดนั้นโดยคลิกพื้นที่ว่างทางด้านซ้ายของหมายเลขบรรทัด
- ตัวบ่งชี้สีแดงจะปรากฏขึ้นเพื่อระบุว่าได้ตั้งค่าเบรกพอยต์แล้ว
- เรียกใช้คำสั่ง
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และส่งกลับ 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
- หยุดเซสชันการแก้ไขข้อบกพร่องโดยคลิกสี่เหลี่ยมสีแดงในแถบเครื่องมือการแก้ไขข้อบกพร่อง

6. ล้างข้อมูล
ยินดีด้วย ในแล็บนี้ คุณได้สร้างแอปพลิเคชัน Java ใหม่ตั้งแต่ต้นและกำหนดค่าให้ทำงานกับคอนเทนเนอร์ได้อย่างมีประสิทธิภาพ จากนั้นคุณได้ทําการทำให้ใช้งานได้และแก้ไขข้อบกพร่องของแอปพลิเคชันไปยังคลัสเตอร์ GKE ระยะไกลตามโฟลว์ของนักพัฒนาซอฟต์แวร์เดียวกันกับที่พบในสแต็กแอปพลิเคชันแบบดั้งเดิม
วิธีล้างข้อมูลหลังจากทำแล็บเสร็จ
- ลบไฟล์ที่ใช้ในห้องทดลอง
cd ~ && rm -rf container-developer-workshop
- ลบโปรเจ็กต์เพื่อนำโครงสร้างพื้นฐานและทรัพยากรที่เกี่ยวข้องทั้งหมดออก