1. סקירה כללית
בשיעור ה-Lab הזה נדגים תכונות ויכולות שנועדו לייעל את תהליך העבודה של מהנדסי תוכנה שמפתחים אפליקציות Python בסביבה בקונטיינרים. בדרך כלל, פיתוח קונטיינרים דורש מהמשתמש להבין את הפרטים של הקונטיינרים ואת תהליך build של הקונטיינרים. בנוסף, מפתחים בדרך כלל צריכים להפסיק את תהליך העבודה שלהם, לצאת מסביבת הפיתוח המשולבת כדי לבדוק את האפליקציות שלהם ולנפות מהן באגים בסביבות מרוחקות. בעזרת הכלים והטכנולוגיות שמוזכרים במדריך הזה, מפתחים יכולים לעבוד ביעילות עם אפליקציות מבוססות-קונטיינרים בלי לצאת מסביבת הפיתוח המשולבת (IDE).
מה תלמדו
בשיעור ה-Lab הזה תלמדו שיטות לפיתוח באמצעות קונטיינרים ב-GCP, כולל:
- יצירת אפליקציית Python חדשה למתחילים
- הסברים על תהליך הפיתוח
- פיתוח שירות REST פשוט של CRUD
2. הגדרה ודרישות
הגדרת סביבה בקצב עצמי
- נכנסים ל-מסוף Google Cloud ויוצרים פרויקט חדש או משתמשים בפרויקט קיים. אם עדיין אין לכם חשבון Gmail או Google Workspace, אתם צריכים ליצור חשבון.



- שם הפרויקט הוא השם המוצג של הפרויקט הזה למשתתפים. זו מחרוזת של תווים שלא נמצאת בשימוש ב-Google APIs, ואפשר לעדכן אותה בכל שלב.
- מזהה הפרויקט חייב להיות ייחודי לכל הפרויקטים ב-Google Cloud, והוא קבוע (אי אפשר לשנות אותו אחרי שמגדירים אותו). מסוף Cloud יוצר באופן אוטומטי מחרוזת ייחודית. בדרך כלל לא צריך להתייחס אליה. ברוב סדנאות ה-Codelab, צריך להפנות למזהה הפרויקט (ובדרך כלל הוא מזוהה כ-
PROJECT_ID), אז אם לא מוצא חן בעיניכם, אפשר ליצור מזהה אקראי אחר, או לנסות מזהה משלכם ולבדוק אם הוא זמין. אחרי שהפרויקט נוצר, הוא 'קפוא'. - יש ערך שלישי, מספר פרויקט, שחלק מממשקי ה-API משתמשים בו. במאמרי העזרה מפורט מידע נוסף על שלושת הערכים האלה.
- בשלב הבא, תצטרכו להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבי Cloud או בממשקי API. העלות של התרגול הזה לא אמורה להיות גבוהה, ואולי אפילו לא תצטרכו לשלם בכלל. כדי לכבות את המשאבים ולא לחייב אתכם מעבר למה שמוסבר במדריך הזה, צריך לפעול לפי ההוראות לניקוי שמופיעות בסוף ה-Codelab. משתמשים חדשים ב-Google Cloud זכאים לתוכנית תקופת ניסיון בחינם בשווי 300$.
הפעלת Cloud Shell Editor
ה-Lab הזה תוכנן ונבדק לשימוש עם Google Cloud Shell Editor. כדי לגשת לכלי העריכה,
- נכנסים לפרויקט Google בכתובת https://console.cloud.google.com.
- בפינה השמאלית העליונה, לוחצים על סמל העורך של Cloud Shell.

- ייפתח חלונית חדשה בחלק התחתון של החלון.
- לוחצים על הלחצן 'פתיחת הכלי לעריכה'.

- העורך ייפתח עם סייר בצד שמאל ועורך באזור המרכזי
- גם חלונית טרמינל צריכה להיות זמינה בתחתית המסך
- אם הטרמינל לא פתוח, משתמשים בשילוב המקשים 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)')
קבלת קוד המקור
- קוד המקור של ה-Lab הזה נמצא ב-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 + ` כדי לפתוח חלון טרמינל חדש.
הקצאת התשתית שמשמשת בשיעור ה-Lab הזה
בשיעור ה-Lab הזה תפרסו קוד ב-GKE ותגשו לנתונים שמאוחסנים במסד נתונים של Spanner. סקריפט ההגדרה שמופיע בהמשך מכין את התשתית הזו בשבילכם. תהליך ההקצאה יימשך יותר מ-10 דקות. אפשר להמשיך לשלבים הבאים בזמן שההגדרה מתבצעת.
../setup.sh
3. יצירת אפליקציה חדשה ב-Python
- יוצרים קובץ בשם
requirements.txtומעתיקים אליו את התוכן הבא:
Flask
gunicorn
google-cloud-spanner
ptvsd==4.3.2
- יוצרים קובץ בשם
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')
- יוצרים קובץ בשם 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 הזה מאפשר להעביר את הערך הזה כארגומנט של build.
יצירת קובצי מניפסט
בטרמינל, מריצים את הפקודה הבאה כדי ליצור את הקבצים skaffold.yaml ו-deployment.yaml כברירת מחדל
- מאתחלים את Skaffold באמצעות הפקודה הבאה
skaffold init --generate-manifests
כשמופיעה הנחיה, משתמשים במקשי החיצים כדי להזיז את הסמן ובמקש הרווח כדי לבחור את האפשרויות.
בוחרים אפשרות:
8080ליציאהyכדי לשמור את ההגדרה
עדכון ההגדרות של Skaffold
- שינוי השם של אפליקציית ברירת המחדל
- פתיחה של
skaffold.yaml - בוחרים את שם התמונה שמוגדר כרגע כ-
dockerfile-image - לוחצים לחיצה ימנית ובוחרים באפשרות 'שינוי כל המופעים'.
- מקלידים את השם החדש ב
python-app - עורכים את הקטע build כדי
- הוספת
docker.buildArgsלכרטיסFLASK_DEBUG=1 - סנכרון ההגדרות כדי לטעון שינויים בקבצים
*.pyמ-IDE למאגר פועל
אחרי העריכות, קטע ה-build בקובץ skaffold.yaml ייראה כך:
build:
artifacts:
- image: python-app
docker:
buildArgs:
FLASK_DEBUG: 1
dockerfile: Dockerfile
sync:
infer:
- '**/*.py'
שינוי קובץ ההגדרות של Kubernetes
- שינוי השם שמוגדר כברירת המחדל
- פתיחת קובץ
deployment.yaml - בוחרים את שם התמונה שמוגדר כרגע כ-
dockerfile-image - לוחצים לחיצה ימנית ובוחרים באפשרות 'שינוי כל המופעים'.
- מקלידים את השם החדש ב
python-app
4. הסבר על תהליך הפיתוח
אחרי שמוסיפים את הלוגיקה העסקית, אפשר לפרוס את האפליקציה ולבדוק אותה. בקטע הבא נסביר איך להשתמש בפלאגין Cloud Code. בין השאר, הפלאגין הזה משתלב עם skaffold כדי לייעל את תהליך הפיתוח. כשמבצעים פריסה ל-GKE בשלבים הבאים, Cloud Code ו-Skaffold יוצרים אוטומטית את קובץ האימג' של הקונטיינר, מעבירים אותו בדחיפה ל-Container Registry ואז פורסים את האפליקציה ל-GKE. התהליך הזה מתבצע מאחורי הקלעים, והפרטים לא מוצגים בתהליך הפיתוח.
פריסה ב-Kubernetes
- בחלונית שבתחתית Cloud Shell Editor, בוחרים באפשרות Cloud Code 

- בחלונית שמופיעה בחלק העליון, בוחרים באפשרות Run on Kubernetes (הפעלה ב-Kubernetes). אם מוצגת בקשה, בוחרים באפשרות Yes כדי להשתמש בהקשר הנוכחי של Kubernetes.

הפקודה הזו מתחילה ליצור את קוד המקור ואז מריצה את הבדיקות. הפעלת ה-build והבדיקות תימשך כמה דקות. הבדיקות האלה כוללות בדיקות יחידה ושלב אימות שבודק את הכללים שהוגדרו לסביבת הפריסה. שלב האימות הזה כבר מוגדר, והוא מבטיח שתקבלו אזהרה על בעיות בהטמעה גם כשאתם עדיין עובדים בסביבת הפיתוח.
- בפעם הראשונה שמריצים את הפקודה, מופיעה הנחיה בחלק העליון של המסך אם רוצים את ההקשר הנוכחי של Kubernetes. בוחרים באפשרות 'כן' כדי לאשר ולהשתמש בהקשר הנוכחי.
- לאחר מכן תוצג בקשה לבחירת מאגר תמונות. מקישים על Enter כדי לאשר את ערך ברירת המחדל שמופיע
- בוחרים בכרטיסייה 'פלט' בחלונית התחתונה כדי לראות את ההתקדמות וההתראות.

- בוחרים באפשרות Kubernetes: Run/Debug - Detailed (קובernetes: הפעלה/ניפוי באגים – מפורט) בתפריט הנפתח של הערוץ בצד שמאל כדי לראות פרטים נוספים ויומנים שמוזרמים בשידור חי מהקונטיינרים.

בסיום הבנייה והבדיקות, בכרטיסייה Output (פלט) מופיע הכיתוב Attached debugger to container "python-app-8476f4bbc-h6dsl" successfully., וכתובת ה-URL http://localhost:8080 מופיעה ברשימה.
- במסוף Cloud Code, מעבירים את העכבר מעל כתובת ה-URL הראשונה בפלט (http://localhost:8080), ואז בתיאור הכלים שמופיע בוחרים באפשרות 'פתיחת תצוגה מקדימה של האינטרנט'.
- תיפתח כרטיסייה חדשה בדפדפן ותוצג בה ההודעה
Hello, World!
Hot Reload
- פתיחת הקובץ
app.py - שינוי הודעת הפתיחה ל
Hello from Python
אפשר לראות מיד שבחלון Output, בתצוגה Kubernetes: Run/Debug, הכלי watcher מסנכרן את הקבצים המעודכנים עם הקונטיינר ב-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 ...
- אם עוברים לתצוגה
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
- כדי לראות את התוצאות המעודכנות, צריך לרענן את הדפדפן.
ניפוי באגים
- עוברים לתצוגת ניפוי הבאגים ומפסיקים את השרשור הנוכחי
. - לוחצים על
Cloud Codeבתפריט התחתון ובוחרים באפשרותDebug on Kubernetesכדי להפעיל את האפליקציה במצבdebug.
- בתצוגה
Kubernetes Run/Debug - Detailedשל החלוןOutput, אפשר לראות ש-skaffold יפרוס את האפליקציה הזו במצב ניפוי באגים.
- בפעם הראשונה שמריצים את הפקודה, מוצגת הנחיה שבה צריך לציין איפה נמצא המקור בתוך הקונטיינר. הערך הזה קשור לספריות בקובץ Dockerfile.
מקישים על Enter כדי לאשר את ברירת המחדל

ייקח כמה דקות עד שהאפליקציה תיבנה ותיפרס.
- בסיום התהליך. תראו שהמנפה מחובר.
Port forwarding pod/python-app-8bd64cf8b-cskfl in namespace default, remote port 5678 -> http://127.0.0.1:5678
- שורת הסטטוס התחתונה משנה את הצבע שלה מכחול לכתום, כדי לציין שהיא במצב ניפוי באגים.
- בתצוגה
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 ***********************************
שימוש בנקודות עצירה (breakpoint)
- פתיחת הקובץ
app.py - מחפשים את המשפט
return message - מוסיפים נקודת עצירה לשורה הזו בלחיצה על השטח הריק שמימין למספר השורה. יוצג סימן אדום כדי לציין שהנקודה לעצירה מוגדרת
- טוענים מחדש את הדפדפן ורואים שהדיבאגר עוצר את התהליך בנקודת העצירה ומאפשר לכם לבדוק את המשתנים ואת מצב האפליקציה שפועלת מרחוק ב-GKE.
- לוחצים על הקטע VARIABLES (משתנים).
- לוחצים על Locals (משתנים מקומיים) ומוצאים את המשתנה
"message". - לוחצים לחיצה כפולה על שם המשתנה message ובחלון הקופץ משנים את הערך למשהו אחר, כמו
"Greetings from Python" - לוחצים על הלחצן 'המשך' בחלונית הבקרה של ניפוי הבאגים

- בודקים את התגובה בדפדפן, שבו מוצג עכשיו הערך המעודכן שהזנתם.
- כדי לעצור את מצב 'ניפוי באגים', לוחצים על לחצן העצירה
ומסירים את נקודת העצירה על ידי לחיצה עליה שוב.
5. פיתוח שירות REST פשוט של CRUD
בשלב הזה, האפליקציה שלכם מוגדרת באופן מלא לפיתוח מבוסס-קונטיינרים, ועברתם על תהליך העבודה הבסיסי לפיתוח באמצעות Cloud Code. בקטעים הבאים תתרגלו את מה שלמדתם על ידי הוספת נקודות קצה של שירות REST שמתחברות למסד נתונים מנוהל ב-Google Cloud.
קידוד שאר השירות
הקוד שבהמשך יוצר שירות REST פשוט שמשתמש ב-Spanner כמסד הנתונים שמאחורי האפליקציה. כדי ליצור את האפליקציה, מעתיקים את הקוד הבא לאפליקציה.
- יוצרים את האפליקציה הראשית על ידי החלפת
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 Identity. כך האפליקציה יכולה לפעול כחשבון שירות משלה ולקבל הרשאות נפרדות כשניגשים למסד הנתונים.
- צריך לעדכן את
deployment.yaml. מוסיפים את הקוד הבא בסוף הקובץ (חשוב לשמור על ההזחות של הכרטיסיות בדוגמה שלמטה)
serviceAccountName: python-ksa
nodeSelector:
iam.gke.io/gke-metadata-server-enabled: "true"
פריסה ואימות של אפליקציה
- בחלונית שבתחתית Cloud Shell Editor, בוחרים באפשרות
Cloud Codeואז בוחרים באפשרותDebug on Kubernetesבחלק העליון של המסך. - בסיום הבנייה והבדיקות, בכרטיסייה Output (פלט) מופיע הכיתוב
Resource deployment/python-app status completed successfully, וכתובת URL מוצגת: 'Forwarded URL from service python-app: http://localhost:8080' (כתובת ה-URL שהועברה מהשירות python-app: http://localhost:8080). - מוסיפים כמה רשומות.
בטרמינל של Cloud Shell, מריצים את הפקודה הבאה
curl -X POST http://localhost:8080/singer -H 'Content-Type: application/json' -d '{"first_name":"Cat","last_name":"Meow", "singer_id": 6}'
- מריצים את הפקודה הבאה בטרמינל כדי לבדוק את ה-GET
curl -X GET http://localhost:8080/singer?singer_id=6
- בדיקת מחיקה: עכשיו מנסים למחוק רשומה על ידי הפעלת הפקודה הבאה. משנים את הערך של item-id אם צריך.
curl -X DELETE http://localhost:8080/singer?singer_id=6
This throws an error message
500 Internal Server Error
זיהוי הבעיה ותיקון שלה
- מפעילים את מצב ניפוי הבאגים ומאתרים את הבעיה. ריכזנו בשבילכם כמה טיפים:
- אנחנו יודעים שמשהו לא בסדר בפקודה DELETE כי היא לא מחזירה את התוצאה הרצויה. לכן, צריך להגדיר את נקודת העצירה ב-
app.pyבשיטהdelete_singer. - מריצים את ההפעלה שלב אחר שלב וצופים במשתנים בכל שלב כדי לראות את הערכים של משתנים מקומיים בחלון הימני.
- כדי לצפות בערכים ספציפיים כמו
singer_idו-request.args, מוסיפים את המשתנים האלה לחלון Watch.
- שימו לב שהערך שהוקצה למשתנה
singer_idהואNone. כדי לפתור את הבעיה, צריך לשנות את הקוד.
קטע הקוד המתוקן ייראה כך.
@app.route('/delete-singer', methods=['DELETE', 'GET'])
def delete_singer():
try:
singer_id = request.args.get('singer_id')
- אחרי שמפעילים מחדש את האפליקציה, מנסים למחוק שוב כדי לבדוק אם הבעיה נפתרה.
- כדי להפסיק את סשן ניפוי הבאגים, לוחצים על הריבוע האדום בסרגל הכלים לניפוי באגים

6. הסרת המשאבים
מעולה! בשיעור ה-Lab הזה יצרתם אפליקציית Python חדשה מאפס והגדרתם אותה כך שתפעל ביעילות עם קונטיינרים. לאחר מכן פרסתם את האפליקציה וניפיתם בה באגים באשכול GKE מרוחק, לפי אותו תהליך פיתוח שקיים במערכות אפליקציות מסורתיות.
כדי לנקות אחרי השלמת ה-Lab:
- מחיקת הקבצים שבהם נעשה שימוש במעבדה
cd ~ && rm -rf container-developer-workshop
- מחיקת הפרויקט כדי להסיר את כל התשתית והמשאבים שקשורים אליו