<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های سجاد نصیری</title><link>https://virgool.ir/feed/@sajjad.nasiri.brn</link><description>backend developer</description><language>fa</language><pubDate>2026-11-11 15:48:56</pubDate><image><url>https://files.virgool.ir/upload/users/153260/avatar/dfb5uw.png</url><title>سجاد نصیری</title><link>https://virgool.ir/@sajjad.nasiri.brn</link></image><item><title>دیزاین پترن Strategy به سبک لاراول</title><link>https://virgool.ir/@sajjad.nasiri.brn/دیزاین-پترن-strategy-به-سبک-لاراول-d16wg9uvod8m</link><description>همانطور که می دانید از دیزاین پترن Strategy در مواقعی استفاده می کنیم که چند implementation برای یک کار یکسان داریم و می خواهیم بر اساس یک استراتژی خاص بین این implementation ها انتخاب کنیم که کدام یک اجرا شود. برای مثال تصور کنید که چندین درگاه بانکی داریم از جمله زرین پال، بانک ملی، بانک ملت و... که هر کدام نحوه پیاده سازی و کانفیگ خاص خودشان را دارند. حالا اگر بخواهیم کاربر انتخاب کند که با کدام درگاه، پرداخت را انجام دهد یا مثلا اگر مبلغ پرداخت بیش از 10 ملیون تومان فقط با درگاه بانک ملی پرداخت انجام شود و... باید از دیزاین پترن استراتژی استفاده کنیم.( برای یادگیری دیزاین پترن استراتژی در php می توانید از لینک اشاره شده استفاده کنید)پیش از مطالعه ادامه این نوشته بهتر است اگر با dependency injection در لاراول آشنایی ندارید، مقاله service container و تزریق وابستگی ها در laravel را مطالعه کنید.هدف ما این است که دیزاین پترن استراتژی را از طریق service provider های لاراول و سیستم پیشرفته تزریق وابستگی های این فریمورک انجام دهیم. همچنین فرض کنید که کاربر قرار است در هنگام پرداخت، در قسمت front سایت نوع درگاهی که می خواهد را مشخص کند و نام درگاه انتخاب شده در پارامتری به نام gateway برای ما ارسال می شود.ابتدا یک interface ایجاد می کنیم که همه درگاه های بانکی ما باید از آن implement کنند:&lt;?php&#xA;&#xA;namespace App\Contracts;&#xA;&#xA;interface Gateway&#xA;{&#xA;&#xA;    public function payment($amount);&#xA;&#xA;}2. سپس کلاس های مربوط به بانک های مختلف را می سازیم:&lt;?php&#xA;&#xA;namespace App\Services;&#xA;&#xA;use App\Contracts\Gateway;&#xA;use Illuminate\Support\Facades\Http;&#xA;&#xA;class MelliGateway implements Gateway&#xA;{&#xA;&#xA;    public function payment($amount)&#xA;    {&#xA;        Http::post(&#39;https://mellibank&#39;, [&#xA;            &#39;amount&#39; =&gt; $amount,&#xA;            &#39;token&#39; =&gt; &#39;123&#39;&#xA;            // other parameters&#xA;        ]);&#xA;&#xA;        // other codes&#xA;    }&#xA;}&lt;?php&#xA;&#xA;namespace App\Services;&#xA;&#xA;use App\Contracts\Gateway;&#xA;use Illuminate\Support\Facades\Http;&#xA;&#xA;class MellatGateway implements Gateway&#xA;{&#xA;&#xA;    public function payment($amount)&#xA;    {&#xA;        Http::post(&#39;https://mellatbank&#39;, [&#xA;            &#39;amount&#39; =&gt; $amount,&#xA;            &#39;token&#39; =&gt; &#39;456&#39;&#xA;            // other parameters&#xA;        ]);&#xA;&#xA;        // other codes&#xA;    }&#xA;}3. پس از این که کلاس های implement شده از اینترفیس Gateway را برای درگاه های مختلف ایجاد کردیم، باید آن ها را در service container ثبت کنیم تا بتوانیم به کنترلر ها، کلاس خا و دیگر بخش های برنامه تزریقشان کرده و از آنها استفاده کنیم. بدین منظور با دستور زیر یک service provider می سازیم:php artisan make:provider GatewayServiceProviderپس از ساخته شدن GatewayServiceProvider در آن کدهایی مشابه زیر می نویسیم:&lt;?php&#xA;&#xA;namespace App\Providers;&#xA;&#xA;use App\Contracts\Gateway;&#xA;use App\Services\MellatGateway;&#xA;use App\Services\MelliGateway;&#xA;use Illuminate\Http\Client\Request;&#xA;use Illuminate\Support\ServiceProvider;&#xA;&#xA;class GatewayServiceProvider extends ServiceProvider&#xA;{&#xA;    /**&#xA;     * Register services.&#xA;     *&#xA;     * @return void&#xA;     */&#xA;    public function register()&#xA;    {&#xA;        //&#xA;    }&#xA;&#xA;    /**&#xA;     * Bootstrap services.&#xA;     *&#xA;     * @return void&#xA;     */&#xA;    public function boot()&#xA;    {&#xA;        $this-&gt;app-&gt;singleton(Gateway::class, function (Request $request){&#xA;&#xA;            if ($request-&gt;gateway == &#39;melli&#39;) {&#xA;&#xA;                return new MelliGateway();&#xA;&#xA;            } elseif ($request-&gt;gateway == &#39;mellat&#39;) {&#xA;&#xA;                return new MellatGateway();&#xA;&#xA;            }&#xA;&#xA;        });&#xA;    }&#xA;}به دلیل این که می خواهیم از سرویس Request که خود نیاز به ثبت شدن درون service container دارد استفاده کنیم تا از طریق آن پارامتر gateway ریکوئست جاری را به دست آوریم، عملیات bind کردن را در متد boot انجام داده ایم. سپس پارامتر gateway ریکوئست جاری را گرفته ایم و بررسی کردیم که اگر melli بود درگاه بانک ملی و اگر mellat بود درگاه بانک ملت برگردانده شود.4. در مرحله بعد باید GatewayServiceProvider را در service container ثبت کنیم. برای این کار به config/app.php رفته و در آرایه &#39;providers&#39; پرووایدری که ساختیم را اضافه می کنیم:&#39;providers&#39; =&gt; [&#xA;&#x9;&#x9;// other providers&#xA;&#x9;&#x9;&#xA;&#x9;&#x9; /*&#xA;         * Application Service Providers...&#xA;         */&#xA;        App\Providers\AppServiceProvider::class,&#xA;        App\Providers\AuthServiceProvider::class,&#xA;        // App\Providers\BroadcastServiceProvider::class,&#xA;        App\Providers\EventServiceProvider::class,&#xA;        App\Providers\RouteServiceProvider::class,&#xA;        // our gateway service provider:&#xA;        App\Providers\GatewayServiceProvider::class&#xA;]5. پس از این مرحله می توانیم به صورت type-hint در سرتاسر کدهای لاراولی مان به Gateway دسترسی داشته باشیم:&lt;?php&#xA;&#xA;namespace App\Http\Controllers;&#xA;&#xA;use App\Contracts\Gateway;&#xA;use Illuminate\Http\Request;&#xA;&#xA;class PaymentController extends Controller&#xA;{&#xA;    public function pay(Request $request, Gateway $gateway)&#xA;    {&#xA;        // validate request yourself&#xA;&#xA;        $gateway-&gt;payment($request-&gt;amount);&#xA;&#xA;    }&#xA;}و به این صورت خیلی زیبا و تمیز توانستیم دیزاین پترن strategy را در لاراول پیاده سازی کنیم?</description><category>سجاد نصیری</category><author>سجاد نصیری</author><pubDate>Tue, 29 Aug 2023 17:24:44 +0330</pubDate></item><item><title>service container و تزریق وابستگی ها در laravel</title><link>https://virgool.ir/@sajjad.nasiri.brn/service-container-و-تزریق-وابستگی-ها-در-laravel-rkfciod3b9i9</link><description>مقدمهدر برنامه نویسی شی گرا گاهی پیش می آید که نیاز داریم از object یک class، در کلاس فعلی که در حال پیاده سازی آن هستیم استفاده کنیم. به طور کلی تزریق وابستگی(dependency-injection) یعنی نیازمندی های یک کلاس از طریق سازنده آن یا متدهای به اصطلاح setter به آن کلاس تزریق شوند تا بتوان درون کلاس از آن ها استفاده کرد.هدف این نوشته اما بررسی تزریق وابستگی به شکل عام آن نیست و برای اطلاعات بیشتر راجع به این مفهوم می توانید از مقاله مفهوم تزریق وابستگی (Dependency Injection) - به زبان ساده استفاده کنید.service provider ها در لاراوللاراول برای مدیریت وابستگی ها از مفهومی به نام service container استفاده می کند.  به طور کلی به 2 صورت می توان تزریق وابستگی ها را در لاراول انجام داد:Zero Configuration Resolution: یعنی نیازی به ساخت service provider و  register کردن آن در config/app.php ندرایم و کافی است کلاس مورد نظر را به صورت type hint به ورودی یک تابع بدهیم تا بدون نیاز به new کردن، یک instance از آن در اختیار داشته باشیم. با ذکر مثالی درک این موضوع واضح تر خواهد شد، دو قطعه کدی که در پی خواهد آمد متناظر هستند و عملکرد مشابهی دارند:&lt;?php&#xA; &#xA;class Service&#xA;{&#xA;    // ...&#xA;}&#xA; &#xA;Route::get(&#39;/&#39;, function (Service $service) {&#xA;    $service-&gt;doSomeThing();&#xA;});در کد بالا $service یک آبجکت از کلاس Service می باشد که می توان بدون استفاده از new آن را در بدنه تابع به کار بست. در واقع کد بالا معادل کد زیر می باشد:&lt;?php&#xA; &#xA;class Service&#xA;{&#xA;    // ...&#xA;}&#xA; &#xA;Route::get(&#39;/&#39;, function () {&#xA;&#x9;$service = new Service();&#xA;    $service-&gt;doSomeThing();&#xA;});اگرچه از روش بالا می توان در اکثر قسمت های برنامه لاراولی از جمله controller، middleware، event listener و... استفاده کرد اما ممکن است در برخی قسمت ها از جمله job ها، با مشکلاتی نیز مواجه شویم. همچنین ممکن است بخواهیم در طی فرایند تزریق وابستگی، طبق استراتژی خاصی interface را از implementation مستقل کنیم. کاری که در دیزاین پترن استراتژی هم انجام می دهیم. در چنین مواردی به اصطلاح لاراولی نیاز به bind کردن کلاس یا همان implementation به یک interface داریم. در لاراول این کار را با service provider ها انجام می دهیم که در ادامه آن را توضیح خواهیم داد.(در صورت نیاز میتوانید از طریق این لینک با interface آشنا شوید)همچنین لازم به ذکر است که در view ها نمی توان از این مدلهای تزریق وابستگی استفاده کرد و در فایل های blade، باید از facade یا به طور دقیق تر از realtime facade ها استفاده کنیم که فعلا به آن نخواهیم پرداخت اما می توانید مستندات لاراول در باره facades را مطالعه کنید.2. service providers: وظیفه اصلی پرووایدر ها در لاراول ثبت و پیکربندی نیازمندی های برنامه لاراولی است. سرویس هایی که ما از آن ها در کدهایمان استفاده می کنیم مانند کش، سشن، کوکی، دیتابیس و ... باید از قبل راه اندازی شده باشند تا برنامه‌ی ما کار بکند. برای این منظور لاراول پیش از این که به سراغ کدهایی که در کنترلرها و یا دیگر قسمت ها نوشته ایم برود، هنگام راه اندازی و boot شدن فریمورک مقدمات لازم و کانفیگ های مربوط به این سرویس ها را انجام داده و سپس آنها را در ظرفی به نام service container می ریزد تا در ادامه بتوانیم به راحتی از آنها در سرتاسر کدهایمان به صورت type hint  یا facade استفاده کنیم.مزیت مهم دیگری که bind کردن برای ما دارد، این است که implementation را از interface جدا می کنیم. برای مثال می توانیم مشخص کنیم که اگر اینترفیس Filesystem::class در کنترلر PhotoController فراخوانی شد، Storage::disk(&#39;local&#39;) برای ما برگردانده شود و اگر در VideoController یا UploadController فرخوانی شد Storage::disk(&#39;s3&#39;) را داشته باشیم:use App\Http\Controllers\PhotoController;&#xA;use App\Http\Controllers\UploadController;&#xA;use App\Http\Controllers\VideoController;&#xA;use Illuminate\Contracts\Filesystem\Filesystem;&#xA;use Illuminate\Support\Facades\Storage;&#xA; &#xA;$this-&gt;app-&gt;when(PhotoController::class)&#xA;          -&gt;needs(Filesystem::class)&#xA;          -&gt;give(function () {&#xA;              return Storage::disk(&#39;local&#39;);&#xA;          });&#xA; &#xA;$this-&gt;app-&gt;when([VideoController::class, UploadController::class])&#xA;          -&gt;needs(Filesystem::class)&#xA;          -&gt;give(function () {&#xA;              return Storage::disk(&#39;s3&#39;);&#xA;          });اگر هنوز مطلب برایتان پرواضح نشده نگران نباشید چرا که در ادامه به تفصیل به ساخت و ثبت service provider در لاراول خواهیم پرداخت.اولین مرحله ساخت یک service provider با دستور زیر است:php artisan make:provider RiakServiceProviderفایل ساخته شده به شکل زیر خواهد بود:&lt;?php&#xA;&#xA;namespace App\Providers;&#xA;&#xA;use Illuminate\Support\ServiceProvider;&#xA;&#xA;class RiakServiceProvider extends ServiceProvider&#xA;{&#xA;    /**&#xA;     * Register services.&#xA;     *&#xA;     * @return void&#xA;     */&#xA;    public function register()&#xA;    {&#xA;        //&#xA;    }&#xA;&#xA;    /**&#xA;     * Bootstrap services.&#xA;     *&#xA;     * @return void&#xA;     */&#xA;    public function boot()&#xA;    {&#xA;        //&#xA;    }&#xA;}2 متد مهم register و boot در این فایل وجود دارد. در هر دو متد می توانیم عملیات bind کردن را انجام دهیم اما تفاوت متد register و boot در این است که متد register در زمان ثبت نام سرویس ها در service container اجرا می شود و این درحالی است که متد boot پس از ثبت شدن همه سرویس ها در service container اجرا خواهد شد. پس اگر سرویسی داریم که به سرویس های دیگری که می خواهیم در container ثبت کنیم وابسته است، باید عملیات binding را در متد boot انجام دهیم. راجع به نحوه bind کردن و تفاوت این 2 متد در ادامه ببیشتر توضیح خواهیم داد.به طور کلی نحوه bind به شکل زیر می باشد:public function register()&#xA;    {&#xA;        $this-&gt;app-&gt;singleton(ConnectionService::class, function (Application $app) {&#xA;            return new Connection(config(&#39;riak&#39;));&#xA;        });&#xA;    }در مثال بالا interface با نام ConnectionService به کلاس Connection به اصطلاح bind یا متصل شده و از این پس هرگاه در طول برنامه به صورت type-hint که پیشتر توضیح دادیم ConnectionService را فراخوانی کنیم درواقع یک instance از کلاس Connection را فراخوانی کرده ایم. متد singleton که این کار را برای ما انجام می دهد، مشابه دیزاین پترن singleton تضمین می کند که فقط یک instance برای ما ساخته شود. می توانستیم به جای singleton از متد bind استفاده کنیم و interface و clouser را به آن پاس دهیم. روش های binding دیگری به جز singleton و bind نیز وجود دارد که می توانید در مستندات لاراول آنها را مطالعه کنید:https://laravel.com/docs/10.x/containerhttps://laravel.com/docs/10.x/providersاین کار بسیار پیشرفته، هوشمندانه و باظرافت است که در هیچ فریمورک دیگری نظیر ندارد. جدا کردن interface از implementation یک مزیت مهم دیگر نیز برای ما دارد و آن این که می توانیم به زیبایی هر چه تمام تر از دیزاین پترن استراتژی استفاده کنیم:public function register()&#xA;    {&#xA;        $this-&gt;app-&gt;singleton(ConnectionService::class, function (Application $app) {&#xA;        &#x9;if(request()-&gt;connect_type == &#39;normal&#39;){&#xA;        &#x9;return new NormalConnection(config(&#39;riak&#39;));&#xA;        &#x9;} elseif(request()-&gt;connect_type == &#39;fast&#39;){&#xA;        &#x9;return new FastConnection(config(&#39;riak&#39;));&#xA;        &#x9;} else {&#xA;        &#x9;return new SlowConnection(config(&#39;riak&#39;));&#xA;        &#x9;}&#xA;        });&#xA;    }در کد بالا گفته ایم که اگر پارامتر connect_type که در request جاری از سمت front برای ما ارسال شده، برابر normal بود کلاس NormalConnection در هنگام فراخوانی اینترفیس ConnectionService در کدها برگردانده شود و اگر برابر fast بود کلاس FastConnection و الی آخر. این دقیقا کاری است که در دیزاین پترن استراتژی انجام میدادیم.اما تفاوت متد boot با متد register چیست؟ تفاوت اصلی در این است که متد boot هنگامی که همه سرویس ها در container ثبت شدند اجرا خواهد شد. پس در این متد به پرووایدر های ثبت شده دسترسی داریم. این در حالی است که در متد register ممکن است با فراخوانی یک پرووایدر با این خطا مواجه شویم که این سرویس وجود ندارد، چرا که هنوز در container ثبت نشده. برای مثال اگر خواستیم از پرووایدر ConnectionService یا پرووایدر ها و سرویس های دیگر لاراول در یک پرووایدر دیگر استفاده کنیم، باید این کار را در متد boot انجام دهیم:&lt;?php&#xA;&#xA;namespace App\Providers;&#xA;&#xA;use Illuminate\Support\ServiceProvider;&#xA;use App\contracts\ConnectToCacheService;&#xA;use App\contracts\ConnectionService;&#xA;&#xA;class ExternalCacheServiceProvider extends ServiceProvider&#xA;{&#xA;    /**&#xA;     * Register services.&#xA;     *&#xA;     * @return void&#xA;     */&#xA;    public function register()&#xA;    {&#xA;        //&#xA;    }&#xA;&#xA;    /**&#xA;     * Bootstrap services.&#xA;     *&#xA;     * @return void&#xA;     */&#xA;    public function boot()&#xA;    {&#xA;        $this-&gt;app-&gt;singleton(ConnectToCacheService::class, function (Application $app, ConnectionService $connect_service) {&#xA;        &#x9;return $connect_service-&gt;chooseService(&#39;cache&#39;);&#xA;        });&#xA;    }&#xA;}همانطور که در بالا می بینید در پرووایدر جدید ExternalCacheServiceProvider، می خواهیم از یک پرووایدر دیگر(ConnectionService) استفاده کنیم و برای این کار به جای پیاده سازی متد register این کار را در متد boot انجام دادیم.امیدوارم این مطلب برای شما مفید باشد. اگر سوالی داشتید، در قسمت نظرات بپرسید✌️</description><category>سجاد نصیری</category><author>سجاد نصیری</author><pubDate>Tue, 29 Aug 2023 17:24:00 +0330</pubDate></item><item><title>دسترسی به request فعلی در سراسر django</title><link>https://virgool.ir/@sajjad.nasiri.brn/دسترسی-به-request-فعلی-در-سراسر-django-nad6atntegi2</link><description>اصلی ترین وظیفه هر برنامه backend از جمله برنامه هایی که در django می نویسیم پردازش request های ارسالی کاربر و سپس برگرداندن یک response مناسب می باشد. در واقع entry point هر برنامه django درخواستی است که از سمت کاربر ارسال شده. بنابراین دسترسی داشتن به request جاری در قسمت های مختلف برنامه برای برنامه نویس اهمیت بالایی دارد.همانطور که دیده اید در django ما به request جاری در بعضی قسمت ها مانند views یا متد __call__ در middleware ها دسترسی داریم چون در ورودی توابع آن را دریافت می کنیم اما اگر بخواهیم در قسمت های دیگر از جمله model ها، form ها، template tag ها و... به request جاری دسترسی داشته باشیم باید چه کار کنیم؟در ادامه به پاسخ این مسئله خواهیم پرداخت. یک راه این است که لایه به لایه request را از view پاس بدهیم تا به مکان مورد نظر ما برسد. البته این یک راه حل عمومی نخواهد بود و ممکن است در مواقعی ممکن نباشد.یک راه حل دیگر اما استفاده از middleware برای رسیدن به این منظور است، که شرح آن در پی خواهد آمد. ابتدا یک middleware به صورت زیر درست می کنیم:from threading import current_thread&#xA;&#xA;_requests = {}&#xA;&#xA;&#xA;def get_request():&#xA;    return _requests[current_thread()]&#xA;&#xA;&#xA;class GlobalRequestMiddleware(object):&#xA;    def __init__(self, get_response):&#xA;        self.get_response = get_response&#xA;&#xA;    def __call__(self, request):&#xA;        _requests[current_thread()] = request&#xA;        return self.get_response(request)در ابتدا نیاز داریم که request مان در thread فعلی را دریافت کنیم و برای همین هم به تابع current_thread را import کردیم. دلیل این کار این هست که وب سرور های جنگو ممکن است به صورت multithread باشند. thread های متعلق به یک process روی یک حافظه مشترک اجرا می شوند، فلذا باید مطمئن شویم request متعلق به thread فعلی را برمیگردانیم نه request دریافت شده توسط thread های دیگر را.(دستور runserver به طور پیشفرض multithread است و در gunicorn هم می توان این کار را کرد)سپس middleware مربوطه را در settings.py ثبت می کنیم(فرض کنید middleware در app بلاگ ساخته شده) :MIDDLEWARE = [&#xA;    &#39;django.middleware.security.SecurityMiddleware&#39;,&#xA;    &#39;django.contrib.sessions.middleware.SessionMiddleware&#39;,&#xA;    &#39;django.middleware.common.CommonMiddleware&#39;,&#xA;    &#39;django.middleware.csrf.CsrfViewMiddleware&#39;,&#xA;    &#39;django.contrib.auth.middleware.AuthenticationMiddleware&#39;,&#xA;    &#39;django.contrib.messages.middleware.MessageMiddleware&#39;,&#xA;    &#39;django.middleware.clickjacking.XFrameOptionsMiddleware&#39;,&#xA;   // our new middleware&#xA;    &#39;blog.middlewares.GlobalRequestMiddleware&#39;&#xA;    ]و حالا می توانیم request جاری را در بقیه بخش ها بدون این که نیاز به پاس دادن های اضافی request داشته باشیم، بگیریم. برای مثال فرض کنید می خواهیم یک property در مدل post داشته باشیم که آیا که آیا کاربر لاگین کرده ما، پست را ذخیره کرده یا نه. برای این کار در models.py و مدل post می نویسیم:from blog.middlewares import get_request&#xA;&#xA;class Post(models.Model):&#xA;// ...&#xA;&#xA;&#x9;@property&#xA;    def is_bookmarked(self):&#xA;        user = get_request().user&#xA;        if user.is_anonymous:&#xA;            return False&#xA;        return User.objects.filter(bookmarks=self).filter(id=user.id).exists()موفق باشید.</description><category>سجاد نصیری</category><author>سجاد نصیری</author><pubDate>Tue, 29 Aug 2023 17:23:05 +0330</pubDate></item><item><title>ساخت docker rgistry شخصی برای استفاده از داکر در ایران</title><link>https://virgool.ir/@sajjad.nasiri.brn/ساخت-docker-rgistry-شخصی-برای-استفاده-از-داکر-در-ایران-ubwhh360pkgi</link><description>همانطور که اطلاع دارید docker hub ایران را تحریم کرده و کسانی که از سرورهای داخل ایران استفاده می کنند مشکلات بسیاری برای استفاده از این سرویس دارند. بنابراین بهترین و حرفه ای ترین راه برای استفاده از داکر در ایران، ساخت یک registry شخصی برای docker است. البته اگر کاربر شرکتی نیستید یا پروژه ای معمولی دارید شاید مقاله تغییر dns های سرور برای استفاده از docker بیشتر برایتان فایده داشته باشد.برای راه اندازی local registry داکر یک image رسمی به نام registry دارد که از آن استفاده خواهیم کرد. همینطور در محیط production قویا توصیه می شود که از reverse proxy و ssl هم استفاده کنید. آموزش هایی که بود، یا به الزامات محیط production نپرداخته بودند و یا به صورت تمام داکری این کار را انجام نداده بودند. بهرحال، در این آموزش ما از nginx بعنوان reverse proxy استفاده خواهیم کرد و همه چیزهایی که نیاز داریم را در محیط docker پیاده سازی خواهیم کرد.مرحله 1 - نصب و پیکربندی nginx و sslدر ابتدا یک دایرکتوری به نام registry میسازیم که فایل ها و config های لازم آنجا ذخیره شوند:mkdir registry&#xA;cd registryسپس در دایرکتوری ایجاد شده نیاز داریم چند دایرکتوری دیگر نیز بسازیم. پس از ساخت در باره آنها توضیح خواهیم داد:mkdir auth cerbot data nginxauth: برای ذخیره سازی اطلاعات مربوط به اعتبار سنجی(username &amp; password) رجیستری شخصی مان از طریق nginx برای اینکه هر کسی از بیرون به آن دسترسی نداشته باشد.cerbot: برای ذخیره سازی کلیدهای گواهینامه ssl.data: محل ذخیره image های موجود در رجیستری ما خواهد بود.nginx: محل ذخیره داکر فایل و  کانفیگ nginx است.برای این که می خواهیم چند container داکر را به طور موازی بالا بیاوریم استفاده از comman line بسیار گیج کننده خواهد شد. بنابراین یک فایل docker-compose.yml ایجاد می کنیم:nano docker-compose.ymlسپس محتویات زیر را درون آن می نویسیم:version: &#34;3.9&#34;&#xA;services:&#xA;  nginx:&#xA;    build: ./nginx&#xA;    restart: always&#xA;    ports:&#xA;      - &#34;80:80&#34;&#xA;      - &#34;443:443&#34;&#xA;    volumes:&#xA;      - ./certbot/www:/var/www/certbot/:ro&#xA;      - ./certbot/conf/:/etc/nginx/ssl/:ro&#xA;      - ./auth:/auth&#xA;  registry:&#xA;    image: registry:latest&#xA;    ports:&#xA;    - &#34;5000:5000&#34;&#xA;    environment:&#xA;      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data&#xA;    volumes:&#xA;      - ./data:/data&#xA;&#xA;  certbot:&#xA;    image: certbot/certbot&#xA;    volumes:&#xA;      - ./certbot/www/:/var/www/certbot/:rw&#xA;      - ./certbot/conf/:/etc/letsencrypt/:rwفایل بالا نکته خاصی ندارد جز این که چند volume مشترک بین سرویس های nginx و cerbot برای اشتراک گذاری اطلاعات ssl ساختیم و در سرویس registry از طریق متغیر محیطی REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY گفتیم که image ها کجا ذخیره شوند و سپس یک volume به روش bind mount برای آن ساختیم. همه این volume ها را می توانستیم به صورت named volume نیز ایجاد کنیم ولی برای ساده ماندن کار آموزشی این کار را نکردیم.همچنین دایرکتوری nginx/. را برای ساختن image سرویس nginx معرفی کردیم. این دایرکتوری شامل یک Dockerfile و یک فایل nginx.conf می باشد. اکنون ما در دایرکتوری registry قرار داریم. فلذا به درون دایرکتوری nginx رفته و این فایل ها را ایجاد می کنیم:cd nginx&#xA;nano Dockerfileمحتویات Dockerfile:FROM nginx:1.23-alpine&#xA;&#xA;RUN rm /etc/nginx/conf.d/default.conf&#xA;COPY nginx.conf /etc/nginx/conf.dسپس به ساختن و پرداختن فایل کانفیگ می پردزایم:nano nginx.confمحتویات nginx.conf:server {&#xA;    listen 80;&#xA;    listen [::]:80;&#xA;&#xA;    server_name example.com;&#xA;    server_tokens off;&#xA;&#xA;    location /.well-known/acme-challenge/ {&#xA;        root /var/www/certbot;&#xA;    }&#xA;&#xA;    location / {&#xA;        return 301 https://example.com$request_uri;&#xA;    }&#xA;}توجه داشته باشید که example.com ها را در هر بار ظاهر شدن، با نام دامنه خود عوض کنید. در قسمت location /.well-known/acme-challenge/ گفتیم فایل های مورد نیاز cerbot را serve کن. اما در قسمت  location / گفتیم که درخواست های کاربر به پورت 80 را به همان route ولی پورت 443 ریدایرکت کن. ولی همانطور که ملاحظه می کنید هیچ کانفیگی برای پورت 443 ننوشته ایم. دلیل آن این است این پورت به برخی پیش نیازهایی برای ssl وابسته است که هنوز ایجاد نشده اند و اگر کسی هوس می کرد برای تست کاری که تا اینجا کردیم docker composer up را اجرا کند با خطا مواجه میشد ولی اکنون با خیال راحت می تواند تست کند. پس ابتدا کانتینرها را می سازیم:docker compose up -d --buildسپس پیش نیازهای ssl را ایجاد می کنیم:docker compose run --rm  certbot certonly --webroot --webroot-path /var/www/certbot/ -d example.comپس از این مرحله می توانیم به nginx.conf رفته و کانفیگ های مورد نیاز پورت 443 و همچنین هدایت ترافیک از این پورت به رجیستری داکرمان را بنویسیم. پس از این مرحله فایل nginx.conf چنین خواهد بود:upstream docker-registry {&#xA;  server registry:5000;&#xA;}&#xA;server {&#xA;    listen 80;&#xA;    listen [::]:80;&#xA;&#xA;    server_name panel.brnstudents.ir;&#xA;    server_tokens off;&#xA;&#xA;    location /.well-known/acme-challenge/ {&#xA;        root /var/www/certbot;&#xA;    }&#xA;&#xA;    location / {&#xA;        return 301 https://panel.brnstudents.ir$request_uri;&#xA;    }&#xA;}&#xA;server {&#xA;    listen 443 default_server ssl http2;&#xA;    listen [::]:443 ssl http2;&#xA;&#xA;    server_name example.com;&#xA;&#xA;    ssl_certificate /etc/nginx/ssl/live/example.com/fullchain.pem;&#xA;    ssl_certificate_key /etc/nginx/ssl/live/example.com/privkey.pem;&#xA;&#xA;&#x9;# disable any limits to avoid HTTP 413 for large image uploads&#xA;    client_max_body_size 0;&#xA;&#xA;    # required to avoid HTTP 411: see Issue #1486 (https://github.com/moby/moby/issues/1486)&#xA;    chunked_transfer_encoding on;&#xA;&#xA;    auth_basic &#34;Registry realm&#34;&#xA;    auth_basic_user_file /auth/nginx.htpasswd;&#xA;&#xA;&#xA;    location / {&#xA;   &#x9;  proxy_pass                          http://docker-registry;&#xA;      proxy_set_header  Host              $http_host;   # required for docker client&#39;s sake&#xA;      proxy_set_header  X-Real-IP         $remote_addr; # pass on real client&#39;s IP&#xA;      proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;&#xA;      proxy_set_header  X-Forwarded-Proto $scheme;&#xA;      proxy_read_timeout                  900;&#xA;    }&#xA;}خب، در ابتدا یک upstream docker-registry تعریف کردیم و به آن server registry:5000 را معرفی کردیم.(مشابه کاری که برای django و gunicorn می کردیم) در واقع این کار به ما کمک می کند که درخواست هایی که به nginx ما می آیند را به سرور معرفی شده که در اینجا کانتینر registry که ساختیم می باشد، پروکسی کنیم. در ادامه کانفیگ آن را به proxy_pass معرفی می کنیم. سپس یک سرور دیگر برای استفاده از پورت 443 ساختیم. چند خط اول برای تنظیمات مربوط به ssl هستند. دو خط بعدی نیز دارای توضیحات لازم هستند. اما auth_basic و auth_basic_user_file مربوط به اعتبار سنجی پروکسی nginx و رمز گذاری روی registry مان هستند. قبلا در docker-compose.yml برای auth یک volume تعریف کرده بودیم. این نحوه اعتبار سنجی را در مقاله رسمی وبسایت داکر Authenticate proxy with nginx دیدم و به نظر روش بهتری از سایر روشها می نماید بخصوص این که در document رسمی داکر هم به آن اشاره شده. در خطوط بعدی نیز ماژول upstream تعریف شده را به  location / به اصطلاح proxy_pass کردیم.حالا می توانید container ها را بسازید:docker compose up -d --buildمرحله بعدی تعیین username و password برای رجیستری است. با دستور زیر می توانید این کار را انجام دهید:docker run --rm --entrypoint htpasswd registry:2.7.0 -Bbn testuser testpassword &gt; auth/nginx.htpasswdtestuser و testpassword را با یوزرنیم و پسوورد مورد نظر خودتان عوض کنید. همچنین می بینید که ما از ایمیج registry:2.7.0 استفاده کردیم. دلیل این کار در واقع باگی در image رجیستری داکر است که فقط در tag 2.7.0 وجود ندارد! به هر حال جزییات بیشتر را می توانید در این لینک بخوانید.حالا باید کانتینر ها را restart کنیم:docker compose restartو حالا داکر رجیستری ما آماده است!مرحله 2 - push و pull کردن image ها در registryحالا که رجیستری ما ساخته شده، می خواهیم push و pull کردن از رجیستری را امتحان کنیم. اولین کاری که می کنیم login کردن با username و password ای است که پیشتر تعریف کردیم:docker login -u=testuser -p=testpassword myregistrydomain.comحالا فرض کنید می خواهیم image موسوم به hello-world داکر را که هنگام نصب برای اطمینان از نصب صحیح داکر run کردیم، به رجیستری مان push کنیم. برای این کار ابتدا با tag زدن به شکل زیر آن را به رجیستری خودمان منتقل می کنیم:docker tag hello-world localhost:5000/hello-world:v1حالا آن را با دستور زیر push می کنیم:docker push localhost:5000/hello-world:v1و تمام! حالا با رفت به آدرس زیر می تونیم لیست image های رجیستری مان را ببینیم:https://myregistrydomain.com/v2/_catalogتوجه داشته باشید که ما می توانیم تمام image هایی که داریم را به این شکل به registry مان push کنیم. حتی از سرورهای دیگر، فقط کافی است به رجیستری مان login کنیم. برای دیدن لیست image ها هم که می توانیم از docker image ls استفاده کنیم.برای pull کردن یک image هم دستور زیر را می زنیم:docker pull myregistrydomain.com/hello-world:v1توجه کنید همان tag ای که push کردید را می توانید pull کنید. که در این مثال v1 بود.موفق باشید.</description><category>سجاد نصیری</category><author>سجاد نصیری</author><pubDate>Tue, 29 Aug 2023 17:22:08 +0330</pubDate></item><item><title>تغییر dns های سرور برای استفاده از docker در شرایط تحریم</title><link>https://virgool.ir/@sajjad.nasiri.brn/تغییر-dns-های-سرور-برای-استفاده-از-docker-در-شرایط-تحریم-atuc8xuhkier</link><description>در حالی که تحریم docker hub مشکلاتی را برای سرورهای داخلی در استفاد از این سرویس ایجاد کرده، در کنار این تحریم ها محدودیت های شدید اینترنت در ایران امکان استفاده از proxy برای دور زدن این تحریم ها را عملا غیر ممکن کرده و مشکلات استفاده از docker را مضاعف کرده است.بنابراین یکی از معدود راه های استفاده از docker hub تغییر dns می باشد. برای پروژه های کوچک و شخصی می توان برای دور زدن تحریم های داکر از روش تغییر dns استفاده کرد. برای پروژه های بزرگتر و شرکتی اما بهتر است یک رجیستری شخصی بسازیم که در مقاله بعدی به تفصیل به آن خواهیم پرداخت. در این آموزش ما از smart dns شرکت شکن استفاده می کنیم. برای تغییر dns های سرور ubuntu به dns های شکن، طبق راه حلی که در این لینک هست باید مطابق مراحل زیر عمل کنیم:ابتدا باید resolve conf را نصب کنیم:sudo apt update&#xA;sudo apt install resolvconf2. چک میکنیم که آیا سرویس resolveconf اجرا شده یا نه:sudo systemctl status resolvconf.service3. اگر اجرا نشده بود دستورات زیر را وارد می کنیم تا اجرا بشود:sudo systemctl start resolvconf.service&#xA;sudo systemctl enable resolvconf.service4. حالا باید فایل /etc/resolvconf/resolv.conf.d/head را ویرایش کنیم:sudo nano /etc/resolvconf/resolv.conf.d/headبه این فایل 2 خط که dns های شکن هستند را اضافه می کنیم:nameserver 178.22.122.100&#xA;nameserver 185.51.200.25. حالا باید به resolveconf گفته شود که این آپدیت را اجرا کن و سپس آپدیت کنیم:sudo resolvconf --enable-updates&#xA;sudo resolvconf -uحالا اگر دستور زیر را بزنید باید dns هایی که وارد کردید را ببینید:cat /etc/resolv.confاگر این کار با موفقیت انجام دهید می توانید بدون محدودیت به سرویس docker hub متصل شوید. یعنی اگه دستور زیر را زدید بدون خطا hello from docker را خواهید دید:sudo docker run hello-worldموفق باشید.</description><category>سجاد نصیری</category><author>سجاد نصیری</author><pubDate>Tue, 29 Aug 2023 17:21:30 +0330</pubDate></item><item><title>dockerize کردن برنامه django با postgres، gunicorn و nginx</title><link>https://virgool.ir/@sajjad.nasiri.brn/dockerize-کردن-برنامه-django-با-postgres-gunicorn-و-nginx-k1tbvvm9blou</link><description>اگر برنامه django خود را نوشته اید و حالا می خواهید آن را به آسان ترین، بهینه ترین و تمیزترین شکل، روی سرور مجازی deploy کنید، این مقاله برای شماست!پیش از هر چیزی باید داکر را روی vps نصب کرده باشید که این، لینک آموزش نصب داکر است. در قدم بعدی اگر سرور ایران دارید باید به طریق تحریم های مروبط به docker hub را رفع کنید. ما تا کنون به 2 روش برای این کار پرداخته ایم:تغییر dns های سرور برای استفاده از docker در شرایط تحریمساخت docker rgistry شخصی برای استفاده از داکر در ایرانپس از این می توانیم سراغ کار اصلی برویم. در ابتدا لازم به ذکر است ساختار استاندارد دایرکتوری ها یا با اصطلاح best practice برای deployment در اکثر پروژهای حرفه ای که دیده ام به این شکل است:├── .env.prod&#xA;├── .env.prod.db&#xA;├── .gitignore&#xA;├── core&#xA;│   ├── src&#xA;│   │   ├── __init__.py&#xA;│   │   ├── asgi.py&#xA;│   │   ├── settings.py&#xA;│   │   ├── urls.py&#xA;│   │   └── wsgi.py&#xA;│   ├── manage.py&#xA;&#xA;├── Dockerfile&#xA;├── docker-compose.yml&#xA;├── README.md&#xA;&#xA;├── requirement.txt&#xA;└── nginx&#xA;    ├── Dockerfile&#xA;    └── nginx.confطبق این ساختار کد های ما و ماژول های برنامه جنگویی ما درون دایرکتوری ای برای مثال به نام core قرار گرفته و فایل های مربوط به deployment و مسائل devops از کدها جدا شده و در دایرکتوری والد قرار می گیرند. گمان می کنم دلیل این کار این باشد که اشاره به دایرکتوری های فرزند از درون دایرکتوری والد کاری راحت است اما اشاره به دایرکتوری های پیشین از درون دایرکتوری های فرزند کاری پر دردسر است و طبق تجربه شخصی در فایل های docker-compose با خطا مواجه می شویم. علاوه بر این که جدا کردن فایل های development و devops از نظر منطق repository کار درستی است.بهر حال اگر ساختار دیگری جز این را مدنظر دارید با کمی تغییر در آدرس دهی های نسبی، می توانید به آن برسید. در این آموزش اما همین ساختار را در نظر می گیریم.ساخت Dockerfile هاما به 2 داکرفایل نیاز داریم. یکی برای nginx و دیگری برای python و نصب وابستگی های پایتونی که در فایل requirements.txt هستند. ابتدا به Dockerfile مربوط به سرویس nginx  می پردازیم:FROM nginx:1.23-alpine&#xA;&#xA;RUN rm /etc/nginx/conf.d/default.conf&#xA;COPY nginx.conf /etc/nginx/conf.dدر ابتدا نسخه آلپاین image مربوط به nginx را بعنوان image پایه این Dockerfile معرفی کردیم و سپس کانفیگ پیش فرض را پاک کرده و کانفیگ خودمان به نام nginx.conf را درون دایرکتوری conf.d کپی کردیم. لازم به ذکر است که هر فایل .conf که درون این دایرکتوری باشد، به طور خودکار توسط nginx بعنوان فایل کانفیگ در نظر گرفته خواهد شد.حالا ببینیم محتویات این فایل کانفیک چه باید باشد. در باره فایل nginx.conf بعدتر توضیح خواهیم داد.حالا نوبت به Dockerfile پایتون می رسد. پیش از توضیح این داکرفایل لازم به ذکر است که برای کاهش حجم نهایی image ساخته شده از روش multi stage build استفاده کرده ایم. اگر تجربه front-end داشته باشید یا با npm آشنا باشید می توانید برای درک بهتر این روش آن را همانند موقعی فرض کنید که دستور npm run production را می زنیم و فایل ها و وابستگی های مورد نیاز ما ساخته شده و دیگر نیازی به پوشه حجیم node module نخواهیم داشت. فلذا از دیدگاه داکری می توانیم بگوییم در این روش کاری می کنیم که فقط binary file های مورد نیازمان را در ایمیج django که خواهیم ساخت، داشته باشیم. در ادامه بیشتر توضیح خواهیم داد. فایل Dockerfile موجود در root پروژه:###########&#xA;# BUILDER #&#xA;###########&#xA;&#xA;# pull official base image&#xA;FROM python:3.10-alpine as builder&#xA;&#xA;# set work directory&#xA;WORKDIR /usr/src/app&#xA;&#xA;# set environment variables&#xA;ENV PYTHONDONTWRITEBYTECODE 1&#xA;ENV PYTHONUNBUFFERED 1&#xA;&#xA;# install psycopg2 dependencies&#xA;RUN apk update \&#xA;    &amp;&amp; apk add postgresql-dev gcc python3-dev musl-dev nano&#xA;&#xA;# lint&#xA;RUN pip install --upgrade pip&#xA;#RUN pip install flake8==3.9.2&#xA;COPY ./core .&#xA;#RUN flake8 --ignore=E501,F401 .&#xA;&#xA;# install dependencies&#xA;COPY ./requirements.txt .&#xA;RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt&#xA;&#xA;#########&#xA;# FINAL #&#xA;#########&#xA;&#xA;# pull official base image&#xA;FROM python:3.10-alpine&#xA;&#xA;# create directory for the app user&#xA;RUN mkdir -p /home/app&#xA;&#xA;# create the app user&#xA;RUN addgroup -S --gid 1000 app &amp;&amp; adduser -S app --uid 1000 -G app&#xA;&#xA;# create the appropriate directories&#xA;ENV HOME=/home/app&#xA;ENV APP_HOME=/home/app/web&#xA;RUN mkdir $APP_HOME&#xA;WORKDIR $APP_HOME&#xA;&#xA;# install dependencies&#xA;RUN apk update &amp;&amp; apk add libpq&#xA;COPY --from=builder /usr/src/app/wheels /wheels&#xA;COPY --from=builder /usr/src/app/requirements.txt .&#xA;RUN pip install --no-cache /wheels/*&#xA;&#xA;# copy project&#xA;COPY ./core $APP_HOME&#xA;&#xA;# chown all the files to the app user&#xA;RUN chown -R app:app $APP_HOME&#xA;&#xA;#RUN python ./manage.py collectstatic&#xA;&#xA;# copy entrypoint.sh&#xA;COPY core/entrypoint.sh /home/app/web&#xA;RUN sed -i &#39;s/\r$//g&#39;  $APP_HOME/entrypoint.sh &amp; chmod o+x  $APP_HOME/entrypoint.sh&#xA;&#xA;# change to the app user&#xA;USER app&#xA;&#xA;# run entrypoint.sh&#xA;#ENTRYPOINT [&#34;/home/app/web/entrypoint.sh&#34;]پیش از هر توضیحی لازم است بگوییم برخی قسمت ها برای حفظ سادگی آموزش کامنت شده اند و اگر به این موارد نیاز داشتید کافی است آن ها را uncomment کنید.خب، در فایل بالا پیش از هر دستور کامنتی گذاشته شده و بسیاری از موارد نیز مربوط به دستورات سیستم عامل لینکوکس می باشند که شاید توضیح آنها چندان ربطی به این مقاله نداشته باشد. همچنین می توانید با کمک chat gpt اطلاعات بیشتری از این دستورات و آپشن ها به دست بیاورید. ولی بعنوان نکته ای مهم که ممکن است برای برخی مشکل ساز بشود، دقت کنید که چون به دلایل امنیتی نمی خواهیم کاربر root را به کانتینر اختصاص دهیم، فلذا ابتدا با دستور زیر یک گروه دسترسی و یک کاربر با id های 1000 ساختیم و آن گروه را به کاربر اختصاص دادیم. سپس در ادامه کاربر و گروه تمام فایل های پروژه را به کاربر و گروه app که ساخته ایم، تغییر دادیم:RUN addgroup -S --gid 1000 app &amp;&amp; adduser -S app --uid 1000 -G app...RUN chown -R app:app $APP_HOMEاما در صورتی که با مسئولیت خودتان، میخواستید امکان فعالیت کاربر بعنوان root درون کانتینر وجود داشته باشد می توانستید به جای موارد بالا از این دستور استفاده کنید:USER rootهمچنین فیل entrypoint.sh که اجرای آن را کامنت کرده ایم، مشخص می کند که اجرای کانتینر با چه فرآیندی شروع شود. برای مثال اگر می خواستیم این کانتینر همراه با تست سالم بودن کانکشن postgres مان شروع شود، ابتدا ENTRYPOINT [&#34;/home/app/web/entrypoint.sh&#34;] را از کامنت در می آوردیم و سپس برای مثال فایلی به نام entrypoint.sh با محتویات زیر می ساختیم:#!/usr/bin/env sh&#xA;&#xA;if [ &#34;$DATABASE&#34; = &#34;postgres&#34; ]&#xA;then&#xA;    echo &#34;Waiting for postgres...&#34;&#xA;&#xA;    while ! nc -z $SQL_HOST $SQL_PORT; do&#xA;      sleep 0.1&#xA;    done&#xA;&#xA;    echo &#34;PostgreSQL started&#34;&#xA;fi&#xA;&#xA;exec &#34;$@&#34;ساخت docker-composeهمانطور که می دانید برای ساخته شدن و بالا آوردن چند کانتینر در داکر باید از docker compose استفاده کنیم. برای این کار یک فایل به نام docker-compose.yml در پوشه اصلی پروژه ایجاد می کنیم و محتویات زیر را درون آن می نویسیم:version: &#34;3.9&#34;&#xA;&#xA;services:&#xA;  backend:&#xA;    build: .&#xA;    container_name: backend&#xA;    command: gunicorn src.wsgi:application --bind 0.0.0.0:8000&#xA;    restart: always&#xA;    volumes:&#xA;      - ./core/:/home/app/web/&#xA;      - ./core/static:/home/app/web/static&#xA;      - ./core/media:/home/app/web/media&#xA;    expose:&#xA;      - &#34;8000&#34;&#xA;    env_file:&#xA;      - ./.env.prod&#xA;    depends_on:&#xA;      - db&#xA;      - redis&#xA;&#xA;  db:&#xA;    image: postgres:15-alpine&#xA;    restart: always&#xA;    volumes:&#xA;      - postgres_data:/var/lib/postgresql/data/&#xA;    env_file:&#xA;      - ./.env.prod.db&#xA;&#xA;  redis:&#xA;    container_name: redis&#xA;    image: redis&#xA;    restart: always&#xA;    ports:&#xA;      - &#34;6379:6379&#34;&#xA;    command: redis-server --appendonly yes --replica-read-only no --save 60 1 --loglevel warning&#xA;&#xA;  nginx:&#xA;    build: ./nginx&#xA;    restart: always&#xA;    ports:&#xA;      - &#34;80:80&#34;&#xA;    volumes:&#xA;      - ./core/static:/home/app/web/static&#xA;      - ./core/media:/home/app/web/media&#xA;    depends_on:&#xA;      - redis&#xA;      - backendدر فایل بالا ما چند کانتینر ایجاد کردیم. backend که همان پروژه جنگوی ماست و براساس Dockerfile که پیشتر توضیح دادیم روی پورت 8000 به اصطلاح expose می شود. یعنی پورت 8000 هاست یا سرور ما به پورت 8000 کانتینتر تعلق میگیرد. gunicorn یک سرور اپلیکیشن wsgi است که بعنوان واسط بین اپلیکیشن جنگوی ما و nginx بعنوان وب سرورمان قرار می گیرد. به بیان دقیق‌تر، بخش سرور،‌ یک شي قابل صدا زدن (callable) را که بخش نرم‌افزار فراهم کرده است، صدا می‌زند. پس باید فایلی داشته باشیم که این شي درون آن قرار دارد. نام این فایل معمولا wsgi.py است. در این جنگو نیز چنین است. یک فایل با اسم wsgi.py در تمام پروژه‌های جنگو قرار دارد و نام شي موردنظر نیز application می‌باشد. حال gunicorn چه می‌کند؟ gunicorn با صدا زدن این شي، هرکاری که برای ایجاد ارتباط بین پروژه و وب‌سرور نیاز باشد را بر عهده می‌گیرد و این ارتباط را ممکن می‌سازد.ما در فایل بالا gunicorn را روی پورت 8000 فعال کردیم و بعدتر در کانفیگ nginx به آن گوش خواهیم داد. در بخش volumes نیز volume هایی را برای دائمی کردن داده ها و فایل های کانتینر تعریف کردیم تا پس از هر بار reset یا down شدن، داده های مهم ما از بین نروند. این قضیه بخصوص در باره دیتابیس اهمیت می یابد.همچنین در پوشه اصلی پروژه یک فایل به نام env.prod. ایجاد کرده و بعنوان متغیرهای محیطی لینوکسی که روی کانتینر در حال اجراست معرفی کردیم. این کار به ما کمک می کند بعدتر در فایل setting.py از این متغیرهای محیطی استفاده کنیم و تنظیمات development و production را از طریق فایل های env. مختلف جدا کنیم. مشاهده یک نمونه از محتویات این فایل کمک کننده خواهد بود:DEBUG=0&#xA;SECRET_KEY=...&#xA;DJANGO_ALLOWED_HOSTS=example.ir www.example.ir 188.121.102.211&#xA;SIT_ID=4&#xA;CSRF_TRUSTED_ORIGINS=https://*.example.ir https://example.ir&#xA;SQL_ENGINE=django.db.backends.postgresql&#xA;SQL_DATABASE=my_djano_db&#xA;SQL_USER=my_djano_user&#xA;SQL_PASSWORD=1234&#xA;SQL_HOST=db  #*service name in docker compose&#xA;SQL_PORT=5432 #posegresdefault port&#xA;DATABASE=postgres&#xA;REDIS_HOST=redis://redis:6379/1یک نکته بسیار مهم درباره فایل های .env که برای نگارنده این سطور نیز مسائل مختلفی را ایجاد کرده این است که در متغیر های مربوط به host دیتابیس حتما باید نام سرویسی که در doceker-compose.yml به سرویس دیتابیس اختصاص دادیم را قرار دهیم. نه نام یا id کانتینر ها را. چرا که id کانتینر ها هر چند مدت به طور خودکار تغییر پیدا می کند و در این صورت کانکشن دیتابیس ما به طورکلی از بین خواهد رفت چرا که به چیزی ارجاع داده شده بود که دیگر وجود ندارد!همچنین به سرویس db نیز یک volume و یک فایل .env اختصاص دادیم. نمونه فایل .env دیتابیس:POSTGRES_USER=my_djano_user&#xA;POSTGRES_PASSWORD=1234&#xA;POSTGRES_DB=my_djano_dbاین متغیرها بر اساس image رسمی داکر برای دیتابیس postgres در داکرهاب مقدار دهی شده اند. برای هر دیتابیس دیگری، مثلا mysql چنین توضیحاتی وجود دارد که باید بر اساس نیاز به آن رجوع کنید.در باره سرویس redis که بخصوص بعنوان cache یا broker مربوط به celery استفاده می شود، برخی آپشن ها را به کار برده ایم که نیازی به توضیح نیست و می توانید با سرچ کردن متوجه شوید که چه هستند. صرفا هدف این بود که با نحوه اعمال این آپشن ها آشنا شوید.و اما یکی از مهمترین قسمت های این فایل، سرویس nginx می باشد که در واقع وب سرور ماست. همانطور که مشاهده می کنید ما این سرویس را روی پورت 80 که پورت پیشفرض سرور است expose کرده ایم تا کاربر بدون وارد کردن پورت در دامنه به وبسایت ما دسترسی داشته باشد. همچنین گفته ایم که از Dockerfile موجود در پوشه nginx برای ساخت این image استفاده شود.نکته مهم در باره سرویس nginx این است که فایل های static  و media پروژه ما در نهایت باید توسط وب سرور nginx به اصطلاح serve شوند. بنابراین باید volume مربوط به این 2 دایرکتوری بین سرویس backend و nginx مشترک باشد.در فایل docker-compose.yml بالا این آدرس ها بدین شکل می باشند:- ./core/static:/home/app/web/static&#xA;- ./core/media:/home/app/web/mediaمی توانستیم از named volume ها نیز استفاده کنیم و اتفاقا روش بهتری نیز بود! اما در اینجا برای سادگی از روش bind mount استفاده کردیم. در صورتی که کنجکاو هستید بد نیست در باره کلیدواژه های عنوان شده یک جست و جوی مختصر انجام دهید.کانفیگ nginxمرحله آخر کار، کانفیگ کردن فایل nginx.conf می باشد. بدین منظور چنین فایلی را با محتویات زیر در دایرکتوری nginx می سازیم:upstream my-django-app {&#xA;    server backend:8000;&#xA;}&#xA;&#xA;server {&#xA;   server_name example.ir;&#xA;    listen 80;&#xA;&#xA;    location /static/ {&#xA;      alias /home/app/web/static/;&#xA;    }&#xA;&#xA;    location /media/ {&#xA;      alias /home/app/web/media/;&#xA;    }&#xA;&#xA;    location / {&#xA;        proxy_pass http://my-django-app;&#xA;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&#xA;        proxy_set_header Host $host;&#xA;        proxy_redirect off;&#xA;    }&#xA;&#xA;}در ابتدا یک upstream تعیین کردیم و پورتی که gunicorn روی آن اجرا می شد را به آن معرفی کردیم. &#34;upstream&#34; مشخص می‌کند که درخواست‌های کاربر به چه سروری منتقل شود. در این مثال تمامی requestهای لوکیشینی که با ‍‍/‍‍ تمام می‌شود را به proxy_pass که مقدارش http://my-django-app است می‌فرستد و از طریق upstream که به نام my-django-app تعریفش کردیم کنترل می‌شود.همچنین لازم به ذکر است که بلاک location ریکوئست ها به سرور را از طریق مسیرشان گروه بندی می کند. پس 2 لوکیشن هم برای serve کردن فایل های static  و media تعریف میکنیم:location /static/ {&#xA;  alias /home/app/web/static/;&#xA;}&#xA;&#xA;location /media/ {&#xA;  alias /home/app/web/media/;&#xA;}در این کانفیگ ما ساده ترین حالت ممکن را انجام داده ایم، حال آنکه بسیاری کارهای دیگر نیز می توان و می بایست انجام می شدند. از جمله گواهی ssl نیز نیاز است. همچنین می توان از nginx به عنوان لودبالانسر نیز استفاده کرد که البته این مباحث این نوشتار را بیش از حد طولانی خواهند کرد.در آینده ممکن است به طور مفصل و اختصاصی تری به مباحث مربوط به nginx بپردازیم. برای چگونگی دریافت گواهینامه ssl اما می توانید به این مقاله رجوع کنید.در نهایت با زدن دستور docker compose up -d --build در پوشه اصلی پروژه، می توانیم کانتیرها را ساخته و سرویس هایمان را اجرا کنیم. برای اطمینان از درست اجرا شدن docker compose می توانید با زدن دستور زیر از وضعیت سرویس ها مطلع شوید:docker compose psهمچنین در صورت وجود هر گونه مشکلی با دستور زیر و مطالعه 100 خط پایانی فایل log از آن مشکل آگاه شویم:docker compose logs -n 100امیدوارم که این مطلب مفیده بوده باشد.</description><category>سجاد نصیری</category><author>سجاد نصیری</author><pubDate>Tue, 29 Aug 2023 17:20:22 +0330</pubDate></item></channel></rss>