
Розробник з codeandcake.dev витратив час на створення рантайму контейнерів заради задоволення та описав, чого він навчився про Docker. Публікація коротка і краща за більшість пояснень про контейнери, які я читав, бо починає з того, що ніколи не доходить при читанні документації: контейнер — це не маленька віртуальна машина. Це процес на вашому ядрі, до якого прикріплено обмеження.
Ваш контейнер зараз у /proc
Це перше, що публікація пояснює правильно. Ядро все ще виконує процес контейнера. Якщо ви знаєте, де шукати, процес перелічений у /proc, як і будь-що інше на машині. Тут немає гіпервізора, немає другого ядра, немає емуляції апаратного забезпечення. Ізоляція реальна, але це ізоляція видимості та ресурсів, а не стіна.
І в Linux немає функції під назвою «контейнер». Як зазначає автор, контейнер — це насправді п'ять функцій ядра в плащі. Namespaces визначають, що процес може бачити. Cgroups визначають, скільки CPU та пам'яті він може використовувати. Мережа — це окрема купа механізмів, які більшість із нас, включно з автором, не до кінця розуміє. Образи виявляються папками, які Docker зберігає десь, а потім передає chroot або pivot_root, що робить цю папку новим кореневим каталогом для процесу. Коли ви вводите docker run з образом Ubuntu та оболонкою bash, це і є хитрість. Папка, перемикання кореня та набір обмежень, застосованих до того, як ваша оболонка взагалі стартує.
Я запускаю Docker роками і досі вважаю, що це знімає напругу корисним чином. Вся магія — у трубах, і ці труби можна інспектувати.
Go був дивним вибором для runc
Найцікавіша частина огляду — про вибір мови. Рантайм контейнерів Docker, runc, написаний на Go. Рантайм Go нативно багатопоточний, що є справді хорошою функцією для серверів, але незручною для рантайму контейнерів. Багато системних викликів, які рантайму доводиться робити, потребують однопоточного середовища. Тому runc постачається з C-модулем, який запускається до ініціалізації рантайму Go, виключно для виконання цих викликів.
Потім є fork. Go не має його так, як хоче рантайм, бо fork форкає лише головний потік. Рантайм контейнерів справді хоче зробити fork, застосувати купу обмежень до дочірнього процесу, а потім виконати exec процесу, який ви запросили. runc обходить це, виконуючи exec /proc/self/exe зі спеціальним аргументом командного рядка, який позначає його як дочірній процес, що наближається до fork достатньо, щоб працювати.
Якщо ви колись дивувалися, чому інструменти контейнерів іноді здаються такими, ніби вони воюють із платформою, ось чому. Вони воюють із рантаймом мови, а не з ядром.
Root — це частина, яку ніхто досі не вирішив
API ядра Linux, які потрібні рантайму, або вимагають root, або раніше вимагали, тому демон Docker історично запускається від root. Це величезна кількість привілеїв, зосереджених в одному місці, і публікація відверто каже, що безпека контейнерів має бути дуже складною роботою. Вона така.
Rootless-рантайми покращили ситуацію. Виняток — мережа. Щоб дати вам ті самі функції без root, rootless-рантайму, здається, доводиться реалізовувати мережевий стек у userspace. Це серйозна робота, щоб повернутися до початкової точки, і це багато пояснює про те, чому rootless-налаштування поводяться інакше, коли щось піде не так із мережею.
Жодне з цього не є аргументом проти контейнерів. Це аргумент на користь того, щоб знати, яка з п'яти функцій відповідає за гарантію, яку ви вважаєте, що маєте. На моїх машинах чесна відповідь, до читання цього, була б «Docker це робить». Це не відповідь. Це плечі, підняті з додатковими кроками.
Публікація закінчується там, де закінчується багато хорошої технічної писемності: автор досі на ранніх етапах власного рантайму та планує продовжувати. Читання змусило мене захотіти пошкребти /proc у запущеному контейнері та побачити процес, що сидить там. Він буде там. Він завжди там.