サーバーサイドKotlinとMicroservicesの今

野澤聡史氏(以下、野澤):それでは、「FRESH LIVEにおけるServer Side KotlinとMicroservicesの今」というタイトルで始めていきます。渋谷からやってきました、サイバーエージェントの野澤です。

(会場拍手)

ありがとうございます。まず自己紹介なんですけど、サイバーエージェントに入社した当初は、サーバサイドエンジニアをやっていたんですが、この半期はAndroidエンジニアをやっていまして、私はAbemaTVではなくて、2016年11月からFRESH LIVEに所属しています。Kotlin歴もだいたい2年ぐらいです。Kotlinをやることによって、今はAndroidエンジニアとしてFRESH LIVEの開発に貢献しています。

私が所属しているFRESH LIVEのサービスの話をすると、FRESH LIVEはPC・スマートフォンのデバイスで低遅延・高画質で動画視聴が楽しめる、生放送の動画プラットフォームになっています。

FRESH LIVEを視聴できるアプリとして、「FRESH CAST」というのがありまして、最近Android版のアプリも出しました。もちろんPCから配信・視聴ができますし、アプリ側、iOSとAndroidで配信・視聴ができるようになって、全デバイスでの視聴・配信が揃っています。

(スライドの)下に書いてあるとおり、サーバサイドのキーワードとして、Microservices Architectureだったり、Full DockernizedやAWS、Kubernetesなどが挙げられます。

余談なんですが、FRESH LIVEで配信している『もずはゃちゃんねるFRESH!』というチャンネルがあって、今日僕が着ているTシャツが、彼らのユニットのTシャツです。

僕はこのチャンネルのファンで、今日はカメラも入ったりすると思ったので、このTシャツが映って、あとで彼らが喜んでくれると嬉しいなと思っています。

ゲーム実況などをしていて、とても話がおもしろい2人組です。Vtuberも初めの頃からやっています。このカエルのキャラクターがVtuberです。以上、余談でした。

今日のアジェンダなんですが、大きく分けて5つぐらいあります。

FRESH LIVEとサーバーサイドKotlinについて。FRESH LIVEのマイクロサービスの事例。Spring Boot 2.0とKotlinの話。コンテナとマイクロサービス。そして、gRPCとマイクロサービスの話をしていきたいと思っています。

みなさんに聞きたいんですが、サーバーサイドKotlinを自分たちのサービスに導入されている方は、ぜひ挙手をお願いします。

(会場挙手)

とても少ないかもしれないですね。今日はFRESH LIVEにおけるサーバーサイドKotlinを中心とした、周辺技術のコンテナとgRPCに触れて、マイクロサービスの開発の知見を共有したいと思います。今日サーバーサイドKotlinの話を聞いて、「ぜひ導入していきたい」というモチベーションを持って帰っていただけると嬉しいです。

さまざまなマイクロサービスにKotlinを採用

では、FRESH LIVEとサーバサイドとKotlinの話です。今までさまざまなマイクロサービスにKotlinを導入してきました。例を挙げると、決済集計だったり、アナリティクス、プッシュ通知、ログ収集、APIゲートウェイなどがありまして、最近では投げ銭機能みたいな感じで、都度課金ができるようになっていて、その課金基盤のマイクロサービスもサーバーサイドKotlinでできます。

なので、「サーバーサイドKotlinって大丈夫なの?」という問いに対しても、さまざまなシステム要件にも柔軟に対応できています。

マイクロサービスの利点について話していきたいと思います。利点の1つとして、「単一障害点で留まって全体は倒れない」ということが挙げられます。マイクロサービスは認証やログ、課金などといった単位で動いていて、「課金ができなくても視聴はできる」とか、「コメントはできなくても視聴はできる」というふうに、サービスが分かれることで単一障害点で留まり、全体が倒れません。

次に、スケール戦略が立てやすいという利点があります。サービス戦略とともに、「今日はなんか視聴が増えそうだね」となったら、「じゃあ視聴のサービス増やそうか」とか、「スケールさせよう」みたいなことができます。「コメントが増えそうだね」となれば、「コメントのサービスを増やそう」とか、サービスの戦略とともにスケールさせるマイクロサービスが明確になります。

そして3つ目の利点は、技術的チャレンジの敷居を下げられるところです。我々はマイクロサービスをどんどん作っていくんですが、Kotlinに限らず、Goだったり、いろいろなチャレンジしたい言語でマイクロサービスを作ってみようというスローガンで、マイクロサービスを作ります。あと、最初はKotlinで作ったけど、まだまだ小さいサービスだから後から作り直そうという軽い気持ちで、マイクロサービスを作れるので、エンジニアのモチベーションにもつながっていると思います。

新規のAPIは基本的にKotlinで

次に、マイクロサービスのアーキテクチャの話をします。「1つのマイクロサービスには1つのレポジトリ、1つのリソースのデータベース」がコンセプトになっています。1つのマイクロサービスは、Dockerfileを持ってコンテナ化しています。

Dockernizedされたマイクロサービスは、EC2のインスタンス上で動きながら、Kubernetesでオーケストレーションをしています。こういったアーキテクチャになっています。

マイクロサービス間の通信にはgRPCを採用していて、一部ではアプリにgRPCのパブリックな受け口も開いているアーキテクチャになっています。

現在のFRESH LIVEの話をしていきたいんですが、サーバーサイドKotlinだけでなく、Golangのマイクロサービスもあったりして、GolangとKotlinのハイブリッド構成になっています。配信システムとか低レイヤー系のところでGolangを採用したりしていて、新規のAPIは基本Kotlinで作っています。先ほど行木さんからもあったように、APIをGolangをいっぱい書くのがとてもつらいです。

僕らはけっこう「Go筋肉痛」と言っているんですけど、APIに関してはなるべくコードを簡略化して書きたいので、このあとサーバーサイドKotlinのコードを見せるんですけど、Java資産を活用すれば早く作れるところもあるので、API層で言うと、Kotlinで作っていこうみたいなスローガンです。マイクロサービスの開発の利点を活かして、言語に縛られず、フラットにやっています。

Kotlinの好きなところはどこかと言うと、みなさんご存知かと思うんですが、Null safetyであったり、高階関数が用意されていたりとか。

僕らはけっこうKotlinを使っていると、「関数を生やす」みたいなキーワードで話しますが、拡張関数があったりとか、Data classやスコープ関数、Reified type paramtersとかがあるところです。私がAndroidエンジニアをできているように、サーバーサイドKotlinに触れることで、Androidエンジニアととても仲よくなれましたし、こういったことを振り返っていくと、Kotlinを愛でたくなるところが本当にたくさんあります。

Spring Boot 2.0を導入

もう1つ質問なんですけど、Spring Boot 2.0を導入されている方は挙手をお願いします。

(会場挙手)

先ほどのサーバーサイドKotlinと同じ人たちが手を挙げてますね。私たちが主に採用しているフレームワークが、Spring Boot 2.0になります。今2.1が、たぶんRCのバージョンとか出ているんですけど、本番投入しているのは2.0.xのリリースです。

Spring Boot 2.0からKotlinが正式にサポートされていて、(スライドの)右にあるURLを開いていただくとわかるんですが、このページの中に「Idiomatic Kotlin Code」という特徴的なキーワードがあって、これは「Kotlinらしいコードを書くことを心がけよう」みたいな内容が入っています。

そこを実際に見ていきたいんですが、Idiomatic Kotlin Codeってなんなのというところで、JavaとKotlinのコードを比較しています。

(スライドの)上に書いてあるのがJavaのコードです。contextという変数に対して、2行目でregisterBeanを呼び出していますが、これをKotlinで書き換えると下のコードになります。

contextという変数に対して、GenericApplicationContextを有した状態で、applyがスコープ関数になっていて、左にあるものをイニシャライズするかたちで、ブレースの中に入っていきます。そうすると、registerBeanという関数が叩けるようになるので、この2つが実行されているコードになります。

この中に拡張関数が3つ入っているんですけど、2行目と3行目のregisterBeanと、3行目にあるgetBeanが拡張関数になっています。

ちょっと見にくいんですがこれが拡張関数になっていて、こういったSpring FrameworkにKotlinのサポートが入っていまして、Reified Type Parameterでコード中の型にアクセスできるようになっています。

次のスライドで見ると、Foo.classというデータがありますが、これが省略されて、registerBeanの型パラメータで渡すことで、getBeanの2行目にある、T::class.javaというふうにアクセスできるかたちになっています。このように、コードをより簡略にすることが、Kotlinらしいコードだというイメージを持っていただければと思います。

ここでSpring Bootの話に戻りますが、今お話したIdiomatic Kotlin Codeに則っているような、象徴的な機能なんですけど、Router Function DSLという機能があります。

これがSpring Framework5のAPI定義である、Router Function DSLで、APIのルーティングをDSLで定義できるようになっています。これはSpring Framework5からの新機能で、APIルーティングのDSLです。

Spring Frameworkを使ったことがある方は知っていると思うんですが、アノテーションでautowiredとかして、classのインスタンスのprivateのところにDIしていくんですけれども、2.0からはConstructor Injectionで、2行目と3行目のprivateのところでautowiredを省略してインジェクトをしています。

アノテーションをまったく使わないかたちで、シンプルにAPIが書けるようになっています。

次がAPIを先に実行しているHandlerの話なんですけれども、Task APIのTaskHandlerクラスです。

ここは、2つの引数を持って、返す関数が並んでいます。この関数型によって、前の9行目と10行目で、クラスの参照渡しができるようになっています。

Spring Web Flux

もう1つ、特徴的な機能を話していきます。SpringのWeb Fluxというものがありまして、5.0から追加されたコンポーネントになっています。Spring-webmvcと隣に並ぶかたちで、Spring-webfluxというコンポーネントができあがりました。

これがReactive Programmingをサポートしまして、サーバPush型のAPIが作れます。このAPIはServer-Sent Eventsで提供されます。ノンブロッキングなランタイム上にReactive Programmingのコードが実行できるようになりました。

これを使っていくと、こういったAPIができるようになるんですが、StreamのAPIを実装できる簡便な仕組みが、Spring Web Fluxによって提供されました。

7行目にあるように、text event streamというコンテンツタイプをサポートしていたり、17行目に線が引っぱってあるとおり、bodyToServerSentEventsという拡張関数を呼ぶことで、簡単にServer-Sent EventsのAPIを提供することができます。

ここまでがサーバーサイドKotlinの話です。

このスライドは言語の活用事例を中心としたスライドになっているので、お時間あるときに見てみてください。

Spring Boot 2.0とKotlinについてのまとめです。

KotlinサポートでKotlinらしいコードを書けるようになって、Router Function DSLでAPI開発に変化が起こりました。Spring Web Fluxによって、リアルタイムなコンテンツが提供可能になりました。

コンテナとマイクロサービスについて

では、次にいきます。コンテナとマイクロサービスについてです。我々のマイクロサービスのルールは、Dockerfileを持ってコンテナ化することです。

ここから3つのキーワードで話していきます。環境変数をコンテナにDIすることと、Kubernetesのリソースを2つ紹介したいと思います。Secretリソースと、DaemonSetによるサービスログ収集の変化を話していきます。

1つ目は、「環境変数をコンテナにDI」ということなんですけど、コンテナの中に動いているイメージに対して、ステージング環境だったり、プロト環境のDBの設定の値とかを渡して動かしたい場合に、DBの設定など異なる値は、コンテナの環境変数にセットすることを、コンセプトとして採用しています。

これは、環境変数として、簡単にSpringのエコシステムを使うことができるんですが、SpringのConfigurationPropertiesでapplication.propertiesをマッピングしています。3行目でAUTH_ENVが環境変数になっていて、これがapplication.propertiesのauth.envに渡ると、下のコードにいって、3行目でauthのプレフィックスがマッピングされていて、次の5行目でenvになるので、AUTH_ENVという環境変数が簡単にSpringの世界の中に持ってこれるようになっています。

こういった各環境の環境変数は、このアプリケーションのレポジトリで管理するのではなくて、集約したレポジトリで管理しています。Springには、こういったコンテナと親和性を持つ仕組みがあります。

Secretリソースとサービスログの収集

次はKubernetesのSecretリソースです。

KubernetesのSecretリソースはなにかと言うと、環境ごとに異なる値、主に機密情報を扱うリソースになっています。

このコードはSecretリソースのマニフェストになっていて、mysecretという名前で、usernameとpasswordが変数化された状態になっています。このリソースは、Namespaceごとにリソース化していきます。

このリソースを環境変数で使う仕組みも用意されていまして、コンテナの環境変数にセットするためには、このマニフェストファイルはpodのマニフェストファイルなんですけれども、12行目のsecretKeyRefを使うことで、簡単にこのコンテナに対して、環境変数がマッピングされます。このSecretリソースを使えば同じNamespaceに配置されたpodで使うことができるので、マイクロサービスが横断的に共通のリソースを参照できる仕組みを構築できます。

次は、サービスログ収集の変化です。これまで我々は、Kubernetesを使わずにECSでコンテナのオーケストレーションをしていたんですが、これまでのECSではマイクロサービス単位でクラスタを組んでいまして、マイクロサービスのコンテナとセット、サイドカーと言うんですけれども、サイドカーコンテナとしてfluentdのコンテナを配置していました。

これが、KubernetesのDaemonSetを使うことで、podの集合体のNodeに配置することで、Node内のコンテナのログを収集することができます。これができることによって、今までマイクロサービスの隣にいたようなサイドカーコンテナのfluentdを撤廃することができました。

あと、コンテナのログを収集することになるので、アプリのログは出力先を標準出力のみにしています。なので、logbackの設定が本当に簡素になって、サービスログの収集もKubernetesを導入することで変化が起きました。

さっき行木さんからも、ログの話で感動したというのがありましたが、私もKubernetesで、このsternというデバッギングツールを使って本当に感動しました。

これはpodのログをtailしてくれるデバッギングツールで、clusterやnamespace、containerとか、selectorでフィルタリングできて、podのログをtailすることができます。

このsternはログをJSONとして出力して、パイプで挟んでjqとしてJSONをフィルタリングして、ログ監視したりできます。認証が通るとローカルからも利用が可能で、わざわざpodの中に入ってログを見るのではなくて、こういったマシンの中でsternというコマンドを打つと、簡単にpodのログが収集できるようになっています。

なので、ぜひKubernetesを導入しようと考えている方は、このsternを使うと「これは絶対使ってみたいな」と思うので、開発のおともになればいいなと思います。

図解でみるコンテナとマイクロサービス

では、コンテナとマイクロサービスの図解です。今まで3つのキーワードで話してきましたが、それらを振り返っていきたいと思います。

1つ目は、コンテナの環境情報を環境変数でDIすることにしています。このpodも環境変数でDIしています。共通化した変数は、KubernetesのSecretリソースを使っています。(podの)上にSecretリソースが配置されているイメージで、pod Aとpod B、pod CがAPI.AUTHというSecretリソースを参照しています。この3つのpodがNode上で動いていて、コンテナはKubernetesのDaemonSetを使うことでNodeに配置して、fluentdで収集しているという話でした。

ここまでの話で、コンテナとかKubernetesに関して気になるところがあれば、ぜひ弊社、FRESH LIVEのTech Leadの山田がこういった本(Docker/Kubernetes 実践コンテナ開発入門)を書いていて、すばらしい内容になっているので、続きはテキストで読んでいただけると山田も喜ぶと思います。ぜひよろしくお願いします。

Docker/Kubernetes 実践コンテナ開発入門

コンテナとマイクロサービスのまとめです。

(マイクロサービスは)コンテナとの親和性が本当に大事です。あと、環境変数のマッピングをすることで、環境ごとに異なるコンテナを動かすことができます。Kubernetesのリソースにより、コンテナ構成にも変化が起こりました。SecretリソースとDaemonSetです。あとはsternが本当に便利なので、ぜひ使ってみてください。

gRPCとストリーミング

次に、gRPCの話をします。gRPCはGoogleが開発したRPCフレームワークで、HTTP/2との効率的な接続ができます。双方向ストリーミングの通信方式で、Protobufによってデータシリアライズされます。

1番の特徴としては、豊富な言語サポートがされていて(スライドを指して)こういった言語がサポートされています。

そして、マイクロサービス開発と合致した接続やデータ転送ができます。我々はGolangとKotlinも採用していますが、このgRPCを使うことで、1つのprotoファイルを共有することができて、簡単にgRPC通信の開発がおこなえます。

次はストリーミングについてです。gRPCには、4つの通信方式があって、1つ目が単一通信で、簡単にクライアントがリクエストを投げるとサーバがレスポンスを返すという、HTTP/1.1の通信方式です。

2つ目のServer streaming RPCは、Server Push型になっていて、クライアントが1回リクエストをすると、Serverが複数のレスポンスを返します。3つ目のClient Push型は、その逆になっています。4つ目の双方向通信は、チャットのように、双方向にクライアントとサーバがリクエストを送りあうかたちになっています。

我々がマイクロサービスで導入している通信方式で1番多いのは、Server Push型です。(スライドの)右の図にあるように、クライアントが1回リクエストを送ると、サーバは非同期でレスポンスを何回も返すかたちになります。

これ(双方向通信)は、どういったところで利用しているかと言うと、視聴数やコメント数、配信状況などのリソースを、画面を開きっぱなしの状態であっても、アプリとかWebでもそうですが、ポーリングをすることなくコンテンツをストリーミングに流すことが、gRPCで可能になっています。

マイクロサービスにおけるgRPCの活用

マイクロサービスとgRPCの話です。1つのマイクロサービスの中で2つ、PublicとInternal向けのgRPCを提供しているんですが、どうやって提供しているかと言うと、Portを分けて2つのServerを起動していて、PublicとInternalの向けのServerを分けています。

これをどう実現するのかに関しては、Spring frameworkのeco systemのCommandLineRunnerを使ったり、DisposableBeanを使うことで、gRPC Serverを簡単に構築することができます。

これがgRPC Server Runnerのコード例です。

8行目でServerのPortを定義しています。12行目は認証のInterceptorを挟んで、AOPができています。CommandLineRunnerとDisposableBeanを実装することでgRPC Serverを構築しています。

次は、Spring frameworkとgRPCについてで、Spring frameworkのeco systemとgrpc-javaの共存はできています。

Server起動とかInterceptorなども、難なく構築が可能です。

gRPCをこれから導入される方がいれば、アドバイスをさせていただきたいんですが、入念なクライアントの疎通確認は、とくにAndroidの場合、した方がいいと思っています。Androidは、OSバージョンや端末によって挙動が変わるので、どこまでのバージョンをサポートするのかの範囲を見極めながら、疎通確認を必ずした方がいいと思います。

あとは、grpc-javaとか、Spring Bootとか、さまざまなライブラリが絡みあって、1つのアプリケーションが動いているんですが、gRPCはNettyというServerのランタイムを使っています。それが1つのライブラリのバージョンに引っ張られて、エラーが起きちゃうことがあるので、定期的に周辺ライブラリのアップデートは必要になります。なので、ぜひアップデートを心がけてください。

では、gRPCとマイクロサービスについてのまとめに入ります。

効率的な通信ができます。gRPCを導入すると、効率的な通信ができますし、マイクロサービスに合致した開発もおこなえます。Spring frameworkでgRPC Serverの構築ができるので、Portの活用をすることで、1つのjarにPublicとInternalのServerが起動できます。

マイクロサービス開発がもたらしたもの

ここまでで、全体のまとめに入っていきます。

我々はマイクロサービス開発をすることで、守りも攻めも果敢にやっています。

サーバーサイドKotlinをこれからやりたいと思う方は、Spring Boot 2.0をおすすめするので、ぜひ導入してみてください。Kotlinサポートだったり、充実したSpring frameworkのコンポーネントがありますし、コンテナとの親和性もあるという話もしました。

そして、Kubernetesの導入によって、コンテナの配置戦略にも変化が起こるという話もしました。そして、我々の動画配信サービスで言うと、リアルタイムなコンテンツ提供が必要になるんですが、そういったところでも最適な通信方式を選択していて、Server-Sent Eventsだったり、http/2です。これに関しては、Spring Web Flux、gRPCを採用しています。

これらも、すべてSpring Boot 2.0上で動かすことができるので、サーバーサイドKotlinの開発をする時は、まずSpring Boot 2.0で始めてみていただければと思います。

小さなマイクロサービスからKotlinでサーバーサイド開発を、ぜひしてみてください。以上です、ご清聴ありがとうございました。