Перейти до вмісту

Модуль 1.1: Образи контейнерів

Hands-On Lab Available
K8s Cluster intermediate 75 min
Launch Lab ↗

Opens in Killercoda in a new tab

Складність: [СЕРЕДНЯ] — потребує розуміння поведінки Dockerfile, посилань на образи, доступу до реєстру та діагностики запуску Pod’ів у Kubernetes.

Час на проходження: 75 хвилин.

Передумови: Модуль 0.2 (Робочий процес розробника), базові знання про контейнери та впевнене читання подій (events) Pod’а.


Результати навчання

Розділ «Результати навчання»

Після завершення цього модуля ви зможете:

  • Діагностувати ImagePullBackOff, ErrImagePull, помилки автентифікації в реєстрі та некоректні посилання на образи, читаючи події Pod’а та поля образу в Kubernetes.
  • Спроєктувати відтворювані посилання на образи для Kubernetes 1.35+, використовуючи явні реєстри, теги, дайджести та значення imagePullPolicy.
  • Порівняти й застосувати поведінку CMD та ENTRYPOINT у Dockerfile разом із перевизначеннями command і args у Kubernetes.
  • Оптимізувати Dockerfile для кешування шарів, мінімальних базових образів, виконання без прав root та меншої поверхні атаки під час виконання.
  • Оцінити OCI-індекси образів, медіатипи шарів, реферери реєстру та підписи під час планування мультиархітектурних робочих процесів або процесів захисту ланцюга постачання.

Чому цей модуль важливий

Розділ «Чому цей модуль важливий»

Гіпотетичний сценарій: розгортання на staging-середовищі виглядає звичайним, поки кожна нова репліка не зупиняється у стані ImagePullBackOff. Деплоймент достатньо справний, щоб існувати, планувальник розмістив Pod’и, а кластер має вільні ресурси, але kubelet не може отримати образ контейнера, який запитує специфікація Pod’а. Розробник може стягнути той самий образ зі свого ноутбука, через що збій здається загадковим, але доступ до реєстру насправді потрібен середовищу виконання на вузлі.

Образи контейнерів — це точка передачі між розробкою застосунку та експлуатацією Kubernetes. Kubernetes не збирає ваш застосунок, не аналізує ваше дерево вихідного коду й не вгадує, який саме двійковий файл ви мали намір постачати. Він просить середовище виконання на вузлі стягнути названий артефакт, розпакувати цей артефакт у файлову систему та запустити налаштований процес із правилами ізоляції, заданими у специфікації Pod’а. Якщо будь-яка ланка цього ланцюга нечітка, змінна або неавтентифікована, навіть бездоганний маніфест може дати збій під час виконання.

Цей модуль розглядає роботу з образами як експлуатаційну навичку, а не як примітку про пакування. Ви пов’яжете рядок посилання на образ із пошуком у реєстрі, інструкції Dockerfile — з полями command та args у Kubernetes, а вибір політики стягування — з поведінкою кешу на вузлі. Мета — не запам’ятати кожен інструмент для роботи з контейнерами. Мета — зробити збої, пов’язані з образами, нудними, бо ви знаєте, де ухвалюється кожне рішення.

Іспит CKAD винагороджує саме такий вид практичної діагностики. Pod, що застряг до запуску контейнера, має інший шлях розслідування, ніж застосунок, який аварійно завершується після запуску, а проблеми з образами часто проявляються лише в розділі подій (Events). До кінця модуля ви маєте вміти подивитися на ім’я образу, політику стягування, Secret та entrypoint у Dockerfile й передбачити, як поведеться вузол Kubernetes 1.35+.

Посилання на образи — це адреси під час виконання

Розділ «Посилання на образи — це адреси під час виконання»

Посилання на образ — це більше, ніж коротке ім’я у YAML. Це адреса, яка повідомляє середовищу виконання на вузлі, до якого реєстру звертатися, який шлях репозиторію читати, який тег або дайджест розв’язувати та який маніфест образу завантажувати. Коли посилання неповне, Kubernetes і середовище виконання контейнерів заповнюють значення за замовчуванням, і ці значення зручні для тренування, але ризиковані для контрольованих розгортань.

Компактну форму посилання варто вивчити, бо вона пояснює багато заплутаних збоїв стягування. Голе посилання nginx — це не якийсь магічний об’єкт Kubernetes; воно перетворюється на запит до простору імен Docker Hub за замовчуванням і тегу latest за замовчуванням. Повністю кваліфіковане посилання несе більше наміру, тоді як дайджест додає криптографічну незмінність, називаючи точний вміст маніфесту замість рухомого тегу.

[registry/][namespace/]image[:tag][@digest]

Частини у квадратних дужках необов’язкові, але необов’язкове не означає неважливе. Пропуск реєстру зазвичай означає docker.io, пропуск простору імен часто означає library, а пропуск тегу означає latest. Ці значення за замовчуванням роблять швидкі демонстрації приємними, проте вони приховують рішення, які робочі системи зазвичай хочуть явно фіксувати в системі контролю версій.

КомпонентОбов’язковийПрикладЗа замовчуванням
РеєстрНіdocker.io, gcr.io, quay.iodocker.io
Простір іменНіlibrary, mycompanylibrary
ОбразТакnginx, myapp-
ТегНіlatest, 1.19.0, alpinelatest
ДайджестНіsha256:abc123...-

Усі наведені нижче приклади виглядають як звичайні значення YAML, але вони відображають різні експлуатаційні гарантії. Коротке посилання на публічний образ легко набрати під час лабораторної, посилання на приватний реєстр потребує облікових даних, а посилання за дайджестом дає найсильнішу повторюваність, оскільки адреса вмісту має збігатися. Kubernetes зберігає наданий вами рядок; середовище виконання на вузлі виконує роботу з реєстром, коли Pod потрапляє на вузол.

# Повна специфікація
image: docker.io/library/nginx:1.21.0
# Еквівалентна коротка форма (docker.io/library мається на увазі)
image: nginx:1.21.0
# Інший реєстр
image: gcr.io/google-containers/nginx:1.21.0
# Власний простір імен
image: myregistry.com/myteam/myapp:v2.0.0
# З дайджестом (незмінне посилання)
image: nginx@sha256:abc123def456...
# Тег latest (уникайте в продакшені)
image: nginx:latest
image: nginx # те саме, що й вище

Теги — це зручні для людини вказівники, а не постійні записи релізів. Команда може перепризначити інший образ під тегом v1.21.0, якщо реєстр це дозволяє, і багато команд випадково перезаписують latest під час розробки. Дайджести інакші, бо хеш обчислюється з вмісту маніфесту, тож той самий дайджест не може непомітно вказувати на інший образ без зміни адреси.

# ПОГАНО: latest може несподівано змінитися
image: nginx:latest
# ДОБРЕ: конкретна версія, відтворювано
image: nginx:1.21.0
# КРАЩЕ: конкретна версія з базою Alpine (менша)
image: nginx:1.21.0-alpine

Для роботи з CKAD практична звичка проста: прочитайте повний рядок образу, перш ніж змінювати будь-що інше. Якщо тег пропущено, ви вже маєте справу з latest. Якщо пропущено реєстр, переконайтеся, що кластер очікувано стягуватиме з публічного джерела за замовчуванням. Якщо посилання містить дайджест, розумійте, що зміна лише тегу деінде не вплине на цей Pod, доки не зміниться значення дайджесту.

Зробіть паузу та передбачте: якщо два Pod’и використовують nginx:1.21.0, але один Pod також закріплює дайджест, який не належить цьому тегу, якому посиланню, на вашу думку, довірятиме середовище виконання? Дайджест є сильнішим селектором вмісту, тож невідповідність має викликати у вас підозру щодо посилання на маніфест ще до того, як ви звинуватите планування або код застосунку.

Образи контейнерів — це стандартизовані артефакти, а не специфічний для Docker фольклор. Open Container Initiative визначає формат образу, поведінку розповсюдження та очікування щодо пакунка середовища виконання, які дають змогу таким інструментам, як Docker, containerd, BuildKit, Podman, Buildah, реєстрам і Kubernetes, взаємодіяти між собою. Docker залишається поширеним інтерфейсом розробника, але робочі вузли Kubernetes зазвичай спілкуються з CRI-сумісним середовищем виконання на кшталт containerd.

Маніфест OCI-образу описує конфігурацію образу та впорядковані шари, що формують файлову систему контейнера. Поле schemaVersion залишається встановленим у 2 для сумісності з реєстрами в стилі Docker, а медіатип OCI для маніфесту одноплатформного образу — application/vnd.oci.image.manifest.v1+json. Саме цей маніфест зазвичай ідентифікує дайджест, коли ви закріплюєте образ за sha256.

Шари — це доступні лише для читання зміни файлової системи, складені у стос за порядком. Запущений контейнер отримує додатковий тонкий шар із можливістю запису, а поведінка «копіювання під час запису» (copy-on-write) означає, що середовище виконання копіює файл у цей шар із можливістю запису лише тоді, коли контейнер його змінює. Саме тому багато Pod’ів на одному вузлі можуть спільно використовувати ті самі шари базового образу, не дублюючи всю файлову систему для кожного контейнера.

Модель шарів пояснює і продуктивність, і несподіванки. Вона ефективна, бо однакові шари можна повторно використовувати між образами та контейнерами, але це також означає, що погано впорядкований Dockerfile може анулювати дорогі кешовані шари щоразу, коли змінюється файл вихідного коду. Оптимізуючи Dockerfile, ви насправді впорядковуєте зміни файлової системи так, щоб стабільна робота залишалася у стабільних шарах, а мінлива робота відбувалася пізніше.

Медіатипи шарів OCI описують, як ці tar-архіви шарів представлено під час передавання. До поширених медіатипів належать application/vnd.oci.image.layer.v1.tar, application/vnd.oci.image.layer.v1.tar+gzip та application/vnd.oci.image.layer.v1.tar+zstd. Варіант zstd має значення в сучасних реєстрах, бо краще стиснення може зменшити пропускну здатність і пришвидшити стягування, особливо для великих парків машин або частих розгортань.

Мультиархітектурні образи додають ще один рівень. Замість одного маніфесту OCI-індекс образу використовує медіатип application/vnd.oci.image.index.v1+json і вказує на платформозалежні маніфести. Коли вузол AMD64 і вузол ARM64 стягують те саме логічне посилання на образ, середовище виконання може вибрати вкладений маніфест, що відповідає архітектурі вузла, завдяки чому специфікація Pod’а залишається переносною в змішаних кластерах.

Це має значення, бо Kubernetes планує Pod’и до того, як відбувається стягування образу. Планувальник може розмістити Pod на вузлі, що задовольняє обмеження Pod’а, а потім kubelet просить середовище виконання отримати образ для цього вузла. Якщо реєстр має лише маніфест AMD64, а Pod потрапляє на ARM64, збій з’являється під час розв’язання образу, навіть якщо YAML виглядав синтаксично коректним.

OCI Distribution Specification керує API реєстру, що використовується для переміщення цих артефактів. Версія 1.1 додала форму Referrers API за адресою GET /v2/<name>/referrers/<digest>, яка дає змогу реєстрам виявляти артефакти, що посилаються на дайджест цільового образу. Це важливо для метаданих ланцюга постачання, бо підписи, SBOM-и та атестації можуть вказувати на образ, не змінюючи сам маніфест образу.

Під час виконання OCI Runtime Specification описує, як розпакований пакунок запускається з просторами імен, cgroups, монтуваннями, налаштуваннями процесу та платформозалежною конфігурацією. Користувачі Kubernetes зазвичай не пишуть пакунки середовища виконання вручну, але концепція корисна: стягування образу та виконання контейнера — це окремі фази. Pod може дати збій, бо образ не вдалося отримати, або образ може успішно стягнутися, а потім дати збій, бо налаштований процес не може запуститися.

Версії сучасних інструментів змінюються швидко, тож уникайте перетворення номерів релізів на постійні припущення дизайну. Станом на поточне оновлення модуля представницькими актуальними релізами є OCI Image Spec 1.1.1, OCI Distribution Spec 1.1.1, OCI Runtime Spec 1.3.0, BuildKit 0.29.0, Docker Buildx 0.33.0, Buildah 1.43.0, Podman 5.8.2 та Cosign 3.0.6. У реальній платформній роботі закріплюйте версію інструмента в CI та перевіряйте примітки до релізу, перш ніж змінювати збирачі або політику підписування.

Dockerfile формує поведінку Kubernetes під час виконання

Розділ «Dockerfile формує поведінку Kubernetes під час виконання»

Dockerfile — це рецепт збирання, але кілька його рішень переходять у поведінку Kubernetes під час виконання. Базовий образ визначає початкову файлову систему, COPY і RUN створюють шари, USER впливає на стандартну ідентичність процесу, а ENTRYPOINT разом із CMD визначають модель процесу. Kubernetes може перевизначити частини цієї моделі, проте найчистіші специфікації Pod’ів зазвичай покладаються на образ, який уже має розумні значення за замовчуванням.

Щоб скористатися сучасною поведінкою Dockerfile, рекомендованою фіксацією фронтенду для стабільного синтаксису Dockerfile 1.x є docker/dockerfile:1. Ви часто бачитимете цю директиву в робочих Dockerfile, бо вона дає змогу збирачам явно вибирати парсер і набір функцій. Простий Dockerfile нижче навмисно невибагливий, але він уже демонструє патерн з урахуванням кешу: копіювати метадані залежностей до того, як копіювати решту застосунку.

# Базовий образ
FROM python:3.9-slim
# Встановити робочий каталог
WORKDIR /app
# Спочатку копіюємо requirements (кешування шарів)
COPY requirements.txt .
RUN pip install -r requirements.txt
# Копіюємо код застосунку
COPY . .
# Оголошуємо порт (документація)
EXPOSE 8080
# Команда запуску
CMD ["python", "app.py"]

Кожна інструкція або змінює файлову систему, або встановлює метадані образу, або записує значення за замовчуванням для запуску контейнера. EXPOSE, наприклад, документує призначені порти, але не публікує їх у Kubernetes; для мережі кластера все одно потрібне поле порту Service або Pod. CMD записує стандартний вектор аргументів, тоді як ENTRYPOINT записує виконуваний файл, який зазвичай має залишатися стабільним.

ІнструкціяПризначенняПриклад
FROMБазовий образFROM nginx:alpine
WORKDIRВстановити робочий каталогWORKDIR /app
COPYКопіювати файли з контексту збиранняCOPY src/ /app/
RUNВиконати команду під час збиранняRUN apt-get update
ENVВстановити змінну середовищаENV PORT=8080
EXPOSEДокументувати порт (не публікує)EXPOSE 8080
CMDКоманда запуску за замовчуваннямCMD ["nginx", "-g", "daemon off;"]
ENTRYPOINTОсновний виконуваний файлENTRYPOINT ["python"]

Найпоширеніша плутанина під час виконання — це зіставлення термінології Docker і термінології Kubernetes. ENTRYPOINT у Dockerfile зіставляється з command у Kubernetes, а CMD у Dockerfile зіставляється з args у Kubernetes. Назви невдалі, бо command звучить так, ніби має зіставлятися з CMD, але це не так. Запам’ятайте це як «спочатку виконуваний файл, потім аргументи за замовчуванням».

Зробіть паузу та передбачте: у специфікації Pod’а Kubernetes command перевизначає одну інструкцію Dockerfile, а args перевизначає іншу. Якщо образ використовує ENTRYPOINT ["python"] та CMD ["app.py"], яке поле ви змінили б, щоб запустити python test.py, не змінюючи виконуваного файлу?

# CMD: легко перевизначити
FROM nginx
CMD ["nginx", "-g", "daemon off;"]
# Можна запустити: docker run myimage sleep 10 (замінює CMD)
# ENTRYPOINT: важко перевизначити
FROM python
ENTRYPOINT ["python"]
CMD ["app.py"]
# Запускає: python app.py
# Можна запустити: docker run myimage script.py (замінює лише CMD)

Для наведеного вище передбачення чисте перевизначення — це args: ["test.py"]. Ви зберігаєте ENTRYPOINT образу як виконуваний файл і замінюєте лише аргумент за замовчуванням. Якщо ви встановите command: ["test.py"], kubelet попросить середовище виконання виконати test.py напряму, що дасть збій, якщо цей файл не є виконуваним і доступним у шляху процесу.

spec:
containers:
- name: app
image: python:3.9
command: ["python"] # Перевизначає ENTRYPOINT
args: ["myapp.py"] # Перевизначає CMD

Та сама відмінність допомагає під час налагодження Pod’ів, що завершуються негайно. Якщо kubectl describe pod показує, що образ успішно стягнуто, але контейнер завершується з помилкою виконуваного файлу, перевірте command та args, перш ніж перезбирати образ. Перевизначення на рівні Pod’а може випадково обійти призначений entrypoint образу навіть тоді, коли сам Dockerfile коректний.

Збирання та надсилання образів не є центральними завданнями іспиту CKAD, але цикл збирання пояснює, чому стан реєстру та стан Pod’а іноді не збігаються. Розробник може зібрати myapp:v1.0.0 локально й забути надіслати його або надіслати за іншим шляхом реєстру, ніж посилається Деплоймент. Kubernetes ніколи не бачить локальне збирання, доки середовище виконання на вузлі не зможе стягнути те саме посилання.

Terminal window
# Зібрати в поточному каталозі
docker build -t myapp:v1.0.0 .
# Зібрати з конкретним Dockerfile
docker build -t myapp:v1.0.0 -f Dockerfile.prod .
# Зібрати з аргументами збирання
docker build --build-arg VERSION=1.0.0 -t myapp:v1.0.0 .

Тегування та надсилання — це окремі операції, і ця окремість створює корисну діагностичну звичку. Якщо Pod посилається на myregistry.com/team/myapp:v1.0.0, підтвердьте, що саме цей тег існує саме за цим шляхом репозиторію. Успішний локальний вивід docker images не доводить, що вузол кластера може автентифікуватися в реєстрі або отримати цей репозиторій.

Terminal window
# Перепризначити тег наявному образу
docker tag myapp:v1.0.0 myregistry.com/team/myapp:v1.0.0
# Надіслати до реєстру
docker push myregistry.com/team/myapp:v1.0.0
# Надіслати всі теги
docker push --all-tags myregistry.com/team/myapp

Багатоетапні збирання (multi-stage builds), запроваджені в Docker Engine 17.05, — це звичний спосіб відокремити інструменти етапу збирання від вмісту під час виконання. Компілюйте на одному етапі, копіюйте кінцевий артефакт у менший образ і залишайте компілятори, кеші пакунків та тестові фікстури позаду. Навіть якщо ви не пишете повний Dockerfile під час практики CKAD, розпізнавання цього патерну допомагає оцінити розмір образу та поверхню атаки.

Політика стягування, облікові дані реєстру та поведінка кешу на вузлі

Розділ «Політика стягування, облікові дані реєстру та поведінка кешу на вузлі»

Після того як планувальник призначає Pod вузлу, kubelet просить середовище виконання контейнерів переконатися, що образ доступний. Середовище виконання може повторно використати локальний образ, звернутися до реєстру або дати збій до запуску процесу контейнера. imagePullPolicy керує цим рішенням щодо кешу, і значення за замовчуванням залежить від форми тегу в посиланні на образ.

spec:
containers:
- name: app
image: nginx:1.21.0
imagePullPolicy: Always # IfNotPresent | Never | Always

Три політики невеликі, але їхні експлуатаційні наслідки великі. Always просить середовище виконання перевіряти реєстр щоразу під час запуску контейнера, що корисно для навмисно мінливих тегів, але дорого для стабільних версіонованих образів. IfNotPresent використовує локальний кеш, коли він доступний, що зазвичай правильно для незмінних тегів версій. Never відмовляється стягувати й має бути зарезервована для навмисних локальних або ізольованих (air-gapped) робочих процесів.

ПолітикаПоведінкаКоли використовувати
AlwaysСтягувати щоразуВикористання тегу latest, потрібен найсвіжіший образ
IfNotPresentСтягувати лише за відсутності в кешіКонкретні теги, економія пропускної здатності
NeverНіколи не стягувати, використовувати кешованийЛокальна розробка, ізольоване середовище

Kubernetes вибирає значення за замовчуванням, щоб зменшити несподіванки для типових випадків. Якщо образ не має тегу або явно використовує :latest, політика за замовчуванням — Always. Якщо образ використовує конкретний тег або дайджест, політика за замовчуванням — IfNotPresent. Ці значення за замовчуванням розумні, але явні політики легше переглядати в маніфестах, які підтримуватимуть кілька інженерів.

Тег образуПолітика за замовчуванням
Без тегу (мається на увазі :latest)Always
:latestAlways
Конкретний тег (:v1.0.0)IfNotPresent
Дайджест (@sha256:...)IfNotPresent

Поведінка кешу локальна для вузла, а не глобальна для кластера. Якщо один робочий вузол уже стягнув myapp:v2.1.0, це не допомагає Pod’у, запланованому на іншому робочому вузлі, доки той другий вузол теж не матиме того самого вмісту образу. Саме тому IfNotPresent безпечна для стабільних тегів, але не є замінником реєстру, наскрізного кешу (pull-through cache) або стратегії попереднього стягування в середовищах, де нові вузли з’являються під час масштабування.

Кеш також не є гарантією коректності. Збирач сміття образів kubelet (image garbage collection) може видаляти невикористані образи, коли тиск на диск перевищує налаштовані пороги, а нещодавно заміщений вузол починає з порожнім локальним сховищем образів. Проєктуйте розгортання так, щоб відсутність кешу спричиняла звичайне стягування з реєстру, а не збій запуску. Якщо робоче навантаження потребує офлайн-запуску, це особливий режим роботи, який слід окремо задокументувати й протестувати.

ImagePullBackOff містить підказку щодо часу, а не лише щодо статусу. Kubernetes не повторює невдале стягування в тісному циклі нескінченно; після повторюваних невдач він робить паузи між спробами. Це захищає реєстр і вузол, але також означає, що виправлений Secret чи тег може не з’явитися в статусі Pod’а миттєво. Читання подій (Events) підкаже, чи остання спроба використала виправлену інформацію, чи Pod усе ще очікує на наступну спробу стягування.

Одна практична звичка — порівнювати поле образу контролера з полем образу найновішого Pod’а після кожного виправлення. Якщо ви виправите лише Pod, яким володіє Деплоймент, контролер може відтворити старий шаблон під час наступної заміни. Виправте контролер, а потім переконайтеся, що новий ReplicaSet або шаблон Pod’а несе виправлене посилання на образ.

Перш ніж це запустити, який вивід ви очікуєте, якщо Pod використовує image: nginx і не має явної imagePullPolicy? Ви маєте очікувати, що Kubernetes трактуватиме образ як nginx:latest і встановить політику стягування Always, бо мінливий тег за замовчуванням слід перевіряти, а не довіряти йому зі застарілого кешу на вузлі.

Приватні реєстри додають межу автентифікації. Ваш ноутбук може бути авторизований у реєстрі через Docker, Podman або хмарний CLI, але робочі вузли Kubernetes не успадковують цей вхід. Pod’у потрібні облікові дані реєстру через imagePullSecrets або потрібен ServiceAccount, що посилається на ці облікові дані, щоб Pod’и, які використовують цей акаунт, могли стягувати приватні образи.

Terminal window
# Створити docker-registry secret
kubectl create secret docker-registry regcred \
--docker-server=myregistry.com \
--docker-username=user \
--docker-password=your-password-here \
--docker-email=user@example.com

Тип Secret docker-registry зберігає облікові дані у форматі, який Kubernetes очікує для стягування образів. У продакшені уникайте розміщення реальних облікових даних в історії командного рядка або спільних документах; використовуйте схвалений вашою командою процес керування секретами. У лабораторній у стилі CKAD команда демонструє форму об’єкта, і важлива частина полягає в тому, що Pod посилається на Secret за іменем.

apiVersion: v1
kind: Pod
metadata:
name: private-app
spec:
containers:
- name: app
image: myregistry.com/team/myapp:v1.0.0
imagePullSecrets:
- name: regcred

Приєднання imagePullSecrets напряму до кожного Pod’а працює, але стає повторюваним, щойно простір імен містить кілька робочих навантажень. Чистіший патерн — приєднати Secret для стягування до ServiceAccount, а потім встановити serviceAccountName на Pod’ах, які мають успадкувати доступ до реєстру. Це тримає політику облікових даних образу поруч із політикою ідентичності робочого навантаження.

apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
imagePullSecrets:
- name: regcred
apiVersion: v1
kind: Pod
metadata:
name: private-app
spec:
serviceAccountName: myapp-sa
containers:
- name: app
image: myregistry.com/team/myapp:v1.0.0

Для діагностики трактуйте облікові дані реєстру як вимоги на боці вузла. Якщо події кажуть unauthorized, authentication required або pull access denied, не витрачайте перші хвилини на переписування Деплойменту. Перевірте, чи Pod або ServiceAccount посилається на Secret, чи Secret у тому самому просторі імен і чи значення сервера реєстру збігається з хостом посилання на образ.

Обмеження частоти запитів Docker Hub — ще одна причина віддавати перевагу явній стратегії реєстру. Публічні базові образи зручні, але неавтентифіковані стягування можуть обмежуватися за швидкістю, а великі події автомасштабування можуть змусити багато вузлів запитувати ті самі базові шари одночасно. Наскрізні кеші, приватні дзеркала та автентифікований доступ до реєстру зменшують цю залежність, зберігаючи передбачуваність специфікацій Pod’ів.

Безпека й усунення проблем починаються у специфікації Pod’а

Розділ «Безпека й усунення проблем починаються у специфікації Pod’а»

Безпека образу починається до контролю допуску чи сканування під час виконання. Якщо ви вибираєте великий базовий образ, запускаєте від root і залишаєте кореневу файлову систему доступною для запису, Kubernetes усе одно зможе запустити Pod, але ви постачили зайві інструменти й привілеї в кожну репліку. Добра гігієна образу прибирає непотрібні файли, звужує стандартну ідентичність процесу й змушує специфікацію Pod’а забезпечувати ті самі припущення.

# ПОГАНО
image: nginx:latest
# ДОБРЕ
image: nginx:1.21.0-alpine

Мінімальні базові образи зменшують розмір і вразливість до загроз, але вони також змінюють ергономіку налагодження. Образи на основі Alpine малі, образи Debian slim часто зручніші для рантаймів мов програмування, а образи scratch взагалі не містять ні оболонки, ні менеджера пакунків. Цей компроміс нормальний: робочі образи продакшену не слід трактувати як універсальні середовища для ремонту.

# 133 МБ
FROM python:3.9
# 45 МБ - значно менший
FROM python:3.9-slim
# 17 МБ - ще менший
FROM python:3.9-alpine

Офіційний образ Alpine Linux історично займав лише кілька мегабайтів, а гілка релізу 3.23 актуальна в часовому проміжку цього модуля. Малий не означає автоматично безпечніший, бо вибір пакунків, ритм оновлень і обробка вразливостей усе одно мають значення. Трактуйте вибір базового образу як інженерне рішення з наслідками для сумісності, підтримки та сканування.

FROM scratch — це крайній мінімальний базовий образ. Він дає вам порожню файлову систему, що чудово підходить для статично злінкованих двійкових файлів, яким не потрібні інструменти оболонки, сертифікати понад ті, що ви копіюєте, чи файли менеджера пакунків. Він погано підходить для застосунків, які очікують на динамічні бібліотеки, дані часових поясів, набори сертифікатів чи скрипти оболонки, доки ви навмисно не додасте ці ресурси.

Образи на основі Distroless і Wolfi розташовані між повними дистрибутивами та scratch. Вони прагнуть прибрати менеджери пакунків та оболонки, зберігаючи достатньо файлів середовища виконання для поширених мов. Оцінюйте конкретний проєкт, канал оновлень, підтримку SBOM та модель підписування, а не припускайте, що всі мінімальні образи поводяться однаково. Найкращий базовий образ — це найменший, який усе ще підтримує ваш контракт середовища виконання.

Запуск без прав root має бути частиною цього контракту. Dockerfile може оголосити користувача без прав root, а специфікація Pod’а може забезпечити, щоб контейнер не запускався з UID нуль. Коли обидва рівні узгоджені, ви зменшуєте привілеї всередині контейнера й полегшуєте виявлення випадкового виконання від root під час розгортання.

FROM python:3.9-slim
RUN useradd -m appuser
USER appuser
COPY --chown=appuser:appuser . /app

securityContext у Kubernetes перетворює намір образу на політику кластера на рівні Pod’а або контейнера. runAsNonRoot: true спричиняє збій запуску, якщо образ або перевизначення запустилися б від root, тоді як runAsUser надає числовий UID. Числові користувачі легше забезпечувати середовищам виконання, ніж імена, бо файлова система образу може містити або не містити файли бази даних користувачів.

spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: app
image: myapp:v1.0.0

Коренева файлова система лише для читання — це ще один потужний запобіжник. Вона змушує застосунок писати лише в явно змонтовані розташування, як-от /tmp, підкріплений emptyDir. Це швидко викриває застосунки, які тихо записують кеші, файли блокування чи згенеровану конфігурацію у файлову систему образу замість використання оголошеного сховища.

spec:
containers:
- name: app
image: myapp:v1.0.0
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}

Безпека ланцюга постачання додає докази навколо образу. Cosign може підписувати образи, а сучасні реєстри можуть зберігати підписи та пов’язані артефакти як OCI-реферери, з’єднані з цільовим дайджестом. Це має найбільше значення, коли політика допуску або автоматизація релізів перевіряє, що точний дайджест має очікуваний підпис, SBOM чи запис походження, перш ніж дозволити робочому навантаженню запуститися.

Усунення проблем починається з відокремлення отримання образу від виконання процесу. Якщо статус Pod’а — Pending, ErrImagePull чи ImagePullBackOff, перевірте події та поля образу. Якщо образ стягується, а контейнер входить у CrashLoopBackOff, переходьте до логів, аргументів команди, проб (probes) і поведінки застосунку. Змішування цих шляхів марнує час, бо фази збою різні.

Зробіть паузу та передбачте: Pod посилається на образ із приватного реєстру, але не має imagePullSecrets. Образ існує, і тег коректний. Якої помилки ви очікуєте в подіях і чим це відрізнятиметься від помилки в тегу? Помилки автентифікації зазвичай згадують авторизацію, тоді як відсутні теги зазвичай згадують пошук маніфесту або помилки «не знайдено».

ПомилкаПричинаРозв’язання
ImagePullBackOffНе вдається стягнути образПеревірте ім’я образу, доступ до реєстру
ErrImagePullСтягування не вдалосяПеревірте, чи образ існує, перевірте облікові дані
InvalidImageNameНекоректне посилання на образВиправте формат імені образу
ImageInspectErrorІнспекція образу не вдаласяПеревірте маніфест образу

Найшвидша команда — зазвичай kubectl describe pod, бо події містять повідомлення від kubelet і середовища виконання. Потім перевірте рядок образу саме так, як його бачить Kubernetes, перевірте, чи Secret для стягування існує в просторі імен, і відтворюйте стягування з належно автентифікованого середовища лише після прочитання помилки на боці кластера. Стягування з ноутбука мало що доводить, якщо кластер використовує інші облікові дані.

Terminal window
# Перевірити події Pod'а
kubectl describe pod myapp | grep -A10 Events
# Перевірити ім'я образу
kubectl get pod myapp -o jsonpath='{.spec.containers[0].image}'
# Переконатися, що Secret існує
kubectl get secret regcred
# Протестувати стягування вручну (якщо доступний docker)
docker pull myregistry.com/team/myapp:v1.0.0

Наведений нижче приклад дотримується точного шляху діагностики. Pod дає збій не тому, що NGINX не може запуститися; він дає збій, бо середовище виконання не може знайти вказаний тег. Щойно посилання на образ виправлено, Kubernetes може створити новий Pod, який стягує дійсний образ і переходить до запуску контейнера.

Terminal window
# Pod застряг у ImagePullBackOff
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# myapp 0/1 ImagePullBackOff 0 5m
# Перевірити події
kubectl describe pod myapp
# Events:
# Failed to pull image "nginx:latst": rpc error: ...not found
# Знайшли: описка в тегу (latst замість latest)
# Виправлення: відредагувати pod або видалити й створити заново
kubectl delete pod myapp
kubectl run myapp --image=nginx:latest

Патерни та антипатерни

Розділ «Патерни та антипатерни»

Перший патерн — закріплювати намір на рівні, що має значення. Використовуйте повний шлях реєстру, коли робоче навантаження не має залежати від публічних значень за замовчуванням, використовуйте конкретний тег, коли процес релізу трактує теги як незмінні, і використовуйте дайджест, коли точна двійкова повторюваність обов’язкова. Це працює, бо рецензенти бачать у специфікації Pod’а призначене джерело й модель змінності.

Відповідний антипатерн — трактувати latest як канал релізів. Команди потрапляють у цю пастку, бо вона скорочує ранні демонстрації й уникає роздумів про версіонування, але вона ускладнює відкати, аудити та реконструкцію інцидентів. Краща альтернатива — тег релізу, створений CI, за бажанням у парі з дайджестом, що був просунутий крізь середовище.

Другий патерн — зробити шлях облікових даних реєстру локальним для простору імен і повторюваним. Розмістіть Secret для стягування образу в тому самому просторі імен, що й робоче навантаження, приєднайте його до ServiceAccount, який використовують пов’язані Pod’и, і тримайте хост реєстру узгодженим із посиланням на образ. Це масштабується краще, ніж копіювання посилань на Secret у кожен маніфест вручну.

Відповідний антипатерн — налагоджувати стягування приватних образів спершу з ноутбука розробника. Цей ноутбук має інше сховище облікових даних, мережевий шлях і конфігурацію реєстру, ніж середовище виконання на вузлі. Починайте з подій Pod’а та об’єктів простору імен, а потім використовуйте зовнішні стягування для підтвердження вмісту реєстру лише після того, як зрозуміли шлях автентифікації на боці кластера.

Третій патерн — впорядковувати шари Dockerfile за змінністю. Копіюйте маніфести залежностей до вихідного коду застосунку, встановлюйте залежності, доки ці маніфести стабільні, і копіюйте код, що часто змінюється, пізніше. Це працює, бо BuildKit та інші збирачі можуть повторно використовувати дорогі шари залежностей, коли змінюються лише файли застосунку.

Відповідний антипатерн — розміщення COPY . . ближче до початку Dockerfile. Це здається простим, бо контекст збирання доступний одразу, але кожне дрібне редагування вихідного коду анулює подальший кеш. Краща структура спочатку копіює лише метадані пакунків, встановлює залежності, а потім копіює решту проєкту.

Четвертий патерн — тримати образ середовища виконання меншим за середовище збирання. Багатоетапні збирання дають змогу компілятору, SDK чи менеджеру пакунків жити у тимчасовому етапі, тоді як кінцевий образ містить лише застосунок і файли середовища виконання. Це зменшує час стягування, шум сканування та кількість інструментів, доступних зловмиснику всередині контейнера.

Відповідний антипатерн — постачання повного образу для розробки у продакшен, бо його легше інспектувати. Можливість налагодження має значення, але репліки продакшену не повинні містити компілятори й кеші пакунків лише задля зручності аварійних оболонок. Використовуйте ефемерні контейнери для налагодження, спеціалізовані діагностичні образи та спостережуваність замість роздування кожного образу застосунку.

П’ятий патерн — узгоджувати стандартну поведінку процесу Dockerfile з перевизначеннями Kubernetes. Розмістіть стабільний виконуваний файл у ENTRYPOINT, розмістіть аргументи за замовчуванням у CMD і перевизначайте лише args, коли Pod’у потрібен інший режим. Це дає Kubernetes змогу налаштовувати поведінку, не замінюючи призначений запускач процесу образу.

Відповідний антипатерн — використання command у кожній специфікації Pod’а за звичкою. Перевизначення command замінює entrypoint образу й може обійти логіку налаштування, яку автор образу очікував запустити. Перш ніж змінювати command, інспектуйте Dockerfile або метадані образу й вирішіть, чи ви справді маєте намір замінити виконуваний файл.

Шостий патерн — давати збій із закритими правами під час виконання (fail closed). Збирайте образ для користувача без прав root, встановлюйте runAsNonRoot і явно монтуйте шляхи з можливістю запису, коли коренева файлова система лише для читання. Це дає застосунку чіткий контракт і перетворює випадкові записи чи припущення про root на ранні збої розгортання замість тихого дрейфу в продакшені.

Відповідний антипатерн — покладатися лише на сканер вразливостей. Сканування — корисний доказ, але воно не робить мінливий тег відтворюваним, не зупиняє процес root і не доводить, що підпис належить дайджесту, який ви розгорнули. Поєднуйте сканування із закріпленням, підписуванням, перевірками допуску та налаштуваннями безпеки Pod’а.

Рамка ухвалення рішень

Розділ «Рамка ухвалення рішень»

Почніть із фази збою. Якщо Pod не може стягнути образ, досліджуйте посилання, тег, хост реєстру, політику стягування, облікові дані та доступ вузла. Якщо образ стягується, а процес завершується, досліджуйте command, args, ідентичність користувача, шляхи з можливістю запису, логи та конфігурацію застосунку. Цей єдиний поділ запобігає більшості сесій налагодження образів від блукань.

Далі вирішіть, наскільки відтворюваним має бути робоче навантаження. Для одноразового лабораторного Pod’а коротке посилання на публічний образ прийнятне. Для спільного простору імен розробки використовуйте явний тег і чітку політику стягування. Для staging і продакшену віддавайте перевагу процесу незмінного тегу релізу й записуйте дайджест, що був просунутий, особливо коли перевірка підпису чи пошук SBOM залежить від цього дайджесту.

Потім вирішіть, наскільки свіжим має бути кеш на вузлі. Always розумна для навмисно мінливих тегів і деяких циклів розробки, але вона створює залежність від реєстру під час кожного запуску. IfNotPresent зазвичай є правильною політикою для версіонованих образів, бо уникає повторюваних завантажень, водночас завантажуючи нові вузли. Never належить лише до контрольованих середовищ, де образ попередньо завантажено.

Тепер перевірте шлях довіри до реєстру. Публічні реєстри зручні, але вони наражають вас на зовнішню доступність, обмеження швидкості та припущення щодо іменування. Приватні реєстри потребують imagePullSecrets, інтеграції ідентичності вузла або механізмів хмарного провайдера, але вони дають командам більше контролю над просуванням, дзеркалюванням, зберіганням і політикою доступу. Специфікація Pod’а має робити цей шлях реєстру очевидним.

Після цього розгляньте вміст образу. Якщо образ великий, запитайте, чи справді середовищу виконання потрібні набір інструментів збирання, кеш пакунків, оболонка та утиліти дистрибутива. Якщо застосунку потрібні динамічні бібліотеки чи сертифікати, не переходьте одразу до scratch; виберіть найменшу базу, яка все ще надає потрібні файли середовища виконання та модель підтримки.

Потім інспектуйте контракт процесу. Якщо образ використовує ENTRYPOINT ["python"] та CMD ["app.py"], args у Kubernetes може вибрати інший скрипт, не замінюючи Python. Якщо образ вбудовує поведінку налаштування в entrypoint-скрипти, заміна command може пропустити цю поведінку. Рішення полягає не в тому, чи Kubernetes може перевизначити це; рішення полягає в тому, чи перевизначення зберігає призначений контракт запуску.

Далі застосуйте обмеження часу виконання. Використовуйте виконання без прав root, коли застосунку не потрібне привілейоване володіння файловою системою, і використовуйте кореневі файлові системи лише для читання, коли каталоги з можливістю запису явні. Якщо застосунок дає збій за цих налаштувань, цей збій — корисна інформація: образ має приховані припущення, які слід задокументувати, змонтувати чи виправити до продакшену.

Нарешті вирішіть, які докази мають супроводжувати образ. Лабораторному образу з низьким ризиком може знадобитися лише читабельний тег. Образу продакшену можуть знадобитися дайджест, SBOM, сканування вразливостей, атестація походження та підпис Cosign. OCI-реферери роблять ці вкладення доступними для виявлення, не змінюючи дайджест образу, що тримає ціль перевірки стабільною.

Коли ви не впевнені, вибирайте варіант, що залишає майбутньому досліднику менше здогадок. Повністю кваліфіковані імена образів, явні політики стягування, локальні для простору імен Secret’и та записи релізів з урахуванням дайджесту не є церемоніальними. Вони зменшують кількість прихованих значень за замовчуванням між маніфестом YAML і процесом, який зрештою запускається на вузлі.

  • OCI image-spec 1.1.0 був першим мінорним релізом після лінійки 1.0.0 із липня 2017 року. Цей довгий інтервал — одна з причин, чому деталі формату образу зазвичай стабільні між інструментами, навіть поки збирачі та реєстри швидко еволюціонують. Джерело: реліз OCI Image Spec v1.1.0.
  • Docker Engine 23.0 зробив BuildKit стандартним збирачем у Linux у лютому 2023 року. Паралельне виконання та модель кешу BuildKit — це причина, чому сучасне впорядкування Dockerfile має прямий вплив на час збирання. Джерело: примітки до релізу Docker Engine 23.0.
  • Неавтентифіковані стягування з Docker Hub історично обмежувалися 100 стягуваннями за 6-годинне вікно. Це число велике для одного ноутбука й мале для кластера з автомасштабуванням, що повторно стягує поширені базові образи. Джерело: обмеження використання стягувань Docker Hub.
  • Тег latest не має хронологічного значення. Це лише стандартний рядок тегу, що використовується, коли ви пропускаєте тег, тож image: nginx означає image: nginx:latest, а не «найновіший перевірений реліз».
ПомилкаЧому це трапляєтьсяЯк виправити
Використання latest у продакшеніЦе здається зручним під час розробки, але приховує, який вміст образу насправді запускавсяЗакріпіть тег релізу й запишіть дайджест, просунутий CI
Одруки в іменах образівРеєстр, простір імен, репозиторій і тег упаковано в один рядокПрочитайте точне поле образу й порівняйте його з вмістом реєстру
Забуття imagePullSecretsВхід у реєстр на ноутбуці помилково сприймається за доступ вузла кластераДодайте Secret у той самий простір імен або приєднайте його до ServiceAccount
Вибір Never, щоб уникнути повільних стягуваньПромахи кешу помилково сприймаються за непотрібне використання мережіВикористовуйте IfNotPresent для стабільних тегів і попередньо стягуйте лише в контрольованих середовищах
Перевизначення command, коли мають змінитися лише аргументиІмена в Kubernetes відрізняються від імен у DockerfileПам’ятайте, що command зіставляється з ENTRYPOINT, а args — із CMD
Копіювання всього дерева вихідного коду до встановлення залежностейПростий Dockerfile написано до того, як враховано поведінку кешуСпочатку копіюйте маніфести залежностей, встановлюйте залежності, потім копіюйте код застосунку
Запуск від root за замовчуваннямБазові образи часто стартують із UID нуль, доки це не зміненоВстановіть користувача без прав root в образі й забезпечте runAsNonRoot у специфікації Pod’а
Трактування результатів сканування як усієї історії ланцюга постачанняЗвіти про вразливості не доводять ідентичність, походження чи незмінністьПоєднуйте сканування із закріпленням дайджесту, підписами, SBOM-ами та політикою допуску
1. Ваш Деплоймент використовує `image: myapp`, і розробник каже, що надіслав новий образ, але перезапущений Pod усе одно поводиться як старий застосунок. Що ви перевіряєте першим? Почніть із розгортання неявного посилання: `myapp` означає `myapp:latest`, зазвичай із простору імен реєстру за замовчуванням. Перевірте точне поле образу Pod'а, репозиторій реєстру, куди надсилали, і чи надісланий тег справді є `latest`. Глибше виправлення — припинити покладатися на мінливе значення за замовчуванням і розгортати явний тег релізу або дайджест, щоб розгортання називало призначений вміст.
2. Pod застряг у `ImagePullBackOff`, а образ стягується з вашого ноутбука. Як ви діагностуєте причину на боці кластера? Використовуйте `kubectl describe pod` і читайте події, перш ніж змінювати маніфест. Якщо події згадують `unauthorized` чи `authentication required`, інспектуйте `imagePullSecrets`, конфігурацію ServiceAccount, розміщення в просторі імен та хост реєстру в Secret'і. Якщо події згадують `manifest unknown` чи `not found`, інспектуйте шлях репозиторію й тег. Стягування з ноутбука лише доводить, що ваш ноутбук має доступ, а не те, що середовище виконання на вузлі має ті самі облікові дані.
3. Образ має `ENTRYPOINT ["python"]` та `CMD ["app.py"]`. У Kubernetes вам потрібно запустити `python test.py` для одного Pod'а. Яке поле слід перевизначити? Перевизначте `args` на `["test.py"]` і залиште `command` невстановленим. `command` у Kubernetes замінює `ENTRYPOINT` у Dockerfile, тоді як `args` у Kubernetes замінює `CMD` у Dockerfile. Збереження entrypoint зберігає призначений виконуваний файл, а зміна аргументів вибирає інший скрипт. Заміна `command` на `["test.py"]` намагалася б виконати скрипт напряму.
4. Ваша команда хоче швидших перезапусків для версіонованих образів на кшталт `myapp:v2.1.0`, і хтось пропонує `imagePullPolicy: Never`. Чому це небезпечно? `Never` робить Pod залежним від попередньо завантаженого образу на кожному вузлі. Це може спрацювати на одному вузлі й одразу дати збій, коли Деплоймент масштабується на новий вузол, під час заміни вузла чи після аварійного відновлення. Для стабільних тегів `IfNotPresent` дає вигоду кешу, водночас дозволяючи вузлу стягнути образ, коли його бракує. Якщо стягування все ще повільні, дослідіть кешування реєстру чи дзеркала, а не повне вимкнення стягувань.
5. Dockerfile починається з `FROM ubuntu:latest`, виконує `COPY . .`, а потім встановлює залежності. Збирання повільні, а образ великий. Які дві зміни дають найбільше покращення? По-перше, перемістіть метадані залежностей на кшталт `requirements.txt` чи `package.json` перед повним копіюванням вихідного коду, потім встановлюйте залежності з цього стабільного шару. Це зберігає повторне використання кешу, коли змінюється код застосунку. По-друге, виберіть менший підтримуваний базовий образ середовища виконання — slim, Alpine, distroless, на основі Wolfi чи кінцевий образ багатоетапного збирання, коли він сумісний. Ці зміни зменшують роботу з перезбирання й прибирають непотрібні файли середовища виконання.
6. Змішаний кластер ARM64 та AMD64 стягує те саме ім'я образу на обох типах вузлів без специфічних для архітектури тегів. Який механізм OCI робить це можливим? Реєстр обслуговує OCI-індекс образу, також званий мультиплатформним списком маніфестів у термінології Docker. Індекс вказує на платформозалежні маніфести, а середовище виконання на вузлі вибирає маніфест, що відповідає архітектурі та операційній системі вузла. Це дає змогу одному логічному посиланню на образ працювати між типами вузлів, доки реєстр містить сумісний маніфест для кожної запланованої платформи.
7. Політика безпеки вимагає SBOM та підпис Cosign для точного дайджесту образу, але команда релізу не хоче змінювати маніфест образу. Яка функція реєстру допомагає? OCI-реферери дають змогу підписам, SBOM-ам та атестаціям вказувати на цільовий дайджест як на пов'язані артефакти. Дайджест цільового образу залишається стабільним, бо метадані посилаються на образ, а не вставляються в маніфест образу. Це корисно для контролерів допуску та автоматизації релізів, які перевіряють докази для точного розгорнутого дайджесту.
8. Pod успішно стягується, але завершується з помилкою виконуваного файлу після того, як хтось додав `command: ["worker"]`. Куди ви дивитеся? Інспектуйте оригінальні `ENTRYPOINT` та `CMD` образу, потім порівняйте їх із `command` та `args` Pod'а. Новий `command` замінив entrypoint образу, тож він міг пропустити скрипт-запускач чи спробувати виконати двійковий файл, якого немає в шляху. Якщо має змінитися лише режим worker, відновіть entrypoint і перевизначте натомість `args`. Якщо виконуваний файл справді має змінитися, переконайтеся, що він існує в образі й має очікувані права доступу.

Сценарій вправи: вас попросили дослідити зламане розгортання на staging-середовищі, а потім посилити налаштування, пов’язані з образами. Виконуйте завдання по порядку, бо кожне з них ізолює іншу частину життєвого циклу образу: розбір посилання, діагностику збою стягування, налаштування приватного реєстру, перевизначення команд, перевірку політики стягування та оптимізацію Dockerfile.

Використовуйте одноразовий простір імен, якщо політика вашого кластера цього вимагає, і прибирайте кожен об’єкт після завершення. Команди припускають, що у вас налаштовано kubectl для кластера Kubernetes 1.35+ чи сумісного локального середовища. Якщо ваше середовище блокує публічні стягування, читайте команди й очікувані події як діагностичну вправу, а не змушуйте до винятку в політиці.

Завдання 1: Налаштувати зламане середовище

Terminal window
# Створити деплоймент із навмисними проблемами образу
kubectl create deploy broken-app --image=nginx:nonexistent

Завдання 2: Діагностувати збій

Спостерігайте за станом деплойменту, щоб визначити точну причину збою стягування образу. Ключова навичка — не просто побачити ImagePullBackOff; це читати події, доки ви не зможете пояснити, яка частина посилання на образ дала збій і яка зміна дала б вузлу змогу стягнути образ успішно.

Terminal window
# Перевірити статус Pod'ів
kubectl get pods
# Показує ImagePullBackOff
# Отримати деталі
kubectl describe pod -l app=broken-app | grep -A5 Events
# Показує: nginx:nonexistent not found
# Виправити, пропатчивши деплоймент
kubectl set image deploy/broken-app nginx=nginx:1.21.0
# Перевірити
kubectl get pods
# Має показати Running
# Прибрати
kubectl delete deploy broken-app

Завдання 3: Розібрати імена образів

Перш ніж змінювати маніфести, попрактикуйтеся перекладати посилання на їхні компоненти: реєстр, простір імен, образ і тег. Це ментальна модель, яку ви використовуєте, коли події згадують not found, бо відсутньою частиною може бути шлях репозиторію, а не кінцевий тег.

1. nginx
Registry: docker.io (за замовчуванням)
Namespace: library (за замовчуванням)
Image: nginx
Tag: latest (за замовчуванням)
2. gcr.io/google-containers/pause:3.2
Registry: gcr.io
Namespace: google-containers
Image: pause
Tag: 3.2
3. mycompany.com/team/app:v2.0.0-alpine
Registry: mycompany.com
Namespace: team
Image: app
Tag: v2.0.0-alpine

Завдання 4: Виправити ще один збій стягування образу

Ця вправа повторює ту саму діагностику з Pod’ом замість Деплойменту. Зверніть увагу, що видалення й повторне створення окремого Pod’а розумне в лабораторній, тоді як кероване робоче навантаження зазвичай слід виправляти, патчачи контролер, щоб Pod’и-замінники успадковували виправлений образ.

Terminal window
# Створити зламаний pod
kubectl run broken --image=nginx:1.999.0
# Діагностувати
kubectl describe pod broken | grep -A5 Events
# Виправити
kubectl delete pod broken
kubectl run broken --image=nginx:1.21.0
# Перевірити
kubectl get pod broken
# Прибрати
kubectl delete pod broken

Завдання 5: Налаштувати Secret приватного реєстру

Це завдання зосереджується на формі об’єкта, а не на реальному приватному реєстрі. У реальному середовищі замініть приклад сервера й облікові дані схваленими значеннями зі свого процесу керування секретами та пам’ятайте, що Secret має бути в тому самому просторі імен, що й Pod, який на нього посилається.

Terminal window
# Створити registry secret
kubectl create secret docker-registry myregistry \
--docker-server=private.registry.io \
--docker-username=testuser \
--docker-password=your-password-here
# Створити pod із посиланням на secret
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: private-pod
spec:
containers:
- name: app
image: private.registry.io/app:latest
imagePullSecrets:
- name: myregistry
EOF
# Перевірити, чи secret посилається
kubectl get pod private-pod -o jsonpath='{.spec.imagePullSecrets}'
# Прибрати
kubectl delete pod private-pod
kubectl delete secret myregistry

Завдання 6: Перевизначити command і args

Використовуйте це завдання, щоб підтвердити зіставлення ENTRYPOINT/CMD у Dockerfile з command/args у Kubernetes у живих специфікаціях Pod’а. BusyBox постачається зі стандартним CMD ["sh"] і без фіксованого ENTRYPOINT, тож Pod лише з args замінює лише стандартну команду, залишаючи entrypoint образу невстановленим. Другий Pod встановлює обидва поля, щоб показати, коли ви повністю замінюєте запускач процесу образу.

Terminal window
# Частина A: перевизначення лише args — `args` у Kubernetes зіставляється з `CMD` у Dockerfile
# Залиште `command` невстановленим, щоб entrypoint образу (якщо є) залишився на місці.
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: args-only-cmd
spec:
containers:
- name: busybox
image: busybox
args: ["echo", "Args-only override replaces image CMD"]
EOF
kubectl logs args-only-cmd
kubectl get pod args-only-cmd -o jsonpath='{.spec.containers[0].command}{"\n"}'
kubectl get pod args-only-cmd -o jsonpath='{.spec.containers[0].args}{"\n"}'
# Частина B: command+args — замінює ENTRYPOINT і CMD у Dockerfile
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: command-and-args
spec:
containers:
- name: busybox
image: busybox
command: ["sh", "-c"]
args: ["echo 'command replaces ENTRYPOINT; args replace CMD' && sleep 5"]
EOF
kubectl logs command-and-args
kubectl get pod command-and-args -o jsonpath='{.spec.containers[0].command}{"\n"}'
kubectl get pod command-and-args -o jsonpath='{.spec.containers[0].args}{"\n"}'
# Прибрати
kubectl delete pod args-only-cmd command-and-args

Завдання 7: Порівняти політики стягування

Створіть два Pod’и з тим самим образом і різними політиками стягування, потім інспектуйте збережені значення політик. Суть не в тому, щоб порівняти швидкість реєстру; це виробити звичку робити поведінку кешу явною, коли робоче навантаження має чіткі вимоги до відтворюваності чи свіжості.

Terminal window
# Створити pod'и з різними політиками
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: pull-always
spec:
containers:
- name: nginx
image: nginx:1.21.0
imagePullPolicy: Always
---
apiVersion: v1
kind: Pod
metadata:
name: pull-ifnotpresent
spec:
containers:
- name: nginx
image: nginx:1.21.0
imagePullPolicy: IfNotPresent
EOF
# Перевірити політики
kubectl get pod pull-always -o jsonpath='{.spec.containers[0].imagePullPolicy}'
kubectl get pod pull-ifnotpresent -o jsonpath='{.spec.containers[0].imagePullPolicy}'
# Прибрати
kubectl delete pod pull-always pull-ifnotpresent

Завдання 8: Повне усунення проблем з образом

Цей сценарій поєднує статус контролера, вибір Pod’а, інспекцію подій, виправлення образу та перевірку розгортання. Використовуйте його як вправу на час у стилі CKAD: визначте збійне посилання на образ, змініть контролер і доведіть, що Pod-замінник досягає очікуваного стану.

Terminal window
# Налаштування (симуляція проблеми)
kubectl create deploy webapp --image=nginx:alpine-wrong-tag
# ВАШЕ ЗАВДАННЯ: Знайти та виправити проблему
# Крок 1: Перевірити статус деплойменту
kubectl get deploy webapp
kubectl get pods -l app=webapp
# Крок 2: Дослідити помилку
kubectl describe pods -l app=webapp | grep -A10 Events
# Крок 3: Знайти правильний тег образу
# (У реальному сценарії перевірте реєстр чи документацію)
# Правильний тег — nginx:alpine
# Крок 4: Виправити
kubectl set image deploy/webapp nginx=nginx:alpine
# Крок 5: Перевірити
kubectl rollout status deploy/webapp
kubectl get pods -l app=webapp
# Прибрати
kubectl delete deploy webapp

Завдання 9: Оптимізувати Dockerfile

Це останнє завдання повертається від діагностики кластера до конструювання образу. Оригінальний Dockerfile працює, але поєднує велику базу з поганим упорядкуванням кешу. Ваше переписування має зробити шар залежностей стабільним і використати меншу базу середовища виконання, коли застосунок сумісний.

FROM node:18
WORKDIR /usr/src/app
COPY . .
RUN npm install
CMD ["node", "index.js"]

Ваші завдання — визначити проблему кешування шарів, що спричиняє повільні перезбирання, визначити проблему розміру базового образу та переписати Dockerfile для його оптимізації.

# 1. Перейти на менший базовий образ (alpine)
FROM node:18-alpine
WORKDIR /usr/src/app
# 2. Спочатку копіювати ЛИШЕ файли package для кешування шарів
COPY package*.json ./
RUN npm install
# 3. Копіювати решту коду застосунку ПІСЛЯ залежностей
COPY . .
CMD ["node", "index.js"]

Критерії успіху:

  • Діагностовано ImagePullBackOff шляхом читання подій Pod’а та визначення збійного посилання на образ.
  • Спроєктовано виправлене посилання на образ і політику стягування, що дають змогу робочому навантаженню передбачувано запускатися.
  • Підтверджено, що Pod із приватним реєстром посилається на очікуваний запис imagePullSecrets.
  • Порівняно поведінку command та args у Kubernetes із ENTRYPOINT та CMD у Dockerfile у живому Pod’і.
  • Оптимізовано Dockerfile для кешування шарів, вибору меншого базового образу та чистішого вмісту середовища виконання.
  • Оцінено, чи мали б значення OCI-індекси образів, дайджести, підписи чи реферери для робочого навантаження, яке ви щойно налагодили.
Нотатки до розв'язання Успішний запуск показує зламаний Деплоймент або Pod, що входить у `ImagePullBackOff`, події, які пояснюють, що запитаного тегу не існує, і виправлене робоче навантаження, що досягає `Running` після зміни образу. Завдання з приватним реєстром може все одно не стягнутися, бо реєстр ілюстративний, але специфікація Pod'а має показати посилання на Secret. Завдання лише з args має вивести `Args-only override replaces image CMD` із порожнім полем `command` у виводі JSONPath, тоді як завдання з command і args має вивести повідомлення оболонки й показати обидва масиви заповненими.

Перевірка для учня

Розділ «Перевірка для учня»

Спостерігайте за станом деплойменту, щоб визначити точну причину збою стягування образу. Ключова навичка — не просто побачити ImagePullBackOff; це читати події, доки ви не зможете пояснити, яка частина посилання на образ дала збій і яка зміна дала б вузлу змогу стягнути образ успішно.

Модуль 1.2: Jobs та CronJobs — опануйте виконання ізольованих робочих навантажень і планування стійкої пакетної інфраструктури.