Tinklaraštis

Linux iš vidaus: architektūra, kurios nemoko pradedantiesiems.

linux_inside
Linux

Linux iš vidaus: architektūra, kurios nemoko pradedantiesiems.

Dažnai sakome „Linux” turėdami omenyje Ubuntu, CentOS ar Debian. Tačiau inžineriniu požiūriu tai neteisinga. Linux yra branduolys. Kodo gabalas, valdantis aparatinę įrangą. Viskas, ką matome ekrane, viskas, su kuo sąveikaujame — tai tik programų rinkinys, veikiantis virš šio branduolio.

Šiame straipsnyje išsiaiškinsime, kaip visa tai veikia. Pradėsime nuo pat apačios.

1 žingsnis. Spaudžiame maitinimo mygtuką

Srovė pajudėjo. Procesorius pabunda — tačiau šiuo momentu jis yra kvailas silicio gabalas. Jis nežino, kas yra Linux. Jis nežino, kas yra failai. Jis net nežino, kiek turi operatyvinės atminties. Jis paprasčiausiai ieško pirmos instrukcijos griežtai užkoduotu adresu.

Pirmoji paleidžiama pagrindinės plokštės programinė įranga — BIOS arba UEFI. Jos užduotis — pažadinti aparatinę įrangą. Ji paleidžia POST (Power-On Self-Test): tikrina, ar yra atmintis, ar veikia vaizdo plokštė, ar inicializavosi procesorius. Tada apklausia įrenginius ir ieško, iš ko galima paleisti sistemą.

2 žingsnis. Įkroviklis — GRUB

Tarkime, BIOS rado standųjį diską. Bet štai problema — BIOS pats nemoka skaityti failų sistemų. Jis nežino, kas yra ext4 ar XFS. Jis negali tiesiog paimti ir atidaryti failo. Todėl jis nuskaito pirmutiniuosius 512 disko baitų (tai MBR) arba žvilgteli į specialų EFI skaidinį. O ten gyvena įkroviklis.

Linux pasaulyje tai beveik visada yra GRUB 2.

GRUB — maža, bet protinga programa. Jos vienintelė supergalia — ji moka skaityti failų sistemas. Ji žvilgteli į /boot skaidinį, kur guli du kritiškai svarbūs failai, be kurių nieko neįvyks.

vmlinuz — tai ir yra pats Linux. Branduolys, suspaustas į dvejetainį failą. Raidė z pabaigoje reiškia zip — tai yra suspaustas.
initramfs — mažas laikinas archyvas. Apie jį — šiek tiek vėliau.

GRUB paima branduolį, išskleidžia jį tiesiai į operatyvinę atmintį ir perduoda jam valdymą. Viskas. Nuo šio momento BIOS išeina parūkyti į šoną, įkroviklis miršta. Dabar sistemoje tėra vienas viršininkas — branduolys.

Maitinimo mygtukas
      |
      v
  BIOS/UEFI
  (POST, įrenginių paieška)
      |
      v
   GRUB 2
  (skaito /boot, įkelia branduolį)
      |
      v
  vmlinuz (branduolys)
      |
      v
  initramfs
  (laikinas diskas su tvarkyklėmis)
      |
      v
  switch_root
  (perjungimas į tikrąjį diską)
      |
      v
  systemd (PID 1)
      |
      v
  SSH, Nginx, Postgres...

3 žingsnis. Vištos ir kiaušinio problema

Ir čia branduolys susiduria su klasikine inžinerine problema.

Branduolys įkeltas į atmintį. Jo užduotis — prijungti pagrindinį diską, kad paleistų sistemą. Tačiau norint prijungti diską, reikia tvarkyklių — failų sistemai ir disko valdikliui. O kur guli tvarkyklės? Teisingai — tame pačiame diske, kurio dar negalime perskaityti, nes neturime tvarkyklių.

Uždaras ratas.

Būtent todėl GRUB įkėlė antrą failą — initramfs. Tai mažas laikinas archyvas, kuris išskleidžiamas operatyvinėje atmintyje kaip virtualus diskas. Viduje — mini Linux su minimaliu tvarkyklių rinkiniu. Branduolys prijungia initramfs kaip laikiną šaknį, iš jo įkelia reikiamas tikrosios aparatinės įrangos tvarkykles (RAID valdikliai, NVMe, LVM), randa tikrąjį diską — ir atlieka triuką pavadinimu switch_root, tai yra šaknies pakeitimas. Laikinas diskas išmetamas iš atminties ir pakeičiamas tikruoju.

Pastaba: kartais galite sutikti terminą pivot_root — tai senesnis mechanizmas. Šiuolaikinės sistemos su initramfs naudoja būtent switch_root.

Svarbu praktikoje: jei serveris užstringa paleidimo metu su klaida kernel panic — branduolys įstrigo būtent čia. Jis arba nerado initramfs, arba jame nebuvo reikiamos disko tvarkyklės.

4 žingsnis. Branduolys — trys pagrindinės užduotys

Įkroviklis atliko savo darbą, tvarkyklės įkeltos — patenkame į kernel space. Procesoriaus architektūros požiūriu esame apsaugos žiede Ring 0. Čia leidžiama viskas: bet kokia procesoriaus instrukcija, bet koks atminties adresas.

Linux yra monolitinis branduolys, o ne atskirų paslaugų rinkinys. Vaizdo plokštės tvarkyklė, TCP/IP tinklo steka, ext4 failų sistemos tvarkyklė — visa tai verda viename milžiniškame katile, vienoje adresų erdvėje. Tai suteikia beprotišką našumą, nes nėra papildomų sąnaudų komponentų tarpusavio komunikacijai. Tačiau yra rizika: jei suklydusi tvarkyklė sugrius — ji nusitemps visą serverį į kernel panic.

Branduolys turi tris pagrindines užduotis.

1. Atminties valdymas

Pagrindinė branduolio užduotis čia — meluoti. Jis meluoja mūsų programoms.

Kai Nginx ar Python prašo atminties, jis mano esąs vienintelis sistemoje. Jam viskas atrodo kaip graži nenutrūkstama adresų juosta. Nors iš tikrųjų fizinė atmintis yra fragmentuota ir duomenų gabalai išbarstyti chaotiškai.

Branduolys tvarko milžinišką virtualiosios atminties lentelę: „virtualus adresas X Nginx procesui iš tikrųjų yra fizinėje ląstelėje Y”. Tai suteikia du dalykus: izoliaciją (vienas procesas fiziškai negali pasiekti kito atminties) ir swap (jei atminties trūksta, branduolys tyliai iškelia dalį duomenų į diską).

Tačiau jei atmintis visiškai išseko ir swap taip pat pilnas — ateina OOM Killer. Ir jis ne veltui taip pavadintas. Tai tikras žudikas, pasamdytas branduolio. Jis pabunda, nuskaito procesų lentelę, suranda tą, kuris ryja daugiausiai atminties esant žemam prioritetui — ir pašauna jam į kaktą. Kubernetes aplinkoje tai nutinka nuolat, kai neteisingai sukonfigūruoti podų limitai.

2. Procesų planuoklis

Tarkime, mūsų serveryje yra 4 branduoliai, bet veikia 500 procesų. Kaip jie visi dirba vienu metu?

Niekada. Tai iliuzija.

Branduolys naudoja preemptyvinę multitasking’ą. Jis leidžia procesui veikti kelias milisekundes — vadinamąjį laiko kvantą — o paskui jį priverstinai sustabdo. Tai vadinama konteksto perjungimu: branduolys išsaugo dabartinės užduoties procesoriaus registro būseną, įkelia kitos užduoties būseną ir paleidžia toliau. Tai vyksta tūkstančius kartų per sekundę.

Jei stebėjimo įrankyje matote aukštą load average, bet CPU apkrova nesiekia 100% — problema greičiausiai visai nesusijusi su procesoriumi. Load average skaičiuoja ne tik aktyviai dirbančius procesus, bet ir tuos, kurie blokuoti laukdami I/O — įstrigę, kol atsakys diskas ar tinklas. Tai būsena D komandos ps išvestyje (uninterruptible sleep). Tuo metu procesorius tuščiąja eiga, o serveris „stringa”. Diagnozuoti galima per iostat arba vmstat — jei wa (I/O wait) reikšmė didelė, priežastis būtent ten.

Konteksto perjungimas — atskira tema. Jis matomas vmstat kaip didelė cs reikšmė. Jei sistema iš tikrųjų eikvoja resursus perjunginėdama tūkstančius procesų — CPU bus apkrautas, o cs reikšmė šoks į viršų.

3. Aparatinės įrangos abstrakcija

Branduolys atlieka vertėjo vaidmenį. Programos naudotojo erdvėje nežino aparatinės įrangos. Štai yra Python programa, kuri rašo žodį į failą. Tačiau Python nežino, kur rašo — į Samsung SSD, seną kietąjį diską ar tinklo katalogą. Kiekvienas įrenginys turi savo protokolą, savo įtampas, savo valdiklio komandas.

Branduolys suteikia universalią sąsają. Programa sako: „rašyk į šį failą”. O branduolio tvarkyklė verčia tai į elektrinius signalus. Dėl to mūsų kodas veikia vienodai ant bet kokios aparatinės įrangos.

5 žingsnis. Sisteminiai iškvietimai — vienintelės durys į branduolį

Tarp naudotojo režimo, kur gyvena mūsų programos, ir branduolio režimo stovi betoninė siena. Procesorius fiziškai draudžia naudotojo erdvės kodui kreiptis į aparatinę įrangą. Jei programa bandys tiesiogiai pasiekti aparatinę įrangą — procesorius ją nedelsiant sustabdys. Jokios prieigos nebus.

Tačiau šioje sienoje yra vienintelės šarvuotos durys. Jos vadinamos sisteminiu iškvietimu (syscall). Tai griežtai reglamentuota API. Branduolys sako: „nepaleisiu tavęs tiesiai prie disko, bet jei mandagiai paprašysi per specialią funkciją — padarysiu tai už tave”.

Pažiūrėkime į pavyzdį. Rašome

print("Hello World")

Python — ir prasideda visa įvykių grandinė:

  1. Python interpretatorius iškviečia standartinę C biblioteką (glibc).
  2. Biblioteka suformuoja write sisteminį iškvietimą.
  3. Ji padeda argumentus į procesoriaus registrus ir generuoja programinę pertrauką.
  4. Procesorius sustabdo programą, perjungia režimą į Ring 0 ir perduoda valdymą branduoliui.
  5. Branduolys patikrina teises ir atvaizduoja tekstą ekrane.
  6. Valdymas grąžinamas programai.

Linux turi apie 300–400 sisteminių iškvietimų. Pagrindiniai, ant kurių laikosi viskas: fork, exec, open, close, read, write, socket, connect.

Kodėl tai svarbu žinoti? Nes programos dažnai meluoja. Žurnalai gali būti tušti, klaidos — neinformatyvios. Tačiau programa negali meluoti branduoliui. Ji privalo daryti syscall’us, kad ką nors atliktų.

Ir čia į sceną žengia strace — pagrindinis derinimo įrankis. Tai pasiklausymo priemonė syscall’ams. Paleidžiate jį ant proceso ir matote viską: ką jis bandė padaryti, ką gavo, kur įstrigo. strace — tai rentgenas. Jei mokate skaityti jo išvestį, jums neegzistuoja nesuprantamų klaidų.

6 žingsnis. Pirmasis procesas — systemd

Branduolys įkeltas, inicializuota atmintis ir tvarkyklės. Tačiau serveris vis dar tuščias — nėra nei SSH, nei konsolės, nei tinklo. Kad sistema atgytų, branduolys turi paleisti pirmąjį procesą naudotojo erdvėje. Jis ieško vykdomojo failo keliuose:

/sbin/init
/etc/init
/bin/init
/bin/sh

Ir paleidžia pirmąjį rastąjį. Šis procesas gauna PID 1 — savo unikalų identifikatorių. Visi kiti sistemos procesai — PID 2, 3, 4, 10000 — bus šio proceso palikuonys. Jei PID 1 mirs — branduolys nuspręs, kad sistema sugedusi, perkris į kernel panic ir serveris sustos.

Šiuolaikiniame Linux systemd atlieka PID 1 vaidmenį.

Senais SysV init laikais procesai buvo paleidžiami eilės tvarka: pirmiausia tinklas, tada diskas, tada duomenų bazė. Lėtai. systemd veikia kaip priklausomybių valdytojas — kuria kryptingą grafą ir paleidžia viską lygiagrečiai, maksimaliai išnaudodamas procesorių paleidimo metu. SSH nuo nieko nepriklauso? Paleidžiame iš karto. Postgres priklauso nuo disko? Palauks.

Kai rašome

systemctl start nginx

— siunčiame komandą systemd per soketą. systemd patikrina teises, žiūri į priklausomybes ir vykdo fork sisteminį iškvietimą (sukuria savo kopiją) + exec (pakeičia kopiją Nginx dvejetainiu failu). Taip gimsta žiniatinklio serverio procesas — systemd vaikas.

7 žingsnis. Failų sistema — „viskas yra failas”

Procesai paleisti, tačiau jiems reikia skaityti konfigūracijas, rašyti žurnalus, išsaugoti duomenis. Kylame į kitą abstrakcijos lygį — failų sistemą.

Pagrindinė Unix filosofija: viskas yra failas. Tačiau pažiūrėkime į tai ne kaip į šūkį, o kaip į inžinerinį sprendimą.

Windows turi diskų raides C:, D:, E: — fizinis įrenginių atskyrimas. Linux požiūris kitoks. Čia egzistuoja vienas katalogų medis, kuris visada prasideda nuo šaknies — simbolio /. Procesui nesvarbu, kiek diskų turime. Jis mato tik medį.

Branduolio viduje yra sluoksnis VFS (Virtual File System) — universali sąsaja. Imame SSD ir prijungiame prie /boot. Imame tinklo diską ir prijungiame prie /mnt/share. Imame operatyvinę atmintį ir prijungiame prie /tmp. Programai viskas atrodo vienodai — ji tiesiog rašo pagal kelius, o VFS realiu laiku nusprendžia, kur siųsti baitus: SATA kabeliu ar tinklu į kitą serverį.

/                   ← šaknis
├── boot/           ← SSD su branduoliu
├── etc/            ← konfigūracijos
├── home/           ← namų katalogai
│   └── user/
├── proc/           ← virtualus (branduolio duomenys)
├── sys/            ← virtualus (branduolio nustatymai)
├── tmp/            ← operatyvinė atmintis
├── usr/
│   ├── bin/        ← programų dvejetainiai failai
│   └── lib/        ← bendros bibliotekos
├── var/
│   └── log/        ← žurnalai
└── mnt/
    └── share/      ← tinklo diskas

Kas yra failas fiziškai?

Mums failas yra photo.jpg. Tačiau branduoliui vardas — tuščias garsas. Tiesiog baitų rinkinys žmogaus patogumui. Branduoliui failas yra inode (indekso mazgas). Kiekvienas sistemos failas — tai tiesiog skaičius.

inode saugo visą metainformaciją, išskyrus vardą: savininkas, prieigos teisės, failo dydis, laiko žymos ir svarbiausia — rodyklės į disko sektorius, kur fiziškai guli duomenys.

O kas tada yra failo vardas? Vardas gyvena kataloge. Techniškai katalogas yra taip pat failas, tačiau jo viduje yra paprasta dviejų stulpelių lentelė: failo vardas → inode numeris. Kai atidarome failą, branduolys skaito katalogą, randa inode numerį, patikrina teises — ir tik po to pradeda skaityti duomenis.

Katalogas /home/user/
┌─────────────┬──────────┐
│   Vardas    │  inode # │
├─────────────┼──────────┤
│ photo.jpg   │   42017  │
│ notes.txt   │   42031  │
│ script.sh   │   42089  │
└─────────────┴──────────┘
                  │
                  ▼
           inode #42017
      ┌─────────────────────┐
      │ savininkas: uid 1000│
      │ teisės:    644      │
      │ dydis:     3.2 MB   │
      │ keistas: 2024-03-01 │
      │ duomenys → [sektoriai]│
      └─────────────────────┘

/proc ir /sys — branduolys kaip failai

Linux kūrėjai pagalvojo: kodėl ribotis diskais? Pavaizduokime patį branduolį kaip failus.

Atsirado /proc ir /sys. Failų dydis ten — 0 baitų. Diske nieko nėra. Tai iliuzija — tačiau tiesioginė sąsaja su branduolio duomenų struktūromis operatyvinėje atmintyje. Skaitote /proc/cpuinfo — branduolys apklausia procesorių ir generuoja atsakymo tekstą realiu laiku.

Ir tai veikia abiem kryptimis. Norite įjungti IP maršrutizavimą ir paversti serverį maršrutizatoriumi? Nereikia ieškoti jokio žymimojo langelio jokiame valdymo skydelyje. Tiesiog įrašykite 1 į reikiamą failą:

echo 1 > /proc/sys/net/ipv4/ip_forward

Branduolys perima šį įrašą ir pakeičia kintamąjį savo kode. Štai ką reiškia „viskas yra failas” — universali sistemos valdymo API.

8 žingsnis. Procesai, stdin/stdout ir vamzdžiai

Procesas — tai izoliuotas konteineris atmintyje su savo PID, kintamaisiais ir ištekliais. Tačiau procesas vakuume yra nenaudingas. Jam reikia gauti duomenis ir grąžinti rezultatą.

Kai branduolys paleidžia bet kokį procesą, jis automatiškai atidaro jam tris ryšio kanalus — tris failus:

Deskriptorius  Pavadinimas                    Numatytasis priskyrimas
0              stdin  (standartinė įvestis)   klaviatūra
1              stdout (standartinė išvestis)  ekranas/terminalas
2              stderr (standartinės klaidos)  ekranas/terminalas

Kodėl atskirti stdout ir stderr? Kad atskirtume naudingus duomenis nuo klaidų. Galime pasakyti apvalkalui: naudingus duomenis (1 deskriptorius) rašyk į failą, o klaidas (2 deskriptorius) meskite į /dev/null — juodąją skylę:

command > data.txt 2>/dev/null

Vamzdis | — pagrindinė Linux magija

Simbolis | — tai tarpprocesinės komunikacijos mechanizmas. Kai rašome:

cat access.log | grep "ERROR"

Branduolys paleidžia cat ir grep vienu metu. cat pakeičia stdout: vietoje ekrano — specialus atminties buferis (vamzdis). grep pakeičia stdin: vietoje klaviatūros — tas pats buferis.

Genialumas tame: cat mano, kad rašo į ekraną. grep mano, kad skaito iš klaviatūros. Jie nežino vienas kito egzistavimo. Branduolys tiesiog perkišo laidus.

  cat access.log          grep "ERROR"
  ┌───────────┐           ┌───────────┐
  │           │  stdout   │           │
  │    cat    ├──────────►│   grep    │──► ekranas
  │           │  vamzdis  │           │
  └───────────┘  (buferis) └───────────┘
   mano, kad               mano, kad
   rašo į ekraną           skaito iš klaviatūros

Tai leidžia kurti bet kokio ilgio konvejerius. Penkios kvailos mažos programos, sujungtos vamzdžiais, virsta galingų analizės įrankiu. Tai ir yra Unix filosofija: rašyti programas, kurios daro vieną dalyką, bet gerai — ir išmokyti jas dirbti kartu per teksto srautus.

9 žingsnis. Signalai — kaip procesai gauna pranešimus

Procesai negyvena visoje izoliacijoje. Branduolys ir kiti procesai gali jiems siųsti žinutes. Tai vadinami signalais — trumpi asinchroniniai pranešimai: „ei, laikas mirti” arba „perskaityk konfigūraciją iš naujo”.

Kai paspaudžiate Ctrl+C terminale — jūs ne „uždarote” programą. Siunčiate jai SIGINT signalą. Programa gali jį sugauti ir teisingai užsibaigti. Arba ignoruoti.

Pagrindiniai signalai, kuriuos verta žinoti:

SIGTERM — mandagus prašymas užsibaigti. Programa gali jį sugauti, išsaugoti būseną ir išeiti.
SIGKILL — įsakymas, kurio negalima ignoruoti. Branduolys nedelsiant nužudo procesą, nesuteikdamas galimybės nieko išsaugoti. Būtent šį signalą naudoja OOM Killer.
SIGHUP — istoriškai reiškė „prarasta terminalo jungtis”. Šiandien demonai tai naudoja kaip signalą iš naujo perskaityti konfigūraciją be paleidimo iš naujo.
SIGINT — klaviatūros pertraukimas (Ctrl+C).
SIGSTOP — užšaldyti procesą. Taip pat negalima ignoruoti.

Signalą rankiniu būdu galima siųsti komanda

kill

— nepaisant pavadinimo, ji gali siųsti bet kokį signalą:

kill -SIGTERM 1234   # mandagiai paprašyti procesą 1234 užsibaigti
kill -SIGKILL 1234   # nužudyti be jokių diskusijų
kill -SIGHUP 1234    # paprašyti iš naujo perskaityti konfigūraciją

Svarbu suprasti: SIGKILL — tai kraštutinė priemonė. Procesas nespėja nieko išsaugoti, uždaryti jungčių, įrašyti duomenų. Todėl visada pirmiausia SIGTERM, ir tik jei nepadėjo — SIGKILL.

10 žingsnis. Prieigos teisės

Linux saugumas — tai ne antivirusinė magija. Tai bitų tikrinimas inode.

Branduolys nežino žodžių. Jis žino tik skaičius. Jūsų naudotojo vardas verčiamas į UID (User ID), grupė — į GID (Group ID). Atitikmenų duomenų bazė saugoma paprastuose tekstiniuose failuose /etc/passwd ir /etc/group.

Kiekviename inode užrašyta: „šio failo savininkas yra UID X, grupė Y”. Ir ten pat trys teisių rinkiniai: user (savininkas), group (grupė), others (visi kiti). Kiekvienam rinkiniui — trys veiksmai:

read (4) — skaityti
write (2) — keisti
execute (1) — paleisti kaip procesą

Tai ne atsitiktiniai skaičiai — tai bitinė kaukė: read = 100, write = 010, execute = 001.

Kai rašome

chmod 755 file

:
7 = 4+2+1 — savininkas gali viską
5 = 4+0+1 — grupė gali skaityti ir vykdyti, bet ne rašyti
5 = 4+0+1 — visi kiti taip pat

Svarbi detalė: žymė x ant katalogo nereiškia vykdymo — ji reiškia teisę į jį įeiti. Jei turite teisę skaityti katalogą, bet neturite teisės įeiti — matysite failų sąrašą, bet negalėsite skaityti jų turinio.

Root ir sudo

Linux turi vieną naudotoją, kuriam branduolys išjungia teisių tikrinimą — tai root (UID 0). Kai procesas su UID 0 atidaro failą, branduolys netikrina inode — tiesiog atidaro. Root gali skaityti /etc/shadow su slaptažodžių maišomis, nužudyti bet kokį procesą, formatuoti diską gyvoje sistemoje.

Todėl dirbti kaip root — bloga praktika ir rizika. Administravimui naudojama sudo — programa su SUID bitu, kuri laikinai pakelia UID iki 0, įvykdo vieną komandą ir, kas kritiškai svarbu, viską įrašo į audito žurnalą. Saugumas neprarandant kontrolės.

11 žingsnis. Paketų tvarkyklė ir bibliotekos

Windows naudotojai įpratę: randi svetainę, parsisiunti .exe, spaudžiai Next, ir kiekviena programa atsitempią savo bibliotekas. Linux naudoja kitokį požiūrį — centralizuotas saugyklas. Tai patikrinti dvejetainių paketų sandėliai, palaikomi jūsų distribucijos kūrėjų.

Kai rašome

apt install nginx

— tai ne paprastas atsisiuntimas. Paketų tvarkyklė išpakuoja paketą pagal FHS standartą: dvejetainiai failai dedami į /usr/bin arba /usr/sbin, konfigūracijos į /etc, paslaugų failas į /lib/systemd/system. Ir svarbiausia — tikrina priklausomybes.

Kodėl tai taip svarbu? Nes Linux programos beveik visada yra dinamiškai susietos. Nginx dvejetainis failas sveria centus — jame nėra šifravimo ar glaudinimo kodo. Paleidimo metu jis paprasčiausiai sako branduoliui: „man reikia tokios bibliotekos”. Tos bibliotekos yra bendros — sistemoje yra vienas egzempliorius. Branduolys įkelia biblioteką į atmintį vieną kartą, o Nginx, SSH klientas ir Python scenarijus tiesiog gauna nuorodą į tą patį atminties regioną. Tai kolosalio dydžio sutaupo operatyvinę atmintį.

Tačiau bibliotekų versijos turi sutapti. Kai šis kortų namelis griūva — tai vadinama Dependency Hell (priklausomybių pragaras). Todėl reikia paketų tvarkyklės, o ne rankinio failų kopijavimo.

12 žingsnis. Namespaces ir cgroups — iš ko sudarytas Docker

Pabaigoje pasakysime, kad „jokio Docker nėra”. Tačiau išsiaiškinkime, ką tai reiškia.

Linux branduolys suteikia du mechanizmus, kurie kartu ir suteikia mums konteinerius.

Namespaces — aplinkos izoliacija

Namespace — tai mechanizmas, kuris procesui sako: „tu matai ne visą pasaulį — tik savo jo dalį”.

Yra keletas namespace tipų:

PID namespace — procesas mano esąs PID 1 sistemoje. Nemato procesų išorėje.
NET namespace — procesas turi savo tinklo sąsają, savo IP, savo ugniasienės taisykles.
MNT namespace — procesas mato savo failų sistemos medį.
UTS namespace — procesas mano turintis savo kompiuterio vardą.
USER namespace — procesas mano esąs root — tačiau tik savo namespace ribose.

Kai Docker kuria konteinerį — jis tiesiog sukuria namespace rinkinį ir paleidžia procesą jų viduje. Procesas mano esąs vienas serveryje. Iš tikrųjų šalia sukasi šimtai tokių pat „vienišų” procesų.

         Šeimininkas (Linux kernel)
┌─────────────────────────────────────┐
│  ┌─────────────┐  ┌─────────────┐   │
│  │ Konteineris1│  │ Konteineris2│   │
│  │ (namespaces)│  │ (namespaces)│   │
│  │  PID: 1     │  │  PID: 1     │   │
│  │  IP: 10.0.1 │  │  IP: 10.0.2 │   │
│  │  cgroups:   │  │  cgroups:   │   │
│  │  512MB RAM  │  │  1GB RAM    │   │
│  │  1 CPU      │  │  2 CPU      │   │
│  └─────────────┘  └─────────────┘   │
└─────────────────────────────────────┘

Cgroups — išteklių apribojimai

Namespaces suteikia izoliaciją. Tačiau ką daryti, jei procesas savo namespace viduje nusprendžia suryti visą serverio atmintį?

Tam egzistuoja cgroups (control groups). Tai branduolio mechanizmas, leidžiantis apriboti, kiek išteklių gali naudoti procesas ar procesų grupė:

memory.limit_in_bytes  — maksimali operatyvinė atmintis
cpu.shares             — procesoriaus laiko dalis
blkio.weight           — disko įvesties/išvesties prioritetas

Būtent cgroups yra tai, ką konfigūruojate Kubernetes aplinkoje, kai rašote resources.limits pod manifeste. Kubernetes sako branduoliui: „šis procesas gali naudoti ne daugiau kaip 512MB atminties”. Jei viršijo — ateina OOM Killer.

Taigi Docker — tai tiesiog patogi apvalkalas virš namespaces ir cgroups. Jokios magijos. Kiekvienas Linux procesas gali naudoti šiuos mechanizmus — Docker tiesiog automatizuoja ir supakuoja tai į patogią sąsają.

Išvada: žiūrėkite į Linux kaip inžinierius

Nukeliavome nuo šalto silicio iki veikiančio žiniatinklio serverio. Dabar galvoje turėtų būti ne komandų kratinys, o aiški vertikali struktūra:

  1. Aparatinė įranga — kvailas silicis, kurį pažadino BIOS
  2. GRUB — įkroviklis rado ir išskleidė branduolį
  3. Branduolys — diktatorius, kuris meluoja programoms apie atmintį, pjausto procesoriaus laiką, valdo tvarkykles
  4. Sisteminiai iškvietimai — vienintelės durys tarp programų ir branduolio
  5. systemd (PID 1) — stato visą naudotojo erdvės aplinką
  6. Failų sistema — vienas medis, kur kiekvienas failas yra skaičius su prieigos teisėmis
  7. Procesai ir vamzdžiai — izoliuoti konteineriai, sujungti duomenų srautais
  8. Signalai — kaip branduolys ir procesai bendrauja tarpusavyje
  9. Prieigos teisės — bitai inode, kuriuos branduolys tikrina kiekvieno syscall metu
  10. Paketai ir bibliotekos — valdoma priklausomybių ekosistema
  11. Namespaces ir cgroups — izoliacija ir išteklių apribojimai, iš kurių sudarytas bet koks konteineris

Laikykite šį vaizdą galvoje — ir magija išnyksta. Lieka švari, sausa logika.

Matote klaidą Permission Denied? Tai ne „Linux pyksta”. Tai branduolys, iškviesdamas open(), palygino jūsų UID su inode bitais ir grąžino klaidos kodą.

Serveris lėtėja, aukštas load average? Tai ne „procesorius pavargęs”. Planuotojo eilė tapo per ilga ir sistema eikvoja išteklius konteksto perjungimui.

Klausia apie Docker? Jo pagrindas — namespaces ir cgroups, kuriuos branduolys suteikia bet kokiam procesui. Docker — tai patogi apvalkalas virš šių mechanizmų. Suprantate branduolį — suprantate konteinerius.

Pradedantysis mokosi komandas mintinai. Inžinierius supranta, kaip teka baitai. Komandą galima rasti per 5 sekundes. Architektūros supratimą galima tik užsidirbti.

Leave your thought here

El. pašto adresas nebus skelbiamas. Būtini laukeliai pažymėti *