Rook czy nie Rook — oto jest pytanie

Rook czy nie Rook — oto jest pytanie

Na początku tego miesiąca, 3 maja, zapowiedziano dużą aktualizację "systemu zarządzania dla rozproszonych magazynów danych w Kubernetes" — Rook 1.0.0. Ponad rok temu już opublikowaliśmy ogólny przegląd Rook. Wtedy też proszono nas, abyśmy podzielili się doświadczeniem w jego praktycznym użyciu — i oto, z okazji tej znaczącej chwili w historii projektu, z radością dzielimy się zebranymi wrażeniami.

Krótko mówiąc, Rook jest zestawem operatorów dla Kubernetes, które w pełni przejmują kontrolę nad wdrażaniem, zarządzaniem, automatycznym przywracaniem takich rozwiązań do przechowywania danych jak Ceph, EdgeFS, Minio, Cassandra, CockroachDB.

Obecnie najbardziej rozwiniętym (i jedynym do stabilnej na tym etapie) rozwiązaniem jest rook-ceph-operator.

Uwaga: wśród ważnych zmian w wersji Rook 1.0.0 związanych z Ceph można odnotować wsparcie dla Ceph Nautilus oraz możliwość użycia NFS dla CephFS lub RGW-bucketów. Z innych wyróżnia się "dojrzałość" wsparcia EdgeFS do poziomu bety.

Tak więc, w tym artykule:

  • odpowiemy na pytanie, jakie korzyści dostrzegamy w używaniu Rook do wdrażania Ceph w klastrze Kubernetes;
  • podzielimy się doświadczeniem i wrażeniami z użytkowania Rook w produkcji;
  • powiemy, dlaczego mówimy Rook’owi "Tak!", oraz o swoich planach na niego.

Zacznijmy od ogólnych koncepcji i teorii.

„Mam przewagę w jedną Wieżę!” (nieznany szachista)

Rook czy nie Rook — oto jest pytanie

Jedną z głównych zalet Rook jest to, że interakcja z magazynami danych odbywa się przez mechanizmy Kubernetes. Oznacza to, że nie trzeba już kopiować poleceń do konfiguracji Ceph z kartki do konsoli.

— Chcesz wdrożyć CephFS w klastrze? Po prostu napisz plik YAML!
­— Co? Chcesz wdrożyć również magazyn obiektów z API S3? Po prostu napisz drugi plik YAML!

Rook został stworzony zgodnie z zasadami typowego operatora. Interakcja z nim odbywa się za pomocą CRD (Definicji Zasobów Niestandardowych), w których opisujemy wymagane przez nas cechy encji Ceph (ponieważ jest to jedyna stabilna realizacja, w artykule domyślnie mowa będzie o Ceph, jeśli nie zaznaczone zostanie inaczej). Zgodnie z zadanymi parametrami operator automatycznie wykona niezbędne polecenia konfiguracyjne.

Szczegóły omówimy na przykładzie tworzenia Object Store, a dokładniej — 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 }}

Podane w ofercie parametry są dość standardowe i raczej nie wymagają komentarza, jednak warto szczególnie zwrócić uwagę na te, które są wyróżnione jako zmienne szablonów.

Ogólna zasada działania sprowadza się do tego, że za pośrednictwem pliku YAML „zamawiamy” zasoby, w wyniku czego operator wykonuje niezbędne komendy i zwraca nam „nie do końca prawdziwy” sekret, z którym możemy następnie pracować. (patrz poniżej). A z zmiennych, które zostały podane powyżej, zostanie zbudowana komenda oraz nazwa sekretu.

Czym jest więc ta komenda? Podczas tworzenia użytkownika dla obiektowego magazynu operator Rook wewnątrz pod’a wykona następujące działania:

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

Wynikiem wykonania tej komendy będzie struktura JSON:

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

Klucze — to, co będzie potrzebne w przyszłości aplikacjom do dostępu do obiektowego magazynu za pośrednictwem S3 API. Operator Rook z nieskrywaną uprzejmością zbiera je i składa w swoim namespace w formie sekretu o nazwie rook-ceph-object-user-{{ $.Values.s3.crdName }}-{{ $.Values.s3.username }}.

Aby skorzystać z danych z tego sekretu, wystarczy dodać je do kontenera jako zmienne środowiskowe. Przykładem może być szablon dla Job, w którym automatycznie tworzymy bucket’y dla każdego środowiska użytkownika:

{{- 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 }}

Wszystkie działania opisane w tym Jobie zostały wykonane w obrębie Kubernetes. Struktury opisane w plikach YAML są złożone w repozytorium Git i wielokrotnie wykorzystywane. W tym widzimy ogromny plus dla inżynierów DevOps oraz procesu CI/CD jako całości.

Z Rook i Rados w radości

Użycie kombinacji Ceph + RBD nakłada pewne ograniczenia na montowanie wolumenów do podów.

W szczególności w namespace musi znajdować się sekretny klucz dostępu do Ceph, aby aplikacje stateful mogły funkcjonować. Jest w porządku, jeśli masz 2-3 środowiska w swoich przestrzeniach nazw: można po prostu skopiować sekret ręcznie. Ale co zrobić, jeśli każde feature dla deweloperów ma mieć swoje własne środowisko z własnym namespace?

U nas rozwiązano ten problem przy pomocy shell-operator, który automatycznie kopiował sekrety do nowych namespace (przykład podobnego hooka opisano w w tym artykule).

#! /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

Jednak podczas korzystania z Rook ten problem po prostu nie istnieje. Proces montowania odbywa się przy użyciu własnych sterowników opartych na Flexvolume lub CSI (na razie w wersji beta) i dlatego nie wymaga sekretów.

Rook automatycznie rozwiązuje wiele problemów, co oraz skłania nas do jego użycia w nowych projektach.

Oblężenie Rook

Zakończymy część praktyczną wdrażaniem Rook i Ceph, aby mieć możliwość przeprowadzania własnych eksperymentów. Aby łatwiej zdobyć tę nieprzyjazną wieżę, deweloperzy przygotowali pakiet Helm. Pobierzmy go:

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

W pliku rook-ceph/values.yaml można znaleźć wiele różnych ustawień. Najważniejsze to wskazanie tolerancji dla agentów i wyszukiwania. O tym, do czego można wykorzystać mechanizm taints/tolerations, szczegółowo mówiłem w w tym artykule.

Krótko mówiąc, nie chcemy, aby pod’y z aplikacją kliencką znalazły się na tych samych node’ach, co dyski do przechowywania danych. Powód jest prosty: w ten sposób działanie agentów Rook nie będzie wpływać na samą aplikację.

Zatem otwieramy plik rook-ceph/values.yaml w ulubionym edytorze i dodajemy na końcu następujący blok:

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

Na każdym węźle zarezerwowanym do przechowywania danych dodajemy odpowiedni taint:

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

Następnie instalujemy wykres Helm poleceniem:

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

Teraz musimy utworzyć klaster i wskazać lokalizację 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"

Sprawdzamy status Ceph — oczekujemy zobaczyć 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

Jednocześnie sprawdzimy, czy pod’y z aplikacją kliencką nie trafiają na zarezerwowane węzły Ceph:

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

Następnie opcjonalnie można skonfigurować dodatkowe komponenty. Szczegóły na ich temat znajdują się w dokumentacji. Zalecamy zainstalowanie dashboardu i toolboxu do zarządzania.

Rook-i-haki: czy Rook wystarcza na wszystko?

Jak widać, rozwój Rook jest w pełnym toku. Jednak wciąż pozostają problemy, które uniemożliwiają nam całkowite odejście od ręcznej konfiguracji Ceph:

  • Żaden sterownik Rook nie potrafi eksportować metryk dotyczących wykorzystania zamontowanych bloków, co pozbawia nas monitoringu.
  • Flexvolume i CSI nie potrafią zmieniać rozmiaru wolumenów (w przeciwieństwie do RBD), dlatego Rook traci użyteczne (a czasami krytycznie potrzebne!) narzędzie.
  • Rook wciąż nie jest tak elastyczny jak tradycyjny Ceph. Jeśli zechcemy skonfigurować, aby pulę dla metadanych CephFS przechowywano na SSD, a same dane na HDD, będzie trzeba ręcznie zapisać oddzielne grupy urządzeń w mapach CRUSH.
  • Mimo że rook-ceph-operator uchodzi za stabilny, aktualnie istnieją pewne problemy przy aktualizacji Ceph z wersji 13 do 14.

Wnioski

„Teraz Rook jest zamknięty przed światem przez pionki, ale wierzymy, że pewnego dnia odegra kluczową rolę w rozgrywce!” (cytat wymyślony specjalnie na potrzeby tego artykułu)

Projekt Rook z pewnością zdobył nasze serca — uważamy, że [ze wszystkimi swoimi plusami i minusami] zasługuje również na Państwa uwagę.

Nasze dalsze plany sprowadzają się do tego, aby uczynić rook-ceph modułem dla addon-operator, co uczyni jego użycie w naszych licznych klastrach Kubernetes jeszcze prostszym i wygodniejszym.

P.S.

Przeczytaj także na naszym blogu:

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster