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

2. การตั้งค่าและข้อกำหนด
การตั้งค่าสภาพแวดล้อมแบบเรียนรู้ด้วยตนเอง
- ลงชื่อเข้าใช้ Google Cloud Console แล้วสร้างโปรเจ็กต์ใหม่หรือใช้โปรเจ็กต์ที่มีอยู่ซ้ำ หากยังไม่มีบัญชี Gmail หรือ Google Workspace คุณต้องสร้างบัญชี



- ชื่อโปรเจ็กต์คือชื่อที่แสดงสำหรับผู้เข้าร่วมโปรเจ็กต์นี้ ซึ่งเป็นสตริงอักขระที่ Google APIs ไม่ได้ใช้ โดยคุณจะอัปเดตได้ทุกเมื่อ
- รหัสโปรเจ็กต์จะไม่ซ้ำกันในโปรเจ็กต์ Google Cloud ทั้งหมดและเปลี่ยนแปลงไม่ได้ (เปลี่ยนไม่ได้หลังจากตั้งค่าแล้ว) Cloud Console จะสร้างสตริงที่ไม่ซ้ำกันโดยอัตโนมัติ ซึ่งโดยปกติแล้วคุณไม่จำเป็นต้องสนใจว่าสตริงนั้นคืออะไร ใน Codelab ส่วนใหญ่ คุณจะต้องอ้างอิงรหัสโปรเจ็กต์ (โดยปกติจะระบุเป็น
PROJECT_ID) หากไม่ชอบรหัสที่สร้างขึ้น คุณก็สร้างรหัสแบบสุ่มอีกรหัสหนึ่งได้ หรือคุณจะลองใช้ชื่อของคุณเองเพื่อดูว่าพร้อมใช้งานหรือไม่ก็ได้ คุณจะเปลี่ยนแปลงรหัสนี้หลังจากขั้นตอนนี้ไม่ได้ และรหัสจะยังคงอยู่ตลอดระยะเวลาของโปรเจ็กต์ - โปรดทราบว่ายังมีค่าที่ 3 ซึ่งคือหมายเลขโปรเจ็กต์ที่ API บางตัวใช้ ดูข้อมูลเพิ่มเติมเกี่ยวกับค่าทั้ง 3 นี้ได้ในเอกสารประกอบ
- จากนั้นคุณจะต้องเปิดใช้การเรียกเก็บเงินใน Cloud Console เพื่อใช้ทรัพยากร/API ของ Cloud การทำตาม 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 REGION=us-central1
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 สคริปต์การตั้งค่าด้านล่างจะเตรียมโครงสร้างพื้นฐานนี้ให้คุณ กระบวนการจัดสรรจะใช้เวลามากกว่า 25 นาที รอให้สคริปต์ทำงานเสร็จก่อนไปยังส่วนถัดไป
./setup_with_cw.sh &
คลัสเตอร์ Cloud Workstations
เปิด Cloud Workstations ใน Cloud Console รอให้คลัสเตอร์อยู่ในสถานะ READY
สร้างการกำหนดค่าเวิร์กสเตชัน
หากเซสชัน Cloud Shell ถูกตัดการเชื่อมต่อ ให้คลิก "เชื่อมต่ออีกครั้ง" แล้วเรียกใช้คำสั่ง gcloud CLI เพื่อตั้งค่ารหัสโปรเจ็กต์ แทนที่รหัสโปรเจ็กต์ตัวอย่างด้านล่างด้วยรหัสโปรเจ็กต์ Qwiklabs ของคุณก่อนเรียกใช้คำสั่ง
gcloud config set project qwiklabs-gcp-project-id
เรียกใช้สคริปต์ด้านล่างในเทอร์มินัลเพื่อสร้างการกำหนดค่า Cloud Workstations
cd ~/container-developer-workshop/labs/spring-boot
./workstation_config_setup.sh
ตรวจสอบผลลัพธ์ในส่วนการกำหนดค่า โดยจะใช้เวลา 2 นาทีในการเปลี่ยนเป็นสถานะพร้อม

เปิด Cloud Workstations ในคอนโซล แล้วสร้างอินสแตนซ์ใหม่

เปลี่ยนชื่อเป็น my-workstation แล้วเลือกการกำหนดค่าที่มีอยู่: codeoss-java

ตรวจสอบผลลัพธ์ในส่วนเวิร์กสเตชัน

เปิดเวิร์กสเตชัน
เริ่มและเปิดตัวเวิร์กสเตชัน

อนุญาตคุกกี้ของบุคคลที่สามโดยคลิกไอคอนในแถบที่อยู่ 

คลิก "เว็บไซต์ไม่ทำงานใช่ไหม"

คลิก "อนุญาตคุกกี้"

เมื่อเปิดตัวเวิร์กสเตชันแล้ว คุณจะเห็น IDE ของ Code OSS ปรากฏขึ้น คลิก "ทำเครื่องหมายว่าเสร็จสิ้น" ในหน้าเริ่มต้นใช้งาน IDE ของเวิร์กสเตชัน

3. การสร้างแอปพลิเคชันเริ่มต้น Java ใหม่
ในส่วนนี้ คุณจะได้สร้างแอปพลิเคชัน Java Spring Boot ใหม่ตั้งแต่ต้นโดยใช้แอปพลิเคชันตัวอย่างที่ spring.io จัดเตรียมไว้ เปิดเทอร์มินัลใหม่

โคลนแอปพลิเคชันตัวอย่าง
- สร้างแอปพลิเคชันเริ่มต้น
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
คลิกปุ่มอนุญาตหากเห็นข้อความนี้ เพื่อให้คุณคัดลอกและวางลงในเวิร์กสเตชันได้

- คลายซิปแอปพลิเคชัน
unzip sample-app.zip -d sample-app
- เปิดโฟลเดอร์ "sample-app"
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"
เพิ่ม 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>
สร้างไฟล์ Manifest
Skaffold มีเครื่องมือแบบผสานรวมที่จะช่วยลดความซับซ้อนในการพัฒนาคอนเทนเนอร์ ในขั้นตอนนี้ คุณจะเริ่มต้น Skaffold ซึ่งจะสร้างไฟล์ YAML ของ Kubernetes ฐานโดยอัตโนมัติ กระบวนการนี้จะพยายามระบุไดเรกทอรีที่มีคำจำกัดความของอิมเมจคอนเทนเนอร์ เช่น Dockerfile จากนั้นจะสร้างไฟล์ Manifest ของการทำให้ใช้งานได้และบริการสำหรับแต่ละรายการ
เรียกใช้คำสั่งด้านล่างในเทอร์มินัลเพื่อเริ่มกระบวนการ

- เรียกใช้คำสั่งต่อไปนี้ในเทอร์มินัล
skaffold init --generate-manifests
- เมื่อได้รับข้อความแจ้ง ให้ทำดังนี้
- ใช้ลูกศรเพื่อเลื่อนเคอร์เซอร์ไปที่
Jib Maven Plugin - กดแป้นเว้นวรรคเพื่อเลือกตัวเลือก
- กด Enter เพื่อดำเนินการต่อ
- ป้อน 8080 สำหรับพอร์ต
- ป้อน y เพื่อบันทึกการกำหนดค่า
เพิ่มไฟล์ 2 ไฟล์ไปยังพื้นที่ทำงาน skaffold.yaml และ deployment.yaml
เอาต์พุต Skaffold:

อัปเดตชื่อแอป
ปัจจุบันค่าเริ่มต้นที่รวมอยู่ในการกำหนดค่าไม่ตรงกับชื่อแอปพลิเคชันของคุณ อัปเดตไฟล์เพื่ออ้างอิงชื่อแอปพลิเคชันแทนค่าเริ่มต้น
- เปลี่ยนรายการในการกำหนดค่า 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/java17-debian11: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 ยังช่วยเพิ่มประสิทธิภาพกระบวนการพัฒนาด้วยการมอบความสามารถในการแก้ไขข้อบกพร่องและการซิงค์ด่วนแบบเดิมให้กับการพัฒนาที่อิงตามคอนเทนเนอร์
ลงชื่อเข้าใช้ Google Cloud
คลิกไอคอน Cloud Code แล้วเลือก "ลงชื่อเข้าใช้ Google Cloud"

คลิก "ไปลงชื่อเข้าใช้"

ตรวจสอบเอาต์พุตในเทอร์มินัลและเปิดลิงก์

เข้าสู่ระบบด้วยข้อมูลเข้าสู่ระบบของนักเรียน Qwiklabs

เลือก "อนุญาต"

คัดลอกรหัสยืนยันแล้วกลับไปที่แท็บเวิร์กสเตชัน

วางรหัสยืนยันแล้วกด Enter

เพิ่มคลัสเตอร์ Kubernetes
- เพิ่มคลัสเตอร์

- เลือก Google Kubernetes Engine

- เลือกโปรเจ็กต์

- เลือก "quote-cluster" ที่สร้างขึ้นในการตั้งค่าเริ่มต้น


ตั้งค่ารหัสโปรเจ็กต์ปัจจุบันโดยใช้ gcloud CLI
คัดลอกรหัสโปรเจ็กต์สำหรับ Lab นี้จากหน้า Qwiklabs

เรียกใช้คำสั่ง gcloud CLI เพื่อตั้งค่ารหัสโปรเจ็กต์ แทนที่รหัสโปรเจ็กต์ตัวอย่างก่อนเรียกใช้คำสั่ง
gcloud config set project qwiklabs-gcp-project-id
ตัวอย่างเอาต์พุต

แก้ไขข้อบกพร่องใน Kubernetes
- ที่แผงด้านซ้าย ให้เลือก Cloud Code ที่ด้านล่าง

- ในแผงที่ปรากฏในส่วน DEVELOPMENT SESSIONS ให้เลือก Debug on Kubernetes
เลื่อนลงหากไม่เห็นตัวเลือกนี้

- เลือก "ใช่" เพื่อใช้บริบทปัจจุบัน

- เลือก "quote-cluster" ที่สร้างขึ้นในระหว่างการตั้งค่าครั้งแรก

- เลือกที่เก็บคอนเทนเนอร์

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

รอให้ระบบติดตั้งใช้งานแอปพลิเคชัน

- ตรวจสอบแอปพลิเคชันที่ทําให้ใช้งานได้ใน GKE ใน Cloud Console

- กลับไปที่มุมมองแบบง่ายโดยเลือก "Kubernetes: เรียกใช้/แก้ไขข้อบกพร่อง" จากเมนูแบบเลื่อนลงในแท็บเอาต์พุต
- เมื่อการสร้างและการทดสอบเสร็จสมบูรณ์ แท็บเอาต์พุตจะแสดง
Resource deployment/demo-app status completed successfullyและ URL ที่ระบุคือ "Forwarded URL from service demo-app: http://localhost:8080" - ในเทอร์มินัล Cloud Code ให้วางเมาส์เหนือ URL ในเอาต์พุต (http://localhost:8080) จากนั้นเลือก "ติดตามลิงก์" ในเคล็ดลับเครื่องมือที่ปรากฏขึ้น

แท็บใหม่จะเปิดขึ้นและคุณจะเห็นเอาต์พุตด้านล่าง

ใช้เบรกพอยท์
- เปิดแอปพลิเคชัน
HelloController.javaที่/src/main/java/com/example/springboot/HelloController.java - ค้นหาคำสั่ง "return" สำหรับเส้นทางรูทซึ่งอ่านว่า
return String.format("Hello from your %s environment!", target); - เพิ่มเบรกพอยต์ในบรรทัดนั้นโดยคลิกพื้นที่ว่างทางด้านซ้ายของหมายเลขบรรทัด ตัวบ่งชี้สีแดงจะแสดงขึ้นเพื่อระบุว่าได้ตั้งค่าเบรกพอยต์แล้ว

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

- คลิกส่วนตัวแปรลงไปจนกว่าจะพบตัวแปร "เป้าหมาย"
- สังเกตค่าปัจจุบันเป็น "local"

- ดับเบิลคลิกที่ชื่อตัวแปร "target" และในป๊อปอัป
เปลี่ยนค่าเป็น "Cloud Workstations"

- คลิกปุ่ม "ดำเนินการต่อ" ในแผงควบคุมการแก้ไขข้อบกพร่อง

- ตรวจสอบการตอบกลับในเบราว์เซอร์ ซึ่งตอนนี้จะแสดงค่าที่อัปเดตที่คุณเพิ่งป้อน

- นำเบรกพอยท์ออกโดยคลิกตัวบ่งชี้สีแดงทางด้านซ้ายของหมายเลขบรรทัด ซึ่งจะช่วยป้องกันไม่ให้โค้ดหยุดการดำเนินการที่บรรทัดนี้เมื่อคุณดำเนินการต่อในแล็บนี้
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>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
</dependency>
บริการ REST ของโค้ด
Quote.java
สร้างไฟล์ชื่อ Quote.java ใน /src/main/java/com/example/springboot/ แล้วคัดลอกโค้ดด้านล่าง ซึ่งกำหนดโมเดลเอนทิตีสำหรับออบเจ็กต์ใบเสนอราคาที่ใช้ในแอปพลิเคชัน
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 ซึ่งจัดเก็บเป็น 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
เปิด deployment.yaml และตรวจสอบว่าค่า DB_HOST ได้รับการอัปเดตด้วย IP ของอินสแตนซ์แล้ว

ทำให้แอปพลิเคชันใช้งานได้และตรวจสอบ
- ในแผงที่ด้านล่างของ Cloud Shell Editor ให้เลือก Cloud Code แล้วเลือกแก้ไขข้อบกพร่องใน Kubernetes ที่ด้านบนของหน้าจอ

- เมื่อการสร้างและการทดสอบเสร็จสมบูรณ์ แท็บเอาต์พุตจะแสดง
Resource deployment/demo-app status completed successfullyและ URL ที่ระบุคือ "Forwarded URL from service demo-app: http://localhost:8080" โปรดทราบว่าบางครั้งพอร์ตอาจแตกต่างกัน เช่น 8081 หากเป็นเช่นนั้น ให้ตั้งค่าที่เหมาะสม ตั้งค่า URL ในเทอร์มินัล
export URL=localhost:8080
- ดูคำคมแบบสุ่ม
จากเทอร์มินัล ให้เรียกใช้คำสั่งด้านล่างหลายครั้งกับปลายทาง random-quote สังเกตการเรียกซ้ำที่แสดงราคาที่แตกต่างกัน
curl $URL/random-quote | jq
- เพิ่มคำพูด
สร้างใบเสนอราคาใหม่โดยมีรหัส=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
- ลบใบเสนอราคา
ตอนนี้ให้ลบคำพูดที่คุณเพิ่งเพิ่มด้วยเมธอดลบและสังเกตโค้ดตอบกลับ HTTP/1.1 204
curl -v -X DELETE $URL/quotes/6
- เกิดข้อผิดพลาดที่เซิร์ฟเวอร์
พบสถานะข้อผิดพลาดโดยการเรียกใช้คำขอล่าสุดอีกครั้งหลังจากลบรายการไปแล้ว
curl -v -X DELETE $URL/quotes/6
โปรดสังเกตว่าการตอบกลับจะแสดง HTTP:500 Internal Server Error
แก้ไขข้อบกพร่องของแอปพลิเคชัน
ในส่วนก่อนหน้า คุณพบสถานะข้อผิดพลาดในแอปพลิเคชันเมื่อพยายามลบรายการที่ไม่ได้อยู่ในฐานข้อมูล ในส่วนนี้ คุณจะตั้งค่าเบรกพอยต์เพื่อค้นหาปัญหา ข้อผิดพลาดเกิดขึ้นในการดำเนินการ DELETE ดังนั้นคุณจะต้องทำงานกับคลาส QuoteController
- เปิด
src/main/java/com/example/springboot/QuoteController.java - ค้นหาวิธี
deleteQuote() - ค้นหาบรรทัดต่อไปนี้
Optional<Quote> quote = quoteRepository.findById(id); - ตั้งค่าเบรกพอยต์ในบรรทัดนั้นโดยคลิกพื้นที่ว่างทางด้านซ้ายของหมายเลขบรรทัด
- ตัวบ่งชี้สีแดงจะปรากฏขึ้นเพื่อระบุว่าได้ตั้งค่าเบรกพอยต์แล้ว
- เรียกใช้คำสั่ง
deleteอีกครั้ง
curl -v -X DELETE $URL/quotes/6
- เปลี่ยนกลับไปใช้มุมมองการแก้ไขข้อบกพร่องโดยคลิกไอคอนในคอลัมน์ด้านซ้าย
- สังเกตบรรทัดการแก้ไขข้อบกพร่องที่หยุดในคลาส QuoteController
- ในดีบักเกอร์ ให้คลิกไอคอน
step over
- สังเกตว่าโค้ดแสดงข้อผิดพลาด HTTP 500 Internal Server Error ต่อไคลเอ็นต์ ซึ่งไม่เหมาะสม
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 Not Found กลับ
แก้ไขข้อผิดพลาด
- ขณะที่เซสชันการแก้ไขข้อบกพร่องยังทำงานอยู่ ให้ดำเนินการตามคำขอให้เสร็จสมบูรณ์โดยกดปุ่ม "ดำเนินการต่อ" ในแผงควบคุมการแก้ไขข้อบกพร่อง
- จากนั้นเปลี่ยนบล็อก
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);
}
}
- เรียกใช้คำสั่งลบอีกครั้ง
curl -v -X DELETE $URL/quotes/6
- ทำตามขั้นตอนในดีบักเกอร์และสังเกตข้อความ "ไม่พบ HTTP 404" ที่ส่งกลับไปยังผู้โทร
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 ระยะไกลตามโฟลว์ของนักพัฒนาซอฟต์แวร์เดียวกันกับที่พบในสแต็กแอปพลิเคชันแบบดั้งเดิม
สิ่งที่คุณได้เรียนรู้
- การพัฒนา InnerLoop ด้วย Cloud Workstations
- การสร้างแอปพลิเคชันเริ่มต้น Java ใหม่
- การเดินผ่านกระบวนการพัฒนา
- การพัฒนาบริการ REST แบบ CRUD อย่างง่าย
- การแก้ไขข้อบกพร่องของแอปพลิเคชันในคลัสเตอร์ GKE
- การเชื่อมต่อแอปพลิเคชันกับฐานข้อมูล Cloud SQL
