19-bo‘lim

Virtualizatsiya va konteynerlar

Gipervizorlar, apparat virtualizatsiyasi, nom fazolari (namespaces), cgroups, konteyner ichkarisi va WSL.

🕑 24 daqiqa o‘qish 📄 1 247 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Ikki yondashuv
  2. Gipervizor turlari
  3. Nom fazolari (namespaces)
  4. Nom fazosini qo'lda yaratish
  5. cgroups - resurs cheklovi
  6. Konteyner ichida ekanini aniqlash
  7. Konteyner xavfsizligi
  8. WSL - o'ziga xos holat
  9. Taqqoslash jadvali
  10. Xulosa

Konteyner - "yengil virtual mashina" emas. U butunlay boshqa mexanizm, va farqni bilish muhim.

Ikki yondashuv #

Virtual mashina va konteyner Virtual mashina Ilova A kutubxonalar Ilova B kutubxonalar Ilova C kutubxonalar Mehmon OT Mehmon OT Mehmon OT Gipervizor Xost OT + apparat Konteynerlar Ilova A kutubxonalar Ilova B kutubxonalar Ilova C kutubxonalar Konteyner ishga tushiruvchisi Bitta umumiy yadro Xost OT + apparat Virtual mashina Ishga tushish: 30-60 soniya Hajmi: gigabaytlar Izolyatsiya: to'liq Konteyner Ishga tushish: millisekundlar Hajmi: megabaytlar Izolyatsiya: yadro darajasida
Konteyner - alohida OT emas, izolyatsiyalangan jarayonlar guruhi
XususiyatVirtual mashinaKonteyner
YadroO'zinikiXostniki
Ishga tushish30-60 s50-200 ms
Hajmi1-20 GB10-500 MB
Boshqa OTHa (Linux ustida Windows)Yo'q
IzolyatsiyaApparat darajasidaYadro darajasida
Ortiqcha yuk5-15%~0%

Gipervizor turlari #

TurQayerda ishlaydiMisollar
1-tur (bare metal)To'g'ridan-to'g'ri apparatdaESXi, Xen, Hyper-V
2-tur (hosted)Xost OT ustidaVirtualBox, VMware Workstation
GibridYadro moduli sifatidaKVM
Terminal
grep -c -E "vmx|svm" /proc/cpuinfo
Natija
8

vmx (Intel VT-x) yoki svm (AMD-V) - apparat virtualizatsiyasini qo'llab-quvvatlash. Natija 0 bo'lsa, BIOS da yoqish kerak.

Terminal
lscpu | grep -i virtual
Natija
Virtualization:                  VT-x
Hypervisor vendor:               KVM
Virtualization type:             full
Terminal
systemd-detect-virt
Natija
wsl
Apparat virtualizatsiyasi nima uchun kerak

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:

RejimKim ishlaydi
VMX rootGipervizor
VMX non-root, ring 0Mehmon yadrosi
VMX non-root, ring 3Mehmon 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 fazosiNimani izolyatsiya qiladi
pidJarayonlar raqamlari
netTarmoq interfeyslari, portlar
mntUlash nuqtalari
utsXost nomi
ipcBo'lishilgan xotira, semaforlar
userUID va GID xaritalari
cgroupcgroup ildizi
timeTizim vaqti (yangi)
C
#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;
}
Natija
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
Terminal
ls -l /proc/self/ns/
Natija
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 #

Terminal
sudo unshare --pid --fork --mount-proc /bin/bash
Terminal
ps aux
Natija
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.

Terminal
sudo unshare --uts /bin/bash
hostname konteyner-sinov
hostname
Natija
konteyner-sinov

Chiqib ketgach xost nomi o'zgarmagan bo'ladi.

Terminal
sudo unshare --net /bin/bash
ip addr
Natija
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.

Bu aynan Docker qiladigan ish
Terminal
docker run -it --rm alpine sh

Ichkarida:

Terminal
ps aux        # faqat o'z jarayonlaringiz
hostname      # tasodifiy nom
ip addr       # o'z virtual interfeysi
ls /          # o'z fayl tizimi

Docker qiladigan ishlar:

  1. Nom fazolari yaratadi (clone bilan);
  2. cgroup ga qo'yadi (resurs cheklovi);
  3. Obraz qatlamlarini overlayfs bilan yig'adi;
  4. pivot_root bilan ildizni almashtiradi;
  5. seccomp filtri bilan tizim chaqiruvlarini cheklaydi;
  6. Kerakli imkoniyatlarni tashlaydi.

Hammasi Linux yadrosining oddiy imkoniyatlari. Docker ularni qulay interfeysga o'radi.

Buni o'zingiz ham qila olasiz:

Terminal
sudo unshare --pid --net --mount --uts --ipc --fork \
     --mount-proc chroot /mnt/alpine /bin/sh

cgroups - resurs cheklovi #

Terminal
cat /sys/fs/cgroup/cgroup.controllers
Natija
cpuset cpu io memory hugetlb pids rdma misc
Terminal
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.

Terminal
cat /sys/fs/cgroup/sinov/memory.current
Natija
8421376
FaylNima uchun
memory.maxQattiq xotira chegarasi
memory.highYumshoq chegara - sekinlashtiriladi
cpu.maxCPU ulushi
pids.maxMaksimal jarayonlar soni
io.maxDisk o'tkazuvchanligi
Terminal
docker run --memory=512m --cpus=1.5 nginx

Bu buyruq aynan yuqoridagi fayllarga yozadi.

cgroups xotira chegarasi va OOM

memory.max ga yetgan jarayon OOM Killer tomonidan o'ldiriladi - lekin faqat shu cgroup ichida.

Terminal
dmesg | grep -i "memory cgroup"
Natija
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).

Terminal
docker inspect --format='{{.State.ExitCode}}' konteyner
Natija
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:

Terminal
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 #

C
#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;
}
Natija
/.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 #

Konteyner - xavfsizlik chegarasi emas

Konteynerlar bir yadroni bo'lishadi. Yadrodagi zaiflik konteynerdan chiqish (container escape) imkonini beradi.

Xavfli sozlamalar:

SozlamaNima uchun xavfli
--privilegedDeyarli barcha imkoniyatlar, /dev to'liq
-v /var/run/docker.sock:...Docker ustidan to'liq nazorat = root
--net=hostXost tarmog'iga to'liq kirish
--pid=hostXost jarayonlarini ko'rish va o'ldirish
-v /:/hostButun fayl tizimi
root sifatida ishlashChiqib ketilsa - xostda root

Xavfsizroq sozlash:

Terminal
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:

DOCKERFILE
RUN adduser -D -u 1000 ilova
USER ilova
CMD ["./server"]

WSL - o'ziga xos holat #

VersiyaQanday ishlaydi
WSL 1Linux tizim chaqiruvlarini Windows chaqiruvlariga tarjima
WSL 2Hyper-V ustida haqiqiy Linux yadrosi
Terminal
uname -r
Natija
5.15.167.4-microsoft-standard-WSL2
Terminal
cat /proc/version
Natija
Linux version 5.15.167.4-microsoft-standard-WSL2 (root@buildvm) (gcc 11.2.0)
WSL 2 nima uchun tez

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:

AmalWSL 1WSL 2
git clone (Linux FS da)SekinTez
/mnt/c dagi fayllarTezSekin
DockerIshlamaydiIshlaydi
Tizim chaqiruvlari mosligiQismanTo'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 #

TexnologiyaIzolyatsiyaYukBoshqa OTQachon ishlatiladi
KonteynerO'rta~0%Yo'qMikroxizmatlar, CI/CD
Virtual mashinaTo'liq5-15%HaKo'p ijarachili, boshqa OT
Kata / gVisorYuqori3-8%Yo'qIshonchsiz kod
chrootPast0%Yo'qOddiy ajratish (xavfsizlik emas)
UnikernelTo'liqJuda kamYo'qMaxsus yuklar
Amaliy topshiriq
  1. systemd-detect-virt bilan muhitingizni aniqlang.
  2. /proc/cpuinfo da vmx yoki svm bayrog'ini toping.
  3. /proc/self/ns/ dagi nom fazolarini sanang.
  4. unshare --pid --fork --mount-proc bilan PID fazasini sinang.
  5. unshare --uts bilan xost nomini o'zgartirib ko'ring.
  6. unshare --net bilan tarmoqsiz muhit yarating.
  7. cgroup yaratib, memory.max ni belgilang.
  8. Konteyner Exit Code 137 nimani anglatishini ayting.
  9. --privileged nima uchun xavfli ekanini tushuntiring.
  10. WSL 2 da /home va /mnt/c tezligini 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.
  • unshare bilan 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.
  • --privileged va docker.sock - root berish bilan teng.
  • Konteynerda hech qachon root ostida ishlamang.
  • WSL 2 da loyihalarni /home ichida saqlang.

Keyingi va oxirgi bo'limda hamma bilimlarni birlashtirib, mini qobiq yozamiz.

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.