טיפ פייתון: פיתוח עם docker compose

07/04/2023

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

בדוגמה של פייתון אפשר לדמיין שנרצה לעבוד על כמה פרויקטי פייתון שכל אחד מהם משתמש בגירסת פייתון אחרת וספריות אחרות, וכל אחד מהם גם משתמש בבסיס נתונים שונה או גירסה שונה של בסיס הנתונים. בעוד שקל לנהל סט חבילות ספציפי לפרויקט עם סביבות וירטואליות, כשמתחילים להוסיף את התוכנות מסביב דברים עלולים להסתבך.

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

בשביל לפתח על המחשב שלנו קוד פייתון עם docker-compose נצטרך להתמודד עם האתגרים הבאים:

  1. נרצה שהקוד בתוך הקונטיינר יהיה זהה לקוד בספריית הפיתוח.

  2. נרצה לשמור את המודולים שהפרויקט משתמש בהם בסביבה וירטואלית ספציפית לפרויקט, שלא קשורה לגירסאות פייתון שמותקנות אצלי במחשב המארח.

  3. נרצה לאפשר לקוד לתקשר עם בסיס נתונים שגם יעלה מתוך ה compose.

  4. נעדיף לא לבנות אימג' בשביל הפיתוח כדי לחסוך פעולות build.

1. תיאור הפיתרון

עם docker compose אפשר לבנות פיתרון שעונה על כל האתגרים:

  1. נמפה את הקוד מתיקיית הפיתוח לתיקייה בתוך הקונטיינר

  2. נשתמש בסקריפט entrypoint שימופה לתוך הקונטיינר שיתקין את התלויות מקובץ requirements.txt לתוך סביבה וירטואלית בתוך הקונטיינר.

  3. נשמור את הסביבה הוירטואלית על volume כדי שבהפעלות הבאות לא נצטרך להתקין מחדש את התלויות.

2. מימוש קוד הפיתרון

נראה דוגמה באמצעות תוכנית flask שמתחברת לבסיס נתונים Postgresql ושולפת משם מידע להחזיר ללקוח. אני יוצר תיקיה עבור הפרויקט ובתוכה קובץ בשם main.py עם התוכן הבא:

from flask import Flask
import psycopg2
import os

conn = psycopg2.connect(
   database="postgres",
   user=os.environ['DB_USER'],
   password=os.environ['DB_PASS'],
   host=os.environ['DB_HOST'],
   port= os.environ['DB_PORT']
)

app = Flask(__name__)

@app.route("/")
def hello_world():
    cursor = conn.cursor()
    cursor.execute("select version()")
    data = cursor.fetchone()

    return f"Using database version {data}"

וקובץ בשם requirements.txt עם התוכן הבא:

flask
psycopg2

עכשיו אני יוצר קובץ בשם docker-compose.yml עם התוכן הבא:

version: "3.9"
services:
  app:
    image: python:3.11-bullseye
    command: /bin/bash /usr/local/bin/entrypoint.sh
    ports:
      - 5000:5000
    volumes:
      - .:/app
      - ./entrypoint-dev.sh:/usr/local/bin/entrypoint.sh
      - venv:/venv

    environment:
      DB_USER: "postgres"
      DB_PASS: "monkey"
      DB_HOST: "db"
      DB_PORT: "5432"

  db:
    image: postgres:15.2
    environment:
      POSTGRES_PASSWORD: "monkey"

    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  venv:
  pgdata:

וקובץ בשם entrypoint-dev.sh עם התוכן הבא:

#!/bin/bash

cd /app

if [[ ! -d /venv/venv ]]
then
  python -m venv /venv/venv
fi
source /venv/venv/bin/activate
pip install -r requirements.txt

flask --app main run --host 0.0.0.0 --port 5000

אחרי כל אלה אפשר לצאת לדרך. מפעילים:

$ docker compose up

ויכולים לגלוש מהדפדפן לכתובת localhost:5000 כדי לראות את העמוד הראשי שידפיס את הגירסה של בסיס הנתונים אליה אנחנו מחוברים.