Linux containerlari: hypervisor’siz izolyatsiya
"Bir xil ilovani har bir mashinada bir xil tarzda" ishga tushirishi kerak bo'lgan jamoa ertami-kechmi operatsiyalardagi eng eski muammoga duch keladi: "mening kompyuterimda ishlaydi" (it works on my machine). Container bu muammoni ilovani uning aniq bog'liqliklari — kutubxonalar, runtime, konfiguratsiya — bilan birga bitta birlikka joylash orqali hal qiladi. Bu birlik noutbukda ham, CI runner’da ham, production serverda ham bir xil ishlaydi, chunki har bir holatda u bir xil narsa ustida ishlaydi: Linux kernel ustida. U bu kernel’ni host bilan kernel’ning o'zi beradigan izolyatsiya chegaralari orqali bo'lishadi. Container’larni tushunish ular hypervisor ma’nosidagi virtualizatsiya texnologiyasi emasligini tushunishdan boshlanadi. Ular Linux kernel’da yillar davomida mavjud bo'lgan jarayonlarni izolyatsiya qilish imkoniyatlaridan ma’lum, tartibli tarzda foydalanish bo'lib, ularni qulay qiladigan vositalar bilan o'ralgan. Bu maqola fikrlash modelini quradi. Keyingi ikki maqola — Namespaces va cgroups va Docker va Podman: amaliy ishlash — navbatma-navbat kernel mexanizmini va kundalik vositalarni ko'rsatadi.
Nimalarni o'rganasiz
Bu maqola oxirida siz quyidagilarni qila olasiz:
- Linux container nima ekanini va nima emasligini aniq tushuntirish;
- container’larni virtual mashinalar bilan shunchaki "container’lar yengilroq" darajasida emas, arxitektura darajasida taqqoslash;
- OCI image, OCI runtime va ularning
docker runostida bir-biri bilan qanday bog'liqligini tasvirlash; docker inspectchiqishini o'qish va uni uning ortidagi izolyatsiya mexanizmlari bilan bog'lash;- qachon container to'g'ri vosita, qachon esa VM xavfsizroq tanlov ekanini mulohaza qilish.
Container — mashina emas, jarayon
Linux container haqidagi eng muhim fakt — ko'pchilik yangi boshlovchilar teskari tushunadigan fakt: container — host kernel’ida bevosita ishlaydigan, tizimni cheklangan ko'rinishda ko'radigan oddiy Linux jarayoni. Kernel’da container’ga xos bajarilish rejimi ham, alohida "container kernel’i" ham yo'q. Jarayonni izolyatsiya qilingan container kabi ko'rinadigan va ishlaydigan qiladigan narsa — o'sha bitta jarayonga qo'llanilgan ikki kernel imkoniyatining birikmasi:
- namespaces jarayon nimani ko'ra olishini cheklaydi — uning o'z jarayonlar daraxti, o'z tarmoq interfeyslari, o'z fayl tizimi mount nuqtalari, o'z hostname’i;
- cgroups jarayon qancha resurs iste’mol qila olishini cheklaydi — unga qancha CPU, xotira va I/O o'tkazish qobiliyatidan foydalanishga ruxsat berilgani.
Qolgan hamma narsa — image’lar, registry’lar, docker run, Kubernetes — shu ikki kernel primitivi ustiga qurilgan vositalar. Namespaces va cgroups maqolasi ikkalasini amaliy buyruqlar bilan batafsil ko'rib chiqadi. Bu maqola uchun muhim nuqta arxitekturaviy: container izolyatsiyasi alohida bajarilish muhiti emas, kernel majburlaydigan ko'rinish.
Bu nginx jarayonlari hostning o'z jarayonlar jadvalida, hostning o'z kernel’i ostida, oddiy PID bilan ko'rinadi — chunki ular aynan shunday. Container ichida esa nginx o'zini PID 1 deb ko'radi, o'z jarayon namespace’ida yolg'iz va xuddi shu mashinada minglab boshqa jarayonlar mavjudligidan bexabar.
Container’lar va virtual mashinalar: chegara aslida qayerda
Virtual mashina va container bir-biriga yaqin muammolarni — izolyatsiya qilingan ish yukini ishga tushirishni — tubdan farq qiladigan arxitekturalar orqali hal qiladi:
Virtual machine stack:
Hardware -> Hypervisor -> Guest kernel (full OS) -> Guest processes
Container stack:
Hardware -> Host kernel -> Namespaced/cgrouped process (the "container")
VM’ning izolyatsiya chegarasi — hypervisor. U butunlay alohida guest kernel’ga apparatni emulyatsiya qiladi (yoki to'g'ridan-to'g'ri uzatadi). Guest butunlay boshqa operatsion tizim bo'lishi mumkin — o'z kernel’i, o'z drayverlari va o'z yuklanish jarayoni bilan. Container’ning izolyatsiya chegarasi esa shu hostdagi har bir container bo'lishadigan yagona kernel ichidagi kernel ma’lumotlar tuzilmalari to'plami. Bunda guest kernel ham, yuklanish jarayoni ham yo'q va ostida biror shakldagi VM bo'lmasa, Linux hostda Windows container’ini ishga tushirish imkoniyati ham yo'q.
| Virtual mashina | Container | |
|---|---|---|
| Izolyatsiya chegarasi | Hypervisor + guest kernel | Bitta umumiy kernel’dagi namespaces + cgroups |
| Ishga tushish vaqti | Bir necha soniyadan o'nlab soniyagacha (kernel yuklanishi) | Millisoniyalardan bir necha soniyagacha (jarayon ishga tushishi) |
| Kernel | Har bir VM uchun bittadan, hostnikidan farq qilishi mumkin | Har doim host bilan umumiy |
| Har bir instansiya uchun qo'shimcha xarajat | To'liq guest OS xotira va CPU iste’moli | Faqat jarayonning o'z iste’moli |
| Turli OS ish yuklari | Ha (Linux host Windows guest’ni ishga tushira oladi) | Yo'q (Linux container’iga Linux bilan mos kernel kerak) |
| Buzib kirilganda attack surface | Guest kernel; hostga chiqish uchun hypervisor xatosi kerak | Host kernel umumiy; kernel xatosi izolyatsiyaga bevosita ta’sir qilishi mumkin |
Oxirgi qatordagi xavfsizlik chizig'i alohida ta’kidlanishi kerak va bu intervyudagi keng tarqalgan tuzoq. Hostdagi har bir container bitta kernel’ni bo'lishgani uchun namespace yoki cgroup izolyatsiyasini buzadigan kernel zaifligi butun hostni buzib kirish uchun hypervisor’dagi shunga o'xshash xatoga qaraganda ancha to'g'ridan-to'g'ri yo'l. Chunki hypervisor xatosi avval apparat darajasidagi virtualizatsiyadan chiqib ketishi kerak. Shuning uchun haqiqatan ishonchsiz, ko'p ijarachili (multi-tenant) ish yuklarini oddiy container’larda — qo'shimcha qatlam beradigan VM, gVisor uslubidagi sandbox runtime yoki Kata Containers’siz — ishga tushirish nazariy emas, haqiqiy production xavfi.
Intervyuda: "container’lar VM’lar kabi xavfsizmi?"
Halol javob — yo'q, standart holatda emas. Nega ekanini tushuntirish marketing gaplarini takrorlash emas, haqiqiy tushunishni ko'rsatadi. VM izolyatsiyasini apparat yordamidagi virtualizatsiya va alohida kernel majburlaydi. Container izolyatsiyasini esa to'liq dasturiy ta’minot — mashinadagi har bir jarayon uchun namespace va cgroup chegaralarini to'g'ri talqin qiladigan bitta umumiy kernel — majburlaydi. Kernel darajasidagi container’dan chiqib ketish zaifligi (yillar davomida bir nechtasi oshkor qilingan) container ichidagi jarayonga to'g'ridan-to'g'ri hostga yetib borish imkonini berishi mumkin. Amalda bu rootless container’lar (Docker va Podman: amaliy ishlash maqolasida Podman’ning rootless modeli ko'rib chiqiladi), faqat o'qiladigan fayl tizimlari, olib tashlangan Linux capabilities va haqiqatan ishonchsiz ish yuklari uchun baribir container’larni VM chegarasi ichida ishga tushirish orqali yumshatiladi (keng tarqalgan bulut shabloni: har bir ijarachi uchun VM, uning ichida har bir ilova uchun container’lar).
Image’lar, container’lar va OCI spetsifikatsiyasi
Kundalik suhbatda ikki so'z deyarli bir-birining o'rnida ishlatiladi, lekin ular turli narsalarni anglatadi:
- Image — faqat o'qiladigan, qatlamli fayl tizimi snapshot’i va metama’lumotlar (ishga tushiriladigan standart buyruq, muhit o'zgaruvchilari, ochiladigan portlar). U diskda yoki registry’da turadigan va o'z-o'zidan hech narsa qilmaydigan ma’lumot.
- Container — image’ning ishlayotgan (yoki to'xtatilgan) nusxasi (instance). Bu image’ning faqat o'qiladigan qatlamlari ustiga qo'shilgan yoziladigan qatlam hamda uning ichida ishlayotgan jarayonni izolyatsiya qiladigan namespace va cgroup konfiguratsiyasi.
Bitta image’dan bir vaqtning o'zida bir nechta container yaratish mumkin va ularning har biri o'z mustaqil yoziladigan qatlamiga ega bo'ladi. Bu ishlayotgan jarayonning u ishga tushirilgan diskdagi bajariladigan faylga munosabati bilan aynan bir xil.
Bu qatlamlash va uni qurish hamda ishga tushirish uchun Docker ham, Podman ham foydalanadigan format Linux Foundation tarkibidagi Open Container Initiative (OCI) loyihasi tomonidan standartlashtirilgan. OCI amalda muhim bo'lgan ikkita spetsifikatsiyani belgilaydi:
- image spec — image’ning qatlamlari, manifest’i va konfiguratsiyasi qanday tuzilishi va saqlanishi;
- runtime spec — mos keladigan runtime image’ning ochilgan fayl tizimi va konfiguratsiya faylini olib, haqiqiy namespace va cgroup bilan cheklangan jarayonni qanday yaratishi kerakligi.
OCI runtime spec’ining namunaviy implementatsiyasi bo'lgan runc — Docker, Podman va containerd oxir-oqibat Namespaces va cgroups maqolasida tasvirlangan unshare/clone ga asoslangan haqiqiy jarayon yaratish uchun chaqiradigan quyi darajadagi vosita. Bu standartlashtirishning amaliy foydasi — ko'chiriluvchanlik (portability): docker build bilan qurilgan image’ni qayta qurmasdan Podman, containerd (Kubernetes ostida container’larni aslida shu ishga tushiradi) yoki istalgan boshqa OCI’ga mos runtime ishga tushira oladi.
docker build ──> OCI image ──> containerd/CRI-O ──> runc ──> namespaced process
^
└── same image, any OCI-compliant runtime
docker inspect ni o'qish
docker inspect container’ning to'liq konfiguratsiyasi va joriy holatini JSON sifatida chiqaradi va undagi har bir maydon yuqoridagi izolyatsiya mexanizmlaridan biriga borib taqaladi. Bu container siz taxmin qilgan narsa emas, aslida nima ekanini tasdiqlash uchun eng foydali buyruq:
docker run -d --name web nginx:alpine
docker inspect -f '{{.State.Pid}} {{.State.Status}} {{.HostConfig.NetworkMode}}' web
.State.Pid(48213) — container’ning asosiy jarayoni oddiy host PID’i sifatida. Bu host’daps -eo pid,commko'rsatadigan o'sha raqam va jarayon tegishli bo'lgan har bir namespace ro'yxati turgan/proc/48213/ns/ga kirish nuqtasi (Namespaces va cgroups maqolasida bosqichma-bosqich ko'rib chiqiladi)..State.Pidning0bo'lishi container ishlamayotganini anglatadi — izolyatsiya qilinadigan jarayon yo'q..State.Status(running) ishlab turgan container’niexited,pausedyokirestartingdan ajratadi. Bu yerdagiexitedholati va noldan farqli.State.ExitCode— container ishlab turmasdan to'xtab qolaversa, birinchi navbatda o'qiladigan narsa..HostConfig.NetworkMode(default) container’ning standart bridge’ga ulangan o'z network namespace'i borligini tasdiqlaydi.hostqiymati esa container hostning network namespace’ini bo'lishishini va umuman tarmoq izolyatsiyasi yo'qligini anglatardi — bu keng tarqalgan va osongina e’tibordan chetda qoladigan noto'g'ri sozlash.
Intervyuda qilinishi kerak bo'lgan bog'lanish: docker inspect sizga virtual mashina xususiyatlarini ko'rsatmayapti. U bitta oddiy host jarayonining PID’i, holati va namespace/cgroup konfiguratsiyasini ko'rsatyapti — container aynan shu.
Nega container’lar va qachon ular kerak emas
Container’lar bir vaqtning o'zida uchta muammoni hal qiladi. Ularning ishlab chiqishda ham, operatsiyalarda ham shunchalik tez tarqalishining sababi shu:
- Qayta takrorlanuvchanlik (reproducibility). Image kutubxonalarning aniq versiyalarini mahkamlaydi, shuning uchun "mening kompyuterimda ishlaydi" "bu image’da ishlaydi"ga aylanadi va buni xuddi shu image’ni ishga tushirgan har kim tekshira oladi.
- Zichlik (density). Yuklanadigan guest kernel ham, har bir instansiya uchun to'liq OS xotira iste’moli ham yo'qligi sababli, resurs ehtiyojlari o'xshash ish yuklari uchun bitta host VM’larga qaraganda ancha ko'p container’ni ishga tushira oladi.
- Tez va bir martalik hayot sikli. Container kernel yuklanishiga emas, jarayon ishga tushishiga ketadigan vaqtda ishga tushadi. Tez masshtablash, tez-tez qayta deploy qilish va container ichida quradigan va sinaydigan CI pipeline’larni zamonaviy ishlab chiqish kutadigan tezlikda amalda mumkin qiladigan narsa shu.
Container’lar quyidagi holatlarda noto'g'ri vosita: ish yuki o'zi ishonmaydigan boshqa ijarachilardan haqiqatan kernel darajasidagi izolyatsiyaga muhtoj bo'lganda (ixtiyoriy mijoz kodini ishga tushiradigan ko'p ijarachili SaaS — klassik holat, bunday platformalarning ko'pchiligi hali ham container guruhlari atrofiga VM chegarasini qo'yadi), ish yukiga host beradigandan boshqa kernel yoki OS kerak bo'lganda, yoki ilova bitta uzoq yashovchi, holatni saqlaydigan tizim bo'lganda (qat’iy I/O va NUMA sozlash talablari bor katta on-premises ma’lumotlar bazasi). Oxirgi holatda alohida VM yoki bare-metal hostning operatsion soddaligi container zichligidan ustun keladi.
Keng tarqalgan xatolar
- "Container" va "virtual mashina" faqat hajmi bilan farq qiladi deb hisoblash. Yuqorida ko'rsatilganidek, farq arxitekturaviy — umumiy kernel va alohida kernel — va bu shunchaki unumdorlik raqami emas, xavfsizlik va ko'chiriluvchanlik uchun bevosita oqibatlarga ega.
- Ishonchsiz kod uchun container izolyatsiyasi VM izolyatsiyasiga teng deb o'ylash. Container o'zingizning ishonchli ish yuklaringiz uchun kuchli standart chegara. Lekin o'z-o'zidan uchinchi tomonlarning ixtiyoriy ishonchsiz kodini ishga tushirishga yaroqli sandbox emas.
- Image’ni container bilan chalkashtirish. To'xtatilgan container hali ham murojaat qiladigan image’ni o'chirish yoki container’ning yoziladigan qatlamidagi o'zgarishlar u qurilgan image’da paydo bo'lishini kutish — ikkalasi ham bu ikkisini farqlamaslikdan kelib chiqadi.
- Har bir container runtime — Docker deb o'ylash. Kubernetes v1.24 dan beri Docker daemon’ini standart runtime sifatida bevosita ishlatmaydi (u CRI orqali containerd yoki CRI-O dan foydalanadi). Image’larning o'zi esa ularni qaysi runtime bajarishidan qat’i nazar OCI bilan mos va ko'chiriluvchan bo'lib qoladi.
Mashqlar
docker run -d --name web nginx:alpineni ishga tushiring, keyin host’daps -eo pid,ppid,commbilan mosnginxjarayon ID’larini toping. Container ichidaginginxo'zini PID 1 deb hisoblasa ham, bu PID’lar nega oddiy host PID’lari ekanini o'z so'zlaringiz bilan tushuntiring.- Siz ishonmaydigan mijozning kodini ishga tushirish uchun virtual mashinaning izolyatsiya modelini oddiy container’dan ko'ra mosroq qiladigan ikkita xususiyatini sanab o'ting.
- Xuddi shu image’ni
docker pull nginx:alpinebilan va (agar Podman o'rnatilgan bo'lsa)podman pull nginx:alpinebilan yuklab oling, keyindocker imagesvapodman imageschiqishlarini taqqoslang. Ikkalasi ham bir xil image’ni o'qiy olishi OCI image formati haqida nimani tasdiqlashini tushuntiring.
Xulosa
Linux container — o'z kernel’iga ega alohida virtual mashina emas, balki tizimning qolgan qismidan namespaces (nimani ko'ra olishi) bilan izolyatsiya qilingan va cgroups (qancha iste’mol qila olishi) bilan cheklangan oddiy jarayon. Image’lar — OCI image spec’ida belgilangan faqat o'qiladigan, qatlamli, ko'chiriluvchan artefakt. Container’lar esa runc kabi OCI’ga mos runtime tomonidan yaratilgan shu image’larning ishlayotgan nusxalari. Bu farq — umumiy kernel, dasturiy jihatdan majburlangan izolyatsiya — container’larni aynan tez va zich qiladigan narsa, va aynan haqiqatan ishonchsiz ish yuklari uchun ularni VM’dan zaifroq xavfsizlik chegarasiga aylantiradigan narsa. Namespaces va cgroups bularning hammasi ortidagi ikki kernel mexanizmini amaliy tafsilotlari bilan ochib beradi.