সরাসরি পর্বে যান
পর্ব ৩৬ · ৮১-এর মধ্যে · মডিউল ৭

Container কেন — image, container, registry আর VM-এর সাথে তুলনা

Why Containers — Images, Containers, Registries and the Comparison with Virtual Machines

Taught in Bangla, this lesson starts from the works-on-my-machine problem, separates images, containers and registries, traces docker run, uses real Docker output to prove a container is a process, compares containers with virtual machines on kernel, startup, density and isolation, explains the short-lived writable layer, distinguishes Docker Engine, Docker Desktop and Podman, and says when not to use containers, with a simulator.

এই পর্বে যা শিখবেন

  • “আমার কম্পিউটারে তো চলে” সমস্যাটি কোথা থেকে আসে, আর 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যা ঘটল
Python3.123.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

  1. Dockerfilebuildরেসিপি
  2. Imagepushহিমায়িত প্যাকেট
  3. Registrypullহিমাগার
  4. Containerrunপাতের খাবার
ছবি ৩৬.১ — Image-এর ভেতরের স্তর আর Dockerfile আসবে পর্ব ৩৭-এ, registry-র গভীর আলোচনা পর্ব ৪১-এ।

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 আগে থেকে মেশিনে ছিল না, তা নামিয়ে চালানো হয়েছে।

  1. CLIdocker runঅনুরোধ
  2. Registrypulllayer নামানো
  3. Createrw layerপ্রস্তুতি
  4. StartPID 1process
TerminalImage মেশিনে নেই
$ 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
TerminalRegistry থেকে আনা শুরু
1.26-alpine: Pulling from library/nginx
5ab072e0bc4f: Pulling fs layer
b8651333baae: Pulling fs layer
… আরও ৬টি layer …
Terminalপ্রতিটি layer আলাদাভাবে আসে
b8651333baae: Download complete
…
0a9a5dfd008f: Pull complete
5e18ef93c435: Pull complete
…
TerminalTag থেকে digest জানা গেল
Digest: sha256:1eadbb07820339e8bbfed18c771691970baee292ec4ab2558f1453d26153e22d
Status: Downloaded newer image for nginx:1.26-alpine
Docker-এর ভেতরেএখনো কোনো আউটপুট নেই
writable layer  : নতুন, খালি
hostname        : demo
port            : 127.0.0.1:18362 → container 80
আলাদা দৃশ্য, সীমা : namespace, cgroup
Terminal আর container logProcess চালু
$ (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
TerminalPage দেখানো শুরু
$ 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
  1. CLI অনুরোধ পাঠাল। docker কমান্ডটি নিজে container চালায় না। এটি একটি daemon-কে API দিয়ে অনুরোধ পাঠায়। Daemon দেখল, nginx:1.26-alpine মেশিনে নেই।
  2. Registry-তে খোঁজ। Registry-র নাম লেখা নেই, তাই docker.io. Official image বলে library/nginx. Daemon আগে manifest পড়ে জানল এই tag-এ মোট ৮টি layer আছে।
  3. Layer নামানো আর খোলা। “Download complete” মানে সংকুচিত ফাইল এসেছে। “Pull complete” মানে সেটি যাচাই হয়ে ডিস্কে খোলা হয়েছে। Layer আগে থেকে থাকলে সেটি আর নামে না, পরে এর প্রমাণ দেখবেন।
  4. Digest পাওয়া গেল। এই লম্বা sha256 মানই image-এর আসল পরিচয়। Tag একটি মুখের নাম, digest হলো পরিচয়পত্রের নম্বর। এই ধাপে image প্রস্তুত, এখনো কোনো process নেই।
  5. Container তৈরি। শুধু-পড়ার layer-গুলোর ওপর একটি খালি writable layer বসল। ঠিক হলো hostname demo, port-এর সংযোগ, আলাদা দৃশ্য আর রিসোর্সের সীমা। এই ধাপের কোনো আউটপুট টার্মিনালে আসে না।
  6. Process চালু। Image-এর নির্দেশ অনুযায়ী প্রথম process চলল, সে-ই container-এর ভেতরে PID 1. -d দেওয়ায় টার্মিনালে শুধু container id ছাপা হলো, আর log জমা হলো docker logs-এ।
  7. 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 ঢোকায়। সেটি বাইরে থেকে এসেছে বলে ভেতরে তার PPID 0 দেখায়।
  • docker top kernel-এর বাইরের দৃষ্টিতে দেখায়: একই 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

VM-এর স্তর বনাম container-এর স্তর Virtual machine: প্রতিটিতে নিজের kernel App AApp BApp C LibraryLibraryLibrary Guest kernelGuest kernelGuest kernel Hypervisor Physical হার্ডওয়্যার Container: সবার kernel একটিই App AApp BApp C LibraryLibraryLibrary Container runtime Host OS-এর একটি kernel Physical হার্ডওয়্যার
ছবি ৩৬.২ — লাল স্তরটি দেখুন: VM-এ kernel তিনটি, container-এ একটি। বাস্তবে cloud-এ container প্রায়ই একটি VM-এর ভেতরেই চলে, তাই দুটি ছবি একসাথেও থাকতে পারে।

“সবার 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 machineContainer
Kernelপ্রতিটির নিজেরhost-এর kernel সবার এক
প্যাকেজের আকারসাধারণত GB-র ঘরেকয়েক MB থেকে কয়েকশো MB, কখনো বেশি
ভিন্ন OSপারে: Linux host-এ Windows VMLinux 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 বা sandboxKernel-এর সীমানায় একা ভরসা করা ঠিক নয়
নিয়ন্ত্রক শর্তে আলাদা 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 Enginedockerd daemon, API আর CLILinux serverOpen source. সাধারণত root হিসেবে চলে, rootless মোডও আছে।
Docker Desktopডেস্কটপ অ্যাপ: Engine, একটি Linux VM, GUI আর ComposeWindows, macOS, Linux ডেস্কটপবড় প্রতিষ্ঠানের জন্য paid হতে পারে। শর্ত বদলায়, Docker-এর সাইটে দেখে নিন।
PodmanDaemon ছাড়া container টুলLinux; অন্য OS-এ podman machine-এর VMRootless-কেন্দ্রিক, 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
  1. শুরু। মেশিনে শুধু image আছে, কোনো container নেই। Image নিজে কিছু চালায় না।
  2. Container তৈরি। docker run image-এর ওপর একটি খালি writable layer বসাল। এখনো কিছু লেখা হয়নি।
  3. ফাইল লেখা। Process চলল আর /tmp/note.txt লিখল। ফাইলটি গেল writable layer-এ। Image-এর কোনো পরিবর্তন হয়নি।
  4. Process শেষ। PID 1 শেষ হলো, অবস্থা Exited (0). কিন্তু থেমে যাওয়া মানে মুছে যাওয়া নয়। docker diff দেখাচ্ছে: C /tmp মানে ফোল্ডার বদলেছে, A /tmp/note.txt মানে নতুন ফাইল।
  5. ফাইল এখনো আছে। Container থেমে আছে, তবু docker cp দিয়ে ফাইলটি পড়া গেল: invoice-1001. Writable layer container-এর আয়ু পর্যন্ত বাঁচে, process-এর আয়ু পর্যন্ত নয়।
  6. Container মুছে ফেলা। docker rm writable layer-সহ container মুছে দিল। ফাইলটি চিরতরে গেল। Image অক্ষত।
  7. নতুন 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 directory

docker 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

ভাগ করা শুধু-পড়ার layer আর আলাদা writable layer c1c2c3 writablewritablewritable App layer Python layer Base OS layer শুধু-পড়ার, সবার এক কপি
ছবি ৩৬.৩ — ডিস্কে শুধু-পড়ার layer একবারই থাকে। Layer-এর ভেতরের ফাইল-কৌশল (overlayfs) আছে অপারেটিং সিস্টেম কোর্সে।

দুটি আসল পরীক্ষা ছবিটিকে সমর্থন করে। প্রথমে একই 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:0599dbb4b1ff

c1-এর ফাইল 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 আর ঠিকানাগুলো বানানো। লক্ষ করুন কোনটি বদলায় না, আর কোনটি বদলায়।

ল্যাপটপbuild আর চালানো
CIনতুন build, test
Registryএক কপি, এক digest
Serverpull, run
ল্যাপটপনমুনা
image    : shop-api:dev
DB_HOST  : localhost
ফল       : চলছে, কিন্তু এটি শুধু শিলার মেশিনে
CIনমুনা
image    : shop-api:1.4.2
digest   : sha256:ab12…
DB_HOST  : test-db
ফল       : ১২০টি test পাস
Registryনমুনা
image    : shop-api:1.4.2
digest   : sha256:ab12…
ফল       : test-পাস করা image-টিই জমা হলো
Serverনমুনা
image    : shop-api:1.4.2
digest   : sha256:ab12…
DB_HOST  : prod-db
secret   : secret manager থেকে, image-এ নয়
ফল       : চলছে
Serverনমুনা: ফিরে যাওয়া
নতুন 1.4.3 : ভুল আচরণ, চালু রাখা হলো না
ফিরে গেল   : shop-api:1.4.2
digest     : sha256:ab12…
ডেটা       : container-এর বাইরে, অক্ষত
  1. ল্যাপটপে। শিলা image বানিয়ে চালালেন। DB হিসেবে নিজের মেশিনের ডেটাবেস। এই image-টি পরীক্ষার জন্য, প্রকাশের জন্য নয়।
  2. CI-তে। কোড push হতেই একটি পরিষ্কার মেশিনে নতুন করে image তৈরি হলো, পেল sha256:ab12…. Test চলল সেই image-এর ভেতরেই, DB হলো test-db. CI-এর কথা এখানে আছে।
  3. Registry-তে। Test পাস করা image-টিই push হলো। Digest বদলায়নি। নতুন করে কিছু বানানো হয়নি।
  4. Server-এ। Server একই digest pull করে চালাল। ভেতরের সব কিছু CI-তে পরীক্ষা করা জিনিসের সাথে হুবহু এক। শুধু বাইরে থেকে দেওয়া environment variable আর secret আলাদা: DB_HOST এখন prod-db.
  5. Rollback. পরের সংস্করণে সমস্যা হলো। আগের image এখনো registry-তে আছে, তাই একই digest আবার চালানো গেল। ডেটা বাইরে ছিল বলে হারায়নি। তবে ডেটাবেসের গঠন বদলে থাকলে ফেরা অত সহজ নয়, সেটি আলাদাভাবে ভাবতে হয়।
Image এক থাকে, config বদলায়, ডেটা বাইরে থাকে।
Image-এর সাথে যায়যায় না
App-এর কোড, library, runtimeHost-এর 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-নির্ভর পুরোনো softwareContainer 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 mountHost ঝুঁকিতে পড়েআসলে কোন ক্ষমতা লাগে তা জানা
-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-এর ডেটাও মুছতে পারেসতর্কতা না পড়ে না চালানো, নাম ধরে মোছা
অস্তিত্বহীন flagError, বা ভুল আচরণdocker run --help আর official docs দেখা

AI-কে সিদ্ধান্ত নিতে না বলে, সিদ্ধান্তের যুক্তি জিজ্ঞেস করুন। নিচের কাঠামোয় পরিবেশ, শর্ত আর ঝুঁকি স্পষ্ট লেখা আছে। কোনো আসল secret, ঠিকানা বা account id লিখবেন না।

আমার একটি [Python/Node] API আছে। দিনে প্রায় [X] request আসে, দল [N] জনের, আর একটি ছোট cloud VM আছে। ১. এটি container-এ নেওয়া ঠিক হবে কি না, পক্ষে আর বিপক্ষে যুক্তি দাও। না নেওয়ার বিকল্পও বলো। ২. নিলে কোন ডেটা container-এর বাইরে রাখতে হবে, তালিকা দাও। ৩. docker run কমান্ড দিলে latest tag, --privileged, docker.sock আর 0.0.0.0-তে port খোলা এড়িয়ে চলো। ৪. প্রতিটি flag-এর অর্থ এক লাইনে লেখো, আর কোন flag তোমার নিশ্চিত নয় তা জানাও। ৫. কোনো মুছে ফেলার কমান্ড দিলে আগে সতর্ক করো।

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 না থাকলেও প্রথম তিনটি করা যাবে।

  1. ঘনত্ব আর ডিস্কের হিসাব: (ক) একটি 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 ভাগ না করার দাম প্রায় ৮.৬ গুণ।

  2. ফলাফল অনুমান করুন: কমান্ডগুলো এই ক্রমে চলল। প্রতিটির পরে কী দেখবেন? (১) 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-এর সাথে মেলে।

  3. প্রস্তাবটির সমালোচনা করুন: একজন সহকর্মী বললেন: “আমাদের অনলাইন বাজারের 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 বাইরে, অনুমতি সবচেয়ে কম।

  4. নিজের মেশিনে দেখুন (ঐচ্ছিক): 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 এক কেন নয়?

    ভেতরে sleep PID 1, কারণ PID namespace-এর প্রথম process. বাইরে তার একটি সাধারণ, বড় নম্বর থাকবে। Process একটিই, kernel দুই জগতে দুই নম্বর রাখে। Docker Desktop-এ এই “বাইরে” মানে পেছনের Linux VM, Windows বা macOS নয়। তাই Windows-এর Task Manager-এ এই sleep আলাদা করে দেখবেন না, আর সেটিই VM থাকার প্রমাণ।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমযে image মেশিনে নেই, সেটি docker run করলে ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. CLI daemon-কে অনুরোধ পাঠায়
  2. Registry থেকে tag-এর manifest পড়া হয়
  3. Layer নামিয়ে ডিস্কে খোলা হয়
  4. Image-এর ওপর writable layer বসে, namespace আর cgroup ঠিক হয়
  5. 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 দেখা

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Container app আর তার পুরো ব্যবহারকারী-স্তরের পরিবেশ একসাথে পাঠায়, তাই পরীক্ষা করা জিনিসটিই চলে।
  2. Image শুধু-পড়ার প্যাকেজ, container চলমান process, registry গুদাম। Tag বদলাতে পারে, digest বদলায় না।
  3. Container-এর kernel host-এর. VM-এর নিজের kernel থাকে। হালকা হওয়ার দাম দুর্বল সীমানা।
  4. Writable layer container-এর সাথে মরে. ডেটা বাইরে রাখুন, config বাইরে থেকে দিন।
  5. Windows আর macOS-এ Linux container চলে একটি Linux VM-এ, আর সব কাজে container ঠিক পছন্দ নয়।

পরের পর্বে: Image-এর ভেতরে ঢুকব। Layer কীভাবে তৈরি হয়, Dockerfile-এর প্রতিটি লাইন কী করে, আর build cache কেন কখনো কাজ করে, কখনো করে না।

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

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান