邵 正 / 2015年4月、エンジニアとして新卒入社。2019年11月、第9回 Google Cloud INSIDE Games & Apps 登壇。リーダー職も務める。
加藤 悠 / 2018年5月、インフラエンジニアとして中途入社。2018年12月、Developers Boost~U30エンジニアの登竜門 登壇。
長澤 翼 / 2020年3月、インフラエンジニアとして中途入社。2020年9月、CloudNative Days Tokyo 2020 登壇。
はじめに
私達コロプラとKubernetesの出会いは2018年の夏。
Google Container EngineがGoogle Kubernetes Engine(以下GKE)と呼ばれるようになってから半年ほどたった頃、私達はGKEを利用する形でKubernetesと付き合い始めました。
そして2018年8月、コロプラ初の女性向けゲーム『DREAM!ing』をリリースします。これが私達が初めてKubernetesを利用してリリースしたゲームタイトルです。
それから超絶ぶっ飛ばしバトル『バクレツモンスター』を同年10月に、本格派RPG『最果てのバベル』を翌2019年6月にリリースし、本格的にKubernetesを利用したインフラ運用が開始されました。
このときの構成は次の通りです。
これまでのGoogle Compute Engineでの運用をGKEに置き換えたことで得られた自動修復やオートスケールの機能は、その導入コストを十分に上回り、インフラ運用をより楽にしてくれました。
それでも、株式会社スクウェア・エニックスから配信され、コロプラが開発を担当している『ドラゴンクエストウォーク』をリリースするにはまだ十分ではありませんでした。
『ドラゴンクエストウォーク』は、みなさんご存知の『ドラゴンクエスト』と位置情報を掛け合わせたスマホゲームですが、2019年9月のリリースより、1週間で500万ダウンロード、約2ヶ月で1000万ダウンロードを突破。そして1周年を過ぎた2021年1月現在も多くのユーザーさまに遊んでいただいており、サーバーの負荷についてはリリース前から徹底的に検証し、備える必要がありました。
前述の新作3本で試した構成からの一番大きな変更点は、GKEクラスターを複数用意する、「マルチクラスター構成」をとったことでした。これによってリリース時の負荷を乗り越えられたと言い切っても過言ではないでしょう。
そこで今回のストーリーでは、私達が『ドラゴンクエストウォーク』がリリースされるにあたってなぜこの構成変更を行ったのか、また今後どうしていこうと考えているのか、紹介したいと思います。
マルチクラスターの利用目的
マルチクラスターを採用しているのは、数あるコロプラのゲーム(コロプラが開発を担当するゲームも含む)の中でも特にユーザー数・負荷の大きいタイトルです。次の図は、マルチクラスター構成を採用している『ドラゴンクエストウォーク』のアーキテクチャ図になります。
図を見ると分かるように、API GatewayクラスターやAppクラスター、Backendクラスター、PvP(Raid Boss協力プレイ用)クラスターなど、役割ごとに分けた複数のクラスターでシステムを構成しています。
なぜ我々がこのマルチクラスターを採用したのか、その背景を説明します。
リリース前時点では既存作品と同様にシングルクラスターでの構成を考えていましたが、シングルクラスター構成で大規模ワークロードを構築した時に、次のような問題が起こりました。
1. Pod・Nodeなどのオブジェクトが多すぎてKubernetes Masterに負荷がかかりすぎる
2. Deploymentのデプロイ(Rolling Update)時間が長くかかる
それぞれの問題について簡単に説明します。
1の「オブジェクトが多すぎてKubernetes Masterに負荷がかかりすぎる」ですが、KubernetesにはSLOとして作成できるNode数やPod数などの目標値が決められています。当然それ以上のオブジェクトを作成することは保証されていません。たとえば1クラスターに1,000Node・10,000Podを作成する場合と300Node・3,000Podのクラスターを3つ用意するのとでは、Master Nodeに対する負荷がかなり違ってきます。『ドラゴンクエストウォーク』の規模では、こうしたオブジェクト過多によるMaster Nodeへの過負荷が問題になりました。
2の「Deploymentのデプロイ(Rolling Update)時間が長くかかる」ですが、コロプラではApplicationのデプロイにSpinnakerを利用しています。Spinnaker経由でDeploymentのRolling Updateを行うのですが、数千〜1万ものPodがあるとデプロイするだけで1時間以上かかるようになりました。もしデプロイ中に新しい修正を入れたくなっても1時間も待たなくてはいけないとなると、実運用ではとても耐えられません。これも1の問題同様に10,000Podの1クラスターにデプロイするよりも、3,000Podの3クラスターにデプロイした方が全体のデプロイ時間が短く済みます。
ある程度以上の規模のワークロードをKubernetes上に載せるとなると、システム的な高負荷以外にこうした問題が出てきます。
当初、実績のないマルチクラスター構成を採用することに不安はありましたが、いざマルチクラスターを採用してみると、上記の問題の解消の他にも様々なメリットがありました。
マルチクラスターは管理するクラスターが増える(=運用する工数がかかる)という点やService Discoveryが複雑になるなどのデメリットもありますが、我々の場合は次のメリットがありました。
1. トラフィックをクラスター単位で調整することで、容易にCanary Releaseを実現できる
2. Kubernetes VersionUpの時にクラスター単位で切り替えることで、Blue/Green戦略でアップグレードできる
1の「トラフィックをクラスター単位で調整することで、容易にCanary Release」については次節で説明します。
2の「Blue/Green戦略でアップグレードできる」については、たとえばAppクラスターが3つあるため、1つずつクラスターをService Outさせ通信が届かない状態にした上でKubernetesのバージョンアップグレード作業を行います。こうすることで稼働しているサービスへの影響を最小限に抑えながら、定期的に行う必要があるアップグレード作業を行うことができます。
マルチクラスターはシステム構成を複雑にする一方で、この構成でしか解決できない問題や得られる恩恵も同時にあります。
次の節では、マルチクラスター構成でクラスター間のService Discoveryをどのように技術的に実現しているのかについて説明します。
クラスター間通信の実現方法
マルチクラスター構成を取ると、二つの問題が出てきます。
1. 複数個存在するAppクラスターに対して、均等にリクエストを流す必要がある
2. Appクラスター <-> Backendクラスターでクラスター間通信と名前解決を行う必要がある。
下図のようにクライアントのリクエストを受けてから複数のAppクラスターにトラフィックをいかに分散させるかという問題と、Appクラスター <-> Backendクラスター間の通信をどのように実現するかという問題です。
『ドラゴンクエストウォーク』のリリース当時はマルチクラスターに対応したIngressのプロダクトが充実していませんでした。今回実現したい機能としては、流量制御やリクエストの重み付けがメインだったので、一般に広く知られているプロキシーとしてEnvoyやHAProxyを検討しました。リリース時間とのTradeoffの中で、最終的には社内で採用実績のあるHAProxyを採用しました。HAProxyでトラフィックの流量制御を行うことで、Applicationクラスターに均等にリクエストを流すことができました。
また、HAProxyの重み付けを変えることで、Applicationに新しい変更が入った時には実験用のクラスターに1〜10%だけリクエストを流す、といったことをクラスターレベルで行うようなCanary Releaseを実現することもできました。
以上が、APIGatewayを導入して、トラフィックをAppクラスターまで均等に流す工夫です。
次に、AppとBackend間の通信方法について説明します。GKEではVPC-nativeという機能があります。これを利用すれば、PodはVPC内との通信が可能となり、VPCネットワークレイヤーでマルチクラスター間のPodは互いに疎通できます。実際に通信を実現するためには、どのPodに通信すべきかを識別するService Discoveryも必要で、たとえば次の図のApp PodからBackendクラスターのStatefulsetにアクセスすることケースを考えてみます。
まず、BackendクラスターにあるStatefulSet PodのIPの取得が必要です。通常はK8sのServiceリソース経由で通信は可能ですが、ServiceのDNS Recordも通常クラスター間に共有されていないため、取得ができません。
さらに、Serviceに付けているIPアドレスは、kube-proxy(iptables)内にしか存在しないもので、VPCネットワークからは通信できません。DNS Recordをクラスターをまたいで共有するために、Backendクラスターに新たにCoreDNSを配置し、UDP/TCP :53のInternal LBを作ることで、クラスター間でDNSに問い合わせることが可能となります。
AppのクラスターからはNode Local DNS Cacheを入れることで、Nodeレベルで名前解決をできるようにします。該当Domain、たとえば「name.ns.svc.cluster.local.」を解決できるようにするには、Node Local DNSのUpstreamを先ほど作成したCoreDNSのInternal LBのIPアドレスに設定します。こうすることで、Node Local DNS CacheにCacheがあればそこから名前解決を返し、なければUpstream先のCoreDNSが名前解決するようになります。
もう一つの問題「Service ClusterIPが疎通できないこと」に関しては、K8sのHeadless Serviceを利用することで、名前解決で返ってくるIPがPodのIPになるので疎通が可能になります。
以上が、マルチクラスター構成におけるクラスター間通信の工夫の話でした。
まとめ【マルチクラスター導入のメリット・デメリット&今後】
最後にマルチクラスター導入のメリットとデメリットについてまとめます。
マルチクラスター導入のメリット
● ワークロードを複数のクラスターに分割することでKubernetes Masterコンポーネントに対する負荷を軽減
● アプリケーションのデプロイ時間の短縮
● クラスターの部分的な切り替えが可能なため無停止かつ安全にKubernetesのバージョン更新が可能
● クラウドプロバイダー側の障害による影響範囲の最小化
マルチクラスター導入のデメリット
● マルチクラスターの運用コストの増加
● マルチクラスター間の通信の複雑さ
運用コストの中で大きな割合を占めるのがKubernetesのバージョン更新です。Kubernetesのリリースサイクルは3-5ヶ月と短く、クラスターの更新作業を1年間に3-4回行う必要があります。GKE (Google Kubernetes Engine) のようなマネージドサービスを利用している場合、上記の更新作業は一定期間で必ず発生します。ゲームタイトル毎に複数のクラスターを管理している場合、ゲームのキャンペーンやイベントの期間を考慮して更新のタイミングを細かく調整することが難しくなります。そのため、大きな変更のないKubernetesのバージョン更新にGKEの自動更新の機能が使えるように、状態を持つMySQLやRedisなどのデータストアをクラスター外に移行する作業を進めています。
また、マルチクラスター間の通信についてもILBとCoreDNSを使って独自にサービス検出を実現するのではなく、Multi-Cluster Services APIなどKubernetesのupstreamに今後追加されていく機能を使うようにシフトしていきます。
Kubernetesのコミュニティでもマルチクラスターに関する議論は熱く、Multi-cluster IngressやKubernetes Cluster Federationなどマルチクラスターの構成を楽にするためのコンポーネントや機能の開発が進められています。
以上がコロプラが実践しているマルチクラスター構成についてでした。Kubernetesのマルチクラスターを採用している例はまだ多くないと思いますが、メリットも多くあることが伝わったかと思います。コロプラでは大規模なクラスターを構築・運用しておりますので、興味のある方はぜひ詳細な話を聞きに来てください!(オンラインでもお待ちしております!)
参考
● 株式会社コロプラ『GKE と Cloud Spanner が躍動するドラゴンクエストウォーク』第 9 回 Google Cloud INSIDE Game & Apps
● マルチクラスタで GKE を最大限 活用するドラゴンクエストウォーク事例