در این آموزش ما قراره یک سرور فلسک (Flask) بسازیم که یک پایگاه داده Postgres داره و با استفاده از داکر، اپلیکیشن فلسک رو اجرا کنیم. این مقاله سه بخش داره:

  1. ساختن اپلیکیشن Hello World
  2. قرار دادن سرور داخل یک کانتینر
  3. پایگاه داده و مهاجرت‌ها

امیدوارم از خوندن این مقاله لذت ببرین.

Hello World!

هر وقت دارم رو یک پروژه جدید کار می‌کنم، اولین کاری که می‌کنم ساختن ساده ترین اپلیکیشن ممکن است. با ساختن ساده‌ترین اپلیکیشن ممکن، شما یک جورایی ساختار پروژه رو حاضر می‌کنین. برای اپلیکیشن فلسک Hello World می‌تونین از این دستورالعمل پیروی کنین:

https://flask.palletsprojects.com/en/1.1.x/quickstart/

من موقع شروع این پروژه دقیقا همین کار رو کردم.

برای افرادی که می‌خوان قدم به قدم پیش برن، مراحل به شکل زیره:

1- در پایتون یک پروژه جدید ایجاد کنین (این کار همه دایرکتوری‌های مورد نیاز برای اضافه کردن محیط مجازی رو فراهم می‌کنه)

2- فلسک رو نصب و با دستور زیر به requirements.txt اضافه‌ش کنید:

pip install Flask
pip freeze > requirements.txt

3- یک فایل با نام app.py ایجاد کنید و کد زیر رو در اون قرار بدین:

from flask import Flask
app = Flask(__name__)
@app.route('/', methods=['GET'])
def hello_world():
    return {
        'hello': 'world'
    }

4- برای اجرای اپلیکیشن می‌تونید کدهای زیر رو اجرا کنید

export FLASK_APP=src/app.py
flask run

5- و حالا شما رشته رو در لینک http://127.0.0.1:5000 مشاهده می کنید. 

امیدوارم تا اینجا همراهی کرده باشید، اگه این طوره که تبریک میگم.

قرار دادن سرویس و پایگاه داده در کانتینر

به نظر من این گام خیلی مهمه چون کانتینرهای اپلیکیشن کار توسعه رو خیلی سریع و آسون می‌کنن. لازم نیست پیکربندی زیادی انجام بدین، به جاش می‌تونین تنها با چند خط کد اپلیکیشن رو پیکربندی و سرویس‌های مربوط به اون رو آماده اجرا کنین.

اول از همه می‌خوام خیلی خلاصه در مورد فایل docker-compose توضیح بدم. همون طور که در اسناد داکر ذکر شده، "compose ابزاری برای تعریف و اجرای اپلیکیشن‌های داکر با چند کانتینر است" (https://docs.docker.com/compose/).

اگر شما چند سرویس دارین که به عنوان بخشی از یک سیستم بزرگتر فعالیت می‌کنن، می‌تونین از docker-compose استفاده کنید و تمام این سرویس‌ها رو با یک فایل ساده پیکربندی بسازید و اجرا کنید.

فایل Docker-compose ما دو سرویس داره: سرور و پایگاه داده. کد فایل به شکل زیر است:

version: '3.6'

services:
  api:
    build: .
    depends_on:
      - db
    environment:
      STAGE: test
      SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://test:test@db/test
    networks:
      - default
    ports:
      - 5000:5000
    volumes:
      - ./app:/usr/src/app/app
      - ./migrations:/usr/src/app/migrations
    restart: always

  db:
    environment:
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
      POSTGRES_DB: test
    image: postgres:latest
    networks:
      - default
    ports:
      - 5405:5432
    restart: always
    volumes:
      - ./postgres-data:/var/lib/postgresql/data

اولین سرویس که سرویس اصلی به شمار میره، در حال حاضر سرور اپلیکیشن `hello world` و دومین سرور پیکربندی پایگاه داده Postgres است. وقتی دستور `docker-compose up`رو اجرا کنید، می‌بینید که  هر دو سرویس ساخته میشن و می‌تونن با استفاده از نام‌هایی که در فایل  docker-compose براشون در نظر گرفته شده، همدیگه رو پیدا و با هم ارتباط برقرار کنن.

بعد از docker-compose نوبت به فایل داکر (Dockerfile) می‌رسه. وقتی دستور ‘docker build’ در حال اجراست، از این فایل به عنوان یک دستورالعمل برای ساخت سرویس لازم استفاده می‌کنه. در این مورد ما دستور docker-compose up رو اجرا می‌کنیم . دستور build در سرویس (خط 5 فایل docker-compose.yml رو در بالا ببینید) به compose می‌گه که فایل داکر رو اجرا کنه. برای کسب اطلاعات بیشتر در مورد فایل داکر می‌تونید اسناد رسمی رو در لینک زیر ببینید:

https://docs.docker.com/engine/reference/builder/

حالا فایل داکر ما باید به این شکل باشه:

# pull official base image
FROM python:3.8.0-alpine
# set work directory
WORKDIR /usr/src/app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
RUN apk update && apk add postgresql-dev gcc python3-dev musl-dev
# install dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt /usr/src/app/requirements.txt
RUN export LDFLAGS="-L/usr/local/opt/openssl/lib"
RUN pip install -r requirements.txt
# copy project
COPY . /usr/src/app/
EXPOSE 5000
RUN ls -la app/
ENTRYPOINT ["app/docker-entrypoint.sh"]

من وارد جزئیات این فایل نمی‌شم اما مهمترین قسمتش خط آخره، که Entrypoint رو برای ایمیج داکر تعریف کرده.

فکر کنم الان وقتشه در مورد Entrypoint صحبت کنیم. Entrypoint به شما قابلیت اجرای کانتینر به عنوان یک فایل اجرایی رو می‌ده. شما می‌تونین در لینک زیر بیشتر در مورد نقطه ورود بخونید:

https://docs.docker.com/engine/reference/builder/#entrypoint

معماری اپلیکیشن ما تا اینجا به شکل زیراست:

docker-compose -> api -> Dockerfile -> docker-entrypoint.sh

docker-compose -> db

و فایل docker-entrypoint.sh خیلی ساده و حاوی کدهای زیر است:

#!/bin/sh
set -e
flask db upgrade
gunicorn -c gunicorn.config.py wsgi:app

دو وظیفه مهم این فایل sh که در واقع تنها وظایفش هم هستن، اجرای دستور ارتقاء پایگاه داده بعد از بررسی مهاجرت‌های جدید و اجرای اپلیکیشن فلسک با استفاده از gunicorn است.

در قسمت بعدی در مورد تنظیمات پایگاه  داده صحبت می‌کنیم و اون وقت شما دستور flask db upgrade رو بهتر درک می‌کنید. 

اگه می‌خواین بیشتر در مورد gunicorn بدونین، می‌تونین این لینک رو دنبال کنید:

https://flask.palletsprojects.com/en/1.1.x/deploying/wsgi-standalone/

پایگاه داده و مهاجرت‌ها

یکی از مهمترین چیزهایی که باید روی سرور داشته باشیم، جایی برای ذخیره اطلاعات، یا همون پایگاه داده است. 

برای این پروژه ما از Postgres استفاده می‌کنیم، اما قبل از اون باید راهی پیدا کنیم که بتونه به تمام کارهای مربوط به پیکربندی رسیدگی کنه. امکان نظارت بر مهاجرت‌ها یکی از قسمت‌های حیاتی پیکربندی است. برای این پروژه ما از دستور Flask-Migrate استفاده می کنیم:

pip install Flask-Migrate
pip freeze > requirements.txt

بعد از مراحل بالا، برای مقداردهی اولیه، فقط کافیه دستور زیر رو اجرا کنیم:

flask db init

این دستور پوشه مهاجرت‌ها رو در دایرکتوری root اپلیکیشن ایجاد می‌کنه. می‌تونیم به این پوشه به عنوان راهی برای نظارت بر نسخه‌های پایگاه داده فکر کنیم.

برای اجرای یک مهاجرت جدید، ما می‌تونیم دستور زیر رو اجرا کنیم:

flask db revision -m "create accounts table"

و خواهید دید که در مسیر migrations/versions یک فایل جدید برای این مهاجرت موجوده. همون‌طور که می بینید دو تابع اینجا هست، upgrade و downgrade. همون‌طور که از اسم‌ها پیداست، وظیفه یکی از اون‌ها ارتقاء پایگاه داده و اضافه کردن شئ‌های تعریف شده در پایگاه داده به این فایل است، و وظیفه دیگری حذف اون شیء‌ها و بازگشت به حالت قبلی پایگاه داده است. فایل مهاجرت به این شکل است:

"""create account table
Revision ID: 9b1d3dcf21f9
Revises: 
Create Date: 2020-02-04 14:46:12.628680
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = '9b1d3dcf21f9'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    account = op.create_table(
        'account',
        sa.Column('id', sa.Integer, primary_key=True, autoincrement=True),
        sa.Column('name', sa.String(50), nullable=False),
        sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.func.now()),
        sa.Column('updated_at', sa.DateTime, nullable=False, server_default=sa.func.now(), onupdate=sa.func.now()),
        sa.Column('deleted_at', sa.DateTime)
    )


def downgrade():
    op.drop_table('account')

نکته: اگر می‌خواین کد رو در پروژه خودتون Copy و Paste کنید، فقط دو تابع upgrade و downgrade رو بررسی کنید.

همون که در تعریف کد می‌بینین، این کد یک جدول با پنج ستون ایجاد می‌کنه.

برای دیدین جزییات بیشتر در مورد کد پاگاه داده، می تونین کد رو در گیت‌هاب چک کنید.

https://github.com/ytimocin/flask-postgres-server

قسمت آخر اضافه کردن endpoint است که یک شیء از نوع account در پایگاه داده ایجاد‌ می‌کنه:

@app.route('/accounts/', methods=['POST'])
def create_user():
    """Create an account."""
    data = request.get_json()
    name = data['name']
    if name:
        new_account = Account(name=name,
                              created_at=dt.now())
        db.session.add(new_account)  # Adds new User record to database
        db.session.commit()  # Commits all changes
        return make_response(f"{new_account} successfully created!")
    else:
        return make_response(f"Name can't be null!")

این کد درخواست رو دریافت می‌کنه، پارامترها رو می‌خونه و یک شیء Account در پایگاه داده ایجاد می‌کنه. اینجا یک گیف (با کیفیت پایین) از فراخوانی endpoint و بررسی پایگاه داده برای داده جدید رو می‌بینید:

اجرای اپلیکیشن فلسک با استفاده از داکر
تشکر ویژه از TablePlus (یک ویرایشگر پایگاه داده فوق العاده) و Postman.

من ویژگی‌های دیگه‌ای هم به این پروژه اضافه می‌کنم اما فعلا، کار ما همین‌جا تمام است. همون‌طور که بالا اشاره کردم، لینک پروژه رو اینجا می‌ونین ببینید.

https://github.com/ytimocin/flask-postgres-server

ممنون از این که تا آخر پست همراهی کردید!