تطوير InnerLoop باستخدام Python

1. نظرة عامة

يوضّح هذا الدرس التطبيقي الميزات والإمكانات المصمَّمة لتبسيط سير عمل التطوير لمهندسي البرامج المكلّفين بتطوير تطبيقات Python في بيئة تستخدم حاويات. يتطلّب تطوير الحاويات المعتاد أن يفهم المستخدم تفاصيل الحاويات وعملية التصميم. بالإضافة إلى ذلك، على المطوّرين عادةً إيقاف عملية التطوير مؤقتًا والانتقال من بيئة التطوير المتكاملة (IDE) لاختبار تطبيقاتهم وتصحيح الأخطاء فيها في بيئات بعيدة. باستخدام الأدوات والتكنولوجيات المذكورة في هذا البرنامج التعليمي، يمكن للمطوّرين العمل بفعالية مع التطبيقات المستندة إلى حاويات بدون مغادرة بيئة التطوير المتكاملة.

ما ستتعلمه

في هذا الدرس التطبيقي، ستتعرّف على طرق التطوير باستخدام الحاويات في Google Cloud Platform، بما في ذلك:

  • إنشاء تطبيق Python جديد للمبتدئين
  • الخطوات المفصّلة لعملية التطوير
  • تطوير خدمة REST بسيطة لإنشاء البيانات وقراءتها وتعديلها وحذفها

2. الإعداد والمتطلبات

إعداد البيئة بوتيرة ذاتية

  1. سجِّل الدخول إلى Google Cloud Console وأنشِئ مشروعًا جديدًا أو أعِد استخدام مشروع حالي. إذا لم يكن لديك حساب على Gmail أو Google Workspace، عليك إنشاء حساب.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • اسم المشروع هو الاسم المعروض للمشاركين في هذا المشروع. وهي سلسلة من الأحرف لا تستخدمها Google APIs، ويمكنك تعديلها في أي وقت.
  • يجب أن يكون رقم تعريف المشروع فريدًا في جميع مشاريع Google Cloud، كما أنّه غير قابل للتغيير (لا يمكن تغييره بعد ضبطه). تنشئ Cloud Console تلقائيًا سلسلة فريدة، ولا يهمّك عادةً ما هي. في معظم دروس الترميز، عليك الرجوع إلى رقم تعريف المشروع (ويتم تحديده عادةً على أنّه PROJECT_ID)، لذا إذا لم يعجبك، يمكنك إنشاء رقم آخر عشوائي، أو يمكنك تجربة رقمك الخاص ومعرفة ما إذا كان متاحًا. ثم يتم "تجميده" بعد إنشاء المشروع.
  • هناك قيمة ثالثة، وهي رقم المشروع الذي تستخدمه بعض واجهات برمجة التطبيقات. يمكنك الاطّلاع على مزيد من المعلومات عن كل هذه القيم الثلاث في المستندات.
  1. بعد ذلك، عليك تفعيل الفوترة في Cloud Console من أجل استخدام موارد/واجهات برمجة تطبيقات Cloud. لن تكلفك تجربة هذا الدرس البرمجي الكثير من المال، إن لم تكلفك شيئًا على الإطلاق. لإيقاف الموارد كي لا يتم تحصيل رسوم منك بعد هذا الدرس التطبيقي حول الترميز، اتّبِع أي تعليمات "تنظيف" واردة في نهاية الدرس. يمكن لمستخدمي Google Cloud الجدد الاستفادة من برنامج الفترة التجريبية المجانية بقيمة 300 دولار أمريكي.

بدء Cloudshell Editor

تم تصميم هذا الدرس التطبيقي واختباره لاستخدامه مع "محرّر Google Cloud Shell". للوصول إلى المحرِّر، اتّبِع الخطوات التالية:

  1. الوصول إلى مشروعك على Google من خلال https://console.cloud.google.com
  2. في أعلى يسار الصفحة، انقر على رمز محرر Cloud Shell

8560cc8d45e8c112.png

  1. سيتم فتح لوحة جديدة في أسفل النافذة
  2. انقر على الزر "فتح المحرِّر"

9e504cb98a6a8005.png

  1. سيتم فتح المحرِّر مع مستكشف على اليسار ومحرِّر في المنطقة الوسطى
  2. يجب أن يتوفّر جزء وحدة طرفية أيضًا في أسفل الشاشة
  3. إذا لم تكن النافذة الطرفية مفتوحة، استخدِم مجموعة المفاتيح `ctrl+`` لفتح نافذة طرفية جديدة.

إعداد البيئة

في Cloud Shell، اضبط رقم تعريف مشروعك ورقم المشروع. احفظها كمتغيرات PROJECT_ID وPROJECT_ID.

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

الحصول على رمز المصدر

  1. يمكنك العثور على الرمز المصدري لهذا المختبر في حاوية container-developer-workshop في GoogleCloudPlatform على GitHub. استنسِخه باستخدام الأمر أدناه، ثم انتقِل إلى الدليل.
git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git &&
cd container-developer-workshop/labs/python
mkdir music-service && cd music-service 
cloudshell workspace .

إذا لم تكن النافذة الطرفية مفتوحة، استخدِم مجموعة المفاتيح `ctrl+`` لفتح نافذة طرفية جديدة.

توفير البنية الأساسية المستخدَمة في هذا الدرس التطبيقي

في هذا التمرين العملي، ستنفّذ رمزًا برمجيًا على GKE وتصل إلى البيانات المخزّنة في قاعدة بيانات Spanner. يُعدّ برنامج الإعداد النصي أدناه هذه البنية الأساسية لك. ستستغرق عملية توفير المتطلبات اللازمة أكثر من 10 دقائق. يمكنك مواصلة الخطوات القليلة التالية أثناء معالجة عملية الإعداد.

../setup.sh

3- إنشاء تطبيق Python أوّلي جديد

  1. أنشئ ملفًا باسم requirements.txt وانسخ المحتوى التالي فيه
Flask
gunicorn
google-cloud-spanner
ptvsd==4.3.2
  1. أنشئ ملفًا باسم app.py والصِق الرمز التالي فيه
import os
from flask import Flask, request, jsonify
from google.cloud import spanner

app = Flask(__name__)

@app.route("/")
def hello_world():
    message="Hello, World!"
    return message

if __name__ == '__main__':
    server_port = os.environ.get('PORT', '8080')
    app.run(debug=False, port=server_port, host='0.0.0.0')

  1. أنشئ ملفًا باسم Dockerfile والصق ما يلي فيه
FROM python:3.8
ARG FLASK_DEBUG=0
ENV FLASK_DEBUG=$FLASK_DEBUG
ENV FLASK_APP=app.py
WORKDIR /app
COPY requirements.txt .
RUN pip install --trusted-host pypi.python.org -r requirements.txt
COPY . .
ENTRYPOINT ["python3", "-m", "flask", "run", "--port=8080", "--host=0.0.0.0"]

ملاحظة: يتيح لك FLASK_DEBUG=1 إعادة تحميل تغييرات الرمز تلقائيًا إلى تطبيق Python flask. يتيح لك Dockerfile تمرير هذه القيمة كمعلَمة إنشاء.

إنشاء بيانات

في الوحدة الطرفية، نفِّذ الأمر التالي لإنشاء ملف skaffold.yaml وdeployment.yaml تلقائيين.

  1. ابدأ Skaffold باستخدام الأمر التالي
skaffold init --generate-manifests

عندما يُطلب منك ذلك، استخدِم الأسهم لتحريك المؤشر ومفتاح المسافة لاختيار الخيارات.

اختيار:

  • 8080 للمنفذ
  • y لحفظ الإعدادات

تعديل إعدادات Skaffold

  • تغيير اسم التطبيق التلقائي
  • فتح "skaffold.yaml"
  • اختَر اسم الصورة المضبوطة حاليًا على dockerfile-image
  • انقر بزر الماوس الأيمن واختَر "تغيير كل التكرارات"
  • اكتب الاسم الجديد باللغة python-app
  • عدِّل قسم الإنشاء أكثر من ذلك ليصبح
  • إضافة docker.buildArgs إلى بطاقة FLASK_DEBUG=1
  • مزامنة الإعدادات لتحميل أي تغييرات على ملفات *.py من بيئة التطوير المتكاملة إلى الحاوية قيد التشغيل

بعد التعديلات، سيكون قسم الإنشاء في ملف skaffold.yaml على النحو التالي:

build:
 artifacts:
 - image: python-app
   docker:
     buildArgs:
       FLASK_DEBUG: 1
     dockerfile: Dockerfile
   sync:
     infer:
     - '**/*.py'

تعديل ملف إعداد Kubernetes

  1. تغيير الاسم التلقائي
  • فتح ملف deployment.yaml
  • اختَر اسم الصورة المضبوطة حاليًا على dockerfile-image
  • انقر بزر الماوس الأيمن واختَر "تغيير كل التكرارات"
  • اكتب الاسم الجديد باللغة python-app

4. التعرّف على عملية التطوير

بعد إضافة منطق النشاط التجاري، يمكنك الآن نشر تطبيقك واختباره. سيسلّط القسم التالي الضوء على استخدام مكوّن Cloud Code الإضافي. يتكامل هذا المكوّن الإضافي مع Skaffold، من بين أمور أخرى، لتبسيط عملية التطوير. عند النشر إلى GKE في الخطوات التالية، ستنشئ Cloud Code وSkaffold تلقائيًا صورة الحاوية، وتدفعها إلى Container Registry، ثم تنشر تطبيقك إلى GKE. يحدث ذلك وراء الكواليس، حيث يتم تجريد التفاصيل بعيدًا عن مسار المطوّر.

النشر على Kubernetes

  1. في اللوحة أسفل "محرّر Cloud Shell"، انقر على Cloud Code 

fdc797a769040839.png

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

cfce0d11ef307087.png

يبدأ هذا الأمر إنشاء الرمز المصدر ثم ينفّذ الاختبارات. سيستغرق إنشاء التطبيق وإجراء الاختبارات بضع دقائق. تشمل هذه الاختبارات اختبارات الوحدات وخطوة التحقّق التي تتحقّق من القواعد التي تم ضبطها لبيئة النشر. تمّت إعداد خطوة التحقّق هذه مسبقًا، وهي تضمن تلقّي تحذير بشأن مشاكل النشر حتى أثناء العمل في بيئة التطوير.

  1. في المرة الأولى التي تنفّذ فيها الأمر، سيظهر طلب في أعلى الشاشة يسألك عمّا إذا كنت تريد سياق Kubernetes الحالي، اختَر "نعم" للقبول واستخدام السياق الحالي.
  2. بعد ذلك، ستظهر رسالة تطلب منك تحديد سجلّ الحاويات الذي تريد استخدامه. اضغط على مفتاح Enter لقبول القيمة التلقائية المقدَّمة
  3. انقر على علامة التبويب "الإخراج" (Output) في اللوحة السفلية للاطّلاع على مستوى التقدّم والإشعارات

f95b620569ba96c5.png

  1. اختَر "Kubernetes: Run/Debug - Detailed" (Kubernetes: تشغيل/تصحيح الأخطاء - تفصيلي) في القائمة المنسدلة للقناة على اليسار لعرض تفاصيل إضافية وسجلات يتم بثها مباشرةً من الحاويات.

94acdcdda6d2108.png

عند اكتمال الإنشاء والاختبارات، ستظهر الرسالة Attached debugger to container "python-app-8476f4bbc-h6dsl" successfully. في علامة التبويب "الإخراج"، وسيتم إدراج عنوان URL http://localhost:8080.

  1. في نافذة Cloud Code الطرفية، مرِّر مؤشر الماوس فوق عنوان URL الأول في الناتج (http://localhost:8080)، ثم انقر على Open Web Preview (فتح معاينة الويب) في تلميح الأداة الذي يظهر.
  2. سيتم فتح علامة تبويب جديدة في المتصفّح وعرض الرسالة Hello, World!

إعادة التحميل السريع

  1. افتح ملف app.py
  2. تغيير رسالة الترحيب إلى Hello from Python

لاحظ على الفور أنّه في نافذة Output، في طريقة العرض Kubernetes: Run/Debug، يزامن برنامج المراقبة الملفات المعدَّلة مع الحاوية في Kubernetes.

Update initiated
Build started for artifact python-app
Build completed for artifact python-app

Deploy started
Deploy completed

Status check started
Resource pod/python-app-6f646ffcbb-tn7qd status updated to In Progress
Resource deployment/python-app status updated to In Progress
Resource deployment/python-app status completed successfully
Status check succeeded
...
  1. إذا انتقلت إلى طريقة العرض Kubernetes: Run/Debug - Detailed، ستلاحظ أنّها تتعرّف على تغييرات الملفات ثم تنشئ التطبيق وتعيد نشره.
files modified: [app.py]
Syncing 1 files for gcr.io/veer-pylab-01/python-app:3c04f58-dirty@sha256:a42ca7250851c2f2570ff05209f108c5491d13d2b453bb9608c7b4af511109bd
Copying files:map[app.py:[/app/app.py]]togcr.io/veer-pylab-01/python-app:3c04f58-dirty@sha256:a42ca7250851c2f2570ff05209f108c5491d13d2b453bb9608c7b4af511109bd
Watching for changes...
[python-app] * Detected change in '/app/app.py', reloading
[python-app] * Restarting with stat
[python-app] * Debugger is active!
[python-app] * Debugger PIN: 744-729-662
  1. يُرجى إعادة تحميل المتصفّح للاطّلاع على النتائج المعدَّلة.

تصحيح الأخطاء

  1. انتقِل إلى "عرض تصحيح الأخطاء" وأوقِف سلسلة المحادثات الحالية 647213126d7a4c7b.png.
  2. انقر على Cloud Code في القائمة السفلية، ثم اختَر Debug on Kubernetes لتشغيل التطبيق في وضع debug.
  • في طريقة العرض Kubernetes Run/Debug - Detailed لنافذة Output، لاحظ أنّ Skaffold سينشر هذا التطبيق في وضع تصحيح الأخطاء.
  1. في المرة الأولى التي يتم فيها تشغيل هذا الإجراء، سيظهر طلب يسأل عن مكان المصدر داخل الحاوية. ترتبط هذه القيمة بالأدلة في Dockerfile.

اضغط على Enter لقبول القيمة التلقائية

583436647752e410.png

سيستغرق إنشاء التطبيق ونشره بضع دقائق.

  1. عند اكتمال العملية ستلاحظ ربط أداة تصحيح الأخطاء.
Port forwarding pod/python-app-8bd64cf8b-cskfl in namespace default, remote port 5678 -> http://127.0.0.1:5678
  1. يتغيّر لون شريط الحالة السفلي من الأزرق إلى البرتقالي للإشارة إلى أنّه في "وضع تصحيح الأخطاء".
  2. في العرض Kubernetes Run/Debug، لاحظ أنّه تم بدء تشغيل حاوية قابلة للتصحيح
**************URLs*****************
Forwarded URL from service python-app: http://localhost:8080
Debuggable container started pod/python-app-8bd64cf8b-cskfl:python-app (default)
Update succeeded
***********************************

استخدام نقاط الإيقاف

  1. افتح ملف app.py
  2. ابحث عن العبارة التي تقول return message
  3. أضِف نقطة توقّف إلى هذا السطر من خلال النقر على المساحة الفارغة على يمين رقم السطر. سيظهر مؤشر أحمر للإشارة إلى أنّه تم ضبط نقطة الإيقاف.
  4. أعِد تحميل المتصفّح ولاحظ أنّ أداة تصحيح الأخطاء توقف العملية عند نقطة التوقف وتتيح لك التحقّق من المتغيرات وحالة التطبيق الذي يتم تشغيله عن بُعد في GKE.
  5. انقر على قسم "المتغيّرات"
  6. انقر على "المتغيرات المحلية"، وستجد المتغير "message".
  7. انقر مرّتين على اسم المتغيّر "message" (الرسالة)، وفي النافذة المنبثقة، غيِّر القيمة إلى قيمة مختلفة مثل "Greetings from Python"
  8. انقر على الزر "متابعة" في لوحة التحكّم في تصحيح الأخطاء 607c33934f8d6b39.png
  9. راجِع الردّ في المتصفّح الذي يعرض الآن القيمة المعدَّلة التي أدخلتها للتو.
  10. أوقِف وضع "تصحيح الأخطاء" من خلال الضغط على زر الإيقاف 647213126d7a4c7b.png وأزِل نقطة التوقف من خلال النقر عليها مرة أخرى.

5- تطوير خدمة REST بسيطة لإنشاء البيانات وقراءتها وتعديلها وحذفها

في هذه المرحلة، يكون تطبيقك قد تم إعداده بالكامل للتطوير ضِمن حاوية، ويكون قد تم إرشادك خلال سير عمل التطوير الأساسي باستخدام Cloud Code. في الأقسام التالية، ستتدرب على ما تعلّمته من خلال إضافة نقاط نهاية لخدمة REST تتصل بقاعدة بيانات مُدارة في Google Cloud.

كتابة رمز خدمة REST

ينشئ الرمز البرمجي أدناه خدمة بسيطة للوصول إلى البيانات من خلال REST تستخدم Spanner كقاعدة بيانات تدعم التطبيق. أنشئ التطبيق عن طريق نسخ الرمز التالي ولصقه في تطبيقك.

  1. أنشئ التطبيق الرئيسي عن طريق استبدال app.py بالمحتويات التالية
import os
from flask import Flask, request, jsonify
from google.cloud import spanner


app = Flask(__name__)


instance_id = "music-catalog"

database_id = "musicians"

spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)


@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

@app.route('/singer', methods=['POST'])
def create():
    try:
        request_json = request.get_json()
        singer_id = request_json['singer_id']
        first_name = request_json['first_name']
        last_name = request_json['last_name']
        def insert_singers(transaction):
            row_ct = transaction.execute_update(
                f"INSERT Singers (SingerId, FirstName, LastName) VALUES" \
                f"({singer_id}, '{first_name}', '{last_name}')"
            )
            print("{} record(s) inserted.".format(row_ct))

        database.run_in_transaction(insert_singers)

        return {"Success": True}, 200
    except Exception as e:
        return e



@app.route('/singer', methods=['GET'])
def get_singer():

    try:
        singer_id = request.args.get('singer_id')
        def get_singer():
            first_name = ''
            last_name = ''
            with database.snapshot() as snapshot:
                results = snapshot.execute_sql(
                    f"SELECT SingerId, FirstName, LastName FROM Singers " \
                    f"where SingerId = {singer_id}",
                    )
                for row in results:
                    first_name = row[1]
                    last_name = row[2]
                return (first_name,last_name )
        first_name, last_name = get_singer()  
        return {"first_name": first_name, "last_name": last_name }, 200
    except Exception as e:
        return e


@app.route('/singer', methods=['PUT'])
def update_singer_first_name():
    try:
        singer_id = request.args.get('singer_id')
        request_json = request.get_json()
        first_name = request_json['first_name']
        
        def update_singer(transaction):
            row_ct = transaction.execute_update(
                f"UPDATE Singers SET FirstName = '{first_name}' WHERE SingerId = {singer_id}"
            )

            print("{} record(s) updated.".format(row_ct))

        database.run_in_transaction(update_singer)
        return {"Success": True}, 200
    except Exception as e:
        return e


@app.route('/singer', methods=['DELETE'])
def delete_singer():
    try:
        singer_id = request.args.get('singer')
    
        def delete_singer(transaction):
            row_ct = transaction.execute_update(
                f"DELETE FROM Singers WHERE SingerId = {singer_id}"
            )
            print("{} record(s) deleted.".format(row_ct))

        database.run_in_transaction(delete_singer)
        return {"Success": True}, 200
    except Exception as e:
        return e

port = int(os.environ.get('PORT', 8080))
if __name__ == '__main__':
    app.run(threaded=True, host='0.0.0.0', port=port)

إضافة إعدادات قاعدة البيانات

للاتصال بـ Spanner بشكل آمن، عليك إعداد التطبيق لاستخدام Workload Identities. يتيح ذلك لتطبيقك العمل كحساب خدمة خاص به والحصول على أذونات فردية عند الوصول إلى قاعدة البيانات.

  1. تعديل deployment.yaml أضِف الرمز التالي في نهاية الملف (احرص على الاحتفاظ بمسافات الجدولة في المثال أدناه)
      serviceAccountName: python-ksa
      nodeSelector:
        iam.gke.io/gke-metadata-server-enabled: "true" 

نشر التطبيق والتحقّق من صحته

  1. في اللوحة أسفل "محرّر Cloud Shell"، انقر على Cloud Code ثم على Debug on Kubernetes في أعلى الشاشة.
  2. عند اكتمال عملية الإنشاء والاختبارات، تعرض علامة التبويب "الإخراج" Resource deployment/python-app status completed successfully، ويتم إدراج عنوان URL: "Forwarded URL from service python-app: http://localhost:8080"
  3. أضِف بضع إدخالات.

من "وحدة Cloudshell الطرفية"، شغِّل الأمر أدناه

curl -X POST http://localhost:8080/singer -H 'Content-Type: application/json' -d '{"first_name":"Cat","last_name":"Meow", "singer_id": 6}'
  1. اختبِر طلب GET من خلال تنفيذ الأمر أدناه في الوحدة الطرفية
curl -X GET http://localhost:8080/singer?singer_id=6
  1. اختبار الحذف: جرِّب الآن حذف إدخال من خلال تنفيذ الأمر التالي. غيِّر قيمة item-id إذا لزم الأمر.
curl -X DELETE http://localhost:8080/singer?singer_id=6
    This throws an error message
500 Internal Server Error

تحديد المشكلة وحلّها

  1. استخدِم وضع تصحيح الأخطاء للعثور على المشكلة. إليك بعض النصائح:
  • ندرك أنّ هناك مشكلة في الأمر DELETE لأنّه لا يعرض النتيجة المطلوبة. لذلك، عليك ضبط نقطة التوقّف في app.py في الطريقة delete_singer.
  • نفِّذ التعليمات البرمجية خطوة بخطوة وراقِب المتغيرات في كل خطوة لملاحظة قيم المتغيرات المحلية في النافذة اليمنى.
  • لمراقبة قيم محدّدة مثل singer_id وrequest.args، أضِف هذه المتغيّرات إلى نافذة "المراقبة".
  1. لاحظ أنّ القيمة المعيّنة لـ singer_id هي None. غيِّر الرمز البرمجي لحلّ المشكلة.

سيبدو مقتطف الرمز الثابت على النحو التالي.

@app.route('/delete-singer', methods=['DELETE', 'GET'])
def delete_singer():
    try:
        singer_id = request.args.get('singer_id')
  1. بعد إعادة تشغيل التطبيق، اختبِر مرة أخرى من خلال محاولة الحذف.
  2. أوقِف جلسة تصحيح الأخطاء من خلال النقر على المربّع الأحمر في شريط أدوات تصحيح الأخطاء 647213126d7a4c7b.png

6. تنظيف

تهانينا! في هذا التمرين المعملي، أنشأت تطبيق Python جديدًا من البداية وأعددته للعمل بفعالية مع الحاويات. بعد ذلك، نشرت تطبيقك وأصلحت أخطاءه في مجموعة GKE بعيدة باتّباع مسار المطوّر نفسه المتوفّر في حِزم التطبيقات التقليدية.

لإجراء عملية تنظيف بعد إكمال الدرس التطبيقي، اتّبِع الخطوات التالية:

  1. حذف الملفات المستخدَمة في المختبر
cd ~ && rm -rf container-developer-workshop
  1. احذف المشروع لإزالة جميع البنية الأساسية والموارد ذات الصلة