投稿日:
更新日:

Rook/Ceph で実現する分散ストレージの構築

Authors

目次

はじめに

現在、研究室で利用するための KaaS 基盤(プライベートクラウド)を整備するべく、ベアメタル Kubernetes の構築に取り組んでいます。

【マルチテナント・シングルクラスタで運用】

01

今回は、CNCF cloud native landscape にもある Rook/Ceph を使用して分散ストレージシステムを導入し、Amazon S3 や Google Cloud Storage に相当するオブジェクトストレージを自前で構築してみたので、その紹介です。

分散ストレージシステム

ストレージシステムの種類

まず、ストレージと一言で言っても、いくつか種類が存在し、扱うデータの容量や種類によって使い分けられます。

  • ファイルストレージ

    02

    • データは「フォルダ」や「ディレクトリ」といった形式で階層的に管理・保存される
    • 階層的に保存されたデータには一意のパスを用いて、階層を辿る形でアクセスする
    • データ保存の際にはファイル名、作成日、サイズ、データの種類等、最低限のメタデータが付与される
    • メリット
      • 小中規模のデータ管理を行うにあたり、簡潔かつ直感的な利用を実現できる
      • CIFS や NFS といった通信規格を用いることで、ネットワーク越しに利用することができる
    • デメリット
      • 大規模なデータ利用や非構造化データの管理が困難
      • データ容量が増加すると、ハードウェアへのリソース要求が大きくなり、パフォーマンスが劣化する
    • 利用例:)我々が普段使用している(UNIX 系 OS や Windows を搭載した)PC, Ceph
  • オブジェクトストレージ

    03

    • データは「オブジェクト」というフラットな形式で保存される
    • ファイルストレージに比べて保存期間やコピー回数等、より多くのメタデータを付与できる
    • 全てのメタデータはオブジェクトと一緒に格納され、一意の識別子を持つアドレス空間に保存される
    • メタデータが任意に設定可能である
    • フォルダやディレクトリだけでなく、メタデータによる複数の属性情報でグルーピングを行うことができる
    • メリット
      • オブジェクトストレージはフラットにアドレス空間に保存されているため、インデックスの作成が容易に行える
      • 大規模なデータや非構造化データであっても効率的な検索ができる
      • 地理的に離れた複数のサーバを並列化して一つのストレージプールを構成できるため、スケールアウトしやすい
      • 馴染み深い HTTP API で操作することができる
    • デメリット
      • オブジェクトというフラットで単純な構造を採用しているため、複雑なクエリ検索や、ファイルシステムのようなディレクトリ階層構造およびブロックレベルのアクセスは制限される
      • 後述するブロックストレージよりも低速であるため、更新頻度が高く、読み書きに速度を要求されるデータの保存には向かない
    • 利用例:)Amazon S3, Google Cloud Storage, OpenStack Swift, Ceph
  • ブロックストレージ

    04

    • 物理マシンの記録領域をボリュームという単位で分割して、内部を固定長のブロックで切り出し、そのブロックにデータを保存、管理する
    • ボリュームとブロックには固有の番号(アドレス)が振り分けられており、ユーザの要求するデータ容量に合わせて、必要となるブロックリソースの確保と統合が行われる
    • 保存するデータがブロックサイズを越える場合、複数のブロック(ブロックが連続している必要はない)に跨ってデータの保存が行われる
    • メリット
      • ブロックストレージは階層構造を必要としない他、読み込むデータへのパスを複数指定することが可能ため高速なアクセスを行うことができる
      • きめ細かやな制御を行うことができるため、高効率なパフォーマンスを求められるアプリケーション(ミドルウェア)の実装に適している
    • デメリット
      • ハードウェアがとにかく高い
      • メタデータの付与を行うこともできないため、ファイルストレージやオブジェクトストレージよりも低レイヤーに位置付けられ、それらの基盤として利用されることが一般的
    • 利用例:)RDBMS, VM ストレージ, コンテナ仮想ディスク, Ceph

まとめると、次のようになります。

ファイルストレージオブジェクトストレージブロックストレージ
特徴ネットワークドライブに格納オブジェクト単位でのアクセス
(ファイルシステムに依存しないため、大量データの格納が可能)
内臓ドライブと同じ RAW デバイスに格納
データ単位ファイルオブジェクトブロックデバイス
一貫性結果整合性強い整合性強い整合性
構造化階層構造フラット構造ボリューム・ブロック
メタデータの付与限定的多様無し
制御構造と処理速度階層構造により 一定容量 まで高速インデックスにより高速緻密で効率的な制御により高速
データ転送プロトコルCIFS(SMB), NFSREST(HTTP, HTTPS)FC-SCSI, iSCSI, FCoE, NVMe-oF
マウントパスの例/path/to/storage/folder-A/file-Ahttps://share.org/storage/file-A/dev/sda
適合データ容量:小〜中規模
更新頻度:高
容量:中〜大規模
更新頻度:低
容量:小〜中規模
更新頻度:高
主な用途ファイルの共有写真・動画の保存データベース, OS ディスク)

分散ストレージのメリット

分散ストレージの特徴、導入するメリットには次のようなものがあります。

  1. 高可用性および耐久性
  • データを複数の場所に分散して保存するため、システムの可用性が向上し、データの耐久性が高まる
  • 単一のディスクやサーバが故障しても、他の場所に冗長なコピーデータが存在するため、データの喪失やアクセスの中断を防ぐことができる
  1. 拡張性
  • 増え続けるデータ数に柔軟に対応することができる
  • 新しいノードやサーバをシステムに追加することで、容量やパフォーマンスを必要に応じて拡張し、大量のデータを効率的に管理・処理することができる
  1. 負荷分散
  • データを複数のノードやサーバに分散するため、負荷を分散できる
  • データへのアクセスや処理のパフォーマンスが向上し、システム全体の応答性が向上する
  1. 柔軟性
  • 異なるデータのタイプや形式、アクセスパターンに対応するための柔軟性を持ち合わせており、多種多用なデータを効率的に管理することができる
  • 異なる場所にデータを配置することも可能であり、地理的な冗長性を確保することもできる(ベアメタルだとこれは厳しい)
  1. コスト面
  • ハードウェアやリソースの共有を可能にするため、コスト効率が高い
  • データの冗長性を維持しながらも、スケーラビリティや耐久性を確保することができる
  • Ceph, OpenStack Swift, GlusterFS 等の OSS 分散ストレージソリューションも多数存在し、ライセンス費用を節約することができる

今回、分散ストレージを導入しようと思った主な理由は、1. 高可用性と耐久性3. 負荷分散の恩恵を受けるためです。

そもそも、Kubernetes を構築した理由は、これまで運用していたオンプレのサーバ群の水平スケールや規模拡張性を実現するためです。 これまで、僕が携わっている研究の一部として、マイクロサービスを採用したクラウドシステムを実現しようとしていたのですが、あくまでも単一のサーバとして稼働させることしかできず、クラウドサービスと呼ぶには程遠い状態でした。 また、これまでの検証で、サーバが一定の負荷に耐えられるのに対して、データベース等のミドルウェアがボトルネックとなり、パフォーマンスを十分に発揮できないという課題がありました。 さらに、データの管理に関しても、単一ストレージでは、水平スケールや多重化されたサーバからの大量な R/W アクセスに耐えられない可能性があると考えました。

そこで、まず、ミドルウェアから対処するべく、分散ストレージの導入を検討し、データのレプリケーションを張ることで、冗長化を図るとともにリクエストを分散させようと思いました。

Rook/Ceph の採用

今回はタイトルにもある通り、分散ストレージを構築するためのツールとして Ceph(Rook)を採用しました。

Ceph は、昔から使用されているストレージプロバイダであり、大規模な分散ストレージシステムを構築することができます。 また、NASA 米国国家航空宇宙局 や CERN 欧州原子核研究機構 等、如何にも膨大なデータ量を管理していそうな機関でも導入実績のあるツールです。 日本でも、富士通株式会社が提供する FUJITSU Storage ETERNUS CD10000 S2 ハイパースケールストレージ 等で使用されている様です。

一方で、Ceph コンポーネントは非常に複雑であり、個人運用する場合において、導入のハードルや管理コストが高いという課題があります。 しかし、有難いことに CNCF は Rook という Ceph の運用・管理を自動化し、セルフスケーリング・セルフヒーリングを提供する OSS を準備してくれています。

Rook は Ceph のオペレータとして機能し、Kubernetes クラスタ上で、容易に Ceph のデプロイや構成、プロビジョニング、スケール、アップグレード、モニタリングを行うことが可能になります。 また、Ceph 上にオブジェクトストレージ(バケット)やブロックストレージ(データベースシステム)といった様々なストレージシステムを柔軟に構築することができるため、今回の利用ケースや目的にかなり適していると思いました。

その他、身近なところでは、サイボウズ株式会社 が提供する kintone の裏側でも使用されており、公開情報が豊富に存在することから導入のハードルが比較的低いのも採用理由の一つです。

Rook/Ceph の詳細

Rook の README によれば、それぞれ次のように説明されています。

  • Rook

    05

    Rook is an open source cloud-native storage orchestrator for Kubernetes, providing the platform, framework, and support for Ceph storage to natively integrate with Kubernetes.

    Rook は Kubernetes 用のオープンソースのクラウドネイティブストレージオーケストレーターであり、Kubernetes とネイティブに統合するためのプラットフォーム、フレームワーク、Ceph ストレージのサポートを提供します。

  • Ceph

    06

    Ceph is a distributed storage system that provides file, block and object storage and is deployed in large scale production clusters.

    Ceph は、ファイル、ブロック、オブジェクトストレージを提供する分散ストレージシステムであり、大規模な実稼働クラスタにデプロイされます。

要するに、『Ceph は大規模な分散ストレージシステムを構築することができて、Operator である Rook を使用すれば Kubernetes 上で簡単に扱えるよ』といったニュアンスだと思います。

アーキテクチャ

07

Rook/Ceph アーキテクチャのうち、特に重要な 3 つの Ceph コンポーネント(MON, MGR, OSD)について紹介します。

  • MON:Ceph Monitor daemon
    • クラスタのヘルス状態に関する情報、すべてのノードのマップ、およびデータ分散ルールを維持する
    • 障害または衝突が発生した場合、クラスタ内の Ceph Monitor ノードは、どの情報が正しいかを多数決で決定する
    • 必ず多数決が得られるように、奇数個(少なくとも 3 個以上)の Ceph Monitor ノードを設定する必要がある
    • 複数のサイトを使用する場合、Ceph Monitor ノードは奇数個のサイトに分散配置する必要がある
    • サイトあたりの Ceph Monitor ノードの数は、1 つのサイトに障害が発生した場合、50% を超える Ceph Monitor ノードの機能が維持される数準備する必要がある
  • MGR:Ceph Manager daemon
    • クラスタ全体からステート情報を収集する
    • MGR daemon は MON daemon と一緒に動作する
    • 追加のモニタリング機能を提供し、外部のモニタリングシステムや管理システムとのインタフェースとして機能する
  • OSD:Object Storage Device daemon 👈 一番重要なコンポーネント
    • オブジェクトストレージデバイスを処理するためのデーモン
    • OSD は、物理ストレージユニットまたは論理ストレージユニット(ハードディスクまたはパーティション)に該当する

    • オブジェクトストレージデバイスは、物理ディスク/パーティションにも論理ボリューム(TopoLVM とか)にも設定できる

    • OSD は他にも、データのレプリケーションや、ノードが追加または削除された場合のリバランスも処理する
    • Ceph の OSD は、MON と通信してステート情報を提供する

これらの主要コンポーネントは、クラスタ毎に少なくとも 1 つ以上配置しなければなりません。

その他にも、MDS や RGW 等が存在します。

  • MDS:Metadata Server
    • CephFS の一部として機能する
    • ファイルおよびディレクトリのメタデータ(ファイル名, 所有者, アクセス許可 等)を管理することで、大規模なファイルシステムで高いパフォーマンスとスケーラビリティを実現できる
    • Ceph クラスタ内でメタデータの一貫性を確保し、それらの情報を提供する
    • 複数クライアントからの同時接続をサポートする
  • RGW:RADOS Gateway
    • Ceph Object Storage の一部として機能し、Ceph クラスタと通信することで、データの保存、取得、削除等のオブジェクト操作を処理する
    • Amazon S3 や Swift とも互換性のある API を提供しており、パブリッククラウド上にオブジェクトストレージを構築できる
      → クラウドストレージやウェブアプリケーションのバックエンドで使用できる

これだけでも、素で扱うと、管理コンポーネントが多すぎてかなり大変そうなのが分かると思います...。

Rook 有り難し...😭😭

注釈

  • RADOS:Reliable Autonomic Distributed Object Store
    • オブジェクトストレージ
    • パブリッククラウドでいう S3 や Cloud Storage
  • RBD:RADOS Block Device
    • デバイス
    • パソコンの内蔵ハードディスクや外付けハードディスクのようなもの(/dev/sda として認識されるような感じ)
  • CephFS(Ceph File System)
    • ネットワーク経由で他のマシンと共有可能なファイルシステム
    • Samba(CIFS)や NFS のようなもの
  • CSI:Containers Storage Interface
    • Kubernetes 環境から外部ストレージにアクセスするためのインターフェース
  • RAW デバイス
    • ファイルシステムを使用せずソフトウェアが直接ディスクへのアクセス制御を行う記録デバイス
    • HDD や SSD を RAW デバイスとして OS に認識させることで、様々なストレージシステムを構築する

参考

Rook/Ceph による分散ストレージの構築

ストレージシステム、Rook/Ceph の概要をある程度抑えたところで、早速、分散ストレージを構築していきます。

前提条件として、3 ノード以上の Kubernetes クラスタが構築済みであるとします。

環境の確認

今回は一台の物理マシンの上に VM を 4 台構築しました。

一台は、C-Plane として動作し、残りが D-Plane(ストレージプールが構築されるノード)として動作します。

  • ホストマシン

    • CPU:Intel(R) Core(TM) i9-13900 CPU @ 5.60GHz 24 コア 32 スレッド
    • アーキテクチャ:amd64
    • OS:Ubuntu 22.04 LTS
    • RAM:128 GB
    • 台数:1
  • ゲストマシン

    • 仮想化:KVM(libvirt)
    • OS:Ubuntu 20.04 LTS
    • RAM:16 GB
    • 台数:4(C-Plane 1 台 / D-Plane 3 台)
  • ハードウェア(仮想ディスク)

    • OSD 用ディスク: /dev/vdb(仮想 HDD)・・・後の手順で、OSD Pod に認識させるための仮想ディスクを準備する
  • Kubernetes

    ToolsVersion
    Kubernetes(kubeadm)v1.25.3
    KVS(etcd)v3.2.26
    CRI(containerd)v1.6.9
    CNI(flannel)v0.3.0
  • Rook/Ceph

    ToolsVersion
    Rookv1.11.0
    Cephv17.2.6

(必要に応じて)仮想ディスクとパーティションの追加

D-Plane ノード の ディスクとパーティションは以下の通りでした。

$ lsblk -f
NAME   FSTYPE LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINT
vda
├─vda1 ext4         7eb46358-875e-4d14-9d62-f391e8b937cf  219.6M    44% /boot
├─vda2 swap         0fd79448-cf0c-44b6-8e53-364f15185254
└─vda3 ext4         c716536e-606d-41cf-84ec-5693a84ba5da   87.2G    24% /
パーティションファイルシステム割り当て
/dev/vda1ext4boot
/dev/vda2swapswap
/dev/vda3ext4root

これらは、フォーマット済みおよびマウント済みであり、いずれも OSD に認識させることはできません。 そこで、まず libvirt で構築した VM に OSD に認識させるための仮想 HDD を追加します。

Rook のドキュメント に従って、事前にパーティションを準備します。

ここで、仮想ディスク/dev/vdbを作成しておき、Rook で Ceph をデプロイした際に OSD に認識させます。 なお、この時、/dev/vdbが正常に作成されていないと、OSD が起動せず、クラスタの立ち上げに失敗します。
(当初、この仕組みを知らず結構時間を溶かしました...)

以下の操作を 3 ノード全ての仮想マシンに対して行います。

  • ホストマシン上での操作
### 仮想ディスクを作成(10 GB x 3 = 30 GB のストレージプールを構築する)
$ sudo dd if=/dev/zero of=/var/lib/libvirt/images/ceph_node01.img bs=1G count=10
10+0 レコード入力
10+0 レコード出力
10737418240 bytes (11 GB, 10 GiB) copied, 4.64974 s, 2.3 GB/s

### RAW 形式のディスクイメージを作成
$ sudo qemu-img create -f raw /var/lib/libvirt/images/ceph_node01.img 10G

### 作成されたことを確認
$ sudo ls -al /var/lib/libvirt/images | grep ceph_node01.img
-rw-r--r-- 1 libvirt-qemu kvm   10737418240  618 17:41 ceph_node01.img

### 仮想マシン名確認
$ virsh list --all
 Id   Name                State
-----------------------------------
 1    libvirt_master      running
 2    libvirt_node01      running
 3    libvirt_node02      running
 4    libvirt_node03      running

### 仮想ディスクを VM に追加
$ sudo virsh attach-disk libvirt_node01 /var/lib/libvirt/images/ceph_node01.img vdb
Disk attached successfully
  • 仮想マシン上での操作
### 仮想マシンを再起動
$ sudo reboot

### ディスクパーティションを作成
$ sudo fdisk /dev/vdb

Welcome to fdisk (util-linux 2.34).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0xa268a278.

============================
--- 新規パーティションを作成 ---
============================
Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p): [Enter]

Using default response p.
Partition number (1-4, default 1): [Enter]
First sector (2048-20971519, default 2048): [Enter]
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-20971519, default 20971519): [Enter]

Created a new partition 1 of type 'Linux' and of size 10 GiB.

============================
--- 保存 ---
============================
Command (m for help): w

The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

### パーティションテーブルを更新
$ sudo partprobe

### 仮想マシンに入ってパーティションテーブルを確認
$ sudo fdisk -l
Disk /dev/vda: 128 GiB, 137438953472 bytes, 268435456 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xca5b07f1

Device     Boot   Start       End   Sectors   Size Id Type
/dev/vda1  *       2048    999423    997376   487M 83 Linux
/dev/vda2        999424   4999167   3999744   1.9G 82 Linux swap / Solaris
/dev/vda3       4999168 268433407 263434240 125.6G 83 Linux


Disk /dev/vdb: 10 GiB, 10737418240 bytes, 20971520 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xa268a278

Device     Boot Start      End  Sectors Size Id Type
/dev/vdb1        2048 20971519 20969472  10G 83 Linux # Block Device が追加されている

### 仮想マシンに入ってブロックデバイスを確認
$ lsblk -f
NAME   FSTYPE LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINT
vda
├─vda1 ext4         7eb46358-875e-4d14-9d62-f391e8b937cf  219.6M    44% /boot
├─vda2 swap         0fd79448-cf0c-44b6-8e53-364f15185254
└─vda3 ext4         c716536e-606d-41cf-84ec-5693a84ba5da   94.8G    18% /
vdb
└─vdb1 # vdb1 が追加される

【余談】dd コマンド

  • if
    • 入力元となるファイルやデバイスを指定
    • /dev/zeroは, Linux システムにおいてゼロで埋められた無限のデータストリームを提供する特殊なデバイスファイル
  • of
    • 出力先となるファイルやデバイスを指定
  • bs
    • ディスクのブロックサイズを指定
  • count
    • ディスクのブロック数を指定

【余談】ノード の再 join

$ kubectl get node
NAME     STATUS     ROLES           AGE   VERSION
master   Ready      control-plane   8h    v1.25.3
node01   NotReady   <none>          8h    v1.25.3
node02   NotReady   <none>          8h    v1.25.3
node03   NotReady   <none>          8h    v1.25.3

仮想マシンを再起動すると、NotReady状態になるため、必要に応じて以下のコマンドを実行し、クラスタに再 join する(放っておいても、何れ Ready になる)

### C-Plane: トークンを再発行(有効期限が切れている場合)
$ sudo kubeadm token create --print-join-command
> ログインコマンドが表示されるので控える

### D-Plane: スワップ領域を無効化
$ sudo swapoff -a

### D-Plane: 既存の情報を削除
$ sudo rm -f /etc/kubernetes/kubelet.conf
$ sudo rm -f /etc/kubernetes/pki/ca.crt

### D-Plane: 再 join
$ sudo kubeadm join [C-PlaneのIPアドレス]:6443 --token XXXXX --discovery-token-ca-cert-hash XXXXX

Rook/Ceph のデプロイ

仮想ディスクを準備したところで、こちら を参考に Rook で Ceph クラスタをデプロイします。

僕は、Kustomize でデプロイしましたが、動作検証するだけなら、Quickstart ドキュメントに従えば十分かと思います。

デプロイが正常に完了すると、以下の Pod が起動します。

### Custom Resource Definision
$ kubectl get crds | grep -E '\.rook\.io|objectbucket\.io'
cephblockpoolradosnamespaces.ceph.rook.io    2023-09-04T20:52:28Z
cephblockpools.ceph.rook.io                  2023-09-04T20:52:28Z
cephbucketnotifications.ceph.rook.io         2023-09-04T20:52:29Z
cephbuckettopics.ceph.rook.io                2023-09-04T20:52:29Z
cephclients.ceph.rook.io                     2023-09-04T20:52:29Z
cephclusters.ceph.rook.io                    2023-09-04T20:52:29Z
cephfilesystemmirrors.ceph.rook.io           2023-09-04T20:52:29Z
cephfilesystems.ceph.rook.io                 2023-09-04T20:52:29Z
cephfilesystemsubvolumegroups.ceph.rook.io   2023-09-04T20:52:29Z
cephnfses.ceph.rook.io                       2023-09-04T20:52:29Z
cephobjectrealms.ceph.rook.io                2023-09-04T20:52:29Z
cephobjectstores.ceph.rook.io                2023-09-04T20:52:29Z
cephobjectstoreusers.ceph.rook.io            2023-09-04T20:52:29Z
cephobjectzonegroups.ceph.rook.io            2023-09-04T20:52:30Z
cephobjectzones.ceph.rook.io                 2023-09-04T20:52:30Z
cephrbdmirrors.ceph.rook.io                  2023-09-04T20:52:30Z
objectbucketclaims.objectbucket.io           2023-09-04T20:52:30Z
objectbuckets.objectbucket.io                2023-09-04T20:52:30Z

### Pod
$ kubectl get -n rook-ceph Pod -o wide
NAME                                               READY   STATUS      RESTARTS   AGE     IP                NODE     NOMINATED NODE   READINESS GATES
csi-cephfsplugin-8stzd                             2/2     Running     0          8m39s   192.168.121.49    node02   <none>           <none>
csi-cephfsplugin-cjphd                             2/2     Running     0          11m     192.168.121.137   node03   <none>           <none>
csi-cephfsplugin-nkc9h                             2/2     Running     0          8m14s   192.168.121.109   node01   <none>           <none>
csi-cephfsplugin-provisioner-59fd67d98-42t9g       5/5     Running     0          11m     10.16.6.71        node03   <none>           <none>
csi-cephfsplugin-provisioner-59fd67d98-qbngz       5/5     Running     0          11m     10.16.5.21        node02   <none>           <none>
csi-rbdplugin-9k2ng                                2/2     Running     0          11m     192.168.121.137   node03   <none>           <none>
csi-rbdplugin-hp7vk                                2/2     Running     0          8m39s   192.168.121.49    node02   <none>           <none>
csi-rbdplugin-provisioner-649c57b978-7zgb5         5/5     Running     0          11m     10.16.6.72        node03   <none>           <none>
csi-rbdplugin-provisioner-649c57b978-psnq9         5/5     Running     0          11m     10.16.5.23        node02   <none>           <none>
csi-rbdplugin-qqqdz                                2/2     Running     0          8m14s   192.168.121.109   node01   <none>           <none>
rook-ceph-crashcollector-node01-564ffff58b-lvj7d   1/1     Running     0          6m56s   10.16.4.25        node01   <none>           <none>
rook-ceph-crashcollector-node02-757694cbc9-bxgl4   1/1     Running     0          6m56s   10.16.5.33        node02   <none>           <none>
rook-ceph-crashcollector-node03-5fbb95b6f6-wf5rf   1/1     Running     0          7m8s    10.16.6.105       node03   <none>           <none>
rook-ceph-mgr-a-7cc74dc66b-rzjh5                   3/3     Running     0          7m25s   10.16.4.22        node01   <none>           <none>
rook-ceph-mgr-b-548d4f969f-zzgn2                   3/3     Running     0          7m23s   10.16.5.29        node02   <none>           <none>
rook-ceph-mon-a-69d8fb675c-7zrk9                   2/2     Running     0          8m9s    10.16.6.104       node03   <none>           <none>
rook-ceph-mon-b-cfc9748d7-ztp7q                    2/2     Running     0          7m43s   10.16.5.28        node02   <none>           <none>
rook-ceph-mon-c-57df994697-n8wfx                   2/2     Running     0          7m34s   10.16.4.20        node01   <none>           <none>
rook-ceph-operator-64544654d8-2p9ml                1/1     Running     0          12m     10.16.6.68        node03   <none>           <none>
rook-ceph-osd-0-85c8dc79d6-jpjk6                   2/2     Running     0          6m56s   10.16.4.24        node01   <none>           <none>
rook-ceph-osd-1-7d96959867-tvj67                   2/2     Running     0          6m56s   10.16.5.32        node02   <none>           <none>
rook-ceph-osd-2-759d58945c-c2jpw                   2/2     Running     0          6m54s   10.16.6.107       node03   <none>           <none>
rook-ceph-osd-prepare-node01-vvxnq                 0/1     Completed   0          6m31s   10.16.4.27        node01   <none>           <none>
rook-ceph-osd-prepare-node02-dsj82                 0/1     Completed   0          6m28s   10.16.5.34        node02   <none>           <none>
rook-ceph-osd-prepare-node03-4dvpl                 0/1     Completed   0          6m25s   10.16.6.108       node03   <none>           <none>
rook-ceph-tools-5b95c67b4b-2w9ts                   1/1     Running     0          5m46s   10.16.4.28        node01   <none>           <none>

### Ceph cluster
$ kubectl -n rook-ceph get cephcluster -o wide
NAME        DATADIRHOSTPATH   MONCOUNT   AGE   PHASE   MESSAGE                        HEALTH      EXTERNAL   FSID
rook-ceph   /var/lib/rook     3          12m   Ready   Cluster created successfully   HEALTH_OK              4ac48dee-f670-4dcc-a30f-9d96f6116577

コンポーネントの数が凄い...。

何が起動したのか除いてみます。

Rook/Ceph で起動する主な Pod の起動順序、役割、コンテナイメージは次の通りです。

起動順序名前役割コンテナイメージ
1rook-ceph-operatorCeph クラスタ(Pod)全体の管理Rook プロジェクトが提供する Rook/Ceph コンテナ
2csi-*CSI ドライバ(Ceph クラスタから PersistentVolume を切り出す)Ceph プロジェクトが提供する各種コンテナ
3rook-ceph-mon-*, rook-ceph-mgr-*Ceph クラスタの MON, MGR daemonCeph プロジェクトが提供する各種コンテナ
4rook-ceph-osd-prepare-*, rook-ceph-osd-*Ceph クラスタの OSD 初期化, および OSD daemonCeph プロジェクトが提供する各種コンテナ

csi-*というのは個々の Rook/Ceph クラスタから CephFS や RBD などを切り出すための CSI ドライバです。 ここで、csi-cephfs*は CephFS に、csi-rbd*は RBD に対応します。 CSI の Pod では、 Rook ではなく Ceph プロジェクトが公式に提供する Ceph CSI ドライバである ceph-csi コンテナが起動している様です。

rook-ceph-mon-*, rook-ceph-mgr-*は、それぞれ 先で述べた MON, MGR に対応します。 -w などのオプションを付けて kubectl get してみると確認できますが、Rook-operator は最初に MON を起動します。 これは、MON を作って初めて、Ceph クラスタの構築準備を進められるからです。 これらの Pod が使うコンテナも Rook ではなく、Ceph プロジェクトが提供する Ceph のコンテナの様です。

rook-ceph-osd-prepare-*は OSD を初期化するための Pod です。 こいつが OSD を初期化して正常終了した後はrook-ceph-osd-*が立ち上がって OSD として動作します。 この時、該当の RAW デバイスがないと、OSD は起動してくれません。 これらは俗に OSD prepare Pod, OSD Pod などど呼ばれ、OSD prepare Pod と OSD Pod が一つの OSD についてニコイチで存在します。

つまり、例えば、OSD が 10 個ある環境では OSD prepare Pod と OSD Pod がそれぞれ 10 個存在することになります。 CSI, MON, MGR と同様に、OSD prepare Pod と OSD Pod も Ceph プロジェクトが提供する Ceph コンテナを使います。

OSD prepare Pod は Job によって、OSD Pod は Deployment によって作成されます。

デプロイが完了した後、rook-ceph-osd-prepare-* のステータスは Completed となります。

rook-ceph-toolsは開発者サイドから Ceph クラスタの状態を監視するため、こちら を参考にデプロイしました。

Ceph クラスタの確認

toolbox をデプロイして Ceph のステータスを確認します。

### Ceph のステータス
$ kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph status
  cluster:
    id:     4ac48dee-f670-4dcc-a30f-9d96f6116577
    health: HEALTH_OK

  services:
    mon: 3 daemons, quorum a,b,c (age 22m)
    mgr: b(active, since 20m), standbys: a
    osd: 3 osds: 3 up (since 21m), 3 in (since 21m)

  data:
    pools:   1 pools, 1 pgs
    objects: 2 objects, 449 KiB
    usage:   62 MiB used, 30 GiB / 30 GiB avail
    pgs:     1 active+clean

### OSD のステータス
$ kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph osd status
ID  HOST     USED  AVAIL  WR OPS  WR DATA  RD OPS  RD DATA  STATE
 0  node01  20.6M  9.97G      0        0       0        0   exists,up
 1  node02  20.6M  9.97G      0        0       0        0   exists,up
 2  node03  20.6M  9.97G      0        0       0        0   exists,up

### Ceph のデータ使用状況
$ kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph df
--- RAW STORAGE ---
CLASS    SIZE   AVAIL    USED  RAW USED  %RAW USED
hdd    30 GiB  30 GiB  62 MiB    62 MiB       0.20
TOTAL  30 GiB  30 GiB  62 MiB    62 MiB       0.20

--- POOLS ---
POOL  ID  PGS   STORED  OBJECTS     USED  %USED  MAX AVAIL
.mgr   1    1  449 KiB        2  1.3 MiB      0    9.5 GiB

$ kubectl -n rook-ceph exec deploy/rook-ceph-tools -- rados df
POOL_NAME     USED  OBJECTS  CLONES  COPIES  MISSING_ON_PRIMARY  UNFOUND  DEGRADED  RD_OPS       RD  WR_OPS       WR  USED COMPR  UNDER COMPR
.mgr       1.3 MiB        2       0       6                   0        0         0     288  494 KiB     153  1.3 MiB         0 B          0 B

total_objects    2
total_used       62 MiB
total_avail      30 GiB
total_space      30 GiB

### 利用可能なプールを確認
$ kubectl -n rook-ceph exec deploy/rook-ceph-tools -- rados lspools
.mgr

今回は、3 ノードのそれぞれに 1 GiB のブロックデバイスを 10 個追加しているので、全体として 30 GiB の分散ストレージとして機能していることが分かります。

また、仮想マシンのディスクパーティションを確認してみると、/dev/vdb/vdb1 が OSD によって認識されていることが分かります。

$ lsblk -f
NAME   FSTYPE         LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINT
vda
├─vda1 ext4                 7eb46358-875e-4d14-9d62-f391e8b937cf  219.6M    44% /boot
├─vda2 swap                 0fd79448-cf0c-44b6-8e53-364f15185254
└─vda3 ext4                 c716536e-606d-41cf-84ec-5693a84ba5da   93.7G    19% /
vdb
└─vdb1 ceph_bluestore  <- OSDが認識している

これで、Rook/Ceph のデプロイは完了です。

S3 互換のオブジェクトストレージを構築

ブロックストレージの追加

Rook/Ceph クラスタの構築が完了したら、分散ストレージを構築するためにブロックストレージを追加します。

こちら を参考に CephBlockPool(CRD) を追加します。

---
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicapool
  namespace: rook-ceph
spec:
  failureDomain: host
  replicated:
    size: 3
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: rook-ceph-block
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
  clusterID: rook-ceph
  pool: replicapool
  imageFormat: '2'
  imageFeatures: layering
  csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
  csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
  csi.storage.k8s.io/controller-expand-secret-name: rook-csi-rbd-provisioner
  csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph
  csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
  csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
  csi.storage.k8s.io/fstype: ext4
reclaimPolicy: Delete
allowVolumeExpansion: true
$ kubectl -n rook-ceph get cephblockpool
NAME          PHASE
replicapool   Ready

$ kubectl -n rook-ceph get storageclass
NAME              PROVISIONER                  RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
rook-ceph-block   rook-ceph.rbd.csi.ceph.com   Delete          Immediate           true                   2m8s

MinIO の導入

08

今回は、Amazon S3 と互換性のある API で操作可能な MinIO を使用します。

MinIO も CNCF cloud native landscape にあるプロジェクトの一つです。

MinIO(みん・あいおー)と読むらしい。

MinIO の詳細なコンポーネントに関する説明は省きますが、大きく MinIO Operator と MinIO Tenant という概念が存在します。 MinIO Operator は、Kubernetes で MinIO を扱うためのオペレーターで、MinIO Tenant が実際にバケットを構築するコンポーネントになります。

手っ取り早く こちら の Helm チャートを用いて, minio-operator と minio-tenant をデプロイします。

Service エンドポイントにポートフォワードしてクラスタネットワークに繋ぎます。

### MinIO Operator
$ kubectl port-forward -n [minio-operator のネームスペース名] service/console 9090:9090
### MinIO Tenant
$ kubectl port-forward -n [minio-tenant のネームスペース名] service/[minio-tenant の 'console'] 9443:9443

(実運用だと一々ポートフォワードするのは現実的でないので、Kubernetes クラスタに MetalLB L2Advertisement を導入して、type: LoadBalancer を使用し、DHCP で LAN の IP アドレスを Service に直接割り当てるようにしています。)

以下のような画面が表示されたら、Rook/Ceph による分散ストレージおよびオブジェクトストレージの構築は完了です。

  • MinIO Operator

09

初回ログイン時のトークンは、以下のワンライナーコマンドで取得できます。

$ kubectl get -n minio-operator secret console-sa-secret -o yaml | awk '/^data:/ {p = 1} p && /token:/ {print $2; exit}' | base64 -d

10 11

  • MinIO Tenant(Console)

12 13

  • ArgoCD Web UI からデプロイされたリソースを確認してみる
    (リソース数は kube-prom-stack とかよりも多い気がする...)

14

今回は特に事前知識が要される作業でした...😅

トラブルシューティング メモ

rook-ceph ネームスペースが Terminating 状態になった場合

  1. マニフェストを json として吐き出す
$ kubectl get ns rook-ceph -o json > ./tmp.json
  1. spec.finalizers の配列の中身を空に変更する
{
    ...
    "spec": {
        "finalizers": []
    },
    ...
}
  1. json データを元に、マニフェストにパッチを当てる
$ kubectl replace --raw "/api/v1/namespaces/rook-ceph/finalize" -f ./tmp.json
$ rm ./tmp.json

Custom Resource の削除

Troubleshooting を参考

  • CephCluster が Deleting フェーズにあることを確認

    $ kubectl -n rook-ceph get cephcluster
    NAME        DATADIRHOSTPATH   MONCOUNT   AGE   PHASE      MESSAGE                    HEALTH        EXTERNAL   FSID
    rook-ceph   /var/lib/rook     3          14h   Deleting   Deleting the CephCluster   HEALTH_WARN              c37ea247-6af3-4cf9-bb92-31f8c12fb35a
    
  • 全ての Custom Resource を削除

    $ kubectl -n rook-ceph delete cephcluster rook-ceph
    
    $ for CRD in $(kubectl get crd -n rook-ceph | awk '/ceph.rook.io/ {print $1}'); do kubectl get -n rook-ceph "$CRD" -o name | xargs -I {} kubectl patch -n rook-ceph {} --type merge -p '{"metadata":{"finalizers": []}}'; done
    
  • 【各ノードで実行】Ceph クラスタのデータを削除

    $ sudo rm -rf /var/lib/rook
    

おわりに

これにて、無事、プライベートクラウドに分散ストレージサービスを構築することができました!やっと研究が進みそう...。

あ、なんでパブリッククラウド初めから使わないのって?

だって、面白くないじゃん。 以上 😇

今回使用したコードは以下のリポジトリにあります。

参考・引用