Docker і контейнери
Як запакувати .NET-застосунок у контейнер і запустити його поруч із залежностями та в кластері: чим контейнер відрізняється від віртуальної машини, як зібрати малий і безпечний образ, як підняти стенд через docker compose і чого Kubernetes чекає від застосунку. На співбесіді це перевіряють питаннями про шари, порти, проби й ліміти.
Перед цією темою: Хостинг і конвеєр.
Що таке контейнер і чим він відрізняється від віртуальної машини?
Section titled “Що таке контейнер і чим він відрізняється від віртуальної машини?”Що це
- Образ — незмінний шаблон: файли застосунку, рантайм, системні бібліотеки й команда запуску.
- Контейнер — запущений екземпляр образу: процес зі своєю файловою системою, мережею й лімітами.
- Dockerfile — текстовий рецепт збирання образу з інструкцій
FROM,COPY,RUN,ENTRYPOINT. - Реєстр — сховище образів: Docker Hub,
mcr.microsoft.com, Azure Container Registry.
Навіщо
Один артефакт працює однаково на ноутбуці, у CI і на сервері: рантайм потрібної версії вже всередині образу.
Як користуватися
docker build— зібрати образ за Dockerfile.docker run -p 5000:8080— запустити: порт 5000 хоста веде в порт 8080 контейнера.-e— передати змінну оточення,-v— підключити том, щоб дані пережили перестворення контейнера.
Типова помилка
Зберігати дані всередині контейнера: завантажені файли, SQLite, логи. Після docker rm або оновлення образу вони зникають.
Як влаштовано
- Шари. Кожна інструкція
RUN,COPYабоADDдає шар — набір змін файлової системи. Шари лише для читання, адресуються хешем вмісту й спільні для різних образів. - Запуск. Docker накладає шари один на одний (overlayfs) і додає згори шар контейнера для запису.
- Ізоляція. Namespaces дають процесу свої PID, мережу, точки монтування й hostname: застосунок усередині — PID 1. Cgroups обмежують CPU, пам’ять і кількість процесів.
- Порти. Контейнер має свій мережевий стек.
EXPOSEу Dockerfile — лише документація, доступ ззовні відкриває-p хост:контейнер.
Коли що обирати
| Ознака | Контейнер | Віртуальна машина |
|---|---|---|
| Ядро ОС | спільне з хостом | своє |
| Старт | частки секунди | десятки секунд |
| Розмір | десятки–сотні МБ | гігабайти |
| Ізоляція | процесна | апаратна, сильніша |
Пастки
- Том чи bind mount. Іменованим томом (
-v data:/data) керує Docker. Bind mount (-v ./conf:/app/conf) підключає теку хоста — зручно в розробці, але прив’язує до конкретної машини. - Linux-контейнери на Windows. Docker Desktop запускає їх у легкій ВМ WSL 2: контейнеру однаково потрібне ядро Linux.
- Тег — не версія.
latestі навіть1.0можна перезаписати. Незмінне посилання — digest@sha256:....
Правило
Контейнер одноразовий: налаштування — у змінних оточення, дані — у томах або зовнішніх сервісах, логи — у stdout.
Під капотом
- Ланцюжок запуску: Docker CLI → dockerd → containerd → runc. Формат образу й рантайму описано стандартом OCI, тому той самий образ запускають Podman і Kubernetes.
- Namespaces: pid, net, mnt, uts, ipc, user, cgroup. Cgroups v2 задають
cpu.maxіmemory.max: за перевищення пам’яті ядро вбиває процес, за перевищення CPU — тротлить. - PID 1 отримує сигнали.
ENTRYPOINT ["dotnet", "Shop.Api.dll"](exec-форма) приймає SIGTERM напряму. Shell-форма запускає застосунок під/bin/sh -c, сигнал до нього не доходить, іdocker stopза 10 секунд убиває процес. - Windows-контейнери (Server Core, Nano Server) існують, зокрема з IIS і .NET Framework, але працюють лише на Windows-хості й важать гігабайти.
Ціна
- Спільне ядро — спільна поверхня атаки. Контейнер із
--privilegedабо змонтованимdocker.sockфактично дорівнює root на хості. - Запис у шар контейнера йде через copy-on-write і повільніший за запис у том.
Компроміси
- Щільність і швидкість доставки проти сили ізоляції: для свого коду контейнерів вистачає, для чужого потрібні ВМ або gVisor і Kata Containers.
- Незмінний образ спрощує відкат — запустити попередній тег, — але конфігурацію й стан доводиться виносити назовні.
Як перевірити в продакшені
docker inspect— ліміти, монтування, змінні;docker stats— CPU і пам’ять відносно лімітів.docker history— які інструкції дали найважчі шари.- Код виходу 137 (128 + 9) — процес убито SIGKILL: через OOM або після тайм-ауту зупинки.
Підсумок: контейнер — процес хоста з обмеженою видимістю й ресурсами, і його сильні та слабкі сторони випливають зі спільного ядра.
# Зібрати образ за Dockerfile з поточної текиdocker build -t shop-api:1.0 .
# Порт хоста 5000 → порт 8080 у контейнері, змінна, том і лімітиdocker run -d --name shop-api -p 5000:8080 \ -e ASPNETCORE_ENVIRONMENT=Staging \ -v shop-uploads:/app/uploads \ --memory 512m --cpus 1.5 \ shop-api:1.0
docker ps # запущені контейнериdocker logs -f shop-api # stdout застосункуdocker stop shop-api # SIGTERM, за 10 секунд — SIGKILLdocker rm shop-api # контейнер видалено, том shop-uploads залишився- Чим образ відрізняється від контейнера? — Образ — незмінний шаблон, контейнер — його запущений екземпляр зі своїм шаром для запису; з одного образу — скільки завгодно контейнерів.
- Що буде з файлами, записаними всередину контейнера? — Живуть до видалення контейнера; переживають його лише томи й bind mount.
- Навіщо
EXPOSE, якщо є-p? —EXPOSEдокументує порт, а публікує його-p(або-Pдля всіх портів зEXPOSE). - Чому застосунок не зупиняється коректно за
docker stop? — Shell-формаENTRYPOINT: SIGTERM отримуєsh, а не застосунок. - Чи можна запустити Linux-контейнер на Windows? — Так, через Docker Desktop з WSL 2: ядро Linux працює в легкій ВМ.
Як правильно зібрати образ .NET-застосунку?
Section titled “Як правильно зібрати образ .NET-застосунку?”Що це
Багатоетапний Dockerfile — кілька FROM в одному файлі. Кожен етап — окремий образ, а в підсумок потрапляє лише останній етап і те, що в нього скопіювали через COPY --from.
Навіщо
SDK потрібен лише для збирання, у продакшені це сотні зайвих мегабайтів і компілятор під рукою в атакувальника. Простий Web API на aspnet:10.0 важить близько 90 МБ у стиснутому вигляді.
Як користуватися
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build— етап збирання.- Скопіювати
.csprojі виконатиdotnet restore— пакети потрапляють в окремий шар. - Скопіювати код і виконати
dotnet publish -o /app. FROM mcr.microsoft.com/dotnet/aspnet:10.0, потімCOPY --from=build /app .іENTRYPOINT.- У
.dockerignoreперелічитиbin/іobj/, щоб вони не потрапляли в контекст збирання.
Типова помилка
Прокидати порт 80 за звичкою. З .NET 8 застосунок в офіційному образі слухає 8080 (ASPNETCORE_HTTP_PORTS=8080), і -p 5000:80 не працює.
Як влаштовано
- Кеш шарів. Docker перевикористовує шар, якщо не змінилися інструкція та вхідні файли, а після першого зміненого перезбирає всі наступні. Тому
.csprojкопіюють до коду: правка в.csне запускає restore. - Образи Microsoft. Теги
10.0вказують на Ubuntu 24.04 (Noble); образів на Debian для .NET 10 немає.
| Образ | Що всередині | Для чого |
|---|---|---|
sdk:10.0 |
SDK, MSBuild, компілятори | збирання й тести |
aspnet:10.0 |
рантайм .NET і ASP.NET Core | вебзастосунки |
runtime:10.0 |
рантайм .NET | консоль, worker |
runtime-deps:10.0 |
лише системні бібліотеки | self-contained, Native AOT |
Коли що обирати
- Chiseled (
aspnet:10.0-noble-chiseled) — distroless-образ Ubuntu без shell і пакетного менеджера, non-root за замовчуванням. ICU і tzdata немає, увімкнено режим інваріантної глобалізації; якщо потрібні культури й часові пояси —-noble-chiseled-extra. - Composite (
-noble-chiseled-composite) — фреймворк зібрано в один ReadyToRun-файл: образ менший, але версія фреймворку жорстко прив’язана. - Без Dockerfile —
dotnet publish /t:PublishContainer: SDK сам бере базовий образ за типом проєкту, користувачаappі порт; варіант задаєContainerFamily.
Пастки
- У звичайних образах користувач
appє, але процес працює від root, доки не вказатиUSER $APP_UID. А non-root не може писати в/appі в томи, створені root. HEALTHCHECKчерезcurlу chiseled-образі не працює: немає ні curl, ні shell.
Правило
Шари — від рідко змінюваних до часто змінюваних; фінальний образ — мінімальний, non-root, з явною версією.
Під капотом
- BuildKit, збирач Docker за замовчуванням, виконує незалежні етапи паралельно.
RUN --mount=type=cache,target=/root/.nuget/packagesзберігає кеш NuGet між збираннями, навіть коли шар restore перезбирається. FROM --platform=$BUILDPLATFORMіdotnet publish -a $TARGETARCHзбирають образ для arm64 на x64-агенті без емуляції: SDK працює на рідній архітектурі.PublishContainerне потрібен Docker-демон: SDK завантажує шари базового образу, додає шар із застосунком і надсилає образ у реєстр (ContainerRegistry) або в архів (ContainerArchiveOutputPath).
Ціна
Стиснутий образ простого Web API, за заміром dotnet-docker на листопад 2025:
| Варіант | Розмір |
|---|---|
aspnet:10.0 |
~92 МБ |
aspnet:10.0-noble-chiseled |
~53 МБ |
Native AOT на runtime-deps:10.0-noble-chiseled |
~12 МБ |
Native AOT дає швидкий старт і менше пам’яті, але обмежує рефлексію й динамічне завантаження коду, а збирається в sdk:10.0-noble-aot. Докладно — у темі Платформа .NET.
Компроміси
- Плаваючий тег
10.0приносить патчі безпеки під час перезбирання, digest — відтворюваність. Зазвичай фіксують digest, а оновлюють його Dependabot або Renovate. - Chiseled без shell не дає зайти всередину: налагоджують окремим контейнером поруч (
kubectl debug) або через dotnet-monitor.
Як перевірити в продакшені
docker history— вага кожного шару.- Сканер вразливостей (Trivy, Docker Scout) по образу. У .NET 10 chiseled-образи містять Chisel manifest, за яким сканери бачать пакети.
docker image inspect -f "{{ .Config.User }}" образ— від якого користувача він запуститься.
Підсумок: добрий образ — мінімальний, non-root, відтворюваний і швидко перезбирається, а chiseled, composite і AOT — це вибір розміру проти сумісності.
# Етап збирання: SDK потрібен лише тутFROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0 AS buildARG TARGETARCHWORKDIR /src
# Спершу лише файл проєкту: шар restore береться з кешу, поки не змінювались пакетиCOPY Shop.Api/Shop.Api.csproj Shop.Api/RUN dotnet restore Shop.Api/Shop.Api.csproj -a $TARGETARCH
# Потім код і публікація (Release — за замовчуванням з .NET 8)COPY . .RUN dotnet publish Shop.Api/Shop.Api.csproj -a $TARGETARCH --no-restore -o /app
# Етап запуску: без shell і пакетного менеджера, non-root за замовчуваннямFROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseledWORKDIR /appCOPY --from=build /app .EXPOSE 8080ENTRYPOINT ["dotnet", "Shop.Api.dll"]# SDK сам візьме aspnet:10.0-noble-chiseled, користувача app і порт 8080dotnet publish Shop.Api --os linux --arch x64 /t:PublishContainer \ -p ContainerRepository=shop-api \ -p ContainerFamily=noble-chiseled \ -p ContainerImageTag=1.4.2
# Образ уже в локальному Dockerdocker run -p 5000:8080 shop-api:1.4.2- Навіщо копіювати
.csprojокремо від коду? — Щоб шар restore брався з кешу, поки не змінювались залежності. - Чому застосунок на .NET 8+ у контейнері не відповідає на порту 80? — Порт за замовчуванням — 8080 (
ASPNETCORE_HTTP_PORTS): непривілейований порт потрібен для роботи без root. - Чим chiseled відрізняється від Alpine? — Обидва без ICU за замовчуванням; Alpine — musl, shell і apk, chiseled — glibc Ubuntu без shell і пакетного менеджера.
- Коли
PublishContainerне підійде? — Коли в образі потрібніRUN-кроки: системні пакети, сертифікати. Тоді Dockerfile або власний базовий образ. - Як налагоджувати контейнер без shell? — Налагоджувальний контейнер поруч (
kubectl debug), dotnet-monitor як sidecar, дампи в том.
Як підняти застосунок із залежностями через docker compose?
Section titled “Як підняти застосунок із залежностями через docker compose?”Що це
Compose-файл compose.yaml перелічує сервіси — контейнери з образом, портами, змінними й томами. docker compose up -d створює й запускає їх усі, docker compose down зупиняє й видаляє контейнери, а томи залишає.
Навіщо
Розробник однією командою отримує API, PostgreSQL і Redis потрібних версій, нічого не встановлюючи на машину.
Як користуватися
- Опишіть сервіси
api,db,cache. - У рядку підключення API вкажіть ім’я сервісу:
Host=db;Port=5432. - Дані бази покладіть в іменований том.
- Назовні (
ports) публікуйте лише те, до чого ходите з хоста.
Типова помилка
Писати localhost у рядку підключення з контейнера до бази. Усередині контейнера localhost — він сам, а база доступна як db.
Як влаштовано
- Compose читає
compose.yaml, автоматично додаєcompose.override.yamlі підставляє${VAR}з.envта оточення. - Створює мережу проєкту: ім’я сервісу стає DNS-іменем. Порти між сервісами публікувати не потрібно.
- Створює томи й запускає контейнери в порядку
depends_on, чекаючи вказаних умов.
Коли що обирати
| Задача | Засіб |
|---|---|
| Дочекатися готовності БД | healthcheck і condition: service_healthy |
| Звичайні налаштування | environment, env_file |
| Паролі й ключі | secrets — файл у /run/secrets/ім'я |
| Необов’язкові сервіси | profiles і --profile tools |
| Відмінності оточень | override-файли, -f compose.yaml -f compose.prod.yaml |
Пастки
- Короткий
depends_onчекає старту контейнера, а не готовності Postgres. healthcheckвиконується всередині контейнера. У chiseled-образі API немаєcurlі shell — таку перевірку не написати.- Змінну з
environmentвидно вdocker inspect, для секретів вона не годиться. docker compose down -vвидаляє томи разом із даними бази.
Правило
Базовий файл — спільний для всіх оточень; порти, bind mount вихідного коду й налагоджувальні сервіси — в override або профілях.
Під капотом
- Compose v2 — плагін
docker compose, клієнт Docker Engine API. Ім’я проєкту (за замовчуванням — ім’я теки) стає префіксом мереж, томів і контейнерів. Полеversionзастаріло: Compose завжди перевіряє файл за останньою схемою. secretsу Compose — монтування файлу в/run/secretsбез шифрування. .NET читає такі файли черезAddKeyPerFile— див. Конфігурація й логування.docker compose watchза секцієюdevelop.watchсинхронізує файли або перезбирає образ у разі зміни коду.
Ціна
Один хост: немає розподілу по вузлах, автомасштабування, перенесення впалого сервісу на інший сервер і викладки без простою. restart: unless-stopped перезапустить процес, але не врятує від відмови машини.
Компроміси
- Compose чи Aspire. Aspire (з версії 13 — без «.NET» у назві) описує стенд кодом в AppHost:
AddPostgres,WithReference,WaitFor. Проєкти .NET він запускає як звичайні процеси, контейнери — для залежностей, дає дашборд OpenTelemetry, аAddDockerComposeEnvironmentгенерує compose-файл для викладки. Compose простіший і не залежить від стеку; Aspire зручніший, коли в рішенні багато сервісів на .NET. - Compose у продакшені доречний для одного сервера й внутрішніх інструментів. Для кількох вузлів і SLA — Kubernetes або керована платформа.
Як перевірити в продакшені
docker compose config— підсумкова конфігурація після злиття файлів і підстановки змінних.docker compose psпоказує статус healthcheck,docker compose logs -f api— логи сервісу.
Підсумок: compose — найкращий інструмент для стенда на одній машині; вище — Kubernetes, а для стенда .NET-розробника є ще й Aspire.
services: api: build: . ports: - "5000:8080" environment: ASPNETCORE_ENVIRONMENT: Development # Хост бази — ім'я сервісу в мережі compose ConnectionStrings__Shop: "Host=db;Database=shop;Username=shop" secrets: - db_password # файл /run/secrets/db_password depends_on: db: condition: service_healthy # чекати готовності, а не лише старту
db: image: postgres:18 environment: POSTGRES_USER: shop POSTGRES_DB: shop POSTGRES_PASSWORD_FILE: /run/secrets/db_password secrets: - db_password volumes: - pgdata:/var/lib/postgresql # з postgres:18 — без /data у кінці healthcheck: test: ["CMD-SHELL", "pg_isready -U shop -d shop"] interval: 5s retries: 10
adminer: image: adminer profiles: ["tools"] # лише з --profile tools ports: - "8081:8080"
volumes: pgdata:
secrets: db_password: file: ./secrets/db_password.txt- Чому
depends_onне гарантує, що база готова? — Коротка форма чекає лише старту контейнера; потрібнаcondition: service_healthy, а в застосунку — повтори підключення. - Чому з контейнера API не підключитися до
localhost:5432? —localhost— сам контейнер; база доступна за іменем сервісу. - Як розділити налаштування розробки й продакшену? — Override-файли, профілі,
.envдля підстановки; підсумок перевіряютьdocker compose config. - Чим
secretsкращі заenvironmentдля пароля? — Файл у/run/secretsне видно вdocker inspect, і він не успадковується дочірніми процесами. - Як піднімати базу для інтеграційних тестів? — Через Testcontainers з коду тесту — див. тему Тестування.
Що потрібно знати .NET-розробнику про запуск у Kubernetes?
Section titled “Що потрібно знати .NET-розробнику про запуск у Kubernetes?”Що це
- Pod — мінімальна одиниця запуску: один або кілька контейнерів зі спільним IP, зазвичай один екземпляр застосунку.
- Deployment — опис «потрібно 3 копії образу
shop-api:1.4.2»: створює поди й замінює їх під час оновлення. - Service — постійне ім’я та IP для набору подів, балансує запити між ними.
- Ingress — правила входу HTTP ззовні: хост, шлях, TLS. Його наступник — Gateway API.
Навіщо
Kubernetes сам тримає потрібну кількість копій: упав процес чи сервер — поди створюються заново.
Як користуватися
- Додайте в застосунок ендпоінти
/healthz/liveі/healthz/ready. - Вкажіть їх у маніфесті як
livenessProbeіreadinessProbe. - Задайте
resources: скільки CPU і пам’яті потрібно і скільки можна. - Налаштування передавайте з ConfigMap і Secret.
Типова помилка
Зберігати важливе в поді: файли на диску, сесії в пам’яті. Під час наступного деплою под перестворюється, і дані зникають.
Як влаштовано
| Проба | У разі збою | Що перевіряти |
|---|---|---|
| startup | перезапуск; до успіху інші проби не працюють | старт завершено: міграції, прогрів |
| liveness | перезапуск контейнера | процес живий, без зовнішніх залежностей |
| readiness | под прибирають із Service | готовий до трафіку: БД і кеш доступні |
- В ASP.NET Core
AddHealthChecks()реєструє перевірки, аMapHealthChecksзPredicateза тегами публікує різні ендпоінти. Докладно — у темі Спостережуваність і діагностика. - За замовчуванням проба виконується кожні 10 секунд із тайм-аутом 1 секунда й спрацьовує після 3 невдач поспіль.
- Requests — резерв: за ними планувальник обирає вузол. Limits — стеля: за перевищення пам’яті — OOMKilled, за перевищення CPU — тротлінг.
Пастки
- Liveness з перевіркою БД: база лягла — перезапускаються всі поди, і відновлення затягується.
- Довгий старт без startup-проби: liveness убиває под раніше, ніж він піднявся, і той іде в
CrashLoopBackOff. - ConfigMap і Secret у змінних оточення не оновлюються без перезапуску пода. Змонтовані файли оновлюються, але не з
subPath. - Secret за замовчуванням лежить в etcd незашифрованим — base64 лише кодує.
Правило
Liveness — дешева й без залежностей, readiness — про готовність до трафіку, startup — для повільного старту.
Під капотом
- .NET читає cgroups v1 і v2.
Environment.ProcessorCount— мінімум із кількості ядер, affinity і CPU limit, округленого вгору; requests не враховуються. Значення перевизначаєDOTNET_PROCESSOR_COUNT. - Ліміт пам’яті GC вважає фізичною пам’яттю,
GCHeapHardLimitза замовчуванням — 75 % ліміту. Докладно — Збирання сміття й ресурси. - Під час видалення пода контролер прибирає його з EndpointSlice, а kubelet паралельно виконує preStop і надсилає SIGTERM. Поки балансувальники не оновилися, запити ще надходять.
preStop: sleep(стабільний з Kubernetes 1.34) не потребує shell — це важливо для chiseled-образів. Зупинку хоста розбирає Хостинг і конвеєр.
Ціна
- Без CPU limit под із request 0,5 CPU на 64-ядерному вузлі бачить 64 ядра: Server GC і пул потоків налаштовуються під чужі ресурси.
- CPU limit ріже квоту за періодами: сплески GC і JIT на старті впираються в тротлінг, ростуть хвостові затримки.
Компроміси
- Частий вибір — CPU request без limit плюс
DOTNET_PROCESSOR_COUNTабо limit цілою кількістю ядер. Для пам’яті limit обов’язковий, зазвичай рівний request. - Stateless: сесії й кеш — у Redis, файли — в об’єктному сховищі, ключі Data Protection — у спільному сховищі. Інакше cookie, видану одним подом, не розшифрує інший. Sticky sessions лише маскують проблему.
Як перевірити в продакшені
kubectl describe pod— причина рестарту (OOMKilled) і збої проб;kubectl top pod— споживання.- Метрика
container_cpu_cfs_throttled_periods_total— тротлінг; dotnet-counters —dotnet.process.cpu.countі розмір купи проти ліміту.
Підсумок: у Kubernetes .NET-сервіс чесно повідомляє про готовність, вкладається в ліміти, які бачить рантайм, коректно зупиняється й не зберігає стан у поді.
using Microsoft.AspNetCore.Diagnostics.HealthChecks;using Microsoft.Extensions.Diagnostics.HealthChecks;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks() // liveness: процес живий, зовнішні залежності не перевіряємо .AddCheck("self", () => HealthCheckResult.Healthy(), tags: ["live"]) // readiness: чи можна приймати трафік .AddCheck<DatabaseCheck>("database", tags: ["ready"]);
var app = builder.Build();
app.MapHealthChecks("/healthz/live", new HealthCheckOptions { Predicate = c => c.Tags.Contains("live") });app.MapHealthChecks("/healthz/ready", new HealthCheckOptions { Predicate = c => c.Tags.Contains("ready") });
app.Run();
public class DatabaseCheck : IHealthCheck{ public Task<HealthCheckResult> CheckHealthAsync( HealthCheckContext context, CancellationToken cancellationToken = default) { // Тут — дешевий запит до БД з коротким тайм-аутом return Task.FromResult(HealthCheckResult.Healthy()); }}apiVersion: apps/v1kind: Deploymentmetadata: name: shop-apispec: replicas: 3 selector: matchLabels: { app: shop-api } template: metadata: labels: { app: shop-api } spec: terminationGracePeriodSeconds: 45 # більше, ніж preStop + ShutdownTimeout containers: - name: api image: registry.example.com/shop-api:1.4.2 ports: - containerPort: 8080 envFrom: - configMapRef: { name: shop-api-config } - secretRef: { name: shop-api-secrets } resources: requests: { cpu: "500m", memory: "512Mi" } limits: { cpu: "2", memory: "512Mi" } # ProcessorCount = 2, купа GC ≈ 384 МБ startupProbe: httpGet: { path: /healthz/live, port: 8080 } failureThreshold: 30 # до 30 × 10 секунд на старт livenessProbe: httpGet: { path: /healthz/live, port: 8080 } readinessProbe: httpGet: { path: /healthz/ready, port: 8080 } lifecycle: preStop: sleep: { seconds: 10 } # дати балансувальникам прибрати под---apiVersion: v1kind: Servicemetadata: name: shop-apispec: selector: { app: shop-api } ports: - port: 80 targetPort: 8080- Чим readiness відрізняється від liveness? — Readiness прибирає под із балансування, liveness перезапускає контейнер.
- Чому під час деплою бувають помилки 502, хоча застосунок обробляє SIGTERM? — Видалення з EndpointSlice йде паралельно із SIGTERM; допомагає пауза в preStop.
- Скільки ядер побачить .NET у поді з
limits.cpu: 1500m? — Два: ліміт округлюється вгору. - Що буде в разі перевищення memory limit? — Ядро вб’є процес (OOMKilled, код 137); GC намагається тримати купу в межах 75 % ліміту.
- Як зберігати ключі Data Protection у кластері? — У спільному сховищі (Redis, БД, blob) через
PersistKeysTo..., інакше інші поди не прочитають cookie.