قراره تو این نوشته راجع به این صحبت کنیم که مشکل چی بوده که تکنولوژی مثل داکر بوجود اومده و چرا ما نیاز داریم ازش استفاده کنیم. شاید خیلی از ماها از داکر استفاده می کنیم بدون اینکه جواب این «چرا؟» رو کامل بدونیم. برای اینکه بتونیم درک عمیق تری از تکنولوژی ها داشته باشیم بهتره اول بریم سراغ اینکه بفهمیم مشکل چی بوده که یه همچین تکنولوژی بوجود اومده؟ اونوقت می تونیم درک بهتری از مفاهیم و قابلیت های اون تکنولوژی داشته باشیم و بهتر ازشون استفاده کنیم.
دلیل استفاده از داکر زیاده ولی ما می خواییم راجع به قابل لمس ترینش حرف بزنیم تا برای همه با هر سطحی قابل درک باشه. خب بیایین با این مثال شروع کنیم که شما یک برنامه کامپیوتری رو توسعه دادید و میخوایین اون رو تو یک محیط متفاوت، مثل سرور production اجرا کنید. با این حال، همیشه احتمال وجود یه سری مشکلات و عدم سازگاری در محیطهای مختلف هست. منظورم اینه که نرم افزارمون روی سیستم یا سرور دیگه درست کار نکنه و مجبور باشیم روی این سیستم مقصد تغییراتی ایجاد کنم (مثل نصب کردن یه سری library). به عبارت دیگه، این برنامه یا نرم افزار که نوشتیم قابل حمل یا portable نیست.
احتمالا براتون اتفاق افتاده که موقع نصب یه نرم افزار به ارور های مختلف خوردین و مجبور شدین یه سری چیزای دیگه رو روی سیستم تون نصب کنین تا بتونین در نهایت نرمافزار رو اجرا کنین، درسته؟ یه نگاه به تصویر زیر بندازین که چرخه نصب یه نرمافزار رو نشون میده:
این تصویر نشون میده ما چه فرایندی رو طی می کنیم تا یه نرمافزار رو روی سیستممون اجرا یا نصب کنیم. بدین صورت پیش میاد که وقتی یه نرمافزاری رو اجرا می کنیم به یک خطایی می خوریم که مثلا پایتون۳ روی سیستم نصب نیست، یه خطایی مثل این:
Error: Python 3 required to run this program.
برای حلش خب اگه ندونیم پایتون چیه شروع می کنیم تو گوگل سرچ کردن راجع به ارور (Throubleshoot Issue) بعد پایتون رو نصب می کنیم و دوباره سعی می کنیم برنامه رو اجرا کنیم (Rerun Program)، بعد اجرا کردن دوباره بازم این احتمال وجود داره که به یه ارور جدید بخوریم (New Error) و مجبور میشیم دوباره بریم سراغ اینکه این مشکل رو هم حل کنیم (Throubleshoot Issue) و دوباره برنامه رو اجرا کنیم به امید اینکه اینبار درست اجرا بشه! این چرخه ممکنه چندین بار تکرار بشه تا ما نهایتا بتونیم برنامه رو اجرا کنیم که میتونه خیلی زمان بر و سخت هم باشه...
بعضی وقتا این مشکلات پیچیده تر از صرفا نصب کردن پایتون میتونن باشن، یا حتی قابل حل شدن هم نباشن، مثلا نرم افزار از یه نسخه خاصی از یک کتابخونه استفاده کنه که شما دسترسی بهش ندارین، یا بدتر از اون اصلا نرمافزار با نسخه سیستمعامل شما مشکل داشته باشه! که به طور خیلی کلی میشه گفت این نرم افزار قابل حمل نیس! خب حالا بیاییم راجع به این صحبت کنیم که به چه نرمافزاری قابل حمل میگن؟
به چه نرم افزاری نرم افزار قابل حمل میگن؟ بطور کلی به نرم افزاری قابل حمل میگیم که خودش به همراه همهی dependencyهاش یه جا پکیج شده باشه و بتونه رو هر ماشین مقصدی صرفه نظر از اینکه چه کانفیگی داره (چه کتابخونه ها و نرم افزارهایی روش نصبه و...) اجرا بشه. خب حالا که به یه تعریفی از نرم افزار قابل حمل رسیدیم، بگیم منظور از dependencyهای یک نرم افزار چیه؟
بیاییم پیچیدهش نکنیم و یه تعریف کلی از dependencyها داشته باشیم. به طور کلی به هر چیزی که یک نرم افزار برای اجرا یا نصب شدن روی یک ماشین نیاز داره اصطلاحا بهش می گیم dependency اون نرمافزار. این dependencyها رو هم می تونیم به دو دسته کلی تقسیم کنیم:
همونطور که از اسم این dependencyها مشخصه، Runtime Dependencies به دپندنسی هایی گفته میشه که نرم افزار که یک نرمافزار زمان اجرا بهشون نیاز داره مثل:
دپندنسیهای زمان کامپایل (Compile-time dependencies) هم اشاره به نیازمندی هایی داره که یک نرم افزار برای کامپایل شدن بهشون نیاز داره، مثل:
نکته: اگه با این مواردی که مثال زدم آشنا نیستید مهم نیست، چون موضوع بحث نیستن، تا همینجا کافی که دپندنسیها به این دو دسته کلی می تونن تقسیم بشن و نرم افزار برای اینکه بتونه درست روی ماشین مقصد کار کنه بهشون نیاز داره.
پس وقتی میگیم یه نرمافزار قابلحمل هست یعنی این dependencyها در کنار خود نرم افزار در یک پکیج قرار گرفتن و نیازی نیست که ماشین مقصد از قبل این دپندنسیها رو تو خودش داشته باشه، و ما صرفا کافیه که این پکیج رو کپی کنیم روی ماشین مقصد و برنامه رو اجرا کنیم.
اینطوری دیگه نیازی نیس نگران این باشیم که آیا dependencyهای نرمافزار روی ماشین مقصد نصب شده یا نه و می تونیم مطعمن باشیم که برنامه بدون مشکل اجرا میشه.
تا اینجا راجع به اینکه نرمافزار قابلحمل چیه صحبت کردیم و متوجه شدیم که نرمافزار برای اجرا شدن نیازه داره dependencyهاش روی ماشین مقصد فراهم شده باشه، که گفتیم برای قابلحمل کردن نرمافزار باید نرمافزار رو به همراه دپندنسیهاش در یک پکیج قرار بدیم و برای اجرا به ماشین مقصد منتقل کنیم. خب این همون مسئلهای هست که ما نیاز به یه ابزار یا راهحلی داریم که بتونه این پکیج رو ایجاد کنه.
پس برای این منظور یعنی قابلحمل کردن نرمافزارها تکنولوژیهایی مثل داکر ایجاد شدند تا بتونیم به کمکشون نرمافزارهای قابلحمل ایجاد کنیم. داکر از دو مفهوم به نامهای Docker Image برای ایجاد پکیج نرمافزار و Container برای اجرای نرمافزار در ماشین مقصد استفاده می کنه که در ادامه راجع بهشون صحبت می کنیم.
داکر ایمیج (Docker Image)، در واقع یک فایل آرشیو شده و قابل اجراست که شامل همهی دپندنسیهای لازم برای اجرای یک برنامه یا نرمافزار هست. یعنی شامل خود کد برنامه، کتابخانههای مورد نیاز و Environment Variablesها هست.
نکته مهم اینکه حتی سیستم عامل هم به عنوان یکی از دپندنسی های نرمافزار به ایمیج داکر اضافه میشه، چون می دونیم که بهرحال هر نرمافزاری باید در بستر یه سیستمعامل اجرا بشه تا بتونه از منابع سیستم استفاده کنه. پس وقتی ما برنامه یا نرمافزارمون رو توی یک ایمیج داکر بستهبندی میکنیم حتی مهم نیس که سیستمعامل ماشین مقصد چیه چون ما حتی سیستمعامل مورد نیاز نرمافزارمون رو کنار خود نرمافزارمون بستهبندی کردیم.
تصویر زیر یه نمونه از ایمیج داکر رو نشون میده که دپندنسیهای برنامه، شامل سیستمعامل، NodeJS و خود برنامه س.
خب تا اینجا گفتیم Docker Image یک فایله که شامل نرمافزار و دپندسیهاش میشه، حالا سوال اینجاست که داکر از کجا می دونه دپندنسیهای این نرمافزار چیه که توی Image قرارشون بده؟ اینجاست که پای Dockerfile به وسط میاد.
جواب این سوال اینه که ما به عنوان سازنده این نرمافزار با استفاده از فایلی به نام Dockerfile مشخص میکنیم که دپندنسیهای این نرمافزار چیه و چه چیزهایی باید در Docker Image بسته بندی بشه. مثلاً، ممکن است تعیین کنیم که چه کتابخانههایی نیازه، یا کدام فایل باید به عنوان فایل اجرایی نرمافزار استفاده بشه، و غیره.
حلا به عنوان مثال بیاییم برای مثالی که تو تصویر بالا آورده شده یک ایمیج داکر ایجاد کنیم. گفتیم برای ایجاد Docker Image باید تو Dockerfile مشخص کنیم که این Image شامل چه چیزهایی میشه، از اونجایی که تو مثال بالا ما یه برنامه NodeJS داریم Image باید شامل:
بنابراین Dockerfile به صورت زیر باید تعریف بشه (برای ایجاد داکرفایل یه فایل با کمک text editorتون مثل vscode با نام Dockerfile بدون پسوند ایجاد کنید و کدهای زیر رو توش بنویسید و زخیره کنید):
در این فایل تو خط اول مشخص کردیم میخواییم برنامهمون رو بستر سیستمعامل alpine اجرا بشه و با استفاده از دستور RUN دپندنسی برنامه که nodejs هست رو نصب کردیم، با استفاده از دستور COPY فایلهای خود برنامهمون رو توی ایمیج کپی کردیم و در نهایت با استفاده از دستور CMD مشخص کردیم برنامهمون از کجا و چطوری باید اجرا بشه.
نکته: توضیحاتی که راجع به دستورات داکرفایل دادم خیلی کلی هستن چون موضوع بحث این نوشته نیستن، ولی تو نوشته بعدی به طور مفصل راجع بهشون حرف میزنیم و به جزيیات می پردازیم.
خود برنامه هم تو فایل index.js نوشته شده که صرفا فقط یه چیزی روی کنسل چاپ می کنه:
خب، اینطوری مشخص کردیم که Docker Image یا پکیج نرمافزارمون میخواییم شامل چه چیزهایی باشه. حالا وقت اینه که ایمیج داکرمون رو ایجاد کنیم، برای این دستور کافیه توی ترمینال بنویسیم:
docker build . -t my-program
همونطور که حدس زدید، این دستور یه ایمیج داکر با نام my-program ایجاد کرده که شامل چیزهایی هست که تو Dockerfile مشخص کردیم. برای اینکار از دستور docker build استفاده کردیم که خیلی کوتاه شامل آپشن های زیر بود:
دستور build:
docker build .
که در اینجا . (dot) مشخص میکنه که Dockerfile کجاست و dot به معنی current directory هست (یعنی همونایی که داریم دستور رو اجرا میکنیم).
آپشن t-
-t my-program
که باهاش مشخص می کنیم اسم image که میخواییم بسازیم چیه.
برای اینکه مطعمن بشیم که imageمون ساخته شده یا نه، میتونیم از دستور زیر استفاده کنیم تا لیست همهی ایمیج های ایجاد شده رو ببینیم:
docker images
همونطور که تو عکس مشخصه یه ایمیج جدید با نام my-program ایجاد شده.
نکته: بیشتر از این نمیخواییم راجع به دستورات docker اینجا صحبت کنیم چون از موضوع صحبت خارج میشه، و بعدا تو یه نوشته دیگه با جزییات بیشتر راجع به این دستورات صحبت میکنیم.
خب تا اینجا ما برنامهمون رو توی docker image بستهبندی کردیم و حالا وقت اینه که برنامه مون رو اجرا کنیم، برای اینکار داکر از مفهومی به نام container استفاده میکنه که در ادامه راجع بهش حرف می زنیم.
بطور خیلی کلی کانتینر یه نمونه اجرا شده از امیج داکر (Docker Image) یا حتی می تونیم بگیم یه process در حال اجراس که ویژگی منحصربهفردش اینه که کاملا ایزوله شدس. بیاییم نریم تو جزییات و پیچیدش نکنیم و اینطوری درنظر بگیریم که وقتی میگیم کانتینر یعنی داریم راجع به یه پروسس در حال اجرا که ایزوله شدس صحبت می کنیم.
خب تا اینجا ایمیج داکر رو ساختیم و الان وقت اینه که با ایجاد یه container اجراش کنیم، برای این کار از دستور زیر استفاده میکنیم:
docker run my-program
همینطور که میبینید برنامه با موفقیت اجرا شده و Hello رو در کنسول چاپ کرد.
اگه میخوایین بیشتر راجع به کانتینرها بدونین، پیشنهاد میکنم این نوشته (پشت پرده کانتینرهای داکر) رو بخونید، تو این نوشته توضیح دادم که داکر چطوری با استفاده از کانتینرها، برنامه(ایمیج) رو بصورت ایزوله شده اجرا میکنه.
خب اگه توجه کرده باشید، ما ایمیج داکر رو روی یک سیستم ایجاد و روی همون سیستم اجراش کردیم ولی گفتیم هدف اینه که با ایجاد ایمیج داکر برنامه های قابل حمل ایجاد کنیم، حالا سوال اینجاس که چطوری میتونیم فایل ایمیج رو به ماشین مقصد منتقل و اجرا کنیم؟
برای اینکه این نوشته طولانی نشه، این موضوع رو توی نوشته بعدی بررسی میکنیم که بتونیم هم با جزيیات بیشتری صحبت و هم این نوشته طولانی نشه.
توی این نوشته راجع به چرایی نیاز و استفاده از داکر صحبت کردیم، تو نوشته های بعدی راجع به اینکه اصلا Docker چی هست صحبت می کنیم و وارد جزییات بیشتری از نحوه کار و استفاده از دستورات داکر میشیم.
سخن آخر اینکه، میخوام سعی کنم منابع مفیدی به زبان فارسی تولید کنم تا هم خودم بیشتر یاد بگیرم و هم کسانی که دسترسی به منابع اینگلیسی ندارن و یا با زبان فارسی بهتر یاد میگیرن کمک کنم، پس اگه نوشتههای منو دوست داشتین برای دوستاتون هم بفرستین که هم اونا استفاده کنن و هم انرژی میشه برای من که ادامه بدم :)
اگه توییتر دارید می تونید منو با آیدی mehdi_zarepour پیدا کنید، نوشتههای جدید رو اونجا توییت میکنم. فعلا نوشتههام راجع به داکر خواهد بود.