注目の記事

【レポート】インフラエンジニアは働かない~AWSのフルマネージドサービスでメンテフリーになるまで~ #AWSSummit

DA事業本部の春田です。

AWS Summit Online絶賛開催中!ということで、本記事では「CUS-60: インフラエンジニアは働かない~AWSのフルマネージドサービスでメンテフリーになるまで~」の内容についてまとめていきます。

セッション情報

  • 株式会社カプコン システム開発部 中村 一樹 氏
  • 株式会社カプコン システム開発部 中島 淳平 氏

DL数500万を超える大型タイトル、モンスターハンターライダーズ。 メンテフリー、省コスト、最先端、をテーマにしたカプコン史上最大のインフラアーキテクチャはどの様に設計され、どう運用されているのか。コンテナって実際どうなの、Kubernetes?ECS?RDBMSを使わずしてサービスを提供することは可能?大量アクセスにより生成されるログを安全に回収するにはどうする?実際に運用してみた経験や事例を踏まえて、カプコンの考えるクラウドネイティブ時代のアーキテクチャをご紹介します。

※セッション動画は以下リンク

アジェンダ

  1. モンスターハンターライダーズ
  2. どういう構成になったの
  3. DBだって楽したい
  4. DynamoDB
  5. 今まで使っていた分析基盤
  6. 今回の分析基盤

モンスターハンターライダーズ

  • 2020年2月19日よりサービス開始
  • iOSとAndroidで配信中のRPG
  • 総DL数 500万↑
  • DAU 30万↑(ピーク時)
  • この規模を支えるインフラがある

これからインフラの話をしよう

  • 働きたくない、とても働きたくない
  • 寝転がってるだけで、お賃金が欲しい
  • (建前)管理の人件費を下げてトータルのコストを下げたい!
    • ぶっちゃけインフラの構築めんどくさい
    • 管理もめんどくさいし運用はもっとめんどくさい
    • ボタン押すのすらめんどくさいし、グラフを見るのもめんどくさい

  • 今までのインフラ
    • 値段は安いほうが良い
    • 性能は高いほうが良い
    • 取れるメトリクスは全部取って監視
    • すべてを把握して管理したい
    • → 面倒くさい
  • 全力で「働かない」インフラ
    • 単価よりも管理コスト
    • 性能よりも管理コスト
    • ブラックボックス上等
    • インフラは不思議な力で動いている
    • ベンダーロックイン?知らんな

どういう構成になったの

  • EKSを使用してAPIサービスを提供している
  • DynamoDBとElastiCacheをデータベースとして使用
    • MySQLは使っていない
  • 使うサービスを絞って、複雑化を回避

なぜEKSなのか

  • ECSではダメだったの?
    • 当初はECSの予定だったが、途中でEKSに切り替えた
    • EKSの方がなんとなく新しそうでかっこいいからっていう理由もあったりする
  • そもそも守備範囲が違う
    • ECSはコンテナの実行環境"しか"提供しない
      • バランシング、デプロイについては他のサービスやツールを併用しなければならない
      • 作り込みたい時はメリットだが私はめんどくさがりである
    • EKSはインフラ全体を包括的に扱う
      • バランシング、デプロイ、スケーリングも含めてEKSが面倒を見る
      • 各オペレーションはあくまでKubernetesのコマンドに則った形で行う
  • ECS → AWSから、ECSを操作する
  • EKS → EKSから、AWSを操作する

→ つまり、EKSの世話だけをしておけば幸せになる

  • 具体的な事例(何がどう楽になったのか)
  • デプロイ
    • いままで
      • ELBからEC2を切り離す
      • EC2に対してコードを配布
      • 依存ライブラリのインストール
      • キャッシュ構築
      • ELBにEC2を戻す
      • 以上をすべてのEC2に対して実行
    • 現在(EKS)
      • kubectl apply –f xxx.ymlだけ!
  • Job実行
    • いままで
      • Rundeckを構築・運用
      • Jobのコード記述
      • 実行
    • 現在
      • kubectl apply –f xxx.ymlだけ!
      • コンテナイメージを使いまわしてJobコードを一元化
  • スケーリング
    • いままで
      • ASGを構築
      • AMIを作成
      • トリガーを検討
      • しきい値の調整
    • げんざい
      • kubectl apply –f xxx.ymlだけ!
      • 一元化による管理コストの削減の恩恵が大きい

真面目なまとめ

  • もともとECSで展開する予定だった
    • EKSが日本でサービスインしたので切り替えた
    • コンテナイメージ自体は同じなので、切り替えはすぐ出来た
  • EKSの後ろには結局AWSがいるのでAWS力は必要
    • 主にトラブルシュート。逆に言えば今までのノウハウは無駄にならない
    • ただ、EKSを使えばAWSへの敷居はかなり下がる。AWSコワクナイ。
  • EKSはECSと用途が違う。上位互換ではない。
    • 併用するというのも全然ありだと思います。
  • 割り切りも必要
    • すべてを把握したい気持ちはわかる。だが、それは本当に必要なことだろうか。
    • なんかよくわからんけどいい感じに動いている、という現実を受け入れれば楽になる
    • (実際は使ってるうちにわかってくるけどね...)

DBだって楽したい

  • インフラ管理で一番めんどくさいこと
    • あえて言おう、DBの管理であると。
  • DBの管理は何故めんどくさいのか
    • 基本的に落とせない
    • サービスを続ける限りストレージが増大していく
    • サービスを続ける限り性能も劣化していく
    • スケールアウトめんどくさい、構成によっては出来ない
    • スケールアップもめんどくさい、構成によっては出来ない
    • その癖、虚弱体質ですぐしぬ

  • Auroraではダメだったの?
    • ゲームならRDBでなくても問題ないのでは?
    • 【前提】DynamoDBとAuroraは前提として用途、目的が違う
    • とりあえずMySQL(互換含む)にしておけばよいという風潮
      • 気持ちはわかる
    • 本当にMySQL(RDB)でなければならないのか?
      • RDB固有の機能は使っている?本当に?
    • 慣れてるメンバー多いから...という理由だけで選んでいないか?
      • 「私SQL書けます!」という言葉は信じてはいけない
      • とんでもないクエリを書く人は(残念ながら)存在する

→ 実はRDBじゃなくてもよいのでは?

DynamoDB

  • 「働かない」観点から見たDynamoDB(で、私はどれだけ楽ができるんだい。)
    • 結局、ゲームにおけるDBはセーブデータの延長であることが殆ど。
    • どれくらい使うのか?をコンソールで入力するだけ
      • 勝手にスケーリングしてくれる
      • スペック課金ではないところがミソ。
      • とりあえず適当に設定しといて数値見つつ調整すれば良い
    • VPCがー、とかサブネットがーとか気にしなくて良い
      • ぶっちゃけネットワークとかめんどくせー(暴論)
    • ユーザーがーとかアクセス権限がーとか気にしなくて良い
      • IAMで許可されていればOK、そうでなければNG。シンプル。
    • スキーマをあまり気にしなくて良い
      • ゲームは仕様変更、改修が多い
      • スキーマレスなので、主キー以外をあまり気にする必要がない
  • DynamoDBを管理するということ
    • ≒CapacityUnitをいい感じに調整すること

→ 本当に楽だったのか(そんなうまい話はない)

スキーマ設計がむずい(適当にやるとしぬ)

  • よくやる例
    • SQL感覚でテーブルを正規化してしまう
      • 1テーブルに対してしかクエリが発行できないのでクエリ発行数が増えて死ぬ
    • 1レコードに何でもかんでも突っ込んでしまう
      • レスポンス容量がでかくなって死ぬ(1クエリで返却できる量に制限がある)
      • 1レコードの容量制限もあるが、これは滅多に超えることはないです
    • スキーマレスだからという理由でスキーマを決めずに実装する
      • 口伝のスキーマ伝説が生まれる
      • 属人性っていうレベルじゃねえぞ
      • ただ、厳密に決めすぎてもメリットが薄まる
  • Solution
    • オレオレDSLを定義して運用した
      • Kubernetesのマニフェストに合わせてYAML形式で記述するようにしてある
      • 流し込むといい感じに解釈してテーブルを作ってくれる
      • ついでにPythonのModelクラスコードも生成する

クエリが念仏レベルでわからん(南無)

  • Problem
    • SQLとあまりに文化が違いすぎる
    • 辞書配列に突っ込む形なので型ヒントの恩恵をほぼ受けられない
    • IDEによる補完もない
    • というかboto3使いづらい
      • IDE補完くらい効かせてくれないですかね
    • OSSライブラリによる支援もあまり期待できない
      • 無くはないが洗練されているとは言えない
  • Solution
    • オレオレORMを作った
      • OSSのORMも存在はするが、残念ながら目的に合わなかった
      • DynamoDBはKVS的な使い方しか出来ないのでラップするのはそれほど難しくない
      • 前述のオレオレDSLと連動してモデルクラスを自動生成する
    • 難しいことは基本的にORMで全部隠す
      • 書けるけど書いてほしくないこと、は結構あります

※Githubで公開(https://github.com/Jumpei-Nakajima/hatsudenki)

CapacityUnitの管理が手間(テーブルごとに決める必要がある)

  • Problem
    • テーブルをガンガン増やすとCUの管理が複雑になってしぬ
      • テーブルの個数自体に課金は無いのでついつい増やしがち
      • AutoScaleするにしても、MIN値とMAX値を適切に設定しないと無駄な課金が発生する
    • AutoScaleはそれほど反応が早くない
      • スパイクで簡単に死ぬ
    • オンデマンドもそれほど反応が早くない
      • オンデマンドでもCUスロットルは起こります
      • AutoScaleよりはマシ、というレベル。
      • スケールに備えるというより、無駄な課金を発生させないための仕組みと考えるべき
  • Solution
    • テーブルの数を絞る

→ 絞ると言ってもどうやって?

テーブルの減らし方(テーブルをまとめるという概念)

  • 隣接関係リスト設計(関係のあるデータを近くに置くという考え方)
    • 用途が似通っているものは一つのテーブルにまとめましょう
      • 例)ユーザーの所持物はすべてBoxテーブルにまとめる
    • 用途が似通っているものは主キーも大体同じになる
      • ゲームにおける主キーはほぼすべてユーザーID(HASH)、固有ID(RANGE)である
      • つまりゲームと隣接関係リスト設計は非常に相性が良い。
      • その気になれば多分1テーブルでサービス提供も可能(流石にやらないけど)
    • DynamoDBは主キー以外は定義しない
      • スキーマレスだからね

→ 具体的には

  • ユーザーの所持物(Box)を考える
    • ユーザーの所持物は種類がたくさん。例として通貨(money)と装備(equip)と素材(material)

  • HashキーはどれもユーザーID(ユーザーの所持物だからね)
    • ゲームにおいて、大抵のテーブルの主キーはユーザーIDになる
    • カーディナリティ的にも一番都合が良い

  • Rangeキーはテーブルによって違う(違うけど意味は同じ)
    • 通貨であれば通貨ID (有償or無償,android or iOS,等)
    • 装備であれば装備ID (飛竜刀[紅葉],飛龍刀[朱],飛龍刀[楓],等)
    • 素材であれば素材ID (火竜の紅玉,火竜の逆鱗,等)
    • カテゴリ毎にユニークなIDという点では共通している

  • 一つのcolumnで複数の種類を表現する(プリフィックス)
    • カテゴリ毎にユニークIDは即ち{カテゴリ}_{ID}で表現可能
      • 例) material_火竜の逆鱗
      • ※実際には_はID部にも使われることが
      • 多いため別の記号の方が良い。@,/とか。
      • ちなみに日本語も問題なく使える

  • クエリで検索する時はbegins_with構文を使う(Rangeキーはソートされているのがミソ)
    • Rangeキーはその内容でソートされていることが保証される
      • つまり、前方一致検索やbetween検索が可能
    • Rangeキーには{カテゴリ}_{ID}で定義されているので
      • ユーザAの素材をすべて取得したい
        • hash=“user_A”, range=begins_with(“material_”)
      • 特定ID(火竜の逆鱗)を取得したい
        • hash=“user_A“, range=“material_火竜の逆鱗”

  • 性能劣化について(ない)
    • DynamoDBはレコード数が増えても性能がほぼ劣化しない。
      • ハッシュキーのカーディナリティがきちんと担保されていれば
      • ゲームにおいてはユーザーIDにしておけば問題ない
    • ただし、キーを利用しない検索(全検索)はできないものと考えること
      • 100万レコードから1レコード探り当てるとか死ぬ
    • 容量もほぼ無限にスケールするから気にするな
    • むしろガンガンレコード入れていった方が良い
    • Keyを使用しない全検索はできない

真面目なまとめ(どのような結果)

  • MySQLを止めたことで失ったものは多かったが得たものも多かった
    • 一度立ち止まって「本当にRDBMSは必要か」を考えてみて欲しい
  • DynamoDBはCUだけ見ていれば良い
    • それすらもAutoScaleにある程度任せられる
  • スキーマレスだけどスキーマはちゃんと決めよう
  • DB自体の機能は少ない。アプリ層でがんばれ。
  • 似た用途のテーブルはまとめてテーブル数を減らそう
  • 性能劣化はない。気にするな。
    • ただしキーの設定は慎重に
  • 結果、得られたもの(働かないインフラは、どれくらい働かなくて済んだのか)
    • 属人性の排除
    • フロントサーバーの運用
      • Kubernetesの機能に全乗っかり。オートスケーラーにおまかせ
    • デプロイフローの運用
      • Kubernetesの機能に全乗っかり。Deploymentにおまかせ
    • DBの運用
      • DynamoDBに全乗っかり。CUスパイクだけ気を付ける。
      • 性能劣化もないので本当にCUだけ見てればいいです。

今まで使っていた分析基盤

  • ゲームサーバーから出力されたログをfluentdでtail → Aggregatorに送る
  • Aggregatorで受け取ったデータを加工・集計 → DWH

  • この構成のメリット
    • It’s a simple
      • 構成がわかりやすい
      • 構築もインスタンスを立てて、fluentdを入れて、設定するだけ
    • リアルタイム性が高い
      • ストリーム送信なのですぐにDWHにデータが入る
  • この構成のデメリット
    • アグリゲーターが障害点
      • 負荷が上がり、耐えることができなくなるとログをこぼす可能性がある
      • 負荷に応じてインスタンスの増減・分散処理をしないといけない(人力)
      • これを避けるためにあらかじめスペック高め、多めのサーバーが必要になる
    • 届く保証がない
      • ストリームのため届く保証がない
      • 取りこぼした場合は復旧が必要

→ アグリゲーターの面倒をめっちゃ見ないといけない

  • 分析基盤だって働きたくない
    • ログが急激に増減しても安心・安全
      • 穏やかな心で日々を過ごしたい……
    • とにかくインスタンスの管理がしたくない
      • インスタンスのおもりはもうしたくない……
    • それでもコスト(お金)は抑えたい
      • 人的コストは下げるためにお金は払うけど
      • それでも安いに越したことはない

今回の分析基盤

  1. ゲームサーバーから出力されたログがKinesisにためられる
  2. 一定量たまった後に、S3バケットに送信
  3. EKSが定期的にS3バケットをポーリング
  4. 新しいデータが到着したことを検知したら、Lambdaを起動
  5. LambdaはS3のデータの分解や加工を行う
  6. 加工されたデータは、別のS3に配置
  7. 加工前のデータも、バックアップとして別のS3に配置
  8. Lambdaが正常終了したら、EKSがSQSへ正常終了のメッセージを送信
    1. 別のEKSがSQSを検知して、DWHのデータロードジョブを発火
    2. DWHが加工済みS3バケット内のデータをロード
  9. Lambdaが異常終了したら、DLQに格納
    1. EKSがDLQを検知し、Lambdaをリトライ起動

  • Kinesis
    • とにかくログを溜めてくれる
      • 設定次第で一気に来ても受け止めてくれる
      • 重複はあるが取りこぼしはなくなる
    • PUSHでなくてPULLなので確実に届けられる
      • ログ出力の増減に関わらず一定のペースでログの処理ができる
        • PULLなので準備出来た状態でログを取り出せるので確実に処理できる
      • リアルタイム性は落ちるが1分1秒争うものでもないので問題ない
    • でもStreamで受けているのでデータはすべて1行……
      • はやくLambdaでjsonを1行ずつに改行入れるやつをデフォルトにいれてー
  • EKS・Lambda・SQS
    • 分析側もEKSのjobでスケジューリング
      • ゲームサーバーでEKSを使っているため
      • 属人性を下げるため、技術統一はできるだけ意識
    • Lambdaなのでサーバー管理の必要がない
      • 複雑な加工等は行っていないのでLambdaで十分
    • SQSで動作保証
      • Lambdaが動かなかったり、エラー吐いたりしても確実に動作させるため

→ 手放し運転でもほぼ問題なくなった!でも若干複雑になったのは反省点……

  • 実際楽になったの?
    • 構築コストは上がっている
    • ログデータの増減に怯える日々はなくなった
      • 急激に増えてもKinesisパイセンが受け止めてくれるから
    • インスタンスが調子悪いなど考える必要がなくなった
      • 自分、サーバーレスなんで……
    • 常に最低限必要なコストで動いている安心感
      • 負荷を気にしてあらかじめスペックの高いインスタンス、多めの台数を用意しなくてよくなった
    • 全体の作業量: 5%削減
    • 精神的負荷: プライスレス