احمد رفیعی
احمد رفیعی
خواندن ۱۳ دقیقه·۶ ماه پیش

در مسیر دواپس اینبار: داکر فایل ( قسمت چهارم )

در ادامه‌ی مسیرمون به سمت دواپس اینبار یه مقدار دقیق تر بررسی می‌کنیم که ایمیج ‌های داکر به چه صورت ساخته میشن و به چه شکل میتونیم اپلیکیشن‌مون رو با استفاده از داکر بیلد کنیم.

Dockerfile
Dockerfile

خب یه مروری کنیم پست‌های قبلی رو:

توصیه می‌کنم که حتما این پست‌ها رو هم مطالعه کنید. بریم که ادامه بدیم.

داکر فایل چیه و چطور می‌نویسن؟

قبل تر گفتم که آدما ایمیج هایی رو که بیلد کردن توی رجیستری میذارن و می‌تونن اونها رو جابه‌جا کنند، حالا سوال اینه که چجوری یه ایمیج رو می‌سازند؟ جوابش داکر فایله یعنی بهترین و دقیق‌ترین راهش داکرفایله.

داکرفایل به فایلی گفته می‌شه که ما تمام مراحل آماده شدن ایمیج‌ها شامل چه پکیجی نصب بشه یا اینکه چه فایلی کپی بشه رو اونجا نوشتیم.

به فایلی که مجموعه مراحل آماده شدن ایمیج مون شامل مواردی مثل اینکه چه فایل ها و دایرکتوری هایی در ایمیج باشن، چه کامند هایی اجرا بشن، چه متغیرهای محیطی تعریف بشن، چه پروسه‌ای موقع بالا اومدن کانتینر اجرا بشه و … رو مشخص می‌کنیم، داکر فایل گفته میشه. یک فایل متنی که مرحله به مرحله تمام مواردی که نیاز است ایمیج آماده بشه رو داخل خودش داره.

یه قابلیت خوبی که داکر فایل بهمون میده Version Tracking هست و میتونیم ورژن های مختلف برای ایمیج‌مون داشته باشیم و توسعه اش بدیم یا اگه نیاز شد برگردیم به ورژن های قبلی و …

یادمون نرفته که ما همیشه دنبال این هستیم که بریم سمت اینکه همه چیز به صورت کد باشه و اینجا هم دنبال این هستیم که ساخت ایمیج‌هامون به صورت کد باشه که می‌شه.

BuildKit
BuildKit

بیلد کیت چیه؟ BuildKit

از ورژن ۱۸.۰۹ به بعد، داکر از بک اند BuildKit برای بیلد کردنش پشتیبانی میکنه که مزیت های زیادی رو نسبت به قبل به همراه میاره در ادامه یه لیستی از اونا رو براتون میذارم:

  • Detect and skip executing unused build stages
  • Parallelize building independent build stages
  • Incrementally transfer only the changed files in your build context between builds
  • Detect and skip transferring unused files in your build context
  • Use external Dockerfile implementations with many new features
  • Avoid side-effects with rest of the API (intermediate images and containers)
  • Prioritize your build cache for automatic pruning
Build and Run
Build and Run

سینتکس داکر فایل:

برای نوشتن داکر فایل، فایلی رو با نام Dockerfile و بدون پسوند ایجاد می‌کنیم و داخل اون دستورات داکر فایل رو می‌نویسیم. البته می‌تونه اسمش این نباشه ولی این اسم پیش‌فرضی هست که می‌شناسه و وقتی این اسم رو براش انتخاب می‌کنیم به راحتی می‌شناسه و سینتکس هایلایت بهمون می‌ده. یک نکته اینکه سینتکس داکرفایل رو به صورت UpperCase همواره در ابتدای خط نوشته می‌شه. می‌تونه به صورت LowerCase هم باشه ولی نرمال اینه که به صورت UpperCase نوشته بشه.

در ادامه لیستی از گزینه هایی که تو داکر فایل استفاده میشه رو با یه توضیح مختصر ازشون براتون میارم و تو اینجا هم می‌تونید رفرنس کاملش رو داشته باشید.

.dockerignore:

معمولا ما Dockerfile رو کنار دایرکتوری و فایل های پروژه مون میذاریم و مثلا دستوراتی میزنیم که فایل هامون رو کپی کنیم توی ایمیج یا کارهای مشابه. حالا اگه فایلی با نام dockerignore. توی پروژه کنار داکر فایل باشه میتونیم داخلش لیستی از فایل ها و دایرکتوری هایی رو که میخوایم داخل داکرفایل نباشه رو مشخص کنیم. مفهومی شبیه gitignore. اگه باهاش آشنا باشید. قبلا این طوری بود که تمام فایل‌ها و دایرکتوری‌هایی که کنار داکرفایل بود رو تو ایمیج لود می‌کرد که الان با این بیلد‌کیت جدیده دیگه این اتفاق نمی‌افته ولی هنوز این امکان وجود داره که خودمون یه سری فایل رو ignore کنیم تا داخل داکرفایلمون نباشه.

FROM:

همونطور که بالاتر هم گفتم هر ایمیجی از یه بیس ایمیج شروع میشه. بنابراین هر داکر فایلی رو که باز می‌کنیم اولش یه FROM هست که اون بیس ایمیج رو مشخص کنه. حالا ممکنه ماجرای مرغ و تخم مرغ ابهامش تو ذهنتون ایجاد شه که خب اون بیس ایمیج‌مون که توی خط اول داکر فایلش نوشته FROM خودش از روی چه ایمیجی ساخته شده؟ پاسخ scratch هست که میتونید در موردش بیشتر بخونید. یعنی base image‌ها از From scratch ساخته می‌شوند.

MAINTAINER:

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

RUN:

یکی از مهمترین سینتکس‌های داکرفایل، با این دستور یک لایه جدید بالای ایمیج درست میشه و کامندی که زدیم توی اون لایه اجرا میشه و نتایج کامند توی اون ذخیره میشه. به صورت کلی هر اکشنی که تو ایمیج می‌خواهیم بزنیم رو با استفاده از RUN انجامش می‌دیم. دقت کنید RUN در زمان ساخت ایمیج اجرا می‌شه و هر RUN یک لایه‌ی جدید تو ایمیج ایجاد می‌کنه.

CMD:

تو کانتینرها چون خیلی سبک و کم‌حجم ساخته می‌شوند سرویسی نداریم شبیه systemd لینوکس که بیاد سرویس‌های دیگه رو تو بک‌گراند ران کنه و بالا نگه داره. برای همین باید خودمون سرویس‌هایی که لازم داریم رو تو Forground بالا نگه داریم. CMD کارش اینه که باهاش ما سرویس‌های خودمون رو تو Forground بالا نگه می‌داریم. دقت کنید که CMD در زمان تبدیل ایمیج به کانتینر اجرا می‌شه.

EXPOSE:

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

ENV:

متغیرهای محیطی ایمیج‌مون رو اینجا تعریف می‌کنیم. متغیرهایی که بعدا در زمان کانتینر هم وجود داره و اگر داخل کانتینر env بزنیم می‌تونیم آنها رو ببینیم.

COPY:

برای کپی کردن فایل‌ها و دایرکتوری‌ها از لوکال سرور ازش استفاده می‌کنیم. همانند RUN یک لایه‌ی جدید ایجاد می‌کنه و روش Official کپی‌ فایل داخل ایمیج هستش.

ADD:

برای انتقال و کپی کردن فایل ها و دایرکتوری هامون به صورت لوکال یا ریموت فایل ازش استفاده می‌کنیم. ADD هم همانند COPY و RUN یک لایه ایجاد می‌کند. تنها تفاوتی که ADD برامون داره اول اینکه از ریموت مثل گیت می‌تونیم فایل داخل ایمیج قرار بدیم و دوم اینکه فایل‌های Compress شده رو هم می‌تونه برامون در حین انتقال از Compress خارج کنه.

ENTRYPOINT :

گاهی پیش میاد که راه‌اندازی پروسه‌ی ما فقط با یه دستور نیست و نیاز داریم که براش یه اسکریپت اجرا کنیم. اینجا می‌تونیم از این سینتکس استفاده کنیم. در برخی از مواقع هم پیش میاد که همزمان با CMD ازش استفاده می‌شه. همانند CMD این سینتکس هم در زمان تبدیل ایمیج به کانتینر اجرا می‌شود. بیشتر برای پروسه‌هایی که نیاز به bootstrapping دارند کاربرد دارد.

VOLUME:

تو این سینتکس داریم مشخص می‌کنیم که دیتاهای حساس ما کجاها قرار دارند. علاوه بر اینکه داریم به فردی که داکرفایل رو می‌خونه می‌گیم که برای این مسیر باید در زمان کانتینر والیوم بسازی و یه جورایی نقش متادیتا رو بازی می‌کنه اگر فرد براش والیوم نساخت یه والیوم با نام رندم ایجاد می‌کنه که یک آی‌دی بهش می‌ده و دیتا رو اونجا Persist می‌کنه. یعنی اگر براش والیوم ساخت و این مسیر رو آنجا مانت کرد که هیچ اگر نساخت خود داکر با توجه به داکرفایل براش می‌سازه.

USER:

هر کاربری که مشخص کنیم در ادامه‌ی ایمیج و در زمان کانتینر با آن کاربر کار می‌کنه. این نکته‌ی مهمی است و جز Best Practiceهای داکرفایل هم هست که نباید ایمیج با کاربر Root باشه و اصطلاحا باید root less باشه.

WORKDIR:

دایرکتوری کاریمون رو مشخص می‌کنه. در ادامه‌ی ایمیج و در زمان کانتینر تو این مسیر دستورات رو می‌زنه و ادامه می‌ده. این دستور هم همانند RUN, COPY و ADD یک لایه‌ی جدید برای ما ایجاد می‌کنه.

ARG:

وریبل های زمان بیلدمون رو اینجا مشخص می‌کنیم. این خاصیت رو داره که به وریبل یه مقدار دیفالت رو بدیم و در زمان بیلد آن آرگومان رو به فراخور نیازمون تغییر بدیم. اگرم ندادیم که مقدار دیفالت رو برمی‌داره. نکته‌‌ای که داره تنها سینتکسی هست که قبل از FROM می‌تونه قرار بگیره.

ONBUILD:

دستور تریگری رو اینجا مشخص می‌کنیم که موقعی‌که داکر فایل دیگه‌ای از ایمیج ما به عنوان بیس ایمیج استفاده کرد براش اعمال شه. یعنی زمانی کاربرد داره که این ایمیج خودش به عنوان Base Image استفاده بشه.

STOPSIGNAL:

اینجا سیگنال سیستم کالی رو که باید برای خروج به کانتینر فرستاده بشه رو مشخص می‌کنیم. یعنی وقتی که دستور kill برای کانتینر ارسال می‌شه اون چه طوری بمیره. واقعا خیلی مهم که کانتینر ما چطوری بمیره. مثلا اینکه وقتی kill براش ارسال شد در لحظه بمیره یا صبر کنه و تمام درخواست‌هایی که سمتش هست رو پاسخ بده و بعد بمیره یا اصلا reload بشه.

LABEL:

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

HEALTHCHECK:

اینجا میتونیم برای داکر مشخص کنیم که به چه شکلی چک کنه که کانتینری که از روی ایمیج ما بالا اومده هنوز داره کار میکنه یا اینکه کارش تموم شده و باید بمیره. یه جورایی داریم مشخص می‌کنه که وضیعت سالم و اوکی از نظر این کانتینر چی هست. دو تا مرحله داره مرحله اول starting و مرحله‌ی دوم Healthy یا Unhealthy هست.

SHELL:

اینجا هم میتونیم شل دیفالتی که برای کامند هامون وجود داره رو overwrite کنیم. به صورت پیش‌فرض معمولا shell رو از base image می‌گیره و اینجا ما می‌تونیم مشخص کنیم که دقیقا چی باشه. دقت کنید این موضوع هم همانند برخی دیگه از سینتکس‌ها در ادامه‌ی ایمیج و در زمان کانتینر اعمال می‌شه.

یه نمونه از داکرفایل یک app flask رو اینجا براتون میذارم:

# our base image
FROM alpine:3.5
# Install python and pip
RUN apk add --update py2-pip
# upgrade pip
RUN pip install --upgrade pip
# install Python modules needed by the Python app
COPY requirements.txt /usr/src/app/
RUN pip install --no-cache-dir -r /usr/src/app/requirements.txt
# copy files required for the app to run
COPY app.py /usr/src/app/
COPY templates/index.html /usr/src/app/templates/
# tell the port number the container should expose
EXPOSE 5000
# run the application
CMD [&quotpython&quot, &quot/usr/src/app/app.py&quot]
Dockerfile Stages
Dockerfile Stages

مالتی استیج داکر فایل:

یه مفهومی هست تحت عنوان Multi Stage توی داکر فایل که خیلی جالب و کاربردیه. تو حالت مالتی استیج ما میتونیم چند تا FROM یا بیس ایمیج توی داکر فایل‌مون داشته باشیم! معمولا استفاده از این قابلیت به این شکل هست که از استیج اول آرتیفکت یا خروجی رو به استیج های بعدی منتقل می‌کنیم. نهایتا ایمیج ما بعد از بیلد از جنس استیج نهایی داکر فایل خواهد بود. کجاها کاربرد داره؟ زمانی که ما می‌خواهیم یه پکیجی رو کامپایل کنیم نیاز داریم که کلی کامپایلر و اینا داشته باشیم در صورتی که بعد از کامپایل کردن دیگه به آنها نیاز نداریم. حالا چی می‌شه داستان؟ این طوری هست که تو استیج‌های اول از یک base image استفاده می‌کنیم که تمام این کامپایلرها رو داشته باشه و بعد از اینکه پکیج‌ خودمون رو کامپایل کردیم آن رو که نتیجه کارمون هست رو تو یه ایمیج خیلی سبک‌تر قرار میدیم که کلی برامون مزیت به همراه داره.

Multi Stage
Multi Stage

یه نمونه از داکرفایل مالتی استیج:

FROM golang:1.14
RUN git clone https://github.com/coredns/coredns.git /coredns
RUN cd /coredns && make
FROM scratch
COPY --from=0 /coredns/coredns /coredns
EXPOSE 53 53/udp
CMD [&quot/coredns&quot]

مزایای Multi Stage Build:

از مزایای Multi Stage می‌تویم به موارد زیر اشاره کنیم:

  • حجم کمتر ایمیج: چون پکیج‌های غیر ضروری تو ایمیجمون نیست به مراتب حجم خیلی پایین‌تری خواهیم داشت که خیلی کارمون رو تو انتقال و نگهداری ایمیج‌ها راحت‌تر می‌کنه.
  • امنیت بالاتر ایمیج: هر چقدر که پکیج‌ کمتری داشته باشیم در نتیجه آسیب‌پذیری کمتری هم خواهیم داشت. معمولا اگر تعداد پکیج‌ها بیشتر بشه به همون اندازه هم می‌تونه آسیب‌پذیری ما بیشتر بشه.
  • هزینه‌ی پایین‌ترمون: هم در زمان ساخت ایمیج، هم در زمان نگهداری آن، هم در زمان انتقال آن و هم در زمان تبدیل به کانتیر کردن باعث می‌شه که هزینه‌ی ما کاهش پیدا کنه.
  • کارایی بالاتر: چون تعداد پکیج‌های کمتری داره در نتیجه بهمون کمک می‌کنه که کارایی یا اصطلاحا Performance بالاتری رو تجربه کنیم.
  • سرعت بیشتر: با توجه به اینکه حجم پایین‌تری داریم سرعت دیپلویمنت بیشتری رو تو CI/CDها و یا به صورت منوآل می‌تونیم تجربه کنیم.
Best Practice
Best Practice

بست پرکتیس‌ها داکرفایل:

خب دیگه الان می‌دونیم چطور داکرفایل بنویسیم و باهاش ایمیج آماده کنیم. در ادامه لیستی از برخی مواردی که توصیه میشه رعایتشون کنیم براتون میذارم:

  • تا جای ممکن از بیس ایمیج های سبک استفاده کنید مثلا debian-slim
  • حتما سعی کنیم که ایمیج‌های ما Root-less باشند و با کاربر سطح پایین‌تری اجرا بشن.
  • حتما سعی کنیم از Multi Stage Build استفاده کنیم.
  • بهتره که Distroless باشیم و از From Scaratch استفاده کنیم.
  • از base imageهای Trusted استفاده کنیم.
  • حتما base imageهایی که استفاده می‌کنیم رو به صورت برنامه‌ی مشخص و زمان‌بندی شده به روز کنیم و آنها رو آپدیت نگه داریم.
  • حتما تو داکرفایل خودمون پورت‌هایی که استفاده می‌شه رو Expose کنیم.
  • دیتاهای حساس همانند پسوردها و توکن‌ها و … رو داخل داکرفایل قرار ندهیم.
  • سعی کنیم لایه‌های ایمیج رو بهینه ایجاد کنیم.
  • خوبه که ایمیج‌های خودمون رو scan کنیم تا از بروز آسیب‌پذیری داخل آنها جلوگیری کنیم.
  • ابزارهایی که برای Tshoot ازشون استفاده می‌کنیم مثل vim و curl داخل ایمیج‌ها قرار ندیدم تا بتونیم ایمیج‌های سبک‌تری ایجاد کنیم.
  • در تمامی محیط های عملیاتی از یک ایمیج باید استفاده کنیم.
  • برای کم کردن حجم ایمیج‌ها داکیومنت و man page پکیج‌هایی که نصب می‌کنیم رو غیرفعال کنید و پس از نصب کش و موارد دیگه رو پاک کنیم.
  • تا جای ممکن لایه های RUN رو کاهش بدید و سعی کنید step های نصب رو مثلا با هم ترکیب کنید. البته دقت کنیم که ترکیب کردن لایه‌ها با هم زمانی کارایی بالایی بهمون می‌ده که بتونیم بیشترین استفاده از cache رو داشته باشیم. یعنی تا جایی که می‌تونیم لایه‌هایی که زیاد تغییر می‌کنند رو پایین‌تر قرار بدیم که بیشترین استفاده از cache رو داشته باشیم.
  • اگه از apt استفاده می‌کنید no-install-recommend-- بزنید تا از نصب پکیج های غیر ضروری جلوگیری کنید.
  • فایل های کش شده و مواردیکه در انتها نیازی بهشون نداریم رو پاک کنید مثلا rm -rf /var/lib/apt/lists/*
  • از ابزارهایی مثل dive یا DockerSlim برای آنالیز ایمیج هاتون استفاده کنید.


خب پس تا اینجا فهمیدیم که چطور میشه اپلیکیشن رو با داکر بیلد کرد و ایمیج برای سرویس مون بسازیم. در ادامه مسیر میریم سراغ داکر کامپوز.

مراقب خودتون باشید. 🌹🐳🌹



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

🫀 Follow DockerMe 🫀

🔔 Follow YouTube 🔔

📣 Follow Instagram 📣

🖇 Follow LinkedIn DockerMe🖇

🔎 Follow Linkedin Ahmad Rafiee 🔎

🕊 Follow Twitter 🕊


داکرفایلdockerfiledockerahmadrafieeداکرمی
مشاور زیرساخت. موسس سایت آموزشی DockerMe.ir
شاید از این پست‌ها خوشتان بیاید