Модуль 4.4: SecurityContext
Складність:
[СЕРЕДНЯ]— важлива для безпеки, багато налаштувань.Час на проходження: 40-50 хвилин.
Передумови: базові поняття користувачів і груп у Linux, Модуль 1.1 (Поди).
Чого ви навчитеся
Розділ «Чого ви навчитеся»Після завершення цього модуля ви зможете:
- Впроваджувати SecurityContext на рівні Пода й на рівні контейнера для
runAsUser,runAsGroup,fsGroupтаrunAsNonRoot. - Діагностувати невдалі запуски Подів та помилки прав доступу, спричинені
runAsNonRoot,readOnlyRootFilesystemі власністю на томи. - Оцінювати налаштування Linux-можливостей (capabilities) та підвищення привілеїв, щоб обирати доступ контейнера за принципом найменших привілеїв.
- Проєктувати придатний для CKAD маніфест безпечного Пода, який зберігає записувані шляхи через томи
emptyDirі відповідає очікуванням Pod Security у Kubernetes 1.35+.
Чому цей модуль важливий
Розділ «Чому цей модуль важливий»Гіпотетичний сценарій: команда відвантажує невеликий HTTP-сервіс у спільний кластер Kubernetes, і образ ідеально працює на ноутбуці, тому що запускається від root і може писати будь-куди у файлову систему контейнера. У кластері той самий деплоймент потрапляє до простору імен з обмежувальними налаштуваннями Pod Security, контейнер відмовляється стартувати, і єдиною видимою підказкою є коротка подія про запуск від root. Програма не зламана, але маніфест не зміг описати той контракт безпеки, якого очікує кластер.
SecurityContext — це та частина специфікації Пода, де ви перетворюєте припущення безпеки Linux на конфігурацію Kubernetes. Він відповідає на практичні запитання, які виникають під час розгортань і усунення несправностей на CKAD: під яким UID працює процес, яка група володіє файлами змонтованого тома, чи є коренева файлова система записуваною, чи може процес отримати більше привілеїв і які вузько окреслені Linux-можливості лишаються доступними. Ці налаштування не є декоративними важелями посилення безпеки. Вони безпосередньо впливають на те, чи запуститься контейнер, чи зможе він відкрити файли і чи відповідає він політиці допуску кластера.
Екзамен CKAD зазвичай тримає завдання з SecurityContext невеликими, але перевіряє ту саму ментальну модель, яка потрібна продакшн-кластерам. Вас можуть попросити зробити так, щоб Под працював від не-root UID, виправити проблему з правами на том, додати записуваний emptyDir для програми з кореневою файловою системою лише для читання, або переглянути фактичні налаштування запущеного Пода. Кожне завдання швидке лише тоді, коли ви вже знаєте, які налаштування належать рівню Пода, які — рівню контейнера, і як Kubernetes розв’язує конфлікти.
Сприймайте SecurityContext як контроль доступу до будівлі. Політика будівлі може визначати типовий бейдж, який отримує кожен орендар, але якась кімната все одно може потребувати суворішого обмеження чи іншого ключа. SecurityContext на рівні Пода надає типові значення для контейнерів цього Пода, тоді як SecurityContext на рівні контейнера може перевизначити окремі налаштування процесу й привілеїв для одного контейнера. Змонтовані томи — це спільні кімнати, тож власність на групу та записувані каталоги слід планувати з огляду на кожен контейнер.
Цей модуль навчає SecurityContext як інструменту налагодження не менше, ніж інструменту безпеки. Ви почнете з ієрархії, а потім перейдете до ідентичності, власності на томи, записуваних шляхів, Linux-можливостей і команд перевірки. Зрештою маніфест з runAsNonRoot, fsGroup, readOnlyRootFilesystem і відкинутими можливостями має читатися як чіткий операційний контракт, а не купа фрагментів YAML.
Рівні SecurityContext
Розділ «Рівні SecurityContext»У Kubernetes є два пов’язані розташування SecurityContext, і плутати їх — найшвидший спосіб написати маніфест, який виглядає безпечним, але поводиться інакше, ніж очікувалося. spec.securityContext на рівні Пода задає типові значення й поведінку для всього Пода, особливо типову ідентичність та обробку груп томів. containers[].securityContext на рівні контейнера керує налаштуваннями, що належать процесу одного контейнера, такими як підвищення привілеїв, можливості, коренева файлова система лише для читання та специфічне для контейнера перевизначення runAsUser.
Цей поділ існує, тому що Под може містити більше одного контейнера. Головний контейнер програми може працювати від UID 1000, тоді як образ-сайдкар може потребувати UID 2000, бо його шляхи у файловій системі будувалися з іншою власністю. Под усе одно може визначити fsGroup для спільних томів, бо том має бути придатним до використання обома контейнерами. Kubernetes дає вам модель типових значень замість того, щоб змушувати кожен контейнер повторювати ті самі поля ідентичності.
Правило пріоритету просте: коли те саме підтримуване поле існує на обох рівнях, для цього контейнера перемагає значення рівня контейнера. Це не означає, що значення рівня Пода зникає. Інші контейнери все одно успадковують його, а налаштування, що існують лише на рівні Пода, як-от fsGroup, лишаються в межах Пода. Добрий маніфест використовує налаштування рівня Пода для спільних типових значень, а налаштування рівня контейнера — коли якийсь образ має законну причину відрізнятися.
apiVersion: v1kind: Podmetadata: name: security-demospec: securityContext: # Pod-level (applies to all containers) runAsUser: 1000 runAsGroup: 3000 fsGroup: 2000 containers: - name: app image: nginx securityContext: # Container-level (overrides pod-level) runAsUser: 2000 allowPrivilegeEscalation: falseУ цьому прикладі контейнер app працює від UID 2000, бо runAsUser на рівні контейнера перевизначає типове значення рівня Пода. Поведінка власності на групу все одно походить з контексту рівня Пода: runAsGroup: 3000 надає первинну групу, коли вона не перевизначена, а fsGroup: 2000 використовується для змонтованих томів, що підтримують керування власністю. Якщо ви додасте ще один контейнер без runAsUser на рівні контейнера, цей другий контейнер успадкує UID 1000.
Зробіть паузу і спрогнозуйте: якщо Под задає runAsNonRoot: true на рівні Пода, а один контейнер задає runAsUser: 0, що, на вашу думку, зробить Kubernetes, коли спробує запустити цей контейнер? Важлива деталь полягає в тому, що runAsNonRoot — це не запит на зміну UID. Це запобіжник, який каже, що контейнер не повинен працювати від UID 0, тож явне перевизначення на root конфліктує із запобіжником, і запуск має завершитися невдачею.
Ієрархію легше запам’ятати, якщо відокремити типові значення від винятків. Розмістіть типову ідентичність на рівні Пода, коли кожен контейнер може її розділяти, розмістіть спільну власність на томи на рівні Пода, бо томи є ресурсами Пода, а налаштування, чутливі до привілеїв, розмістіть на рівні контейнера, бо привілеї прив’язані до окремих процесів. Коли сайдкар потребує іншого UID, зробіть перевизначення явним і тримайте причину видимою в маніфесті.
┌─────────────────────────────────────────────────────────────┐│ SecurityContext Hierarchy │├─────────────────────────────────────────────────────────────┤│ ││ Pod Security Context ││ ┌─────────────────────────────────────────────────┐ ││ │ runAsUser: 1000 │ ││ │ runAsGroup: 3000 │ ││ │ fsGroup: 2000 (for volumes) │ ││ │ │ ││ │ Container A Container B │ ││ │ ┌────────────────┐ ┌────────────────┐ │ ││ │ │ (inherits pod) │ │ runAsUser: 2000│ │ ││ │ │ runAsUser:1000 │ │ (overrides pod)│ │ ││ │ │ │ │ │ │ ││ │ │ capabilities: │ │ readOnly: true │ │ ││ │ │ drop: [ALL] │ │ │ │ ││ │ └────────────────┘ └────────────────┘ │ ││ └─────────────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────┘Діаграма показує операційне правило, яке ви використовуватимете під час усунення несправностей. Спершу перегляньте типові значення рівня Пода, потім перегляньте кожен контейнер на наявність перевизначень, і нарешті пам’ятайте, що деякі поля ніколи не стають рішеннями рівня контейнера. Цей порядок запобігає поширеній помилці: багаторазовій зміні значення рівня Пода, тоді як фактична поведінка походить від перевизначення в контейнері далі вниз у специфікації.
Ідентичність, групи та власність на томи
Розділ «Ідентичність, групи та власність на томи»Процеси Linux працюють з числовим ідентифікатором користувача та групи, і SecurityContext у Kubernetes дозволяє задати ці числа без перебудови образу. Образ усе ще може оголошувати типового користувача, але runAsUser у специфікації Пода чи контейнера є чіткішим з погляду кластера. На екзамені використання числового UID зазвичай безпечніше, ніж покладання на ім’я користувача, бо імена потребують записів у /etc/passwd усередині образу, тоді як числові ідентифікатори ядро розуміє напряму.
securityContext: runAsUser: 1000 # UID to run as runAsGroup: 3000 # GID for the process fsGroup: 2000 # GID for mounted volumesrunAsUser змінює UID головного процесу в контейнері, а runAsGroup змінює його первинну групу. fsGroup відрізняється: це налаштування рівня Пода, яке надає контейнерам додаткову групу для підтримуваних монтувань томів і може коригувати власність на групу для вмісту тома. Ця відмінність важлива, бо файли програми, запечені в образ, є частиною кореневої файлової системи контейнера, тоді як змонтовані томи приєднує Kubernetes під час виконання.
Якщо контейнер може запуститися від UID 1000, але не може писати до /data, справжньою проблемою може бути не UID. Каталог може бути змонтований з тома, власність на групу якого не збігається з групами процесу. У такому разі fsGroup часто виправляє доступ до тома, зберігаючи при цьому не-root процес. Саме тому налагодження прав на томи починається з id та ls -la, а не з випадкових змін кожного поля SecurityContext.
securityContext: runAsNonRoot: true # Fail if image tries to run as rootrunAsNonRoot: true часто розуміють хибно, бо назва звучить як інструкція. Краще читати її як твердження: «цей контейнер не повинен працювати від root». Якщо Kubernetes може визначити, що образ запустився б від UID 0, або якщо ви явно задаєте runAsUser: 0, контейнер відхиляється. Якщо образ використовує іменованого користувача, а Kubernetes не може перевірити числовий UID, поведінка може залежати від деталей середовища виконання, тож явний числовий runAsUser лишається чіткішим вибором.
Перш ніж це запускати, який вивід ви очікуєте від id, якщо Под задає runAsUser: 1000, runAsGroup: 3000 та fsGroup: 2000? Правильний прогноз має згадати UID 1000, первинний GID 3000 і додаткову групу, що включає 2000. Цей прогноз дає вам конкретну ціль, коли ви згодом оглядаєте Под, і не дозволяє сприймати кожну помилку прав доступу як загадку.
apiVersion: v1kind: Podmetadata: name: volume-permsspec: securityContext: runAsUser: 1000 fsGroup: 2000 # Volume files owned by this group containers: - name: app image: busybox command: ['sh', '-c', 'ls -la /data && sleep 3600'] volumeMounts: - name: data mountPath: /data volumes: - name: data emptyDir: {}Цей Под volume-perms навмисно малий, бо ізолює поведінку. Процес працює від UID 1000, змонтований emptyDir з’являється на /data, а fsGroup: 2000 надає контейнеру груповий зв’язок з цим томом. У реальних системах зберігання зміни власності можуть мати поведінку, специфічну для продуктивності та драйвера, але рішення на рівні CKAD лишається тим самим: використовуйте fsGroup для доступу до тома Пода, а не для файлів, які вже є всередині образу.
Деякі образи дають збій після зміни UID, бо їхні записувані каталоги належали root під час збирання образу. Цей збій — не непослідовність Kubernetes; це права Linux роблять саме те, що мають робити. Якщо образ очікує писати під /var/cache/app, вам або потрібно, щоб образ підтримував не-root UID, або потрібно змонтувати записуваний том на тому шляху, який має лишатися змінним.
Найнадійніша послідовність усунення несправностей є вузькою і повторюваною. Спершу перегляньте події, щоб відрізнити збої допуску від збоїв середовища виконання, далі перегляньте ієрархію SecurityContext, а потім запустіть id та переліки каталогів, якщо контейнер стартує. Якщо контейнер не стартує взагалі, порівняйте типового користувача образу з вашими налаштуваннями runAsNonRoot і runAsUser. Якщо він стартує, але не може писати, перегляньте цільовий шлях і вирішіть, що буде правильним виправленням: fsGroup, emptyDir чи перебудова образу.
Файлові системи, записувані шляхи та коренева система лише для читання
Розділ «Файлові системи, записувані шляхи та коренева система лише для читання»readOnlyRootFilesystem: true робить кореневу файлову систему контейнера незмінною з погляду процесу. Це цінно, бо зловмисник або процес із дефектом не може переписати двійкові файли програми, файли конфігурації, стан менеджера пакетів чи скрипти запуску в шарі образу. Компроміс є негайним: багато звичайних програм пишуть тимчасові файли, сокети, PID-файли, кеші чи логи під шляхами, які лежать у кореневій файловій системі, доки ви не надасте їм явних записуваних монтувань.
securityContext: readOnlyRootFilesystem: true # Container can't write to filesystemКоренева система лише для читання не означає, що контейнер не може писати ніде. Це означає, що записи мають іти до змонтованих томів чи інших записуваних файлових систем, які ви навмисно надаєте. Для короткочасного робочого простору emptyDir є звичайною відповіддю на CKAD, бо його легко оголосити, він прив’язаний до життєвого циклу Пода і не потребує окремого об’єкта зберігання. Для довговічних даних програми ви б обрали натомість персистентний том, але це рішення проєктування зберігання, а не швидкий хід через SecurityContext.
Наведений нижче безпечний приклад у стилі nginx зберігає форму оригінального модуля, бо демонструє точне поєднання, яке учні мають розпізнавати. Контейнер має кореневу файлову систему лише для читання, відкидає можливості й вимикає підвищення привілеїв, але все одно монтує записувані томи emptyDir на /tmp, /var/cache/nginx та /var/run. Контроль безпеки лишається непорушним, бо записуваними є лише відомі шляхи виконання.
apiVersion: v1kind: Podmetadata: name: secure-podspec: securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 containers: - name: app image: nginx securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL volumeMounts: - name: tmp mountPath: /tmp - name: cache mountPath: /var/cache/nginx - name: run mountPath: /var/run volumes: - name: tmp emptyDir: {} - name: cache emptyDir: {} - name: run emptyDir: {}Офіційний образ nginx за замовчуванням слухає порт 80. Порти нижче 1024 традиційно потребували можливості NET_BIND_SERVICE, проте цей приклад відкидає всі можливості й усе одно стартує на багатьох тренувальних кластерах, бо сучасні середовища виконання контейнерів можуть задавати net.ipv4.ip_unprivileged_port_start=0, що дозволяє непривілейованим процесам прив’язуватися до низьких портів. Завдання CKAD, які явно вимагають прив’язки до порту нижче 1024, усе одно очікують, що ви додасте NET_BIND_SERVICE, а не покладатиметеся на цю поведінку sysctl середовища виконання.
Зупиніться й подумайте: якщо програма тепер зазнає невдачі з permission denied під час запису /var/log/nginx/access.log, чи варто вимкнути readOnlyRootFilesystem, змінити UID, чи додати записуване монтування для шляху логів? Найкраща відповідь залежить від того, чи справді програмі потрібно туди писати. Якщо запис є очікуваною поведінкою виконання, додайте конкретне монтування тома або переналаштуйте програму на логування до stdout; не прибирайте ширший захист кореневої системи лише для читання заради того, щоб приховати один відсутній записуваний шлях.
Операційна звичка — робити записувані шляхи буденними й явними. Рецензент має змогти переглянути маніфест і побачити, що /tmp записуваний, бо це робочий простір, /var/run записуваний, бо там може створюватися сокет чи PID-файл, а дані програми обробляються правильним типом зберігання. Коли ці шляхи не явні, програма може пройти тести розробки й дати збій лише після того, як політика кластера примусово застосує кореневу систему лише для читання.
Тут також є урок про пакування. Добре зібраний образ контейнера може працювати від не-root UID, писати тимчасові дані лише до задокументованих шляхів і надсилати логи до stdout чи stderr. SecurityContext може примушувати ці очікування, але не може чарівним чином полагодити образ, який припускає root-доступ до кожного каталогу. Для завдань CKAD ви зазвичай виправляєте маніфест; для продакшн-роботи ви часто виправляєте і образ, і маніфест, щоб контракт був чітким на кожному рівні.
Можливості та межі привілеїв
Розділ «Можливості та межі привілеїв»Linux-можливості (capabilities) розщеплюють деякі повноваження root на іменовані привілеї. Замість того, щоб робити контейнер повністю привілейованим, ви можете надати вузьку можливість, як-от NET_BIND_SERVICE, коли процес має прив’язатися до низького порту, або NET_ADMIN, коли він має налаштовувати мережеві параметри. Безпечніше типове рішення — відкинути всі можливості й додати назад лише ті, які робоче навантаження доказово потребує.
securityContext: capabilities: add: - NET_ADMIN # Network configuration - SYS_TIME # System clock drop: - ALL # Remove all capabilities firstМожливості потужні, бо лежать нижче Kubernetes у ядрі Linux. Помилка з можливістю може мати значення навіть тоді, коли процес не працює від UID 0, бо можливості надають вибрані привілейовані операції. Саме тому «не-root» і «найменші привілеї» пов’язані, але не тотожні. Не-root процес із надмірними можливостями все одно може робити речі, яких звичайний процес програми не повинен мати змоги робити ніколи.
| Capability | Призначення |
|---|---|
NET_ADMIN | Налаштування мережі |
NET_BIND_SERVICE | Прив’язка до портів < 1024 |
SYS_TIME | Зміна системного годинника |
SYS_PTRACE | Налагодження інших процесів |
CHOWN | Зміна власності на файли |
Таблиця перелічує поширені можливості, які ви можете побачити в прикладах, а не рекомендацію використовувати їх безтурботно. NET_BIND_SERVICE значно вужча за NET_ADMIN, а SYS_TIME рідко є доречною для контейнера програми. Коли завдання просить вас прив’язатися до порту нижче 1024, додайте NET_BIND_SERVICE, навіть якщо ваш локальний кластер випадково дозволяє непривілейовані низькі порти через ip_unprivileged_port_start. SYS_PTRACE може бути корисною для інструментів налагодження, але є ризикованою у звичайних робочих навантаженнях. CHOWN може приховувати проблеми з правами образу, якщо ви використовуєте її як милицю замість виправлення власності під час збирання.
securityContext: allowPrivilegeEscalation: false # Prevent gaining more privilegesallowPrivilegeEscalation: false не дає процесу отримати більше привілеїв, ніж його батьківський процес, зокрема через setuid-двійкові файли. У Kubernetes це налаштування тісно пов’язане з поведінкою Linux no_new_privs. Це сильне типове значення для контейнерів програм, бо звичайні сервіси не повинні потребувати перетворення запуску з нижчими привілеями на стан виконання з вищими привілеями.
securityContext: privileged: true # Full host access (DANGEROUS - rarely needed)privileged: true — протилежний кінець спектра. Він дає контейнеру широкий доступ до хоста, зокрема можливості та доступ до пристроїв, що обходять звичайну модель ізоляції. Деякі системні агенти рівня вузла потребують такого доступу, але більшість Подів програм CKAD — ні. Якщо завдання просить можливість, додайте можливість; якщо воно просить виконання від не-root, привілейований режим майже напевно є хибним напрямком.
Який підхід ви б тут обрали і чому: надати privileged: true, щоб контейнер міг коригувати правила iptables, чи відкинути всі можливості й додати лише NET_ADMIN? Другий підхід є відповіддю з найменшими привілеями, бо надає конкретну операцію ядра, не даючи контейнеру кожну доступну привілейовану операцію. Вам усе одно варто запитати, чи має програма взагалі змінювати iptables, але вибір на рівні маніфесту є чітким.
securityContext: capabilities: drop: - ALL add: - NET_BIND_SERVICEПатерн «відкинути все, додати конкретне» легко обстоювати, бо він робить виняток видимим. Рецензентам не треба гадати, які типові можливості надало середовище виконання, а майбутні супровідники можуть точно побачити, чому можливість було додано. У роботі на CKAD цей патерн також запобігає випадковому надмірному наданню, коли запитання просить одну іменовану можливість і нічого більше.
Можливості взаємодіють з іншими засобами контролю, а не замінюють їх. Ви можете працювати від не-root UID, заборонити підвищення привілеїв, використати кореневу файлову систему лише для читання й усе одно додати одну конкретну можливість за потреби. Цей багаторівневий підхід практичний, бо кожне поле обробляє інший режим збою: ідентичність керує власником процесу, підвищення привілеїв керує змінами під час виконання, можливості керують повноваженнями ядра, а налаштування файлової системи керують записуваними поверхнями.
Робочий процес перевірки та налагодження
Розділ «Робочий процес перевірки та налагодження»Налагодження SecurityContext має ґрунтуватися на доказах. Почніть із налаштованих полів об’єкта API, а потім перегляньте докази виконання всередині контейнера, якщо він стартує. Невдалий запуск може бути спричинений політикою допуску, конфліктами користувача образу чи перевіркою виконання, як-от runAsNonRoot. Запущений контейнер із помилками прав доступу потребує іншого шляху: перегляньте UID, групи, власність на монтування й точний каталог, який дав збій.
# Check what user process runs askubectl exec my-pod -- id# uid=1000 gid=3000 groups=2000
# Check file ownership in volumekubectl exec my-pod -- ls -la /data# drwxrwsrwx 2 root 2000 ...
# Check capabilitieskubectl exec my-pod -- cat /proc/1/status | grep CapКоманда id повідомляє ідентичність процесу, яку Linux фактично використовує. Перелік каталогу повідомляє, чи є цільовий шлях записуваним для цієї ідентичності чи однієї з її груп. Статус можливостей у /proc/1/status є нижчого рівня й закодований, але він усе одно корисний, коли вам потрібен доказ зміни можливостей. Заради швидкості на CKAD перші дві команди зазвичай розв’язують більшість питань прав доступу SecurityContext.
Не пропускайте kubectl describe pod, коли контейнер дає збій ще до того, як ви можете виконати exec до нього. Події часто кажуть, чи Kubernetes відмовив контейнеру, бо той працював би від root, чи монтування тома зазнало невдачі, чи контейнер увійшов у цикл збоїв після старту процесу. Ця різниця важлива, бо збій допуску SecurityContext виправляється в маніфесті, тоді як збій програми може потребувати записуваних шляхів чи змін прав на рівні образу.
# Pod-levelspec: securityContext: runAsUser: 1000 runAsGroup: 3000 fsGroup: 2000 runAsNonRoot: true
# Container-levelcontainers:- name: app securityContext: runAsUser: 1000 allowPrivilegeEscalation: false readOnlyRootFilesystem: true privileged: false capabilities: drop: ["ALL"] add: ["NET_BIND_SERVICE"]Цей короткий довідник навмисно розділено за рівнями. Якщо ви швидко редагуєте під час екзамену, він нагадує не ставити fsGroup під окремий контейнер і не ховати налаштування лише для контейнера на хибному рівні. Kubernetes відхиляє деякі неправильно розміщені поля під час валідації, але інші помилки тонші, бо чинне значення на хибному рівні змінює хибну область дії.
Коли ви оглядаєте живий Под, віддавайте перевагу прямим запитанням над широкими дампами. kubectl get pod secure-pod -o yaml корисна, але може поховати потрібні рядки в довгому документі. Сфокусований JSONPath може бути швидшим, коли ви знаєте поле, тоді як describe краще підходить для подій і стану високого рівня. Ключ — рухатися від симптому до доказу, а не від симптому до здогаду.
kubectl get pod secure-pod -o jsonpath='{.spec.securityContext}{"\n"}'kubectl get pod secure-pod -o jsonpath='{.spec.containers[0].securityContext}{"\n"}'kubectl describe pod secure-podЯкщо Под працює від хибного користувача, перегляньте і контекст Пода, і контекст контейнера, перш ніж редагувати. Якщо том не є записуваним, перегляньте id, шлях монтування та fsGroup рівня Пода. Якщо коренева система лише для читання ламає запуск, визначте точний шлях, до якого виконується запис, і змонтуйте там том. Якщо проблема стосується привілейованої поведінки, порівняйте запитану операцію з поточними можливостями замість того, щоб одразу стрибати до privileged: true.
Розбір прикладу: проєктування безпечного Пода
Розділ «Розбір прикладу: проєктування безпечного Пода»Сценарій вправи: ви отримуєте завдання CKAD, яке просить Под на ім’я secure-pod, що запускає nginx, з не-root UID, без підвищення привілеїв, з кореневою файловою системою лише для читання та записуваними тимчасовими каталогами. Наївна версія задає runAsNonRoot: true і readOnlyRootFilesystem: true, а потім дає збій, бо nginx очікує шляхи запису під час виконання. Успішна версія поєднує кожне обмеження з мінімальною підтримувальною конфігурацією.
Перше рішення — ідентичність. Оскільки завдання вимагає не-root поведінки, задайте runAsNonRoot: true і вкажіть числовий runAsUser, а не покладайтеся на типове значення образу. Друге рішення — записувані шляхи. Оскільки коренева файлова система лише для читання, додайте монтування emptyDir для шляхів, до яких nginx пише під час виконання. Третє рішення — привілеї. Оскільки програмі не потрібні додаткові повноваження ядра, вимкніть підвищення привілеїв і відкиньте всі можливості.
Цей приклад не просить вас запам’ятати внутрішню будову nginx. Він навчає діагностичної форми, яка застосовна до багатьох образів: після того, як ви замкнете ідентичність і кореневу файлову систему, стежте за конкретними шляхами, що все одно потребують записів під час виконання. Монтуйте лише ці шляхи, тримайте решту образу незмінною й лишайте додавання можливостей осторонь, доки завдання не дасть вам конкретної причини.
Підсумковий маніфест є наведеним раніше прикладом безпечного Пода й навмисно дружній до CKAD. Він використовує один Под, один контейнер, чіткі числові ідентифікатори та явні монтування томів. Під час перевірки ви можете запустити kubectl logs secure-pod, щоб підтвердити старт процесу, kubectl exec secure-pod -- id, щоб підтвердити ідентичність, та kubectl exec secure-pod -- touch /test, щоб підтвердити, що корінь лишається лише для читання. Ці перевірки пов’язують YAML зі спостережуваною поведінкою.
Ті самі міркування застосовні й тоді, коли ви ремонтуєте наявний маніфест. Прочитайте поточні поля, визначте, якої вимоги бракує, і зробіть найменшу зміну, що задовольняє цю вимогу без послаблення іншої. Додавання emptyDir до /tmp є малою зміною. Прибирання readOnlyRootFilesystem, бо один шлях потребує записів, є широким послабленням, а широкі послаблення рідко є правильною відповіддю на екзамені чи в огляді.
Контекст політики та сортування на екзамені
Розділ «Контекст політики та сортування на екзамені»SecurityContext — це конфігурація робочого навантаження, а не політика кластера, але ці два аспекти зустрічаються під час допуску. Простір імен може мати політику допуску, яка перевіряє Под перед його плануванням, і Под, що порушує цю політику, може ніколи не створити запущеного контейнера. Саме тому перше запитання налагодження — чи Kubernetes відхилив специфікацію Пода, чи процес контейнера стартував і потім дав збій. Те саме поле YAML може бути частиною обох історій, але докази з’являються в різних місцях.
Pod Security Admission у Kubernetes використовує мітки простору імен, щоб застосувати Pod Security Standards, як-от privileged, baseline та restricted. Як кандидат CKAD, ви не маєте адмініструвати всю систему допуску, але маєте писати робочі навантаження, що можуть вижити в обмежених середовищах. Коли простір імен примушує профіль restricted, налаштування на кшталт privileged: true, широких можливостей, відсутніх засобів контролю не-root і підвищення привілеїв стають чимось більшим за зауваження в огляді. Вони можуть стати жорсткими причинами відхилення ще до того, як долучиться планувальник.
Профіль restricted корисний, бо перекладає широку мету безпеки на конкретні перевірки маніфесту. Він не просто каже «будь безпечнішим»; він шукає конкретні налаштування високого ризику. Привілейований контейнер відхиляється, бо отримує широкий доступ рівня хоста. Контейнер, що дозволяє підвищення привілеїв, відхиляється, бо може отримати більше привілеїв після старту. Правила можливостей підштовхують вас до відкидання ALL і додавання назад лише вузько дозволених винятків, як-от NET_BIND_SERVICE.
Помилки допуску часто є дієвішими, ніж здаються спершу. Вони зазвичай називають шлях поля, заборонене значення чи родину засобів контролю, що зазнала невдачі. Якщо повідомлення вказує на securityContext.privileged, приберіть привілейований режим і вирішіть, яку меншу можливість, якщо взагалі якусь, насправді потребує робоче навантаження. Якщо повідомлення вказує на додавання можливостей, порівняйте запитану можливість із профілем restricted. Якщо воно вказує на виконання від не-root, перегляньте, чи задає Под runAsNonRoot, runAsUser або перевизначення контейнера, що суперечить типовому значенню Пода.
Помилки виконання розповідають іншу історію, бо контейнер уже стартував. Рядок логу про невдачу запису файлу кешу — це не збій допуску; це процес зустрічається з правами Linux. Збій після увімкнення кореневої файлової системи лише для читання не є доказом того, що політика занадто сувора; це доказ того, що маніфест не оголосив усіх записуваних шляхів виконання. Тримання цих категорій окремо не дає вам виправляти проблему шляху виконання послабленням засобів контролю безпеки, звернених до допуску.
Сценарій вправи: Под приймається в просторі імен розробки, але відхиляється в обмеженому тренувальному просторі імен. Маніфест працює від UID 1000, але один сайдкар задає allowPrivilegeEscalation: true і додає SYS_PTRACE для налагодження. Правильний ремонт — не зміна ідентичності головного контейнера. Правильний ремонт — прибрати або обґрунтувати налаштування привілеїв сайдкара, бо відхилення походить від винятку рівня контейнера, що порушує політику простору імен.
Ті самі міркування допомагають із попередженнями. Деякі кластери налаштовують мітки Pod Security у режимі warn чи audit, перш ніж примусово застосувати політику. Попередження — це не пропуск; це попередній перегляд майбутнього відхилення. Якщо ви бачите попередження про поле SecurityContext під час практики, сприймайте його як безкоштовну підказку. Відремонтуйте маніфест, доки робоче навантаження ще стартує, а потім переконайтеся, що попередження зникло, перш ніж покладатися на Под у суворішому просторі імен.
Робота для Kubernetes 1.35+ має використовувати термінологію Pod Security Admission, а не старіші припущення про PodSecurityPolicy. PodSecurityPolicy було видалено з Kubernetes роками раніше, тож сучасна відповідь CKAD не повинна залежати від створення чи прив’язки об’єкта PSP. Для авторів робочих навантажень практичний підсумок простіший: пишіть специфікації Подів, які напряму задовольняють політику простору імен. Поля SecurityContext — це засоби контролю з боку робочого навантаження, які ви можете редагувати, не потребуючи прав адміністратора кластера.
Коли завдання на екзамені згадує обмежений простір імен, читайте це як обмеження на проєктування маніфесту. Почніть із не-root ідентичності, уникайте привілейованого режиму, вимкніть підвищення привілеїв, відкиньте можливості й надайте явні записувані шляхи, якщо вмикаєте кореневу систему лише для читання. Потім перевірте, чи завдання просить конкретного винятку. Якщо ні — не додавайте його. Маніфест, що проходить без винятку, легше пояснити, і він менш імовірно зіткнеться з допуском.
Є одна тонка пастка: допуск може валідувати поля, не знаючи кожної проблеми прав на файли під час виконання. Под може задовольняти політику restricted і все одно дати збій, бо образ пише до каталогу, яким володіє root. Це не збій політики; це збій контракту образу та файлової системи. Ремонт — змонтувати потрібний записуваний шлях, налаштувати програму писати в доречне місце або перебудувати образ із власністю, що відповідає налаштованому UID.
Використовуйте двостовпцевий ментальний чек-лист, навіть якщо не записуєте його. З боку політики запитайте, чи використовує маніфест заборонені привілеї, чи дозволяє підвищення, чи працює від root, чи зберігає широкі можливості. З боку виконання запитайте, чи може ідентичність процесу читати й писати ті шляхи, які йому насправді потрібні. Безпечний Под має задовольняти обидва стовпці. Проходження допуску є необхідним, але не доводить, що програма має правильні записувані каталоги.
Цей контекст політики також пояснює, чому SecurityContext належить до оглядів програм. Поля близькі до робочого навантаження, але виражають загальнокластерні очікування щодо радіуса ураження та ізоляції. Рецензент може порівняти оголошений виняток із потребою програми: вебсерверу може знадобитися записуваний каталог кешу, але йому не має знадобитися SYS_TIME; сайдкару метрик може знадобитися змонтований сокет, але він не має автоматично отримувати привілейований режим. Ця дисципліна огляду перетворює поля YAML на дієві інженерні рішення.
Патерни та антипатерни
Розділ «Патерни та антипатерни»Патерни SecurityContext корисні лише тоді, коли зберігають конкретну властивість. «Захистити все» занадто розпливчасто, щоб скеровувати редагування маніфесту, тож кожен патерн нижче називає ситуацію, де він застосовний, і компроміс, який вводить. Спільна тема — явні найменші привілеї: вкажіть користувача, вкажіть записувані шляхи, вкажіть можливості й дайте Kubernetes відхиляти конфігурації, що порушують контракт.
| Патерн | Коли використовувати | Чому працює | Зауваження щодо масштабування |
|---|---|---|---|
| Типова ідентичність рівня Пода | Усі контейнери можуть розділяти той самий UID, GID і групу тома | Зменшує повторюваний YAML, зберігаючи контракт Пода видимим | Сайдкари з іншою власністю образу можуть потребувати перевизначень контейнера |
| Коренева система лише для читання плюс іменовані записувані монтування | Програмі потрібні робочі шляхи виконання, але вона не повинна змінювати файли образу | Тримає шар образу незмінним, дозволяючи очікувані записи | Документуйте кожен записуваний шлях, щоб пізніші зміни не додавали прихованого стану |
| Відкинути всі можливості й додати одну назад | Робоче навантаження потребує одного іменованого дозволу ядра | Робить виняток придатним для огляду й уникає типових значень виконання | Додавання можливостей мають бути прив’язані до вимоги програми |
| Перевірка виконання після редагувань маніфесту | Поля безпеки змінювалися під тиском часу | Підтверджує чинний UID, групи й записувані шляхи | Автоматизуйте перевірки в CI для повторюваних навантажень, але знайте ручні команди |
Антипатерни зазвичай походять зі спроби виправити симптом без визначення рівня, що його спричинив. Под, який не може писати до змонтованого тома, не потребує автоматично root. Контейнер, якому потрібна прив’язка порту, не потребує автоматично привілейованого режиму. Збій запуску після runAsNonRoot не означає, що налаштування погане; він означає, що образ чи маніфест усе ще хоче root.
| Антипатерн | Що йде не так | Краща альтернатива |
|---|---|---|
Задавання privileged: true для виправлення невідомих помилок | Надає широкий доступ рівня хоста й приховує справжній збій | Спершу перегляньте події, UID, групи, шляхи та можливості |
Використання runAsNonRoot без числового UID | Образ усе ще може розв’язатися в root або бути неперевірюваним | Поєднайте його з runAsUser: 1000 чи іншим відомим ненульовим UID |
Вимкнення readOnlyRootFilesystem заради одного шляху запису | Робить весь шар образу знову змінним | Змонтуйте emptyDir чи відповідний том на потрібному шляху |
| Додавання широких можливостей до тестування | Дає процесу повноваження ядра, яких він може не потребувати | Відкиньте всі можливості, потім додайте лише іменовану вимогу |
Ці патерни масштабуються, бо тримають усунення несправностей локальним. Коли Под дає збій, ви можете запитати, яка обіцянка порушена: ідентичність, записуваний шлях, можливість чи підвищення привілеїв. Коли відповідь локальна, виправлення зазвичай теж локальне. Ця звичка важлива у більших кластерах, де швидке, але широке послаблення може створити виняток політики, що переживе початковий інцидент.
Структура прийняття рішень
Розділ «Структура прийняття рішень»Використовуйте цю структуру, коли вирішуєте, де розмістити налаштування або як відремонтувати збій. Мета — не зробити кожне робоче навантаження однаковим. Мета — зробити кожне робоче навантаження достатньо явним, щоб Kubernetes, рецензенти й майбутні оператори могли зрозуміти межу безпеки. Рухайтеся від ідентичності до файлової системи й до привілеїв, бо цей порядок відповідає найпоширенішим симптомам CKAD.
Start with the symptom | vDoes the pod fail before the process starts? | +-- yes --> Check events for runAsNonRoot, UID, policy, or validation errors | +-- no --> Can the process write the path it needs? | +-- no --> Check id, ls -la, fsGroup, and writable volume mounts | +-- yes --> Does the app need a kernel-level operation? | +-- yes --> Drop ALL capabilities, add the named capability | +-- no --> Keep privilege escalation false and root read-onlyПерша гілка відокремлює відхилення конфігурації від поведінки програми. Якщо контейнер не стартує, kubectl exec поки не допоможе, тож події та огляд маніфесту йдуть першими. Якщо процес стартує й потім логує помилки прав доступу, ядро вже примушує правила ідентичності й файлової системи, тож ваші докази мають походити з id, власності на каталоги й конкретної цілі запису.
| Запитання | Оберіть це | Уникайте цього |
|---|---|---|
| Чи всі контейнери розділяють той самий UID і GID? | runAsUser і runAsGroup рівня Пода | Повторення однакових значень у кожному контейнері без причини |
| Чи потрібен одному контейнеру інший UID? | Перевизначення runAsUser рівня контейнера | Зміна типового значення Пода й поломка інших контейнерів |
| Чи потрібен змонтованому тому груповий доступ на запис? | fsGroup рівня Пода | Спроба задати fsGroup під одним контейнером |
Чи потрібен образу /tmp, поки корінь лише для читання? | emptyDir, змонтований на /tmp | Глобальне вимкнення readOnlyRootFilesystem |
| Чи потрібен процесу один привілей ядра? | Відкиньте ALL, додайте іменовану можливість | Задавання privileged: true |
Структура також підказує, коли SecurityContext не є єдиною відповіддю. Якщо образ пише в жорстко закодовані каталоги програми, якими володіє root, маніфест може змонтувати поверх цих шляхів, але кращим довгостроковим виправленням може бути перебудова образу з правильною власністю й налаштовуваними шляхами. Kubernetes може примусити безпечніше середовище виконання, проте дизайн образу все одно визначає, наскільки легко цього середовища досягти.
Заради швидкості на CKAD запам’ятовуйте порядок рішень, а не кожне можливе поле. Збої ідентичності потребують runAsUser, runAsGroup і runAsNonRoot. Збої прав на томи вказують на fsGroup і власність на каталоги. Збої файлової системи лише для читання вказують на явні записувані монтування. Запити привілеїв вказують спершу на можливості, а на привілейований режим — лише тоді, коли робоче навантаження справді є системним компонентом рівня вузла.
Чи знали ви?
Розділ «Чи знали ви?»- Kubernetes документує SecurityContext окремо для Подів і контейнерів, бо
PodSecurityContextіSecurityContext— це різні об’єкти API з полями, що перетинаються, але не є тотожними. NET_BIND_SERVICEіснує, бо Unix-подібні системи історично обмежували прив’язку до портів нижче1024привілейованими процесами; деякі сучасні середовища виконання послаблюють це черезip_unprivileged_port_start, але CKAD усе одно очікує цю можливість, коли завдання вимагає низького порту.allowPrivilegeEscalation: falseвідображає ідею Linuxno_new_privs, яка не дає процесу отримати привілеї через переходи exec.fsGroupвпливає на підтримувані змонтовані томи, а не на звичайні файли, уже запечені в шар образу контейнера.
Типові помилки
Розділ «Типові помилки»Більшість помилок SecurityContext — це малі вибори в YAML із великими ефектами під час виконання. Виправлення зазвичай полягає не в прибиранні засобів контролю безпеки, а в розміщенні правильного засобу контролю на правильному рівні та перевірці результату однією конкретною командою. Використовуйте цю таблицю як чек-лист налагодження, коли Под дає збій під обмежувальною політикою чи стартує з помилками прав на файли.
| Помилка | Чому це стається | Як це виправити |
|---|---|---|
runAsNonRoot з образом, що все одно працює від root | Прапорець валідує не-root поведінку, але не обирає UID | Додайте числовий ненульовий runAsUser, потім перевірте через kubectl exec POD -- id |
readOnlyRootFilesystem без записуваних монтувань виконання | Образ пише тимчасові файли, сокети, кеші чи PID-файли під кореневими шляхами | Змонтуйте томи emptyDir на точні записувані шляхи, як-от /tmp чи /var/run |
| Не відкидання можливостей перед додаванням однієї | Типові значення виконання можуть лишити більше повноважень ядра, ніж потребує навантаження | Задайте capabilities.drop: ["ALL"], потім додайте лише іменовану можливість |
| Плутання контексту рівня Пода й рівня контейнера | Деякі поля перетинаються, тоді як інші чинні лише на одному рівні | Розмістіть спільні типові значення й fsGroup на рівні Пода; розмістіть засоби контролю привілеїв на рівні контейнера |
Використання privileged: true як загального виправлення | Воно обходить багато звичайних меж ізоляції й приховує справжню вимогу | Визначте, чи навантаження потребує зміни ідентичності, файлової системи чи однієї можливості |
Задавання fsGroup для виправлення прав шару образу | fsGroup націлений на підтримувані томи, а не на файли, запечені в образ | Перебудуйте образ із правильною власністю або змонтуйте записуваний том на шляху |
| Ігнорування подій, коли Под не стартує взагалі | Поки немає процесу контейнера для огляду, тож exec не може працювати | Використайте kubectl describe pod і прочитайте причину збою перед зміною YAML |
Тест
Розділ «Тест»Запитання 1: Ваш Под задає `runAsNonRoot: true`, але контейнер дає збій із повідомленням, що образ запуститься від root. У чому проблема і яку зміну маніфесту вам слід зробити?
runAsNonRoot: true — це перевірка валідації, а не призначення UID. Kubernetes відмовляється стартувати контейнер, бо чинний користувач образу — root або не може бути прийнятий як не-root. Додайте явний числовий ненульовий runAsUser, як-от 1000, на рівні Пода чи контейнера, що відповідає навантаженню. Якщо образ потім стикається з помилками прав на файли, збережіть налаштування не-root і відремонтуйте записувані шляхи чи власність образу замість прибирання запобіжника.
Запитання 2: Після увімкнення `readOnlyRootFilesystem: true` контейнер nginx дає збій під час запису `/var/cache/nginx`. Як зберегти перевагу безпеки й змусити Под стартувати?
Збережіть кореневу файлову систему лише для читання й змонтуйте записуваний том на конкретному шляху, що потребує записів під час виконання. Для цієї форми образу доречний emptyDir на /var/cache/nginx, а маніфест також може потребувати записуваних монтувань на /tmp і /var/run. Вимкнення readOnlyRootFilesystem зробило б весь шар образу знову змінним, що є ширшою зміною, ніж вимагає симптом. Правильне виправлення зберігає обмеження й документує очікувані записувані каталоги.
Запитання 3: Под має `spec.securityContext.runAsUser: 1000`. Контейнер A не має SecurityContext контейнера, тоді як Контейнер B задає `runAsUser: 2000`. Який UID використовує кожен контейнер і що вам слід оглянути далі, якщо спільний том не є записуваним?
Контейнер A успадковує UID 1000 від SecurityContext рівня Пода, тоді як Контейнер B працює від UID 2000, бо поле рівня контейнера перевизначає типове значення Пода для цього контейнера. Якщо спільний том не є записуваним, перегляньте fsGroup рівня Пода, групи процесу через id і власність на каталог через ls -la. Перевизначення UID пояснює ідентичність процесу, але доступ до тома часто залежить від власності на групу. Зміна обох контейнерів на root приховала б справжню проблему прав на том.
Запитання 4: Вашій команді потрібен контейнер для прив'язки до низького порту, і хтось пропонує `privileged: true`. Який підхід SecurityContext дотримується найменших привілеїв?
Не використовуйте привілейований режим для вузької вимоги прив’язки порту. Спершу відкиньте всі можливості, потім додайте NET_BIND_SERVICE, якщо процес справді має прив’язатися до порту нижче 1024. Це надає конкретний дозвіл ядра без широкого доступу до хоста. Вам також варто розглянути, чи може програма слухати на вищому порту контейнера й дозволити Сервісу виставити потрібний порт, бо повне уникнення можливості ще простіше, коли програма це підтримує.
Запитання 5: Под успішно стартує від UID `1000`, але `touch /data/file` дає збій із permission denied на `emptyDir`, змонтованому на `/data`. Що ви перевіряєте й змінюєте?
Спершу запустіть id усередині контейнера й ls -la /data, щоб порівняти UID і групи процесу з власністю й режимом каталогу. Якщо каталог потребує групового доступу для Пода, задайте fsGroup рівня Пода, що відповідає бажаній власності на групу. fsGroup — це правильний важіль для підтримуваних змонтованих томів, тоді як runAsUser керує UID процесу. Якщо шлях був би всередині образу, а не томом, доречнішим було б виправлення власності образу чи записуване монтування.
Запитання 6: Ви оглядаєте маніфест і знаходите `fsGroup` під `containers[0].securityContext`. Чому це підозріло і де має жити це налаштування?
fsGroup обмежений областю Пода, бо впливає на підтримувані змонтовані томи для Пода, а не лише на процес одного контейнера. Він належить до spec.securityContext, поряд з іншими типовими значеннями рівня Пода. SecurityContext рівня контейнера — це місце для полів на кшталт allowPrivilegeEscalation, readOnlyRootFilesystem і змін можливостей окремого контейнера. Переміщення fsGroup на рівень Пода робить область дії відповідною ресурсу, який налаштовується.
Запитання 7: Обмежений простір імен відхиляє Под, що задає `privileged: true`, але навантаженню потрібно лише оглянути власну ідентичність процесу й писати тимчасові файли. Як би ви переробили Под?
Приберіть privileged: true, бо описаному навантаженню не потрібен доступ рівня хоста. Задайте не-root UID через runAsUser і runAsNonRoot, вимкніть підвищення привілеїв, відкиньте всі можливості й увімкніть кореневу файлову систему лише для читання, якщо образ це підтримує. Додайте emptyDir на /tmp чи будь-який інший задокументований тимчасовий шлях. Цей дизайн задовольняє фактичні потреби, водночас узгоджуючись з очікуваннями політики restricted.
Практична вправа
Розділ «Практична вправа»Ця вправа перетворює рішення модуля на спостережувану поведінку Пода. Запускайте команди в одноразовому просторі імен, якщо ви використовуєте спільний кластер, і видаляйте кожен Под перед переходом далі, якщо у вашому середовищі жорсткі квоти на ресурси. Маніфести використовують BusyBox і nginx, бо вони роблять ідентичність, записи у файли та каталоги виконання легкими для огляду без додаткового коду програми.
Завдання просуваються від ідентичності до власності на томи, до поведінки файлової системи лише для читання й нарешті до комбінованого безпечного Пода. Для кожного завдання спрогнозуйте результат перед запуском команди перевірки. Ця звичка важлива, бо робота з SecurityContext легша, коли ви знаєте, чи перевіряєте UID, власність на групу, записувані шляхи чи можливості.
Завдання: налаштувати параметри безпеки для Подів.
- Впровадити
runAsUserіrunAsGroupрівня Пода, потім перевірити ідентичність процесу через логи. - Діагностувати, як
fsGroupзмінює власність на змонтований том для не-root процесу. - Довести, що
readOnlyRootFilesystemблокує записи до шару образу, тримаючи Под запущеним. - Оцінити зміну лише можливостей, відкинувши всі можливості й додавши
NET_BIND_SERVICE. - Спроєктувати придатний для CKAD маніфест безпечного Пода з не-root ідентичністю, кореневою системою лише для читання, відкинутими можливостями й записуваним змонтованим шляхом.
Частина 1: Запуск від не-root
Розділ «Частина 1: Запуск від не-root»Перший Под — це мінімальна перевірка ідентичності. Він задає UID і первинний GID на рівні Пода, запускає id, а потім спить, щоб ви могли оглянути його за потреби. Очікуваний вивід має показати налаштовані UID і GID, що доводить: ідентичність процесу походить із SecurityContext, а не з типового значення образу.
cat << 'EOF' | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: nonroot-podspec: securityContext: runAsUser: 1000 runAsGroup: 3000 containers: - name: app image: busybox command: ['sh', '-c', 'id && sleep 3600']EOF
kubectl wait --for=condition=ready pod/nonroot-pod --timeout=30skubectl logs nonroot-pod# Should show: uid=1000 gid=3000 groups=3000Нотатки до розв'язання для Частини 1
Якщо Под стає готовим, kubectl logs nonroot-pod має показати UID 1000 і GID 3000. Якщо ні — перегляньте kubectl describe pod nonroot-pod на проблеми завантаження образу чи планування перед зміною SecurityContext. Це завдання ізолює лише ідентичність процесу; воно не перевіряє fsGroup, бо том не змонтовано.
Частина 2: Демонстрація fsGroup
Розділ «Частина 2: Демонстрація fsGroup»Другий Под додає змонтований emptyDir і fsGroup рівня Пода. Це найменша корисна демонстрація різниці між ідентичністю процесу й власністю на том. Процес усе ще працює від UID 1000, але змонтований каталог має показати групову поведінку, пов’язану з 2000.
cat << 'EOF' | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: fsgroup-podspec: securityContext: runAsUser: 1000 fsGroup: 2000 containers: - name: app image: busybox command: ['sh', '-c', 'ls -la /data && id && sleep 3600'] volumeMounts: - name: data mountPath: /data volumes: - name: data emptyDir: {}EOF
kubectl wait --for=condition=ready pod/fsgroup-pod --timeout=30skubectl logs fsgroup-pod# Files in /data owned by group 2000Нотатки до розв'язання для Частини 2
Вивід логу має показати каталог /data й ідентичність процесу. Шукайте групу 2000 у власності чи списку груп. Якщо ваш драйвер зберігання обробляє власність інакше, зосередьтеся на концепції: fsGroup оголошується на рівні Пода, бо змонтовані томи є ресурсами Пода, і ви перевіряєте його, порівнюючи id з ls -la.
Частина 3: Файлова система лише для читання
Розділ «Частина 3: Файлова система лише для читання»Третій Под навмисно намагається писати до кореневої файлової системи. Команда перехоплює збій і виводить чітке повідомлення, тож Под усе одно має стати готовим. Це дає вам безпечний спосіб довести, що коренева файлова система лише для читання, не перетворюючи вправу на завдання з налагодження циклу збоїв.
cat << 'EOF' | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: readonly-podspec: containers: - name: app image: busybox command: ['sh', '-c', 'touch /test 2>&1 || echo "Cannot write!"; sleep 3600'] securityContext: readOnlyRootFilesystem: trueEOF
kubectl wait --for=condition=ready pod/readonly-pod --timeout=30skubectl logs readonly-pod# Should show: Cannot write!Нотатки до розв'язання для Частини 3
Повідомлення підтверджує, що записи до /test заблоковані кореневою файловою системою лише для читання. Це очікувано й бажано. Якщо програмі справді потрібен записуваний шлях, змонтуйте том на цьому шляху замість вимкнення налаштування безпеки для всієї кореневої файлової системи.
Прибирання
Розділ «Прибирання»Приберіть перші три Поди перед запуском вправ-дрилів, якщо у вашому кластері жорсткі квоти на Поди. Прапорці примусового видалення прийнятні для цієї одноразової вправи, бо Поди не керують довговічним станом. У продакшн-просторі імен віддавайте перевагу звичайному видаленню, якщо у вас немає причини обходити коректне завершення.
kubectl delete pod nonroot-pod fsgroup-pod readonly-pod --force --grace-period=0Дриль 1: Запуск від користувача
Розділ «Дриль 1: Запуск від користувача»Цей дриль повторює призначення UID без додаткового поля групи. Його мета — швидкість: ви маєте змогти створити маніфест, дочекатися готовності, прочитати лог і видалити Под, не шукаючи розташування полів. Сигнал перевірки — вивід id.
cat << 'EOF' | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: drill1spec: securityContext: runAsUser: 1000 containers: - name: app image: busybox command: ['sh', '-c', 'id && sleep 3600']EOF
kubectl wait --for=condition=ready pod/drill1 --timeout=30skubectl logs drill1kubectl delete pod drill1 --force --grace-period=0Дриль 2: Примус не-root
Розділ «Дриль 2: Примус не-root»Цей дриль поєднує runAsNonRoot з явним UID, щоб запобіжник мав конкретну не-root ідентичність для примусу. Якщо ви приберете runAsUser, поведінка може залежати від оголошеного користувача образу. З наявним UID специфікація Пода чітко повідомляє вимогу не-root.
cat << 'EOF' | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: drill2spec: securityContext: runAsNonRoot: true runAsUser: 1000 containers: - name: app image: busybox command: ['sleep', '3600']EOF
kubectl wait --for=condition=ready pod/drill2 --timeout=30skubectl get pod drill2kubectl delete pod drill2 --force --grace-period=0Дриль 3: Можливості
Розділ «Дриль 3: Можливості»Цей дриль демонструє патерн можливостей з найменшими привілеями. Контейнер відкидає кожну можливість і додає назад лише NET_BIND_SERVICE. Вивід /proc/1/status закодований, тож не сприймайте його як зрозумілий новачку звіт; використовуйте його як доказ того, що стан можливостей спостережний зсередини контейнера.
cat << 'EOF' | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: drill3spec: containers: - name: app image: busybox command: ['sleep', '3600'] securityContext: capabilities: drop: - ALL add: - NET_BIND_SERVICEEOF
kubectl wait --for=condition=ready pod/drill3 --timeout=30skubectl exec drill3 -- cat /proc/1/status | grep Capkubectl delete pod drill3 --force --grace-period=0Дриль 4: Лише для читання з тимчасовим каталогом
Розділ «Дриль 4: Лише для читання з тимчасовим каталогом»Цей дриль поєднує кореневу файлову систему лише для читання із записуваним монтуванням /tmp. Команда пише до /tmp, а не до кореня образу, тож вона має успішно виконатися. Це той патерн, який ви використовуватимете щоразу, коли програмі потрібен робочий простір, але решта файлової системи має лишатися незмінною.
cat << 'EOF' | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: drill4spec: containers: - name: app image: busybox command: ['sh', '-c', 'touch /tmp/test && echo "Wrote to /tmp" && sleep 3600'] securityContext: readOnlyRootFilesystem: true volumeMounts: - name: tmp mountPath: /tmp volumes: - name: tmp emptyDir: {}EOF
kubectl wait --for=condition=ready pod/drill4 --timeout=30skubectl logs drill4kubectl delete pod drill4 --force --grace-period=0Дриль 5: Перевірка fsGroup
Розділ «Дриль 5: Перевірка fsGroup»Цей дриль пише файл у змонтований том і перелічує каталог. Він допомагає пов’язати fsGroup із файлами, створеними під монтуванням тома. Якщо вивід відрізняється між бекендами зберігання, поясніть різницю в термінах поведінки власності на том, а не змінюйте Под на root.
cat << 'EOF' | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: drill5spec: securityContext: fsGroup: 2000 containers: - name: app image: busybox command: ['sh', '-c', 'touch /data/file && ls -la /data && sleep 3600'] volumeMounts: - name: data mountPath: /data volumes: - name: data emptyDir: {}EOF
kubectl wait --for=condition=ready pod/drill5 --timeout=30skubectl logs drill5# File should be owned by group 2000kubectl delete pod drill5 --force --grace-period=0Дриль 6: Повний безпечний Под
Розділ «Дриль 6: Повний безпечний Под»Фінальний дриль поєднує частини в один безпечний Под. Він працює від не-root UID, надає групу для доступу до тома, вимикає підвищення привілеїв, робить кореневу файлову систему лише для читання й відкидає можливості. Змонтований шлях /data дає процесу навмисне записуване розташування без послаблення шару образу.
cat << 'EOF' | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: drill6spec: securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 fsGroup: 2000 containers: - name: app image: busybox command: ['sh', '-c', 'id && ls -la /data && sleep 3600'] securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL volumeMounts: - name: data mountPath: /data volumes: - name: data emptyDir: {}EOF
kubectl wait --for=condition=ready pod/drill6 --timeout=30skubectl logs drill6kubectl get pod drill6 -o yaml | grep -A20 securityContextkubectl delete pod drill6 --force --grace-period=0Нотатки до розв'язання для Дрилю 6
Логи мають показати UID 1000, первинний GID 1000 і груповий доступ, що включає групу тома. Огляд YAML має показати налаштування безпеки і рівня Пода, і рівня контейнера. Якщо Под дає збій, спершу використайте kubectl describe pod drill6, потім перевірте, чи збій є валідацією ідентичності, поведінкою образу чи проблемою записуваного шляху.
Перевірка засвоєння
Розділ «Перевірка засвоєння»Завдання CKAD, які явно вимагають прив’язки до порту нижче 1024, усе одно очікують, що ви додасте
NET_BIND_SERVICE, а не покладатиметеся на цю поведінку sysctl середовища виконання.
Джерела
Розділ «Джерела»- https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
- https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#securitycontext-v1-core
- https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#podsecuritycontext-v1-core
- https://kubernetes.io/docs/concepts/security/pod-security-standards/
- https://kubernetes.io/docs/concepts/security/linux-kernel-security-constraints/
- https://kubernetes.io/docs/concepts/storage/volumes/#emptydir
- https://kubernetes.io/docs/reference/kubectl/generated/kubectl_exec/
- https://man7.org/linux/man-pages/man7/capabilities.7.html
- https://man7.org/linux/man-pages/man2/setuid.2.html
- https://man7.org/linux/man-pages/man2/setgid.2.html
Наступний модуль
Розділ «Наступний модуль»Модуль 4.5: ServiceAccount — далі ви пов’яжете ідентичність Пода з доступом до API Kubernetes, де ServiceAccount визначають, ким може автентифікуватися робоче навантаження і які дозволи воно може запитувати всередині кластера.