এই পর্বে যা শিখবেন
- “আমার কম্পিউটারে তো চলে” সমস্যাটি কোথা থেকে আসে, আর container কোন অংশটি সমাধান করে
- Image, container আর registry-র পার্থক্য, আর
docker run-এর পুরো পথ - আসল Docker আউটপুট দেখে প্রমাণ করা যে container একটি process
- VM-এর সাথে তুলনা: kernel, চালুর সময়, ঘনত্ব আর বিচ্ছিন্নতা
- Docker Engine, Docker Desktop আর Podman-এর পার্থক্য, আর কখন container ব্যবহার করবেন না
১ · “আমার কম্পিউটারে তো চলে” — সমস্যাটা কোথায়
একটি কাল্পনিক অনলাইন বাজার অ্যাপ ধরুন। শিলা ল্যাপটপে কোড লিখলেন, পরীক্ষা করলেন, সব ঠিক। রাতে server-এ তোলার পর অ্যাপ চালু হলো না। শিলা বললেন, “আমার কম্পিউটারে তো চলে!” কথাটি সত্য। কিন্তু গ্রাহক তো শিলার ল্যাপটপ থেকে কেনাকাটা করেন না।
কারণ খুঁজলে দেখা যায়, app কখনো একা চলে না। তার পেছনে একটি লম্বা তালিকা থাকে: ভাষার runtime, library, OS-এর প্যাকেজ, environment variable, ফাইলের পথ। এই তালিকার একটি জিনিস আলাদা হলেই আচরণ বদলাতে পারে।
| বিষয় | শিলার ল্যাপটপ | Server | যা ঘটল |
|---|---|---|---|
| Python | 3.12 | 3.9 | নতুন syntax-এ error |
| Image library | সদ্য ইনস্টল করা | অনেক পুরোনো | একটি function নেই |
| OS-এর প্যাকেজ | আগে থেকেই ছিল | ইনস্টল করা হয়নি | অ্যাপ চালু হওয়ার সময়ই বন্ধ |
| ফাইলের নাম | বড়-ছোট হাতের অক্ষর আলাদা ধরে না | আলাদা ধরে | Logo.png পাওয়া গেল না |
সমস্যাটি আরও বাড়ে যখন একই server-এ দুটি app বসে। একটি app চায় একটি library-র পুরোনো সংস্করণ, আরেকটি চায় নতুন। OS-এ একই জায়গায় দুটি সংস্করণ রাখা কঠিন। একটি ঠিক করলে আরেকটি ভাঙে। এই জট-কেই বলে dependency hell. পর্ব ৮-এ দেখা package dependency এখানে শত্রু হয়ে ওঠে।
আগেও সমাধান ছিল। পর্ব ১৬-এর machine image পুরো server-এর ছবি রাখে। কিন্তু সেটি ভারী, GB-র হিসাবে বড়, আর ল্যাপটপে সহজে চলে না। Python-এর virtualenv শুধু Python library আলাদা করে, OS-এর প্যাকেজ নয়। হাতে লেখা নির্দেশিকা সময়ের সাথে পুরোনো হয়, আর server-গুলো ধীরে ধীরে একে অন্যের থেকে আলাদা হয়ে যায় (drift).
Container-এর ধারণা সরল: “এই লাইব্রেরিগুলো ইনস্টল করুন” না বলে app আর তার পুরো ব্যবহারকারী-স্তরের পরিবেশ একসাথে প্যাক করে পাঠানো। যা পরীক্ষা করা হয়েছে, হুবহু সেটিই চলবে।
লঞ্চঘাটের শিক্ষা — মানক বাক্স
সদরঘাটে একটি লঞ্চে মাল ওঠার দৃশ্য ভাবুন। চালের বস্তা, তেলের টিন, কাচের বাক্স, ঝুড়িভর্তি মুরগি। প্রতিটি জিনিসের ওঠানো, রাখা আর নামানোর নিয়ম আলাদা। কুলিকে জানতে হয় কোনটি ভঙ্গুর, কোনটি ভারী। এবার ভাবুন চট্টগ্রাম বন্দর। জাহাজ থেকে লোহার বাক্স নামছে, সোজা ট্রেলারে উঠছে, ট্রেলার থেকে ট্রেনে। ক্রেন জানে না ভেতরে পোশাক না যন্ত্রপাতি। ক্রেন শুধু বাক্সের মাপ আর কোনার হুক চেনে।
এই বদলটিই software-এ এসেছে। মানক বাক্সের আগে server-এর প্রশাসককে প্রতিটি app-এর নিয়ম জানতে হতো: Java হলে এই ধাপ, Python হলে ওই ধাপ। Container আসার পর platform-কে শুধু একটি কথা জানতে হয়: “এই image চালান”। ভেতরে Java, Python না Go, platform-এর তা জানার দরকার নেই।
| বন্দরে | Software-এ | ফল |
|---|---|---|
| মানক মাপের লোহার বাক্স | OCI image ফরম্যাট | যেকোনো সরঞ্জাম একই বাক্স চেনে |
| বাক্সের ভেতরের মাল | App আর library | মালিক নিজের মতো সাজান |
| ক্রেন, জাহাজ, ট্রেলার | Runtime, CI, Kubernetes | বাক্স খোলার দরকার পড়ে না |
| বন্দরের গুদাম | Registry | এক জায়গায় রেখে অনেক জায়গায় পাঠানো |
তুলনাটির একটি সীমা মনে রাখুন। লোহার বাক্স একদম আলাদা জিনিস। কিন্তু software container একই kernel-এর ওপর বসে থাকে, যেন সব বাক্সের তলা একটিই। এই একটি পার্থক্য থেকেই এই পর্বের বেশিরভাগ trade-off এসেছে।
২ · Image, container আর registry
এই তিনটি শব্দ গুলিয়ে ফেললে Docker-এর কোনো আলোচনাই পরিষ্কার হবে না। রান্নাঘরের রেসিপি খাতা দিয়ে ভাবুন। Dockerfile হলো রেসিপি। Image হলো সেই রেসিপিতে বানানো, হিমায়িত করা খাবারের প্যাকেট, যা আর বদলায় না। Container হলো প্যাকেট খুলে গরম করে পাতে দেওয়া খাবার: একই প্যাকেট থেকে অনেক পাত বাড়া যায়, আর এক পাতে লবণ দিলে অন্য পাতে তা যায় না। Registry হলো হিমাগার, যেখান থেকে যেকোনো শাখা প্যাকেট নিয়ে যায়।
Image
শুধু-পড়ার ফাইলের স্তর আর একটি নির্দেশ: কোন প্রোগ্রাম চালাতে হবে। চলে না, শুধু থাকে। একে layer-এ ভাগ করা থাকে।
নিষ্ক্রিয়, অপরিবর্তনীয়Container
Image-এর ওপর একটি writable layer বসিয়ে চালানো process. একটি image থেকে অনেক container হয়।
চলমান, ক্ষণস্থায়ীRegistry
Image রাখার server. push করে তোলা হয়, pull করে নামানো হয়। Docker Hub আর cloud provider-দের registry এর উদাহরণ।
রেসিপি থেকে চলমান process
Dockerfile → build → image → push → registry → pull → run
- Dockerfile
buildরেসিপি - Image
pushহিমায়িত প্যাকেট - Registry
pullহিমাগার - Container
runপাতের খাবার
Image-এর নাম পড়তে শিখুন। docker.io/library/nginx:1.26-alpine নামের চারটি অংশ: registry-র ঠিকানা (docker.io), namespace (library, official image-এর জায়গা), repository (nginx) আর tag (1.26-alpine). Registry-র নাম না লিখলে Docker ধরে নেয় docker.io. Tag না লিখলে ধরে নেয় latest.
latest মানে “সবচেয়ে নতুন” নয়। এটি একটি সাধারণ নাম, যা repository-তে push করার অনুমতি যাঁর আছে তিনি যেকোনো image-এ বসাতে পারেন। Tag বদলানো যায়, তাই আজকের nginx:1.26-alpine আর আগামী মাসেরটি আলাদা image হতে পারে। হুবহু একই image চাইলে digest (sha256:…) লাগে, যা ফাইলের বিষয়বস্তু থেকেই হিসাব করা হয়।
docker run চাপলে কী হয়
এই পর্বের সব Docker আউটপুট একটি আসল run থেকে নেওয়া (Docker Engine 29, BuildKit). প্রয়োজনমতো লাইন ছাঁটা হয়েছে, আর ছাঁটা জায়গায় … আছে। ID, digest, আকার আর সময় আপনার মেশিনে আলাদা হবে। নিচের stepper-এ যে image আগে থেকে মেশিনে ছিল না, তা নামিয়ে চালানো হয়েছে।
- CLI
docker runঅনুরোধ - Registry
pulllayer নামানো - Create
rw layerপ্রস্তুতি - Start
PID 1process
$ docker run -d --name abcl-cf-pull --hostname demo \
-p 127.0.0.1:18362:80 nginx:1.26-alpine
Unable to find image 'nginx:1.26-alpine' locally
1.26-alpine: Pulling from library/nginx 5ab072e0bc4f: Pulling fs layer b8651333baae: Pulling fs layer … আরও ৬টি layer …
b8651333baae: Download complete … 0a9a5dfd008f: Pull complete 5e18ef93c435: Pull complete …
Digest: sha256:1eadbb07820339e8bbfed18c771691970baee292ec4ab2558f1453d26153e22d
Status: Downloaded newer image for nginx:1.26-alpine
writable layer : নতুন, খালি hostname : demo port : 127.0.0.1:18362 → container 80 আলাদা দৃশ্য, সীমা : namespace, cgroup
$ (docker run-এর শেষ লাইন: container id) e3120d3e3aa9a91a7a46b31b156346dfbbfcd8b4cd4a7e902ecb6bf4857387ab $ docker logs abcl-cf-pull /docker-entrypoint.sh: Configuration complete; ready for start up … [notice] 1#1: nginx/1.26.3
$ curl -sI http://127.0.0.1:18362/ | head -2
HTTP/1.1 200 OK
Server: nginx/1.26.3
$ docker ps --filter name=abcl-cf-pull \
--format 'table {{.Image}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}'
IMAGE STATUS PORTS NAMES
nginx:1.26-alpine Up Less than a second 127.0.0.1:18362->80/tcp abcl-cf-pull
- CLI অনুরোধ পাঠাল।
dockerকমান্ডটি নিজে container চালায় না। এটি একটি daemon-কে API দিয়ে অনুরোধ পাঠায়। Daemon দেখল,nginx:1.26-alpineমেশিনে নেই। - Registry-তে খোঁজ। Registry-র নাম লেখা নেই, তাই
docker.io. Official image বলেlibrary/nginx. Daemon আগে manifest পড়ে জানল এই tag-এ মোট ৮টি layer আছে। - Layer নামানো আর খোলা। “Download complete” মানে সংকুচিত ফাইল এসেছে। “Pull complete” মানে সেটি যাচাই হয়ে ডিস্কে খোলা হয়েছে। Layer আগে থেকে থাকলে সেটি আর নামে না, পরে এর প্রমাণ দেখবেন।
- Digest পাওয়া গেল। এই লম্বা
sha256মানই image-এর আসল পরিচয়। Tag একটি মুখের নাম, digest হলো পরিচয়পত্রের নম্বর। এই ধাপে image প্রস্তুত, এখনো কোনো process নেই। - Container তৈরি। শুধু-পড়ার layer-গুলোর ওপর একটি খালি writable layer বসল। ঠিক হলো hostname
demo, port-এর সংযোগ, আলাদা দৃশ্য আর রিসোর্সের সীমা। এই ধাপের কোনো আউটপুট টার্মিনালে আসে না। - Process চালু। Image-এর নির্দেশ অনুযায়ী প্রথম process চলল, সে-ই container-এর ভেতরে PID 1.
-dদেওয়ায় টার্মিনালে শুধু container id ছাপা হলো, আর log জমা হলোdocker logs-এ। - Page দেখানো শুরু।
127.0.0.1:18362-এ আসা অনুরোধ container-এর port 80-তে পৌঁছাল, nginx উত্তর দিল200 OK. একটি লম্বা কমান্ডের পেছনে আসলে এতগুলো ধাপ ছিল।-p-এর বিস্তারিত পর্ব ৩৯-এ।
৩ · Container একটি process — চোখে দেখুন
“Container একটি process” কথাটি মুখস্থ না করে প্রমাণ দেখা যাক। একটি ছোট Alpine image থেকে container চালানো হলো, যা শুধু ১০ মিনিট ঘুমায়। --hostname demo দেওয়া হয়েছে, যাতে ভেতরের hostname আমরাই ঠিক করি।
$ docker run -d --name abcl-cf-demo --hostname demo alpine:3.20 sleep 600
39dec5c5c16e2e40a39381383edc8361d4d10fa82b97a8c5d807071030624bcb
$ docker ps --filter name=abcl-cf-demo
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
39dec5c5c16e alpine:3.20 "sleep 600" Less than a second ago Up Less than a second abcl-cf-demo
$ docker exec abcl-cf-demo hostname
demo
$ docker exec abcl-cf-demo ps -o pid,ppid,user,args
PID PPID USER COMMAND
1 0 root sleep 600
13 0 root ps -o pid,ppid,user,args
$ docker top abcl-cf-demo -o pid,ppid,args
PID PPID COMMAND
1169 1147 sleep 600এই ছোট আউটপুটে অনেক কথা আছে:
docker ps-এরCOMMANDঘরে একটিই জিনিস:sleep 600. কোনো OS boot হয়নি, কোনো init চলছে না।- ভেতর থেকে দেখলে পুরো “কম্পিউটারে” মাত্র দুটি process:
sleepআর আমাদেরps.sleepনিজেকে PID 1 ভাবছে। docker execনতুন container বানায় না। এটি চলমান container-এর ভেতরে আরেকটি process ঢোকায়। সেটি বাইরে থেকে এসেছে বলে ভেতরে তার PPID0দেখায়।docker topkernel-এর বাইরের দৃষ্টিতে দেখায়: একইsleep-এর আসল নম্বর1169. একটি process, দুটি নম্বর। ভেতরের নম্বরটি শুধু তার নিজের জগতের।
এই আলাদা দৃশ্য দেয় kernel-এর namespace, আর CPU-মেমরির সীমা ও হিসাব রাখে cgroup. এই দুটির ভেতরের কৌশল এই কোর্সে আবার লেখা হবে না। অপারেটিং সিস্টেম কোর্সের Container-এর ভেতর — namespace, cgroup আর Docker পর্বে আটটি namespace, PID 1-এর দায়িত্ব আর cgroup হাতে-কলমে দেখানো আছে। এখানে এক লাইনে মনে রাখুন: namespace ঠিক করে process কী দেখবে, cgroup ঠিক করে কতটা পাবে।
Container নিজে থেকে চলতে থাকে না, process শেষ হলেই container থেমে যায়। sleep 600 ১০ মিনিট পর শেষ হলে container-এর অবস্থা হবে Exited. তাই একটি container যতক্ষণ চলে, তার PID 1-ও ঠিক ততক্ষণ চলে। ব্যাপারটি অদ্ভুত লাগলে মনে রাখুন, Linux-এ “container” নামের কোনো একক বস্তু kernel-এ নেই।
৪ · VM-এর সাথে তুলনা — এক kernel বনাম অনেক kernel
পর্ব ১৫-এ দেখেছেন, virtual machine নকল হার্ডওয়্যারের ওপর পুরো একটি OS চালায়, আর hypervisor তাদের আলাদা রাখে। বাড়ি ভাড়ার ভাষায়: VM মানে আলাদা প্লটে আলাদা বাড়ি, নিজের ভিত, নিজের পানির পাম্প। Container মানে একই ভবনের আলাদা ফ্ল্যাট, ভিত আর পানির মূল লাইন সবার এক।
VM-এর স্তর বনাম container-এর স্তর
VM stack vs container stack on one physical host
“সবার kernel এক” কথাটি পরীক্ষা করা যায়। তিনটি ভিন্ন image-এর ভেতরে OS-এর সংস্করণ দেখা হলো, তারপর তিন জায়গার kernel-এর সংস্করণ মিলিয়ে দেখা হলো। Kernel-এর আসল লেখাটি পাতায় দেখানো হচ্ছে না, শুধু তুলনার ফল দেখানো হচ্ছে।
$ for i in alpine:3.20 python:3.12-alpine nginx:1.27-alpine; do
printf '%s ' "$i"; docker run --rm --hostname demo "$i" cat /etc/alpine-release
done
alpine:3.20 3.20.10
python:3.12-alpine 3.24.3
nginx:1.27-alpine 3.21.3
$ a=$(docker run --rm alpine:3.20 uname -r)
$ b=$(docker run --rm nginx:1.27-alpine uname -r)
$ c=$(docker run --rm python:3.12-alpine uname -r)
$ [ "$a" = "$b" ] && [ "$b" = "$c" ] && echo "same kernel (3)"
same kernel (3)তিনটি image-এ OS-এর ব্যবহারকারী-স্তর তিন রকম (Alpine 3.20, 3.24, 3.21), কিন্তু kernel একটিই। Image-এ kernel থাকে না। Image আনে ফাইল, kernel আসে host থেকে। এজন্যই “Alpine container” বলতে Alpine-এর library আর টুল বোঝায়, Alpine-এর kernel নয়।
চালু হতে কত সময় লাগে — আসল মাপ
নিচের সংখ্যাগুলো এই পর্ব লেখার সময় একটি development মেশিনে মাপা। প্রতিটি কাজ ১১ বার চালানো হয়েছে, আর সময় ধরা হয়েছে কমান্ড শুরু থেকে কাজ শেষ পর্যন্ত।
| কাজ (image আগে থেকে মেশিনে আছে) | সর্বনিম্ন | মধ্যক | সর্বোচ্চ |
|---|---|---|---|
docker exec দিয়ে চলমান container-এ true | ৭১ ms | ১০১ ms | ১১০ ms |
docker run -d nginx:1.27-alpine থেকে প্রথম 200 OK | ২৪৮ ms | ৩২১ ms | ৩৯৩ ms |
docker run --rm alpine:3.20 true, চালানো থেকে মুছে ফেলা পর্যন্ত | ৪৪৫ ms | ৫৩৯ ms | ৬২৪ ms |
এই সংখ্যা সাবধানে পড়ুন। মেশিনটিতে একই সময়ে অন্য কাজ চলছিল, আর Docker নিজে একটি Linux VM-এর ভেতরে চলছিল। সময়ের মধ্যে CLI চালু হওয়ার খরচও আছে, তাই এটি শুদ্ধ container-চালুর সময় নয়। তিন নম্বর সারিটি nginx-এর চেয়ে বেশি কারণ সেখানে container মুছে ফেলার সময়ও ধরা আছে, আর nginx-এর মাপ থামে প্রথম উত্তরে। Image না থাকলে pull-এর সময় যোগ হবে। আর app-এর নিজের চালু হওয়ার সময় (ধরুন একটি বড় Java app-এর কয়েক সেকেন্ড) container কমায় না। সবচেয়ে বড় কথা: একই মেশিনে কোনো VM boot মাপা হয়নি, তাই এটি পাশাপাশি পরীক্ষা নয়।
একটি সাধারণ VM-এ পুরো OS boot করতে হয়, যা সাধারণত কয়েক সেকেন্ড থেকে এক-দুই মিনিট নেয়, আকার আর provider-ভেদে। অর্থাৎ পার্থক্যটি প্রায়ই এক থেকে দুই ঘরের গুণিতকে। তবে এটি প্রকৃতির নিয়ম নয়: বিশেষভাবে ছাঁটা microVM এক সেকেন্ডের কম সময়েও চালু হতে পারে, যা আমরা পরের অংশে দেখব।
এক host-এ কতগুলো ধরবে — ঘনত্বের হিসাব
প্রতিটি VM-কে নিজের kernel আর OS-এর জন্য বাড়তি মেমরি দিতে হয়। Container-এর বাড়তি খরচ খুব কম। নিচের সিমুলেটরে সংখ্যাগুলো ধরে নেওয়া, কোনো মাপা ফল নয়। VM_EXTRA বা HOST_GIB বদলে আবার চালিয়ে দেখুন।
// How many copies of one app fit on one host? All numbers are ASSUMED.
const HOST_GIB = 64; // RAM of the physical host
const RESERVED_MIB = 2048; // host OS, hypervisor or container runtime
const VM_EXTRA = 600; // MiB per VM: guest kernel, init, agents
const CT_EXTRA = 20; // MiB per container: shim, kernel bookkeeping
const usable = HOST_GIB * 1024 - RESERVED_MIB;
const fit = (app, extra) => Math.floor(usable / (app + extra));
const pct = (app, extra) => (100 * extra / (app + extra)).toFixed(1) + "%";
console.log("usable MiB =", usable);
console.log("app MiB | VMs | containers | ratio | VM overhead | ct overhead");
for (const app of [50, 100, 300, 1000, 4000]) {
const v = fit(app, VM_EXTRA), c = fit(app, CT_EXTRA);
console.log(
String(app).padStart(7), "|", String(v).padStart(3), "|",
String(c).padStart(10), "|", (c / v).toFixed(2).padStart(5), "|",
pct(app, VM_EXTRA).padStart(11), "|", pct(app, CT_EXTRA).padStart(11));
}একটি আসল রানের আউটপুট:
usable MiB = 63488
app MiB | VMs | containers | ratio | VM overhead | ct overhead
50 | 97 | 906 | 9.34 | 92.3% | 28.6%
100 | 90 | 529 | 5.88 | 85.7% | 16.7%
300 | 70 | 198 | 2.83 | 66.7% | 6.3%
1000 | 39 | 62 | 1.59 | 37.5% | 2.0%
4000 | 13 | 15 | 1.15 | 13.0% | 0.5%
৩০০ MiB app-এর হিসাব হাতে: ব্যবহারযোগ্য মেমরি 64 × 1024 − 2048 = 63488 MiB. VM-প্রতি 300 + 600 = 900 MiB, তাই 63488 ÷ 900 ≈ 70.5, অর্থাৎ ৭০টি। Container-প্রতি 320 MiB, তাই ১৯৮টি। আসল শিক্ষা শেষ সারিগুলোতে: ছোট app-এ container-এর সুবিধা বিশাল, বড় app-এ প্রায় মিলিয়ে যায়। একটি 4 GiB-র ডেটাবেসের জন্য ঘনত্ব দেখে container বেছে নেওয়ার যুক্তি প্রায় নেই।
| বিষয় | Virtual machine | Container |
|---|---|---|
| Kernel | প্রতিটির নিজের | host-এর kernel সবার এক |
| প্যাকেজের আকার | সাধারণত GB-র ঘরে | কয়েক MB থেকে কয়েকশো MB, কখনো বেশি |
| ভিন্ন OS | পারে: Linux host-এ Windows VM | Linux container-এ Linux kernel লাগে |
| মেরামতের দায় | Guest OS-এর patch আপনার | Image আবার build করতে হয়, host kernel-এর দায় host-এর |
| বিচ্ছিন্নতা | হার্ডওয়্যার-সমর্থিত সীমানা | Kernel-এর নিয়ম, kernel-এর বাগ সবার ঝুঁকি |
| স্বভাব | দীর্ঘদিন চলা server | ফেলে দেওয়া আর নতুন করে চালানো যায় এমন process |
৫ · বিচ্ছিন্নতা — ফ্ল্যাটের দেয়াল কতটা শক্ত
“হালকা” হওয়ার দাম কোথায়? দাম সীমানার শক্তিতে। VM-এর guest যা-ই করুক, তাকে নকল হার্ডওয়্যারের সরু দরজা দিয়ে hypervisor-এর সাথে কথা বলতে হয়। Container-এর process সরাসরি host kernel-এ system call করে। Linux kernel-এ কয়েকশো system call আছে, আর প্রতিটি একটি সম্ভাব্য দরজা। Kernel-এ একটি গুরুতর বাগ থাকলে একটি container থেকে পুরো host ঝুঁকিতে পড়তে পারে।
এর মানে এই নয় যে container অনিরাপদ। মানে হলো, কে কার পাশে বসছে তার ওপর সিদ্ধান্ত নির্ভর করে। নিচের তালিকা একটি সাধারণ নিয়ম, পাথরে লেখা নয়:
| পরিস্থিতি | সাধারণ পছন্দ | কারণ |
|---|---|---|
| নিজের দলের কয়েকটি service, একই আস্থা | সাধারণ container | সবাই একই মালিকের, ঝুঁকি কম |
| একই প্রতিষ্ঠানের ভিন্ন দল | Container, সাথে আলাদা node বা বাড়তি নিয়ম | ভুলের প্রভাব সীমিত রাখা |
| অচেনা গ্রাহকের কোড (multi-tenancy) | VM, microVM বা sandbox | Kernel-এর সীমানায় একা ভরসা করা ঠিক নয় |
| নিয়ন্ত্রক শর্তে আলাদা kernel দরকার | VM | শর্তটি নিজেই সিদ্ধান্ত দেয় |
মাঝের পথও আছে। MicroVM (যেমন Firecracker, Kata Containers) প্রতিটি container বা function-কে খুব ছোট VM-এ চালায়। gVisor container আর host kernel-এর মাঝে একটি user-space kernel বসায়। পাশাপাশি rootless container নিয়ে চালালে ভেতরের root বাইরে সাধারণ ব্যবহারকারী হয়। এসবের ভেতরের ব্যাখ্যা অপারেটিং সিস্টেম কোর্সে আছে, আর Kubernetes-এ প্রয়োগ পর্ব ৪৯-এ আসবে।
দুটি সাধারণ ভুল দেয়াল ভেঙে দেয়। এক, --privileged দিয়ে চালানো: এতে বেশিরভাগ সুরক্ষা উঠে যায়। দুই, container-এর ভেতরে /var/run/docker.sock mount করা: Docker socket যার হাতে, সে নতুন container চালিয়ে কার্যত host-এর root হতে পারে। AI বা টিউটোরিয়ালে এ দুটি দেখলে থামুন, আর কেন লাগবে তা জানুন।
৬ · ইতিহাস আর Docker পরিবার
১৯৭৯ · chroot: Version 7 Unix-এ process-এর root ফোল্ডার বদলানোর সুবিধা। এটি নিরাপত্তার সীমানা ছিল না, কিন্তু ধারণার বীজ এখানেই।
২০০০ · FreeBSD jails: FreeBSD 4.0-তে আসে। Jail-এর নিজের ফাইল-অংশ, hostname আর IP ঠিকানা থাকে। Hosting-এর গ্রাহকদের আলাদা করাই ছিল মূল প্রয়োজন।
২০০৪–২০০৫ · Solaris Zones: Sun Microsystems আনে, Solaris 10-এ রিসোর্স-সীমাসহ আলাদা zone.
২০০৮ · LXC: Linux-এর namespace আর cgroup জুড়ে তৈরি জনপ্রিয় user-space টুল। ব্যবহারের ধরন ছিল হালকা VM-এর মতো।
২০১৩ · Docker: dotCloud কোম্পানির Solomon Hykes মার্চে PyCon-এ দেখান। নতুন kernel-যন্ত্র নয়, Docker-এর অবদান ছিল image, layer আর registry: এক কমান্ডে বানানো, পাঠানো আর চালানো।
২০১৫ · OCI: Docker, CoreOS, Google-সহ শিল্পের অনেকে মিলে Open Container Initiative গড়েন। Docker দান করে runc. Image আর runtime-এর মানদণ্ডের ১.০ সংস্করণ আসে ২০১৭ সালে।
২০১৫–২০১৯ · containerd: Docker নিজের ভেতরের অংশটি আলাদা করে। ২০১৭ সালে এটি CNCF-এ যায়, আর ২০১৯ সালে সেখানে graduated হয়। এখন Docker-ও এর ওপর চলে, আর বহু Kubernetes cluster সরাসরি এটি ব্যবহার করে।
এখানে নামগুলো গুলিয়ে যায়, তাই পরিষ্কার করে নিই। Docker একটি কোম্পানি, একটি টুল-পরিবার আর লোকমুখে container-এর প্রতিশব্দ, তিনটিই।
| নাম | কী | কোথায় | খেয়াল রাখুন |
|---|---|---|---|
| Docker Engine | dockerd daemon, API আর CLI | Linux server | Open source. সাধারণত root হিসেবে চলে, rootless মোডও আছে। |
| Docker Desktop | ডেস্কটপ অ্যাপ: Engine, একটি Linux VM, GUI আর Compose | Windows, macOS, Linux ডেস্কটপ | বড় প্রতিষ্ঠানের জন্য paid হতে পারে। শর্ত বদলায়, Docker-এর সাইটে দেখে নিন। |
| Podman | Daemon ছাড়া container টুল | Linux; অন্য OS-এ podman machine-এর VM | Rootless-কেন্দ্রিক, CLI অনেকটা Docker-এর মতো। সব আচরণ হুবহু এক নয়। |
ভেতরের শিকলটি এমন: docker CLI → dockerd → containerd → container runtime runc → আপনার process. Runc process চালিয়ে দিয়েই সরে যায়। বিস্তারিত অপারেটিং সিস্টেম কোর্সের docker run থেকে আপনার process অংশে আছে। আর OCI মান থাকার ফলে Docker-এ বানানো image Podman বা Kubernetes-এ চলে। বাক্সের মাপ মানক হলে ক্রেনের কোম্পানি বদলানো যায়, ঠিক তেমনি।
Windows আর macOS-এ কেন একটি VM লাগে
একটি Linux container-এর ভেতরের প্রোগ্রাম Linux-এর system call করে, আর namespace ও cgroup চায়। এগুলো Linux kernel-এর জিনিস। Windows আর macOS-এর kernel আলাদা, তারা এই ভাষা বলে না। ফলে উপায় একটিই: পেছনে একটি ছোট Linux VM চালানো, আর container-কে সেই VM-এর kernel-এ বসানো। Docker Desktop এটিই করে। Windows-এ সাধারণত WSL 2-র ভেতরে, macOS-এ Apple-এর virtualization সুবিধা দিয়ে। এজন্য Windows বা Mac-এ docker run --rm alpine:3.20 uname -s চালালেও উত্তর আসে Linux.
দুটি প্রান্তিক কথা। Windows-এর নিজস্ব “Windows container”-ও আছে, যা Windows kernel ভাগ করে, Linux image সেখানে চলে না। আর CPU-র ধরনও আলাদা ব্যাপার: amd64-এর জন্য বানানো image ARM মেশিনে সরাসরি চলে না। অনেক image তাই দুই ধরনের জন্যই প্রকাশ করা হয়। অন্যথায় emulation-এ চলে, ধীরে, আর কখনো ভুল আচরণ করে।
৭ · Writable layer — যা লিখবেন, তা ক্ষণস্থায়ী
Image শুধু-পড়ার। তাহলে container-এর ভেতরের process ফাইল লিখলে সেটি যায় কোথায়? প্রতিটি container-এর জন্য image-এর ওপর একটি পাতলা writable layer বসে। লেখা সব সেখানে যায়। আগের একটি ফাইল বদলালে ফাইলটি ওপরে কপি হয় (copy-on-write), নিচের layer অক্ষত থাকে। এই স্তরটি container-এর সাথেই জন্মায়, আর container মুছলে মরে যায়।
ভাবুন, পরীক্ষার হলে সবার জন্য একটি ছাপানো প্রশ্নপত্র দেয়ালে টাঙানো। সেটি কেউ বদলাতে পারে না (image). প্রত্যেকের হাতে একটি নিজের খসড়া খাতা (writable layer). পরীক্ষা শেষে খসড়া খাতা ফেলে দেওয়া হয়। পরের জন নতুন খাতা পান, আগের জনের হিসাব পান না।
1# alpine:3.20 মেশিনে আছে2docker run --name abcl-cf-note \3 --hostname demo alpine:3.20 \4 sh -c 'echo invoice-1001 > /tmp/note.txt'5docker diff abcl-cf-note6docker cp abcl-cf-note:/tmp/note.txt - | tar -xO7docker rm abcl-cf-note8docker run --rm --hostname demo \9 alpine:3.20 cat /tmp/note.txt
| Container | |
| Writable layer | |
| Image |
- শুরু। মেশিনে শুধু image আছে, কোনো container নেই। Image নিজে কিছু চালায় না।
- Container তৈরি।
docker runimage-এর ওপর একটি খালি writable layer বসাল। এখনো কিছু লেখা হয়নি। - ফাইল লেখা। Process চলল আর
/tmp/note.txtলিখল। ফাইলটি গেল writable layer-এ। Image-এর কোনো পরিবর্তন হয়নি। - Process শেষ। PID 1 শেষ হলো, অবস্থা
Exited (0). কিন্তু থেমে যাওয়া মানে মুছে যাওয়া নয়।docker diffদেখাচ্ছে:C /tmpমানে ফোল্ডার বদলেছে,A /tmp/note.txtমানে নতুন ফাইল। - ফাইল এখনো আছে। Container থেমে আছে, তবু
docker cpদিয়ে ফাইলটি পড়া গেল:invoice-1001. Writable layer container-এর আয়ু পর্যন্ত বাঁচে, process-এর আয়ু পর্যন্ত নয়। - Container মুছে ফেলা।
docker rmwritable layer-সহ container মুছে দিল। ফাইলটি চিরতরে গেল। Image অক্ষত। - নতুন container, নতুন খাতা। একই image থেকে নতুন container চালিয়ে ফাইলটি খোঁজা হলো। উত্তর:
No such file or directory.--rmদিলে এই মুছে ফেলার কাজটি process শেষ হতেই আপনাআপনি হয়।
--rm দিলে কী হয়, সরাসরি দেখুন। প্রথম container ফাইল লিখে নিজেই পড়ল। সে শেষ হতেই মুছে গেল। দ্বিতীয় container ফাইলটি পেল না।
$ docker run --rm --hostname demo alpine:3.20 \
sh -c 'echo invoice-1001 > /tmp/note.txt; cat /tmp/note.txt'
invoice-1001
$ docker run --rm --hostname demo alpine:3.20 cat /tmp/note.txt
cat: can't open '/tmp/note.txt': No such file or directorydocker stop আর docker start-এও ফাইল টেকে। এই পরীক্ষাটিও আসল run-এ করা হয়েছে: চলমান container-এ ফাইল লিখে, থামিয়ে, আবার চালিয়ে cat করলে invoice-1001-ই এসেছে। সমস্যা হলো নতুন সংস্করণে যাওয়ার সময়। তখন পুরোনো container মুছে নতুন image থেকে নতুন container বানানো হয়। তাই ডেটা ভেতরে রাখা মানে পরের deploy-তেই হারানো।
ডেটাবেসের ফাইল, গ্রাহকের আপলোড বা গুরুত্বপূর্ণ log writable layer-এ রাখা যাবে না। এদের জন্য volume, object storage বা managed database লাগে। Volume আসবে পর্ব ৩৯-এ। Cloud-এর স্থানীয় ephemeral storage-এর সাথে মিলটি লক্ষ করুন: দুটিই “থাকতে পারে, ভরসা নয়”।
একটি image, অনেক container — ভাগ করা স্তর
ভাগ করা শুধু-পড়ার layer, আলাদা writable layer
Shared read-only layers, one writable layer per container
দুটি আসল পরীক্ষা ছবিটিকে সমর্থন করে। প্রথমে একই image থেকে দুটি container চালিয়ে দুটি আলাদা ফাইল লেখা হলো। তারপর alpine:3.20-র ওপর দুটি ছোট image বানিয়ে তাদের layer-এর তালিকা মেলানো হলো।
$ docker diff abcl-cf-c1
C /tmp
A /tmp/c1.txt
$ docker diff abcl-cf-c2
C /tmp
A /tmp/c2.txt
$ docker ps -s --filter name=abcl-cf-c --format 'table {{.Names}}\t{{.Image}}\t{{.Size}}'
NAMES IMAGE SIZE
abcl-cf-c2 alpine:3.20 12.3kB (virtual 8.5MB)
abcl-cf-c1 alpine:3.20 12.3kB (virtual 8.5MB)
$ printf 'FROM alpine:3.20\nRUN echo A > /a.txt\n' | docker build -q -t abcl-cf-a -
sha256:e4e2c8036ec6b9f1eef6ca91b5fc056a5b5666b5259286cdf2a41061e10d6543
$ printf 'FROM alpine:3.20\nRUN echo B > /b.txt\n' | docker build -q -t abcl-cf-b -
sha256:503d0b4624ad9833ea2b9d6c752053e8c94de5d9d311df54287f5ddc3b32cce3
$ for i in alpine:3.20 abcl-cf-a abcl-cf-b; do echo "$i"
docker image inspect "$i" --format '{{range .RootFS.Layers}}{{println .}}{{end}}' | cut -c1-19
done
alpine:3.20
sha256:08bc4e534116
abcl-cf-a
sha256:08bc4e534116
sha256:fa3c3934b03a
abcl-cf-b
sha256:08bc4e534116
sha256:0599dbb4b1ffc1-এর ফাইল c2-তে নেই। প্রতিটির নিজের writable layer মাত্র 12.3kB, আর virtual 8.5MB হলো ভাগ করা image-সহ মোট দৃশ্য। নিচের অংশে তিনটি image-এরই প্রথম layer-এর hash হুবহু এক (cut দিয়ে ১২ ঘরে ছাঁটা)। Hash এক মানে বিষয়বস্তু এক, তাই ডিস্কে আর নেটওয়ার্কে এটি একবারই লাগে। শুধু ওপরের ছোট layer দুটি আলাদা।
এবার নিজে হিসাবটি পরীক্ষা করুন। নিচের খেলনা মডেলে দুটি image একই base আর python layer ভাগ করে। আকারগুলো ধরে নেওয়া। read আগে writable layer খোঁজে, তারপর ওপর থেকে নিচের layer-এ যায়।
const layers = { // sizes in MB (assumed)
base: { size: 8, files: ["/bin/sh", "/etc/app.conf"] },
python: { size: 50, files: ["/usr/bin/python3"] },
web: { size: 5, files: ["/app/web.py"] },
worker: { size: 6, files: ["/app/worker.py"] },
};
const images = { web: ["base", "python", "web"],
worker: ["base", "python", "worker"] };
const live = {}; // container -> { image, rw: {path: MB} }
function read(c, path) { // top-down: writable first, then newest layer
if (path in live[c].rw) return `${c} ${path}: writable layer`;
for (const l of [...images[live[c].image]].reverse())
if (layers[l].files.includes(path)) return `${c} ${path}: read-only "${l}"`;
return `${c} ${path}: No such file`;
}
function disk() {
const used = new Set(), cs = Object.values(live); let rw = 0, copies = 0;
for (const c of cs) {
images[c.image].forEach((l) => { used.add(l); copies += layers[l].size; });
rw += Object.values(c.rw).reduce((a, b) => a + b, 0);
}
const shared = [...used].reduce((a, l) => a + layers[l].size, 0);
return `${cs.length} containers: shared ${shared} + writable ${rw} = ` +
`${shared + rw} MB (full copies: ${copies + rw} MB)`;
}
for (const c of ["c1", "c2", "c3"]) live[c] = { image: "web", rw: {} };
live.c4 = { image: "worker", rw: {} };
live.c1.rw["/tmp/note.txt"] = 1;
live.c2.rw["/etc/app.conf"] = 1; // copy-up: base layer stays unchanged
for (const [c, p] of [["c1", "/tmp/note.txt"], ["c2", "/tmp/note.txt"],
["c2", "/etc/app.conf"], ["c3", "/etc/app.conf"]]) console.log(read(c, p));
console.log(disk());
delete live.c1; live.c5 = { image: "web", rw: {} };
console.log(read("c5", "/tmp/note.txt"));
console.log(disk());একটি আসল রানের আউটপুট:
c1 /tmp/note.txt: writable layer
c2 /tmp/note.txt: No such file
c2 /etc/app.conf: writable layer
c3 /etc/app.conf: read-only "base"
4 containers: shared 69 + writable 2 = 71 MB (full copies: 255 MB)
c5 /tmp/note.txt: No such file
4 containers: shared 69 + writable 1 = 70 MB (full copies: 254 MB)
c2 একটি base ফাইল বদলালেও c3 পুরোনোটিই দেখছে, কারণ বদল গেছে c2-র writable layer-এ। ডিস্কে শুধু-পড়ার অংশ 8 + 50 + 5 + 6 = 69 MB. ভাগ না করলে লাগত ২৫৫ MB. আর c1 মুছে নতুন c5 চালালে আগের ফাইলটি আর নেই। এই মডেলে একটি জিনিস বাদ আছে: বাস্তবে ফাইল বদলালে পুরো ফাইলটি কপি হয়, তাই বড় ফাইলে ছোট বদলও ব্যয়বহুল।
৮ · এক app, তিন জায়গা — আসল লাভ
এবার সব জোড়া লাগাই। একটি কাল্পনিক shop-api ল্যাপটপ থেকে CI হয়ে server-এ যাচ্ছে। এই stepper-টি একটি নমুনা, এর digest আর ঠিকানাগুলো বানানো। লক্ষ করুন কোনটি বদলায় না, আর কোনটি বদলায়।
image : shop-api:dev DB_HOST : localhost ফল : চলছে, কিন্তু এটি শুধু শিলার মেশিনে
image : shop-api:1.4.2
digest : sha256:ab12…
DB_HOST : test-db
ফল : ১২০টি test পাস
image : shop-api:1.4.2
digest : sha256:ab12…
ফল : test-পাস করা image-টিই জমা হলো
image : shop-api:1.4.2
digest : sha256:ab12…
DB_HOST : prod-db
secret : secret manager থেকে, image-এ নয়
ফল : চলছে
নতুন 1.4.3 : ভুল আচরণ, চালু রাখা হলো না
ফিরে গেল : shop-api:1.4.2
digest : sha256:ab12…
ডেটা : container-এর বাইরে, অক্ষত
- ল্যাপটপে। শিলা image বানিয়ে চালালেন। DB হিসেবে নিজের মেশিনের ডেটাবেস। এই image-টি পরীক্ষার জন্য, প্রকাশের জন্য নয়।
- CI-তে। কোড push হতেই একটি পরিষ্কার মেশিনে নতুন করে image তৈরি হলো, পেল
sha256:ab12…. Test চলল সেই image-এর ভেতরেই, DB হলোtest-db. CI-এর কথা এখানে আছে। - Registry-তে। Test পাস করা image-টিই
pushহলো। Digest বদলায়নি। নতুন করে কিছু বানানো হয়নি। - Server-এ। Server একই digest
pullকরে চালাল। ভেতরের সব কিছু CI-তে পরীক্ষা করা জিনিসের সাথে হুবহু এক। শুধু বাইরে থেকে দেওয়া environment variable আর secret আলাদা:DB_HOSTএখনprod-db. - Rollback. পরের সংস্করণে সমস্যা হলো। আগের image এখনো registry-তে আছে, তাই একই digest আবার চালানো গেল। ডেটা বাইরে ছিল বলে হারায়নি। তবে ডেটাবেসের গঠন বদলে থাকলে ফেরা অত সহজ নয়, সেটি আলাদাভাবে ভাবতে হয়।
| Image-এর সাথে যায় | যায় না |
|---|---|
| App-এর কোড, library, runtime | Host-এর kernel আর তার সংস্করণ |
| OS-এর ব্যবহারকারী-স্তরের প্যাকেজ | Secret আর environment-ভেদের config |
| কোন প্রোগ্রাম চলবে তার নির্দেশ | ডেটাবেসের ডেটা, নেটওয়ার্ক, DNS |
| CPU-র ধরন-নির্দিষ্ট binary | সঠিক CPU-র ধরন, GPU driver, আসল রিসোর্স |
তাই container “আমার কম্পিউটারে তো চলে” সমস্যার বড় অংশ দূর করে, পুরোটা নয়। ভুল config, ধীর নেটওয়ার্ক বা সীমিত মেমরি প্রোডাকশনে এখনো ঝামেলা বাধাবে। এই ধারণার পুরো রূপ Twelve-Factor নীতিতে আছে, আর “একবার build, সব জায়গায় deploy” আসবে পর্ব ৫৮-এ।
৯ · কখন container নয়
Container জনপ্রিয় বলেই সব জায়গায় ঠিক, এমন নয়। পরের প্রশ্নগুলো নিজেকে করুন। পর্ব ১৯-এ compute বাছাইয়ের পুরো কাঠামো আছে।
| পরিস্থিতি | কেন ভাবতে হবে | বিকল্প |
|---|---|---|
| স্থির ওয়েবসাইট | চালানোর মতো process-ই নেই | Object storage আর CDN |
| একজনের ছোট প্রকল্প, একটি server | বাড়তি স্তর শিখতে আর সামলাতে হয় | PaaS, বা systemd service |
| ভিন্ন kernel বা kernel module লাগে | Host-এর kernel ভাগ করতে হয় | VM |
| অচেনা ব্যক্তির কোড চালানো | Kernel-এর সীমানা যথেষ্ট নাও হতে পারে | VM, microVM বা sandbox |
| ডেস্কটপ অ্যাপ, GUI-নির্ভর পুরোনো software | Container server-এর process-এর জন্য তৈরি | সাধারণ ইনস্টলার, VM |
| পুরো OS ধরে রাখা প্রোগ্রাম | একই image-এ অনেক daemon, আলাদা init চাই | VM, অথবা আগে ভাগ করা |
| স্টেটফুল ভারী ডেটাবেস, ছোট দল | Container অসম্ভব নয়, কিন্তু backup, failover আপনার দায় | Managed database |
| খুব কম latency-র হার্ডওয়্যার-নির্ভর কাজ | নেটওয়ার্ক আর ডিস্কের স্তর মাপতে হয় | মাপার পর সিদ্ধান্ত, দরকারে bare metal |
আরও গভীরে: OCI-র তিনটি মান আছে: image, runtime আর distribution. Image-এ ফাইল, config আর manifest বিষয়বস্তুর hash দিয়ে চিহ্নিত, তাই digest এক হলে বিষয়বস্তুও এক। নিরাপত্তার মান হিসেবে NIST SP 800-190 (২০১৭) পড়ার মতো। পারফরম্যান্সের দিকে Felter ও সহকর্মীদের ২০১৫ সালের IBM গবেষণাপত্র দেখায়, CPU আর মেমরিতে container প্রায় সরাসরি চলার সমান, আর I/O-র খরচ নির্ভর করে কনফিগারেশনের ওপর। Firecracker-এর নকশা বর্ণনা আছে NSDI ২০২০-র গবেষণাপত্রে।
পরীক্ষা ও ইন্টারভিউতে:
- Image আর container-এর পার্থক্য কী? Image শুধু-পড়ার প্যাকেজ; container তার ওপর writable layer বসিয়ে চালানো process.
- Container আর VM-এর মূল পার্থক্য? VM-এর নিজের kernel থাকে, container host-এর kernel ভাগ করে।
- Container মুছলে ভেতরের ফাইলের কী হয়? Writable layer-সহ মুছে যায়, টিকিয়ে রাখতে volume লাগে।
- Mac বা Windows-এ Linux container কীভাবে চলে? পেছনের একটি Linux VM-এর kernel-এ।
- Docker কি container আবিষ্কার করেছে? না, namespace আর cgroup আগেই ছিল। Docker image, layer আর registry দিয়ে ব্যবহার সহজ করেছে।
১০ · AI-এর যুগে container
AI কয়েক সেকেন্ডে Docker কমান্ড লিখে দেয়, আর সেগুলো প্রায়ই চলেও। কিন্তু “চলে” আর “নিরাপদে চলে” এক কথা নয়। AI আপনার server, খরচ বা ডেটা দেখতে পায় না।
AI সাধারণত যেসব ভুল করে
| AI যা লেখে | ঝুঁকি | আপনি যা করবেন |
|---|---|---|
nginx:latest | কাল অন্য image আসতে পারে | নির্দিষ্ট tag, প্রোডাকশনে digest |
--privileged, Docker socket mount | Host ঝুঁকিতে পড়ে | আসলে কোন ক্ষমতা লাগে তা জানা |
-p 8080:80 | সব interface-এ খোলে, অর্থাৎ 0.0.0.0 | দরকার না হলে 127.0.0.1:8080:80 |
| ডেটা container-এর ভেতরে, volume নেই | পরের deploy-তে ডেটা শেষ | ডেটার জায়গা আগে ঠিক করা |
-e DB_PASSWORD=... কোড বা script-এ | Secret ফাঁস | Secret manager (পর্ব ৩৩) |
docker system prune -a --volumes | অন্যের image আর volume-এর ডেটাও মুছতে পারে | সতর্কতা না পড়ে না চালানো, নাম ধরে মোছা |
| অস্তিত্বহীন flag | Error, বা ভুল আচরণ | docker run --help আর official docs দেখা |
AI-কে সিদ্ধান্ত নিতে না বলে, সিদ্ধান্তের যুক্তি জিজ্ঞেস করুন। নিচের কাঠামোয় পরিবেশ, শর্ত আর ঝুঁকি স্পষ্ট লেখা আছে। কোনো আসল secret, ঠিকানা বা account id লিখবেন না।
AI-এর container-পরামর্শ চালানোর আগে — চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১একটি লোহার বাক্স বিশ্ববাণিজ্য বদলে দিয়েছিল। Software container কি সত্যিই একই গল্প, নাকি শুধু সুন্দর উপমা?
১৯৫৬ সালে মার্কিন পরিবহন-ব্যবসায়ী Malcom McLean একটি পুরোনো জাহাজে করে ট্রাকের মালের বাক্স পাঠান, Newark থেকে Houston. তখন জাহাজে মাল তোলা হতো হাতে, বস্তা আর পিপা ধরে ধরে। খরচের বড় অংশ যেত বন্দরের শ্রমে, আর জাহাজ দিনের পর দিন বন্দরে পড়ে থাকত। McLean-এর আসল ভাবনা বাক্স নয়, ছিল পুরো পরিবহন-ব্যবস্থা নতুন করে সাজানো।
এখানেই ইতিহাসের সবচেয়ে কাজের শিক্ষা। Marc Levinson তাঁর The Box বইয়ে (২০০৬) দেখান, বাক্স আসার পরও বড় লাভ আসতে বহু বছর লেগেছিল। কারণ বাক্সের মাপ মানক হতে হয়েছে, বন্দরে নতুন ক্রেন বসাতে হয়েছে, জাহাজ আর ট্রেন নতুন করে বানাতে হয়েছে, আর ডকশ্রমিকদের সাথে দীর্ঘ বিরোধ মেটাতে হয়েছে। ১৯৬০-এর দশকে ISO-র মাপ এলে তবে এক কোম্পানির বাক্স আরেক কোম্পানির জাহাজে উঠতে পারল।
Software-এ মিল চোখে পড়ার মতো। Docker একা যথেষ্ট ছিল না। OCI-র মান, registry, CI-এর সমর্থন আর Kubernetes-এর মতো “ক্রেন” গড়ে উঠতে হয়েছে। মানুষের কাজের ধরনও বদলাতে হয়েছে: server-কে যত্ন করে রাখা থেকে ফেলে দিয়ে নতুন চালানোর অভ্যাসে।
অমিলও আছে। লোহার বাক্স একে অন্যকে স্পর্শ করে না, software container একই kernel ভাগ করে। আর বাণিজ্যের বাক্সের ভেতরের মাল বন্দরকে আক্রমণ করতে পারে না, কিন্তু একটি ক্ষতিকর image পারে। তাই উপমাটি বোঝায় মানকীকরণের শক্তি, নিরাপত্তা বোঝায় না।
মূল উপলব্ধি: মানক interface একটি ছোট জিনিস, কিন্তু তার চারপাশের পুরো ব্যবস্থা বদলালে তবেই আসল লাভ আসে। Container-এর সফলতার মূল বাক্সে নয়, বাক্স ঘিরে গড়ে ওঠা ব্যবস্থায়।
প্র ০২Kubernetes “Docker সমর্থন বন্ধ করেছে” শুনে ২০২০ সালে অনেকে ভয় পেয়েছিলেন। আসলে কী ঘটেছিল, আর এতে OCI-র গুরুত্ব কীভাবে বোঝা যায়?
শুরুর দিকে Kubernetes সরাসরি Docker-এর সাথে কথা বলত। অন্য runtime আসতে শুরু করলে Kubernetes ২০১৬ সালের দিকে Container Runtime Interface (CRI) নামে একটি সাধারণ interface আনে। Docker নিজে সেই interface মানত না, তাই Kubernetes-এর ভেতরে “dockershim” নামে একটি অনুবাদক রাখা হয়েছিল।
ডিসেম্বর ২০২০-এ Kubernetes প্রকল্প জানায়, dockershim বিদায় নেবে, আর সংস্করণ ১.২৪-এ (২০২২) এটি সরানো হয়। শিরোনাম হলো “Kubernetes dropped Docker”, আর অনেক দল ভাবল তাদের সব image অকেজো হবে। আসলে কিছুই হয়নি। Kubernetes শুধু একটি অতিরিক্ত ধাপ বাদ দিয়েছিল। Docker নিজেই তো ভেতরে containerd ব্যবহার করে, তাই kubelet সরাসরি containerd-এর সাথে কথা বলা শুরু করল।
আপনার Docker-এ বানানো image কেন চলতেই থাকল? কারণ image-এর ফরম্যাট আর Docker কোম্পানির সম্পত্তি ছিল না। ২০১৫ সালে OCI গড়ার পর image আর runtime-এর মান একটি নিরপেক্ষ জায়গায় চলে যায়। মানটি না থাকলে একটি টুল-বদল মানে পুরো শিল্পের image নতুন করে বানানো।
একটি প্রশ্ন অবশ্য খোলা থাকে: মান থাকলেই কি সব একরকম? না। CLI-এর আচরণ, নেটওয়ার্কের ডিফল্ট, log-এর জায়গা, build টুল, এসব মানের বাইরে। তাই runtime বদলাতে গেলে monitoring আর debugging-এর অভ্যাস বদলাতে হয়েছিল, যেমন node-এ docker ps দিয়ে দেখার অভ্যাস। অর্থাৎ বাক্সের মান এক থাকলেও বন্দরের কর্মীদের সরঞ্জাম বদলায়।
মূল উপলব্ধি: একটি খোলা মান একটি কোম্পানির সিদ্ধান্তের ঝুঁকি থেকে আপনাকে বাঁচায়। প্রযুক্তি বাছাইয়ের সময় জিজ্ঞেস করুন: কোনটি মান, আর কোনটি একটি পণ্যের সুবিধা?
প্র ০৩Cloud-এ container প্রায়ই VM-এর ভেতরেই চলে। তাহলে দুই স্তরের খরচ দেওয়া কি অপচয় নয়? Container কি VM-কে সরিয়ে দিল?
প্রথম দেখায় অপচয়ই মনে হয়। একটি host-এ hypervisor, তার ওপর VM, তার ওপর container runtime, তার ওপর app. কিন্তু দুটি স্তর দুটি ভিন্ন সমস্যা সমাধান করে। VM ভাড়াটেদের আলাদা করে: cloud provider অচেনা গ্রাহকদের একই হার্ডওয়্যারে বসাতে পারেন। Container একটি VM-এর ভেতরের app-গুলোকে প্যাকেজ আর ঘন করে সাজায়। প্রথমটি নিরাপত্তার সীমানা, দ্বিতীয়টি পরিচালনার সুবিধা।
খরচ কত? ২০১৫ সালে IBM-এর Wes Felter ও সহকর্মীদের একটি বহুল-উদ্ধৃত গবেষণা KVM আর Docker-কে সরাসরি চলার সাথে তুলনা করে। সেখানে CPU-কেন্দ্রিক কাজে দুটোর খরচই সামান্য, আর I/O-তে পার্থক্য চোখে পড়ে। পরে হার্ডওয়্যার-সহায়তা আরও বেড়েছে। ফলে আধুনিক cloud-এ সাধারণ কাজের জন্য VM-এর বোঝা ততটা ভয়ের নয়, যদিও প্রতিটি কাজে মেপে দেখা ছাড়া সংখ্যা বলা যায় না। বড় খরচ আসলে মেমরিতে, যা আমাদের ঘনত্বের সিমুলেটরে দেখেছেন।
ভাগাভাগিটি এখন আরও ঝাপসা। AWS ২০১৮ সালে Firecracker প্রকাশ করে, যা serverless কাজের জন্য তৈরি ছোট VM. প্রকাশিত দাবি অনুযায়ী এটি প্রায় ১২৫ মিলিসেকেন্ডে চালু হতে পারে, আর প্রতিটির মেমরি-খরচ খুব কম। Kata Containers একই ধারণা Kubernetes-এ আনে: বাইরে থেকে container, ভেতরে ছোট VM. Google-এর gVisor আরেক পথ নেয়। অর্থাৎ শিল্প “VM না container” থেকে সরে এসেছে “container-এর ব্যবহার, VM-এর সীমানা” ধরনের মিশ্রণে।
আর VM সরে যায়নি। ভিন্ন kernel, পুরোনো Windows software, নিয়ন্ত্রক শর্ত, দীর্ঘমেয়াদি স্টেটফুল কাজ, এসবের জন্য VM এখনো স্বাভাবিক পছন্দ।
মূল উপলব্ধি: স্তরকে অপচয় বলার আগে দেখুন সে কোন সমস্যা সমাধান করছে। VM আর container প্রতিদ্বন্দ্বী নয়, ভিন্ন সীমানার ভিন্ন হাতিয়ার।
প্র ০৪Java-র স্লোগান ছিল “একবার লিখুন, সব জায়গায় চালান”। Container কি সেই প্রতিশ্রুতি পূরণ করল? ঠিক কতটা portable একটি image?
১৯৯৫ সালের দিকে Sun Microsystems Java-র জন্য “write once, run anywhere” কথাটি প্রচার করে। পথ ছিল একটি virtual machine (JVM): bytecode এক, প্রতিটি OS-এ JVM আলাদা। বাস্তবে GUI, ফাইল পথ আর সংস্করণের পার্থক্যে মানুষ ঠাট্টা করে বলত “write once, debug everywhere”.
Container অন্য একটি স্তরে একই চেষ্টা করে। ভাষার সীমানা নয়, সীমানা হলো Linux-এর system call interface. Linux প্রকল্পের একটি বিখ্যাত নীতি আছে, Linus Torvalds বারবার যা জোর দিয়ে বলেছেন: kernel ব্যবহারকারী-স্তরের প্রোগ্রাম ভাঙবে না। এই স্থিতিশীল চুক্তির জন্যই পুরোনো Alpine-এর binary নতুন kernel-এ চলে। Container-এর portability দাঁড়িয়ে আছে এই একটি সামাজিক প্রতিশ্রুতির ওপর।
কিন্তু সীমা আছে, আর প্রতিটি সীমা বাস্তব সমস্যার উৎস:
- CPU-র ধরন:
amd64-এর binary ARM server-এ সরাসরি চলে না। Multi-architecture image সমাধান, কিন্তু সেটি বানাতে হয়। - Kernel-এর সুবিধা: নতুন syscall বা বিশেষ module চাইলে পুরোনো host-এ কাজ নাও করতে পারে। উল্টো দিকটি তুলনায় নিরাপদ।
- cgroup-এর সংস্করণ: পুরোনো runtime বা JVM cgroup v2-এর সীমা ঠিকমতো না পড়লে মেমরি-হিসাবে ভুল করতে পারে।
- পরিবেশ: GPU driver, ঘড়ি, DNS, ফাইলের অনুমতি, এসব image-এর বাইরে।
তাই সৎ দাবিটি হলো: একই CPU-র ধরন আর যুক্তিসঙ্গত kernel-এ image প্রায় হুবহু এক আচরণ করে। পার্থক্য কমে যায়, শূন্য হয় না।
মূল উপলব্ধি: Portability সব সময় একটি চুক্তির ওপর দাঁড়ায়। চুক্তিটি কোথায় শেষ, সেটি জানলেই বোঝা যায় কোথায় ভাঙবে।
প্র ০৫ফাইল হারিয়ে যাওয়া তো শুনতে দোষের মতো। তবু কেন বলা হয় ক্ষণস্থায়ী writable layer container-এর একটি গুণ?
পুরোনো server-জীবনের একটি রোগ ভাবুন। একজন প্রশাসক রাতে একটি ছোট সমস্যা সারাতে server-এ ঢুকে একটি config বদলালেন, লিখে রাখলেন না। মাসখানেক পর দুটি “একই” server দুই রকম আচরণ করে। কেউ জানে না কেন, আর সেই server-কে কেউ রিবুট করতেও ভয় পায়। একে Martin Fowler ২০১২ সালে “snowflake server” বলেছিলেন: তুষারকণার মতো, প্রতিটি আলাদা।
এই সময়েই কয়েকটি ধারণা একসাথে পাকছিল। Pets vs cattle উপমাটি Microsoft-এর Bill Baker-এর বলে জানা যায়, আর Randy Bias cloud-এর প্রসঙ্গে একে জনপ্রিয় করেন: server-কে পোষা প্রাণীর মতো নাম দিয়ে যত্ন করবেন, না খামারের পশুর মতো একটি বদলে আরেকটি আনবেন? ২০১১ সালের দিকে Heroku-র Adam Wiggins-এর লেখা Twelve-Factor App বলে: process হবে stateless আর ফেলে দেওয়ার যোগ্য। ২০১৩ সালে Chad Fowler “trash your servers and burn your code” লিখে immutable infrastructure-এর কথা বলেন।
Container এই দর্শনকে অভ্যাসে নয়, যন্ত্রের নিয়মে পরিণত করে। আপনি ভেতরে ঢুকে হাতে কোনো config বদলালে সেটি পরের deploy-তে হারাবে। ফলে বদলাতে হলে Dockerfile বদলাতে হয়, যা git-এ থাকে, review হয়, আবার build হয়। ক্ষণস্থায়িত্বই এখানে শৃঙ্খলা। আরেকটি সুবিধা: একটি container নষ্ট হলে মেরামত নয়, নতুন একটি চালালেই হয়, যা autoscaling আর rollback-কে সহজ করে।
তবে দামও আছে। ডেটা তো কোথাও থাকতেই হবে। সমস্যাটি উধাও হয় না, সরে যায় volume, object storage আর ডেটাবেসে, যেগুলো আলাদা করে পরিচালনা করতে হয়। আর crash হলে ভেতরের log হারাতে পারে, তাই log বাইরে পাঠানো জরুরি (পর্ব ৬৪)।
মূল উপলব্ধি: ক্ষণস্থায়ী হওয়া মানে ডেটা গুরুত্বহীন নয়। মানে হলো state-কে একটি সচেতন, আলাদা জায়গায় রাখতে আপনাকে বাধ্য করা।
অনুশীলন
হিসাব, অনুমান আর সমালোচনা। Docker না থাকলেও প্রথম তিনটি করা যাবে।
-
ঘনত্ব আর ডিস্কের হিসাব: (ক) একটি host-এ 128 GiB RAM, 4 GiB সংরক্ষিত। App 500 MiB. VM-প্রতি বাড়তি 700 MiB, container-প্রতি বাড়তি 30 MiB. কতটি করে ধরবে? (খ) 200 MB-র একটি image থেকে ২০টি container চলছে, প্রতিটি নিজের স্তরে 15 MB লিখেছে। ডিস্কে মোট কত লাগবে, আর ভাগ না হলে কত লাগত?
(ক) ব্যবহারযোগ্য = 128 × 1024 − 4 × 1024 = 126976 MiB. VM: 126976 ÷ 1200 = 105.8, অর্থাৎ ১০৫টি। Container: 126976 ÷ 530 = 239.6, অর্থাৎ ২৩৯টি। অনুপাত প্রায় ২.২৮ গুণ।
(খ) ভাগ করলে 200 + 20 × 15 = 500 MB. ভাগ না হলে 20 × 215 = 4300 MB. এখানে layer ভাগ না করার দাম প্রায় ৮.৬ গুণ।
-
ফলাফল অনুমান করুন: কমান্ডগুলো এই ক্রমে চলল। প্রতিটির পরে কী দেখবেন? (১)
docker run --rm alpine:3.20 sh -c 'echo invoice-1001 > /tmp/note.txt; cat /tmp/note.txt'(২)docker run --rm alpine:3.20 cat /tmp/note.txt(৩)docker run --name box alpine:3.20 sh -c 'echo hi > /tmp/a.txt', তারপরdocker diff box(৪)docker rm box, তারপরdocker ps -a-তেboxখোঁজা।(১)
invoice-1001ছাপা হবে, কারণ একই container লিখেছে আর পড়েছে। (২)cat: can't open '/tmp/note.txt': No such file or directory, আর exit code 1, কারণ নতুন container, নতুন writable layer. (৩) প্রথম কমান্ডে কিছু ছাপা হবে না,docker diffদেখাবেC /tmpআরA /tmp/a.txt. Container থেমে আছে, মোছা হয়নি। (৪)docker rmনামটি ছাপবে, তারপর তালিকায়boxথাকবে না। ফাইলটিও আর নেই। প্রথম দুটি আর অনুরূপ ধাপের আউটপুট এই পর্বের আসল run-এর সাথে মেলে। -
প্রস্তাবটির সমালোচনা করুন: একজন সহকর্মী বললেন: “আমাদের অনলাইন বাজারের PostgreSQL, API আর গ্রাহকের ছবি, সব একটি container-এ
app:latestনামে চালাব। ডেটা ভেতরেই থাকবে। পাসওয়ার্ড Dockerfile-এ লিখে দেব। monitoring-এর জন্য Docker socket mount করব,--privilegedদেব, ঝামেলা কম।” কোথায় কোথায় সমস্যা, আর কী করবেন?- এক container-এ সব: আলাদা করে scale বা update করা যায় না। প্রতিটি কাজ আলাদা container-এ, ডেটাবেস সম্ভব হলে managed.
- ডেটা ভেতরে: পরের deploy-তে ডেটা হারাবে। ডেটাবেস আর ছবি যাবে volume, managed database আর object storage-এ।
latest: কোন সংস্করণ চলছে জানা যায় না, rollback কঠিন। নির্দিষ্ট tag আর digest ব্যবহার করুন।- Dockerfile-এ পাসওয়ার্ড: Image-এর layer-এ থেকে যায়, registry-র সবার হাতে যায়। Secret manager ব্যবহার করুন।
- Docker socket আর
--privileged: একটি app-এর ত্রুটি পুরো host-এর ঝুঁকি হয়ে যায়। Monitoring-এর জন্য শুধু-পড়ার, সীমিত agent বেছে নিন।
পরিমিত সমাধান: API আলাদা container, managed PostgreSQL, ছবি object storage-এ, secret বাইরে, অনুমতি সবচেয়ে কম।
-
নিজের মেশিনে দেখুন (ঐচ্ছিক): Docker থাকলে
docker run -d --name try-it --hostname demo alpine:3.20 sleep 300চালান। ভেতরেpsআর বাইরেdocker top try-itমিলিয়ে দেখুন। তারপরdocker rm -f try-itদিয়ে মুছুন। এটি স্থানীয় কাজ, cloud-এর খরচ নেই। প্রশ্ন: দুই জায়গায়sleep-এর PID এক কেন নয়?ভেতরে
sleepPID 1, কারণ PID namespace-এর প্রথম process. বাইরে তার একটি সাধারণ, বড় নম্বর থাকবে। Process একটিই, kernel দুই জগতে দুই নম্বর রাখে। Docker Desktop-এ এই “বাইরে” মানে পেছনের Linux VM, Windows বা macOS নয়। তাই Windows-এর Task Manager-এ এইsleepআলাদা করে দেখবেন না, আর সেটিই VM থাকার প্রমাণ।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমযে image মেশিনে নেই, সেটি docker run করলে ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- CLI daemon-কে অনুরোধ পাঠায়
- Registry থেকে tag-এর manifest পড়া হয়
- Layer নামিয়ে ডিস্কে খোলা হয়
- Image-এর ওপর writable layer বসে, namespace আর cgroup ঠিক হয়
- Image-এর নির্দেশিত প্রোগ্রাম PID 1 হিসেবে চালু হয়
Image আর container-এর সম্পর্ক সবচেয়ে ঠিকভাবে বলে কোনটি?
- Container শুধু-পড়ার প্যাকেজ, image তার চলমান রূপ
- Image শুধু-পড়ার প্যাকেজ, container তার ওপর writable layer বসিয়ে চালানো process
- দুটি একই জিনিসের দুই নাম
- Image থাকে host-এ, container থাকে registry-তে
একটি Alpine container আর একটি Debian-ভিত্তিক container একই Linux host-এ চলছে। কোন কথাটি ঠিক?
- দুটির নিজের নিজের আলাদা kernel চলছে
- এটি সম্ভব নয়, সব container-কে একই distro হতে হয়
- দুটির ব্যবহারকারী-স্তর আলাদা, kernel একটিই
- Hypervisor দুটিকে আলাদা kernel দিচ্ছে
একটি container-এ /tmp/a.txt লেখা হলো। কোন কাজের পরে ফাইলটি আর পাওয়া যাবে না?
- Process শেষ হয়ে container Exited হলে
- docker stop করলে
- docker stop, তারপর docker start করলে
- docker rm করলে
Windows বা macOS-এ Linux container চালাতে পেছনে কেন একটি Linux VM লাগে?
- Container-এর process-এর Linux kernel দরকার, আর ওই দুই OS-এর kernel আলাদা
- VM ছাড়া image নামানো যায় না
- Registry শুধু Linux থেকে সংযোগ নেয়
- লাইসেন্সের শর্তের কারণে
৬৪ GiB host-এ VM-প্রতি বাড়তি 600 MiB আর container-প্রতি বাড়তি 20 MiB ধরা হলো। App-এর আকার বাড়তে থাকলে container-এর ঘনত্বের সুবিধার কী হবে?
- সুবিধা বাড়তেই থাকবে
- সুবিধা কমে ১ গুণের কাছে আসবে
- সুবিধা ঠিক একই থাকবে
- VM-এ বেশি সংখ্যক app ধরবে
কোন ক্ষেত্রে শুধু সাধারণ container-এর ওপর ভরসা না করে VM, microVM বা sandbox ভাবা সবচেয়ে যুক্তিসঙ্গত?
- নিজের দলের দুটি API একই host-এ
- ল্যাপটপে local ডেটাবেস চালানো
- অচেনা গ্রাহকের লেখা কোড একই host-এ চালানো
- নিজের app-এর log দেখা
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Container app আর তার পুরো ব্যবহারকারী-স্তরের পরিবেশ একসাথে পাঠায়, তাই পরীক্ষা করা জিনিসটিই চলে।
- Image শুধু-পড়ার প্যাকেজ, container চলমান process, registry গুদাম। Tag বদলাতে পারে, digest বদলায় না।
- Container-এর kernel host-এর. VM-এর নিজের kernel থাকে। হালকা হওয়ার দাম দুর্বল সীমানা।
- Writable layer container-এর সাথে মরে. ডেটা বাইরে রাখুন, config বাইরে থেকে দিন।
- Windows আর macOS-এ Linux container চলে একটি Linux VM-এ, আর সব কাজে container ঠিক পছন্দ নয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৩৭ · Docker image — layer, Dockerfile আর build cacheপরবর্তী পর্বImage কীভাবে তৈরি হয় আর build কেন দ্রুত বা ধীর হয়।
- ক্লাউড কম্পিউটিং ও DevOps · কোর্স হোম৮১টি পর্বপুরো কোর্সের রোডম্যাপ।
- Container-এর ভেতর — namespace, cgroup আর Dockerগভীরে যানKernel-এর ভেতরের কৌশল, হাতে-কলমে।
- Virtual machine আর hypervisorপাশের ট্র্যাকHypervisor আর VM আসলে কীভাবে কাজ করে।
- পর্ব ১৯ · কোন compute কখনসিদ্ধান্তVM, container, PaaS আর FaaS-এর তুলনা।