OpenMatch の production 運用を考える

May 1, 2021 01:08 · 287 words · 2 minute read

OpenMatch とは、OSS のゲームマッチメイクフレームワークです。

たとえば、以下のようなケースに利用できます。

  • ランダムな5人をマッチングして協力プレイ
  • 1人の「ネコ」と、4人の「ネズミ」をマッチングして鬼ごっこ
  • 異なるロールのキャラクターを1チーム5人ずつマッチングして 5vs5

OpenMatch では、これらを「チケット」「アサインメント」などの概念で管理しています。 開発者は、これらを管理するための「frontend」「director」「match function」などのコンポーネントを k8s 上に実装していくことになります。

これらのコンポーネントは、OpenMatch が提供してくれている チケットやアサインメントを管理するサービス に依存して実装します(この記事では便宜上、これらを OpenMatch-Core と呼ぶことにします)。

上記のコンポーネントは Pod として k8s クラスタに追加する形になるので、運用者は他の k8s リソースと同様にマニフェストを管理する必要があります。

この記事では、 production 環境で OpenMatch を安定稼働させるために 「どれくらいのリソースを割り当てればいいのか」、「可用性を高めるにはどういう設定を当てればいいのか」など、検討したことをまとめています。

2通りのデプロイ方法 🔗

installation を読むと、OpenMatch-Core のデプロイ方法は大きく2通りあることがわかります。

1つは、OpenMatch が公開している yaml ファイル を apply する方法です。
もう1つは、OpenMatch が公開している helm チャート を install する方法です。

前者の方法だと、どこを開発者側で書き換えるべき値なのかわかりにくかったので、helm を採用しました。

公式の Production Best Practice にも、「本番構成にするなら values-production.yaml を差し込んでね」と書かれている点も、helm の採用を後押ししました。

余談ですが、今回の project では、k8s マニフェストを ArgoCD で管理していたので、helm との相性があまりよくありませんでした(helm on ArgoCD で管理するには、chart と value ファイルが同じレポジトリにないとダメだったり…)。

そこで、helm はあくまでもマニフェストの自動生成ツールとして利用して、設定値を value に寄せる、という管理方法に行きつきました。

具体的には、以下のコマンドでマニフェストファイルを生成し、

$ helm template \
  -f [value file] \
  -n open-match \
  -v v1.1.0 \
  open-match/open-match [release] \
  > open-match.yaml

生成したファイルを ArgoCD で監視する形にすることで対応しました。

変更可能な値を確認する 🔗

以下のコマンドを実行することで、開発者側で設定可能な値を確認できます。

$ helm show values open-match/open-match

設定可能な項目はかなり多いので、基本的には values-production.yaml をベースに必要な部分だけ書き換えていく方針で進めました。

必須の設定を入れていく 🔗

公式にも、

Open Match needs to be customized to run as a Matchmaker. This custom configuration is provided to the Open Match components via a ConfigMap (om-configmap-override).
Thus, starting the core service pods will remain in ContainerCreating until this config map is available.

とあることから、以下の設定は必須です。

  • open-match-customize.enable=true
  • open-match-customize.evaluator.enabled=true
  • open-match-override.enabled=true

また、image の設定ですが、 default では 0.0.0-dev となっているので、ここも使いたい OpenMatch-Core のバージョン (e.g., 1.0.0) を指定する必要があります。

あとは gcpProjectId がダミーのものになっています。

このままでも特に問題なく動いてそうではありましたが、GKE を使っていたのでその project ID に変えました。

redis Stateful Set の設定を入れる 🔗

k8s クラスタに open-match をデプロイして開発を進めていたのですが、ある日突然「何回かに一回 ticket not found が返される」というケースに遭遇しました。

詳しくは別途記事を書く予定なので要点だけ書くと、

OpenMatch-Core はデフォルトだと、チケットの状態管理に redis stateful set をデプロイして使用しています。
この redis sts は、サイドカーとして redis sentinel サーバーを含む構成になっていて、複数の slave を持つ HA 構成になっています。
redis sentinel を使って fail over を担保するためには、最低でも2台以上正常に稼働している必要があります。
先に述べた問題は、同じノードに複数の redis sts が配置され、かつノードの auto upgrade またはノード障害によって同時に複数の redis sts が kill されたために発生した問題でした。

なので、これを避けるために、redis sts 間には anti-affinity の設定をし、同一トポロジー(最低でも同一ノード)には配置されないように設定してあげる必要があると考えています。

具体的には、redis.master redis.slave に以下の設定を追記してあげました。

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - topologyKey: kubernetes.io/hostname
      labelSelector:
        matchExpressions:
        - key: component
          operator: In
          values: 
          - open-match-redis

安全に倒すなら、redis の master だけは auto upgrade を off にしたノードに配置されるように selector を指定するのも良いかもしれません。

redis:
  master:
    nodeSelector:
      key: value
    affinity:
      # ...同上

必要なリソース量を見直す 🔗

values-production.yaml デフォルトの resources.requests 設定だと、 master memory=3Gi/cpu=3 とかなり多くのリソースを要求してきます。
slave だとこの 1/3 ですが、レプリカは3つになるように設定されています。

OpenMatch-Core 自体は、スケーラビリティをかなり意識して作られているので、よっぽど大規模なゲームか、あるいは複雑なマッチングロジック出ない限り上記の設定はオーバースペックになるかと思います。

ここは負荷試験の結果や、サービスインした後の状況から調整すると良さそうです。

まとめ 🔗

今回は OpenMatch-Core を本番運用する際の設定について考慮したポイントを紹介しました。
helm だったり ArgoCD だったり、他の知識を登場させてしまったので、これらについては別途記事を書く予定です。
また、分散システムで登場する「スプリットブレイン」(=マスターが複数発生してしまう問題)が観測されたので、それについての傾向と対策も、今後ご紹介できたらと思います。