TopoLVMによるPVC-basedなRook/Ceph with Pod Topology Spread Constraints

はじめに

2020/07/03にJapan Rook Meetup #3 - connpassにて「ML環境でのRook/Ceph」と題してお話させていただきました.

speakerdeck.com

話の中でCSIにTopoLVMを使用したOSDs on PVCs環境を用いたデモを行ったのですが,その際使用した環境を構築した手順を残しておきます. また,本記事で紹介する構築はシングルマスター構成K8sクラスタですが,マスター冗長構成K8sクラスタにおいても同様の方法で構築することができます.

前提条件

スライド中にも記載しているのですが,以下の環境で構築します. パブリッククラウドを用いた環境ではないためパブリッククラウドを使用される際はご注意ください.

TopoLVMの導入

はじめにTopoLVMについて軽く紹介した後に実際に構築していきます.

TopoLVMとは

Kubernetes(以下K8s)でストレージを扱う際,マネージドK8sサービスではCSIドライバが用意されており,K8sでのダイナミックプロビジョニングなど非常に容易に行えるようになっています.(例えばEKSのAmazon EBS CSIドライバー)しかしながらベアメタルサーバなどの,特にローカルストレージを束ねてK8sのストレージとして使用しようとすると何らかのCSIドライバを用意してあげなくてはいけません.

TopoLVMではPVCが発行されるとLinuxのLogical Volumeの仕組みを利用し,TopoLVM用のK8s拡張スケジューラによって適切にPVを作成する Nodeを選択しLogical VolumeのVGからLVを切り出してダイナミックプロビジョニングしてくれます.

dynamic_provisioning_by_topolvm
図1: TopoLVMによるダイナミックプロビジョニングの様子

PVC-basedなRook/Cephを使う場合hostpathを利用して手動でPVを作成することも可能ですが,今回はTopoLVMをCSIドライバとして使用することでダイナミックプロビジョニングを可能にします.

より詳しいTopoLVMのアーキテクチャなどについてはTopoLVMを作成しているサイボウズさんのブログに詳しくまとめられているのでそちらをご覧ください.

blog.cybozu.io

TopoLVM準備

基本的に以下の公式ドキュメントに則って構築していきますので適宜参照してください.またこの節で行う工程はストレージを使用する全てのNodeで必須です.

github.com

まずはじめに使用ポートを開放します.

$ sudo ufw allow 9251

次にlvm2をインストールし,volume groupを作成していきます. 今回volume groupに使用する物理デバイス名は「/dev/disk/by-id/」を参照させます.物理デバイス名は各環境に合わせて以下作業を行ってください.

$ sudo apt install -y lvm2
$ sudo vgcreate myvg scsi-360022480166e76746b201caa42ba7db7 \
scsi-3600224804f19c35d51d9ec43cb37351a

上記ではvg名をmyvgとしていますが,任意の名前を使用可能です.

次にsystemdとしてlvmdを動かしていきます. 事前に以下からlvmdのビルド済みファイルをダウンロードして おいてください.本記事では執筆時点で最新版のv0.5.0を使用していきます.

github.com

次に以下からlvmd.serviceをダウンロードしてきてください.

github.com

ダウンロードできたら配置し,起動していきます.

$ sudo mkdir /opt/sbin
$ mv lvmd /opt/sbin/lvmd
$ mv lvmd.service /etc/systemd/system/lvmd.service
$ sudo systemctl start lvmd

topolvm-schedulerの導入

topolvmではkube-schedulerを拡張するscheduler-extenderを使用しています.本節ではkubeadmを使用してK8sクラスタを構築する前提でtopolvm-schedulerの準備をしていきます.

まずはじめに使用するファイルを以下からダウンロードしてきてください.

github.com

次にダウンロードしてきたフォルダの「deploy/scheduler-config/scheduler-donfig.yaml」「deploy/scheduler-config/scheduler-policy.cfg」を以下のように配置します.

$ mkdir /etc/kubernetes/topolvm/
$ mv deploy/scheduler-config/* /etc/kubernetes/topolvm/

K8sクラスタの構築

共通事前準備

ポートを開放していきます.

  • control plane node
$ sudo ufw allow 53 &&\
sudo ufw allow 6443-6444 &&\
sudo ufw allow 6789 &&\
sudo ufw allow 2379:2380/tcp &&\
sudo ufw allow 8285 &&\
sudo ufw allow 8472 &&\
sudo ufw allow 9153 &&\
sudo ufw allow 10000 &&\
sudo ufw allow 10250:10252/tcp &&\
sudo ufw allow 31416
  • node
$ sudo ufw allow 53 &&\
sudo ufw allow 6444 &&\
sudo ufw allow 8225 &&\
sudo ufw allow 8472 &&\
sudo ufw allow 9153 &&\
sudo ufw allow 10000 &&\
sudo ufw allow 10250 &&\
sudo ufw allow 30000:32767/tcp &&\
sudo ufw allow 31416

Control Plane Node

まずはじめに以下のようなkubeadm-configファイル「kubeadm_config.yaml」を作成してtopolvm-schedulerを使用できるK8sクラスタを構築していきます.

apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: v1.18.3
scheduler:
  extraVolumes:
    - name: "config"
      hostPath: /etc/kubernetes/topolvm/
      mountPath: /var/lib/scheduler
      readOnly: true
  extraArgs:
    config: /var/lib/scheduler/scheduler-config.yaml
networking:
  podSubnet: "10.244.0.0/16"

上記のkubeadm-configファイルはCNIとしてflannelを使用する前提ですので,他のCNIを使用する場合は適宜変更してください.

作成したkubeadm-configを使用してK8sクラスタを構築します.

$ kubeadm init --upload-certs --config=/etc/kubeadm/kubeadm_config.yaml
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

CNIをデプロイします.

$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml 

Node

kubeadm initの最後に出力されたトークンを使用してK8sクラスタに参加させます.

$ kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \
>     --discovery-token-ca-cert-hash sha256:hogehoge

topolvmのデプロイ

事前準備

次節からではhelm3とkustmizeを使用してアプリケーションをデプロイしていくため事前準備としてhelm3とkustmizeをインストールしていきます.既にインストール済みの方は飛ばしていただいて大丈夫です.

helm3

ubuntu環境では以下のドキュメントにしたがってインストールしていきます.

helm.sh

curl https://helm.baltorepo.com/organization/signing.asc | sudo apt-key add -
$ sudo apt-get install apt-transport-https --yes
$ echo "deb https://baltocdn.com/helm/stable/debian/ all main" | \
sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
$ sudo apt-get update
$ sudo apt-get install helm

kustomize

以下の公式Githubから最新のバイナリファイルをダウンロードしてきてください. 本記事ではv3.6.1を使用しています.

github.com

ダウンロードしたら,実行権限を与えて配置します.

$ tar  -vfxz kustomize_v3.8.0_linux_amd64.tar.gz
$ chmod -x kustomize_v3.8.0_linux_amd64
$ mv  kustomize_v3.8.0_linux_amd64 /usr/local/bin/kustomize

デプロイ

まずはじめにtopolvmm-controllerが使用する自己証明書発行のためにcert-managerをデプロイします.

cert-manager.io

今回はhelm3を使用してデプロイしていきます.

$ helm repo add jetstack https://charts.jetstack.io
$ kubectl create namespace cert-manager
$ helm install cert-manager --namespace cert-manager jetstack/cert-manager \
--values $HOME/K8s_manifest/certmanager/values.yaml --version v0.15.1

次にkustomizeを用いてTopoLVMをデプロイしていきます. マニフェストtopolvm-schedulerの導入でダウンロードしてきた「topolvm/deplot/manifest/」を使用していきます.

$ kubectl label ns kube-system topolvm.cybozu.com/webhook=ignore
$ kustomize build topolvm/deploy/manifest/overlays/daemonset-scheduler | kubectl apply -f -

次に,node Podが全て正常に起動したことを確認した後に,以下を実行します.

$ kubectl get po -n topolvm-system -l app.kubernetes.io/name=node
NAME         READY   STATUS    RESTARTS   AGE
node-b6r52   3/3     Running   0          6h1m
node-d9dm4   3/3     Running   0          6h33m
node-g7d92   3/3     Running   0          5h58m
node-kk5wv   3/3     Running   0          6h33m
node-qttzx   3/3     Running   0          6h33m
node-w9nbc   3/3     Running   0          6h5m
$
$ kubectl apply -f topolvm/deploy/manifest/base/certificates.yaml
$
$ kubectl get po -n topolvm-system
NAME                          READY   STATUS    RESTARTS   AGE
controller-6457d85455-8ks5k   5/5     Running   0          6h34m
controller-6457d85455-9tqhf   5/5     Running   0          6h34m
node-b6r52                    3/3     Running   0          6h2m
node-d9dm4                    3/3     Running   0          6h34m
node-g7d92                    3/3     Running   0          5h59m
node-kk5wv                    3/3     Running   0          6h34m
node-qttzx                    3/3     Running   0          6h34m
node-w9nbc                    3/3     Running   0          6h6m
topolvm-scheduler-4qvwm       1/1     Running   0          6h34m

以上でTopoLVMの準備は完了です.

Rook/Cephのデプロイ

事前準備

まずはじめに各Nodeにrackをラベルとして与えます. 「vm-w0」のようなところは適切なホスト名に置き換えてください.

$ kubectl label node vm-w0 topology.rook.io/rack=rackA
$ kubectl label node vm-w1 topology.rook.io/rack=rackA
$ kubectl label node vm-w2 topology.rook.io/rack=rackB
$ kubectl label node vm-w3 topology.rook.io/rack=rackB
$ kubectl label node vm-w4 topology.rook.io/rack=rackC
$ kubectl label node vm-w5 topology.rook.io/rack=rackC

デプロイ

「common.yaml」「operator.yaml」をデプロイしていきます. ここは通常のHOST-basedパターンと同様です.

$ deploy K8s_manifest/rook-1.3.5/cluster/examples/kubernetes/ceph/common.yaml
$ kubectl apply -f K8s_manifest/rook-1.3.5/cluster/examples/kubernetes/ceph/operator.yaml

次に「cluster-on-pvc.yaml」を以下のように書き換えます. 以下のマニフェストでは発表資料と同様に10GBのOSDを12個作成する構成になっています. またPodTopologySpreadConstaraintsにより,rack障害耐性,rack内node障害耐性を持たせた構成になっています. 注意点として必ず2箇所のstorageClassNameの指定はtopolvm-provisionerにしてください.

apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
  name: rook-ceph
  namespace: rook-ceph
spec:
  dataDirHostPath: /var/lib/rook
  mon:
    count: 3
    allowMultiplePerNode: false
    volumeClaimTemplate:
      spec:
        storageClassName: topolvm-provisioner
        resources:
          requests:
            storage: 3Gi
  cephVersion:
    image: ceph/ceph:v14.2.9
    allowUnsupported: false
  skipUpgradeChecks: false
  continueUpgradeAfterChecksEvenIfNotHealthy: false
  mgr:
    modules:
    - name: pg_autoscaler
      enabled: true
  dashboard:
    enabled: true
    ssl: true
  crashCollector:
    disable: false
  storage:
    storageClassDeviceSets:
    - name: topolvm-ssd-cluster
      count: 12
      portable: false
      tuneSlowDeviceClass: true
      placement:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - rook-ceph-osd
                - key: app
                  operator: In
                  values:
                  - rook-ceph-osd-prepare
              topologyKey: kubernetes.io/hostname
        topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.rook.io/rack
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - rook-ceph-osd
              - rook-ceph-osd-prepare
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
          # whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - rook-ceph-osd
              - rook-ceph-osd-prepare
      resources:
      volumeClaimTemplates:
      - metadata:
          name: data
        spec:
          resources:
            requests:
              storage: 10Gi
          storageClassName: topolvm-provisioner
          volumeMode: Block
          accessModes:
            - ReadWriteOnce
  disruptionManagement:
    managePodBudgets: false
    osdMaintenanceTimeout: 30
    manageMachineDisruptionBudgets: false
    machineDisruptionBudgetNamespace: openshift-machine-api

CephFSのデプロイ

最後にCephFSもrack障害耐性,rack内node障害耐性を持たせてデプロイします.

以下のように「filesystem.yaml」を設定してください.

apiVersion: ceph.rook.io/v1
kind: CephFilesystem
metadata:
  name: tenzenfs
  namespace: rook-ceph
spec:
  metadataPool:
    failureDomain: rack
    replicated:
      size: 3
      requireSafeReplicaSize: true
  dataPools:
    - failureDomain: rack
      replicated:
        size: 3
        requireSafeReplicaSize: true
      compressionMode: none
  preservePoolsOnDelete: true
  metadataServer:
    activeCount: 1
    activeStandby: true
    placement:
       podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - rook-ceph-mds
            topologyKey: kubernetes.io/hostname
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - rook-ceph-mds
              topologyKey: topology.rook.io/rack
    annotations:
    resources:

デプロイしていきます.

$ kubectl apply -f  filesystem.yaml

以上でjapan rook meetup #3 Hands onで使用した環境が出来上がります.

おわりに

今回はmeetupで使用した環境について紹介しました. 参考になれば幸いです.

Prometheus + Grafana を用いた Rook Ceph クラスタの監視

はじめに

この記事は,Rookと仲間たち、クラウドネイティブなストレージの Advent Calendar 2020 20日目の記事です.

元々参加する予定はなかったのですが,悪い大人に見つかってしまったのと,昨年の Rook アドベントカレンダーには読者として大変お世話になったこともあり,参加することになりました.

本記事では,以前 DCGM Exporter を用いた Kubernetes における NVIDIA GPU 監視環境の構築 の記事で Kubernetes 上に構築した Prometheus Grafana 環境に Rook Ceph の監視環境を構築していきます.Kubernetes 上への Prometheus や Grafana の構築はそちらを参照してください.

Ceph クラスタの監視

Ceph には ダッシュボードが用意されており,もちろん Rook Ceph でも使用することができます.

github.com

しかしながら既に他コンポーネントの監視を Prometheus + Grafana で行なっている場合,Ceph クラスタも Prometheus + Grafana で監視環境を構築できれば非常に便利です. Rook のドキュメントには monitoring 用のドキュメントが用意されており,そこで構築方法などが公開されています.

github.com

今回はこのドキュメントを参考に監視環境を構築してみます.なお,今回は Alert Manager を使った通知などを行いません.

備考

Rook の公式ドキュメントの prometheus-monitoring では,既存の Prometheus を使用する場合はannotations を使って scrape 対象とすることができると記述があります.

If your cluster already contains a Prometheus instance, it will automatically discover Rooks scrape endpoint using the standard prometheus.io/scrape and prometheus.io/port annotations.

しかしながら,prometheus operator のドキュメントの prometheusioscrape では,以下のように annotation を用いた scrape はサポートされておらず,PodMonitor や ServiceMonitor を使用するように案内されています.

The prometheus operator does not support annotation-based discovery of services, using the PodMonitor or ServiceMonitor CRD in its place as they provide far more configuration options.

そこで,今回は Prometheus Operator のドキュメントにしたがって構築していきます.

Pprometheus.io/scrape Annotation に関する問題は@kameneko1004 氏の以下のブログが参考になるので,興味のある人は参照すると良いでしょう.

prometheus.io/scrape などのAnnotations について調べてみた | by kameneko | penguin-lab | Medium

Rook での準備

Rook 側で行うことは以下の4点のみです.

  1. CephCluster カスタムリソースで monitoring を有効にする
  2. 監視用 RBAC ルールを作成する
  3. rook-ceph-mgr 用の ServiceMonitor カスタムリソースをデプロイする
  4. csi 用の ServiceMonitoring カスタムリソースをデプロイする

Ceph Cluster の monitoring を有効化する

以下の部分を false から true へ変更するのみで完了です.

apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
  name: rook-ceph
  namespace: rook-ceph
spec:
...(省略)
  # enable prometheus alerting for cluster
  monitoring:
-    enabled: false
+    enabled: true
    rulesNamespace: rook-ceph
  network:

K8s クラスタへ適用します.

$ kubectl apply -f cluster.yaml

Prometheus 用 リソースの作成

2, 3, 4 は ceph/monitoring ディレクトリにマニフェストが用意されているのでこれらを使用していきます.

$ # RBAC ルールの作成
$ kubectl apply -f ceph/monitoring/rbac.yaml
$ # rook-ceph-mgr 用 ServiceMonitoring カスタムリソースの作成
$ kubectl apply -f ceph/monitoring/csi-metrics-service-monitor.yaml
$ # csi 用 ServiceMonitoring カスタムリソースの作成
$ kubectl apply -f ceph/monitoring/service-monitor.yaml

Prometheus での準備

前回の Prometheus 環境構築の記事にも記載しましたが,以下のように全ての K8s namespace 内の ServiceMonitor および PodMonitor を検出できるようにするため,values.yamlserviceMonitorSelectorNilUsesHelmValues: falsepodMonitorSelectorNilUsesHelmValues: false を設定します.

By default, Prometheus discovers PodMonitors and ServiceMonitors within its namespace, that are labeled with the same release tag as the prometheus-operator release. Sometimes, you may need to discover custom PodMonitors/ServiceMonitors, for example used to scrape data from third-party applications. An easy way of doing this, without compromising the default PodMonitors/ServiceMonitors discovery, is allowing Prometheus to discover all PodMonitors/ServiceMonitors within its namespace, without applying label filtering. To do so, you can set prometheus.prometheusSpec.podMonitorSelectorNilUsesHelmValues and prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues to false.

github.com

prometheus:
  prometheusSpec:
    serviceMonitorSelectorNilUsesHelmValues: false
    podMonitorSelectorNilUsesHelmValues: false

Grafana での準備

Grafana 用 Ceph ダッシュボードはいくつか提供されていますが,Rook Ceph では以下 3 つのダッシュボードが対応しています.

github.com

Grafana のダッシュボードの import は GUI で行う方法がありますが,今回はこれら対応済みダッシュボードをマニフェストに組み込んで Grafana が起動したら自動で import されるように設定します.

以下のドキュメントにあるように,Grafana Labs で公開されている ダッシュボードは ID や revision を指定するだけで簡単に import することができます.今回は,rook-ceph ディレクトリを作成して 3つのダッシュボードをそこへ import します.

github.com

DCGM Exporter を用いた Kubernetes における NVIDIA GPU 監視環境の構築 で作成した Grafana 用の values.yaml を以下のように変更を加えます.

grafana:
...(省略)
+  dashboardProviders:
+   dashboardproviders.yaml:
+     apiVersion: 1
+     providers:
+     - name: 'rook-ceph'
+       orgId: 1
+       folder: 'rook-ceph'
+       type: file
+       disableDeletion: false
+       editable: true
+       options:
+         path: /var/lib/grafana/dashboards/rook-ceph
+  dashboards:
+    rook-ceph:
+      ceph-cluster:
+        gnetId: 2842
+        revision: 14
+        datasource: default
+      ceph-osd-single:
+        gnetId: 5336
+        revision: 5
+        datasource: default
+      ceph-pools:
+        gnetId: 5342
+        revision: 5
+        datasource: default
...(省略)

ダッシュボードの表示

ここまでできたら,kube-prometheus-stack Helm Chart を Upgrade します.

$ helm upgrade kube-prometheus prometheus-community/kube-prometheus-stack --values values.yaml

以下のようにCeph のダッシュボードは /rook-ceph に import されていることがわかります.

grafana-ceph-dir
Grafana のディレクトリ構成

ダッシュボードを選択すると,正しく情報の取得と表示が行えていることがわかります.

grafana-rook-cephcluster-dashboard
CephCluster ダッシュボード

grafana-rook-cephosd-dashboard
Ceph OSD ダッシュボード

おわりに

Prometheus + Grafana で Rook Ceph Cluster を監視する際の参考になれば幸いです. また,記事公開時間がアドベントカレンダーの担当日に間に合ってよかったです.