19-bo‘lim
Virtualizatsiya va konteynerlar
Gipervizorlar, apparat virtualizatsiyasi, nom fazolari (namespaces), cgroups, konteyner ichkarisi va WSL.
Ushbu bo‘lim mundarijasi
Konteyner - "yengil virtual mashina" emas. U butunlay boshqa mexanizm, va farqni bilish muhim.
Ikki yondashuv #
| Xususiyat | Virtual mashina | Konteyner |
|---|---|---|
| Yadro | O'ziniki | Xostniki |
| Ishga tushish | 30-60 s | 50-200 ms |
| Hajmi | 1-20 GB | 10-500 MB |
| Boshqa OT | Ha (Linux ustida Windows) | Yo'q |
| Izolyatsiya | Apparat darajasida | Yadro darajasida |
| Ortiqcha yuk | 5-15% | ~0% |
Gipervizor turlari #
| Tur | Qayerda ishlaydi | Misollar |
|---|---|---|
| 1-tur (bare metal) | To'g'ridan-to'g'ri apparatda | ESXi, Xen, Hyper-V |
| 2-tur (hosted) | Xost OT ustida | VirtualBox, VMware Workstation |
| Gibrid | Yadro moduli sifatida | KVM |
grep -c -E "vmx|svm" /proc/cpuinfo
8
vmx (Intel VT-x) yoki svm (AMD-V) - apparat virtualizatsiyasini
qo'llab-quvvatlash. Natija 0 bo'lsa, BIOS da yoqish kerak.
lscpu | grep -i virtual
Virtualization: VT-x
Hypervisor vendor: KVM
Virtualization type: full
systemd-detect-virt
wsl
Virtualizatsiyadan oldin gipervizor mehmon OT ning har bir imtiyozli buyrug'ini ushlab, dasturiy usulda taqlid qilishi kerak edi (binary translation). Bu sekin va murakkab.
Intel VT-x va AMD-V protsessorga yangi ish rejimi qo'shdi:
| Rejim | Kim ishlaydi |
|---|---|
| VMX root | Gipervizor |
| VMX non-root, ring 0 | Mehmon yadrosi |
| VMX non-root, ring 3 | Mehmon ilovalari |
Mehmon yadrosi o'zini ring 0 da deb hisoblaydi va oddiy buyruqlarni bemalol bajaradi. Faqat haqiqatan xavfli buyruqlar gipervizorga "VM exit" orqali uzatiladi.
Natijada zamonaviy virtual mashinaning tezligi haqiqiy apparatga juda yaqin - ortiqcha yuk odatda 2-5%.
Xotira uchun esa EPT/NPT (ikki bosqichli sahifa jadvali) qo'shildi - mehmon virtual manzili ikki marta tarjima qilinadi, lekin apparat tomonidan.
Nom fazolari (namespaces) #
Konteyner - bu nom fazolari + cgroups + fayl tizimi. Sehr yo'q.
| Nom fazosi | Nimani izolyatsiya qiladi |
|---|---|
pid | Jarayonlar raqamlari |
net | Tarmoq interfeyslari, portlar |
mnt | Ulash nuqtalari |
uts | Xost nomi |
ipc | Bo'lishilgan xotira, semaforlar |
user | UID va GID xaritalari |
cgroup | cgroup ildizi |
time | Tizim vaqti (yangi) |
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
int main(void) {
DIR *k = opendir("/proc/self/ns");
if (k == NULL) {
printf("/proc/self/ns ochilmadi\n");
return 1;
}
int soni = 0;
struct dirent *y;
printf("Jarayonimiz a'zo bo'lgan nom fazolari:\n");
while ((y = readdir(k)) != NULL) {
if (y->d_name[0] != '.') {
soni++;
}
}
closedir(k);
printf(" Jami: %s\n", soni >= 6 ? "6 tadan ko'p" : "6 tadan kam");
printf(" Har biri ramziy havola\n");
printf(" Ikki jarayon bir xil havolaga ega bo'lsa - bir fazoda\n");
return 0;
}
Jarayonimiz a'zo bo'lgan nom fazolari:
Jami: 6 tadan ko'p
Har biri ramziy havola
Ikki jarayon bir xil havolaga ega bo'lsa - bir fazoda
ls -l /proc/self/ns/
lrwxrwxrwx 1 husanboy husanboy 0 sen 9 12:04 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 husanboy husanboy 0 sen 9 12:04 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 husanboy husanboy 0 sen 9 12:04 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 husanboy husanboy 0 sen 9 12:04 net -> 'net:[4026531840]'
lrwxrwxrwx 1 husanboy husanboy 0 sen 9 12:04 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 husanboy husanboy 0 sen 9 12:04 user -> 'user:[4026531837]'
lrwxrwxrwx 1 husanboy husanboy 0 sen 9 12:04 uts -> 'uts:[4026531838]'
Qavs ichidagi son - nom fazosining identifikatori. Ikki jarayonda bir xil bo'lsa, ular o'sha fazoda.
Nom fazosini qo'lda yaratish #
sudo unshare --pid --fork --mount-proc /bin/bash
ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 11876 5120 pts/0 S 12:08 0:00 /bin/bash
root 8 0.0 0.0 12480 3584 pts/0 R+ 12:08 0:00 ps aux
bash PID 1 bo'lib qoldi - va boshqa hech qanday jarayon
ko'rinmaydi.
sudo unshare --uts /bin/bash
hostname konteyner-sinov
hostname
konteyner-sinov
Chiqib ketgach xost nomi o'zgarmagan bo'ladi.
sudo unshare --net /bin/bash
ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
Faqat lo bor va u ham o'chirilgan - tarmoq butunlay yo'q.
docker run -it --rm alpine sh
Ichkarida:
ps aux # faqat o'z jarayonlaringiz
hostname # tasodifiy nom
ip addr # o'z virtual interfeysi
ls / # o'z fayl tizimi
Docker qiladigan ishlar:
- Nom fazolari yaratadi (
clonebilan); - cgroup ga qo'yadi (resurs cheklovi);
- Obraz qatlamlarini overlayfs bilan yig'adi;
pivot_rootbilan ildizni almashtiradi;- seccomp filtri bilan tizim chaqiruvlarini cheklaydi;
- Kerakli imkoniyatlarni tashlaydi.
Hammasi Linux yadrosining oddiy imkoniyatlari. Docker ularni qulay interfeysga o'radi.
Buni o'zingiz ham qila olasiz:
sudo unshare --pid --net --mount --uts --ipc --fork \
--mount-proc chroot /mnt/alpine /bin/sh
cgroups - resurs cheklovi #
cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma misc
sudo mkdir /sys/fs/cgroup/sinov
echo "100M" | sudo tee /sys/fs/cgroup/sinov/memory.max
echo "50000 100000" | sudo tee /sys/fs/cgroup/sinov/cpu.max
echo $$ | sudo tee /sys/fs/cgroup/sinov/cgroup.procs
cpu.max da 50000 100000 - har 100 ms davrda 50 ms, ya'ni
yarim yadro.
cat /sys/fs/cgroup/sinov/memory.current
8421376
| Fayl | Nima uchun |
|---|---|
memory.max | Qattiq xotira chegarasi |
memory.high | Yumshoq chegara - sekinlashtiriladi |
cpu.max | CPU ulushi |
pids.max | Maksimal jarayonlar soni |
io.max | Disk o'tkazuvchanligi |
docker run --memory=512m --cpus=1.5 nginx
Bu buyruq aynan yuqoridagi fayllarga yozadi.
memory.max ga yetgan jarayon OOM Killer tomonidan
o'ldiriladi - lekin faqat shu cgroup ichida.
dmesg | grep -i "memory cgroup"
Memory cgroup out of memory: Killed process 8421 (java)
Konteynerda bu eng ko'p uchraydigan sabab: dastur
Exit Code 137 bilan tugaydi (128 + 9 = SIGKILL).
docker inspect --format='{{.State.ExitCode}}' konteyner
137
JVM va Node.js kabi ish vaqtlari uzoq vaqt cgroup chegarasini
ko'rmasdi - ular /proc/meminfo dan xostning xotirasini
o'qib, uyum hajmini noto'g'ri hisoblardi.
Zamonaviy versiyalarda bu tuzatilgan, lekin eski obrazlarda aniq belgilash kerak:
java -XX:MaxRAMPercentage=75 -jar ilova.jar
memory.high esa yumshoqroq: chegaraga yetganda jarayon
o'ldirilmaydi, balki sekinlashtiriladi va sahifalar
chiqariladi. Bu ko'p hollarda afzalroq.
Konteyner ichida ekanini aniqlash #
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(void) {
/* 1-usul: /.dockerenv fayli */
int docker_fayli = access("/.dockerenv", F_OK) == 0;
/* 2-usul: cgroup yo'lida "docker" yoki "containerd" */
FILE *f = fopen("/proc/1/cgroup", "r");
int cgroup_belgisi = 0;
char qator[512];
if (f != NULL) {
while (fgets(qator, sizeof qator, f) != NULL) {
if (strstr(qator, "docker") || strstr(qator, "containerd")) {
cgroup_belgisi = 1;
break;
}
}
fclose(f);
}
/* 3-usul: PID 1 ning nomi */
f = fopen("/proc/1/comm", "r");
char nom[64] = "";
if (f != NULL) {
if (fgets(nom, sizeof nom, f) != NULL) {
nom[strcspn(nom, "\n")] = '\0';
}
fclose(f);
}
int init_emas = strcmp(nom, "systemd") != 0 && strcmp(nom, "init") != 0;
printf("/.dockerenv mavjudmi? : %s\n", docker_fayli ? "ha" : "yo'q");
printf("cgroup da docker bormi?: %s\n", cgroup_belgisi ? "ha" : "yo'q");
printf("PID 1 init emasmi? : %s\n", init_emas ? "ha" : "yo'q");
printf("\nEng ishonchli usul - systemd-detect-virt buyrug'i\n");
return 0;
}
/.dockerenv mavjudmi? : yo'q
cgroup da docker bormi?: yo'q
PID 1 init emasmi? : yo'q
Eng ishonchli usul - systemd-detect-virt buyrug'i
Uchala tekshiruv ham "yo'q" - demak biz konteyner ichida emasmiz.
Konteyner xavfsizligi #
Konteynerlar bir yadroni bo'lishadi. Yadrodagi zaiflik konteynerdan chiqish (container escape) imkonini beradi.
Xavfli sozlamalar:
| Sozlama | Nima uchun xavfli |
|---|---|
--privileged | Deyarli barcha imkoniyatlar, /dev to'liq |
-v /var/run/docker.sock:... | Docker ustidan to'liq nazorat = root |
--net=host | Xost tarmog'iga to'liq kirish |
--pid=host | Xost jarayonlarini ko'rish va o'ldirish |
-v /:/host | Butun fayl tizimi |
| root sifatida ishlash | Chiqib ketilsa - xostda root |
Xavfsizroq sozlash:
docker run \
--user 1000:1000 \
--read-only \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--security-opt=no-new-privileges \
--memory=512m \
--pids-limit=100 \
mening-obrazim
Haqiqiy izolyatsiya kerak bo'lsa (ishonchsiz kod ishga tushirish), virtual mashina ishlating: Kata Containers, gVisor yoki Firecracker - ular konteyner interfeysini beradi, lekin ichida alohida yadro bo'ladi.
Dockerfile qoidasi: hech qachon root ostida ishlamang:
RUN adduser -D -u 1000 ilova
USER ilova
CMD ["./server"]
WSL - o'ziga xos holat #
| Versiya | Qanday ishlaydi |
|---|---|
| WSL 1 | Linux tizim chaqiruvlarini Windows chaqiruvlariga tarjima |
| WSL 2 | Hyper-V ustida haqiqiy Linux yadrosi |
uname -r
5.15.167.4-microsoft-standard-WSL2
cat /proc/version
Linux version 5.15.167.4-microsoft-standard-WSL2 (root@buildvm) (gcc 11.2.0)
WSL 1 har bir Linux tizim chaqiruvini Windows ekvivalentiga tarjima qilardi. Ko'p chaqiruvlarning aniq mos kelishi yo'q edi - natijada fayl amallari juda sekin ishlardi.
WSL 2 esa yengil virtual mashinada haqiqiy Linux yadrosini ishlatadi. Natija:
| Amal | WSL 1 | WSL 2 |
|---|---|---|
git clone (Linux FS da) | Sekin | Tez |
/mnt/c dagi fayllar | Tez | Sekin |
| Docker | Ishlamaydi | Ishlaydi |
| Tizim chaqiruvlari mosligi | Qisman | To'liq |
Muhim amaliy maslahat: loyihalaringizni /home ichida
saqlang, /mnt/c da emas. Fayl amallari o'nlab barobar tez
bo'ladi.
/mnt/c DrvFs orqali ulanadi va Linux huquqlarini to'liq
qo'llab-quvvatlamaydi - chmod ishlamaydi, st_blocks nol
qaytaradi.
Taqqoslash jadvali #
| Texnologiya | Izolyatsiya | Yuk | Boshqa OT | Qachon ishlatiladi |
|---|---|---|---|---|
| Konteyner | O'rta | ~0% | Yo'q | Mikroxizmatlar, CI/CD |
| Virtual mashina | To'liq | 5-15% | Ha | Ko'p ijarachili, boshqa OT |
| Kata / gVisor | Yuqori | 3-8% | Yo'q | Ishonchsiz kod |
chroot | Past | 0% | Yo'q | Oddiy ajratish (xavfsizlik emas) |
| Unikernel | To'liq | Juda kam | Yo'q | Maxsus yuklar |
systemd-detect-virtbilan muhitingizni aniqlang./proc/cpuinfodavmxyokisvmbayrog'ini toping./proc/self/ns/dagi nom fazolarini sanang.unshare --pid --fork --mount-procbilan PID fazasini sinang.unshare --utsbilan xost nomini o'zgartirib ko'ring.unshare --netbilan tarmoqsiz muhit yarating.- cgroup yaratib,
memory.maxni belgilang. - Konteyner
Exit Code 137nimani anglatishini ayting. --privilegednima uchun xavfli ekanini tushuntiring.- WSL 2 da
/homeva/mnt/ctezligini solishtiring.
Xulosa #
- Konteyner o'z yadrosiga ega emas - xostnikini ishlatadi.
- Virtual mashina to'liq izolyatsiya beradi, lekin og'irroq.
- VT-x/AMD-V virtualizatsiyani apparat darajasida tezlashtiradi.
- Konteyner = nom fazolari + cgroups + fayl tizimi qatlamlari.
- Nom fazolari:
pid,net,mnt,uts,ipc,user,cgroup. unsharebilan konteynerni qo'lda yaratish mumkin.- cgroups CPU, xotira, disk va jarayonlar sonini cheklaydi.
- Exit Code 137 - cgroup xotira chegarasi (
SIGKILL). - Konteyner xavfsizlik chegarasi emas - yadro umumiy.
--privilegedvadocker.sock- root berish bilan teng.- Konteynerda hech qachon root ostida ishlamang.
- WSL 2 da loyihalarni
/homeichida saqlang.
Keyingi va oxirgi bo'limda hamma bilimlarni birlashtirib, mini qobiq yozamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.