Kubernetes導入で実現したい世界とその先にあるMicroservices

はじめに

CTO兼SREエンジニアリングマネージャーの中野です。ここしばらくの間、CTO/SREエンジニアリングマネージャーとして注力しているKubernetes導入について紹介したいと思います。

今回は、Kubernetes自体がどういうものなのかということより、それをツールとしてどう使い、それでどういう世界を実現したいのかみたいなところを中心に紹介できたらと思います。

f:id:masatomon:20180822163745p:plain

まず現在の状況ですが、Quipperでは、大きく分けてスタディサプリの小中高校生向け日本以外向けの2つのサービスを展開しています。サービスとしての構成はほぼ同じですが、基盤としては別々のAWSアカウントで運営されています。このうち日本国外向け環境では、Kubernetes化がほぼ完了というステータスになっています。目下、スタディサプリも移行中です。

Kubernetes化以前は、Deis(Herokuクローン的なもの)をベースに構築していました。Quipperでは創業初期の頃からHerokuを使っていてそれを途中でDeisに移行したという経緯なので、インフラの中身としては常に進化しつつも、Webエンジニア目線でのインフラという意味では、初期からそれほど大きく変わっていません。運用の仕組みも進化しつつも基本的なところは同じでした。今回はその辺の仕組みを含め刷新しようと考えています。

実現したい世界

今回、Kubernetesを導入し、また周辺の運用フローを改善することによって、Quipper内に変化を起こしたいことや解決したいことがいくつかあります。すべてがつながっていますが別視点でもあります。

SRE業務を部分的にWebエンジニアへ

Kubernetes化以前は、いわゆるWebアプリケーション(Ruby on Railsと場合によってはフロントエンドSPAのようなもの)がWebエンジニアの守備範囲で、その周辺のリバースプロキシやCDNのようなものはSREチームの分担になっていました。

ただ、CDNをいかに有効活用するかとか、cache戦略をどうするかとか、複数Webアプリケーションをリバースプロキシで束ねて同じサービスのように見せる、と言ったようなはものは、ユーザーから見える「サービス/Webアプリケーション」そのものだし、そこがWebエンジニアではなく、SREチームだけが主担当というのはちょっと違うのかなあという課題感がありました。

ボーダーレスを目指しているQuipperとしてはWebエンジニアにも気軽にそのあたりを触ってほしいというのもあります。

また、現在のQuipperのSREチームは少人数でかつ守備範囲がとても広い感じになっています。例えば、新しいサービスのテスト環境/ベータ環境の構築だったり、テスト環境やプロダクトション環境と言った各種環境でのリバースプロキシの設定や、セキュリティ(監査)周りのことまでもカバーしています。また当然のことながら、各サービスのアラートの対応や各種メトリクスのモニタリング、それを元にした障害の予防等もしています。これを日本国内、海外と合わせて数人の1チームで行っています。SREチームの拡大も視野にいれつつ、同時にWebエンジニアとタスクの分担も見直したいなと考えています。

そんなことから、SREチームの負荷を減らしたいという組織的な理由もあります。ただ、それ以上に、自分自身が長年Webエンジニアをしてきた経験から、この辺はWebエンジニアが主体的にできることが多い方が、スピード感やオーナーシップを持ってサービスを運営していけると思っているという面も強いです。

ただ、一方で、今までのシステム構成そのままでWebエンジニアに「そっちもやってください」とお願いするのは無理があるなあとも感じていました(現実的に今までもSRE周りのレポジトリはもちろん社内で公開されているしpull-requestも推奨したこともあったがあまり発生しなかった)。これはよく理解でき、Webエンジニアとして勉強することがたくさんある中で、それにプラスして、SREチームが導入しているツールを使いこなすのは現実的ではないです。

例えば、AnsibleとかTerraformといった構成管理ツールをWebエンジニアが理解し主体的に運用していくというのはなかなか難しいと感じています。ツール自体を理解することもそうだし、それらのツールが社内でどう使われているのか、テンプレートがどういう構成になっていてそれがどういうルールでデプロイされているのかと言った社内の決めごとを理解する必要もあり、なかなか難しいと思っています(Kubernetesを使った上で、その上にHelmのような高機能なテンプレートエンジン付きなパッケージ管理ソフトウェアを入れることもできるのですが、なるべく素のKubernetesで複雑さを上げずにいきたいなと思っています。)

僕自身、SREチームができサーバー運用から少し離れた後は、ちょっとした変更がどの範囲の影響になるのかを理解しきれず触るのが怖いと感じてしまい、それだったらSREチームに頼んでしまえとなっていて、ふがいなさを感じることがありました。その辺の心理的なハードルもKubernetesとその周辺ツールと仕組みで下げていきたいと思っています。

明確な線引はしていないですし、相互にはみ出るのは大歓迎なのですが、イメージとしてはSREとWebエンジニアの境界はこんな感じにしていきたいと思っています。

f:id:masatomon:20180822132139p:plain

なぜKubernetesだとそれが実現できるのか - テンプレートエンジンいらず

一つの設定ファイルを元に、テンプレートエンジンによっていろいろな環境にフィットする設定ファイルを生成するAnsibleのような構成管理ツールは使い方によってはとても便利です。

ただ、ある設定ファイルを読むときに{{ domain }} とか {{ app_elb }} とか {{ postgresql_host }} とが、現れると、これは実際本番(orテスト/ステージング等)環境ではどう展開されるんだ? というのが直感的にすぐわかりません。これ自体を展開するソフトウェアのことを理解する必要があるし、そもそもどのフェーズで展開されるのか、またこの変数は何が入るんだろう、とか、入れたい値自体は別ファイルで管理するの? それはどこ? 影響範囲は? 新しい環境を作りたいんだけど、どうすればいいの? とさまざまなことを知らないといけません。

Kubernetesでは関連する複数のアプリケーションを namespace に入れることで、アプリケーション群として管理します。その namespace の中では他の環境のことは意識する必要がありません。

例えば、Quipperでは、生徒向けサービスや先生向けサービス、課金サービス、またそれらを管理するバックオフィスサービスと言ったものが存在し、相互に依存し呼び合っています。

これらのサービスのhost名が環境(本番やステージング等)によらずに常に一つに決まるということです。これにより環境により動的にコードや設定を変更する必要がなくなります。

具体的には、生徒向けサービスは learn 、先生向けサービスは link という名前にすることで(Kurbenretes的にはそれぞれを Service にしています)、一つの namespace の中では http://link みたいなものは常に同じ namespace 内の先生向けサービスを指すことになります。 http://link.{{ domain }} のようにしなくて済むということです(Kubernetes内のDNSがそういう名前解決をしてくれる)。

例えば、これを使い、リバースプロキシ内でURLのpathによって後ろ側のサーバーを切り替える、というようなことを以下のように書き換えられます(今までこういうものもSREチームがWebエンジニアの依頼で設定していた)。

元々の設定ファイルです(実際のものより簡略化しています)。

location ~ ^/vp/(?<new_path>.*)$ {
  set $backend '{{ backend['learn-video-api'] }}';
  proxy_pass https://$backend/$new_path$is_args$args;
}

これが素直に、

location ~ ^/vp/(?<new_path>.*)$ {
  proxy_pass http://video-payment/$new_path$is_args$args;
}

という感じで書けます。

またこれらの設定ファイル自体を同じレポジトリに入れることや(後述します)、このNginx自体を他のWebアプリケーションと同じように1サービスとして管理することで、リリースタイミングやリリースの仕方もWebアプリケーションと揃えられます。これによってWebエンジニアが新しいリリースの仕組みを覚えたり、WebエンジニアとSREチームが息を合わせてリリースをする、といった必要もなくなります。

また、Webアプリケーション内でも同じです。例えば、あるWebアプリケーションから別のWebアプリケーションのREST API等を呼ぶというケースは多いと思います。

よくあるコードとして、

url = "#{ENV["VIDEO_PAYMENT_END_POINT"]}/api/orders?user_id=#{current_user.id}"

response = HTTParty.get(url)
if response.code == 200
  render json: { orders: response.data }
else
  render json: { error: response.code }
end

これが、ベタにこう書けます。

url = "https://video-payment/api/orders?user_id=#{current_user.id}"

response = HTTParty.get(url)
if response.code == 200
  render json: { orders: response.data }
else
  render json: { error: response.code }
end

こんな感じで設定を一つなくせます。この例では一つの変数でしたが、これが、たくさんのアプリケーションの中それぞれに複数入っていたとしたらそれを環境ごとに正しくセットするというのはなかなか手間がかかります。

また、設定が存在しないことで、設定を間違えたことによって想定と違うサーバーにつながってた、みたいなことも同時に防げます(もちろん、プロダクションとステージングはネットワーク的につながらないとは思うが、ステージング環境内とかでネットワーク的に制限していないこともあるだろう)。

また、テスト環境では動いたのに、プロダクション環境では設定が漏れていてつながらない、みたいのも仕組み的に起こらないことになるので安心です。そもそも、〜環境向け、に切り替えるコードを書かずに済むのでそこに頭を使う必要がなくなるのも大きなメリットになります。

こんな感じで、Webエンジニアが、環境によってどういう構成になっているのか意識せずに、 Kubernertesのクラスター(内の namespace)に閉じたことだけを考え実装すると、それがローカルからプロダクションまですべて有効になるということが狙いです。

なぜKubernetesだとそれが解決できるのか - リソースの抽象化によって

サービスを運営していくにあたり、各サービスがどのくらいのリソースを使っているのかというのを考えるのは、パフォーマンスやスケーラビリティに直結する大事なところです。今までの環境では、開発者はそれほど意識せずにやってきました。そこをチューニングするのはSREチームの担当となっていました。

Kubernetesでは、その辺が高く抽象化されていてWebエンジニアが扱うのが現実的だと感じています。どのくらいCPUを使いたいのか(また限界値)、どのくらいMemoryを使いたいのか(また限界値)を宣言的に書くだけで、後はKubernetesがいい感じにクラスター内に配置してくれます。

具体的にはKubernetesの設定に以下のように書くだけです。これならWebエンジニアにも直感的にできるだろうと思っています。(もちろん実際どの値にすべきみたいなノウハウはドキュメント化したり、SREチームの詳しい人がスーパーバイズしたりします)

resources:
  limits:
    cpu: 200m
    memory: 1000Mi
  requests:
    cpu: 100m
    memory: 500Mi

この設定を元にKubernetesが自動的に配置しますが、その各アプリケーションの要求に対してどうコンピュータリソースをスケールアウト/スケールアップしていくのかというところからはSREチームの責任にしていきたいと考えています。Kurbenetes用語でいうと、Node以下の管理はSREチームの主担当、その上はWebエンジニアが主担当というイメージです。

この辺のリソース管理の一部もSREチームからWebエンジニアに渡していけたらと思っています。

ローカル開発環境の改善

上述した環境は、すべてローカルのMacでも動くようにしてあります。 Docker for Mac (with Kubernetes)で同じように動きます。

しかし、一方で、現在Quipperでは、 docker-composeを使ったローカル開発環境を提供しています(by @mtsmfm)。 これは開発者の開発体験を大きく改善しました。これ自体はとても素晴らしいのですが、docker-composeと実際にユーザーが使うAWS上の構成は別ものなので、2つのメンテナンスが必要になってしまいます。

ただ、ローカル環境でのコーディング体験向上という面ではdocker-composeに一日の長があるので、docker-composeとKubernetesのローカル環境を両立したまま発展させていくのか、統合させていくのかは目下検討中です。

ただ、少なくとも、Nginxの設定の確認だったり、複数のアプリケーションがしっかり連携するかみたいなところは、ローカルのKubernetesでしっかり動作確認してからサーバーに上げることで、サーバーに上げてみて確認 -> ローカルで直してサーバーに上げてみて確認 のループは減らせるかなと思っています。

モノレポの導入

また、今回のKubernetes移行にあわせモノレポへの移行もしました。

今までのQuipperでは、一般的な1サービス/アプリケーション毎に1レポジトリ構成を取ってきました。これをすべてのサービスを1つのレポジトリに入れることにしました。これを社内ではモノレポと呼んでいます。

モノレポとKubernetes自体に直接的な関連性はありませんが、上述した namespace ごとに管理できるKubernetesとモノレポの相性が非常に良いです。一つのbranchをそのまま namespace として扱い、topic branchをそのままKubernetesのステージングクラスタにデプロイする仕組みも構築しました(何年たっても同じようなことをしている)。

例えば、サービス毎にレポジトリが別れているときは、生徒向けサービスと先生向けサービス両方にまたがるような機能追加をしたいとき、開発者はそれぞれ別のpull-requestを送る必要がありました。また、それがそれぞれ別々にテスト環境にデプロイされます。 例えばそれらがAPIでコミュニケーションする場合は上で例をあげたような ENV["VIDEO_PAYMENT_END_POINT"] のようなものをセットすることで、その2つを結びつける必要がありました。これはだるいです。

また、それぞれのpull-requestがmergeされるタイミングや、サービスがデプロイされるタイミングも開発者が調整する必要がありました。モノレポ上では以上の修正が、一つのpull-requestでまとめられ、それがmergeされると専用のnamespaceが作成され、その上でそのまま2つのサービスをテストできます。

また、SPA的なフロントエンドアプリとAPIサーバーも同じレポジトリにあるので、その辺も同じような感じで同時に開発し、同時にデプロイが自然にできる仕組みにもなりました。

f:id:masatomon:20180822134325p:plain

↑はモノレポで動いているCI。

f:id:masatomon:20180823084317p:plain

↑各topic branchがどうdeployされ、ネットワーク的にルーティングされるか (社内ドキュメントから抜粋)

標準化とドキュメント

Quipperでは、今までサービスを作る時の手順みたいのが確立されていませんでした。その辺も今回の更新と合わせて標準化していっています。例えば、ローカル開発環境の作り方だったり、モノレポ上にどういうディレクトリ構成でサービスをおいていく、とか、DockerfileやKurbenetesの設定ファイルはどこへ置くか、みたいなところです。

また、それらがどうデプロイされるのかもある程度レールを引くことによって標準化しています。そのため、新しいアプリケーションが増えた場合また、上述したようなリバースプロキシーのNginx等も、同一の開発、デプロイフローになっています。

その辺を共通化することで Convention over configuration にもつながり、いろいろなところをみんなが「設定」することでつなげるのではなくて、ルールを守ることでどの環境でも自然につながる方向性を目指しています。設定することが増える = 覚えることが増える = ミスが増える (そのためテストも増える)。

またその辺も今回Kubernetes化とその周辺の改善において、ドキュメント化は徹底的に行いました。

f:id:masatomon:20180816084112p:plain

これは外部公開してもいいレベルと内容で書いているので近い内に公開したいと考えています。

そして、その先にあるMicroservices移行

Microservices移行自体についてここでたくさんは書きませんが、Quipperでは目指していきたいとは思っています。VP of Engineeringに就任してもらった刺身さん曰くの 分断されたモノリス 状況であるQuipperのサービス群はなかなか開発が厳しくなって来ています。典型的な、少しずつ育ってきた複数のWebサービスがあります。その複数のWebサービスは、一つの巨大なデータベースを共用していてそこがグローバル変数的に扱われ、各サービスがデータベース周りで結合されている感じになっています。

また、そのデータベースにアクセスするためには社内のRuby gemが必須なので、必然的にそのデータにアクセスする必要のあるアプリケーションではRuby縛りということになってしまいます。Ruby自体は個人的にも大好きですが、今後の多様性、拡張を考えたときにその制約は外していきたいです。

ただ、Microservices化自体が簡単なものではないし、拙速にしたくないとも思っています。今回Kurbernetes化のプロジェクトで、極一部をMicroservices化してみたのですが、なかなか一筋縄ではないかなあという感じを受けています。

ただ、やはり着実にその方向へ挑戦していきたいとは考えています。この辺は進捗ありましたらまたブログ記事にしていきたいと思っています。

その上で、今回Kuberbetesを使って改善したことは、Microservicesに特有なことではないけど、Microservices化していくにあたり最低限必要なことだとも思っています。Microservicesの世界では、よりサービスの数が増え、開発、管理がある面では複雑になるからです。また、各種サービス自体は共通した仕組みでつながっているからこそ、各Mircoserviceの自由度を上げられ、それが全体として協調して動くものを作れます。

今回のKubernetes化はそういう未来につながる第一歩だと考えています。

課題や今後やりたいこと

  • 開発環境をどうしていくか。今のところDocker for Macでなんとなくできているが開発者の声を聞いて改善していきたい。
  • end-to-endテストの標準化。今のところ簡単なスモークテストしかない。Quipper史上何度か失敗しているend-to-endテストをKubernetes上に構築したい。end-to-end用のnamespaceを作成し、その上でReverse proxy含めてテストできそうな感じはしているがシードデータどうするか含めて色々考えどころ。
  • サービスメッシュ導入。まず薄くKubernetesを導入したいと考えているのでまだ導入していない。Istioは動作確認とテストまではしているが他にもいくつかあるので解決したい課題とセットで考えていきたい。
  • distributed tracing。現状、 Jaeger を入れてあるが、サービスメッシュとセットで再考したいと思っている。
  • service readinessリストの更新。サービスの投入前のチェックリストとしてservice readinessを用意しているがまだ未完。特にMicroservices化にあたり必須だと考えているので今後拡充させていく。 (リストは、 プロダクションレディマイクロサービス ――運用に強い本番対応システムの実装と標準化 を参考にしている)
  • モニタリングや障害予防的なことは未だにSREメンバーがメインなので、もう少しWebエンジニアを巻き込む。
  • canary release及びそれに適したツールの導入(Spinnaker?)
  • audit logを取りつつ開発者にrails console等の実行を許可する仕組み

他にもたくさん。

まとめ

自分の立場ごとに今回の改善を振り返ってみると、

(元?) Webエンジニア としてはSREチームに任せきりなっていた部分を少し取り返し、自分だけでできることを増やしたい。

SREエンジニアリングマネージャー としてはWebエンジニアに自分でできることは自分でしてもらい、SREチームの現在の負荷を下げ、SREチームでは日々の開発/運用周りの他にSREチームでしかできない技術的難易度の高いものに挑戦していきたい。

CTO としてはコミュニケーションや運用周りの時間的なコストを下げ、開発者がユーザーやデータに向き合える時間が増え、より多くの価値をユーザーに提供できるように。また、開発者が快適に開発できる環境を提供しいわゆるDX(開発者体験)の向上。また過去Quipperの歴史の中で、各サービスがばらばらだった部分の標準化や、それらのドキュメント化をしっかり行い開発者が増えていく中でスピード感を落とさず進めていきたい。そして、今後のMicroservices化向けた基礎固めといったところがあります。

謝辞

モノレポやDocker周りでは @mtsmfm の影響を強く受けています。また、DX改善という面ではDocker composeの導入とか熱心にしてくれています。いつもありがとうございます! Kubernetesの導入やリソース周りのことについては、SREチーム (@lamanotrama / @rbmrclo / @yuya-takeyama / @chaspy ) の力なくして無理でした! また、Mircoservices化の調査、導入、ドキュメント周りではフィリピンから @kjcpaas が大活躍。ブログ公開直前にこのブログ用にかわいい絵(一番上のボートのやつ)をさくっと書いてくれた @takeshiugajin 。 みなさんありがとうございました!

最後に

こんなQuipperでは、 SRE、Webエンジニアなどいろいろなポジションで募集しています。 まだまだ上に書いたような課題や他にもやりたいこともたくさんあるのでなんとかしてやる!という方いましたら是非一度お話からでもさせてください。よろしくおねがいします。 また、この環境の上で走るアプリケーションを書いたり、サービスを企画したり、デザインしたりと様々なポジションでも募集しています。 待ってます!