MagicOnionから始めるリアルタイム通信 (後編)

Happy Elements Advent Calendar 2020 8日目の記事です。

7日目の記事に引き続き、「<a href="https://mercstoria.happyelements.co.jp/">**メルクストーリア**</a>」エンジニアの 岸本 です。

前回の記事では、**MagicOnion** の基本的な部分や、
**サーバー構成** の考え方について、ご紹介させていただきました。

今回は、**Kubernetes** や **Agones** といったインフラ寄りの話題に加えて、
具体的な処理の流れなどをご紹介したいと思います。

<font color="#FF0000">**本内容ですが、現在開発中のものであり、実際の仕様とは異なる場合があります！**</font>

## Kubernetes / Agones

さて、 ここまでの内容を実現するために、今度はインフラ面でどうするか問題になってきます。

今回は、**Kubernetes** や **Agones** を用いることで解決します。
(Kubernetes や Agones の細かい説明は割愛します！)

### Kubernetes

<a href="https://kubernetes.io/ja/">**Kubernetes**</a>とは、コンテナ化されたアプリケーションの **デプロイ** や **スケリーング** をお任せできる、
**コンテナオーケストレーション** のシステムです。

今回のように、**サービスディスカバリー** や **リアルタイム通信サーバー** といった、
複数のサーバーから構成される **マイクロサービス** に適しており、非常に強力なシステムですが、
**ステートフルな構成** で **リアルタイム通信** を実現する場合に、若干問題があります。

- サーバーを直接外部に公開できない。
  **Kubernetes** では、**サーバー (Pod)** を直接外部に公開することはできず、
  **Service** というロードバランサーのような仕組みを経由させて公開する必要があり、
  特定のユーザーを特定のサーバーに接続させたい場合、若干不向きな仕組みになっています。

- スケーリングからの保護ができない。
  ユーザーの常時接続が必要なリアルタイム通信サーバーが、 
  **Kubernetes** の **スケーリング** の対象となって、
  意図しないタイミングでシャットダウンされると困るので、やはり若干不向きな仕組みになっています。

この問題点を解決するため、**Agones** を利用します。

### Agones

<a href="https://agones.dev/">**Agones**</a> とは、**Kubernetes** で **ステートフルな構成** の **リアルタイム通信サーバー** を稼働させることを目的とした、
**Kubernetes** の拡張システムです。

- サーバーを直接外部に公開できる！
  **Agones** では、公開可能な IPアドレス を **サーバー (Pod)** に割当することで、
  **Service** を介さず、直接外部に公開できるようになります。
  
- スケーリングから保護できる！
  **Agones** では、**サーバー (Pod)** のステータスを管理できるようになっていて、
  **使用中 (Allocated)** なサーバーは、**Kubernetes** の **スケーリング** から保護されるようになります。

このような特徴から **Kubernetes** でも **ステートフルなリアルタイム通信サーバー** を扱いやすくなり、
今回のような **リアルタイム通信サーバー** の運用に適したシステムとなっています。

## 全体構成図

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352646/63830bd1-c061-f37f-6193-ee48fa7456c6.png" width="800" alt="1208_01.png">

ここまでの仕組みを組み合わせることで、
クラウド上では、大まかにこのような構成となりました。
(運用上、他に必要なものもあるため、これがすべてではありません！)

### サービスディスカバリーとリアルタイム通信サーバー

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352646/04ea7258-224d-3069-990a-0faf01830da9.png" width="800" alt="1208_02.png">

ソフト的には、このような構成となっています。

**サービスディスカバリー** は、Kubernetes上で稼働するサーバーであり、
**MagicOnion** の **Service** を利用します。

**リアルタイム通信サーバー** は、Kubernetes上で稼働するサーバーであり、
**MagicOnion** の **StreamingHub** を利用します。

ひとつのサーバー内に、複数のポートでホストを立てており、
クライアントから使用する公開用の API とは別に、
サーバー間通信で必要な内部用の API は、別立てして分割しています。

また、**Kubernetes** や **Agones** の API に問い合わせることで、
他のサーバーの情報など、**Kubernetes内部** の様々なことを知ることができるようになっています。

下記は、コマンドベースの簡単な例ですが、
**Agones** で稼働している **リアルタイム通信サーバー (ゲームサーバー)** の一覧を取得して、
外部に公開可能な IPアドレス と ポート の情報を取得しています。
(これは **Minikube環境** での結果となります。)

```
$ kubectl get gameserver
NAME                    STATE   ADDRESS          PORT   NODE       AGE
real-time-xhxkn-8szj7   Ready   192.168.99.100   7400   minikube   24m
real-time-xhxkn-ff4rp   Ready   192.168.99.100   7101   minikube   24m
```

C#含めて様々な言語で **Kubernetes** や **Agones** の **SDK** が公開されており、
それらを通じて、アプリケーション内部からでも上記のような情報を取得できるようになります。

## 処理の流れ

**チャット** を例に、具体的な処理の流れを考えてみます。

完全にランダムなチャットルームへの参加ではなく、
**パーティー** や **ギルド** といった、特定のグループ専用のチャットルームへの参加を前提とします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352646/fe17142d-36cb-5fcf-a687-21a646c10865.png" width="800" alt="1208_03.png">

1. **ユーザー (クライアント)** は、**チャットルーム (リアルタイム通信サーバー)** の接続先がわからないため、  
  **サービスディスカバリー (APIサーバー)** に **チャットルーム** の接続先を問い合わせます。
2. **サービスディスカバリー**は、**Kubernetes** で稼働している **チャットルームのリスト** が欲しいため、  
  **Kubernetes** に問い合わせます。
3. **サービスディスカバリー** は、**チャットルームのリスト** を基に割当状況などを確認し、  
  **ユーザーが要求しているチャットルーム** を確保します。  
  このとき、**チャットルーム** は、**Agones** に対して **使用中 (Allocated)** である旨を宣言し、  
  **Kubernetes** のスケーリングから保護します。 
4. **サービスディスカバリー** は、**チャットルーム** の接続先を **ユーザー** に返します。
5. **ユーザー** は、**サービスディスカバリー** から得た **チャットルーム** に接続します。  
  あとは、常時接続でクライアントサーバー間の双方向通信ができるようになるので、  
  自由にチャットすることができます。
6. すべての **ユーザー** が退室して、**チャットルーム** が不要になれば、  
  **Agones** に対して **シャットダウン可能** である旨を宣言し、スケーリング対象に戻します。

ちょっと長い道のりでしたが、
ここまでできれば、**ステートフルな構成** の **リアルタイム通信** を前提とした環境ができあがり、
一通り動かせるようになりました。

あとは、要件に応じてカスタマイズすることで、やりたいことを実装できるかと思います！

## おまけ

### デプロイの仕組み

**AWS** での稼働が前提となりますが、
下記のようなフローで、デプロイできる仕組みを構築しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352646/bddc6413-9bcd-de3e-8f73-c17b959a20b8.png" width="800" alt="1208_04.png">

**Kubernetes** の最も基本的なデプロイ方法として、
対象となるサーバーの **Deployment (もしくは Agones の Fleet)** で指定している、
コンテナのイメージバージョンを変更することで解決できます。
 
```yaml
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: hoge
        image: hoge:v1.0 → hoge:v2.0
```

この yaml定義 の更新自体を自動化するような仕組みでも可能ですが、
メルクストーリアでは、管理画面からデプロイできる仕組みを構築しており、
アプリケーション内部から **Deployment** や **Fleet** のパッチをできるようにしています。

ソースコードを **GitHub** にコミットすれば、**CodeBuild** で自動的にコンテナビルドし、
それを **ECR** にプッシュします。
**管理画面** から **ECR** のリポジトリにアクセスして、デプロイしたいイメージバージョンを選択することで、
デプロイできるようになっています。

また、Agones管理下 の使用中の **リアルタイム通信サーバー** は、
接続中のコネクション切断など、**グレースフルシャットダウン** が必要になるため、
**デプロイバッチ** を動かすことで解決しています。

こうすることで、可能な限り手作業を排除し、安全にデプロイできるようにしています。

### シークレットの取り扱い

こちらも **AWS** での稼働が前提となりますが、
下記のようなフローで、シークレットを扱えるようにしています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352646/0fb887f5-3129-d9a6-c116-454e9ca4b9ef.png" width="800" alt="1208_05.png">

**Kubernetes** では、**Secret** という機密情報を扱える仕組みがありますが、
例えば **秘密鍵** のようなものを **Secret** で扱うには、管理面での注意が必要となります。

そこで、機密情報自体は、AWS の **SecretsManager** で管理し、
アプリケーション内部から **SecretsManagere** にアクセスすることで、
**Kubernetes** の **Secret** に反映できるようにしました。

こうすることで、コードへの機密情報埋込をはじめ、
Git にコミットしたり、ローカルで管理したり…、といったことを回避できるようにしています。

## まとめ

ということで、**MagicOnion** を用いた **リアルタイム通信** の仕組みづくりについてのご紹介でした！

まだまだ開発中のものなので、ご紹介できる具体例も少なく、
前提として必要になる技術のご紹介も含めたため、
なかなかボリュームのある内容になってしまいましたが、いかがだったでしょうか…？

日本国内のゲームのサーバーサイドの事情として、
**MagicOnion** を含めて **サーバーサイドC#** を採用している事例はまだまだ少なく、
弊社カカリアスタジオでも、クライアントサイドは、**Unity (C#)**、
サーバーサイドは、**Ruby on Rails (Ruby)** が主流となっています。

そんな中、**MagicOnion** をはじめ、**Kubernetes** や **Agones** などを用いた開発は、
なかなか挑戦的な内容になっているかと思いますので、
**リアルタイム通信** や **サーバーサイドC#** など、少しでも気になっている方の参考になれば幸いです！

## メンバー募集

Happy Elements株式会社 カカリアスタジオでは、
いっしょに【熱狂的に愛されるコンテンツ】をつくっていただけるメンバーを大募集中です！

もし弊社にご興味持っていただけましたら、是非一度
下記採用サイトをご覧ください。
[Happy Elements株式会社 採用特設サイト](https://recruit.happyelements.co.jp/)

引き続き、Happy Elements Advent Calendar 2020 をお楽しみください！
ありがとうございました！