Pull to refresh
Флант
DevOps-as-a-Service, Kubernetes, обслуживание 24×7

Rook или не Rook — вот в чём вопрос

Reading time 7 min
Views 11K


В начале этого месяца, 3 мая, был анонсирован крупный релиз «системы управления для распределённых хранилищ данных в Kubernetes» — Rook 1.0.0. Более года назад мы уже публиковали общий обзор Rook. Тогда же нас просили рассказать об опыте его использования на практике — и вот, как раз к столь значимой вехе в истории проекта, мы рады поделиться накопленными впечатлениями.

Если кратко, Rook представляет собой набор операторов для Kubernetes, которые полностью берут под контроль развертывание, управление, автоматическое восстановление таких решений для хранения данных, как Ceph, EdgeFS, Minio, Cassandra, CockroachDB.

На данный момент самым развитым (и единственным в стабильной стадии) решением является rook-ceph-operator.

Примечание: среди значимых изменений в релизе Rook 1.0.0, связанных с Ceph, можно отметить поддержку Сeph Nautilus и возможность использовать NFS для CephFS- или RGW-бакетов. Из прочих выделяется «созревание» поддержки EdgeFS до уровня беты.

Итак, в этой статье мы:

  • ответим на вопрос, какие плюсы видим в использовании Rook для развертывания Ceph в кластере Kubernetes;
  • поделимся опытом и впечатлениями от использования Rook в production;
  • расскажем, почему мы говорим Rook’у «Да!», и о своих планах на него.

Начнём с общих концепций и теории.

«У меня преимущество в одну Ладью!» (неизвестный шахматист)




Одним из главных преимуществ Rook является то, что взаимодействие с хранилищами данных ведется через механизмы Kubernetes. Это означает, что больше не нужно копировать команды для настройки Ceph с листочка в консоль.

— Хочешь развернуть в кластере CephFS? Просто напиши YAML-файл!
­— Что? Хочешь развернуть ещё и object store с S3 API? Просто напиши второй YAML-файл!


Rook создан по всем правилам типичного оператора. Взаимодействие с ним происходит при помощи CRD (Custom Resource Definitions), в которых мы описываем необходимые нам характеристики сущностей Ceph (поскольку это единственная стабильная реализация, по умолчанию в статье будет идти речь именно про Ceph, если явно не указано иное). Согласно заданным параметрам, оператор автоматически выполнит необходимые для настройки команды.

Конкретику давайте рассмотрим на примере создания Object Store, а точнее — CephObjectStoreUser.

apiVersion: ceph.rook.io/v1
kind: CephObjectStore
metadata:
  name: {{ .Values.s3.crdName }}
  namespace: kube-rook
spec:
  metadataPool:
    failureDomain: host
    replicated:
      size: 3
  dataPool:
    failureDomain: host
    erasureCoded:
      dataChunks: 2
      codingChunks: 1
  gateway:
    type: s3
    sslCertificateRef:
    port: 80
    securePort:
    instances: 1
    allNodes: false
---
apiVersion: ceph.rook.io/v1
kind: CephObjectStoreUser
metadata:
  name: {{ .Values.s3.crdName }}
  namespace: kube-rook
spec:
  store: {{ .Values.s3.crdName }}
  displayName: {{ .Values.s3.username }}

Указанные в листинге параметры достаточно стандартны и вряд ли нуждаются в комментариях, однако стоит обратить особое внимание на те, что выделены в переменные шаблонов.

Общая схема работы сводится к тому, что через YAML-файл мы «заказываем» ресурсы, для чего оператор выполняет нужные команды и возвращает нам «не самый настоящий» секрет, с которым мы можем дальше работать (см. ниже). А из переменных, что указаны выше, будет составлена команда и имя секрета.

Что же это за команда? При создании пользователя для объектного хранилища Rook-оператор внутри pod’а выполнит следующее:

radosgw-admin user create --uid="rook-user" --display-name="{{ .Values.s3.username }}"

Результатом выполнения этой команды станет JSON-структура:

{
    "user_id": "rook-user",
    "display_name": "{{ .Values.s3.username }}",
    "keys": [
        {
           "user": "rook-user",
           "access_key": "NRWGT19TWMYOB1YDBV1Y",
           "secret_key": "gr1VEGIV7rxcP3xvXDFCo4UDwwl2YoNrmtRlIAty"
        }
    ],
    ...
}

Keys — то, что потребуется в будущем приложениям для доступа к объектному хранилищу через S3 API. Rook-оператор любезно выбирает их и складывает в свой namespace в виде секрета с именем rook-ceph-object-user-{{ $.Values.s3.crdName }}-{{ $.Values.s3.username }}.

Чтобы использовать данные из этого секрета, достаточно добавить их в контейнер в качестве переменных окружения. Как пример приведу шаблон для Job, в котором мы автоматически создаем bucket’ы для каждого пользовательского окружения:

{{- range $bucket := $.Values.s3.bucketNames }}
apiVersion: batch/v1
kind: Job
metadata:
  name: create-{{ $bucket }}-bucket-job
  annotations:
    "helm.sh/hook": post-install
    "helm.sh/hook-weight": "2"
spec:
  template:
    metadata:
      name: create-{{ $bucket }}-bucket-job
    spec:
      restartPolicy: Never
      initContainers:
      - name: waitdns
        image: alpine:3.6
        command: ["/bin/sh", "-c", "while ! getent ahostsv4 rook-ceph-rgw-{{ $.Values.s3.crdName }}; do sleep 1; done" ]
      - name: config
        image: rook/ceph:v1.0.0
        command: ["/bin/sh", "-c"]
        args: ["s3cmd --configure --access_key=$(ACCESS-KEY) --secret_key=$(SECRET-KEY) -s --no-ssl --dump-config | tee /config/.s3cfg"]
        volumeMounts:
        - name: config
          mountPath: /config
        env:
        - name: ACCESS-KEY
          valueFrom:
            secretKeyRef:
              name: rook-ceph-object-user-{{ $.Values.s3.crdName }}-{{ $.Values.s3.username }}
              key: AccessKey
        - name: SECRET-KEY
          valueFrom:
            secretKeyRef:
              name: rook-ceph-object-user-{{ $.Values.s3.crdName }}-{{ $.Values.s3.username }}
              key: SecretKey
      containers:
      - name: create-bucket
        image: rook/ceph:v1.0.0
        command: 
        - "s3cmd"
        - "mb"
        - "--host=rook-ceph-rgw-{{ $.Values.s3.crdName }}"
        - "--host-bucket= "
        - "s3://{{ $bucket }}"
        ports:
        - name: s3-no-sll
          containerPort: 80
        volumeMounts:
        - name: config
          mountPath: /root
      volumes:
      - name: config
        emptyDir: {}
---
{{- end }}

Все действия, перечисленные в этом Job’е, были произведены, не выходя за рамки Kubernetes. Описанные в YAML-файлах структуры сложены в Git-репозиторий и многократно повторно использованы. В этом мы видим огромный плюс для DevOps-инженеров и процесса CI/CD в целом.

С Rook и Rados в радость


Использование связки Ceph + RBD накладывает определенные ограничения на монтирование томов к pod’ам.

В частности, в namespace обязательно должен лежать секрет для доступа к Ceph, чтобы stateful-приложения могли функционировать. Нормально, если у вас есть 2-3 окружения в своих пространствах имен: можно пойти и скопировать секрет вручную. Но что делать, если на каждую feature для разработчиков создается отдельное окружение со своим namespace?

У себя мы решили данную проблему при помощи shell-operator, который автоматически копировал секреты в новые namespace (пример подобного хука описан в этой статье).

#! /bin/bash

if [[ $1 == “--config” ]]; then
   cat <<EOF
{"onKubernetesEvent":[
 {"name": "OnNewNamespace",
  "kind": "namespace",
  "event": ["add"]
  }
]}
EOF
else
    NAMESPACE=$(kubectl get namespace -o json | jq '.items | max_by( .metadata.creationTimestamp ) | .metadata.name')
    kubectl -n ${CEPH_SECRET_NAMESPACE} get secret ${CEPH_SECRET_NAME} -o json | jq ".metadata.namespace=\"${NAMESPACE}\"" | kubectl apply -f -
fi

Однако при использовании Rook данной проблемы попросту не существует. Процесс монтирования происходит при помощи собственных драйверов на базе Flexvolume или CSI (пока в бета-стадии) и поэтому не требует секретов.

Rook автоматически решает многие проблемы, что и подталкивает нас использовать его в новых проектах.

Осада Rook


Завершим практическую часть разворачиванием Rook и Ceph для возможности проведения собственных экспериментов. Для того, чтобы брать штурмом эту неприступную башню было легче, разработчики подготовили Helm-пакет. Давайте скачаем его:

$ helm fetch rook-master/rook-ceph --untar --version 1.0.0

В файле rook-ceph/values.yaml можно найти множество различных настроек. Самое важное — указать tolerations для агентов и поиска. Для чего можно использовать механизм taints/tolerations, мы подробно рассказывали в этой статье.

Если вкратце, мы не хотим, чтобы pod’ы с клиентским приложением располагались на тех же узлах, где расположены диски для хранения данных. Причина проста: так работа агентов Rook не будет влиять на само приложение.

Итак, открываем файл rook-ceph/values.yaml любимым редактором и добавляем в конец следующий блок:

discover:
  toleration: NoExecute
  tolerationKey: node-role/storage
agent:
  toleration: NoExecute
  tolerationKey: node-role/storage
  mountSecurityMode: Any

На каждый узел, зарезервированный под хранение данных, добавляем соответствующий taint:

$ kubectl taint node ${NODE_NAME} node-role/storage="":NoExecute

После чего устанавливаем Helm-чарт командой:

$ helm install --namespace ${ROOK_NAMESPACE} ./rook-ceph

Теперь необходимо создать кластер и указать местоположение OSD:

apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
  clusterName: "ceph"
  finalizers:
  - cephcluster.ceph.rook.io
  generation: 1
  name: rook-ceph
spec:
  cephVersion:
    image: ceph/ceph:v13
  dashboard:
    enabled: true
  dataDirHostPath: /var/lib/rook/osd
  mon:
    allowMultiplePerNode: false
    count: 3
  network:
    hostNetwork: true
  rbdMirroring:
    workers: 1
  placement:
    all:
      tolerations:
      - key: node-role/storage
        operator: Exists
  storage:
    useAllNodes: false
    useAllDevices: false
    config:
      osdsPerDevice: "1"
      storeType: filestore
    resources:
      limits:
        memory: "1024Mi"
      requests:
        memory: "1024Mi"
    nodes:
    - name: host-1
      directories:
      - path: "/mnt/osd"
    - name: host-2
      directories:
      - path: "/mnt/osd"
    - name: host-3
      directories:
      - path: "/mnt/osd"

Проверяем статус Ceph — ожидаем увидеть HEALTH_OK:

$ kubectl -n ${ROOK_NAMESPACE} exec $(kubectl -n ${ROOK_NAMESPACE} get pod -l app=rook-ceph-operator -o name -o jsonpath='{.items[0].metadata.name}') -- ceph -s

Заодно проверим, что pod’ы с клиентским приложением не попадают на зарезервированные под Ceph узлы:

$ kubectl -n ${APPLICATION_NAMESPACE} get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName

Далее по желанию настраиваются дополнительные компоненты. Подробнее о них указано в документации. Для администрирования настоятельно рекомендуем установить dashboard и toolbox.

Rook’и-крюки: на всё ли хватает Rook?


Как видно, разработка Rook идёт полным ходом. Но всё ещё остаются проблемы, которые не позволяют нам полностью отказаться от ручной настройки Ceph:

  • Ни один драйвер Rook не умеет экспортировать метрики по использованию смонтированных блоков, что лишает нас мониторинга.
  • Flexvolume и CSI не умеют изменять размер томов (в отличие от того же RBD), поэтому Rook лишается полезного (а иногда и критически нужного!) инструмента.
  • Rook всё ещё не такой гибкий, как обычный Ceph. Если мы захотим настроить, чтобы пул для метаданных CephFS хранился на SSD, а сами данные — на HDD, потребуется прописывать отдельные группы устройств в CRUSH maps вручную.
  • Несмотря на то, что rook-ceph-operator считается стабильным, на данный момент существуют определенные проблемы при обновлении Ceph c версии 13 до 14.

Выводы


«Сейчас Ладья закрыта от внешнего мира пешками, но мы верим, что однажды она сыграет решающую роль в партии!» (цитата придумана специально для этой статьи)

Проект Rook, несомненно, завоевал наши сердца — мы считаем, что [со всеми своими плюсами и минусами] он точно заслуживает и вашего внимания.

У нас же дальнейшие планы сводятся к тому, что сделать rook-ceph модулем для addon-operator, что сделает его использование в наших многочисленных Kubernetes-кластерах ещё более простым и удобным.

P.S.


Читайте также в нашем блоге:

Tags:
Hubs:
+26
Comments 4
Comments Comments 4

Articles

Information

Website
flant.ru
Registered
Founded
Employees
201–500 employees
Location
Россия
Representative
Тимур Тукаев