Foundationপ্রথম নীতি থেকে
LEVEL 12লেসন ৬/২২কঠিন৫০ মিনিট

Container বনাম VM — দুইটা সম্পূর্ণ ভিন্ন Isolation Boundary

Containers vs VMs: Two Different Isolation Boundaries

এই module এতদিন যা শিখিয়েছে তার সবটাই এক ধরনের isolation-এর গল্প — একটা guest পুরো hardware-এর বিভ্রম পায়, নিজস্ব সম্পূর্ণ kernel নিয়ে। আজ সেই গল্পের বিপরীত মেরুতে দাঁড়িয়ে দেখব দ্বিতীয় ধরনের isolation — container, যেখানে কোনো দ্বিতীয় kernel নেই, শুধু একই host kernel-এর ওপর namespace আর cgroup দিয়ে বেড়া দেওয়া প্রসেস। দুইটার মধ্যেকার পার্থক্যটাই ঠিক করে দেয় স্টার্টআপ গতি, density, আর সবচেয়ে গুরুত্বপূর্ণভাবে — আক্রমণকারী ঠিক কী ভাঙলে বাইরে বেরিয়ে আসতে পারবে।

এই লেসন শেষে আপনি পারবেন

  • VM ও container-এর মধ্যে সবচেয়ে মৌলিক পার্থক্যটা — hardware virtualization বনাম OS-level virtualization — স্পষ্টভাবে ব্যাখ্যা করতে পারবেন, আর কেন 'container একটা হালকা VM' কথাটা ভুল তা যুক্তি দিয়ে প্রমাণ করতে পারবেন
  • VM escape ও container escape-এর আক্রমণ-পৃষ্ঠ (attack surface) নির্ভুলভাবে তুলনা করে বলতে পারবেন কেন এই দুইটা সম্পূর্ণ ভিন্ন মাত্রার নিরাপত্তা-ঝুঁকি তৈরি করে
  • VM ও container-এর startup latency, density, আর memory overhead বাস্তব সংখ্যা দিয়ে (কিছু নিজে মেপে, কিছু প্রামাণ্য উৎস থেকে উদ্ধৃত করে) তুলনা করতে পারবেন
  • কোন পরিস্থিতিতে VM-level isolation প্রয়োজন আর কোথায় container যথেষ্ট — multi-tenant untrusted workload বনাম same-trust microservices-এর একটা concrete উদাহরণ দিয়ে যুক্তি দিতে পারবেন
  • এই module-এর VM-কেন্দ্রিক লেসনগুলো (hypervisor, trap-and-emulate, paravirtualization পর্যন্ত) আর সামনের namespace/cgroup-কেন্দ্রিক লেসনগুলোর মধ্যে এই লেসন কীভাবে সেতুবন্ধন করে তা ব্যাখ্যা করতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

এই পর্যন্ত এই module-এর প্রতিটা লেসন একটা একক বিশ্বাসের ওপর দাঁড়িয়ে ছিল: একটা guest-কে সম্পূর্ণ বিভ্রম দাও যে সে একটা আস্ত, একা কম্পিউটার। Hypervisor CPU trap করে, EPT/NPT দিয়ে memory-কে দুই স্তরে ভাঙে, virtio-র মতো paravirtual driver দিয়ে device access দ্রুত করে — কিন্তু এই প্রতিটা কৌশলের লক্ষ্য একটাই: guest-এর ভেতরে বসে থাকা একটা সম্পূর্ণ, স্বাধীন kernel-কে বিশ্বাস করানো যে সে raw hardware-এর ওপর বসে আছে। virtualization-cloud/paravirtualization লেসন পর্যন্ত আমরা এই একটা ধারণাকেই বারবার সূক্ষ্ম করেছি — কীভাবে সেই বিভ্রমকে দ্রুততর, সস্তা, আর নিরাপদ করা যায়।

আজ এই ধারণাটাকেই একটা প্রশ্নের মুখোমুখি করা যাক:

প্রতিটা isolated workload-এর কি সত্যিই নিজস্ব একটা সম্পূর্ণ kernel লাগে?

উত্তরটা শিল্পের একটা বিশাল অংশের ভিত্তি বদলে দিয়েছে: না। ২০১৩ সালের পর থেকে (Docker-এর উত্থান, যদিও অন্তর্নিহিত kernel primitive-গুলো আরও পুরনো) production workload-এর একটা বিরাট অংশ চলছে container-এ — এমন একটা isolation যেখানে গুচ্ছের পর গুচ্ছ workload একই, একটামাত্র host kernel শেয়ার করে, কোনো দ্বিতীয় kernel বুট না করেই।

এই লেসনটা এই module-এর একটা সন্ধিস্থল (hinge)। এতদিন যা শিখেছেন — hardware virtualization-এর পুরো যন্ত্র — সেটা একটা সুনির্দিষ্ট, নামকরণ করা জিনিস হিসেবে বন্ধ হবে এখানে, একটা ধারালো সংজ্ঞার বিপরীতে দাঁড় করিয়ে। সংক্ষেপে সেই আগের পাঁচটা লেসনের সারসংক্ষেপ: type-1 (bare-metal) আর type-2 (hosted) hypervisor-এর মধ্যে পার্থক্য, trap-and-emulate যা privileged instruction ধরে hypervisor-এ পাঠায়, VT-x/AMD-V-এর মতো hardware-assisted virtualization যা এই trap-কে সস্তা করে, EPT/NPT দিয়ে দুই-স্তরের memory translation, আর সবশেষে paravirtualization (virtualization-cloud/paravirtualization) যেখানে guest নিজেই জানে সে virtualize হচ্ছে, আর virtio-র মতো driver দিয়ে emulation এড়িয়ে সরাসরি hypervisor-কে অনুরোধ পাঠায়। প্রতিটা কৌশল ভিন্ন, কিন্তু লক্ষ্য অভিন্ন — একটা সম্পূর্ণ, স্বাধীন guest kernel-কে যতটা সম্ভব দ্রুত, যতটা সম্ভব নিরাপদে hardware-এর বিভ্রম দেওয়া। আর সামনে যা আসছে — namespace, cgroup, container runtime-এর ভেতরের কাজ — তার দরজাও এখান থেকেই খুলবে। operating-systems/namespaces-and-cgroups লেসন ইতিমধ্যে namespace আর cgroup-এর syscall-স্তরের মেকানিক্স বিস্তারিত দেখিয়েছে (এই লেসন সেটার পুনরাবৃত্তি করবে না); আর security/sandboxing-isolation লেসন দেখিয়েছে কীভাবে seccomp/capabilities/SELinux একটা container-এর ওপরে আরও কড়াকড়ি চাপায়। আজকের প্রশ্ন সেই দুইটার কোনোটাই না — আজকের প্রশ্ন আরও মৌলিক: container জিনিসটা আদৌ কী, VM-এর তুলনায়, আর কেন এই দুইটাকে গুলিয়ে ফেলা বিপজ্জনক?

মূল ধারণা

একটা বাক্যে — কী virtualize হচ্ছে, সেটাই আসল পার্থক্য

পুরো লেসনের কেন্দ্রীয় দাবিটা একটা বাক্যে বলা যায়, আর এটাই মনে রাখার মতো একমাত্র বাক্য যদি বাকি সব ভুলেও যান:

VM virtualize করে HARDWARE-কে। Container virtualize করে OPERATING SYSTEM-কে।

এই দুইটা বাক্যাংশ আলাদা করে খোলা যাক।

VM: hardware virtualization। একটা VM-এ প্রতিটা guest তার নিজস্ব, সম্পূর্ণ kernel চালায় — নিজস্ব process scheduler, নিজস্ব memory manager, নিজস্ব device driver স্ট্যাক, নিজস্ব filesystem cache। সেই guest kernel বিশ্বাস করে সে একটা সম্পূর্ণ CPU, একটা সম্পূর্ণ RAM address space, একগুচ্ছ virtual device-এর ওপর বসে আছে — hypervisor (virtualization-cloud-এর type-1/type-2 hypervisor লেসনের বিষয়) trap-and-emulate আর EPT/NPT (hardware-assisted virtualization লেসনের বিষয়) দিয়ে এই বিভ্রমটা টিকিয়ে রাখে। একই physical মেশিনে পাঁচটা VM চললে, সেই মেশিনে আক্ষরিক অর্থেই ছয়টা kernel চলছে — একটা host-এর নিজের (বা bare-metal hypervisor-এর ক্ষেত্রে hypervisor নিজেই), আর পাঁচটা guest-এর।

Container: OS-level virtualization। একটা container-এ কোনো দ্বিতীয় kernel নেই। সেই মেশিনে যত container-ই চলুক — পাঁচটা, পঞ্চাশটা, পাঁচশোটা — সবাই ঠিক একটাই kernel শেয়ার করে, host-এর নিজের kernel। Isolation আসে সম্পূর্ণ ভিন্ন জায়গা থেকে — দুইটা kernel-স্তরের mechanism যা operating-systems/namespaces-and-cgroups-এ বিস্তারিত দেখা হয়েছে:

  • namespace — একটা process কী দেখতে পায় তা সীমিত করে (নিজস্ব PID তালিকা, নিজস্ব mount tree, নিজস্ব network stack বলে মনে হয়)
  • cgroup — একটা process কতটুকু ব্যবহার করতে পারে তা সীমিত করে (CPU সময়, memory, PID সংখ্যা)

দুইটা মিলিয়েই একটা “container” — kernel-এর দৃষ্টিতে এটা একটা সাধারণ process, শুধু কিছু অস্বাভাবিক namespace/cgroup সেটিং সহ। docker run চালালে kernel-এ নতুন কোনো struct তৈরি হয় না; একটা task_struct তৈরি হয় (ঠিক প্রতিটা fork()-এ যেমন হয়), তার nsproxy pointer কিছু নতুন namespace-এর দিকে নির্দেশ করে, cgroups pointer একটা নির্দিষ্ট cgroup path-এর দিকে।

“Container একটা হালকা VM” — এই কথাটা কেন বিপজ্জনকভাবে ভুল

শিল্পের সবচেয়ে বেশি শোনা ভুল বাক্যটা হলো “container একটা lightweight VM” — শোনায় নির্দোষ, উপকারী একটা শর্টহ্যান্ড, কিন্তু এটা ঠিক ভুল জায়গায় ভুল। “হালকা VM” বলাটা বোঝায় container একটা ছোট, কম-সুবিধাযুক্ত VM — যেন পার্থক্যটা শুধু মাত্রার (degree), একটা VM-কে ছোট করে ফেললেই একটা container পাওয়া যাবে।

এটা ভুল কারণ পার্থক্যটা মাত্রার না, ধরনের (kind)। একটা VM-কে যত ছোটই করা হোক না কেন — RAM কমান, CPU কমান, disk image ছোট করুন — সেটা এখনও একটা VM থেকে যায়, কারণ এখনও তার নিজস্ব, সম্পূর্ণ, বুট-করা একটা kernel আছে। একটা container-এর কোনো kernel-ই নেই বুট করার জন্য — এটা host kernel-এর ওপর দাঁড়িয়ে থাকা একটা process-দের গ্রুপ, differently-isolated, কিন্তু কোনোভাবেই একটা “ছোট VM” না।

সঠিক বাক্যটা:

একটা container “ছোট VM” না — এটা host kernel-এর ওপর একটা ভিন্নভাবে isolate করা process-গ্রুপ।

এই সংশোধনটা কোনো pedantic শব্দ-খেলা না — এই লেসনের পরের অংশ (isolation boundary, attack surface) পুরোপুরি এই পার্থক্যের ওপর দাঁড়িয়ে। যদি container সত্যিই “শুধু একটা ছোট VM” হতো, তার নিরাপত্তা-বৈশিষ্ট্যও VM-এর কাছাকাছি হতো। বাস্তবে তা না — আর ঠিক কেন না, সেটাই পরের section-এর বিষয়।

ভেতরে কী ঘটছে

Isolation boundary — একজন আক্রমণকারী ঠিক কী ভাঙবে

দুইটা isolation mechanism-এর তুলনা করার সবচেয়ে honest উপায় একটাই প্রশ্ন: যদি একজন আক্রমণকারী guest/container-এর ভেতরে কোড execute করার ক্ষমতা পায়, তাকে বাইরে বেরিয়ে আসতে ঠিক কী ভাঙতে হবে?

VM escape — একটা সংকীর্ণ, ভারীভাবে-নিরীক্ষিত পথ

একটা VM-এর ভেতরের guest kernel সরাসরি host-এর কোনো kernel object দেখতে পায় না — hypervisor-এর মধ্যে দিয়ে ছাড়া তার host-এর সাথে কোনো সংযোগই নেই। “VM escape” মানে আক্ষরিক অর্থে hypervisor নিজেকে exploit করা — নির্দিষ্টভাবে দুইটা জায়গার একটায়:

  • CPU virtualization যন্ত্র — VT-x/AMD-V-এর VMEXIT/VMENTER হ্যান্ডলিং, EPT/NPT-এর page-table walk লজিক (virtualization-cloud-এর hardware-assisted virtualization লেসনের বিষয়)। এই কোড Intel/AMD নিজেরাই বছরের পর বছর ধরে নিরীক্ষা করছে, আর CPU-তে hardware-এ বসানো — bug থাকলেও সংখ্যায় খুব কম, আর প্রতিটা বিশাল নিরাপত্তা-গবেষণার লক্ষ্যবস্তু।
  • Device emulation কোড — QEMU-র মতো software যা virtual disk, virtual NIC, virtual GPU emulate করে। ঐতিহাসিকভাবে এখানেই বেশিরভাগ বাস্তব VM escape CVE পাওয়া গেছে (যেমন QEMU-র floppy disk controller বা VGA emulation-এর বাগ) — কিন্তু এটাও একটা নির্দিষ্ট, সীমিত কোডবেস, পুরো OS না।

মূল কথা: VM escape-এর জন্য আক্রমণকারীকে একটা সংকীর্ণ, উদ্দেশ্যমূলকভাবে ছোট রাখা interface ভাঙতে হয় — hypervisor নিজে। এই কোডবেস বিশাল হলেও (QEMU লক্ষ লক্ষ লাইন), guest-এর কাছে exposed অংশটা তুলনামূলক ছোট — কয়েক ডজন emulated/paravirtual device, আর CPU-র নিজস্ব hardware-বাস্তবায়িত trap মেকানিজম।

Container escape — পুরো kernel syscall table-ই সীমানা

একটা container-এর ভেতরের process সরাসরি host kernel-এর ওপর syscall চালায় — read(), mmap(), openat(), সবকিছু সরাসরি host kernel-এর নিজস্ব syscall handler-এ যায়, কোনো hypervisor-স্তরের মধ্যস্থতা ছাড়াই। namespace আর cgroup সেই syscall-গুলোর ফলাফল সীমিত করে (কোন PID দেখা যাবে, কতটুকু memory allocate হবে) — কিন্তু syscall dispatch mechanism নিজেই অপরিবর্তিত।

এর মানে: host kernel-এর যেকোনো syscall-এ থাকা যেকোনো bug একটা সম্ভাব্য container escape। operating-systems/namespaces-and-cgroups লেসনের শেষ punchline ঠিক এই কারণেই ছিল — “container escape মূলত একটা সাধারণ kernel-privilege bug”। আক্রমণকারীর জন্য target পৃষ্ঠ হলো পুরো Linux kernel-এর syscall table — শত শত syscall, প্রতিটার নিজস্ব parsing লজিক, নিজস্ব edge case, দশ মিলিয়নেরও বেশি লাইনের kernel কোডবেস।

এই পার্থক্যটাকে অতিরঞ্জিত না করে সৎভাবে বলা দরকার — এর মানে এই না যে container “নিরাপদ না”, বা VM “সবসময় নিরাপদ”। এর মানে শুধু এটা: দুইটা সম্পূর্ণ ভিন্ন মাত্রার attack surface, আর যেকোনো architecture সিদ্ধান্তে এই পার্থক্যটা honest ভাবে হিসাবে নেওয়া দরকার — container-কে “VM-এর সমান নিরাপদ” বলে বিক্রি করাটা misleading।

Startup latency — কেন একটা তাৎক্ষণিক, আরেকটা সেকেন্ড-স্কেল

VM শুরু করা মানে একটা সম্পূর্ণ কম্পিউটার বুট করা — firmware/BIOS বা তার হালকা সংস্করণ (UEFI বা paravirtual bootloader), তারপর guest kernel নিজেই boot হওয়া (device probe, driver init, memory manager চালু হওয়া), তারপর init system (systemd-জাতীয় কিছু) চালু হয়ে userspace service শুরু করা। এই পুরো chain সেকেন্ডের স্কেলে — সাধারণ cloud VM-এর জন্য প্রায়ই ৩০-৬০ সেকেন্ড, খুব optimized minimal VM-এর জন্যও কয়েক সেকেন্ড।

Container শুরু করা মানে শুধু host kernel-কে একটা clone()/fork() কল করতে বলা, নতুন namespace flag-সহ — কোনো firmware নেই, কোনো দ্বিতীয় kernel boot নেই, কোনো device re-probe নেই। যে kernel ইতিমধ্যেই running আর সম্পূর্ণ initialize করা, সেটাকেই শুধু একটা নতুন, বিচ্ছিন্ন view-সহ একটা process দিতে বলা হচ্ছে। এই অপারেশন মিলিসেকেন্ডের স্কেলে।

Memory overhead — একটা পুরো OS বনাম শুধু userspace ফাইল

একটা VM-এর নিজস্ব সম্পূর্ণ kernel মানে সেই kernel-এর নিজস্ব memory-এর কপি লাগে — page table, kernel data structure, buffer cache, সবকিছু প্রতিটা guest-এর জন্য আলাদা। এমনকি একটা “খালি” guest-এও (কোনো application চলছে না) কয়েকশো মেগাবাইট RAM লাগে শুধু guest kernel আর ন্যূনতম userspace চালানোর জন্য।

একটা container-এর কোনো নিজস্ব kernel memory নেই — সেই memory একটাই, host kernel-এর, সব container একসাথে শেয়ার করে। একটা container শুধু তার নিজস্ব userspace file — application binary, library, config — এর জন্য memory লাগে, যেটা প্রায়ই কয়েক মেগাবাইট। container image-এর “নিজস্ব OS ফাইল” (যেমন alpine বা ubuntu base image) আসলে কোনো চলমান OS না — এটা শুধু একগুচ্ছ userspace ফাইল (libc, coreutils, ইত্যাদি), যা overlay filesystem (একটা পরের লেসনের বিষয়) দিয়ে container-এর mount namespace-এ সাজিয়ে দেওয়া হয়। Kernel-এর কোনো অংশ image-এর ভেতরে নেই।

Fault isolation — শুধু নিরাপত্তা না, নির্ভরযোগ্যতার প্রশ্নও

এতক্ষণ আক্রমণকারীর দৃষ্টিকোণ থেকে কথা হলো, কিন্তু একই boundary-র পার্থক্য একটা সম্পূর্ণ নিরীহ প্রশ্নেও প্রাসঙ্গিক: একটা kernel panic হলে কী হয়? একটা VM-এর guest kernel panic করলে শুধু সেই একটা VM ক্র্যাশ করে — host আর বাকি guest-রা অক্ষত থাকে, কারণ প্রতিটার নিজস্ব, সম্পূর্ণ বিচ্ছিন্ন kernel memory। কিন্তু host kernel নিজেই panic করলে (যা container-এর জগতে সরাসরি প্রাসঙ্গিক, কারণ container-এর “kernel” মানেই host kernel) — সেই একই মেশিনের প্রতিটা container একসাথে মারা যায়, ব্যতিক্রম ছাড়াই। এটা কোনো আক্রমণ না, নিছক reliability engineering-এর হিসাব — “blast radius” শব্দটা এখানে খাটে: VM-এর blast radius একটা guest, container-এর blast radius পুরো host।

উদাহরণ

দুইটা “শুরু করা” পাশাপাশি — একই লক্ষ্য, সম্পূর্ণ ভিন্ন পথ

নিচের দুইটা trace একই কাজ করছে বলে মনে হতে পারে — “একটা isolated workload চালু করা” — কিন্তু ধাপগুলো একদম ভিন্ন স্তরে।

একটা VM বুট করা — একটা সম্পূর্ণ, দ্বিতীয় কম্পিউটার জাগানো
  1. hypervisor: নতুন guest তৈরির নির্দেশvCPU allocate, guest physical memory allocate, EPT/NPT টেবিল বসানো (hardware-assisted virtualization লেসনের বিষয়)
  2. firmware/bootloader (BIOS/UEFI বা paravirtual boot)virtual hardware খুঁজে বের করে, boot device নির্ধারণ করে
  3. guest kernel বুটmemory manager init, device driver probe, scheduler চালু — একটা সম্পূর্ণ, স্বাধীন kernel initialization
  4. guest init systemsystemd-জাতীয় কিছু, guest-এর ভেতরের service চালু করে
  5. application প্রসেস চালুguest kernel-এর ওপর, guest-এর নিজস্ব scheduler-এ shedule হয়ে চলে
একটা container শুরু করা — একই kernel-কে একটা নতুন view দিতে বলা
  1. container runtime: নতুন container-এর নির্দেশimage layer unpack (আগে থেকেই disk-এ থাকতে পারে, cache হয়) — এটাই সাধারণত সবচেয়ে বেশি সময় নেয়, kernel-level operation না
  2. clone(CLONE_NEWPID|NEWNS|NEWNET|...) — host kernel-কেই একটা কলকোনো নতুন kernel বুট হয় না — চলমান host kernel একটা নতুন task_struct বানায়, নতুন namespace-এর দিকে pointer সহ
  3. cgroup.procs-এ লেখানতুন process-কে resource-limited group-এ যোগ করা — একটা filesystem write, milliseconds-এরও কম
  4. pivot_root() + execve(entrypoint)rootfs বদলানো, তারপর application সরাসরি চালু — host kernel-এরই normal scheduler এই process-কে schedule করে

লক্ষ্য করুন প্রথম trace-এ here চিহ্নিত ধাপটা — guest kernel বুট — এটাই সেই একটা ধাপ যা container-এর trace-এ পুরোপুরি অনুপস্থিত। বাকি সব ধাপ (memory allocate, resource সীমা বসানো, application চালু করা) দুইটাতেই আছে কোনো না কোনো রূপে; পার্থক্যটা একটা মাঝখানের, ব্যয়বহুল ধাপ থাকা বনাম না-থাকায়।

ছবিতে

একই host, দুই ধরনের isolation — পাশাপাশি

VM দিয়ে isolation                         Container দিয়ে isolation
────────────────────                      ──────────────────────

  ┌───────┐ ┌───────┐ ┌───────┐             ┌─────┐ ┌─────┐ ┌─────┐
  │guest A│ │guest B│ │guest C│             │ App │ │ App │ │ App │
  │kernel │ │kernel │ │kernel │             │  A  │ │  B  │ │  C  │
  └───┬───┘ └───┬───┘ └───┬───┘             └──┬──┘ └──┬──┘ └──┬──┘
      │         │         │                    │ ns/cg │ ns/cg │ ns/cg
      └────┬────┴────┬────┘                    └───────┴───┬───┴───────┘
           │hypervisor│                                     │
           └────┬─────┘                          একটাই host kernel
                │                              (namespace + cgroup দিয়ে
           host kernel                          প্রতিটা App-কে আলাদা view
        (বা bare-metal hypervisor)               ও resource সীমা দেওয়া)
                │                                           │
           physical hardware                          physical hardware

   মোট চলমান kernel: ৪টা                        মোট চলমান kernel: ১টা
   (host + ৩টা guest)                            (শুধু host-এর)
তুলনা৩টা workload আলাদা রাখার দুইটা সম্পূর্ণ ভিন্ন স্থাপত্য — বাম দিকে VM (৪টা kernel), ডানে container (১টা kernel)।

বাম দিকের প্রতিটা বাক্স স্বাধীনভাবে বুট হওয়া, স্বাধীনভাবে memory-ম্যানেজ করা একটা সম্পূর্ণ OS। ডান দিকের প্রতিটা বাক্স একটাই kernel-এর schedule-করা একটা সাধারণ process, শুধু nsproxy/cgroups pointer আলাদা।

নিজে চালিয়ে দেখুন

EXPERIMENT

Host process তৈরির গতি বনাম প্রামাণ্য VM/microVM boot-latency

যেকোনো OS (Python 3) — process-timing অংশ সত্যিই চালানো, VM boot-latency উদ্ধৃত· ১০ মিনিট
# proc_timing.py -- measure how long it takes the HOST KERNEL to create a
# new, isolated process (the container-style primitive: fork()+exec() --
# the userspace-visible cost of what clone() with namespace flags does) vs.
# printing a cited, published figure for full VM boot time (which this
# sandbox cannot measure directly -- no hypervisor available here).
import subprocess
import time

N = 200

# Each iteration: fork+exec a brand-new OS process running /bin/true.
# This models the CORE operation a container runtime performs to "start a
# container" -- ask the kernel for a new process -- with none of the
# image-unpacking/network-setup overhead layered on top by Docker.
start = time.perf_counter()
for _ in range(N):
    subprocess.run(["/usr/bin/true"], check=True)
elapsed = time.perf_counter() - start

per_process_ms = (elapsed / N) * 1000
print(f"processes started        : {N}")
print(f"total wall time           : {elapsed:.4f} s")
print(f"average per process        : {per_process_ms:.3f} ms")
print()
print("-- cited, not measured here (no hypervisor in this sandbox) --")
print("typical full VM boot (BIOS/UEFI + bootloader + kernel + init)")
print("  general-purpose VM (KVM/QEMU, full firmware+OS boot): ~10-40 s")
print("  optimized cloud VM import/boot (AWS EC2 typical)     : ~30-60 s")
print("  Firecracker microVM (minimal kernel, no BIOS)         : ~125 ms")
print()
ratio_vs_slow_vm = (30.0) / (per_process_ms / 1000)
ratio_vs_microvm = (0.125) / (per_process_ms / 1000)
print(f"ratio, this process start vs. a 30s general VM boot : ~{ratio_vs_slow_vm:,.0f}x faster")
print(f"ratio, this process start vs. a 125ms Firecracker microVM boot : ~{ratio_vs_microvm:,.0f}x faster")

প্রকৃত চালিয়ে পাওয়া output (এই মেশিনে; সংখ্যা হার্ডওয়্যারভেদে সামান্য বদলাবে, কিন্তু মাত্রার পার্থক্য টিকে থাকবে):

processes started        : 200
total wall time           : 0.6538 s
average per process        : 3.269 ms

-- cited, not measured here (no hypervisor in this sandbox) --
typical full VM boot (BIOS/UEFI + bootloader + kernel + init)
  general-purpose VM (KVM/QEMU, full firmware+OS boot): ~10-40 s
  optimized cloud VM import/boot (AWS EC2 typical)     : ~30-60 s
  Firecracker microVM (minimal kernel, no BIOS)         : ~125 ms

ratio, this process start vs. a 30s general VM boot : ~9,178x faster
ratio, this process start vs. a 125ms Firecracker microVM boot : ~38x faster

মনে রাখা জরুরি — এই 3.269 ms-এর মধ্যে subprocess.run()-এর নিজস্ব Python-level overhead (module import, pipe সেটআপ) অন্তর্ভুক্ত, একটা raw clone() কল আরও দ্রুত হতে পারে। তবু মাত্রাটা (milliseconds বনাম দশ-সেকেন্ড) সম্পূর্ণ বাস্তব, আর ঠিক এটাই Felter et al. (2015)-এর গবেষণার কেন্দ্রীয় পর্যবেক্ষণের সাথে মেলে — container startup প্রায় সবসময় সাব-সেকেন্ড, VM startup প্রায় সবসময় সেকেন্ড-থেকে-মিনিট স্কেলে।

এটা কী প্রমাণ করে

Host kernel-কে একটা নতুন, বিচ্ছিন্ন process বানাতে বলা (container শুরু করার মূল kernel অপারেশন) একটা সম্পূর্ণ VM বুট করার চেয়ে হাজার গুণ দ্রুত — এমনকি সবচেয়ে optimized microVM-এর চেয়েও বহুগুণ দ্রুত।

নিজে বানান

BUILD IT

IsolationBoundaryCalculator — attack surface আর density-কে সংখ্যায় নামানো

Python · ●●●○○
  1. Linux x86-64 kernel-এর মোট syscall সংখ্যা (container-এর attack surface প্রক্সি) আর একটা typical QEMU/KVM guest-এর exposed device সংখ্যা (VM-এর attack surface প্রক্সি) সংজ্ঞায়িত করুন
  2. দুইটার অনুপাত বের করে দেখান container-এর kernel-facing surface কতগুণ বড়
  3. VM ও container-এর প্রামাণ্য per-instance base memory overhead দিয়ে একটা ফিক্সড RAM budget-এ কয়টা করে চলতে পারবে তা হিসাব করুন
  4. density ratio বের করে দেখান — একই RAM-এ কতগুণ বেশি container চলতে পারে VM-এর তুলনায়

লক্ষ্য: এই লেসনের দুইটা কেন্দ্রীয় দাবি — “container-এর attack surface অনেক বড়” আর “container-এর density অনেক বেশি” — কে শুধু বাক্যে না রেখে সংখ্যায় নামানো। প্রতিটা input constant একটা citable, বাস্তব সংখ্যা (হয় সুপরিচিত ধ্রুবক, নয় প্রামাণ্য শিল্প-পরিসংখ্যান); script শুধু গাণিতিক হিসাবটা করে।

# isolation_calc.py -- IsolationBoundaryCalculator
# Quantifies, from cited real-world figures, the two honest tradeoffs this
# lesson argues for: (1) container attack surface (whole host kernel syscall
# table) vs VM attack surface (the narrow hypervisor interface), and
# (2) density -- how many containers vs how many VMs fit in the same RAM,
# given each technology's real per-instance base memory overhead.
# Every number below is either a well-known, citable constant (Linux x86-64
# syscall count) or a documented industry figure (see lesson references);
# this script only does the arithmetic, run for real.

# ---- (1) attack surface: what an escaping process must exploit ----
LINUX_X86_64_SYSCALL_COUNT = 450          # man7.org syscalls(2) table, 2020s kernels
# A typical QEMU/KVM guest's exposed interface: the CPU virtualization
# instructions themselves (VT-x/AMD-V trap-and-emulate, not "callable" the
# way syscalls are) plus a small, fixed set of emulated/paravirtualized
# devices a guest driver actually talks to.
TYPICAL_QEMU_VIRTIO_DEVICE_SURFACE = 12   # virtio-net, virtio-blk, virtio-scsi,
                                           # virtio-console, virtio-balloon, virtio-rng,
                                           # virtio-gpu, virtio-fs, virtio-vsock, i8042,
                                           # RTC, ACPI -- the realistic device count for
                                           # a minimal cloud VM config

ratio = LINUX_X86_64_SYSCALL_COUNT / TYPICAL_QEMU_VIRTIO_DEVICE_SURFACE
print("=== attack surface: what a successful escape must exploit ===")
print(f"container escape path : entire host kernel syscall table = {LINUX_X86_64_SYSCALL_COUNT} syscalls")
print(f"VM escape path        : hypervisor CPU-virt + typical device surface = ~{TYPICAL_QEMU_VIRTIO_DEVICE_SURFACE} emulated devices")
print(f"ratio                  : container's kernel-facing surface is ~{ratio:.1f}x larger by this (rough, illustrative) count")
print()

# ---- (2) density: how many instances fit in fixed RAM ----
HOST_RAM_GB = 64
VM_BASE_OVERHEAD_MB = 512     # cited: minimal Linux guest kernel + init, no app -- typical published figure
CONTAINER_BASE_OVERHEAD_MB = 2  # cited: a namespaced/cgrouped process sharing the host kernel -- just its own userspace RSS

host_ram_mb = HOST_RAM_GB * 1024
max_vms = host_ram_mb // VM_BASE_OVERHEAD_MB
max_containers = host_ram_mb // CONTAINER_BASE_OVERHEAD_MB

print("=== density: fixed 64 GB host, overhead-only budget (app memory excluded) ===")
print(f"VM base overhead        : {VM_BASE_OVERHEAD_MB} MB/instance (own kernel + minimal OS image)")
print(f"container base overhead  : {CONTAINER_BASE_OVERHEAD_MB} MB/instance (shares host kernel, own userspace only)")
print(f"max VMs by overhead alone       : {max_vms:,}")
print(f"max containers by overhead alone : {max_containers:,}")
print(f"density ratio                    : ~{max_containers // max_vms:,}x more containers than VMs fit in the same RAM budget")

প্রকৃত চালিয়ে পাওয়া output (deterministic — হুবহু এটাই আসবে):

=== attack surface: what a successful escape must exploit ===
container escape path : entire host kernel syscall table = 450 syscalls
VM escape path        : hypervisor CPU-virt + typical device surface = ~12 emulated devices
ratio                  : container's kernel-facing surface is ~37.5x larger by this (rough, illustrative) count

=== density: fixed 64 GB host, overhead-only budget (app memory excluded) ===
VM base overhead        : 512 MB/instance (own kernel + minimal OS image)
container base overhead  : 2 MB/instance (shares host kernel, own userspace only)
max VMs by overhead alone       : 128
max containers by overhead alone : 32,768
density ratio                    : ~256x more containers than VMs fit in the same RAM budget

দুইটা সংখ্যা একসাথে পড়লে এই লেসনের পুরো যুক্তিটা এক জায়গায় ধরা পড়ে — container-এর attack surface প্রায় ৩৮ গুণ বড় (এই illustrative গণনায়; বাস্তব সংখ্যা প্রেক্ষাপটভেদে বদলাবে), অথচ একই RAM-এ container প্রায় ২৫৬ গুণ বেশি density দেয়। এই দুইটা দাবিই একসাথে সত্যি, আর এই দুইটার মধ্যে trade-off বোঝাটাই পরের section-এর “কোনটা কখন” প্রশ্নের ভিত্তি।

বাস্তব সিস্টেমে

কোনটা কখন — একটা architecture সিদ্ধান্ত, নিরাপত্তা-বনাম-দক্ষতার

এতদিনের তুলনা এখন একটা ব্যবহারিক প্রশ্নে রূপ নিতে পারে: নতুন একটা সিস্টেম ডিজাইন করছেন — VM, নাকি container?

Multi-tenant, mutually-hostile workload — সাধারণত VM-level isolation চান। যদি একই হার্ডওয়্যারে ভিন্ন ভিন্ন গ্রাহকের, পরস্পরের প্রতি সম্ভাব্য শত্রুভাবাপন্ন কোড চালাতে হয় (public cloud-এর classic উদাহরণ — AWS EC2, Google Compute Engine), attack surface-এর পার্থক্যটা সিদ্ধান্তকারী হয়ে ওঠে। একটা customer-এর কোড যদি host kernel-এর একটা syscall bug exploit করে ফেলে, আর সেই host kernel যদি ৫০০টা ভিন্ন গ্রাহকের container একসাথে চালাচ্ছে, একটা bug-ই সবার জন্য বিপর্যয়। VM (বা এই ঝুঁকি কমাতে ডিজাইন করা microVM hybrid, যা একটা অনেক পরের লেসনের বিষয়) এখানে standard পছন্দ — ছোট attack surface, hardware-স্তরের isolation।

Same-team, same-trust-level microservices — container-ই সাধারণত সঠিক পছন্দ। একটা কোম্পানির নিজস্ব ২০টা microservice, একই দল বা কাছাকাছি-বিশ্বস্ত দল লিখেছে, একই দল deploy করছে — এখানে attacker-এর মডেল আলাদা: ঝুঁকিটা বাইরের hostile কোড না, বরং bug/vulnerability। এখানে density আর startup-speed-এর সুবিধা (দ্রুত scale up/down, একই হার্ডওয়্যারে বহুগুণ বেশি service) সাধারণত জেতে। এটাই Kubernetes-এর মতো container orchestration-এর মূল ব্যবহারিক ক্ষেত্র।

বাস্তবে অনেক production সিস্টেম দুটোই ব্যবহার করে — একসাথে, স্তরে স্তরে। একটা অতি সাধারণ, অত্যন্ত ব্যাপক pattern: container VM-এর ভেতরে চলে — প্রতিটা customer বা প্রতিটা ঝুঁকিপূর্ণ workload আগে একটা VM-এ বিচ্ছিন্ন হয়, তারপর সেই VM-এর ভেতরে একাধিক container চলে, defense-in-depth-এর জন্য। এটাই কার্যত AWS-এর EC2-এর ওপর চলা বেশিরভাগ Kubernetes cluster-এর মডেল — node নিজেই একটা VM, তার ওপর container চলে।

এই দুই-মেরুর মাঝখানে আজকাল একটা তৃতীয় শ্রেণীও শক্তভাবে দাঁড়িয়েছে, যাদের নামই ইঙ্গিত দেয় তারা সরাসরি এই লেসনের ফাঁক পূরণ করতে ডিজাইন করা: Kata Containers container-এর মতো API আর workflow রেখে ভেতরে প্রতিটা “container”-কে আসলে একটা হালকা microVM-এ চালায় — অর্থাৎ container-এর সুবিধাজনক userspace আর VM-এর hardware isolation, একসাথে। gVisor ভিন্ন পথ নেয় — সম্পূর্ণ hardware virtualization ছাড়াই, একটা userspace-এ বাস্তবায়িত kernel (Sentry) container-এর syscall-গুলো আটকে নিজে পুনরায় বাস্তবায়ন করে, host kernel-কে সরাসরি ছোঁয়া থেকে বিরত রাখে। দুইটাই “শুধু container, শুধু VM” দ্বিমুখী সিদ্ধান্তের বাইরে গিয়ে attack-surface কমানোর ভিন্ন ভিন্ন কৌশল — এই hybrid জগতের বিস্তারিত একটা অনেক পরের লেসনের বিষয়, আজ শুধু এটুকু মনে রাখা যথেষ্ট যে “container বনাম VM” কোনো closed, দ্বি-মুখী সিদ্ধান্ত না।

যে ভুলগুলো সবাই করে

“Container একটা lightweight VM — শুধু ছোট আর দ্রুত একটা VM”

ভুল একটা মাত্রাগত ভুল ধারণা না, এটা একটা শ্রেণীগত ভুল। একটা VM-কে যতই ছোট করা হোক, তার এখনও নিজস্ব, বুট-করা একটা kernel থাকে — সেটা VM-ই থেকে যায়। একটা container-এর কোনো নিজস্ব kernel নেই বুট করার জন্য — এটা host kernel-এর ওপর namespace/cgroup দিয়ে বিচ্ছিন্ন একটা process-গ্রুপ। “ছোট করলে VM থেকে container পাওয়া যায়” — এই পথটাই নেই; দুইটা সম্পূর্ণ ভিন্ন isolation স্থাপত্য।

“Container VM-এর মতোই নিরাপদ, শুধু দ্রুত”

Container দ্রুত এটা সত্যি, কিন্তু “তাই সমান নিরাপদ” এই সিদ্ধান্তে লাফ দেওয়াটা ভুল। VM escape-এর জন্য hypervisor-এর সংকীর্ণ, ভারীভাবে-নিরীক্ষিত interface (CPU trap মেকানিজম, কয়েক ডজন emulated device) ভাঙতে হয়। Container escape-এর জন্য পুরো host kernel syscall table-এর (~৪৫০টা syscall, কোটি লাইনের কোডবেস) যেকোনো একটা bug যথেষ্ট। এই দুইটা attack surface-এর আকার একই মাত্রায় নেই — সৎভাবে বললে, honestly VM-level isolation-ই এখনও বেশিরভাগ threat model-এ কড়া সীমানা হিসেবে ধরা হয়।

“প্রতিটা container image-এর ভেতরে একটা মিনি অপারেটিং সিস্টেম চলে (যেমন 'Alpine Linux container', 'Ubuntu container')”

একটা alpine/ubuntu container image-এ কোনো চলমান kernel নেই — এতে শুধু userspace ফাইল আছে (libc, coreutils, shell) যা bind mount/overlay filesystem দিয়ে container-এর mount namespace-এ বসানো হয়। যে kernel সেই ফাইলগুলোর ওপরের প্রতিটা syscall handle করে, সেটা সবসময় host-এর kernel — image-এর ভেতরে কোনো Linux kernel বান্ডিল করা নেই। এই কারণেই একটা Alpine container আর একটা Ubuntu container একই মেশিনে, একই host kernel শেয়ার করেই স্বাভাবিকভাবে পাশাপাশি চলতে পারে।

“Container সবসময় ভালো পছন্দ কারণ এটা দ্রুত আর সস্তা — VM এখন প্রায় অপ্রয়োজনীয়”

স্টার্টআপ-গতি আর density একমাত্র নির্ণায়ক না। যেখানে workload-গুলো পরস্পরের প্রতি সম্ভাব্য hostile (multi-tenant public cloud, untrusted user-submitted code), সেখানে attack-surface-এর পার্থক্যটাই নির্ধারক হয়ে ওঠে, গতি না। VM (বা microVM hybrid) এখনও সেই threat model-এর জন্য শিল্প-মান — Firecracker-এর মতো প্রযুক্তির অস্তিত্বই প্রমাণ করে বড় বড় cloud provider-রাও এখনও প্রতিটা untrusted invocation-এর জন্য VM-স্তরের isolation-এ বিনিয়োগ করে।

বুঝেছেন কি না দেখুন

1এক বাক্যে — VM ঠিক কী virtualize করে, আর container ঠিক কী virtualize করে?স্মরণ
VM virtualize করে hardware-কে — প্রতিটা guest নিজস্ব সম্পূর্ণ kernel-সহ একটা সম্পূর্ণ কম্পিউটারের বিভ্রম পায়। Container virtualize করে operating system-কে — সব container একটাই host kernel শেয়ার করে, namespace (visibility) আর cgroup (resource limit) দিয়ে বিচ্ছিন্ন হয়।
2“Container একটা হালকা VM” — এই বাক্যটা কেন ভুল, শুধু imprecise না? পার্থক্যটা কী মাত্রার (degree), নাকি ধরনের (kind)?যুক্তি
পার্থক্যটা ধরনের। একটা VM-কে যতই ছোট করা হোক (RAM কমান, disk ছোট করুন), এটা তবু একটা VM-ই থাকে কারণ তার নিজস্ব, বুট-হওয়া kernel থাকে। একটা container-এর কোনো kernel-ই নেই বুট করার — এটা কোনো VM-এর “ছোট সংস্করণ” না, বরং সম্পূর্ণ ভিন্ন একটা isolation স্থাপত্য (host kernel-এর ওপর namespace/cgroup দিয়ে বিচ্ছিন্ন process)। “ছোট করলে container পাওয়া যায়” এমন কোনো continuum নেই।
3একজন আক্রমণকারী একটা container-এর ভেতরে কোড execute করার ক্ষমতা পেয়েছে। তাকে “বেরিয়ে আসতে” ঠিক কী দরকার, আর এটা VM escape-এর চেয়ে কীভাবে ভিন্ন?যুক্তি
Container-এর ভেতরের প্রতিটা syscall সরাসরি host kernel-এ যায় — namespace/cgroup সেই syscall-এর ফলাফল সীমিত করে কিন্তু dispatch mechanism নিজেকে বদলায় না। তাই আক্রমণকারীর লক্ষ্যবস্তু হলো host kernel-এর ~৪৫০টা syscall-এর যেকোনো একটার বাগ — পুরো kernel কোডবেস। VM escape-এ আক্রমণকারীর লক্ষ্যবস্তু শুধু hypervisor-এর সংকীর্ণ interface — CPU trap মেকানিজম আর কয়েক ডজন emulated device — যা তুলনামূলক ছোট আর ভারীভাবে নিরীক্ষিত।
4একটা মেশিনে একটাই container চালানো হচ্ছে, memory.max cgroup limit ছাড়া কোনো resource সীমা ছাড়াই। এই container একটা fork bomb চালালে কী ঘটবে, আর namespace কি এটা ঠেকাতে পারত?প্রয়োগ
না — namespace কোনো resource সীমা নিয়ন্ত্রণ করে না, শুধু visibility নিয়ন্ত্রণ করে (operating-systems/namespaces-and-cgroups লেসনের orthogonality আলোচনা)। pids.max cgroup সীমা ছাড়া, একটা fork bomb host-এর পুরো PID স্পেস আর CPU খেয়ে ফেলতে পারে, host-এর অন্য সব process-সহ — namespace শুধু এই container-কে অন্যদের PID তালিকা দেখা থেকে আটকায়, নিজের অতিরিক্ত resource ব্যবহার থেকে না। এটাই প্রমাণ করে কেন namespace একা যথেষ্ট না, cgroup আলাদাভাবে দরকার।
5একটা 128 MB RAM-এর ছোট IoT gateway ডিভাইসে ৩০টা ছোট, একে অপরকে বিশ্বাস করা microservice চালাতে হবে। VM নাকি container — কোনটা বাস্তবসম্মত, আর কেন?প্রয়োগ
Container। ৩০টা VM-এর প্রতিটার জন্য প্রায় কয়েকশো MB base overhead লাগলে 128 MB-তে একটাও পুরোপুরি চলবে না। Container-এর base overhead প্রতিটার কয়েক MB মাত্র (শুধু userspace ফাইল, একই kernel শেয়ার করে) — এই density-র সুবিধাটাই এখানে নির্ধারক, আর যেহেতু সব microservice পারস্পরিক বিশ্বস্ত, container-এর তুলনামূলক বড় attack surface এখানে গ্রহণযোগ্য ঝুঁকি।
6আপনাকে একটা multi-tenant “code playground” SaaS ডিজাইন করতে বলা হয়েছে — গ্রাহকরা arbitrary, untrusted কোড আপলোড করে চালাবেন, ভিন্ন ভিন্ন গ্রাহকের কোড একে অপরের প্রতি সম্ভাব্য শত্রুভাবাপন্ন ধরে নিতে হবে, কিন্তু প্রতিটা execution দ্রুত (কয়েকশো মিলিসেকেন্ডে) শুরু হতে হবে আর একই হার্ডওয়্যারে হাজার হাজার concurrent execution চালাতে হবে। শুধু container, শুধু full VM, নাকি hybrid — কোনটা বেছে নেবেন, আর সিদ্ধান্তের প্রতিটা অংশ কোন trade-off-এর ভিত্তিতে?ডিজাইন
শুধু container প্রত্যাখ্যাত হবে — untrusted, mutually-hostile কোডের জন্য পুরো host kernel syscall surface একটা ভাগ-করা bug-এ প্রতিটা tenant-কে প্রকাশ করে দেয়, এই স্কেলে অগ্রহণযোগ্য ঝুঁকি। শুধু full VM-ও ব্যবহারিকভাবে কঠিন — সেকেন্ড-স্কেল boot latency আর কয়েকশো MB per-instance overhead হাজার হাজার concurrent, sub-second execution-এর লক্ষ্যের সাথে সাংঘর্ষিক। সঠিক উত্তর হলো microVM hybrid (Firecracker-জাতীয়) — প্রতিটা tenant execution একটা নিজস্ব, ন্যূনতম, ~১২৫ ms-এ বুট হওয়া VM-এ চলে, যা hardware-স্তরের isolation বজায় রাখে অথচ density আর গতি container-এর কাছাকাছি নিয়ে আসে। এটাই ঠিক AWS Lambda-র আসল architecture — একটা ভবিষ্যতের লেসনের বিষয়।

এরপর কী

আজকে দুইটা নাম বারবার এসেছে কিন্তু ইচ্ছাকৃতভাবে গভীরে যাওয়া হয়নি — namespace (একটা process কী দেখতে পায়) আর cgroup (একটা process কতটুকু ব্যবহার করতে পারে)। operating-systems/namespaces-and-cgroups লেসন এই দুইটার একটা survey-স্তরের পরিচয় ইতিমধ্যে দিয়েছে — ছয় ধরনের namespace, clone()/unshare() flag, cgroup v2-এর filesystem interface।

পরের লেসন, virtualization-cloud/linux-namespaces-deep-dive, ঠিক namespace-এর ওপর অনেক বেশি গভীরে যাবে — শুধু ছয়টা না, সবগুলো namespace type (cgroup namespace, time namespace-সহ), প্রতিটার নিজস্ব clone()/unshare() flag-এর নির্ভুল semantics, আর namespace hierarchy-র ভেতরের kernel data structure। আজকের লেসন যে সীমানাটা টেনেছে — “VM-এর জগত এখানে শেষ, container-এর জগত এখান থেকে শুরু” — পরের কয়েকটা লেসন সেই container-এর জগতের প্রতিটা ইট খুলে দেখাবে।

আরও পড়ুন

  • An Updated Performance Comparison of Virtual Machines and Linux Containers — Wes Felter, Alexandre Ferreira, Ram Rajamony, Juan Rubio — IBM Research (IEEE ISPASS, 2015) · VM ও Docker container-এর মধ্যে CPU, memory, disk I/O, network throughput-এর সরাসরি, পরিমাপযোগ্য তুলনা — এই লেসনের startup/overhead দাবিগুলোর পেছনের প্রামাণ্য গবেষণা
  • Firecracker: Lightweight Virtualization for Serverless Applications — Alexandru Agache et al. — Amazon Web Services (USENIX NSDI, 2020) · microVM boot-latency-এর প্রামাণ্য উৎস — কেন একটা minimal, firmware-বিহীন VM কয়েক মিলিসেকেন্ডে বুট করতে পারে, আর কেন এটা এখনও একটা পূর্ণাঙ্গ দ্বিতীয় kernel
  • NIST SP 800-190 — Application Container Security Guide — Murugiah Souppaya, John Morello, Karen Scarfone — NIST · Container ও VM isolation-এর নিরাপত্তা-মডেলের পার্থক্য নিয়ে একটা সরকারি, শিল্প-প্রামাণ্য তুলনা — 'container = VM না' এই দাবির নিরাপত্তা-নীতিগত ভিত্তি
  • Control Group v2 — kernel admin guide · cgroup-এর মূল kernel documentation — এই লেসন যে দাবি করে যে container resource-limiting কোনো hypervisor ছাড়াই সম্ভব, তার প্রাথমিক উৎস