Урок 01 · Kubernetes на практике
Docker ровно настолько, насколько нужно для Kubernetes
Что-то не запустилось или осталось непонятным? Задайте вопрос в комментариях к разбору этого урока в канале — разберёмся вместе.
Kubernetes не запускает код напрямую — он запускает контейнеры. Прежде чем оркестрировать контейнеры, стоит один раз своими руками пройти путь Docker: собрать образ, понять слои и кеш сборки, вынести данные в том. Это единственный урок, который целиком про Docker — дальше те же идеи мы встретим внутри Kubernetes, уже под другими именами.
Образ vs контейнер
Два термина, которые часто путают:
- Образ (image) — неизменяемый шаблон: файловая система приложения плюс инструкция, что запускать. Он лежит на диске (или в registry) и ничего не делает сам по себе.
- Контейнер (container) — запущенный экземпляр образа. Из одного образа можно запустить сколько угодно контейнеров одновременно.
Аналогия из программирования: образ — это класс, контейнер — объект этого класса. Это ровно то различие, которое дальше будет иметь значение в Kubernetes: манифест Pod'а ссылается на образ, а кластер уже сам создаёт из него запущенные экземпляры.
Практика: соберите свой первый образ
Понадобится установленный Docker Desktop (или Docker Engine на Linux) — если ещё не стоит, поставьте по официальной инструкции под вашу ОС.
-
Создайте папку
hello-fastapi, а в ней файлmain.py:from fastapi import FastAPI app = FastAPI() @app.get("/") def root(): return {"message": "Привет из контейнера"} @app.get("/health") def health(): return {"status": "ok"}/health— не для украшения: в следующих уроках именно к таким эндпоинтам k8s будет обращаться, проверяя, жив ли ваш сервис. -
Рядом создайте
requirements.txt:fastapi uvicorn[standard] -
И
Dockerfile(без расширения):FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY main.py . EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]Читается построчно: взять образ
python:3.12-slim→ перейти в/app→ поставить зависимости → скопировать код → при запуске поднять uvicorn на порту 8000. -
Соберите образ (точка в конце — собрать из текущей папки):
docker build -t hello-fastapi:0.1 . -
Запустите контейнер, пробросив порт:
docker run -p 8000:8000 hello-fastapi:0.1 - Откройте localhost:8000/docs — увидите автосгенерированную страницу FastAPI. Это и есть ваш ощутимый результат урока: код, упакованный в переносимый образ, работает одинаково у вас и на любой другой машине с Docker.
hello-fastapi:0.1) в следующем уроке мы опишем YAML-манифестом и запустим не командой docker run, а через Kubernetes — с автоматическим перезапуском при падении и масштабированием на несколько копий.
Слои и кеш сборки: почему порядок в Dockerfile не случаен
Каждая инструкция Dockerfile создаёт отдельный слой (layer). Docker кеширует слои и переиспользует их, если ничего не поменялось с начала файла и до этой строки. Поэтому в нашем Dockerfile COPY requirements.txt и RUN pip install стоят раньше COPY main.py — зависимости меняются редко, а код приложения часто. Если бы порядок был обратным, каждое изменение в main.py заставляло бы Docker заново ставить все зависимости.
Проверьте на практике: добавьте в main.py ещё один эндпоинт и пересоберите образ под новым тегом:
docker build -t hello-fastapi:0.2 .
В выводе шаги с COPY requirements.txt и RUN pip install будут помечены CACHED — Docker не выполнял их заново, потому что эти строки Dockerfile и файл requirements.txt не изменились. Пересобралась только последняя, самая дешёвая часть.
Тома (volumes): данные переживают контейнер
Файловая система контейнера живёт ровно столько, сколько сам контейнер. Удалите контейнер — исчезнет всё, что было записано внутри. Том (volume) — способ вынести данные наружу: подключить папку с хоста внутрь контейнера, чтобы она пережила и перезапуск, и удаление.
docker run --rm -v "$(pwd)":/data alpine ls /data
Здесь -v "$(pwd)":/data монтирует текущую папку хоста как /data внутри одноразового контейнера alpine — команда покажет содержимое вашей папки изнутри контейнера. --rm удаляет контейнер сразу после завершения, но данные на хосте, разумеется, остаются.
Volume/PersistentVolume. Заодно держите в голове: по умолчанию контейнеры друг друга не видят — а в Kubernetes есть Service, который находит нужные Pod'ы по имени автоматически, даже когда их становится много. Как это устроено в Docker вручную, разбирать не будем — сразу увидим версию для кластера в отдельном уроке.
Проверьте себя
Что из перечисленного правильно описывает связь образа и контейнера?
Напишите команду, которая соберёт образ с тегом myapp:1.0 из Dockerfile в текущей папке:
Почему в Dockerfile зависимости ставят до копирования кода приложения?