পাঠ ৫০ · ৫৬-এর মধ্যে · মডিউল ১২
Home / Courses / Operating Systems (OS) / ভার্চুয়ালাইজেশন ও কন্টেইনার

কন্টেইনার — OS-লেভেল ভার্চুয়ালাইজেশন

Containers — OS-level virtualization
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • কন্টেইনার কীভাবে VM (L49) থেকে মৌলিকভাবে ভিন্ন — কার্নেল শেয়ারিং বনাম কার্নেল ডুপ্লিকেশন
  • namespaces (আইসোলেশন) ও cgroups (রিসোর্স সীমাবদ্ধতা) — Linux-এ কন্টেইনারের দুটি মূল স্তম্ভ
  • কেন কন্টেইনার এত দ্রুত চালু হয়, এবং এর বিনিময়ে কী নিরাপত্তা-ট্রেড-অফ মেনে নিতে হয়
  • Python দিয়ে একটি বাস্তব PID-namespace-স্টাইল সিমুলেশন — কীভাবে দুটি কন্টেইনার একই লোকাল PID পেয়েও প্রকৃতপক্ষে ভিন্ন প্রসেস থাকে

১ · কন্টেইনার — কার্নেল শেয়ার করে বিচ্ছিন্নতার ভ্রম

L49-এর ভার্চুয়াল মেশিনের বিপরীতে, কন্টেইনারContainerহোস্ট OS-এর একক কার্নেল শেয়ার করে, কিন্তু OS-লেভেল আইসোলেশন ফিচার ব্যবহার করে প্রতিটি কন্টেইনারকে তার নিজস্ব ফাইল সিস্টেম, প্রসেস ট্রি, নেটওয়ার্ক স্ট্যাক ও রিসোর্স সীমার ভ্রম দেয়। নিজের আলাদা কার্নেল চালায় না — এটি হোস্ট OS-এর একটিমাত্র কার্নেল শেয়ার করে। কিন্তু OS-লেভেল আইসোলেশন ফিচার ব্যবহার করে প্রতিটি কন্টেইনারকে দেওয়া হয় তার নিজস্ব বিচ্ছিন্ন ফাইল সিস্টেম, প্রসেস ট্রি, নেটওয়ার্ক স্ট্যাক এবং রিসোর্স সীমার ভ্রম।

২ · Linux-এর দুই স্তম্ভ — namespaces ও cgroups

Linux-এ এই বিভ্রম দুটি সম্পূর্ণ ভিন্ন, একসাথে কাজ করা মেকানিজম দিয়ে তৈরি হয় — একটি আইসোলেশনের জন্য, আরেকটি রিসোর্স সীমাবদ্ধতার জন্য।

NamespacesNamespacesপ্রতিটি namespace টাইপ একটি নির্দিষ্ট ধরনের গ্লোবাল সিস্টেম রিসোর্সকে বাইরের প্রসেস থেকে লুকিয়ে দেয়। — আইসোলেশন
প্রতিটি namespace টাইপ একটি নির্দিষ্ট গ্লোবাল রিসোর্সকে বাইরের প্রসেস থেকে লুকিয়ে দেয় — যেমন PID namespace একটি কন্টেইনারের প্রসেস আইডিগুলোকে ১ থেকে নতুন করে শুরু হওয়া বলে মনে করায়, যা কন্টেইনারের বাইরের/অন্য কন্টেইনারের প্রসেসের কাছে অদৃশ্য থাকে।
Cgroups (Control Groups) — রিসোর্স সীমাবদ্ধতা
একদল প্রসেস মিলে সর্বমোট কতটা CPU, মেমরি ইত্যাদি ব্যবহার করতে পারবে তা সীমাবদ্ধ করে দেয় — সরাসরি M2-এর প্রসেস/রিসোর্স ধারণা ও M7-এর মেমরি বণ্টনের সাথে যুক্ত, শুধু এখন প্রতি-প্রসেস নয়, প্রতি-কন্টেইনার প্রয়োগ হয়।
গতি বনাম বিচ্ছিন্নতা — একটি সরাসরি ট্রেড-অফ

যেহেতু কন্টেইনারে কোনো আলাদা কার্নেল বুট হয় না, এটি সেকেন্ডের ভগ্নাংশে চালু হয় — L49-এর VM-এর তুলনায় (যাকে একটি সম্পূর্ণ গেস্ট OS বুট করতে হয়) বিশাল কর্মক্ষমতা পার্থক্য। কিন্তু এর মূল্য হলো দুর্বলতর বিচ্ছিন্নতা — যেহেতু সব কন্টেইনার একই কার্নেল শেয়ার করে, একটি কার্নেল-স্তরের নিরাপত্তা দুর্বলতা তাত্ত্বিকভাবে একাধিক কন্টেইনারকে প্রভাবিত করতে পারে, কারণ তারা প্রকৃতপক্ষে সম্পূর্ণ পৃথক OS ইনস্ট্যান্স নয় (গভীর বিশ্লেষণের জন্য দেখুন Cybersecurity কোর্সের কন্টেইনার-সিকিউরিটি বিষয়বস্তু)।

৩ · একটি PID-namespace-স্টাইল সিমুলেশন

নিচে আমরা PID namespace-এর মূল ধারণাটি সিমুলেট করছি — একটি গ্লোবাল "হোস্ট" প্রসেস-আইডি কাউন্টার (যা কখনো পুনরাবৃত্তি হয় না, প্রকৃত সিস্টেমের প্রতিটি প্রসেসের জন্য অনন্য) এবং প্রতিটি কন্টেইনারের নিজস্ব লোকাল PID কাউন্টার (যা সবসময় ১ থেকে শুরু হয়)।

Python
# PID namespace-স্টাইল আইসোলেশন সিমুলেশন -- বাস্তব Linux namespace নয়, শুধুই ধারণাগত মডেল

containers = {}                    # container_id -> {"next_local_pid": int, "pid_map": {local: host}}
host_pid_counter = {"next": 100}   # গ্লোবাল হোস্ট PID অ্যালোকেটর -- প্রতিটি প্রসেসের জন্য সত্যিকারের অনন্য মান

def create_process_in_container(container_id, containers, host_pid_counter):
    if container_id not in containers:
        containers[container_id] = {"next_local_pid": 1, "pid_map": {}}

    container = containers[container_id]
    local_pid = container["next_local_pid"]        # এই কন্টেইনারের নিজস্ব দৃষ্টিভঙ্গি থেকে PID
    host_pid = host_pid_counter["next"]              # হোস্টের দৃষ্টিভঙ্গি থেকে প্রকৃত, অনন্য PID

    container["pid_map"][local_pid] = host_pid
    container["next_local_pid"] += 1
    host_pid_counter["next"] += 1

    return local_pid, host_pid


# দুটি ভিন্ন কন্টেইনারে পালাক্রমে প্রসেস তৈরি
local_a1, host_a1 = create_process_in_container("web", containers, host_pid_counter)
print(f"container 'web'  -> local pid {local_a1}, প্রকৃত host pid {host_a1}")

local_b1, host_b1 = create_process_in_container("db", containers, host_pid_counter)
print(f"container 'db'   -> local pid {local_b1}, প্রকৃত host pid {host_b1}")

local_a2, host_a2 = create_process_in_container("web", containers, host_pid_counter)
print(f"container 'web'  -> local pid {local_a2}, প্রকৃত host pid {host_a2}")

print("\n--- মূল পয়েন্ট যাচাই ---")
print(f"'web' ও 'db' উভয়েরই একটি প্রসেস local pid {local_a1} == {local_b1}? "
      f"{local_a1 == local_b1}")
print(f"কিন্তু তাদের প্রকৃত host pid ভিন্ন ({host_a1} বনাম {host_b1})? "
      f"{host_a1 != host_b1}")

    
লক্ষ্য করুন — web কন্টেইনারের প্রথম প্রসেস ও db কন্টেইনারের প্রথম প্রসেস উভয়েরই লোকাল PID ১ (প্রতিটি কন্টেইনার নিজেকে "আমিই প্রথম প্রসেস, PID ১" বলে বিশ্বাস করে), অথচ হোস্টের দৃষ্টিভঙ্গিতে তাদের প্রকৃত PID ভিন্ন (১০০ ও ১০১) — এটিই PID namespace-এর মূল বিভ্রম, ঠিক যেমন বাস্তব Docker/LXC কন্টেইনারে ঘটে।
মূল কথা · Key takeaway

কন্টেইনার কোনো নতুন "মেশিন এমুলেশন" নয় (L49-এর VM যেমন) — এটি একই কার্নেলের উপর namespaces (কী দেখা যায়) ও cgroups (কতটুকু ব্যবহার করা যায়) নামক দুটি নিয়ন্ত্রণ-প্রক্রিয়া দিয়ে তৈরি একটি হালকা-ওজনের বিভ্রম। বাস্তব Docker/Kubernetes ওয়ার্কফ্লোতে এই মেকানিজম কীভাবে ব্যবহারিকভাবে প্রয়োগ হয় তা গভীরভাবে দেখতে চাইলে যান Cloud Computing & DevOps কোর্সে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ namespaces ও cgroups — এই দুটো মেকানিজম কি একে অপরের বিকল্প, নাকি একসাথে কাজ করে? একটি ছাড়া আরেকটি একা থাকলে কী সমস্যা হতো?

এগুলো একে অপরের পরিপূরক, বিকল্প নয়। শুধু namespaces থাকলে (cgroups ছাড়া) একটি কন্টেইনার "দেখতে" বিচ্ছিন্ন মনে হতো, কিন্তু কোনো সীমা ছাড়াই সম্পূর্ণ হোস্টের CPU/মেমরি খেয়ে ফেলতে পারত, অন্য কন্টেইনারদের ক্ষুধার্ত রেখে (M7-এর রিসোর্স-বণ্টন সমস্যার সরাসরি পুনরাবৃত্তি)। শুধু cgroups থাকলে (namespaces ছাড়া) রিসোর্স ব্যবহার সীমাবদ্ধ হতো ঠিকই, কিন্তু একটি কন্টেইনারের প্রসেস অন্য কন্টেইনারের প্রসেস তালিকা, ফাইল সিস্টেম বা নেটওয়ার্ক সরাসরি দেখতে/প্রভাবিত করতে পারত — কোনো প্রকৃত বিচ্ছিন্নতার ভ্রমই তৈরি হতো না।

প্র ০২ কন্টেইনারের "দুর্বলতর বিচ্ছিন্নতা" ঠিক কোন পরিস্থিতিতে সবচেয়ে বড় ঝুঁকি হয়ে দাঁড়ায়? এমন কোনো ব্যবহারিক পরিস্থিতির কথা ভাবুন যেখানে এই কারণে VM (L49) বেছে নেওয়া উচিত হবে।

যখন একই হার্ডওয়্যারে সম্পূর্ণ অপরিচিত বা পারস্পরিক-অবিশ্বস্ত ওয়ার্কলোড (যেমন ভিন্ন ভিন্ন গ্রাহকের কোড, যাদের একজন হয়তো ইচ্ছাকৃতভাবে ক্ষতিকর) একসাথে চালাতে হয়, তখন কন্টেইনারের শেয়ার্ড-কার্নেল ঝুঁকি সবচেয়ে গুরুত্বপূর্ণ হয়ে ওঠে — একটি কন্টেইনার থেকে কার্নেল-দুর্বলতা কাজে লাগিয়ে পালানো (escape) সম্ভব হলে অন্য সব কন্টেইনার ঝুঁকিতে পড়ে। এমন পরিস্থিতিতে (যেমন একটি পাবলিক ক্লাউড প্ল্যাটফর্ম ভিন্ন ভিন্ন গ্রাহকের কোড চালাচ্ছে) প্রতিটি গ্রাহককে আলাদা VM-এ (আলাদা কার্নেলসহ) রাখা অনেক বেশি নিরাপদ পছন্দ — L51-এ এই সিদ্ধান্ত-কাঠামো আরও বিস্তারিতভাবে দেখা হবে।

প্র ০৩ উপরের কোড সেলে যদি "web" কন্টেইনারে আরও একটি (তৃতীয়) প্রসেস তৈরি করা হতো, তার লোকাল PID ও host PID কী হতো? হাতে হিসাব করে যাচাই করুন।

কোড চালানোর পর web-এর next_local_pid ইতিমধ্যে ৩-এ পৌঁছে গেছে (প্রথম প্রসেসের পর ১→২, দ্বিতীয় প্রসেসের পর ২→৩), তাই তৃতীয় প্রসেসের লোকাল PID হবে ৩। আর host_pid_counter["next"] ততক্ষণে ১০২-এ পৌঁছেছে (১০০→১০১→১০২, প্রতিটি নতুন প্রসেসের পর ১ বেড়েছে, কন্টেইনার নির্বিশেষে), তাই তৃতীয় প্রসেসের প্রকৃত host PID হবে ১০২। এটি নিশ্চিত করে যে host PID কাউন্টার সব কন্টেইনার জুড়ে একটিমাত্র, কখনো-না-পুনরাবৃত্ত ধারা বজায় রাখে, যেখানে প্রতিটি কন্টেইনারের লোকাল কাউন্টার সম্পূর্ণ স্বাধীন।

অনুশীলন

  1. পরীক্ষা করুন: উপরের কোড সেলে "db" কন্টেইনারে আরও দুটি প্রসেস তৈরি করে (মোট তিনটি কল) Run চাপুন — db-এর তৃতীয় প্রসেসের লোকাল PID এবং host PID কী আসে দেখুন, এবং হাতে হিসাব মিলিয়ে নিন।

    মূল কোডে তিনটি প্রসেস তৈরির পর host_pid_counter["next"] থাকে ১০৩-এ। db-এ আরও দুটি প্রসেস যোগ করলে সেগুলো host PID ১০৩ ও ১০৪ পাবে, কিন্তু db-এর নিজস্ব next_local_pid (যা আগে থেকেই ২-এ ছিল প্রথম প্রসেসের পর) এখন ২ ও ৩ বরাদ্দ করবে সেই দুই নতুন প্রসেসকে। ফলাফল — db-এর তৃতীয় প্রসেস (কোড অনুসারে সামগ্রিকভাবে চতুর্থ তৈরি হওয়া প্রসেস) পাবে লোকাল PID ৩, host PID ১০৪।

  2. চিন্তা করুন: এই সিমুলেশনটি শুধু PID namespace দেখাচ্ছে। একটি বাস্তব কন্টেইনার রানটাইমকে আরও কোন কোন namespace টাইপ (যেমন নেটওয়ার্ক, ফাইল সিস্টেম মাউন্ট) বাস্তবায়ন করতে হয় বলে আপনার মনে হয়, এবং কেন?

    একটি সম্পূর্ণ বিচ্ছিন্নতার ভ্রম তৈরি করতে অন্তত নেটওয়ার্ক namespace (প্রতিটি কন্টেইনারের নিজস্ব নেটওয়ার্ক ইন্টারফেস/IP ঠিকানার ভ্রম, একে অপরের নেটওয়ার্ক ট্রাফিক না দেখা) এবং মাউন্ট/ফাইল-সিস্টেম namespace (প্রতিটি কন্টেইনারের নিজস্ব রুট ফাইল সিস্টেমের ভ্রম, একে অপরের ফাইল সরাসরি না দেখা) প্রয়োজন — কারণ শুধু PID বিচ্ছিন্ন থাকলেও যদি সব কন্টেইনার একই ফাইল সিস্টেম বা নেটওয়ার্ক দেখতে পারে, তাহলে "বিচ্ছিন্নতা"-র দাবিটি অর্থহীন হয়ে যায় — বাস্তব Linux কন্টেইনার রানটাইম (Docker ইত্যাদি) তাই একসাথে একাধিক namespace টাইপ ব্যবহার করে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
L49 · ভার্চুয়াল মেশিন ও হাইপারভাইজার