🎓 מפתחי בינה מלאכותית - GenAI Engineers

הפרדה בין צד לקוח וצד שרת, ושילוב APIs בין שירותים

234 שעות אקדמיות 2 סמסטרים E2E - קונספט עד ענן

ענן והפצה - Docker + Cloud Run

בפרודקשן, "זה עובד אצלי" לא מספיק. צריך אריזה קבועה, ניהול תלויות, קונפיגורציה, ושיטה עקבית לפריסה. כאן נכנסים Docker ו-Cloud Run.

המטרה: לקחת שירות FastAPI (למשל Notes או RAG API), לארוז אותו לקונטיינר שניתן להריץ זהה בכל מקום, ואז לפרוס אותו ל-Cloud Run עם ניהול סודות, סקיילינג, ותצפיות (logs/metrics).

1) מהו Docker באמת?

Docker הוא סט כלים שמאפשר לבנות Container Image - חבילה שמכילה את האפליקציה, התלויות שלה, והוראות איך להריץ אותה. זה לא VM: הקונטיינר משתף את הקרנל של מערכת ההפעלה, ולכן הוא קל ומהיר יותר.

Image

תבנית בלתי משתנה (immutable) שמכילה שכבות. בונים אותה עם Dockerfile ומעלים לרג'יסטרי.

Container

תהליך רץ שנוצר מ-image. אפשר להריץ הרבה קונטיינרים מאותו image.

למה זה חשוב בהנדסת מערכות?
  • שחזור: אותו image פועל אותו דבר במחשב, ב-CI, ובענן.
  • דיפלוי מהיר: Pull ל-image והרצה.
  • אבטחה: אפשר לסרוק image לפגיעויות ולנעול גרסאות.
  • יעילות: שכבות מאפשרות cache לבילדים מהירים.

2) עקרונות Dockerfile נכון (ל-Python + FastAPI)

Dockerfile הוא מתכון. המטרה: בילד מהיר, image קטן, והרצה יציבה. הדפוס הפופולרי הוא:

# Dockerfile
FROM python:3.12-slim

# 1) יציבות והרצה צפויה
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app

# 2) תלויות קודם - כדי לקבל cache
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 3) קוד אחר כך
COPY . .

# 4) Cloud Run מצפה לשרת שמאזין ל-$PORT
ENV PORT=8080
EXPOSE 8080

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
.dockerignore שחוסך זמן וכסף

בפרויקט אמיתי לא רוצים לשלוח ל-Docker build קבצים מיותרים:

__pycache__/
*.pyc
.venv/
.env
.git/
.gitignore
tests/
*.db
*.log

הדגש: כל מה שלא צריך בזמן ריצה - לא נכנס ל-image.

3) פרמטרים תפעוליים: env, secrets, ו-config

קונטיינר טוב לא "מקשיח" פרטים סביבתיים. במקום זה משתמשים במשתני סביבה:

DATABASE_URL=postgresql+psycopg://user:pass@host/db
OPENAI_API_KEY=...
LOG_LEVEL=info

כלל פרודקשן: לא שמים סודות בתוך ה-image ולא בקוד. ב-Cloud Run משתמשים ב-Secret Manager וממפים secrets למשתני סביבה בזמן דיפלוי.

4) Cloud Run במודל מחשבתי

Cloud Run מריץ קונטיינרים בצורה מנוהלת. אתה מספק image, והוא דואג ל:

דרישה מרכזית: האפליקציה חייבת להאזין לפורט שניתן ב-PORT (בדרך כלל 8080). לכן ב-Dockerfile אנחנו מאזינים ל-0.0.0.0:8080.

5) בנייה מקומית והרצה

נבנה image ונריץ קונטיינר:

# build
docker build -t notes-api:local .

# run
docker run --rm -p 8080:8080 \
  -e LOG_LEVEL=info \
  notes-api:local

עכשיו אפשר לגשת ל-http://localhost:8080/docs ולראות את ה-OpenAPI.

6) Health checks וניטור מינימלי

Cloud Run לא דורש endpoints מיוחדים, אבל פרודקשן כן. מוסיפים endpoint כמו:

@app.get("/health")
def health():
    return {"status": "ok"}

בנוסף, מומלץ להדפיס לוגים כ-JSON כדי להקל על חיפוש:

{
  "level": "info",
  "event": "request_done",
  "path": "/api/v1/notes",
  "status": 200,
  "latency_ms": 37
}

7) Cloud Run: build, push, deploy (תהליך טיפוסי)

יש שני דפוסים נפוצים. הראשון הוא להשתמש ב-Cloud Build לבניית image ישירות מה-repo, בלי Docker מקומי.

# 1) build + push (Artifact Registry)
gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT/REPO/notes-api:1.0 .

# 2) deploy
gcloud run deploy notes-api \
  --image REGION-docker.pkg.dev/PROJECT/REPO/notes-api:1.0 \
  --region REGION \
  --platform managed \
  --allow-unauthenticated \
  --set-env-vars LOG_LEVEL=info
הערות פרודקשן חשובות ב-Cloud Run
  • Concurrency: כמה בקשות במקביל לכל instance. גבוה יותר חוסך כסף אבל דורש קוד יעיל.
  • Timeout: ברירת מחדל לא תמיד מתאימה ל-LLM. מגדירים timeout מתאים.
  • CPU allocation: אפשר CPU רק בזמן בקשה או תמיד. משפיע על ביצועים ומשימות רקע.
  • Min instances: אם חשוב latency נמוך, משאירים 1 instance תמיד, אבל עולה כסף.
  • Secrets: ממפים מ-Secret Manager ולא מעבירים בקוד.

8) Object Storage - למה תמונות, וידאו וקבצים כבדים הולכים ל-"S3" ולא ל-DB

במערכות מודרניות, קבצים כבדים (תמונות, וידאו, PDFים, ZIPים) לא נשמרים בתוך הקונטיינר ולא בתוך טבלאות DB כ-BLOBים (למעט מקרים חריגים). במקום זה משתמשים ב-Object Storage - שירות בסגנון S3 (ב-GCP זה Cloud Storage, ב-AWS זה S3).

למה לא לשמור בתוך הקונטיינר?

  • Cloud Run הוא stateless: ה-instance יכול להיסגר בכל רגע, והדיסק המקומי לא מובטח להתמיד.
  • סקיילינג: כשיש כמה instances, לכל אחד דיסק מקומי שונה - הנתונים לא משותפים.
  • העתקה איטית: לשכפל קבצים בין instances זו מורכבות ותקלות.

למה לא לשמור ב-DB כ-BLOB?

  • עלות וביצועים: DB רלציוני לא בנוי לזרימת קבצים גדולים בקצב גבוה.
  • גיבויים: גיבוי DB נהיה כבד ויקר.
  • CDN: Object storage משתלב בקלות עם CDN והורדות מהירות.

הדפוס הנכון: מעלים את הקובץ ל-Object Storage ומקבלים URL (או key). ב-DB שומרים רק מטא-דאטה: מי העלה, מתי, איזה סוג, גודל, וכתובת הקובץ.

# טבלת DB (Postgres/Mongo) - מטא-דאטה בלבד
file_id | user_id | content_type | size_bytes | storage_key | created_at

# Object storage (S3/Cloud Storage)
storage_key -> actual binary object (image/video/pdf)
איך מעלים קובץ בצורה נכונה (פרקטי)
  • Signed URL: השרת יוצר URL חתום להעלאה, והלקוח מעלה ישירות ל-storage בלי להעביר את הקובץ דרך השרת.
  • Public vs private: לרוב שומרים פרטי ומגישים דרך Signed URLs להורדה.
  • Lifecycle: מדיניות מחיקה/ארכוב לקבצים זמניים כדי לשלוט בעלויות.

9) DB בענן - Postgres/Mongo לטבלאות מידע

במקומי השתמשנו ב-SQLite כדי ללמוד. בענן בדרך כלל משתמשים ב-PostgreSQL (Cloud SQL או ספק אחר). עקרונות חשובים:

מלכודת נפוצה: לעלות ל-Cloud Run עם SQLite ולצפות להתמדה. Cloud Run הוא stateless, והדיסק המקומי לא מיועד להיות DB. SQLite מצוין ללמידה, לא לשירות רשת סקיילבילי.

10) ארכיטקטורה של דיפלוי למערכת GenAI

במערכת GenAI, לרוב נפריד בין:

למה להפריד worker מ-API?

כי אינדוקס מסמכים יכול לקחת דקות, וזה לא תואם request-response. לכן שולחים job לתור (למשל Pub/Sub) ו-worker עושה את העבודה. Cloud Run מתאים גם ל-workers, אבל צריך לבחור מודל הרצה מתאים.

תרגול

שאלות בדיקה (10)
  1. מה ההבדל בין image ל-container?
  2. למה Docker build נהיה מהיר יותר כשמעתיקים requirements לפני הקוד?
  3. למה אסור לשים סודות בתוך image?
  4. מה המשמעות של PORT ב-Cloud Run?
  5. למה חשוב להאזין ל-0.0.0.0 ולא ל-127.0.0.1?
  6. מה tradeoff של concurrency גבוה ב-Cloud Run?
  7. למה SQLite לא מתאים לפרודקשן ב-Cloud Run?
  8. מה תפקיד Secret Manager?
  9. למה צריך health endpoint גם אם Cloud Run לא דורש?
  10. מתי כדאי להפריד worker משירות API?
משימת בית (Dockerize + Deploy)

בחר אחד מהשירותים שבנית (Notes DB או RAG API) ובצע:

  • הוסף Dockerfile ו-.dockerignore.
  • וודא שהשירות מאזין ל-PORT=8080 וכולל /health.
  • בנה והרץ מקומית עם docker run והדגם שה-OpenAPI ב-/docs עובד.
  • פרוס ל-Cloud Run עם gcloud (או דרך Console) והדגם URL ציבורי עובד.
docker build -t my-api:local .
docker run --rm -p 8080:8080 my-api:local

gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT/REPO/my-api:1.0 .
gcloud run deploy my-api --image REGION-docker.pkg.dev/PROJECT/REPO/my-api:1.0 \
  --region REGION --allow-unauthenticated
בדיקת פתרון (Checklist)
  • הקונטיינר עולה מקומית ו-GET /health מחזיר 200.
  • הקונטיינר מאזין ל-8080, והפורט נפתח עם -p 8080:8080.
  • אין סודות בתוך ה-repo או ה-image.
  • ב-Cloud Run יש URL עובד, ו-/docs נגיש.
  • לוגים מופיעים ב-Cloud Logging וכוללים trace id אם הוספת.

הפרק הבא בסדרה יהיה Webhooks + Automation (Make/N8N) כולל חתימות, retries, idempotency, ותירגול.