رفتن به محتوا
LoopX

کانتینر در برابر ماشین مجازی

توی این درس یاد می‌گیری کانتینر (container) و ماشین مجازی (virtual machine یا VM) دقیقاً چه فرقی دارند: چرا VM به یک سیستم‌عامل کامل و hypervisor نیاز دارد ولی کانتینر هسته‌ی (kernel) میزبان را با بقیه به اشتراک می‌گذارد، namespace و cgroup چه نقشی دارند، و در عمل کی کدام را انتخاب می‌کنی. همه‌چیز را با دستور واقعی خودت می‌سنجی، از جمله اینکه کرنل داخل کانتینر با کرنل سیستمت یکی است یا نه.

تشبیه: خانه‌ی مستقل در برابر آپارتمان

Section titled “تشبیه: خانه‌ی مستقل در برابر آپارتمان”

ماشین مجازی مثل خانه‌ی ویلایی مستقل است: هر خانه فونداسیون، لوله‌کشی، برق و سقف خودش را دارد. امن و کاملاً جداست، ولی ساختنش گران است و حجم زیادی زمین و مصالح می‌خواهد.

کانتینر مثل آپارتمان در یک ساختمان است: همه فونداسیون، لوله‌کشی اصلی و برق ساختمان (هسته‌ی سیستم‌عامل) را به اشتراک می‌گذارند، ولی هر واحد در بسته است و همسایه‌ها داخلش را نمی‌بینند. ساختنش ارزان و سریع است. بهایش این است که همه به یک ساختمان وابسته‌اند: اگر لوله‌کشی اصلی مشکل داشته باشد، همه را درگیر می‌کند.

در VM هر برنامه روی سیستم‌عامل مهمان کامل خودش است و hypervisor پایین‌تر آن‌ها را مدیریت می‌کند. در کانتینر، برنامه‌ها مستقیم روی هسته‌ی میزبان با موتور کانتینر اجرا می‌شوند.
  • Hypervisor نرم‌افزاری است که سخت‌افزار را «مجازی» می‌کند تا چند سیستم‌عامل هم‌زمان روی یک دستگاه اجرا شوند (مثل VMware، VirtualBox، Hyper-V، KVM).
  • کانتینر هیچ سیستم‌عامل مهمانی ندارد. فقط یک پروسه روی هسته‌ی میزبان است که با ویژگی‌های هسته‌ی لینوکس (namespace و cgroup) جدا شده است.

مثال ۱: کرنل کانتینر کدام است؟

Section titled “مثال ۱: کرنل کانتینر کدام است؟”
Terminal window
docker run --rm alpine uname -a
uname -sr
docker info --format 'کرنل موتور داکر: {{.KernelVersion}}'
خروجی
Linux 82dc7603ca7b 7.0.14-linuxkit #1 SMP PREEMPT Fri Sep 18 10:19:32 UTC 2026 aarch64 Linux
Darwin 27.0.0
کرنل موتور داکر: 7.0.14-linuxkit

دستور اول uname -a را داخل یک کانتینر Alpine اجرا کرد؛ دستور دوم همان را روی سیستم میزبان خودت. دستور سوم کرنل موتور داکر را می‌پرسد. دقت کن: کانتینر Linux می‌گوید حتی اگر سیستم تو مک (Darwin) باشد، و شماره‌ی کرنلش دقیقاً همان کرنل موتور داکر است. کانتینر کرنل خودش را ندارد و از همان کرنل لینوکسی استفاده می‌کند که موتور داکر روی آن اجرا می‌شود (روی مک و ویندوز، یک VM سبک لینوکسی؛ روی خود لینوکس، خود سیستم تو).

مثال ۲: حجم و سرعت راه‌اندازی

Section titled “مثال ۲: حجم و سرعت راه‌اندازی”
Terminal window
docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}' alpine
docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}' ubuntu
خروجی
REPOSITORY TAG SIZE
alpine latest 26.6MB
alpine 3.12 8.7MB
REPOSITORY TAG SIZE
ubuntu 24.04 141MB
Terminal window
TIMEFORMAT='زمان کل: %Rs'
time docker run --rm alpine echo "hello"
خروجی
hello
زمان کل: 0.287s

image کامل یک سیستم‌عامل مینیمال (Alpine) فقط چند مگابایت است و روشن کردنش کسری از ثانیه طول می‌کشد، چون هیچ سیستم‌عاملی بوت نمی‌شود؛ فقط یک پروسه شروع می‌شود. (اعداد روی سیستم تو فرق می‌کنند؛ مهم مقیاس آن‌هاست: مگابایت و کسری از ثانیه، نه گیگابایت و دقیقه.)

مثال ۳: جداسازی با namespace

Section titled “مثال ۳: جداسازی با namespace”
Terminal window
docker run --rm alpine sh -c 'echo "PID من: $$"; echo "hostname: $(hostname)"; ps'
خروجی
PID من: 1
hostname: 0dbb9e396c8c
PID USER TIME COMMAND
1 root 0:00 ps

داخل کانتینر پروسه‌ی اصلی شماره‌ی ۱ دارد و فقط پروسه‌های خودش را می‌بیند؛ ده‌ها پروسه‌ی دیگر سیستم میزبان دیده نمی‌شوند. hostname هم مخصوص خود کانتینر است. این همان جداسازی PID namespace و UTS namespace است.

⚡ بررسی سریع

کانتینری که روی یک مک اجرا می‌شود، از چه هسته‌ای استفاده می‌کند؟

مثال ۴: محدودیت منابع با cgroup

Section titled “مثال ۴: محدودیت منابع با cgroup”
Terminal window
docker run --rm --cpus 0.5 alpine cat /sys/fs/cgroup/cpu.max
docker run --rm --memory 100m alpine cat /sys/fs/cgroup/memory.max
docker run --rm alpine cat /sys/fs/cgroup/memory.max
خروجی
50000 100000
104857600
max

عدد 50000 100000 یعنی «در هر ۱۰۰۰۰۰ میکروثانیه فقط ۵۰۰۰۰ میکروثانیه CPU» که معادل نصف یک هسته است. عدد 104857600 همان ۱۰۰ مگابایت است و max یعنی بدون سقف. این محدودیت‌ها را cgroup هسته اعمال می‌کند، نه یک نرم‌افزار جداگانه.

مثال ۵: کانتینر از بیرون چه شکلی است؟

Section titled “مثال ۵: کانتینر از بیرون چه شکلی است؟”
Terminal window
docker run -d --name lx-sleeper alpine sleep 300
docker top lx-sleeper
docker inspect --format 'PID در کرنل میزبان (داخل VM داکر): {{.State.Pid}}' lx-sleeper
docker rm -f lx-sleeper
خروجی
095d727d0680efad9140267266771565731103f87140e43c99a4a1ad858986f3
UID PID PPID C STIME TTY TIME CMD
root 66250 66227 4 13:43 ? 00:00:00 sleep 300
PID در کرنل میزبان (داخل VM داکر): 66250
lx-sleeper

از بیرون، کانتینر فقط یک پروسه‌ی sleep است. docker top پروسه‌های داخل کانتینر را از دید میزبان نشان می‌دهد و docker inspect شماره‌ی واقعی آن را در کرنل. (این شماره با «PID ۱ داخل کانتینر» فرق دارد؛ یک پروسه، دو شماره، چون هر namespace شماره‌گذاری خودش را دارد.)

مثال ۶: حداقل منابع قابل قبول

Section titled “مثال ۶: حداقل منابع قابل قبول”
Terminal window
docker run --rm --memory 1m alpine true
خروجی
docker: Error response from daemon: Minimum memory limit allowed is 6MB
Run 'docker run --help' for more information

داکر برای سقف حافظه حداقلی دارد (در خطای بالا می‌بینی). این یعنی حتی کانتینر هم نمی‌تواند با هر منبع ناچیزی اجرا شود.

پشت پرده: دیوارهای کانتینر از چه ساخته شده‌اند؟

Section titled “پشت پرده: دیوارهای کانتینر از چه ساخته شده‌اند؟”

کانتینر یک «چیز» واحد در هسته‌ی لینوکس نیست؛ ترکیبی از چند ویژگی است:

ویژگی هسته چه چیزی را جدا یا محدود می‌کند؟
PID namespace فهرست پروسه‌ها؛ کانتینر فقط پروسه‌های خودش را می‌بیند
Network namespace کارت شبکه، IP، جدول مسیریابی و پورت‌ها
Mount namespace فایل‌سیستم؛ کانتینر ریشه‌ی خودش را دارد
UTS namespace hostname
IPC namespace ارتباط بین پروسه‌ها (حافظه‌ی مشترک و صف پیام)
User namespace نگاشت شماره‌ی کاربرها (اختیاری)
cgroup محدودیت و حسابداری منابع (CPU، حافظه، I/O)

namespace مشخص می‌کند پروسه چه چیزی را می‌بیند؛ cgroup مشخص می‌کند چقدر می‌تواند مصرف کند. VM به‌جای این‌ها، سخت‌افزار را شبیه‌سازی می‌کند و یک سیستم‌عامل کامل روی آن بوت می‌شود؛ جداسازی‌اش قوی‌تر است چون هسته‌ی جدا دارد، ولی بهایش مصرف بیشتر و شروع کندتر است.

چون همه‌ی کانتینرها یک هسته دارند، یک باگ امنیتی در هسته می‌تواند همه را درگیر کند. به همین دلیل در محیط‌هایی که به جداسازی بسیار قوی نیاز است (مثلاً اجرای کد ناشناس مشتری‌های مختلف)، کانتینر را معمولاً داخل یک VM اجرا می‌کنند. این دو رقیب نیستند؛ اغلب مکمل هم‌اند. (درس «امنیت پایه» همین بخش را بیشتر باز می‌کند.)

معیار کانتینر ماشین مجازی
زمان شروع ثانیه یا کمتر معمولاً دقیقه
حجم مگابایت تا صدها مگابایت چند گیگابایت
هسته مشترک با میزبان مخصوص خود VM
سیستم‌عامل مهمان متفاوت (مثلاً ویندوز روی لینوکس) نه بله
جداسازی خوب (در سطح پروسه) قوی‌تر (هسته‌ی جدا)
تراکم روی یک سرور بسیار زیاد کمتر
مناسب برای میکروسرویس، CI، محیط توسعه‌ی یکسان سیستم‌عامل‌های متفاوت، جداسازی شدید، برنامه‌های قدیمی

۱) «داخل کانتینر bash نیست»

Section titled “۱) «داخل کانتینر bash نیست»”
Terminal window
docker run --rm alpine bash
خروجی
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: exec: "bash": executable file not found in $PATH
Run 'docker run --help' for more information

image های کوچک مثل Alpine ابزارهای کمی دارند. راه‌حل: به‌جای bash از sh استفاده کن، یا image ای مثل ubuntu که bash دارد.

۲) انتظار «سیستم‌عامل کامل»

Section titled “۲) انتظار «سیستم‌عامل کامل»”
Terminal window
docker run --rm alpine systemctl status
خروجی
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: exec: "systemctl": executable file not found in $PATH
Run 'docker run --help' for more information

داخل کانتینر معمولاً سیستم init و سرویس‌های سیستمی (systemd) وجود ندارد؛ کانتینر برای اجرای یک برنامه‌ی اصلی ساخته شده، نه یک سرور کامل. راه‌حل: هر سرویس را در کانتینر جدا بگذار.

۳) فکر کردن «کرنل را می‌توانم داخل کانتینر عوض کنم»

Section titled “۳) فکر کردن «کرنل را می‌توانم داخل کانتینر عوض کنم»”

کرنل مال میزبان است و از داخل کانتینر عوض نمی‌شود (مثال ۱: همان کرنل میزبان بود). اگر کرنل دیگری لازم داری، به VM نیاز داری.

✎ تمرینآسان

نسخه‌ی کرنل را یک بار داخل یک کانتینر Alpine و یک بار داخل یک کانتینر Ubuntu چاپ کن (uname -r). چه نتیجه‌ای می‌گیری؟

دیدن جواب
Terminal window
docker run --rm alpine uname -r
docker run --rm ubuntu:24.04 uname -r
خروجی
7.0.14-linuxkit
7.0.14-linuxkit

هر دو عدد یکی است، چون هر دو کانتینر از کرنل مشترک موتور داکر استفاده می‌کنند؛ فقط فایل‌های سطح کاربر (Alpine یا Ubuntu) فرق دارند.

✎ تمرینمتوسط

یک کانتینر با hostname دلخواه my-box اجرا کن که hostname خودش را چاپ کند. سپس بدون آن گزینه اجرا کن و hostname پیش‌فرض را ببین. نتیجه چه می‌گوید؟

دیدن جواب
Terminal window
docker run --rm --hostname my-box alpine hostname
docker run --rm alpine hostname
خروجی
my-box
9e302545810f

hostname هر کانتینر مال خودش است (UTS namespace)؛ پیش‌فرضش شناسه‌ی کوتاه کانتینر است و با --hostname عوض می‌شود.

✎ تمرینسخت

یک کانتینر با سقف CPU برابر 0.25 هسته بساز و با خواندن فایل cgroup ثابت کن اعمال شد. بعد حساب کن عدد اول cpu.max چرا چنین است. در آخر یک سقف حافظه‌ی نامعتبر (512k) بده و خطا را ببین.

دیدن جواب
Terminal window
docker run --rm --cpus 0.25 alpine cat /sys/fs/cgroup/cpu.max
docker run --rm --memory 512k alpine true
خروجی
25000 100000
docker: Error response from daemon: Minimum memory limit allowed is 6MB
Run 'docker run --help' for more information

نصف هسته 50000 از 100000 بود؛ پس 0.25 برابر 25000 از 100000 می‌شود. عدد دوم دوره‌ی زمانی (۱۰۰ میلی‌ثانیه) و عدد اول سهمیه‌ی CPU در آن دوره است. سقف حافظه‌ی خیلی کم هم توسط داکر رد می‌شود.

؟ آزمونک
  1. مهم‌ترین تفاوت کانتینر با VM چیست؟

  2. namespace چه چیزی را مشخص می‌کند؟

  3. خروجی cpu.max برابر «50000 100000» یعنی…

  4. برای اجرای یک برنامه‌ی ویندوزی روی سرور لینوکس کدام لازم است؟

  5. چرا کانتینر در حد ثانیه بالا می‌آید؟

  • VM = سخت‌افزار مجازی + سیستم‌عامل مهمان کامل + هسته‌ی جدا؛ کانتینر = یک پروسه‌ی جداشده روی هسته‌ی مشترک.
  • جداسازی کانتینر با namespace (چه چیزی را می‌بیند) و cgroup (چقدر می‌تواند مصرف کند) انجام می‌شود.
  • کانتینر سبک، سریع و متراکم است؛ VM جداسازی قوی‌تر و امکان سیستم‌عامل متفاوت دارد.
  • روی مک و ویندوز، کانتینرها داخل یک VM سبک لینوکسی اجرا می‌شوند.
  • این دو مکمل‌اند: کانتینر را اغلب داخل VM اجرا می‌کنند.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
docker run --rm alpine uname -aاجرای uname داخل کانتینر Alpine (و پاک شدن)
uname -srسیستم‌عامل و کرنل میزبان
docker info --format "{{.KernelVersion}}"کرنل موتور داکر
docker run --rm --cpus 0.5 IMAGEسقف CPU نصف هسته
docker run --rm --memory 100m IMAGEسقف حافظه ۱۰۰ مگابایت
docker run --rm --hostname NAME IMAGEhostname دلخواه برای کانتینر
docker top NAMEپروسه‌های داخل کانتینر از دید میزبان
docker inspect --format "{{.State.Pid}}" NAMEPID واقعی کانتینر در میزبان