פוסט

Docker מאפס לגיבור: Compose, דיבוג וניקוי

מריצים את האפליקציה ואת מסד הנתונים מקובץ compose.yaml אחד עם בדיקות בריאות, פקודות Compose, ארגז כלים לדיבוג, פינוי מקום בדיסק והרגלים ל-images של פרודקשן.

בחלק הקודם חיברנו את hello-app ל-Postgres עם רשת, volume ושלוש שורות docker run ארוכות. Compose מחליף את השורות האלה בקובץ אחד.

Docker Compose: כל האפליקציה בקובץ אחד

Docker Compose מתאר אפליקציה מרובת containers בקובץ אחד ומריץ אותה בפקודה אחת. אבני הבניין שלו הן services (ה-containers), ‏networks ו-volumes. יוצרים compose.yaml (‏Compose מחפש את השם הזה קודם):

services:
  web:
    build: .
    ports:
      - "8080:3000"
    environment:
      DATABASE_URL: postgres://postgres:dev-only@db:5432/postgres
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: dev-only
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5

volumes:
  pgdata:

Compose מכניס את שני השירותים לרשת משותפת, ולכן web מגיע למסד הנתונים בשם db. (השרת הזעיר שלנו לא קורא את DATABASE_URL; הוא רק מראה את החיווט.)

את ה-healthcheck עם condition: service_healthy נוטים לפספס. ‏depends_on רגיל קובע רק סדר הפעלה: תלות נחשבת מוכנה כשה-container שלה רץ, לא כשהתוכנה שבפנים מקבלת חיבורים. מסד נתונים יכול לקחת כמה שניות לעלות, ובדיקת הבריאות גורמת ל-web לחכות באמת.

הפקודות שצריך:

docker compose up -d            # בונה אם צריך, יוצר רשתות ו-volumes, מפעיל הכול
docker compose ps               # השירותים והפורטים שלהם
docker compose logs -f web      # עוקב אחרי הלוגים של שירות אחד
docker compose up -d --build    # בונה מחדש images אחרי שינוי קוד
docker compose down             # עוצר ומוחק את ה-containers ואת הרשת
docker compose down -v          # ...וגם את ה-volumes בעלי השם (זה מוחק את הנתונים שלנו)

שימו לב לרווח: ‏docker compose היא הפקודה הנוכחית; ‏docker-compose העצמאית היא הדור הקודם.

כדי להוציא הגדרות מהקובץ, משתמשים ב-env_file:

services:
  web:
    env_file:
      - path: ./defaults.env
      - path: ./overrides.env
        required: false

קבצים מאוחרים דורסים מוקדמים, ו-required: false מדלג על קובץ חסר. לערך חד-פעמי, docker compose run -e LOG_LEVEL=debug web דורס אותו רק להרצה הזאת.

ארגז כלים לדיבוג

כשמשהו לא עובד, הולכים מבחוץ פנימה:

docker ps -a                       # האם הוא רץ? האם הוא יצא?
docker logs --tail 50 <name>       # מה הוא אמר לפני שמת?
docker exec -it <name> sh          # מסתכלים בפנים
docker inspect <name>              # כל הפרטים: IP, mounts, env, קוד יציאה
docker stats                       # CPU וזיכרון חיים לכל container
docker cp <name>:/app/file.txt .   # מעתיק קובץ החוצה מ-container

החשודים הרגילים: האפליקציה מאזינה ל-127.0.0.1 בתוך ה-container במקום ל-0.0.0.0 (ולכן הפורט המפורסם לא מגיע אליה), פורט שכבר תפוס במארח, משתנה סביבה חסר, קובץ חסר כי .dockerignore החריג אותו, או נתיב ששונה בין המחשב שלנו ל-container.

ניקוי

images ישנים, containers שנעצרו ו-build cache מצטברים:

docker system df              # מה תופס מקום
docker system prune           # מוחק containers שנעצרו, רשתות לא בשימוש, images "תלויים", build cache לא בשימוש
docker system prune -a        # ...וגם כל image שאף container לא משתמש בו
docker builder prune          # רק את ה-build cache

‏docker system prune מבקש אישור ולא נוגע ב-volumes כברירת מחדל. ‏--volumes מוחק גם volumes אנונימיים שלא בשימוש, אז קוראים את האזהרה קודם.

הרגלים ל-images של פרודקשן

מעבר ל-image בסיס קטן, בנייה רב-שלבית, USER, סדר שמתאים ל-cache, ‏.dockerignore ובלי סודות ב-image:

  • עניין אחד לכל container. אפליקציית web ומסד הנתונים שלה הם שני containers.
  • מקבעים גרסאות של images בסיס ובונים מחדש בקביעות בשביל תיקוני אבטחה.
  • סורקים את ה-images לפגיעויות ידועות ב-CI.
  • תהליך אחד, בחזית, שכותב ל-standard output בשביל docker logs.

לאן ממשיכים

כדי לקבע את זה, מכניסים ל-container אפליקציה קיימת, עם Dockerfile, ‏.dockerignore ו-Compose לצד מסד הנתונים שלה.

הצעד הבא: לבנות ולדחוף את ה-image אוטומטית בכל מיזוג. ‏GitHub Actions מאפס לגיבור בונה בדיוק את ה-pipeline הזה, ‏GitHub מאפס לגיבור מכסה את הצד של הריפוזיטורי, ו-Git מאפס לגיבור שם אם בקרת גרסאות עדיין חדשה. ה-Get started של Docker וה-Dockerfile best practices הם ההמשך הטוב ביותר.

כדי להריץ הרבה containers על כמה מכונות, עם ריפוי עצמי ועדכונים בלי השבתה, ממשיכים ל-Kubernetes מאפס לגיבור. כדי להריץ containers בענן, מתחילים במפה ב-AWS מאפס לגיבור. ואם הפקודות שמקלידים בתוך container הן החלק הלא מוכר, בדף העזר לפקודות Linux יש אותן.