Re:silience から始めるカオスエンジニアリング生活

小杉山拓弥氏(以下、小杉山):それでは、発表を始めさせていただきます。

まず、自己紹介をします。

IDはitkqでインターネットでやっていて、クックパッドには2018年に新卒入社しました。技術部SREグループというところにいて、カオス大臣という設定があるんですが、まあ、それはよくて、耐障害性の向上などの取り組みをやっています。

今日はタイトルにもある通り、カオスエンジニアリングの話をしていきたいと思います。この言葉自体は去年もバズったところがあって、みなさんも聞いたことがあるのではないかなと思っています。

簡単に説明だけしていくんですが、もっとも有名な定義として「カオスエンジニアリングの原則」というものがあります。ここには日本語訳を出してるんですが、「分散システムにおいてシステムが不安定な状態に耐えることのできる環境を構築するための検証の規律です」。なるほどという感じです。

ちょっと抽象的なところがありますが、例えとして挙げられるのが予防接種です。将来的な病気を予防するために、人体に対して意図的に弱らせたウイルスを侵入させることによって免疫をつける、ということがあると思うんですが、その人体をシステムに置き換えてみるとわかりやすいかなと思います。

さっそくクックパッドでもカオスエンジニアリングをやっていくということで、Netflixが出してるChaos Monkeyというツールがあるんですが、それを使ってランダムにインスタンスを落としていきます。

……ウソなんですが、(笑)、クックパッドではChaos Monkeyは使ってないし、すぐに使う予定もありません。

今日話したいこととしては、「結局、カオスエンジニアリングって何だっけ?」ということを考えたい、というのがあります。Chaos Monkeyが確かにカオスエンジニアリングという言葉を広めたのは確かだと思うんですが、少なくとも「ランダムに落とす」というところだけ切り取ると、ここは重要じゃないと思っています。

最近カオスエンジニアリングが流行っているから、うちでもランダムにインスタンスを落としていこうというのは説明が足りてないと思うし、こういう導入の仕方は間違ってるのではないかなと、個人的には思っています。

今日は、クックパッドのTechConfということもあるので、クックパッドがカオスエンジニアリングをやっていく理由と、「実際どうやってやっているのか?」という、その2点について話していきたいと思います。

マイクロサービスアーキテクチャへの道のり

最初の理由についてなんですが、これはクックパッドが長い時間をかけてやっているマイクロサービスアーキテクチャへの移行というのが大きく関係しているので、まずここから話していきたいと思います。

2008年当時、cookpad.comというレシピサービスは、1つのRailsアプリケーションでした。下にあるディベロッパーの数は適当なんですが、上の円と比較した規模感だと思ってください。

これが2015年になると、開発も進むし、ディベロッパーも増えて、「世界最大のRailsモノリス」と呼ばれるまでに成長します。

ここまで大きなモノリスになるとさまざまな問題が起きていて、コードベースもそれなりに大きくて、テストもたくさんあるので、CIが遅いとか、共有コードがたくさんあるので、どっか1行変更しただけで、ぜんぜん違う部分のテストがめちゃくちゃ落ちるとか、ということがありました。

また、違う機能を作ってるはずなんだけど、共有コードの問題からコンフリクトしてしまって、「どっちが先に出すのか?」という調整が発生したりしました。

この時、並行して、事業の変化から新しいサービスというのが立ち上がってきます。これはRailsとは独立したアプリケーションとして開発されていて、必要があればレシピの情報を問い合わせるようになっていきます。

これとは別に、レシピサービスから切り出せる機能というのを切り出す、という動きもありました。切り出すコストというのはそれなりにあったはずなんですが、さまざまなトレードオフを考えた結果、このような独立がいくつか起きていきます。

モノリスからマイクロサービスに移行

このようにクックパッドでは、組織の構造の変化とか事業の変化を起点として、全体のシステムを1つのアプリケーションではなくて、アプリケーションの集合体からなるシステムとして構築していく、マイクロサービスアーキテクチャへ移行していくことにしました。

これによって、先ほどのモノリスで非常に問題だった速度面の問題は、大きく改善されたと言っていいと思います。ただ一方で、トレードオフはあります。全体としては分散システムになっていって、複雑になっていました。

ここに出してるのは、実際にいまクックパッドで起こっているサービス間の関係を一部出したものなんですが、認証やA/Bテストという内部システムがあったりとか、cookpadTVなど、新規のサービスも出てきています。

突然、認証サービスのレスポンスが遅くなったことがありました。

すると、その認証に依存している、とくにユーザーに面しているサービスまでその影響が及んで、レスポンスタイムが遅くなったり、エラーを返してしまうということが起こりました。

このようにマイクロサービスアーキテクチャを進めていくと、1つのシステムの障害が別のシステム、複数のシステムにまで及んでしまうということが起こりうります。例えば、レスポンスタイムが遅くなったら、クライアント側でリトライするとかということもあると思うんですが、逆にリトライが集中すると、かえって障害を悪化させてしまうということもあります。

当時、中央的にインフラを運用していたSREチームからすると、サービス間通信の状況がすぐわからないというのが、課題としてありました。個々のシステムのメトリクスは取れているんだけど、サービス間通信のメトリクスは取れていなくて困っていました。

サービスメッシュとはなにか

問題を解決するために導入したのがサービスメッシュです。

これは簡単なサービスメッシュの図のつもりなんですが、簡単に言うとサービスメッシュは、アプリケーションレイヤーから通信を引き離したものになります。

実際の例で説明していくんですが、このService AがService BとかService Cに通信したいとします。この時に、アプリケーションが直接これらにつなげにいくのではなく、隣にいるEnvoy Proxyに「通信したい」ってアプリケーションが依頼すると、実際の通信はこのEnvoy Proxyがやります。

上に「Control Plane」と書いてある部分が、通信の設定などをする部分、独立したコンポーネントになっていて、Service Bは自分のIPアドレスなどをこれに教えてあげるし、Envoyはその情報を取ってきて、Service Bがどこにいるかわかるようになっています。

これを導入したことによって、アプリケーションにほとんど手を加えることなく、サービス間通信を実現しました。

これによってうれしいことがありました。Envoyは、サービス間通信のメトリクスを吐くことができます。例えば、AからBの通信でリトライが何回起きてるとか、そういう情報を吐くことができます。

あらゆるサービスにこのEnvoyというのを導入していくことによって、中央からメトリクスを集めると、サービス間通信の状況がよくわかるようになりました。

また、これまでアプリケーションレイヤーでやっていた、タイムアウトやリトライ処理を個々のアプリケーションに実装するのではなく、そのEnvoyという共通のコンポーネントで行うことによって、コンフィグを共通化できるし、中央的にそれを管理できることによって、一時的な障害に耐えやすくなりました。

障害が起きたときにユーザーに対してどう振る舞うか

なったんですが、これで問題が解決したかというとそんなことはなくて、突然ストレージがぶっ壊れて、サービスが応答しなくなるということもあるし、ネットワークが突然ダメになってしまうということもある、ということです。

このように、障害をゼロにするというのは絶対にできなくて。クラウドのSLAは100パーセントじゃないし、逆にどんな高価な機器でも、壊れる可能性はあるということです。

そんな時に考えたのが、依存先のシステムが障害になった時に、とくにユーザーに面してるサービスはどうふるまうべきなのか、ということでした。

マイクロサービスアーキテクチャにおいて障害に対する問題意識を改めて考えたんですが、ユーザーにとっては、システムの裏側がモノリスなのかマイクロサービスなのかはどうでもよくて、ユーザーがやりたいことが提供できるのかできないのかという、ただそれだけが重要だと思っています。

あるシステムで障害が起きた時に、引きづられてサービスごと全部ダウンしてしまうのか、それとも部分的に機能を提供できるのかというのは、大きな違いがあると思っています。部分的に提供したほうがうれしいユーザーは絶対いるはず。

ということで、まずは我々のシステムが、障害が起きるとどのようにふるまうのかを知る必要がありました。ただ、複雑な関係性だったり、全部をシミュレーションしてトレースするのは難しいので、逆に意図的に障害を起こす、Fault Injectionするという考え方がありました。

カオスエンジニアリングを始めるのは必然

というのが我々とカオスエンジニアリングの出会いです。

ここに出してるのは、冒頭で出した原則なんですが、我々なりの解釈でいくと、マイクロサービスアーキテクチャで発生する障害に対する、プロアクティブなアプローチのエンジニアリングというふうに捉えています。

去年、カオスエンジニアリングやっていく宣言というのが、突然出たかのように見えたんですが、まったく突然ということはなくて。今まで説明してきたように、我々のシステムのアーキテクチャが発展していくにしたがって、自然と、また、必然的に必要になったものということを強調したいです。

この図に戻るんですが、分散システム的に構築されたマイクロサービスアーキテクチャで重要なレイアウトととして、ネットワークがあります。

このネットワークは、全体を支えるというのもあるんですが、場合によってはアプリケーションレイヤーの障害やパフォーマンス劣化をこいつが隠してしまうことがあります。

Network Fault Injection Platform

そこで我々が導入したのが、Network Fault Injection Platformです。

これは先ほど説明したサービスメッシュと深く関係していて、このData plane、実際に通信を行う部分を担当していたEnvoyというものがあるんですが、これが組み込みのFault Injectionの機能を持っています。

例えば、Service AからBへの通信のリクエストの30パーセントを503で失敗させるとか、ということができます。なので、この機能をメインに、Network Fault Injectionするプラットフォームというのを作っていくことにしました。

大きな理由としては、アプリケーションにほとんど手を入れることなく、ネットワーク層の障害をエミュレートできることです。

クックパッドにはhako-consoleといういい感じのコンソールがあって、これはディベロッパーもSREも見ることができる、サービスのためのコンソールになっています。

ここにFault Injectionするための設定のUIと、このContorol Planeを担当しているItachoというソフトウェアがあるんですが、こいつを操作するロジックを入れたことで、実質的にこれがプラットフォームになりました。

Known unknowns

プラットフォームができたはいいんですが、「じゃあ、実際どうやってやっていくのか?」という問題があって、場合によってはユーザーに対して悪影響を実際与えることにもなりかねないので、慎重になる必要があります

Known unknownsという概念があります。これを使って、まずはシステムにおける障害を分類して考えています。

3つあるんですが、Known knownsは知ってること。例えばAがダメになってしまった時に、Bがどれだけダメになるのかがわかる。Known unknownsは、Aがダメになることがわかってるんだけど、どういう影響があるのかは実際わからない。最後、Unknown unknownsは、想像もできていないことで、Bがダメになることすら考えたことがない、という状況になっています。

これ自体は非常に汎用的な概念で、一般的に知識というのは知れば知るほど知らないことが出てくるので、広がる方向としては下向きです。

カオスエンジニアリングは、この下2つに対抗する手段だと考えています。その知らないことに対して、それを知るためには、仮説を立てて実験するという方法があります。

我々の出発点である、システムに、「これがダメになったらどういう影響があるのか?」というの状態はまさにKnown unknownsになっていて、これを知っていくためにFault Injection Testingという手段があると考えています。

本番環境での継続した実験は、カオスエンジニアリングの原則ではアドバンスのものを渡されていて、個人的にはこれがUnknown unknownsに対抗する手段だと考えています。

我々にはまだKnown unknownsがあるということで、ここから始めていくことを決めました。これを放ったらかしにすると、たぶん本番環境で実験するとユーザーに悪い影響が出ると分かっているけれど、分かっていながら実験するのは、僕は「違うんじゃないかな」と最初に思いました。

ステージング環境で障害をエミュレート

先ほどの図に戻りますが、我々のサービスメッシュというのは、重要なこととして、本番環境も、ステージングも、だいたい全部が同じメッシュ構造を持っていることです。

なので、ネットワークレイヤーの障害のエミュレートをステージングでやっても、ある程度、なにかしら発見があるのではないかと考えました。

この例では、クックパッドブログからレシピサービスへの通信を失敗させるという、すごく単純な実験をしてみました。クックパッドブログはレシピサービスのレスポンスをキャッシュしている部分があるので、そのロジックに耐障害性に関する弱い部分が見つかって、それを修正することができました。

これは本当に単純な実験で、今日は時間の関係で詳細は言えませんが、また機会があれば発表したいと思います。

今日は「クックパッドがなんでカオスエンジニアリングをやっていくのか?」と、「どうやっていくのか?」の2点について話しました。Network Fault Injectionを起点として、Known unknownsの検証や改善からやっていって、ユーザーやディベロッパーをないがしろにしないやり方で、今後も耐障害性の向上に取り組んでいきたいと思います。

以上で発表を終わります。ありがとうございました。

(会場拍手)