Итоговый проект:
Берём стек из главы 8 (api, postgres, nginx) и доводим до состояния "не стыдно показать на собесе". Чеклист: multi-stage сборка и запуск не от root (главы 9 и 13), HEALTHCHECK и .dockerignore (глава 13), точные теги, лимиты и ротация логов в compose (глава 12), сборка через buildx с кэшем (глава 15), скан и публикация в registry из CI (главы 14 и 16). Эталонный Dockerfile, к которому мы шли весь курс:
Код: Выделить всё
# syntax=docker/dockerfile:1
FROM node:24-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
RUN npm run build && npm prune --omit=dev
FROM node:24-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY --from=build --chown=node:node /app/dist ./dist
COPY --from=build --chown=node:node /app/node_modules ./node_modules
USER node
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=15s \
CMD wget -qO- http://127.0.0.1:8080/health || exit 1
ENTRYPOINT ["node", "dist/server.js"]
Код: Выделить всё
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag cr.yandex/crp9f3k2/shop-api:1.5.0 \
--push .
trivy image --severity HIGH,CRITICAL --exit-code 1 \
cr.yandex/crp9f3k2/shop-api:1.5.0
ssh deploy@prod 'cd /srv/shop && docker compose pull && docker compose up -d --wait'
Kubernetes:
Compose управляет контейнерами на одной машине. Когда машин несколько, нужен оркестратор: он сам решает, на каком узле запустить контейнер, перезапускает упавшие, раскатывает новые версии без даунтайма. Стандарт де-факто здесь Kubernetes. Вместо compose-файла вы описываете желаемое состояние в манифестах:
Код: Выделить всё
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-api
spec:
replicas: 3
selector:
matchLabels: { app: shop-api }
template:
metadata:
labels: { app: shop-api }
spec:
containers:
- name: api
image: cr.yandex/crp9f3k2/shop-api:1.5.0
ports:
- containerPort: 8080
livenessProbe:
httpGet: { path: /health, port: 8080 }
Podman:
Podman это другой движок контейнеров: без демона и по умолчанию rootless, контейнеры живут как обычные процессы пользователя. CLI совместим с Docker почти один в один, alias docker=podman закрывает большинство команд. В RHEL-семействе (Альма, Рокки, РЕД ОС) он идёт из коробки, так что на части серверов в СНГ вы встретите именно его. Автозапуск там делается через systemd, механизм называется quadlet:
Код: Выделить всё
# /etc/containers/systemd/shop-api.container
[Container]
Image=cr.yandex/crp9f3k2/shop-api:1.5.0
PublishPort=127.0.0.1:8080:8080
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
OCI:
Причина, по которой один образ работает и в Docker, и в Podman, и в Kubernetes, называется Open Container Initiative. Это три спецификации: image-spec (формат образа), runtime-spec (как запускать контейнер, эталонная реализация runc) и distribution-spec (протокол registry). Docker, containerd, CRI-O, Podman и любой registry из главы 14 говорят на этом общем языке. Меняется обвязка, образ и принципы остаются.
Типичные грабли:
Kubernetes на единственном VPS. Плата за оркестрацию это сложность: etcd, сертификаты, сетевые плагины, обновления кластера. Для одного сервера compose с restart-политиками честно закрывает задачу, k8s берите, когда серверов несколько или этого требует работа.
Ожидание, что Podman сам поднимет контейнеры после ребута. Демона нет, рестартить некому: без quadlet или podman-restart.service после перезагрузки всё лежит. А инструментам, которые ходят в /var/run/docker.sock (testcontainers, CI-агенты), нужен включённый podman.socket.
Попытка выучить всё сразу. Kubernetes это месяцы практики, не приложение к финальной главе.
Что дальше:
Прогоните итоговый проект от git push до работающего прода без ручных шагов, это и есть экзамен. Дальше дорога ветвится: поднять кластер k3s из трёх узлов и перенести туда тот же проект, перевести домашний сервер на Podman с quadlet или копать вглубь, к containerd и runc. Docker вы уже знаете. Спасибо, что дошли до конца.