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で使用した環境について紹介しました. 参考になれば幸いです.