علیرغم همه پیشرفت ها در حوزه برنامه نویسی مدرن، پیکربندی صحیح یک محیط توسعه محلی(Local) هنوز هم یک چالش دشوار به حساب میاد. علت این قضیه اینه که متغیر های زیادی باید در نظر گرفته بشن، مثل: رایانه ها، سیستم عاملها و ورژن های مختلف جنگو و ... . حالا اگر کار کردن در یک محیط تیمی که همه باید از تنظیمات و راه اندازی های یکسان استفاده کنند رو هم به این چالش اضافه کنید میبینید که این چالش خیلی بیشتر خودشو نشون میده.
در سالهای اخیر یک راه حل پدید اومده به نام Docker که به سرعت تبدیل به یک انتخاب پیشفرض برای تیم های توسعه تبدیل شده.
حالا با وجود Docker(داکِر) این امکان به وجود اومده که شما بتونید یک محیط توسعه محلی راه اندازی کرده و از نسخه دقیق پایتون گرفته تا نسخه دقیق جنگوی موردنظرتون رو داخلش نصب کنید. این بدین معناست که دیگه مهم نیست شما از ویندوز استفاده میکنید یا مَک یا لینوکس، چون همه چیز داخل داکر اجرا میشه.
همچین داکر همکاری بین اعضای تیم رو آسون تر میکنه. دیگه از روز هایی که برای اضافه کردن یک توسعه دهنده جدید به تیم از فایل های README استفاده میکردیم فاصله گرفتیم و در عوض اینکارو داکر با اشتراک گذاری دو فایل Dockerfile و docker-compose.yml به راحتی میسر میکنه و توسعه دهنده های جدید میتونن با اطمینان از اینکه محیط کاریشون با بقیه تیم یکسان هست، وارد پروژه بشن و کارشون رو شروع کنن.
در این بخش ما قراره تا کمی در مورد داکر یاد بگیریم و به اصطلاح با "Dockerize" کردن اولین پروژه خودمون آشنا بشیم.
داکر چیه ؟
داکر یه تکنولوژی برای ایزوله کردن یک سیستم عامل بطور کامل از طریق کانتینر های لینوکسی هستش، که این کانتینرها به نوبه ی خود نوعی virtulization هستن. ریشه ی virtualization(مجازی سازی) به اوایل علوم کامپیوتر برمیگرده که در آن زمان کامپیوتر های بزرگ و گران قیمت رواج داشتند. اما چجوری چندین کاربر بطور همزمان از یک سیستم استفاده میکردن؟ پاسخ این سوال در virtualizaion و به ویژه virtual machine ها نهفته هستش، که در واقع یک کپی کامل از یک سیستم کامپیوتری اند.
اگر یک فضای اَبری را از یک ارائه دهنده سرویس اَبری مثل Amazon Web Service (aws) اجاره کنید، شما در واقع یک قطعه سخت افزاری مختص به خودتون رو اجاره نکردید بلکه یک سرور فیزیکی رو با مشتری های دیگه شریک شدین. اما چون هر مشتری ماشین مجازی(virtual machine) مختص به خودش رو روی سرور اجرا میکنه، از دید مشتری اینطور به نظر میاد که سرور منحصر به خودش رو داره.
به کمک این تکنولوژی، اضافه یا حذف کردن سرور ها از فضای ابری ممکن هستش و در پشت صحنه ی همه این قضایا، تغییرات کلی بیشتر در بُعد نرم افزاری اتفاق میفته تا بعد سخت افزاری.
حالا جنبه منفی یک ماشین مجازی چی هستش ؟ سایز و سرعت. یک سیستم عامل مهمان میتونه بطور معمول حدود 700MB فضا اشغال کنه. پس اگر یک سرور فیزیکی از سه ماشین مجازی پشتیبانی کنه، حداقل 2.1GB از فضای دیسک خودش رو به اونا اختصاص میده و این تازه جدای از نیازی هستش که به CPU و Memory داره.
داکر وارد میشود !
چیزی که این وسط باید بهش توجه داشته باشید اینه که اکثر کامپیوتر ها به سیستم عامل لینوکسی یکسانی تکیه دارند؛ پس اگه به جای این که ما از صفر بخواهیم این فضای مختص به هر کاربر رو مجازی سازی کنیم، بیاییم و از این لایه لینوکسی به بالاتر رو مجازی سازی کنیم چطور میشه؟ به نظرتون اینطوری یک روش سریع تر و سبک تر برای تکثیر کردن این فضاها که اکثرا عملکرد یکسانی ازشون انتظار میره، پیدا نکردیم؟ جواب ما بله هست. برای اکثر اَپلیکیشن ها - به ویژه وب اَپلیکیشن ها – یک ماشین مجازی منابعی بیشتر از حد نیاز رو ارائه میکنه و در عوض یک کانتِینر منابع کافی رو در اختیار ما قرار میده. این قضیه در واقع پایه و اساس چیزی هستش که ما بهش میگیم Docker : روشی برای پیاده سازی کانتِینر های لینوکسی !
مقیاسه ای که میتونیم اینجا به کار ببریم، مقایسه خونه های ویلایی و آپارتمانها هستند. ماشین های مجازی مثل خونه هایی هستند که زیرساخت های منحصر به فرد رو دارند، از قبیل: سیستم حرارتی و لوله کشی و ... ؛ اما کانتِینر های داکر( Docker container ) مثل آپارتمانهایی هستند که زیر ساختهای مشترکی دارند، از قبیل : سیستم حرارتی و لوله کشی و ... ولی سایز های متفاوتی دارند تا بتونند پاسخگوی نیازهای یک مشتری باشند.
محیط های مجازی در مقایسه با کانیتِینر ها
محیط های مجازی روشی برای ایزوله کردن پکیج های پایتون هستن. به کمک محیط های مجازی، یک رایانه میتونه چندین پروژه رو بصورت محلی(Local) اجرا کنه. برای نمونه: پروژه A ممکنه از پایتون نسخه 3.10 و جنگو نسخه 4.0 استفاده کنه در حالیکه پروژه B از پایتون نسخه 3.5 و جنگو نسخه 1.11 استفاده کنه، در این حالت بجای اینکه هر بار بخواهیم کل رایانه رو برای اجرای هر پروژه دچار تغییرات کنیم کافیه تا برای هر پروژه یک محیط مجازی اختصاصی ایجاد کنیم و پکیج های پایتون رو با توجه به نسخه های مورد نیازشون در اون محیط نصب، بروز رسانی و نگه داری کنیم و دیگه کاری به کل رایانه نداشته باشیم!
ابزار های متعددی برای پیاده سازی محیط های مجازی وجود دارند که در این باره میشه به virtualenv و venv و Pipenv اشاره داشت که همگی اساسا کار یکسانی رو انجام میدن.
تفاوت مهم بین محیط های مجازی و کانتِینر های داکِر در اینه که محیط های مجازی فقط میتونن پکیج های پایتون رو ایزوله کنن و نمیتونن نرم افزار های غیر پایتونی از قبیل دیتابیس های PostgreSQL یا MySQL رو ایزوله کنن و از طرفی این نیاز هم برای محیط های مجازی وجود داره که برای پیاده سازیشون باید پایتون رو روی سیستم خودتون نصب کرده باشید( به عبات دیگه باید پایتون روی رایانه شما نصب باشه). محیط های مجازی به پایتونی که روی سیستم شما نصب شده اشاره دارند و خودشون شامل پایتون نیستن.
کانتِینر های لینوکسی یک قدم فراتر از این برداشتن و نه تنها قسمت های مربوط به پایتون، بلکه کل سیستم عامل رو ایزوله میکنن. به عبارت دیگه، ما هم پایتون و هم یک دیتابیس رو داخل داکر نصب و اجرا خواهیم کرد.
*: البته خودِ Docker مبحث پیچیده ای هستش و ما خیلی به عمقش نمیریم ولی درک اجزای کلیدیش واسمون مهمه.
نصب Docker
حالا وقتشه تا جنگو و داکر رو با همدیگه استفاده کنیم. اولین مرحله ایجاد یک اکانت رایگان در Docker Hub و سپس نصب کردن برنامه ی Docker desktop هستش:
از اونجایی که فایل نسبتا بزرگی هستش، دانلود کردنش یکم زمان میبره.
بعد از اینکه داکر رو نصب کردین وقتشه که از اجرای نسخه صحیحش مطمئن بشیم که برای اینکار کافیه تا دستور " docker –version " رو توی ترمینال خودتون اجرا کنید. نسخه نصب شده باید حداقل 18 باشه.
Shell
> docker –version
Docker version 20.10.17, build 100c701
Docker Hello,World
داکر با خودش یک ایمیج (image) آماده از "Hello, World" به همراه داره که به عنوان اولین قدم با هم اجراش میکنیم. در ترمینال خودتون دستور " docker run hello-world " رو وارد کنید. این دستور یک داکر ایمیج (docker image) دانلود میکنه و داخل یه کانتینر اجراش میکنه. در مورد کانتینرها و ایمیج ها یکم جلوتر صحبت میکنیم.
Shell
> docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:62af9efd515a25f84961b70f973a798d2eca956b1b2b026d0a4a63a3b0b6a3f2
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
دستور " docker info " به ما گزارشی از عملکرد داکر ارائه میده که شامل خروجی های زیادی هستش اما اگر به خطوط ابتدایی توجه کنید میبینید که ما الان یک ایمیج و یک کانتینر داریم که در وضعیت stopped قرار داره:
Shell
> docker info
Client:
Debug Mode: false
...
Server:
Containers: 1
Running: 0
Paused: 0
Stopped: 1
Images: 1
...
این بدین معنی هستش که داکر با موفقیت نصب و اجرا شده.
نصب کردن جنگو
حالا یک پروژه جنگویی "Hello,World" ایجاد میکنیم که بصورت محلی روی رایانه خودمون اجرا میشه و سپس به کلی انتقالش میدیم به داکر یا پروژه رو به اصطلاح "Dockerize" میکنیم.
میتونید کد های خودتون رو هر جایی ذخیره کنید ولی برای راحتی در این فصل از دوره ما در دایرکتوری desktop یک دایرکتوری جدید به نام code ایجاد میکنیم و کد های هر بخش از این فصل از دوره رو در دایرکتوری های جداگانه ذخیره میکنیم که شما میتونید به هر نحوی که دلخواه خودتون هست کد هاتون رو ذخیره کنید.
Shell
# Windows
> cd desktop
> mkdir code
> cd code
> mkdir part-2
> cd part-2
# MacOS
% cd desktop
% mkdir code
% cd code
% mkdir part-2
% cd part-2
*: از اونجائیکه این بخش از دوره در لیست با شماره 2 نشون داده میشه، این شماره رو برای عنوان دایرکتوری هم انتخاب کردیم تا بعدا در بررسی سورس کد پروژه های دوره دچار اشتباه نشید.
در قدم بعدی، محیط مجازی خودمون رو ایجاد و جنگو رو داخلش نصب میکنیم:
Shell
> pipenv install django~=4.0.0
حالا محیط مجازی خودمون رو استارت میزنیم:
Shell
> pipenv shell
Django Hello,World
حالا میتونیم با استفاده از دستور " startproject " یک پروژه جنگویی جدید ایجاد کنیم و اسمشو " django_project " بذاریم. دقت کنید که اضافه کردن نقطه ( . ) در انتهای دستور اختیاری هستش ولی اکثر توسعه دهنده ها این کار رو میکنن. بدون نقطه در انتهای دستور، جنگو یک دایرکتوری مازاد به پروژه اضافه میکنه که با گذاشتن نقطه در انتهای دستور این قضیه منتفی میشه.
ما همچنین میتونیم با استفاده از دستور " migrate " یک دیتابیس اولیه راه اندازی کنیم و سپس با استفاده از دستور " runserver " ، وب سرور محلی خودمون رو راه اندازی کنیم.
Shell
(part-2-oR6S7oIS) > django-admin startproject django_project .
(part-2-oR6S7oIS) > python manage.py migrate
(part-2-oR6S7oIS) > python manage.py runserver
با فرض اینکه همه چیز تا اینجا درست جلو رفته، میتونیم به آدرس " http://127.0.0.1:8000 " بریم و صفحه خوش آمد گویی جنگو رو در مرورگر خودمون مشاهده کنیم:
اَپ Pages
حالا کار رو با ایجاد یک اَپ اختصاصی با نام " pages " برای صفحه homepage خودمون ادامه میدیم. سرور محلی رو با استفاده از کلید های Ctrl+C متوقف میکنیم و سپس با استفاده از دستور " startapp " اَپ خودمون رو ایجاد میکنیم:
Shell
(part-2-oR6S7oIS) > python manage.py startapp pages
جنگو بصورت خودکار یک دایرکتوری جدید با نام " pages " به همراه چندین فایل برامون ایجاد میکنه ولی صرفا ایجاد کردن یک اَپ کافی نیست و ما این اَپ جدید رو به جنگو معرفیش کنیم تا در موردش اطلاع داشته باشه. واسه همین ما باید این اَپ جدید رو به تنظیمات " INSTALLED_APPS " در فایل django_project/settings.py اضافه کنیم. جنگو اَپ ها رو از بالا به پایین بارگذاری میکنه و به همین خاطر بهتره خودمون رو عادت بدیم که اَپ های جدید رو در انتهای این بخش از تنظیمات اضافه کنیم چون ممکنه عملکردشون به اَپ های داخلی خود جنگو از قبیل admin و auth و ... اتکا داشته باشه:
Code
# django_project/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# LocalApps
'pages', # new
]
حالا میتونیم URL path یا مسیر های URL برای اَپ جدیدمون رو معین کنیم. از اونجائیکه ما میخواهیم پیغاممون در صفحه homepage نشون داده بشه از دَبل کوتیشن های بدون محتوا یا رشته خالی "" استفاده میکنیم. همچنین یادتون نره که در خط دوم include رو فراخوانی کنید:
Code
# django_project/urls.py
from django.contrib import admin
from django.urls import path, include # new
urlpatterns = [
path('admin/', admin.site.urls),
path("", include("pages.urls")), # new
]
در این مرحله از کار ما بجای ایجاد یک template جداگانه، فقط یک پیام ساده رو در فایل pages/views.py وارد میکنیم که خروجیش یک رشته حاوی "Hello, World" خواهد بود:
Code
# pages/views.py
from django.http import HttpResponse
def home_page_view(request):
return HttpResponse("Hello, World!")
قدم بعدی ما ایجاد یک فایل urls.py داخل اَپ pages و متصل کردنش به home_page_view هستش. پس بعد از ایجاد فایل pages/urls.py، در خط اول path و در ادامه home_page_view رو فراخوانی میکنیم و تنظیمشون میکنیم روی مسیری که مجدداً با رشته خالی "" معین کردیم. توجه داشته باشید که ما یک اسم اختیاری با عنوان "home" هم به این مسیر اختصاص دادیم که این نامگذاری گرچه اختیاری هستش ولی به درد بخوره و به عنوان یک توسعه دهنده میتونه عادت مفیدی براتون باشه:
Code
# pages/urls.py
from django.urls import path
from .views import home_page_view
urlpatterns = [
path("", home_page_view, name="home")
]
روند کلی عملکرد صفحه homepage به این صورت خواهد بود که : وقتی یک کاربر به homepage میره، در ابتدا به django_project/urls.py هدایت میشه و سپس به pages/urls.py و نهایتا به home_page_view هدایت میشه که پیغام "Hello,World" رو برمیگردونه.
کار ما تمومه و کافیه تا وب سرور خودمون رو دوباره راه اندازی کنیم تا نتیجه رو ببینیم:
Shell
(part-2-oR6S7oIS) > python manage.py runserver
حالا اگه دوباره به آدرس " http://127.0.0.1:8000 " برید، خروجی مورد نظر رو میتونید مشاهده کنید:
حالا میریم سر وقت داکر !
ابتدا با استفاده از کلید های Ctrl+C وب سرور رو متوقف میکنیم و از اونجائیکه دیگه با محیط مجازی کاری نداریم، با استفاده از دستور " exit " ازش خارج میشیم:
Shell
(part-2-oR6S7oIS) > exit
>
حالا شاید براتون سوال باشه که ما از کجا میدونیم از محیط مجازی خارج شدیم؟ خب اگه به اول خط دستوری خودتون در ترمینال دقت کنید دیگه پارانتز های حاوی نام محیط مجازی رو نمیبینید و در این نقطه از کار هر دستور جنگویی که وارد کنید دیگه اجرا نمیشه.
Docker Image
داکر ایمیج(Docker image) فایلی هستش که فقط قابلیت خوانده شدن(read-only) داره و تشریح کاملی از نحوه ایجاد یک داکر کانتِینر(Docker container) رو ارائه میده. در واقع، ایمیج یک دستورالعمل هستش و کانتِینر یک نمونه اجرا شده بر پایه اون دستور العمل؛ اگر بخواهیم قیاس آپارتمان رو از قسمت قبلی ادامه بدیم میشه گفت که یک ایمیج بمثابه نقشه ایه که برای ساخت یک آپارتمان به کار میره و کانتِینر ساختمانی هستش که از روی این نقشه ساخته شده.
ایمیج ها اغلب بر پایه ایمیج دیگه ای ساخته میشن و سفارشی سازی های منحصر به خودشون رو دارند. برای نمونه میشه به لیست بلندی از ایمیج های پشتیبانی شده برای پایتون اشاره کرد که به نسخه پایتون منحصر به خودشون تکیه دارن.
Dockerfile
برای پروژه جنگوی خودمون نیاز داریم تا یک ایمیج سفارشی بسازیم که نه تنها شامل پایتون هستش، بلکه کد هایی که تا اینجا وارد کردیم و تنظیماتی که اعمال کردیم رو هم شامل میشه. برای ایجاد ایمیج خودمون باید یک فایل ویژه که با نام " Dockerfile " شناخته میشه درست کنیم که مراحل ایجاد و اجرای ایمیج سفارشی رو تشریح میکنه.
در دایرکتوری پایه خودتون یک فایل با نام " Dockerfile " ایجاد کنید و سپس خطوط زیر رو بهش وارد کنید که در ادامه خط به خط با همدیگه بررسی میکنیم.(منظور از دایرکتوری پایه همون دایرکتوری part-2 هستش که قبلا ایجاد کردیم.)
Dockerfile
# Pull base image
FROM python:3.10.4-slim-bullseye
# Set environment variables
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# Set work directory
WORKDIR /code
# Install dependencies
COPY Pipfile Pipfile.lock /code/
RUN pip install pipenv && pipenv install --system
# Copy project
COPY . /code/
*: این فایل رو میتونید در text editor خودتون باز کرده و خطوط بالا رو بهش اضافه کنید.
به هنگام ایجاد یک ایمیج، فایل " Dockerfile " از بالا به پایین خونده میشه. در ابتدای این فایل یک دستور " FROM " رو میبینید که به داکر میگه ما میخواهیم از چه ایمیجِ پایه ای برای اَپلیکیشن خودمون استفاده کنیم. داکر ایمیج ها میتونن بصورت وراثتی از طریق ایمیج های دیگه ایجاد بشن که ما هم بجای ایجاد ایمیج خودمون، از ایمیج رسمی پایتون استفاده میکنیم که خودش شامل ابزارها و پکیج هایی هستش که ما برای اَپلیکیشن جنگو خودمون نیاز داریم. ایمیج پایه ای که ما در اینجا ازش استفاده میکنیم نسخه 3.10.4 پایتون با تَگ slim هستش که شامل حداقل پکیج های لازم برای اجرای پایتون هست، و در ادامه تَگ bullseye رو مشاهده میکنید که اشاره داره به آخرین نسخه عرضه شده و پایدارِ Debian.
سپس در ادامه از دستور " ENV " برای تنظیم کردن سه متغیر محیطی استفاده کردیم:
- متغیر اول یا PIP_DISABLE_PIP_VERSION_CHECK : بروزرسانی های خودکار برای pip رو غیرفعال میکنه.
- متغیر دوم یا PYTHONDONTWRITEBYTECODE : بدین معنیه که پایتون تلاشی برای نوشتن فایلهای pyc. نخواهد کرد.
- متغیر سوم یا PYTHONUNBUFFERED : تضمین میکنه که خروجی کنسول ما ظاهر آشنایی داره و توسط داکر، باف نشده.
از دستور " WORKDIR " به منظور تعیین یک دایرکتوری کاریِ پیشفرض به هنگام اجرای مابقی دستورات استفاده میکنیم. اینطوری به داکر میگیم که از این مسیر به عنوان مکان پیشفرض برای دستورات بعدی استفاده کنه. اگر این کار رو نمیکردیم، هر بار که میخواستیم دستوراتی رو درکانتِینر خودمون اجرا کنیم مجبور میشدیم مسیر کامل رو تایپ کنیم ولی این شکلی دیگه داکر فرض رو بر این میذاره که ما میخواهیم همه دستورات رو از این دایرکتوری اجرا کنیم. توی این مورد، دایرکتوریِ کاری ما " code/ " هستش ولی بعضی وقتها میتونه طولانی باشه، مثل: "app/src/" یا "usr/src/app/" یا مسیر های گوناگون دیگه ای که بسته به نیاز های خاص یک پروژه میتونن متفاوت باشن.
قدم بعدی نصب کردن ابزارها و پکیج های مورد نیاز برای اجرای پروژه هستن که به اصطلاح دقیق تر بهشون میگیم "dependencies"(دیپِندِنسی) ، که برای این منظور از Pipenv استفاده میکنیم. پس دو فایل Pipfile و Pipfile.lock رو به دایرکتوری "/code/" در داکر کپی میکنیم.
خالی از لطف نیست که بدونیم چرا Pipenv یک فایل Pipfile.lock هم ایجاد میکنه. مفهوم فایلهای قفل شده یا lock files منحصر به پایتون یا Pipenv نیست؛ در واقع این مفهوم در ابزارهای مدیریت پکیج اکثر زبانهای برنامه نویسی مدرن حضور داره. مثل: Gemfile.lock در زبان Ruby و yarn.lock در زبان JavaScript و composer.lock در زبان PHP و ... .
اولین پروژه ی محبوبی که این مفهوم رو در پکیج های پایتون جا داد، Pipenv بود.
مزیت یک فایل قفل شده در این هستش که منجر به یک ساختار ثابت و قطعی میشه: یعنی مهم نیست که شما چند دفعه پکیج های نرم افزاری رو نصب میکنید، در هر صورت نتیجه یکسان میمونه و بدون یک فایل قفل شده که دیپِندِنسی ها و ترتیبشون رو قفل میکنه، همچین اتفاقی نمیفته که این بدین معنی هستش که اگه دو تا هم تیمی بخوان لیست یکسانی از پکیج های نرم افزاری رو نصب کنن، نهایتا از لحاظ ساختاری کمی با هم دیگه تفاوت خواهند داشت.
وقتیکه با داکر کار میکنیم و کد پروژه همزمان هم روی رایانه ما و هم در داکر حاضر هستش، پتانسیل بروز تداخل با Pipfile.lock به هنگام بروزرسانی پکیج های نرم افزاری بالا میره که در این مورد در بخش بعدی بیشتر صحبت میکنیم.
خب حالا اگه بخواهیم دوباره برگردیم به کد های بالا میبینیم که در خط بعدی از دستور " RUN " استفاده کردیم تا ابتدا Pipenv رو نصب کنیم و سپس با استفاده از دستور " pipenv install " پکیج های نرم افزاری داخل فایل pipfile.lock رو نصب کنیم. مهمه که " system-- " رو اضافه کنیم چون Pipenv بصورت پیشفرض به دنبال یک محیط مجازی میگرده تا پکیج ها رو داخلش نصب کنه اما چون ما الان داخل داکر هستیم، از لحاظ فنی اصلا هیچ محیط مجازی وجود نداره و ما با کانتِینرِ خود داکر سر و کار داریم. پس باید از " system-- " استفاده کنیم تا مطمئن باشیم پکیج های ما در همه جای داکر در دسترس هستن.
به عنوان آخرین قدم مابقی کدهای خودمون رو در دایرکتوری "/code/" در داخل داکر کپی میکنیم. اما چرا کد خودمون رو دو بار کپی میکنیم؟ بار اول Pipfile و Pipfile.lock ، و بار دوم بقیه اش رو ؟ علتش اینه که ایمیج ها بر اساس دستورالعمل هایی ساخته میشن که از بالا به پایین اجرا میشن و چون کد هایی که خودمون مینویسیم اغلب دچار تغییر میشن، میخواهیم که این کد ها در آخرین مرحله قرار بگیرن. اینطوری هر دفعه که تغییری اتفاق میفته کافیه اون بخشی از ایمیج رو بازسازی کنیم که دچار تغییر شده، و دیگه نیازی نباشه هر دفعه که تغییری رخ میده همه چیز رو از اول نصب کنیم. و از اونجائیکه پکیج های نرم افزاری حاضر در فایل های Pipfile و Pipfile.lock به ندرت تغییر میکنن، منطقیه که اونها رو در ابتدا کپی و نصب کنیم.
اگر مطالب بالا گیج کننده به نظر میاد براتون، اصلا نگران نباشید چون داکر خودش به تنهایی تکنولوژی بزرگی هستش و ما قرار نیست در این دوره روی داکر تمرکز کنیم ولی از بابت داکرایز(Dockerize) کردن پروژه ها خیالتون راحت باشه چون اکثر پروژه ها مراحل یکسانی رو طی میکنن.
.dockerignore
فایل " dockerignore. " یکی از بهترین روش ها برای معین کردن فایلها و دایرکتوری هایی هستش که نمیخواهیم در ایمیج ما حضور داشته باشن. اینطوری میتونیم ابعاد کلی ایمیج رو کاهش بدیم و امنیت کار رو بالا ببریم.
از اونجائیکه ما قرار هستش تا در آینده دایرکتوری " git. " و فایل " gitignore. " رو هم ایجاد کنیم، میتونیم همین الان این دو مورد رو در فایل جدیدمون یعنی " dockerignore. " لحاظ کنیم. پس در دایرکتوری پایه خودتون یک فایل جدید با نام " dockerignore. " درست کنید و موارد زیر رو بهش اضافه کنید: (منظور از دایرکتوری پایه همون دایرکتوری part-2 هستش که قبلا ایجاد کردیم.)
.dockerignore
.git
.gitignore
حالا ما تمامی مراحل لازم برای ایجاد یک ایمیج سفارشی سازی شده رو طی کردیم ولی هنوز به معنای واقعی کلمه ایجادش نکردیم. دستوری که برای اینکار استفاده میکنیم " docker build " هستش که در انتهاش یدونه نقطه " . " میذاریم که نمایانگر اینه که Dockerfile در دایرکتوری فعلی حضور داره.(دایرکتوری پایه)
بعد از اجرای این دستور خروجیهای زیادی دریافت میکنیم که اینجا فقط 2 خط اولش رو به همراه خط آخرش نشون دادیم:
Shell
> docker build .
[+] Building 56.8s (11/11) FINISHED
=> [internal] load build definition from Dockerfile
...
=> => writing image sha256:caa7c0...
docker-compose.yml
ایمیجی که ساختیم الان بطور کامل در دسترس ما هست تا به عنوان یک کانتِینر اجراش کنیم. بمنظور اجرای کانتِینر ما به لیستی از دستورالعمل ها در قالب یک فایل-
با نام " docker-composer.yml " نیاز داریم. در دایرکتوری پایه خودتون یک فایل جدید با نام " docker-compose.yml " ایجاد کنید و کد های زیر رو بهش وارد کنید:
docker-compose.yml
version: "3.9"
services:
web:
build: .
ports:
- "8000:8000"
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
در خط اول آخرین نسخه از Docker Compose رو تعیین کردیم که در حال حاضر 3.9 هستش.در ادامه مشخص کردیم که کدوم سرویس ها (یا کانتِینر ها) رو میخواهیم که در داخل داکر اجرا بشن. این امکان وجود داره که چندین سرویس در حال اجرا داشته باشیم ولی ما فعلا فقط یکی داریم و اونم برای web هستش.
اگر دقت کنید در داخل سرویسِ web تنظیمات build رو به گونه ای اعمال کردیم که داخل دایرکتوری فعلیمون به دنبال فایل Dockerfile بگرده که اینکار فقط با گذاشتن یک نقطه " . " میسّر شده. در ادامه از پورت پیشفرض جنگو یعنی پورت 8000 استفاده کردیم و در خط بعد با اجرا شدن command انتظار داریم که وب سرور محلیمون راه اندازی بشه و نهایتا میرسیم به پیاده سازی volumes. اتفاقی که در این قسمت میفته اینه که فایلهای داکر و فایلهای رایانه ما(فایلهای محلی) با همدیگه بطورخودکار همگام سازی میشن، به عبارت دیگر اگر به کد خودمون در داکر تغییراتی رو اعمال کنیم، بطور خودکار این تغییرات در فایلهای محلی ما هم خوشون رو نشون میدن.
حالا نوبت به راه اندازی کانتِنر میرسه که با استفاده از دستور " docker-compose " این کار رو میکنیم. این دستور هم بعد از اجرا خطوط خروجی زیادی رو در ترمینال نشون میده که ما چندین خط رو در پایین آوردیم:
Shell
> docker-compose up
[+] Building 3.8s (11/11) FINISHED
=> [internal] load build definition from Dockerfile
...
Attaching to part-2-web-1
part-2-web-1 | Watching for file changes with StatReloader
part-2-web-1 | Performing system checks...
part-2-web-1 |
part-2-web-1 | System check identified no issues (0 silenced).
part-2-web-1 | October 09, 2022 - 11:00:36
part-2-web-1 | Django version 4.0.8, using settings 'django_project.settings'
part-2-web-1 | Starting development server at http://0.0.0.0:8000/
part-2-web-1 | Quit the server with CONTROL-C.
برای اینکه مطمئن بشید کار میکنه میتونید به آدرس " http://127.0.0.1:8000 " برید تا دوباره پیام "Hello, World" رو مشاهده کنید.
جنگو حالا بطور صحیح در داخل کانتِینرکار میکنه و ما دیگه با یک محیط مجازی که بصورت محلی راه اندازی میکردیم کار نمیکنیم، دیگه دستور " runserver " رو اجرا نمیکنیم چون همه کد های ما در کانتِینر حاضر هستن و حالا سرور جنگو در داخل یک کانتِینر کار میکنه. پس همه مراحل موفقیت آمیز بودن !
در طی این فصل از دوره چندین ایمیج و کانتِینر میسازیم و روند کلی پایین، رفته رفته براتون جا میفته:
- ایجاد فایل Dockerfile با دستورالعمل های ایمیج سفارشی سازی شده
- اضافه کردن فایل dockerignore.
- ایجاد کردن ایمیج
- ایجاد فایل docker-compose.yml
- راه اندازی کانتِینر
کانتِینر در حال اجرای فعلی رو با استفاده از کلید های Ctrl+C متوقف کنید و بعدش دستور " docker-compose down " رو در ترمینال خودتون وارد کنید. کانتِینر های داکر بخش زیادی از مِموری رو اشغال میکنن، واسه همین بهتره که هر وقت کارتون باهاشون تموم شد متوقفشون کنید:
Shell
> docker-compose down
[+] Running 2/2
- Container part-2-web-1 Removed
- Network part-2_default Removed
>
هر وقت که یک تکنولوژی جدید معرفی میشه، در کنارش پتانسیل یک سری خطرات امنیتی هم مطرح میشه. در مورد داکر میشه این مثال رو زد که: داکر بصورت پیشفرض کاربر رو روی root تنظیم میکنه. کاربرِ root ( که با عناوین " admin " و " superuser " هم شناخته میشه) یک حساب کاربری ویژه هستش که در لینوکس برای مدیریت سیستم مورد استفاده قرار میگیره.
مستندات داکر شامل یک بخش بزرگ برای موارد امنیتی و به ویژه rootless mode میشن تا بشه از خطر ذکر شده اجتناب کرد. ما این موارد رو پوشش نمیدیم ولی اگه وبسایت شما اطلاعات حساسی رو ذخیره میکنه، توصیه میکنیم که حتما بخش امنیتی این اسناد رو مطالعه کنید.
Git
این روزا گیت سیستم کنترل نسخه ای هستش که انتخاب خیلی از توسعه دهنده هاست و ما هم قراره در این دوره ازش استفاده کنیم. پس برای شروع یک ریپازیتوری(repository) جدید با استفاده از دستور " git init " راه اندازی کنید و سپس دستور " git status " رو اجرا کنید تا ببینیم که کدوم فایلها/دایرکتوری ها قراره ثبت بشن:
Shell
> git init
> git status
قبل از اینکه اولین commit خودمون رو اعمال کنیم بهتره که یک فایل gitignore. در دایرکتوری پایه پروژه ایجاد کنیم. توی این فایل فولدر pycache و دیتابیس محلی خودمون یعنی db.sqlite3 رو لحاظ میکنیم، و اگر دارید از سیستم عامل مَک استفاده میکنید باید " DS_Store. " رو هم اضافه کنید و در غیر اینصورت نیازی نیست.(فولدر pycache رو با نام __pycache__ توی دایرکتوری django_project میتونید پیدا کنید.)
.gitignore
__pycache__/
db.sqlite3
.DS_Store # Mac only
حالا نوبت اینه که همه فایلهامون رو به Git اضافه کنیم و اولین commit خودمون رو به همراه یک پیام اعمال کنیم:
Shell
> git add .
> git commit -m “part-2”
جمع بندی
ما هر چیزی که برای توسعه محلی لازم داشته باشیم رو میتونیم در داخل داکر پیدا کنیم: سرویس های وب، دیتابیس ها و ... و وقتیکه از جنگو به همراه داکر استفاده میکنیم، الگوی کلی کار ثابت میمونه:
- ایجاد یک محیط مجازی جدید و نصب جنگو
- ایجاد یک پروژه جدید جنگو در داخل این محیط
- نوشتن یک فایل Dockerfile و ایجاد ایمیج اولیه
- نوشتن یک فایل docker-compose.yml و راه اندازی کانتِینر با دستور " docker-compose up "
در ادامه کار چندین پروژه جنگویی دیگه با داکر میسازیم و این روند بالا بیشتر جا میفته، اما در کل همه اش همین موارد بالاست. در بخش بعدی یک پروژه جدید جنگو با استفاده از داکر میسازیم و PostgreSQL رو به عنوان دیتابیس مورد استفاده خودمون در داخل یک کانتِینر جداگانه به پروژه اضافه میکنیم.