メルカリがマイクロサービスを選んだ理由

中島大一氏(以下、中島):マイクロサービスプラットフォームチームでテックリードを務めています、中島です。よろしくお願いします。

今年のMTCのメインテーマは「エボリューション」ですが、この1年でメルカリに起こった大きな変化の1つは、モノリスアーキテクチャからマイクロサービスアーキテクチャへの移行です。

今日このあとも、マイクロサービスに関するセッションがいくつかあると思いますが、私からはインフラの基盤や組織のデザインをどのように行っているかについて紹介していきます。

メルカリがマイクロサービスに舵を切った大きな理由は、組織をどんどん拡大していくという目標です。現時点でのメルカリのエンジニア数は300人ですが、我々はその3倍の、1000人規模のエンジニアリング組織を目指しています。

そして今まで以上のスピードとパフォーマンスを保ってプロダクトを開発し、お客様により良いサービスを提供していくことを目指しています。

こちらは、今年の3月に発売された『Accelerate』という本です。

2013年から2017年の4年間を通して、スタートアップを含む2,000以上の企業に「いかに組織のパフォーマンスを加速させるか」という視点で聞き取り調査を行った結果をまとめたものです。

調査結果の1つに、こちらのグラフがあります。

組織のエンジニアの数とそのパフォーマンスを、組織の違いによってマッピングしたグラフです。横軸は、組織におけるエンジニアの人数。縦軸は、組織のパフォーマンス。ここでは指標として、1日のエンジニアあたりのデプロイ数を使っています。

ハイパフォーマンスの組織は、エンジニアが増えるほどデプロイ数も増える

調査結果によると、パフォーマンスの低い組織は、エンジニアが増えれば増えるほどデプロイ数が減少しました。普通のパフォーマンスの組織は、エンジニアが増えてもデプロイ数に変化はありませんでした。

一方で、ハイパフォーマンスの組織は、エンジニアの数が増えれば増えるほど、指数関数的にデプロイの数が増えました。メルカリが目指していきたいのは、この、ハイパフォーマンスの組織と同じ状態になることです。

この目標は、単純にアーキテクチャをモノリスからマイクロサービスに分割するだけでは達成できません。「逆コンウェイの戦略」といわれるように、それぞれのアーキテクチャに合わせて、最もパフォーマンスが発揮できるチーム構成や組織のデザインをしていくことが非常に大切です。

なので、まずは、我々がどのように組織だったりチームをデザインしているかという話から始めたいと思います。

ソフトウェアのライフサイクルは、大きく4つに分けられると思います。

開発をし、テストをし、デプロイをし、それを運用する。これを繰り返します。

モノリスのアーキテクチャでよく見られるチーム構成では、専門性でチームを分けています。開発を行うバックエンドチーム、テストを行うQAチーム、そしてデプロイ・運用を担うSREもしくはインフラチームというふうに分けられると思います。

マイクロサービス化とは、モノリスだったコードベースを機能ごとに分割して、独立したサービスとして開発をしていくことです。

マイクロサービスにおける理想的な組織構造は?

しかし、組織のことをなにも考えず、アーキテクチャのみを変更していくと、マイクロサービス化の利点を失ってしまいます。

まず、バックエンドチームをそのままにしてしまった場合は、マイクロサービスのオーダーが不明瞭になってしまいます。その結果、誰が管理しているかまったくわからない、誰も触れない放置サービスをどんどん生み出してしまうことになります。

ほかにも、QAチームやSREチームをそのままにしてしまうと、マイクロサービスが増えれば増えるほどリリースのボトルネックになってしまい、マイクロサービスの開発のスピード感を失ってしまう可能性があります。

また、専門性でチームを割る構成を続けてしまうと、チーム間でのコミュニケーションや知識の共有もどんどん少なくなってしまい、結果、サイロ化というものを引き起こしてしまいます。

サイロ化はチーム間でのコラボレーションを妨げてしまうので、効率的に開発のサイクルを回し改善していくことができなくなってしまいます。

これらの課題を解決する、マイクロサービスアーキテクチャにおける理想的な組織構造は、専門性でチームを割るのではなく、こちらはサービスAのためのチーム、そちらはサービスBのためのチームというように、各マイクロサービスごとにチームを構成し、そのチーム内でテストからデプロイ、オペレーションまでを行うことです。

これで、先ほど挙げたようなボトルネックがなくなります。各チームは自律的に、独立して意思決定を行い、効率的な開発フローを自分たちで回すことができるようになります。

また、各チーム内ですべてのソフトウェアライフサイクルを担うことで、チーム内の開発やデプロイ、オペレーションの各領域の知識共有ができます。これによって、より良いサービスの開発につなげたり、開発サイクルそのものをチーム内で改善していくことができるようになります。

「理想的な組織構造」にも弱点がある

これが我々の考えている、マイクロサービスにおける理想的なチームのあり方です。

しかしこの組織構造は、多くの利点の一方で、新しい課題も生み出してしまいます。マイクロサービスアーキテクチャにおける開発者は、ソフトウェアエンジニアだけでなく、ソフトウェアのテストのスペシャリストであるSET、そして運用のスペシャリストであるSREとして行動する必要があります。

要するに、これまで開発のみに集中すればよかった開発者が、自分たちでデプロイや運用もやらなきゃいけなくなるわけです。これをいきなり「やれ」って言われたら、すぐできるでしょうか。私はなかなか難しい要求だなと思います(笑)。

これらを助けるために設立されたのが、マイクロサービスプラットフォームチームです。

これは、開発者が自分たちでマイクロサービスの開発からデプロイ、運用を行うことを助けるためのプラットフォームです。

Mercari JPがマイクロサービスへの移行を始めたのは、ちょうど1年前です。このグラフはこの1年の、マイクロサービスプラットフォーム上におけるマイクロサービスの数の変化を表しています。

横軸が時間で、縦軸がマイクロサービスの数です。赤い線が既に本番で動いているマイクロサービスの数、青い線が現在開発中のマイクロサービスの数です。

1年間でマイクロサービスの数を数十倍にもできた方法とは

1年前は、1つのマイクロサービスしかありませんでした。今、本番で動いているものは19です。そして開発中のものは、73にもなっています。この数はどんどん増えています。

今日の発表では、1年でこの数字になるために、マイクロサービスプラットフォームチームがどのような取り組みをしていったかについて、簡単に紹介をしていきたいと思います。

我々が達成したのは、一言で表すと「なにもないところにベースとなる道を整えた」ということです。

より具体的に言うと、基礎となるマイクロサービスのためのアーキテクチャの構築、そして基本的なDevOpsの文化の醸成を行いました。

まず、アーキテクチャについて紹介したいと思います。

ここについて整えたことは、大きく4つあります。APIゲートウェイアーキテクチャへの移行、サービス間のコミュニケーションプロトコルの統一化、新規のマイクロサービスを作るためのテンプレートプロジェクトの提供、そして開発者のインフラへのアクセス制限管理のデザインです。

はじめに、APIゲートウェイのアーキテクチャの移行について簡単に紹介します。

(スライドを指して)これは1年前のメルカリの、メインのアーキテクチャです。

モノリスのAPIと、それが使う巨大なデータベースのみがあり、すべての開発がここで行われていました。

これに対してまず、この全体に自分たちで内製した「APIゲートウェイ」というものを配置しました。

これによって、メルカリのすべてのリクエストはこのAPIゲートウェイ経由になるようになりました。

開発者自身で運用できる環境づくりを進めた

これだけを見ると特に利点があるようには見えないですが、新しいサービスを作りたいと思ったとき、そのAPIゲートウェイ配下にサービスを置くことができます。

これによって、モノリスのコードベースからある程度独立させて新規のマイクロサービスの開発を行うことができるようになります。

また、既存の機能をどんどんマイクロサービスとしてモノリスから切り離していくときも、このゲートウェイ配下にサービスを配置して、レイヤーで1件ずつ振替を行うことで、段階的にモノリスからマイクロサービスに移行できます。

また、APIゲートウェイはGCP、Google Cloud Platform上にデプロイすることで、クラウドへの移行も開始しています。

これによって、例えばサービスAがなにかしらのデータストアを使いたいと思ったときに、クラウドのマネージドサービスを使うことができるようになりました。

さらに、新しいサービスはコンテナとしてKubernetes上にデプロイしています。

Kubernetesとは、Googleが社内のシステムの知見をもとにOSS化したコンテナオーケストレーションシステムです。

これを使うことで、例えばコンテナが死んでしまったとき、それを自動で立ち上げてくれるセルフヒーリングの機能だったり、サービスディスカバリー機能や、リクエストが大量に来たとき各リクエストに合わせてコンテナを水平にスケールさせるオートスケーリングといった機能を使うことができます。

これによって、インフラのオペレーションコストも劇的に減らすことができるようになっています。以上が、APIゲートウェイへの移行になります。

マイクロサービス化にあたり具体的に何が必要だったか?

次に我々が整えたのは、サービス間のコミュニケーションプロトコルの統一化です。ゲートウェイからマイクロサービス、マイクロサービス間のサービスのコミュニケーションプロトコルとして、我々はgRPCを採用しています。

こちらも、GoogleからOSS化されたRPCのフレームワークです。

gRPCでは、プロトコルバッファというIDLを使ってサービスのインターフェースを定義します。そして、その定義をもとに、あらゆる言語のサーバークライアントライブラリを自動生成できます。これによって初期の実装コストを減らしたり、マイクロサービス間でインターフェース定義の統一化を図れます。

各マイクロサービスのプロトコルバッファの定義の管理や、サーバークライアントの生成を簡単に行えるように、我々は「platform-proto」というリポジトリを準備しています。

各サービスの開発チームは、自分たちのサービスのプロトコルバッファの定義を書いて、このリポジトリにプルリクエストを送ることができます。

このプルリクエストをマージすると、CI上で各ゲート……現在のメルカリで主に使われているものだとGo、Python、Java、Node.js、Swiftといった言語のクライアントサーバーを自動生成できます。

この仕組みによって、マイクロサービスを提供する側も、それを使いたいと思った側も、すぐに実装を始めることができるようになっています。

またこのプロトコルバッファの定義を、すべて1個のリポジトリにまとめているので、これがドキュメント的な役割も果たしています。例えばサービスAを使いたいと思った人は、このリポジトリに来てサービスAのプロトコルバッファの定義を見れば、どうやってそのサービスAを叩けばいいか簡単にわかるようになっています。

各々で運用するためのインフラを整備する

次に我々が整えたのは、新規のマイクロサービスを作るためのテンプレートプロジェクトの提供です。これまでメルカリのメインの言語はPHPでした。マイクロサービスではメインとして、Go言語を採用しています。

もちろんすべてのエンジニアが、Goが得意なわけではありません。Goの初心者がいきなりパッケージ構成などを考えながらスクラッチでマイクロサービスを書き上げるのは、非常に大変なことです。そのコストを減らして、すぐにマイクロサービスの開発を始められるように「mercari-echo-jp」というテンプレートプロジェクトを提供しています。

アプリケーションとしては非常にシンプルで、受け付けたリクエストをそのまま返すだけのgRPCアプリケーションですが、この中に標準的なGoのパッケージ構成や、本番運用で必要になってくるモニタリング、エラーリポーティング、ヘルスチェッキングといったコードを実装しています。

またアプリケーションの実装以外にも、デプロイのときに必要になるコンテナ化を行うための標準的なDockerfileや、最低限必要なCIの設定ファイルなどをここで提供しています。マイクロサービス開発者は、このリポジトリをコピーするだけですぐに開発を始められるようになっています。

最後に整えたのは、開発者のインフラへのアクセス制限の管理制度です。モノリスのアーキテクチャでは、インフラレイヤーを触れるのはSREのみでした。

なのでインフラへのアクセス制限はすごくシンプルで、SREか否か、それだけでした。

マイクロサービスでは、先ほど言ったように、各チームが自分たちのサービスを自分たちで管理・運用できる必要があります。

つまり各チームが自分たちの管理しているサービスのインフラに、自由にさわれる必要があります。それを整えるための、アクセス権限のデザインを行いました。

最初にアクセス権限のデザインを行った

まず、1番の基礎となるマイクロサービスの基盤・Kubernetesは、マイクロサービスプラットフォームチームが、クラスタadminとしてその安定利用に責任を持っています。

マイクロサービスの開発者は、Kubernetesクラスタそのもののアクセス権限は持っていません。

例えば、サービスAチームが新しいマイクロサービスをリリースしたいという場合を考えます。

まず、実際にサービスをデプロイするKubernetes上には、サービスA専用のネームスペースを作ります。

これはKubernetesの機能で、仮想的にクラスタを分離する機能です。「クラスタの中にサービスA専用の部屋を作る」と考えるとわかりやすいでしょうか。

そしてKubernetesのRBACという機能を使って、サービスAチームをそのネームスペースの管理者として登録します。部屋の鍵をサービスAに渡す、ということです。

これで、サービスAチームはこのネームスペースに自由にアクセスできるようになりました。

例えば問題が起こってしまったときにネームスペース内のコンテナのログを見たり、コンテナを再起動するといったオペレーションを、自分たちでできるようになります。

次に、サービスAがGCPのマネージドのサービス、例えばSpannerとかを使いたい場合は、サービスA専用のGCPプロジェクトを作って、サービスAチームをそのプロジェクトの管理者として登録します。

これでサービスAチームは、自身のGCPプロジェクトにマネージドのDBを足したり、そこでクラウドファンクションを使うことが自由にできるようになりました。

サービスBチームが新しくサービスをリリースしたいときも同様です。

Kubernetes上にはBチーム専用のネームスペース、GCP上には専用のGCPプロジェクトを提供します。これによって、サービスAチームはサービスAの、サービスBチームはサービスBのためのインフラに自由にアクセスできるようになって、自分たちでサービスを開発したら、自分たちでサービス管理・運用できるようになっています。

以上の4つが、基礎となるアーキテクチャの整備です。

DevOps文化の醸成をはかる

この1年で我々が整えた大きなことの2つ目は、DevOps文化の醸成です。DevOpsとは、開発チームが自分たちでインフラの構築からデプロイを行うことです。

この1年でマイクロサービスの開発者が、自分たちでできるようになったことが大きく2つあります。1つ目は、インフラのセットアップを行うプロビジョニング。2つ目は、サービスのデプロイです。

まずプロビジョニングから見ていきたいと思います。

先ほど、各チームがKubernetesのネームスペースや、GCPにアクセスできるようにしているという話をしました。

マイクロサービスを始めたいときは、そもそもこのネームスペースやGCPプロジェクトをセットアップする作業だったり、データベースを使いたいときはそのデータベースのセットアップを行わないといけません。これがプロビジョニングです。

我々は、このプロビジョニングを、SREの中では当たり前になってきた「Infrastructure as Code」という文化を開発者にどんどん浸透させていくことで実現しています。

さまざまなツールを利用しマイクロサービスを簡単に構築する

具体的なツールとしては、HashiCorpの開発しているTerraformというツールを使っています。

インフラ管理をしている人にとってはデファクトのツールだと思いますが、TerraformはGCPだったりAWSといったクラウドプロバイダのAPIを抽象化して、コードとして宣言的にインフラのあるべき状態を記述することで、その状態になるようにインフラのプロビジョニングを行ってくれるツールです。

各マイクロサービスのTerraformのコードの管理だったり、そのコードの自動アプライを実現するために「microservice-terraform」というリポジトリを準備しています。

このリポジトリには、「Starter-kit」というTerraformのモジュールも提供されています。

このStarter-kitを使うことで、マイクロサービスをリリースするのに最低限必要なインフラを簡単に構築できるようになっています。この最低限必要なインフラの例としては、さっき紹介したKubernetesのネームスペースや、モニタリングの設定、GCPプロジェクトなどがあります。

Starter-kitを使えば、それらを1発でブートストラップできるようになっています。

また、別途そのサービス特有でデータベースを使いたいとなった場合は、このような占有的なコードを開発者自身が書いて、Terraformに食わせることで、DBなどを自分のプロジェクトの中に構築できます。

開発者のインフラの経験不足はプロのレビューでカバー

ワークフローとしては、まず開発者は自分たちでTerraformのコードを書いて、このリポジトリにプルリクエストを送ってもらいます。

インフラの経験が少ない開発者がいきなりTerraformのコードをバリバリ書くのは難しいので、プロフェッショナルである我々プラットフォームチームがレビューを行います。

レビューが通ってコードをマージすると、CI上でTerraformの実行が行われて、実際にプロビジョニングが行われるというフローになっています。

リポジトリ内には、各サービス専用のディレクトリが準備してあります。

サービスAのためのディレクトリ、Bのためのディレクトリ。各チームはそのディレクトリにプルリクエストを送って、自分たちのサービスのプロビジョニングを行えるようにしています。

このワークフローで問題になってしまうのは、プラットフォームチームのレビューの部分です。

ちゃんとしたTerraformのコードを保守していくためにレビューは必須ですが、マイクロサービスが増えれば増えるほどここがボトルネックになってしまって、結局スピード感を失ってしまう原因になっています。

この課題を解決するために、GitHubの「CODEOWNERS」という機能を使っています。

CODEOWNERSを使うと、リポジトリの特定のディレクトリに「コードオーナー」を指定できます。コードオーナーで、プルリクエストのアプルーブだったりマージができるようになります。

この機能を使って、各サービスの専用ディレクトリに、各サービスのチームメンバーをコードオーナーとしてアサインします。そうすることで、自分たちのサービスに関するプルリクエストを自分たちでアプルーブして、マージできるようになります。

つまり、プラットフォームチームのレビューをオプショナルにしました。

レビューシステムで開発者を育成

レビューを何度か受けて、自分たちでTerraformのコードを書いていける自信がついたチームは、自分たちでレビューを行って、スピード感を持って開発を進める。まだTerraformの作成に不安があるチームは、プラットフォームチームにレビューを依頼して、しっかりしたコードの書き方を覚える、というふうにやっています。

この仕組みはとてもうまく回っていて、数字で見ると今でこのリポジトリに、110もコントリビューターがいます。

これはどういうことかというと、100名以上の開発者が自分たちでインフラの管理をしているということです。

1年前は、インフラの管理ができるのは10名くらいのSREしかいませんでした。それを考えると10倍以上の成長になっています。

直近1週間のPR(プルリクエスト)の数を見てみると、45のPRがマージされています。

つまり営業日でいうと、1日あたり9回のインフラ変更を、開発者自身が自分たちで取り組んでいることになります。この変化は、プラットフォームがもたらした文化的に大きな成果の1つだと思っています。

将来的には開発者自身がどの設定も行えるようにしたい

次は、サービスのデプロイです。これまでサービスのデプロイの設計などは、基本的にSREにお願いすることが多かったんですが、マイクロサービスではそのデプロイさえも自分たちが、そのサービスの特性に合わせて設定を行っていく必要があります。

我々はこれを可能にするために「Spinnaker」というものを会社で提供しています。

SpinnakerはNetflixがOSSにした、Continuous Deliveryプラットフォームです。マイクロサービスチームはSpinnakerにアクセスして、そこで自分のネームスペースでのコンテナのデプロイを確保します。

Spinnakerはさまざまなステージというものを提供しています。例えば、カナリアデプロイメントを行うものや、ブルーグリーンデプロイメントを行う、もしくはデプロイの承認を行うといったものです。

開発者は、それぞれのステージを自分たちのサービスに合わせて組み合わせていくことで、デプロイのパイプラインを構築しています。Spinnakerを導入して1年以上になりますが、今は60以上のパイプラインが開発者自身で作られています。

これはSpinnakerの設定画面の例です。

開発者はこの画面を通じて、デプロイの設定などを行います。

ただ、これはGUIなんですね。多くの開発者にとってずっと、この「GUIで設定をしないといけない」ことがかなりのペインポイントになっていました。

小規模のチームで素早く動くときには、GUIは便利なツールだと思います。ですが、チームがスケールすればするほど、変更のレビューができなかったり、ドキュメントとして残すことが難しかったり、ほかのチームの設定をコピーして使えないことが問題になります。

これらがマイクロサービスの開発のクオリティを阻害する要因になってしまっていました。

この問題を解決するために、Spinnakerにいるコンテナデプロイも「Infrastructure as Code」への移行を始めています。

より具体的には、「Kubernetes v2 provider」というSpinnakerの機能を利用し始めています。これはGUIで設定するのではなく、KubernetesのYAMLを直接書く方式です。つまり、KubernetesのYAMLさえも開発者に書いてもらおう、という方向に移行しています。

次の課題は「いかに開発者が本番でオペレーションできるようになるか」

これらを導入するにあたって「microservice-kubernetes」というリポジトリを準備しています。

このリポジトリを使ったワークフローはTerraformと一緒で、開発者はデプロイの設定に必要になるKubernetesのYAMLファイルを自分で書き、このリポジトリにプルリクエストを送ります。

Terraformと同様、いきなり理想的なYAMLを書くことは難しいので、プラットフォームチームがレビューを行います。レビューが通り、コードがマージされると、これをSpinnakerとシンクさせて、それを使ってデプロイを行うことができます。

この仕組みはまだ導入したばかりですが、GUIによる設定の負担を減らして、かつ開発者としてはKubernetesのYAMLファイルとか設定ファイルを覚えることで、自分たちでより良いデプロイメントをカスタマイズしていけるのではないかと期待しています。

以上が、我々プラットフォームチームがこの1年で取り組んできたことです。

ここからは、我々が今後、どういうことに取り組んでいきたいかを簡単に紹介したいと思います。

最初に紹介したように、現在本番で動いているマイクロサービス19に対して、開発中のものは73です。

マイクロサービスの開発を始めるという部分は整ってきたと思える一方で、今後はより多くの、本番に出てくるマイクロサービスに対処しないといけません。次に来る我々の課題は、いかにマイクロサービス開発者が自分たちのサービスを、リライアブルに本番でオペレーションできるようになるか、という部分です。

これに対して、いくつかアイデアを紹介したいと思います。

SLI/SLOの導入で開発とリライアビリティのバランスを図る

1つ目のアイデアは、すべてのサービスに対してGoogleのSREが採用している、SLI/SLOという指標を導入することです。

SLIは「Service Level Indicator」の略で、提供しているサービスのサービスレベルの定量的な指標です。SLOは「Service Level Objective」という、SLIに対する目標値です。

これらは各サービスごとに、その特性に合わせて決定していくことになります。例えばそのサービスが「早くレスポンスを返す」ことが重要な指標だとしたら、SLIは「すべてのリクエストのうち、100ms以内にレスポンスを返せたリクエストの数」などで計ります。

SLIとSLOを設定することで、サービスの機能開発とリライアビリティに対する取り組みのバランスを、数字を使って取れるようになると考えています。

例えばSLOを99.9パーセントに設定した場合、これを満たしている場合はひたすら機能開発を続ける。その割合を割ってしまった場合は、サービスの機能開発を止めてリライアビリティの改善に努める、といったバランスが取れるようになります。

こうすることで、例えば機能開発ばかりしていて障害が頻出するサービスをなくしたり、逆にリライアビリティばかり追求して新しい機能をぜんぜん出せない状況を避けられるようになると考えています。

プラットフォームチームとしては、まずこのSLI/SLOという考え方を浸透させることが大事だと思っています。その上で、サービス全体のSLOを俯瞰できるようなダッシュボードなどを整えていくことも、1つのアイデアかなと思っています。

カオステスティングで障害に対応できる開発者を育成

2つ目のアイデアは、カオステスティングです。

これまで障害対応はSREが担ってきて、開発者が自分で障害対応することはなかなかありませんでした。しかし今後は、開発者が自分たちでサービス障害の1次対応をやっていく必要があります。

カオステスティングを使ってわざと障害を起こすことで、例えば問題のあるコードに、障害が起こった時点で気付くのではなく、すぐに気付けるようになる。もしくは、モニタリングやアラートがそもそもちゃんと動いているかをチェックする。

あとは、開発者が実際に自分たちで障害対応を経験することで、実際に障害が起こっても落ち着いて対応できるようになる仕組みを、この手法で整えていきたいと思っています。

そして最後のアイデアが、サービスメッシュです。

モノリスのコードベースでは、ほとんどがプロセス内の関数呼び出しで完結していました。一方でマイクロサービスにおいては、ネットワーク越しのサービス間コミュニケーションが大量に発生します。

サービスメッシュを導入することで、サービス間コミュニケーションのオブザーバビリティの改善だったり、ネットワークコミュニケーションのリライアビリティを向上させたり、あとはセキュリティを強化していきたいと思っています。

以上が、我々プラットフォームチームがこの1年で取り組んできたことと、今後取り組んでいきたいことです。以上で発表を終わります。ありがとうございました。

(会場拍手)