Модуль 1.1: Образи контейнерів
Складність:
[СЕРЕДНЯ]— потребує розуміння поведінки 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.io | docker.io |
| Простір імен | Ні | library, mycompany | library |
| Образ | Так | nginx, myapp | - |
| Тег | Ні | latest, 1.19.0, alpine | latest |
| Дайджест | Ні | 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:latestimage: 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 nginxCMD ["nginx", "-g", "daemon off;"]# Можна запустити: docker run myimage sleep 10 (замінює CMD)
# ENTRYPOINT: важко перевизначитиFROM pythonENTRYPOINT ["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 ніколи не бачить локальне збирання, доки середовище виконання на вузлі не зможе стягнути те саме посилання.
# Зібрати в поточному каталозіdocker build -t myapp:v1.0.0 .
# Зібрати з конкретним Dockerfiledocker 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 не доводить, що вузол кластера може автентифікуватися в реєстрі або отримати цей репозиторій.
# Перепризначити тег наявному образу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 |
:latest | Always |
Конкретний тег (: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’и, які використовують цей акаунт, могли стягувати приватні образи.
# Створити docker-registry secretkubectl 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: v1kind: Podmetadata: name: private-appspec: containers: - name: app image: myregistry.com/team/myapp:v1.0.0 imagePullSecrets: - name: regcredПриєднання imagePullSecrets напряму до кожного Pod’а працює, але стає повторюваним, щойно простір імен містить кілька робочих навантажень. Чистіший патерн — приєднати Secret для стягування до ServiceAccount, а потім встановити serviceAccountName на Pod’ах, які мають успадкувати доступ до реєстру. Це тримає політику облікових даних образу поруч із політикою ідентичності робочого навантаження.
apiVersion: v1kind: ServiceAccountmetadata: name: myapp-saimagePullSecrets:- name: regcredapiVersion: v1kind: Podmetadata: name: private-appspec: 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-slimRUN useradd -m appuserUSER appuserCOPY --chown=appuser:appuser . /appsecurityContext у 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 для стягування існує в просторі імен, і відтворюйте стягування з належно автентифікованого середовища лише після прочитання помилки на боці кластера. Стягування з ноутбука мало що доводить, якщо кластер використовує інші облікові дані.
# Перевірити події 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, який стягує дійсний образ і переходить до запуску контейнера.
# Pod застряг у ImagePullBackOffkubectl 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 myappkubectl 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: Налаштувати зламане середовище
# Створити деплоймент із навмисними проблемами образуkubectl create deploy broken-app --image=nginx:nonexistentЗавдання 2: Діагностувати збій
Спостерігайте за станом деплойменту, щоб визначити точну причину збою стягування образу. Ключова навичка — не просто побачити ImagePullBackOff; це читати події, доки ви не зможете пояснити, яка частина посилання на образ дала збій і яка зміна дала б вузлу змогу стягнути образ успішно.
# Перевірити статус 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’и-замінники успадковували виправлений образ.
# Створити зламаний podkubectl run broken --image=nginx:1.999.0
# Діагностуватиkubectl describe pod broken | grep -A5 Events
# Виправитиkubectl delete pod brokenkubectl run broken --image=nginx:1.21.0
# Перевіритиkubectl get pod broken
# Прибратиkubectl delete pod brokenЗавдання 5: Налаштувати Secret приватного реєстру
Це завдання зосереджується на формі об’єкта, а не на реальному приватному реєстрі. У реальному середовищі замініть приклад сервера й облікові дані схваленими значеннями зі свого процесу керування секретами та пам’ятайте, що Secret має бути в тому самому просторі імен, що й Pod, який на нього посилається.
# Створити registry secretkubectl create secret docker-registry myregistry \ --docker-server=private.registry.io \ --docker-username=testuser \ --docker-password=your-password-here
# Створити pod із посиланням на secretcat << EOF | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: private-podspec: containers: - name: app image: private.registry.io/app:latest imagePullSecrets: - name: myregistryEOF
# Перевірити, чи secret посилаєтьсяkubectl get pod private-pod -o jsonpath='{.spec.imagePullSecrets}'
# Прибратиkubectl delete pod private-podkubectl delete secret myregistryЗавдання 6: Перевизначити command і args
Використовуйте це завдання, щоб підтвердити зіставлення ENTRYPOINT/CMD у Dockerfile з command/args у Kubernetes у живих специфікаціях Pod’а. BusyBox постачається зі стандартним CMD ["sh"] і без фіксованого ENTRYPOINT, тож Pod лише з args замінює лише стандартну команду, залишаючи entrypoint образу невстановленим. Другий Pod встановлює обидва поля, щоб показати, коли ви повністю замінюєте запускач процесу образу.
# Частина A: перевизначення лише args — `args` у Kubernetes зіставляється з `CMD` у Dockerfile# Залиште `command` невстановленим, щоб entrypoint образу (якщо є) залишився на місці.cat << EOF | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: args-only-cmdspec: containers: - name: busybox image: busybox args: ["echo", "Args-only override replaces image CMD"]EOF
kubectl logs args-only-cmdkubectl 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 у Dockerfilecat << EOF | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: command-and-argsspec: containers: - name: busybox image: busybox command: ["sh", "-c"] args: ["echo 'command replaces ENTRYPOINT; args replace CMD' && sleep 5"]EOF
kubectl logs command-and-argskubectl 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’и з тим самим образом і різними політиками стягування, потім інспектуйте збережені значення політик. Суть не в тому, щоб порівняти швидкість реєстру; це виробити звичку робити поведінку кешу явною, коли робоче навантаження має чіткі вимоги до відтворюваності чи свіжості.
# Створити pod'и з різними політикамиcat << EOF | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: pull-alwaysspec: containers: - name: nginx image: nginx:1.21.0 imagePullPolicy: Always---apiVersion: v1kind: Podmetadata: name: pull-ifnotpresentspec: containers: - name: nginx image: nginx:1.21.0 imagePullPolicy: IfNotPresentEOF
# Перевірити політики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-замінник досягає очікуваного стану.
# Налаштування (симуляція проблеми)kubectl create deploy webapp --image=nginx:alpine-wrong-tag
# ВАШЕ ЗАВДАННЯ: Знайти та виправити проблему
# Крок 1: Перевірити статус деплойментуkubectl get deploy webappkubectl 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/webappkubectl get pods -l app=webapp
# Прибратиkubectl delete deploy webappЗавдання 9: Оптимізувати Dockerfile
Це останнє завдання повертається від діагностики кластера до конструювання образу. Оригінальний Dockerfile працює, але поєднує велику базу з поганим упорядкуванням кешу. Ваше переписування має зробити шар залежностей стабільним і використати меншу базу середовища виконання, коли застосунок сумісний.
FROM node:18WORKDIR /usr/src/appCOPY . .RUN npm installCMD ["node", "index.js"]Ваші завдання — визначити проблему кешування шарів, що спричиняє повільні перезбирання, визначити проблему розміру базового образу та переписати Dockerfile для його оптимізації.
# 1. Перейти на менший базовий образ (alpine)FROM node:18-alpineWORKDIR /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; це читати події, доки ви не зможете пояснити, яка частина посилання на образ дала збій і яка зміна дала б вузлу змогу стягнути образ успішно.
Джерела
Розділ «Джерела»- https://kubernetes.io/docs/concepts/containers/images/
- https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
- https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/
- https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
- https://github.com/opencontainers/image-spec/releases/tag/v1.1.0
- https://github.com/opencontainers/image-spec/blob/v1.1.1/spec.md
- https://github.com/opencontainers/image-spec/blob/v1.1.1/media-types.md
- https://github.com/opencontainers/distribution-spec/blob/v1.1.1/spec.md
- https://github.com/opencontainers/runtime-spec/releases/tag/v1.3.0
- https://docs.docker.com/reference/dockerfile/
- https://docs.docker.com/build/building/multi-stage/
- https://docs.docker.com/build/buildkit/
- https://docs.docker.com/engine/release-notes/23.0/
- https://docs.docker.com/docker-hub/usage/pulls/
- https://docs.docker.com/engine/storage/containerd/
- https://docs.sigstore.dev/cosign/
- https://www.alpinelinux.org/releases/
Наступний модуль
Розділ «Наступний модуль»Модуль 1.2: Jobs та CronJobs — опануйте виконання ізольованих робочих навантажень і планування стійкої пакетної інфраструктури.