Зарегистрируйтесь, чтобы продолжить обучение

Volume Kubernetes

Volume

Контейнеры в Kubernetes эфемерны — при перезапуске Pod все данные в файловой системе контейнера теряются. Для приложений, которым нужно сохранять данные между перезапусками, эта модель не подходит. База данных потеряет все записи, файловое хранилище лишится загруженных файлов, логи исчезнут. Volume решает эту проблему, предоставляя механизм для хранения данных вне жизненного цикла контейнера.

Проблема эфемерного хранилища

Представьте веб-приложение, которое позволяет пользователям загружать изображения. Файлы сохраняются в директорию /uploads внутри контейнера. Всё работает, пока Pod не перезапустится из-за обновления или сбоя. После перезапуска все загруженные файлы исчезают — контейнер создаётся с чистой файловой системой.

Другой сценарий: приложение логирует важные события в файл. При отладке проблемы вы хотите посмотреть логи, но контейнер уже перезапущен, и логи потеряны. Или база данных PostgreSQL, которая хранит данные в /var/lib/postgresql/data. Каждый перезапуск — это потеря всей базы данных.

Volume позволяет монтировать хранилище, которое существует независимо от жизненного цикла контейнера. Данные сохраняются при перезапуске Pod и могут разделяться между несколькими контейнерами в одном Pod.

Что такое Volume

Volume — это директория, доступная контейнерам в Pod. Kubernetes поддерживает множество типов Volume: от временных директорий в памяти до постоянных дисков в облачных хранилищах. Тип Volume определяет, где и как хранятся данные, как долго они живут и кто имеет к ним доступ.

Простейший пример — Volume типа emptyDir, который создаёт пустую директорию при создании Pod:

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
    - name: app
      image: nginx:1.21
      volumeMounts:
        - name: data-volume
          mountPath: /usr/share/nginx/html
    - name: content-generator
      image: busybox
      command:
        [
          "sh",
          "-c",
          'echo "Hello from Volume" > /data/index.html && sleep 3600',
        ]
      volumeMounts:
        - name: data-volume
          mountPath: /data
  volumes:
    - name: data-volume
      emptyDir: {}

В этом примере два контейнера разделяют Volume data-volume. Контейнер content-generator пишет файл в /data/index.html, а контейнер app (nginx) читает его из /usr/share/nginx/html/index.html. Оба пути указывают на один и тот же Volume.

Типы Volume

emptyDir

emptyDir создаёт пустую директорию на узле, которая существует в течение жизни Pod. Когда Pod удаляется, директория тоже удаляется. Используйте emptyDir для временных данных: кеша, буферов обмена между контейнерами, промежуточных результатов вычислений.

volumes:
  - name: cache-volume
    emptyDir: {}

По умолчанию emptyDir использует дисковое хранилище узла. Для быстрого доступа можно использовать память:

volumes:
  - name: cache-volume
    emptyDir:
      medium: Memory
      sizeLimit: 128Mi

Данные в emptyDir с medium: Memory хранятся в RAM и исчезают при перезапуске узла. Это полезно для данных, требующих максимальной скорости чтения/записи.

hostPath

hostPath монтирует директорию или файл с узла в контейнер. Это позволяет получить доступ к файловой системе узла, но создаёт жёсткую привязку к конкретному узлу:

volumes:
  - name: host-volume
    hostPath:
      path: /data
      type: Directory

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

Типы hostPath:

  • Directory — директория должна существовать
  • DirectoryOrCreate — создать директорию, если не существует
  • File — файл должен существовать
  • Socket — Unix socket должен существовать

configMap и secret

Volume типа configMap и secret монтируют ConfigMap и Secret как файлы. Мы уже рассматривали их в уроке про конфиги:

volumes:
  - name: config-volume
    configMap:
      name: app-config
  - name: secret-volume
    secret:
      secretName: app-secret

Каждый ключ в ConfigMap или Secret становится файлом, значение — содержимым файла.

downwardAPI

downwardAPI предоставляет метаданные Pod в виде файлов. Это позволяет приложению узнать своё имя, namespace, метки и другую информацию:

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
  labels:
    app: myapp
    version: v1.0
spec:
  containers:
    - name: app
      image: busybox
      command: ["sh", "-c", "cat /etc/podinfo/labels && sleep 3600"]
      volumeMounts:
        - name: podinfo
          mountPath: /etc/podinfo
  volumes:
    - name: podinfo
      downwardAPI:
        items:
          - path: "labels"
            fieldRef:
              fieldPath: metadata.labels
          - path: "name"
            fieldRef:
              fieldPath: metadata.name
          - path: "namespace"
            fieldRef:
              fieldPath: metadata.namespace

В директории /etc/podinfo появятся файлы labels, name и namespace с соответствующими значениями. Это полезно для логирования, мониторинга и конфигурации приложения.

PersistentVolumeClaim

Для постоянного хранилища используйте PersistentVolumeClaim (PVC). Это абстракция, которая отделяет запрос на хранилище от конкретной реализации:

volumes:
  - name: data-volume
    persistentVolumeClaim:
      claimName: database-pvc

PVC — это основной механизм работы с постоянным хранилищем в Kubernetes. Рассмотрим его подробнее.

PersistentVolume и PersistentVolumeClaim

PersistentVolume (PV) — это ресурс хранения в кластере, который существует независимо от Pod. PV может быть сетевым диском в облаке (AWS EBS, GCE Persistent Disk), NFS-шарой, локальным диском узла или другим типом хранилища.

PersistentVolumeClaim (PVC) — это запрос на хранилище от пользователя. Разработчик создаёт PVC, указывая размер и требования к хранилищу, а Kubernetes находит подходящий PV и связывает их.

Такое разделение позволяет разработчикам не знать детали инфраструктуры. Разработчик просто запрашивает "10GB хранилища с режимом ReadWriteOnce", а администратор кластера заранее подготавливает PV нужных типов.

Создание PersistentVolume

Администратор создаёт PV, описывая физическое хранилище:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /mnt/data
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - node-1

Этот PV предоставляет 10GB хранилища на узле node-1 в директории /mnt/data. Режим доступа ReadWriteOnce означает, что хранилище может быть смонтировано для чтения/записи только на одном узле.

Режимы доступа:

  • ReadWriteOnce (RWO) — чтение/запись на одном узле
  • ReadOnlyMany (ROX) — чтение на многих узлах
  • ReadWriteMany (RWX) — чтение/запись на многих узлах

Не все типы хранилищ поддерживают все режимы. Например, AWS EBS поддерживает только RWO, а NFS поддерживает все три режима.

Создание PersistentVolumeClaim

Разработчик создаёт PVC, запрашивая хранилище:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: database-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: local-storage

Kubernetes найдёт PV, который удовлетворяет требованиям: имеет нужный storageClassName, достаточный размер и подходящий режим доступа. PV и PVC будут связаны (bound), и хранилище станет доступно для использования.

Посмотрите на статус:

# Посмотреть PV
kubectl get pv

# Посмотреть PVC
kubectl get pvc

# Подробная информация
kubectl describe pv local-pv
kubectl describe pvc database-pvc

Статус Bound означает, что PVC успешно связан с PV.

Использование PVC в Pod

Смонтируйте PVC в Pod:

apiVersion: v1
kind: Pod
metadata:
  name: database-pod
spec:
  containers:
    - name: postgres
      image: postgres:13
      env:
        - name: POSTGRES_PASSWORD
          value: supersecret
      volumeMounts:
        - name: data-volume
          mountPath: /var/lib/postgresql/data
  volumes:
    - name: data-volume
      persistentVolumeClaim:
        claimName: database-pvc

База данных сохранит данные в PVC. При перезапуске Pod данные останутся, так как хранилище существует независимо от Pod.

StorageClass и динамическое создание Volume

Ручное создание PV неудобно — администратор должен заранее создавать множество PV разных размеров. StorageClass решает эту проблему, позволяя автоматически создавать PV по требованию.

StorageClass описывает тип хранилища и параметры его создания:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer

Этот StorageClass использует AWS EBS provisioner для создания дисков типа gp3 с заданными параметрами производительности.

Создайте PVC с указанием StorageClass:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: fast-ssd

Kubernetes автоматически создаст PV нужного размера, используя параметры из StorageClass. Разработчик получает хранилище без участия администратора.

Параметр volumeBindingMode контролирует, когда создаётся хранилище:

  • Immediate — создать сразу при создании PVC
  • WaitForFirstConsumer — создать только когда Pod использует PVC (позволяет выбрать правильную зону доступности)

Параметр allowVolumeExpansion: true позволяет увеличивать размер PVC без пересоздания:

# Увеличить размер PVC
kubectl patch pvc app-pvc -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'

Политики освобождения хранилища

Когда PVC удаляется, возникает вопрос: что делать с данными в PV? Политика persistentVolumeReclaimPolicy контролирует это поведение:

Retain

Политика Retain сохраняет данные и переводит PV в статус Released. PV не может быть использован другим PVC автоматически — администратор должен вручную очистить данные и удалить PV:

persistentVolumeReclaimPolicy: Retain

Используйте Retain для важных данных, которые нельзя случайно удалить.

Delete

Политика Delete удаляет PV и физическое хранилище при удалении PVC:

persistentVolumeReclaimPolicy: Delete

Это удобно для временных данных и тестовых окружений, но опасно для production — случайное удаление PVC приведёт к потере данных.

Recycle (устарела)

Политика Recycle очищает данные (rm -rf /volume/*) и делает PV доступным для нового PVC. Эта политика устарела и не рекомендуется к использованию.

StatefulSet и постоянное хранилище

StatefulSet — это контроллер для stateful-приложений, которым нужны стабильные идентификаторы и постоянное хранилище. В отличие от Deployment, StatefulSet создаёт Pod с предсказуемыми именами и автоматически управляет PVC для каждого Pod.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: database
spec:
  serviceName: database
  replicas: 3
  selector:
    matchLabels:
      app: database
  template:
    metadata:
      labels:
        app: database
    spec:
      containers:
        - name: postgres
          image: postgres:13
          env:
            - name: POSTGRES_PASSWORD
              value: supersecret
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 10Gi
        storageClassName: fast-ssd

StatefulSet создаст три Pod с именами database-0, database-1, database-2 и три PVC с именами data-database-0, data-database-1, data-database-2. Каждый Pod получает свой PVC, который сохраняется даже при перезапуске Pod.

При масштабировании StatefulSet создаёт новые PVC для новых Pod. При уменьшении количества реплик Pod удаляются, но PVC остаются — данные не теряются, и при возвращении Pod к прежнему количеству они используют существующие PVC.

# Посмотреть StatefulSet
kubectl get statefulset

# Посмотреть Pod (обратите внимание на имена)
kubectl get pods

# Посмотреть PVC для каждого Pod
kubectl get pvc

Практический пример: веб-приложение с базой данных

Создадим полноценное приложение с базой данных PostgreSQL и веб-сервером:

# StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
# PersistentVolume
apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: standard
  hostPath:
    path: /mnt/data/postgres
---
# PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: standard
---
# PostgreSQL Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:13
          env:
            - name: POSTGRES_DB
              value: appdb
            - name: POSTGRES_USER
              value: appuser
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: password
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: postgres-storage
              mountPath: /var/lib/postgresql/data
      volumes:
        - name: postgres-storage
          persistentVolumeClaim:
            claimName: postgres-pvc
---
# PostgreSQL Service
apiVersion: v1
kind: Service
metadata:
  name: postgres-service
spec:
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432
  type: ClusterIP
---
# Web Application Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
        - name: webapp
          image: example/webapp:1.0
          env:
            - name: DATABASE_HOST
              value: postgres-service
            - name: DATABASE_PORT
              value: "5432"
            - name: DATABASE_NAME
              value: appdb
            - name: DATABASE_USER
              value: appuser
            - name: DATABASE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: password
          ports:
            - containerPort: 8080
          volumeMounts:
            - name: uploads
              mountPath: /app/uploads
      volumes:
        - name: uploads
          emptyDir: {}

База данных использует PVC для постоянного хранения данных. Веб-приложение использует emptyDir для временного хранения загруженных файлов — для production лучше использовать object storage (S3, GCS) или отдельный PVC.

Резервное копирование и восстановление

Данные в PV нужно регулярно копировать. Есть несколько подходов к резервному копированию:

Snapshot

VolumeSnapshot позволяет создавать моментальные снимки PVC:

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: database-snapshot
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: postgres-pvc

Снимок создаётся на уровне хранилища и не влияет на работу приложения. Для восстановления создайте новый PVC из снимка:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc-restored
spec:
  dataSource:
    name: database-snapshot
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

Backup через Pod

Запустите отдельный Pod для резервного копирования:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: database-backup
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: backup
              image: postgres:13
              command:
                - sh
                - -c
                - pg_dump -h postgres-service -U appuser appdb > /backup/dump-$(date +%Y%m%d).sql
              env:
                - name: PGPASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: db-secret
                      key: password
              volumeMounts:
                - name: backup
                  mountPath: /backup
          restartPolicy: OnFailure
          volumes:
            - name: backup
              persistentVolumeClaim:
                claimName: backup-pvc

CronJob запускается каждый день в 2:00 и создаёт дамп базы данных в отдельном PVC.

Velero

Velero — инструмент для резервного копирования всего кластера, включая PVC:

# Установить Velero
velero install --provider aws --bucket velero-backups

# Создать резервную копию
velero backup create app-backup --include-namespaces default

# Восстановить из резервной копии
velero restore create --from-backup app-backup

Velero поддерживает различные облачные провайдеры и может копировать данные в S3, GCS или Azure Blob Storage.

Лучшие практики

При работе с Volume следуйте рекомендациям для обеспечения надёжности и производительности. Используйте правильный тип хранилища для каждой задачи: emptyDir для временных данных, PVC для постоянных данных, ConfigMap/Secret для конфигурации.

Настраивайте политику освобождения хранилища. Для production-данных используйте Retain, чтобы избежать случайного удаления. Для тестовых окружений удобнее Delete.

Регулярно создавайте резервные копии важных данных. Автоматизируйте процесс с помощью CronJob или специализированных инструментов вроде Velero.

Мониторьте использование хранилища. Настройте алерты на заполнение дисков и автоматическое расширение PVC, если StorageClass поддерживает это.

Используйте StorageClass с volumeBindingMode: WaitForFirstConsumer для облачных хранилищ. Это гарантирует, что PV создаётся в той же зоне доступности, что и Pod.

Тестируйте процесс восстановления из резервных копий. Периодически проверяйте, что backup работает корректно и данные можно восстановить.

Документируйте структуру хранилища. Описывайте, какие PVC использует каждое приложение, что в них хранится и как часто создаются бэкапы.

Заключение

Volume — это механизм для работы с хранилищем данных в Kubernetes. От временного emptyDir до постоянного PersistentVolume — каждый тип Volume решает свои задачи. PersistentVolume и PersistentVolumeClaim отделяют запрос на хранилище от его реализации, StorageClass автоматизирует создание PV.

StatefulSet обеспечивает стабильные идентификаторы и автоматическое управление PVC для stateful-приложений. Резервное копирование через VolumeSnapshot или специализированные инструменты защищает данные от потери.

Правильный выбор типа хранилища и настройка политик обеспечивают надёжность и производительность приложений, работающих с данными в Kubernetes.

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff