1. نظرة عامة
يوضّح هذا الدرس التطبيقي الميزات والإمكانات المصمَّمة لتبسيط سير عمل التطوير لمهندسي البرامج المكلّفين بتطوير تطبيقات Java في بيئة تستخدم حاويات. يتطلّب تطوير الحاويات المعتاد أن يفهم المستخدم تفاصيل الحاويات وعملية التصميم. بالإضافة إلى ذلك، على المطوّرين عادةً إيقاف عملية التطوير مؤقتًا والانتقال من بيئة التطوير المتكاملة (IDE) لاختبار تطبيقاتهم وتصحيح الأخطاء فيها في بيئات بعيدة. باستخدام الأدوات والتكنولوجيات المذكورة في هذا البرنامج التعليمي، يمكن للمطوّرين العمل بفعالية مع التطبيقات المستندة إلى حاويات بدون مغادرة بيئة التطوير المتكاملة.
ما ستتعلمه
في هذا الدرس التطبيقي، ستتعرّف على طرق التطوير باستخدام الحاويات في Google Cloud Platform، بما في ذلك:
- الإعداد والمتطلبات
- إنشاء تطبيق Java جديد للمبتدئين
- التعرّف على عملية التطوير
- تطوير خدمة REST بسيطة لإنشاء البيانات وقراءتها وتعديلها وحذفها
- تنظيف
2. الإعداد والمتطلبات
إعداد البيئة بوتيرة ذاتية
- سجِّل الدخول إلى Google Cloud Console وأنشِئ مشروعًا جديدًا أو أعِد استخدام مشروع حالي. إذا لم يكن لديك حساب على Gmail أو Google Workspace، عليك إنشاء حساب.



- اسم المشروع هو الاسم المعروض للمشاركين في هذا المشروع. وهي سلسلة من الأحرف لا تستخدمها Google APIs، ويمكنك تعديلها في أي وقت.
- يجب أن يكون رقم تعريف المشروع فريدًا في جميع مشاريع Google Cloud، كما أنّه غير قابل للتغيير (لا يمكن تغييره بعد ضبطه). تنشئ Cloud Console تلقائيًا سلسلة فريدة، ولا يهمّك عادةً ما هي. في معظم دروس الترميز، عليك الرجوع إلى رقم تعريف المشروع (ويتم تحديده عادةً على أنّه
PROJECT_ID)، لذا إذا لم يعجبك، يمكنك إنشاء رقم آخر عشوائي، أو يمكنك تجربة رقمك الخاص ومعرفة ما إذا كان متاحًا. ثم يتم "تجميده" بعد إنشاء المشروع. - هناك قيمة ثالثة، وهي رقم المشروع الذي تستخدمه بعض واجهات برمجة التطبيقات. يمكنك الاطّلاع على مزيد من المعلومات عن كل هذه القيم الثلاث في المستندات.
- بعد ذلك، عليك تفعيل الفوترة في Cloud Console من أجل استخدام موارد/واجهات برمجة تطبيقات Cloud. لن تكلفك تجربة هذا الدرس البرمجي الكثير من المال، إن لم تكلفك شيئًا على الإطلاق. لإيقاف الموارد كي لا يتم تحصيل رسوم منك بعد هذا الدرس التطبيقي حول الترميز، اتّبِع أي تعليمات "تنظيف" واردة في نهاية الدرس. يمكن لمستخدمي Google Cloud الجدد الاستفادة من برنامج الفترة التجريبية المجانية بقيمة 300 دولار أمريكي.
بدء Cloudshell Editor
تم تصميم هذا الدرس التطبيقي واختباره لاستخدامه مع "محرّر Google Cloud Shell". للوصول إلى المحرِّر، اتّبِع الخطوات التالية:
- الوصول إلى مشروعك على 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)')
الحصول على رمز المصدر
يمكنك العثور على الرمز المصدري لهذا المختبر في حاوية container-developer-workshop في GoogleCloudPlatform على GitHub. استنسِخه باستخدام الأمر أدناه، ثم انتقِل إلى الدليل.
git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git
cd container-developer-workshop/labs/spring-boot
توفير البنية الأساسية المستخدَمة في هذا الدرس التطبيقي
في هذا التمرين العملي، ستنشر رمزًا برمجيًا على 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
cd sample-app && cloudshell workspace .
إضافة spring-boot-devtools وJib
لتفعيل Spring Boot DevTools، ابحث عن pom.xml وافتحه من "المستكشف" في المحرّر. بعد ذلك، الصِق الرمز التالي بعد سطر الوصف الذي يظهر فيه <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-plugin في ملف pom.xml
Jib هي أداة مفتوحة المصدر من Google لوضع تطبيقات Java في حاويات، وتتيح لمطوّري 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 إذا طُلب منك تغيير ملف الإصدار.

إنشاء بيانات
توفّر Skaffold أدوات متكاملة لتسهيل تطوير الحاويات. في هذه الخطوة، ستُهيّئ skaffold التي ستنشئ تلقائيًا ملفات YAML الأساسية في Kubernetes. تحاول العملية تحديد الأدلة التي تتضمّن تعريفات صور الحاويات، مثل Dockerfile، ثم تنشئ بيان نشر وبيان خدمة لكل دليل.
نفِّذ الأمر أدناه لبدء العملية.
- نفِّذ الأمر التالي في الوحدة الطرفية
skaffold init --generate-manifests
- عندما يُطلب منك ذلك:
- استخدِم الأسهم لتحريك المؤشر إلى
Jib Maven Plugin - اضغط على مفتاح المسافة لاختيار الخيار.
- اضغط على Enter للمتابعة
- أدخِل 8080 للمنفذ
- أدخِل y لحفظ الإعدادات
تمت إضافة ملفَين إلى مساحة العمل، وهما skaffold.yaml وdeployment.yaml
تعديل اسم التطبيق
لا تتطابق القيم التلقائية المُضمَّنة في الإعداد حاليًا مع اسم تطبيقك. عدِّل الملفات للإشارة إلى اسم تطبيقك بدلاً من القيم التلقائية.
- تغيير الإدخالات في ملف إعدادات Skaffold
- فتح "
skaffold.yaml" - اختَر اسم الصورة المضبوطة حاليًا على
pom-xml-image - انقر بزر الماوس الأيمن واختَر "تغيير كل التكرارات"
- اكتب الاسم الجديد باللغة
demo-app
- تغيير الإدخالات في إعدادات Kubernetes
- فتح ملف
deployment.yaml - اختَر اسم الصورة المضبوطة حاليًا على
pom-xml-image - انقر بزر الماوس الأيمن واختَر "تغيير كل التكرارات"
- اكتب الاسم الجديد باللغة
demo-app
تفعيل المزامنة السريعة
لتسهيل تجربة إعادة التحميل السريع المحسّنة، ستستخدم ميزة "المزامنة" التي توفّرها أداة Jib. في هذه الخطوة، ستضبط Skaffold لاستخدام هذه الميزة في عملية التصميم.
يُرجى العِلم أنّ ملف "المزامنة" الذي يتم إعداده في إعدادات Skaffold يستفيد من ملف "المزامنة" في 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. التعرّف على عملية التطوير
في هذا القسم، ستتّبع بضع خطوات باستخدام مكوّن Cloud Code الإضافي للتعرّف على العمليات الأساسية والتأكّد من صحة إعدادات تطبيقك الأوّلي.
يتكامل Cloud Code مع Skaffold لتبسيط عملية التطوير. عند النشر إلى GKE في الخطوات التالية، ستنشئ Cloud Code وSkaffold تلقائيًا صورة الحاوية، وتدفعها إلى Container Registry، ثم تنشر تطبيقك إلى GKE. يحدث ذلك وراء الكواليس، حيث يتم تجريد التفاصيل بعيدًا عن مسار المطوّر. تعمل Cloud Code أيضًا على تحسين عملية التطوير من خلال توفير إمكانات تصحيح الأخطاء والمزامنة السريعة التقليدية للتطوير المستند إلى الحاويات.
النشر على Kubernetes
- في اللوحة أسفل "محرّر Cloud Shell"، انقر على Cloud Code 

- في اللوحة التي تظهر في أعلى الصفحة، اختَر "تصحيح الأخطاء على Kubernetes". إذا طُلب منك ذلك، اختَر "نعم" لاستخدام سياق Kubernetes الحالي.

- في المرة الأولى التي تنفّذ فيها الأمر، سيظهر طلب في أعلى الشاشة يسألك عمّا إذا كنت تريد سياق Kubernetes الحالي، اختَر "نعم" للقبول واستخدام السياق الحالي.

- بعد ذلك، ستظهر رسالة تطلب منك تحديد سجلّ الحاويات الذي تريد استخدامه. اضغط على مفتاح Enter لقبول القيمة التلقائية المقدَّمة

- انقر على علامة التبويب "الإخراج" (Output) في اللوحة السفلية للاطّلاع على مستوى التقدّم والإشعارات

- اختَر "Kubernetes: Run/Debug - Detailed" (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)، ثم انقر على "فتح معاينة الويب" (Open Web Preview) في نصيحة الأداة التي تظهر.
سيكون الردّ:
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
- انقر على قسم المتغيّرات إلى أن تجد المتغيّر "الهدف".
- مراقبة القيمة الحالية على أنّها "محلية"
- انقر مرّتين على اسم المتغيّر "target" (الهدف)، وفي النافذة المنبثقة، غيِّر القيمة إلى قيمة مختلفة مثل "Cloud" (السحابة الإلكترونية).
- انقر على الزر "متابعة" في لوحة التحكّم الخاصة بتصحيح الأخطاء
- راجِع الردّ في المتصفّح الذي يعرض الآن القيمة المعدَّلة التي أدخلتها للتو.
إعادة التحميل السريع
- غيِّر العبارة لعرض قيمة مختلفة، مثل "Hello from %s Code"
- يتم حفظ الملف ومزامنته تلقائيًا في الحاويات البعيدة في GKE
- يُرجى إعادة تحميل المتصفّح للاطّلاع على النتائج المعدَّلة.
- أوقِف جلسة تصحيح الأخطاء من خلال النقر على المربّع الأحمر في شريط أدوات تصحيح الأخطاء

5- تطوير خدمة REST بسيطة لإنشاء البيانات وقراءتها وتعديلها وحذفها
في هذه المرحلة، يكون تطبيقك قد تم إعداده بالكامل للتطوير ضِمن حاوية، ويكون قد تم إرشادك خلال سير عمل التطوير الأساسي باستخدام Cloud Code. في الأقسام التالية، ستتدرب على ما تعلّمته من خلال إضافة نقاط نهاية لخدمة REST تتصل بقاعدة بيانات مُدارة في Google Cloud.
ضبط التبعيات
يستخدم الرمز البرمجي للتطبيق قاعدة بيانات للاحتفاظ ببيانات خدمة REST. تأكَّد من توفّر التبعيات من خلال إضافة ما يلي في 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 Console - يُرجى تغيير القيمة. - DB_USER وDB_PASS: كما تم ضبطهما في إعدادات مثيل CloudSQL، ويتم تخزينهما كبيانات سرية في Google Cloud Platform
عدِّل ملف 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"، اختَر "Cloud Code"، ثمّ اختَر "تصحيح الأخطاء على Kubernetes" في أعلى الشاشة.
- عند اكتمال عملية الإنشاء والاختبارات، ستظهر الرسالة
Resource deployment/demo-app status completed successfullyفي علامة التبويب "الإخراج"، وسيتم إدراج عنوان URL: "Forwarded URL from service demo-app: http://localhost:8080" - عرض اقتباسات عشوائية
من "وحدة Cloudshell الطرفية"، نفِّذ الأمر أدناه عدة مرات مقابل نقطة نهاية random-quote. مراقبة معاودة الاتصال بشكل متكرّر لعرض عروض أسعار مختلفة
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.
تصحيح أخطاء التطبيق
في القسم السابق، عثرت على حالة خطأ في التطبيق عند محاولة حذف إدخال غير موجود في قاعدة البيانات. في هذا القسم، ستضبط نقطة توقّف لتحديد المشكلة. حدث الخطأ في عملية 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 Not Found.
صحِّح الخطأ.
- مع استمرار تشغيل "جلسة تصحيح الأخطاء"، أكمل الطلب من خلال النقر على الزر "متابعة" في لوحة التحكّم في تصحيح الأخطاء.
- بعد ذلك، أضِف الكتلة التالية إلى الرمز:
} 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
- احذف المشروع لإزالة جميع البنية الأساسية والموارد ذات الصلة