サーバーレス

サーバーレス ギャンビット: Cloud Run で ChessMsgs.com を構築

ChessMsgs Hero Image

※この投稿は米国時間 2020 年 12 月 17 日に、Google Cloud blog に投稿されたものの抄訳です。

この間、Netflix でクイーンズ・ギャンビットを観ていたとき、かつては自分もチェスに大変夢中になっていたことを思い出しました。たまらず一局指したくなったので、「D2-D4」とツイートし始めました。これがチェスの最初の一手を表していることをわかってくれた誰かが次の一手を返して、お相手をしてくれるかもしれないと思ったからです。ただ、ツイートボタンを押す前に、そのように対局を進めるにはチェス盤(物理的なものであれ仮想のものであれ)が必要だと気付きました。もし複数の人から返事をもらったら、チェス盤もその分だけ必要になりそうです。そこで、ツイートの発信は止めることにしました。

ChessMsgs.com screenshot

その日の遅くに、私のユースケースに対応するシンプルなサービスを作り出すアイデアを思いつきました。完全なチェスのサイトを設計するのではなく、Twitter などの SNS を介してチェスを指せるよう、指し手の記録や可視化ができるものを作成することにしました。

プレーヤーは指し手の代わりにリンクを交互にツイートします。リンク先のウェブサイトではチェス盤の状況が再現されており、そこでプレーヤーが次の一手を打つと新しいリンクが作成されます。そのリンクを対戦相手に送り返すのです。これを 100% サーバーレスで実現したいと考えました。それはつまり、ゼロへのスケーリング、メンテナンスの必要をゼロにすることを意味していました。私はこのアイデアにわくわくしつつ、必要なものをリストにまとめてみることにしました。

最小実装製品(MVP)の要件:

  • サーバー側からはステートレスのままになるよう、盤上の駒の配置を表現する(URL 内で完結させられることが理想)。

  • チェス盤を表示し、プレーヤーが次の手を指せるようにする。

ストレッチ ゴール:

  • チェスのルールを適用する(ルールに沿った手のみが許可される)。

  • Open GraphTwitter カードの画像として使用できるチェス盤の png / jpg を動的に作成する。それによって、プレーヤーがリンクを送信すると盤面の画像が自動的に表示される。

すべてを組み合わせる

盤上の駒の配置を表現する

Forsyth–Edwards Notation(FEN)と呼ばれる、チェスの駒の配置を表現する標準の表記法がありました。これは私の構想にぴったりでした。FEN は、ASCII 文字のシーケンスです。たとえば、チェスの始めの駒の配置は、次の文字列で表現されます。

  rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1

それぞれの文字が駒を表します。ポーンは「P」、ナイトは「N」、ビショップは「B」、ルークは「R」、クイーンは「Q」、キングは「K」です。大文字は白駒、小文字は黒駒を表します。文字列の最後の部分は、チェスの特定のルールを表します(FEN についての詳細をお読みください)。

これを URL で使用できることがわかったので、1 つ目の要件は満たされました。URL で盤面の状況を表現できるため、バックエンドでデータを保存する必要がありませんでした。

チェス盤を表示し、ドラッグ&ドロップで手を指せるようにする

膨大なチェスのライブラリを入手できます。特に注目したライブラリは、chessboard.js で、これは「フレキシブルな「just a board」API を使用する JavaScript のチェスボード コンポーネント」と説明されていました。このライブラリで、FEN でのチェス盤の表示、駒の移動、FEN の更新が可能なことがすぐにわかりました。最適です。

わずか 2 時間で基本的な機能を実装できました。

チェスのルールを適用する

当初はこのサービスにチェスのルールを盛り込むことは難しいと考えていました。しかし、chessboard.js ドキュメントの例を確認したところ、chess.js という別のライブラリを使用してルールを統合する方法が示されていました。chess.js は、「チェスの指し手の生成と検証、駒の配置と移動、チェック / チェックメイト / ステイルメイトの検知のために使用する JavaScript チェス ライブラリ(AI を除くほぼすべて)」とのことでした。これを機能させるのに時間はかかりませんでした。ストレッチ ゴールその 1 は達成です。

対局が進んでいく様子の例を次に示します。

新しい対局で D2 から D4 にポーンを動かす - https://chessmsgs.com/?fen=rnbqkbnr%2Fpppppppp%2F8%2F8%2F3P4%2F8%2FPPP1PPPP%2FRNBQKBNR+b+KQkq+d3+0+1&to=d4&from=d2&gid=mOhlhRlMboYsHLqBF1f7I

黒側は同じように D7 から D5 にポーンを動かす - https://chessmsgs.com/?fen=rnbqkbnr%2Fppp1pppp%2F8%2F3p4%2F3P4%2F8%2FPPP1PPPP%2FRNBQKBNR+w+KQkq+d6+0+2&to=d5&from=d7&gid=mOhlhRlMboYsHLqBF1f7I

URL には次のデータが含まれています。

  • fen - 直近の駒の配置

  • fromto - 指された手を示す(盤上の目のハイライト表示に使用)

  • gid - 対局の固有 ID(nanoid を使用)- 今後、指し手を 1 つの対局に結び付けるために使用する予定。たとえば、棋譜全体をユーザーがリクエストできる機能などを追加できる。

完成。しかし...

現時点では、シンプルな HTML の静的ホスティングを除いて、サーバーは必要ありません。しかし、友人や家族とプレイしてみた後、Open Graph や Twitter カードの画像として使用できるチェス盤の png / jpg を動的に作成するという 2 番目のストレッチ ゴールを達成しようと決心しました。その機能があれば、プレーヤーがリンクを送信したときに盤面の画像が自動的に表示されます。これがないと、対局は見づらい URL の連続となってしまいます。

Open Graph 画像を動的に作成する

この要件によりサーバー側の要件が生じました。サーバーで 2 つのことを行う必要があります。

まず、盤面の画像を FEN を基に動的に生成する必要がありました。これも、オープンソースで(ほぼ)まかなうことができました。chess-image-generator という、FEN から PNG を作成する JavaScript ライブラリを見つけました。これを短い Node.js / Express コードにラップすることで、静的であるかのように画像にアクセスできるようなりました。たとえば、実際のエンドポイントのデモはこのようになります。https://chessmsgs.com/fenimg/v1/rnbqkb1r/ppp1pppp/5n2/3p4/3P4/2N5/PPP1PPPP/R1BQKBNR w KQkq - 2 3.pngこのリンクで次の画像が生成されます。

Dynamically created chessboard

次に、この FEN が組み込まれた URL をメイン HTML のメタタグのコンテンツ属性に動的に挿入する必要がありました。JavaScript で DOM 操作を行うだけでよく、サーバー上で HTML の動的な変更は必要ないとお考えかもしれません。私もそうでした。しかし、Open Graph 画像は、メッセージに使用しているいかなるサービスからでも bot により取得できます。これらの bot は、クライアント側の JavaScript を実行することはありません。また、すべての値が静的であることを前提として機能します。そのため、追加のサーバー側の作業が発生しました。

以下を動的に変換する必要がありました。

  <meta property="og:url" content="{{url}}" />
<meta property="og:image" content="{{imgUrl}}" />

次のように変換しました。

  <meta property="og:url" content="https://chessmsgs.com/?fen=rnbqkb1r/ppp1pppp/5n2/3p4/3P4/2N5/PPP1PPPP/R1BQKBNR+w+KQkq+-+2+3&to=f6&from=g8&gid=ziL3VfMEoIT9iNwp6csBh" />
<meta property="og:image" content="https://chessmsgs.com/fenimg/v1/rnbqkb1r/ppp1pppp/5n2/3p4/3P4/2N5/PPP1PPPP/R1BQKBNR w KQkq - 2 3.png" />

これを実行するために数あるノード テンプレート エンジンのうちの 1 つを使用することもできましたが、この単純な置換でそうするのは過剰のようでした。そこで、ノードサーバーで string.replace() 呼び出し用のコードを数行だけ記述しました。

この機能を追加したことで、Twitter(やその他の SNS)上での対局は格段に改善されました。

ChessMsgs game on Twitter

コードの確認

chessmsgs.com のソースは GitHub(https://github.com/gregsramblings/chessmsgs)で入手可能です。

ホストする場所の決定

ホスティングの要件はシンプルです。Node.js / Express、ドメイン マッピング、SSL がサポートされていることが条件でした。Compute Engine(VM)、App EngineKubernetes Engine など、Google Cloud にはいくつものオプションがあります。しかし、このアプリを完全にサーバーレスにしたかった私は、すぐに Cloud Run に行きつきました。Cloud Run はマネージド プラットフォームで、ウェブ リクエストまたは Pub/Sub イベント経由で呼び出し可能なステートレス コンテナを実行できます。

Always Free 枠に 18 万 vCPU 秒、36 万 GiB 秒、1 か月あたり 200 万リクエストが含まれるため、Cloud Run はこのタイプのプロジェクト用としては基本的に無料です(本ブログ執筆時点で。最新情報については、Cloud Run 料金ページをご覧ください)。仮に無料枠を超過しても、課金はリクエストがコンテナ インスタンス上で処理される間だけ発生するため、このタイプのアプリ向けの料金は非常に格安になります。私のコードはシンプルで速いものだったのでなおさらでした。

最後に、Cloud Run 上にデプロイしたことには多くの付加的なメリットがありました。たとえば、Cloud Build による継続的デプロイや、Cloud Logging によるログ管理と分析が行えることです。両方とも設定がとても簡単です。

次のステップ

もしある日突然サイトの人気が沸騰してアクセスが集中しても、Cloud Run を使用しているおかげでスケーラビリティについてはまったく心配がありません。極端な負荷に対応できるように構築したければ、世界中の複数のリージョンに簡単にデプロイし、ロードバランサと、場合によっては CDN を設定することが可能です。また、ウェブ ホスティング機能を画像生成機能から分離し、必要に応じてそれぞれをスケールすることもできるでしょう。

画像生成について最初に検討し始めた際、当然 Google Cloud Storage に画像をキャッシュ保存することを考えました。これは簡単に実行でき、ストレージはとても低料金です。しかし、少し調べたみたところ、次のような興味深い事実を見つけました。2 手が指された時点(両プレーヤーが 1 手ずつ)で考えられる駒の配置は 400 通りある。さらに両プレーヤーが 1 手ずつ指した時点(それぞれが 2 手)では、考えられる駒の配置は 71,782 通り存在する。さらに両プレーヤーが 1 手ずつ指した時点(それぞれが 3 手)では、考えられる駒の配置は 913 万 2,484 通りにまでなる。開始後の最も一般的な何通りかの指し手をキャッシュに保存してパフォーマンスを多少向上させることもできましたが、実際の対局はすぐにキャッシュ保存した画像の先へ進んでしまうので意味があるとは思えませんでした。ちなみに、起こりうるすべての盤面の配置をキャッシュに保存しようとすると、配置は 1046 通りになります。呼び方もないような途方もない数です。

まとめ

これは楽しいプロジェクトでした。私の「本業」ではコードの記述に多くの時間を割くことは許されないので、まるで癒しのように感じました。このアプリに人気が出たら、いろいろな改良のアイデアが出てくることでしょう。

これは、秀逸なクイック スタート(Go、Node.js、Python、Java、C#、C++、PHP、Ruby、Shell などのサンプル)以外では、Cloud Run を使用した最初の実体験でした。Google のデベロッパー アドボカシーでの役職から、Cloud Run のほとんどの機能や特徴については知っていましたが、実際に使用してみることで、開発者の皆様に大変気に入っていただいている理由を理解することができました。

詳細情報

-デベロッパー アドボカシー担当ディレクター Greg Wilson