Як працюють volumes та storage в Kubernetes
Всім привіт! Мене звати Олександр Рихальський, працюю Senior DevOps Engineer у київському центрі розробки SoftServe. У своїй повсякденній роботі дуже часто доводиться розв’язувати інженерні задачі, пов’язані із підтримкою, розвитком й оптимізацією наявних та впровадженням нових хмарних сервісів для замовників із різних куточків земної кулі.
За моїми спостереженнями Kubernetes (k8s) використовується на 8 з 9 проєктах. Навички роботи з ним є обов’язковими у світі DevOps/SRE. K8s — складна система для керування та автоматизації роботи із контейнеризованими застосунками, й охопити її всю за одну публікацію неможливо. Тому в цій статті я хотів би сфокусуватися на тому, як працювати зі storage в k8s, що може бути корисним інженерам із різних IT-спеціальностей.
Нижче зупинюсь на основних складниках системи керування постійними (persistent) та тимчасовими (ephemeral) даними в k8s, як-то диски (volumes) та їх типи (nfs, CSI, EBS/GCE провайдери), StorageClass. А також на тому, яким чином перелічені вище елементи працюють разом. Наприкінці статті поділюсь кількома випадками з практики, що якраз стосуються теми.
Volumes та storage в Kubernetes: theoretical minimum
Volumes та storage в Kubernetes відіграють ключову роль в керуванні постійними (persistent) та тимчасовими або ефемерними (ephemeral) даними для контейнеризованих застосунків. За замовчуванням контейнери в Kubernetes є «stateless» та ephemeral, і будь-які дані, що зберігаються в контейнері, будуть втрачені у випадку його видалення.
Іншим викликом для сторейджу в Kubernetes є випадок, коли кілька контейнерів в рамках одного поду (Pod) повинні мати доступ до одних і тих же файлів. Для розв’язання таких задач в k8s якраз і використовуються persistent volumes та інші абстракції системи сторейджу (storage abstractions).
Основні типи Kubernetes Volumes
За своєю сутністю k8s volume — це певна директорія, можливо, із даними, до якої мають доступ контейнери в поді. Kubernetes підтримує такі основні типи volumes: Ephemeral Volumes та Persistent Volumes.
Ephemeral Volumes існують доти, доки існує под, і разом із видаленням поду видаляється й volume з усіма даними. На противагу Persistent Volumes можуть існувати незалежно від життєвого циклу поду. Спільною рисою даних типів k8s Volumes є те, що дані зберігаються у випадку рестарту поду.
Ephemeral Volumes. Цей тип k8s volumes використовується для розв’язання таких практичних задач:
- Застосунки (applications), які потребують додаткового місця для сторейджу, але для них немає значення чи зберігаються дані після перезапуску подів — або в разі їх видалення і повторного створення. Прикладом можуть бути сервіси, що кешують (caching services). У таких сервісів основне навантаження, як правило, припадає на оперативну пам’ять, обсяг якої не є нескінченним. З метою її звільнення такі застосунки можуть частину даних переносити в повільніший сторейдж.
- Застосунки, які потребують присутності на файловій системі read-only даних на кшталт конфігураційних файлів або secret keys.
До основних видів Ephemeral Volumes належать:
- emptyDir — пуста директорія, що залишається такою при запуску поду, а всі необхідні дані беруться локально із k8s ноди (k8s node). Зазвичай це кореневий каталог або RAM.
- configMap, secret — дають змогу «інжектувати» різні типи Kubernetes даних (ConfigMap, Secret) в под.
- CSI ephemeral volumes — подібні до викладених вище типів volumes, але для своєї роботи вимагають встановлення спеціальних CSI-драйверів.
- generic ephemeral volumes — тип Volumes, що може бути створений за допомогою тих же storage-драйверів, які також підтримують створення persistent volumes
Persistent Volumes. Для застосунків, що потребують збереження даних незалежно від життєвого циклу поду (наприклад, бази даних або інші «stateful» сервіси), kubernetes використовує Persistent Volumes (PV) та Persistent Volume Claims (PVC).
PersistentVolume (PV) — є елементом storage в кластері, який створюється адміністратором або динамічно, або за допомогою так званих Storage Classes. Життєвий цикл PV не залежить від будь-якого поду, що його використовує.
PersistentVolumeClaim (PVC) — цей k8s-об’єкт можна розглядати як запит від користувача на створення storage. Якщо провести паралель із подом, то под використовує наявні ресурси k8s ноди, а PVC використовує ресурси PV. Як і у випадку із подом, ми можемо вказати, скільки ресурсів (CPU та memory) він може споживати. У випадку PVC ми вказуємо розмір необхідного дискового простору та режим доступу (ReadWriteOnce, ReadOnlyMany, ReadWriteMany, див. AccessModes).
PersistentVolumeClaims дають можливість використовувати лише абстрактні ресурси. Але для розв’язання тих чи інших задач може виникнути необхідність мати PersistentVolumes із різними параметрами продуктивності (IOPS, пропускна здатність тощо). Для того, щоб надати користувачу можливість вибирати тип PV без розкриття технічних деталей імплементації сторейджу, існує окремий тип k8s-ресурсу — StorageClass.
StorageClass є механізмом, що дає змогу адміністраторам описати класи або різновиди storage, які пропонуються. Різні класи storage можуть відповідати різним рівням QoS, політикам резервного копіювання, storage backend (NFS, EBS, gceDisk тощо). Кожен StorageClass-об’єкт містить такі поля, як provisioner, parameters та reclaimPolicy, що використовуються при динамічному створенні PersistentVolume, який належить до даного класу відповідно до PersistentVolumeClaim (PVC) специфікації.
Lifecycle of a volume and claim, або Як це все працює разом
Як було сказано вище, PV є ресурсом в k8s-кластері, а PVC — запит на ці ресурси. Взаємодію між PV та PVC можна описати за допомогою представленої нижче схеми Kubernetes cookbook, згідно з якою процес виділення Storage можна розділити на такі етапи.
- Provisioning. PVs можуть створюватися статично або динамічно. При статичному створенні PV всі операції здійснюються адміністратором k8s-кластера. Динамічне створення PV здійснюється за допомогою StorageClasses: у PVC вказується, який storage class використовувати, але задані StorageClasses мають бути встановленими в k8s-кластері заздалегідь.
- Binding. На цьому етапі користувач створює PVC із заданими об’ємом storage та режимом доступу. Після цього Kubernetes control plane до кожного новоствореного PVC знаходить відповідний PV і зв’язує (bind) їх.
- Using. Kubernetes-поди використовують PVC як volume. При цьому кластер відстежує PVC та монтує відповідний volume до вказаного поду.
На практиці все вказане вище може виглядати наступним чином. В специфікації поду в секції «volumes» користувач вказує назву PVC під параметром persistentVolumeClaim. Точка монтування даного volume вказується окремо для кожного контейнеру в поді.
Наприклад:
apiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: my-container image: my-image volumeMounts: - mountPath: "/data" name: my-volume volumes: - name: my-volume persistentVolumeClaim: claimName: my-pvc
Де PVC з назвою «my-pvc» використовується як persistent storage для поду «my-pod», точкою монтування в контейнері є директорія /data.
У випадку використання StorageClass маніфест для PVC може виглядати так:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc spec: storageClassName: fast accessModes: - ReadWriteOnce resources: requests: storage: 10Gi
В цьому прикладі ми за допомогою PVC створюємо запит на виділення 10Gi дискового простору, використовуючи StorageClass «fast».
Приклад із власного досвіду
Кейс 1: обмеження кількості PV, які можна примонтувати до одного k8s ноду в Cloud
Героями цієї історії є AWS Cloud та EKS (Amazon Elastic Kubernetes Service). Клієнт мав багато k8s-кластерів різного розміру від 30 до 200 нод кожен залежно від енву. Основним типом інстансів був С5, R5, k8s-версія на той момент була 1.23. Команд розробників та QA-інженерів також працювало немало і щоб кожна із них могла працювати над своїми задачами, не впливаючи на роботу інших, їм пропонувалось створювати для роботи повну копію їхнього продукту. Продукт являв собою цілу екосистему, що складалась із 30+ мікросервісів, і з них 12 потребували persistent storage (PV+PVC). Бекендом до сторейджу виступав EBS CSI Driver.
До певного моменту все працювало чудово, але в один прекрасний спринт розробники почали скаржитися на те, що «падають» CI/CD джоби, які створюють відповідні енви й багато сервісів не є доступними. У процесі аналізу було виявлено, що відповідні поди «застрягали» на стадії створення в очікуванні аттачменту відповідного PV. З погляду кластера спостерігалось багато подів, що застрягли в стані «Pending».
При цьому describe pod давав наступні записи в секції івентів:
FailedScheduling ….. pod has unbound immediate PersistentVolumeClaims
Далі настав час читати документацію і виявилося, що є два типи лімітів за кількістю дисків, які можна приєднати до одного інстансу:
1. Ліміти Cloud-провайдера.
2. Ліміти Kubernetes. Зараз про цю проблему є окрема сторінка в документації, але на той момент довелось інформацію збирати по частинах. В моєму випадку в Kubernetes scheduler були встановлено ліміти для Amazon Elastic Block Store (EBS) за замовчуванням в 39 volumes per Node. Але згідно лімітів самого AWS для інстансів типу C5, R5 можна було приєднати лише 25 EBS дисків. Тобто в умовах «низького» навантаження на k8s-кластер все працювало нормально, але коли багато команд розробників майже в один і той же проміжок часу (часто при підготовці до релізу) почали створювати собі енви, існувала висока ймовірність «впертись» в обмеження.
Які можна запропонувати стратегії виходу із цієї ситуації:
- Збільшення кількості інстансів у кластері.
- Модернізація безпосередньо мікросервісів із метою зменшення кількості необхідних PV+PVC. В моєму випадку це можна було зробити, бо енв мав кілька інстансів Elasticsearch та Postgresql, для яких була технічна можливість використовувати один Deployment/StatefulSet замість
2-3. - Міграція із persistent volume на NFS. Тобто, міграція з EBS на EFS.
Що ж до конкретно мого кейсу, то як швидкий фікс брали опцію 1 — тимчасове збільшення кількості інстансів у кластері, щоб розблокувати команди розробників і виграти трохи часу. А далі — опція 3, де була виконана міграція з EBS на EFS.
Кейс 2: EFS throughput
Другий кейс є продовженням першого і пов’язаний з особливостями роботи з EFS (NFS у виконанні AWS), а точніше — із її пропускною здатністю. Згідно з документацією, EFS має кілька типів налаштувань пропускної здатності (throughput):
- Elastic throughput — чудовий вибір, коли вимоги до пропускної здатності важко передбачити через те, що ваші сервіси можуть мати несподівані «спайки» — короткочасне і несподіване зростання кількості ресурсів, які споживає аппка або сервіс (більше тут: Elastic throughput).
- Provisioned throughput — цей режим найкраще підходить до випадків, коли точно відомо, яка пропускна здатність необхідна для EFS (наприклад, 75 Мбіт/с) і дозволяє статично задати саме бажані значення (більше тут: Provisioned throughput).
- Bursting throughput — цей режим має сенс встановлювати якщо ми хочемо, щоб пропускна здатність збільшувалась пропорційно збільшенню об’єму даних, що зберігається в EFS (більше тут: Bursting throughput).
Від самого початку міграції k8s storage на EFS із погляду економії коштів був встановлений режим Bursting throughput, бо залежно від енву мали від
Висновок
Розуміння принципів роботи storage підсистеми в k8s в повсякденній роботі може значно прискорити процес імплементації нових сервісів, а також скоротити час на пошук та діагностику проблем, що виникають із роботою мікросервісів.
Маю надію, що матеріал стане вам у пригоді як для розвитку загальної ерудиції та у професійній діяльності, а також допоможе уникнути описаних вище проблем.
15 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів