تمرین هفته ۵
در این تمرین، مجموعهای از مهارتهای کلیدی هفتهی پنجم را در قالب یک پروژهی واقعی و استاندارد پیادهسازی میکنید: ساخت ایمیج بهینه با رویکرد Multi-stage Build، انتشار ایمیج در رجیستری ابری (GitHub Container Registry - GHCR)، مدیریت Secretها و متغیرها با .env و در نهایت ارکستراسیون و راهاندازی Declarative سرویسها (وبسرویس پایتون + پایگاهدادهی PostgreSQL) با Docker Compose.
در هفتهی چهارم وبسرویس را مستقیماً روی سیستمعامل میزبان با استفاده از systemd و venv مستقر کردید. در این هفته وارد دنیای مدرن Cloud-Native میشوید؛ برنامهی خود را بستهبندی (Containerize) میکنید تا مستقل از بستر میزبان، روی هر سروری با یک دستور بالا بیاید.
پیش از شروع، درسهای این هفته بهویژه ساخت Image با Dockerfile و بهینهسازی لایهها، کار با Registryها و مدیریت سرویسهای چندکانتینری با Docker Compose را مرور کنید.
مشخصات فنی و استانداردهای تمرین
پیادهسازی شما باید دقیقاً مطابق با مشخصات فنی زیر باشد:
| مولفه / نیازمندی | مقدار استاندارد الزامی | توضیحات |
|---|---|---|
| وبسرویس پایتون | app/app.py | مبتنی بر Flask، با اندپوینتهای بررسی سلامت و اتصال به دیتابیس |
| ساختار Dockerfile | Multi-stage Build | تفکیک مرحلهی ساخت وابستگیها (builder) از مرحلهی نهایی و سبک (runtime) |
| کاربر غیر روت (اختیاری) | USER <username> | اجرای پروسهی کانتینر با کاربر غیر روت جهت ارتقای امنیت (اختیاری/بونس) |
| رجیستری انتشار | ghcr.io (GHCR) | ایمیج باید در رجیستری گیتهاب منتشر و دسترسی آن عمومی (Public) باشد |
| آدرس ایمیج در Compose | image: ghcr.io/<username>/<image>:<tag> | فایل Compose نباید ایمیج وب را بهصورت محلی (build: .) بسازد؛ بلکه باید مستقیماً ایمیج GHCR را فراخوانی کند |
| پایگاهداده | postgres:16-alpine | سرویس دیتابیس در فایل Compose همراه با Named Volume ماندگار |
| بررسی سلامت دیتابیس (اختیاری) | healthcheck با pg_isready | اطمینان از بالا آمدن کامل دیتابیس پیش از استارت وبسرویس (اختیاری/بونس) |
| پورتهای ارتباطی | وب: 8080 (هاست) ◄ 5000 (کانتینر)دیتابیس: فقط داخلی روی پورت 5432 | پورت دیتابیس به دلایل امنیتی نباید روی هاست منتشر (ports) شود |
| مدیریت Secretها و متغیرها | .env و .env.example | مقادیر واقعی در .env (ignore شده در گیت) و نمونهی تمپلیت در .env.example |
| شبکهی ارتباطی | Custom Bridge Network | ارتباط دو کانتینر از طریق نام سرویس در شبکهی اختصاصی Compose |
گام ۱ — آمادهسازی وبسرویس پایتون
در این مرحله ساختار اولیهی پروژهی پایتونی را آماده میکنید. این وبسرویس وظیفه دارد با پایگاهدادهی PostgreSQL ارتباط برقرار کند، وضعیت سلامت اتصال را بسنجد و تعداد درخواستهای ثبتشده را در جدولی درون پایگاهداده ذخیره و شمارش کند.
ابتدا دایرکتوری کار تمرین را در ماشین لینوکسی خود بسازید:
۱. فایل نیازمندیها (app/requirements.txt)
فایل app/requirements.txt را با محتویات زیر ایجاد کنید:
۲. کد وبسرویس (app/app.py)
فایل app/app.py را بسازید. این برنامه مشخصات اتصال به پایگاهداده را از متغیرهای محیطی دریافت میکند:
گام ۲ — ساخت Dockerfile چندمرحلهای (Multi-stage Build)
هدف این گام، نوشتن یک Dockerfile استاندارد با تکنیک Multi-stage Build است تا ایمیج نهایی فاقد کامپایلرها، پکیجهای بیلد اضافه و فایلهای کش pip باشد.
۱. ایجاد فایل .dockerignore
در ریشهی پروژه (~/week-5-lab/.dockerignore) فایلی بسازید تا فایلهای غیرضروری و حساس وارد Build Context نشوند:
۲. الزامات و الگوی مفهومی فایل Dockerfile
در مسیر ~/week-5-lab/Dockerfile یک فایل داکرفایل دومرحلهای طراحی کنید که نیازمندیهای زیر را برآورده سازد:
- مرحلهی اول — ساخت و وابستگیها (
Builder Stage):- از یک ایمیج پایهی رسمی و پایدار پایتون (مانند
python:3.11-slim) با یک شناسه (مانندAS builder) استفاده کنید. - یک مسیر کاری (
WORKDIR) مشخص کنید. - یک محیط مجازی (
venv) بسازید و مسیر باینریهای آن را در متغیر محیطیPATHقرار دهید تا نیازمندیها درون آن نصب شوند. - فایل وابستگیها (
app/requirements.txt) را پیش از کدهای سورس کپی کرده و وابستگیها را با سوئیچ--no-cache-dirنصب کنید تا از قابلیت Caching لایهها نهایت استفاده را ببرید.
- از یک ایمیج پایهی رسمی و پایدار پایتون (مانند
- مرحلهی دوم — زمان اجرای نهایی (
Runtime Stage):- از یک ایمیج سبک و تمیز (مانند
python:3.11-slim) به عنوان پایهی مرحلهی نهایی استفاده کنید. - صرفاً پوشهی محیط مجازی آماده را با دستور
COPY --from=builder ...از مرحلهی بیلد به ایمیج نهایی منتقل کنید. - متغیر
PATHرا به مسیر محیط مجازی کپیشده تنظیم کنید تا دستورpythonمستقیماً پکیجهای نصبشده را شناسایی کند. - کدهای برنامه (
app/) را به کانتینر منتقل کنید. - پورت وبسرویس (
5000) را باEXPOSEمشخص کنید و دستور اجرای وبسرویس (CMD) را به فرمت استاندارد Exec Form تعریف نمایید.
- از یک ایمیج سبک و تمیز (مانند
قالب مفهومی (Conceptual Skeleton):
بخش اختیاری — اجرای امن با کاربر غیر روت (Non-root User)
چالش اختیاری: امنیت بیشتر کانتینر
بهطور پیشفرض، پردازهی درون کانتینر با کاربر root اجرا میشود. در محیطهای واقعی Production، توصیه میشود یک کاربر و گروه سیستمی بدون دسترسی روت بسازید و اجرای برنامه را با دستور USER به آن واگذار کنید:
پیادهسازی این بخش در تمرین اختیاری است.
۳. آزمایش ساخت ایمیج بهصورت محلی و بررسی لایهها
ایمیج را با یک تگ محلی بسازید و مشخصات آن را بررسی کنید:
با دستور docker history، لایههای ساختهشده و میزان حجم هر لایه را مشاهده کنید:
بررسی کنید که کدام لایهها بیشترین فضا را اشغال کردهاند و چگونه انتقال تمیز فایلهای محیط مجازی (/opt/venv) باعث شده تا ابزارهای بیلد در ایمیج نهایی قرار نگیرند. خروجی کوتاه دستورهای بالا را برای ثبت در 01-dockerfile.md یادداشت کنید.
گام ۳ — آشنایی با GHCR و انتشار ایمیج (Image Push)
رجیستری GHCR چیست و چرا از آن استفاده میکنیم؟
GitHub Container Registry (GHCR) با آدرس دامنه ghcr.io، سرویس میزبانی کانتینر اختصاصی گیتهاب است که با استاندارد OCI مطابقت دارد.
چرا بهجای Docker Hub از GHCR استفاده میکنیم؟
- محدودیتها و تحریمهای داکر هاب برای کاربران ایران: همانطور که در درس سوم دیدید، سرویس داکر هاب بهدلیل تحریمها دسترسی مستقیم کاربران و سرورهای با IP ایران را مسدود کرده است (
403 Forbiddenیا خطای دسترسی جغرافیایی). ثبت اکانت، ورود و ارسال (docker push) به Docker Hub نیازمند استفادهی مداوم از ابزارهای تغییر IP و پروکسیهای اختصاصی است؛ در حالی که رجیستری GHCR بهصورت مستقیم و پایدار از طریق اکانت گیتهاب در دسترس است و در بسیاری از محیطها دردسرهای تحریم Docker Hub را ندارد. - یکپارچگی مستقیم با گیتهاب: مخزن کدهای شما، دسترسیها و پکیجهای کانتینری همگی ذیل یک حساب کاربری قرار میگیرند و برای پایپلاینهای CI/CD (هفتههای آینده) نیاز به اتصال به سرویس شخص ثالث مجزا ندارید.
- سقف درخواست بسیار بالاتر (Rate Limits): داکر هاب برای کاربران ناشناس محدودیت سختگیرانهی ۱۰۰ پول در ۶ ساعت اعمال میکند؛ در حالی که GHCR در تیمهای DevOps پایداری و ظرفیت به مراتب بالاتری دارد.
- همنامی سازمانی با مخزن پروژه: نام ایمیج مستقیماً شامل نام کاربری شما در گیتهاب میشود:
ghcr.io/<your-github-username>/techstack-1405-python-app:1.0.0.
برای انتشار ایمیج در GHCR مراحل زیر را دنبال کنید:
۱. ساخت توکن دسترسی شخصی (Personal Access Token - PAT)
برای ورود به GHCR با خط فرمان داکر، نمیتوانید از رمز عبور عادی گیتهاب استفاده کنید. باید یک Personal Access Token بسازید:
- در مرورگر وارد حساب کاربری GitHub خود شوید.
- به بخش Settings (پروفایل شخصی) بروید.
- در منوی سمت چپ به پایین صفحه اسکرول کرده و روی Developer settings کلیک کنید.
- روی Personal access tokens و سپس Tokens (classic) کلیک کنید.
- روی Generate new token ◄ Generate new token (classic) کلیک کنید.
- یک نام در بخش
Noteوارد کنید (مانندGHCR Token Week 5). - تیک گزینههای زیر را فعال کنید:
write:packages(آپلود و انتشار پکیجها در GitHub Package Registry)read:packages(دانلود و دریافت پکیجها)delete:packages(اختیاری - برای مدیریت و حذف پکیجهای قدیمی)
- روی دکمهی سبز رنگ Generate token کلیک کرده و رشتهی توکن ایجادشده (که با
ghp_آغاز میشود) را در جایی امن کپی کنید.
هشدار امنیتی توکنها
هرگز مقدار توکن PAT را در مخزن گیت کامیت نکنید یا آن را در فایلهای متنی داخل ریپازیتوری ذخیره نسازید. توکنها معادل رمز عبور شما هستند.
۲. لاگین در GHCR با دستور docker login
روی ماشین لینوکس خود، دستور docker login را برای رجیستری ghcr.io اجرا کنید:
داکر بهصورت تعاملی (Interactive) نام کاربری و رمز عبور را از شما درخواست میکند:
- Username: نام کاربری حساب کاربری GitHub خود را وارد کنید (با حروف کوچک انگلیسی).
- Password: مقدار Personal Access Token (PAT) که در مرحلهی قبل کپی کردید را Paste کنید (رمز عبور اصلی اکانت کار نخواهد کرد و در ترمینال کاراکترهای آن نمایش داده نمیشوند).
نکته در مورد نام کاربری در GHCR
در مراحل لاگین و نامگذاری ایمیجها، حتماً <your-github-username> را با حروف کوچک انگلیسی بنویسید؛ زیرا استاندارد رجیستریهای کانتینر نسبت به حروف بزرگ حساس است.
در صورت موفقیت، پیام Login Succeeded در ترمینال نمایش داده میشود.
۳. تگگذاری و ارسال ایمیج (Tag & Push)
ایمیج محلی را مطابق الگوی GHCR برچسب بزنید و به سرورهای گیتهاب ارسال کنید:
۴. الزامی: عمومیسازی دسترسی پکیج (Change Package Visibility to Public)
بهصورت پیشفرض، ایمیجهایی که برای نخستین بار به GHCR ارسال میشوند ممکن است در وضعیت Private قرار گیرند. اگر ایمیج Private بماند، تیم داوری یا همکاران نمیتوانند بدون لاگین به اکانت شما آن را Pull کنند.
برای عمومی کردن ایمیج:
- در وبسایت گیتهاب به صفحه اصلی پروفایل خود بروید:
https://github.com/<your-github-username> - به زبانهی Packages بروید.
- پکیج تازهتولیدشدهی
techstack-1405-python-appرا انتخاب کنید. - در سمت راست، روی Package settings کلیک کنید.
- به انتهای صفحه بخش Danger Zone بروید و روی دکمهی Change visibility کلیک کنید.
- گزینهی Public را انتخاب کرده و با تایپ نام پکیج، تغییر را تأیید نمایید.
برای آزمودن عمومی بودن، میتوانید از طریق یک نشست یا شل بدون لاگین امتحان کنید:
گام ۴ — مدیریت متغیرهای محیطی با .env و .env.example
یکی از اصول بنیادین متدولوژی Twelve-Factor App (بهویژه اصل سوم: III. Config)، تفکیک کامل پیکربندیها و Secretها از کد برنامه است:
- فایل
.env: حاوی مقادیر واقعی محیط جاری شماست و هرگز نباید کامیت شود. - فایل
.env.example: یک فایل الگو شامل نام تمام متغیرها و مقادیر پیشفرض یا ساختگی است که در مخزن گیت کامیت میشود تا دیگران بدانند چه متغیرهایی برای راهاندازی پروژه لازم است.
در مسیر ~/week-5-lab/.env.example فایل زیر را بسازید:
سپس برای اجرای محلی خودتان، یک کپی از آن به نام .env ایجاد کرده و پسورد دلخواه خود را تعیین کنید:
گام ۵ — تدوین compose.yaml و راهاندازی یکپارچهی سرویسها
اکنون در ریشهی پروژه، فایل ~/week-5-lab/compose.yaml را طراحی کنید. این فایل زیرساخت دو کانتینری پروژه (دیتابیس PostgreSQL و وبسرویس پایتون) را بهصورت توصیفی مدیریت میکند.
قانون مهم در مورد مرجع ایمیج وبسرویس
در فایل compose.yaml برای سرویس وب، نباید از کلید build: . استفاده کنید!
سرویس وب باید مستقیماً ایمیج عمومی شما در GHCR را فراخوانی کند:
این کار تضمین میکند که ایمیج روی رجیستری ابری با موفقیت پابلیش شده، قابل دانلود عمومی است و بدون نیاز به کدهای سورس محلی روی سرور اجرا میشود.
الزامات پیادهسازی compose.yaml:
- سرویس دیتابیس (
db):- استفاده از ایمیج سبک و پایدار
postgres:16-alpine. - دریافت متغیرهای نام دیتابیس، کاربر و رمز عبور از فایل
.env(مانند${POSTGRES_DB}). - اتصال دادههای پایگاهداده (
/var/lib/postgresql/data) به یک Named Volume جهت جلوگیری از حذف اطلاعات با خاموش شدن کانتینر. - اتصال به شبکهی اختصاصی کانتینرها.
- نکته امنیتی: به هیچ وجه پورت
5432را به هاست نگاشت نکنید (ports)؛ ارتباط دیتابیس فقط باید در شبکهی داخلی کانتینرها برقرار باشد.
- استفاده از ایمیج سبک و پایدار
- سرویس وب (
web):- فراخوانی ایمیج ساختهشده از GHCR (
ghcr.io/...). - اتصال پورت هاست (از متغیر
${APP_PORT}) به پورت کانتینر (5000). - تنظیم متغیرهای محیطی اتصال به پایگاهداده (
DB_HOSTبرابر با نام سرویس دیتابیس یعنیdb،DB_PORTبرابر با5432و سایر مشخصات از.env). - تعریف وابستگی به دیتابیس (
depends_on: - db). - اتصال به همان شبکهی اختصاصی کانتینرها.
- فراخوانی ایمیج ساختهشده از GHCR (
- تعریف سراسری منابع:
- بخشهای
volumes:وnetworks:در انتهای فایل تعریف شوند.
- بخشهای
قالب مفهومی (Conceptual Skeleton):
بخش اختیاری — سلامتسنجی دیتابیس (Healthcheck)
چالش اختیاری: راهاندازی مطمئن با Healthcheck
در داکر کامپوز، استفادهی ساده از depends_on: - db فقط ترتیب استارت کانتینر را تعیین میکند، اما تضمین نمیکند که پروسهی PostgreSQL در لحظهی شروع وبسرویس کاملاً بالا آمده و آمادهی پذیرش اتصال باشد.
برای پیادهسازی حرفهای این سازوکار، میتوانید یک healthcheck روی کانتینر دیتابیس با باینری pg_isready تعریف کنید و در سرویس وب با شرط condition: service_healthy به آن وابسته شوید:
چرا $$ به جای $ در دستور test استفاده شده؟
در فایلهای Compose، نماد $ برای جایگزینی متغیرهای سیستم میزبان (Compose Variable Interpolation) به کار میرود. با نوشتن $$ به داکر کامپوز اعلام میکنید که متغیر را در سطح هاست Expand نکند ($$ به $ خروجی داده میشود) تا شلِ داخل خود کانتینر در زمان اجرای دستور، مقدار متغیر محیطی داخل کانتینر را بخواند.
استفاده از این مکانیزم در تمرین اختیاری است و باعث پایداری و خودترمیمی بیشتر سرویسهای شما در محیطهای عملیاتی میشود.
گام ۶ — تست، راهاندازی و اعتبارسنجی پایداری دادهها
۱. حذف ایمیجهای محلی (تضمین Pull واقعی از GHCR)
برای اینکه مطمئن شوید فایل compose.yaml واقعاً ایمیج را از رجیستری ابری GHCR دانلود میکند و به کدهای محلی یا ایمیج کششده روی سیستمتان وابسته نیست، تگهای محلی ساختهشده را از سیستم خود پاک کنید:
اکنون هنگام اجرای داکر کامپوز، لاگ دانلود لایهها (Pulling from ...) را به وضوح در خروجی مشاهده خواهید کرد.
۲. راهاندازی سرویسها با Docker Compose
۳. بررسی وضعیت و لاگها
وضعیت سرویس دیتابیس و وب باید فعال (Up یا healthy) باشد.
۴. تست اندپوینت سلامت
در ترمینال، صحت ارتباط با پایگاهداده را با curl بیازمایید:
خروجی باید کد وضعیت 200 OK و پاسخ JSON زیر باشد:
۵. تست ثبت و شمارش دادهها
چند بار اندپوینت اصلی را فراخوانی کنید:
با هر درخواست، مقدار کلید visits باید یک واحد افزایش یابد (مثلاً "visits": 3).
۶. آزمون ماندگاری دادهها (Data Persistence Test)
اکنون کانتینرها را بهطور کامل متوقف کرده و حذف نمایید:
توجه داشته باشید که دستور بالا کانتینرها را حذف میکند اما Named Volume حاوی اطلاعات دیتابیس حفظ میشود. دوباره کانتینرها را استارت کنید:
مجدداً اندپوینت را صدا بزنید:
مقدار visits باید ادامهی ارقام قبلی (مثلاً 4) باشد و از ۱ شروع نشود. این آزمایش تأیید میکند که والیوم داکر به درستی کار میکند.
گام ۷ — ساختار تحویل تمرین و ارسال Pull Request
مستندات و فایلهای تمرین را در فورک مخزن دورهی تمرینها روی شاخهی week-5 و درون ساختار پوشهی زیر قرار دهید:
شرح محتوای فایلهای Markdown (کوتاه و متنی):
نیازی به گزارشنویسی طولانی، ارائهی لاگهای مفصل یا اسکرینشات نیست؛ کافی است خروجیهای متنی کوتاه زیر را در فایلها قرار دهید:
01-dockerfile.md:- توضیح کوتاه (چند خط) دربارهی دو مرحلهی Dockerfile.
- خروجی متنی دستورهای
docker imagesوdocker historyبرای ایمیج ساختهشده.
02-ghcr-publish.md:- آدرس لینک عمومی پکیج شما در GHCR (مثلاً
https://github.com/<username>/techstack-1405-python-app/pkgs/container/techstack-1405-python-app). - خروجی چند خطی دستور
docker push.
- آدرس لینک عمومی پکیج شما در GHCR (مثلاً
03-compose-setup.md:- خروجی متنی دستور
docker compose ps. - خروجی دستورهای
curlبرای تست اندپوینتهای/healthو/. - خروجی تست ماندگاری بازدیدها پس از اجرای مجدد کانتینرها.
- خروجی متنی دستور
چکلیست نهایی تحویل تمرین
پیش از باز کردن Pull Request، موارد زیر را بررسی و تأیید کنید:
- فایل
Dockerfileاز تکنیک Multi-stage Build استفاده کرده و حجم آن بهینه شده است. - فایل
.dockerignoreدر کنار Dockerfile قرار گرفته و مانع ارسال فایلهای غیرضروری به کانتینر شده است. - (اختیاری) اجرای پروسهی کانتینر با کاربر غیر روت (
USER) در فایل Dockerfile پیادهسازی شده است. - ایمیج به رجیستری GHCR با تگ مشخص (مانند
1.0.0) پوش شده و تنظیم دسترسی آن روی Public قرار دارد. - در فایل
compose.yamlازimage: ghcr.io/...استفاده شده و کلیدbuild:وجود ندارد. - پایگاهداده PostgreSQL به یک Named Volume متصل است و پورت آن مستقیماً به هاست باز نشده است.
- فایل
.env.exampleحاوی متغیرهای نمونه در پوشهassets/قرار داده شده و فایل واقعی.envدر گیت کامیت نشده است. - (اختیاری) سلامتسنجی (
healthcheck) دیتابیس در فایل Compose کانفیگ شده و سرویس وب با شرطcondition: service_healthyبه آن وابسته است. - آزمون ماندگاری دادهها (شمارش بازدیدها پس از
docker compose down) با موفقیت انجام شده است. - ساختار دایرکتوری در ریپازیتوری تمرینها دقیقا در مسیر
<your-github-username>/5/ایجاد شده است. - شاخهی گیت
week-5آماده و Pull Request ارسال شده است.