Rook чи не Rook — ось у чому питання

Rook чи не Rook — ось у чому питання

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

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

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

Примітка: серед значних змін у релізі Rook 1.0.0, пов'язаних з Ceph, можна відзначити підтримку Seph Nautilus і можливість використовувати NFS для CephFS або RGW-бакетів. З-поміж інших виділяється «дозрівання» підтримки EdgeFS до рівня бети.

Отже, у цій статті ми:

  • відповімо на питання, які плюси бачимо у використанні Rook для розгортання Ceph у кластері Kubernetes;
  • поділимося досвідом та враженнями від використання Rook у production;
  • Розкажемо, чому ми говоримо Rook'у «Так!», і про свої плани на нього.

Почнемо із загальних концепцій та теорії.

«У мене перевага в одну Ладью!» (Невідомий шахіст)

Rook чи не 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, ми докладно розповідали цієї статті.

Якщо коротко, ми не хочемо, щоб під'ї з клієнтським додатком розташовувалися на тих же вузлах, де розташовані диски для зберігання даних. Причина проста: так робота агентів 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 з версії 13 до 14.

Висновки

«Зараз Човна закрита від зовнішнього світу пішаками, але ми віримо, що якось вона відіграє вирішальну роль у партії!» (цитата вигадана спеціально для цієї статті)

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

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

PS

Читайте також у нашому блозі:

Джерело: habr.com

Додати коментар або відгук