[Storage] Cephのアーキテクチャを勉強してみる

こないだ自宅で起きたCephストレージ障害から

こないだから、k8s環境で動かすブロックストレージをNFS主体からRBD主体に切り替え、やっと安定したストレージ環境ができるようになったかと思ったら、意外と私の配置が下手くそだったこともあり、それなりにCephクラスタがぶっ壊れました。

破壊と再構築を繰り返す中で、なんだかCephの構成要素とかよく分かってないところが多いなぁ?と感じたところがあったんで、Cephのドキュメント http://docs.ceph.com/docs/master/architecture/ なんかを読みながら勉強しようということに。その記憶メモのようなものだと思ってください。

データアクセスにおけるアーキテクチャ

ざっくりデータアクセスに関する利便性については、Cephって実に高くて、Unifiedストレージにもなるし、RESTful APIストレージにもなれる、アプリケーションを作り込めばもっと効率的なRADOSアクセスが可能になるという、OSSの中ではある意味夢のような分散ストレージと言えるのかなと思います。

とは言っても、CephFSのCIFS機能は完全とは言えないので、やっぱりRESTでいくのか、RBDでいくのか・・・が多いのかなという気がします。

Cephのコンポーネントをもう少し細かく見る

クライアントとクラスタの関係は以下のようになっています。RADOSとしてアクセスする部分には、共通したプロトコルが存在していて、「Ceph Storage Cluster Protocol」と呼ばれるようです。その配下で、構成モジュールであるOSD、MDS、Monが動いており、クライアントとのやり取りが発生するよう構成されています。

ダッシュボードから主な役割について見てみる

ダッシュボードでは、どのノードに何の役割をもたせているかを確認することができます。主な機能として「MDS」「OSD」「MON」があります。

RBDの場合、主に使うのは「MON」「OSD」となります。「MDS」はCephFSで主に使われる機能になっています。

そもそもオブジェクトとは

Cephは、一種のオブジェクトストレージなのですが、そもそもオブジェクトってなんでしょう?

というわけで、それもドキュメントに書かれていたので読んでみました。オブジェクトというのは、数のように、IDで管理されたデータの塊を指しており、構成要素としては「ID」「バイナリデータ」「メタデータ」の3つで構成されています。

このオブジェクトをファイルとして扱い、ディスクに放り込むと言う形で、オブジェクトストレージとして成立させているようです。

そして、これらオブジェクトを用いたIOを行うために、クライアントとMON/MDS/OSDは連動して動いています。それがまとめられたのが下図内容になります。

基本的にMONはクラスタ管理のリーダーシップを摂る存在であり、その配下にMDS/OSDがいます。
MONはユーザーからの初回アクセスに対する認証、配下に対する認証セットを構成して、認証成立時にクライアントへチケットを払い出します。このあたりはどこかKerberosっぽいなーって感じがします。

後はそのチケットをMDS/OSDとやり取りすることにより、オブジェクトを用いたIOが成立するようになります。

Cephは同期IOのストレージ

Cephは結構オーバーヘッドがデカイということを聞いたことがあります。それはどうやらClient/OSD間のWrite処理において、同期IOを使用していることが原因のようです。実は書き込み処理をすると気、後述するPG(Placement Group)単位でPrimaryのOSDが定められており、ClientはPrimary OSDにしか書き込み要求を送ることができません。

Primary OSDはこの要求を受けて、まず自分のディスクに書き込みを行い、続いてSecondary以下のOSDに対してこのオブジェクトをコピーして回ります。全体からのAckが帰って来ないとClientに対してAckを返しません。

つまりは、全体の応答が正常に返ってくるまで「帰れま10」状態になるということです。これは不整合を防止するという意味では効果があると思いますが、まぁ遅いです。それでも、分散ストレージの避け得ぬ部分なのかなーという気がしないでもないです。EMC IsilonとかECSとかどうしてるんだろうか。

クライアントとプール間のWrite処理は?

クライアントと、OSD間のWrite処理は前述したとおりなんですが、実際にクライアントがアクセスする際は、プールを指定して書き込みを行います。このときの流れについて記載があったので読み込みました。

Clientはクラスターマップを参照し、オブジェクトのWrite処理をプールに投げるという動作をしているようです。上図の通り、この構成情報の同期をとることも非常に重要で、そのあたりを統括しているのがmonということになります。

プール上では上記のようにCRUSH Ruleというものが定められており、例えば細かいところを言うと、どのホストにどれほどのオブジェクトを配置するか、その割合を定義したりもします。

CRUSHルールに関しては上記記述のとおりなのですが、実はこの配下に色々な構成要素があり、書き込み割合の件について書いたのはその中にあります。その定義はSTEPセクションの中で記述する形になっていて、そのためのbucket設計が重要なのかなーと感じています。

障害発生時によく見かけた「PGs」と言う文言

障害発生の際「PGが○○個Degradeしたべや!」とか叫び声をCephが上げていて、そもそもPGってなんだろう?と。オブジェクトが何割破損したとかそういうのはわかるけど、PGって何?と。

そこで、PGについて調べました。Placement Groupの略です。

そして、Placement Groupとは、オブジェクトとしての実際の受け皿であり、OSDを抽象化するものであるということを知りました。PGとOSDの紐付けはMONが頭に立ち、OSD間の相互ネゴシエーションで実現しているようです。

ここでまとめられた情報がCluster Mapとして更新され、MONを経由してOSD/Clientに渡されたりしながら、それぞれのコンポーネントはIO処理要求/応答のやり取りをしてるということになります。

OSDに対するロケーションの決定なのですが、ClientからのWrite処理をMONがどう受け取り、どうOSD上に分かるよう場所を定義しているかについて書かれていたので読み込んでみました。

実際CRUSH MAPを表示させてみると、どのオブジェクトがどの位置にいるかについて、上図の()付き数字の部分のような表記が為されています。つまりはオブジェクトの場所を把握する単位として適切なのはどうやらPGIDみたいですね。それとオブジェクトIDが紐ついていれば、ある程度破損した場合のデータ特定とかができそうに見えます(実際には死ぬほど大変だろうし、フィジビリティの面でありえね~気もするけど)

もう一つ見かけた文言「Peering」

障害発生から復旧するときによくでてたメッセージ、Peering。
頭の中でBGPがちらついたんですけど、Cephではどうなんでしょう?

というわけでまとめたのが上図なのですが、まず、Clustermapを構成する際、最初にmonはオブジェクトIDを基にしたハッシュ値に従い、PGIDを計算し、それとプールIDを使って場所を定義します。

そしてその場所をどのOSDに割り当てるか、まずはPrimaryに対して決定を行います。この時点でSecondary以下のOSDはまだ決定されておらず、ここからReplicaに沿った数の対象OSDを選定しなければなりません。動きとしてこれをMONに任せきりにするのは非常に負担も大きく、厳しいことが予想されます。そこで、Primary OSDが他のOSDに申し入れてSecondary以下を決定し、MONへ報告するという処理がPeeringです。

ピアリングが起きてからの流れを書いています。基本的に人とは違って、「なってよー」「えーやだー」ってことは起きない(リソース枯渇になったらわからんけど)だろうと予想される中で流れを書いたらこんな感じ・・・というやつですかね。Replica数が2である場合を想定して書いてみました。

これであれば、mon側としては報告を受け、マスタデータを更新するだけなんでだいぶ楽っすね。

Cephふっとばしかける発端になったリバランス処理

これは分散ストレージあるあるというか、一般的な話なんで、そんなに難しいことでもないんですが、基本的にストレージというものは玉やノードを増やすと必ずと言っていいほどリバランスが行われます。そうでなくとも、ディスクあるいはストレージにおいて不均衡を検知したらこの処理が行われます。

理由は単純で、負荷の不均衡が続くとその部品寿命が短くなるからです。より長持ちさせるには、極力均等な負荷をかける、というのが特にHDDなんかでは当たり前な話だったりしました。SSDに関しても、半導体素子の負荷偏りを避けるために、内部に小さなRAIDコントローラなんかを組み込んで簡単なストライプをさせてるケースがあったりしましたが、今はどうなんだろうかなー・・

ただこのリバランス、結構負荷が高いです。先日Cephをふっとばしたときは、このリバランス処理に一部のノードがついていけず、スローディスクとなってダウンしたことで、当初想定以上の障害となり、IOブロックが走った・・・ということが起きちゃいました。

唯一わかって安心したというか、データ破損が発覚した時点で、不意なデータロストを避けるために書き込み保護をする機能があるんだなと言うのがわかった点ですかね。基本的にWrite IOが発生した時点でactivatedされたPGにしかアクセスを許容しないと言う挙動が当時確認されましたし、恐らくオブジェクトの関連付け、PGの関連付けなんかも裏にしっかりなされてるんだろうなと。

ただ、activating状態(未activatedで、ブロックされてるオブジェクト)に対してどう復帰させるか?というのがこの場合かなり難しいんだろうなと思われ、まぁよくぞ復旧できたなーとか思いました。

実際、一番大変だったのは破損した状態でノードを何とかディスク応答の速いところへ移動した後、「どうやってリビルドを開始させるか」でした。詳細は実はここにはかけぬのですが、RedHatのトラブルシューティングガイドを見ればだいたい分かるんじゃないかなと思います。

第5章:OSDのトラブルシューティング
https://access.redhat.com/documentation/ja-jp/red_hat_ceph_storage/2/html/troubleshooting_guide/troubleshooting-osds

k8sとCephの連携

備忘録的な感じですが、現在CephはRBDを使って一部k8sコンテナのデータストアとしての役割を果たしています。この手のオーケストレーターって、すごく便利なところがあって、ストレージプロビジョナーと呼ばれるコンテナを配置し、PVCでStorage Classを指定することによって、自動的に領域を払い出させる方式が一般的みたいです。

私の環境ではStorage Classが1つしかなく、上記内容になってます。

Provisionerは下図構成になっていて、まだ全体が理解しきれていません。プロビジョナー本体の中身が何しろわかってないので、そこをなんとかしたいんですが、ただ、k8sワーカーにCephパッケージ入れないといけないので、直接RADOSを触ってるというわけではなく、あくまでRBDを中継してなんぼの仕組みにはなってるようです。

k8sを使ってると、「コンテナはyamlぶっこめばいいだけだから楽だぜウェーイ」とか思っちゃうわけなんですが、よくよく考えたらそれじゃ中身見えてないわけで、あまりよろしくないっすね。最初はうぇーいでいいとしても、追々中身に対する理解は進めないとドツボにはまるなーとか思いました。

ココ最近、分散ストレージの障害が多いなと感じる

オブジェクトストレージサービスを中心として、ここ最近分散ストレージの障害が多いなって気がしていて、ちょっと心配です。しかもその内容が結構甚大で、実際対応された方々は相当な苦労を伴ったのではなかろうかと、心中お察し申し上げます。

分散ストレージって私は比較的懐疑的な認識を持っていて、それゆえに自宅Cephも少ない容量の領域(DBデータ領域:多くて10GBとか)ぐらいにしか行っていません。理由は、どうしても構成が複雑になるからです。

RAIDも大概怖いと言えば怖いのですが、ディスク同士がまだ、バックプレーンやSAS/FCループとかでつながってるうちは良いのかなと思うのですが、どうしても疎結合アーキテクチャになると、遅延とかソフトウェアバグの影響とかを気にしてしまうのです。恐らく職業病でしょう。

で、これまた恐らくは、テスト精度をガツンとあげないと死ぬのかなと。特にCephのようなOSSに関しては、その製品の不具合責任を誰が持つのかなとか考えると、プロプライエタリな代物のほうがまだコスト面(賠償金等のコンティンジェンシーな部分含めて)において優位なのかなーと言う感じがします。(ニフクラさんが使ってたのはCephを商用化した親会社のアプライアンスなんで、もうあれはそこに責任をとっていただいて・・・・なーんて)

分散ストレージって障害に強そう!ってイメージを持たれるケースが有るのかもしれませんが、意外に弱かったりするので、本当に気をつけたほうが良いよーというのが私個人として正直な気持ちです。ストレージはTHE 土台 of 土台 ですので、ぜひぜひ慎重に設計を・・・

No tags for this post.