Перейти к содержанию

PVC и граница постоянного хранения

Основной API «Площадки» читает константный каталог и не хранит пользовательские записи. Благодаря этому экземпляры можно заменять без потери предметных данных. Теперь создадим отдельную ветку storage-demo, чтобы увидеть другую задачу: сохранить файл после замены Pod. Мы не подключаем общий файл к двум основным репликам API и не выдаём его за базу данных.

Полный 09-storage.yaml находится в каталоге примера. Он создаёт PVC и отдельный Deployment с одной копией существующего собственного образа API. Код server.mjs уже содержит учебные маршруты чтения и записи постоянной строки. Автор не создавал том, не записывал данные и не проверял их сохранность; все описанные результаты ожидаемые.

Файл контейнера и файл тома

Если программа записывает файл в обычную writable-область контейнера, этот файл принадлежит конкретному экземпляру контейнера. Замена экземпляра не обещает перенести запись в новый процесс. Поэтому перенос приложения на Deployment не делает произвольный путь постоянным. Нужно явно указать, где находятся данные и кто отвечает за их жизненный цикл.

Для временной совместной работы контейнеров одного Pod может использоваться emptyDir. Он удобен для промежуточных файлов, но исчезает вместе с Pod. Это не подходящий механизм для данных, которые должны пережить удаление Pod. При этом временность не означает бесполезность: кэш или результат промежуточной распаковки часто не нужно сохранять после замены.

PersistentVolumeClaim выражает запрос приложения на хранение. Его можно сопоставить с выделенным PersistentVolume или обслужить через механизм динамического выделения StorageClass. Эти сущности отделяют запрос разработчика от конкретного способа предоставления диска. Однако особенности выбранной реализации по-прежнему важны: доступность на узлах, тип доступа и политика удаления.

Для лаборатории декларация PVC имеет вид:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: storage-note
  namespace: ploshchadka
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: local-path
  resources:
    requests:
      storage: 256Mi

Имя local-path нужно сопоставить с фактической StorageClass лаборатории. Перед применением читатель смотрит kubectl --context kind-ploshchadka get storageclass. Если имя отличается, в файле его меняют осознанно. Не следует оставлять неизвестную ссылку и интерпретировать Pending PVC как падение Node-приложения.

Отдельный экземпляр учебного хранилища

Deployment storage-demo монтирует PVC в /data и передаёт UPLOAD_DIR=/data. Наш API использует этот параметр только для /api/storage-example. Основной Deployment api не получает UPLOAD_DIR, поэтому его каталог остаётся независимым от учебного тома. Такое разделение делает границу примера видимой.

В контейнерном описании используются:

env:
  - name: UPLOAD_DIR
    value: /data
volumeMounts:
  - name: note
    mountPath: /data
volumes:
  - name: note
    persistentVolumeClaim:
      claimName: storage-note

Это фрагмент полного файла: volumes находится на уровне Pod, volumeMounts — на уровне контейнера. Совпадение имени note связывает объявленный том с точкой монтирования. Имя PVC определяется claimName; оно не обязано совпадать с локальным именем тома внутри шаблона.

API запускается пользователем node. В Pod задан fsGroup: 1000, чтобы согласовать доступ к смонтированному каталогу при поддержке такого поведения драйвером хранения. Если запись всё равно запрещена, нужно исследовать права конкретного тома. Запуск приложения от root ради обхода неизвестной причины не объясняет модель хранения и может создать другой набор проблем.

Замена Pod и доступ к данным

Для storage-demo выбрана стратегия Recreate и одна реплика. Обновление сначала завершает старую копию, затем создаёт новую. Для этой учебной записи допустим перерыв доступности. Выбор предотвращает ожидание, что две копии должны одновременно записывать один файл при обычном rolling update. Формат доступов PVC не заменяет согласование записи приложением.

ReadWriteOnce обычно связывает доступ с одним узлом, а не гарантирует единственный процесс-писатель во всех возможных условиях. Это особенно важно для одноузлового kind: несколько Pod на одном узле не превращают файл в транзакционное хранилище. Сам Node-код writeFile также не обеспечивает механизм конкурентных бизнес-операций или уникальных записей.

Будущий локальный доступ к отдельной ветке:

kubectl --context kind-ploshchadka apply -f manifests/09-storage.yaml
kubectl --context kind-ploshchadka -n ploshchadka port-forward deployment/storage-demo 3001:3000

После готовности читатель отправляет POST к http://127.0.0.1:3001/api/storage-example, затем GET к тому же адресу. Ожидается постоянная строка «Площадка: учебная запись». После замены Pod нужно открыть port-forward заново и повторить GET. При сохранённом PVC ожидается прежняя запись, хотя имя процесса изменилось.

Такое наблюдение подтверждает только выбранную границу жизненного цикла. Оно не доказывает, что данные переживут удаление всего kind-кластера, потерю Docker-диска или поломку компьютера. Локальный provisioner может хранить данные на том же физическом устройстве. Размещение вне writable-слоя контейнера и независимая резервная копия — разные свойства.

Когда приложение должно использовать другое хранилище

Путь mountPath является контрактом контейнера: он должен совпасть с UPLOAD_DIR. Если указать разные каталоги, программа может успешно записать файл вне тома, и ошибка проявится только после замены экземпляра. Проверяйте именно связь параметра и точки монтирования.

Для загрузок настоящего сайта часто нужен внешний объектный сервис, а для каталога с изменениями — база данных. Выбор зависит от операций: поиск, транзакции, конкурентная запись, размер объектов и восстановление. Kubernetes может запустить процессы рядом с данными, но не добавит SQL-транзакцию к обычному текстовому файлу.

У StatefulSet есть полезные свойства идентичности и связи экземпляров с томами, однако его создание не означает готовый кластер базы. Репликация, выбор лидера, миграции и резервные копии всё ещё относятся к устройству приложения хранения. Для курса веб-разработчика важнее сначала понять эту ответственность, чем добавить сложный манифест с неизвестным образом.

Политика reclaim определяет судьбу выделенного тома после удаления claim. Перед удалением PVC читатель смотрит связанный PV и его политику. Вариант Delete может привести к удалению данных на соответствующем хранилище. Поэтому очистка namespace, включающего PVC, требует отдельного решения о сохранении данных, даже если все объекты помечены как учебные.

Документация Persistent Volumes описывает отношения PV, PVC и режимов доступа. Для нашей передачи достаточно записать четыре факта: владелец данных storage-demo, точка /data, имя PVC storage-note и отсутствие выполненной резервной копии. Неопределённое обещание «данные сохраняются» хуже такой конкретной границы.

В следующем уроке вернёмся к основному stateless API и рассмотрим rollout и rollback версии. Учебный storage-demo останется отдельной веткой, чтобы обновление приложения не выглядело одновременно экспериментом с общим файлом.

Оглавление серии