🎮

Webブラウザとクラウドサービスを使った遠隔仮想環境でゲーム用の低予算バックエンドを拵える話 (その3)

2021/10/22に公開

概要

ネットワークの事前知識がそれほどない(これから勉強していきたい!)という前提で、ゲームにネットワークの要素をなんとか低予算で導入したいと考えている方に向けて、地道に進めるバックエンド環境構築例を紹介します。途中、以下のサービスやテクノロジーが登場します。(記載内容は2021年10月中旬時点の情報となります)

  • Google Cloud Platform(以下、GCP), Google Compute Engine(以下、GCE), Cloud Shell, Cloud Shell Editor
  • Linux, Debian
  • PostgreSQL, PhpPgAdimin, SQL
  • Node.js, Expressフレームワーク, fsモジュール, node-postgres(pg)モジュール
  • HTTP, HTTPS

実現すること

  • 手元の開発環境(PC,ネットワーク)の影響を極力受けない状態でバックエンドを用意する(Webブラウザ=HTTP,HTTPSのみ、SSHクライアントは使用しない)
  • アプリケーションサーバ(Webサーバ+サーバサイド言語処理環境)を用意する
  • データベースサーバを用意する
  • 各サーバ用のデータ作成とデータ編集が行える環境を整える
  • お金(費用)はなるべくかけない

作業のステップ

作業をいくつかのステップに分け、順番に紹介してきます。

  1. 事前準備+GCEで遠隔仮想環境を構築
  2. テキスト編集環境を用意する
  3. アプリケーションサーバを用意する
  4. データベースサーバを用意する
  5. 取り回しの良さを向上させる

3回目の今回は「アプリケーションサーバを用意する」編となります。

前回までのおさらい

第1回と第2回の記事では、

  • GCPを使った仮想環境の構築
  • 仮想環境上の基本操作(CUI、アップロード&ダウンロード)

について確認を行いました。仮想環境がどのように構築されており、それを操るための基本的な手段を把握したところで、いよいよバックエンドの中枢となる「アプリケーションサーバ」の準備を進めていきます。

アプリケーションサーバとは

とはいえ、「アプリケーションサーバ」とは一体どんなもののことを指すのでしょうか?ただの「サーバ」と「アプリケーションサーバ」はどのように違うのでしょうか?アプリケーションサーバを実現する前にいくつかの用語について確認を行います。

クライアントサーバシステム

通信ネットワークの世界では、「クライアントサーバシステム(モデル)」と呼ばれる負荷分散の仕組みがよく知られています。負荷分散とは、ある大きな課題(処理)を1台のコンピュータに集中させるのではなく、複数の異なる役割を持つコンピュータで協力しあって(負荷を分散しあって)解決していこうという考え方、もしくはそれを実現するテクノロジーのことを指します。

「クライアントサーバシステム」は、課題を解決するために使用するコンピュータ群を2つの役割、「クライアント」と「サーバ」に分けて負荷分散を実現しようというモノです。「クライアントサーバシステム」は今日の多くのアプリ、Webサービスに応用されています。

ゲームを例に挙げれば、「オンライン協力プレイ」という課題を解決するために、ユーザーがゲームを遊ぶために使用するスマホ、ゲーム機やPCに「クライアント」という役割を与え、さらにゲーム会社が用意したコンピュータ(今まさに用意している仮想環境など)に「サーバ」という役割を与えることで課題を解決しているわけです。

以下は、「クライアントサーバシステム」における2つの役割を整理した表です。

クライアント(Client) サーバ(Server)
意味 直訳すると「顧客」 直訳すると「提供者」
役割を担う端末例 スマホ、ゲーム機、PC・・・etc 仮想環境・・・etc
端末の構成数と性能 比較的台数は多い
性能はまちまち
比較的台数は少ない
高性能である場合が多い
端末の主な担当 入力と出力 演算と記憶
担当実務の例 ボタンを押す
サーバに情報を送信する
サーバから情報を受信する
情報を画面に表示する
クライアントから受信した情報を分析する
クライアントから受信した情報を記録する
クライアントに情報を送信する

以上のことを踏まえると、生まれたての我らが仮想環境(「サーバ」の役割)に必要なものが見えてきます。

  1. クライアントと連携する機能
  2. 演算や記憶をはじめとした様々な処理を行う機能

単純に「サーバ」と呼ばれるコンピュータは一般的に上記のどちらか、もしくは双方をもったモノであると言えそうです。また、双方が非常に大雑把な表現であることからもわかるように、その「機能」をどのように実現するかまで厳密に定められていません。それがハードウェアであるかソフトウェアであるかも問いません。(実は、今日の「サーバ」はほとんどがソフトウェアの組み合わせで実現されています)

こういった点から、様々な「○○サーバ」と呼ばれる単語が存在します。

アプリケーションサーバ

アプリケーションサーバとは、「プログラミング言語で作った処理(アプリケーション)が動いている」サーバです。事前に様々な種類のアプリケーションを作成しておき、クライアントから「このアプリケーションを動かして欲しい!」という要請を受け、必要な処理を行った結果をクライアントへ送信します。こういった動作を取る面から、アプリケーションサーバは、「演算や記憶をはじめとした様々な処理を行う機能(アプリケーション)」のみだけでなく、「クライアントと連携する機能」も持ち合わせていることが多いです。

アプリケーションサーバの選択肢

アプリケーションサーバのニーズは高く、Webの混迷期から今日に至るまで様々なソフトウェアが登場しています。「クライアントと連携する機能」と「演算や記憶をはじめとした様々な処理を行う機能」を同梱した製品もあれば、それぞれを別に組み合わせて実現することなども可能で実に多様です。以下に選択肢をいくつかご紹介します。

  • 「クライアントと連携する機能」を持つ製品、技術
    • Apache
    • Nginx
    • Internet Information Services(略してIIS)
    • BSDソケット
  • 「演算や記憶をはじめとした様々な処理を行う機能」を持つ製品、技術
    • Phusion Passenger(アプリケーション開発言語:Ruby,Pythonなど)
    • Puma(アプリケーション開発言語:Ruby)
    • ASP.NET(アプリケーション開発言語:C#,VB.NETなど。IISとセットで利用する事が多い)
  • 双方の機能を持つ製品、技術
    • Apache Tomcat(アプリケーション開発言語:Java。カスタマイズされたApacheを使用)
    • Eclipse GrassFish(アプリケーション開発言語:Java)
    • Django(アプリケーション開発言語:Python)
    • Gin(アプリケーション開発言語:Go)
    • Laravel(アプリケーション開発言語:PHP)

Node.js

多様な選択肢の存在するアプリケーションサーバではありますが、そんな中で今回採用するのはNode.jsと呼ばれる環境です。

Node.jsのメリット

今回Node.jsを利用する最大のポイントは即効性です。本来、アプリケーションサーバを実現するためには様々な設定を施したり、動作原理の理解を前提に操作する必要があったりといった多くの手間が必要でした。Node.jsは他の選択肢に比べ、「すぐに使える」というスピード感を強く感じます。(筆者の趣味もあります。異論は認めます。)

なお、Node.jsはアプリケーション開発にJavaScript言語を採用していますが、言語仕様について当記事では細かく言及しません。この後、登場するサンプルプログラムはその動作を理解するのに必要な要素のみ解説していきます。JavaScript言語に抵抗のある方は是非、前述の他の選択肢にあげた製品や技術を吟味してください。

Node.jsのインストール

Node.js公式サイトNode.js 公式のバイナリディストリビューションのページを参考にLTS版(推奨版、長期サポート版)をインストールします。

GCPコンソールから仮想環境(Debian LinuxOS)を立ち上げ、CUI上で2つのコマンドを順に入力します。(リンク先のページと若干異なるコマンドを入力してますが、ここまでの構築手順と同じ流れであれば次のコマンドで通ります。)

 curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo bash -

※ setup_lts.x と sudo の間にあるのは記号のパイプ(縦棒)です。

 sudo apt-get install -y nodejs

※ apt-get install は apt install と同じ意味合いのコマンドです
※ -y は「質問に全てYesで答える」というオプションです

インストールが完了後は次のコマンドを入力して結果を確認します。想定していたバージョン番号が表示されればインストール成功です。筆者環境ではv14.18.1でした。

 node --version

aptを使用してNode.jsをインストールすると過去バージョンのNode.jsがインストールされてしまうようです。v10.24.0などの表記が出た場合は、上記に従って再度インストールを行っていただければと思います。

サンプルプログラム(その1)

では早速ですが、動作確認を行うためのJavaScript言語で記述したサンプルプログラム(JavaScriptファイル)をひとつ用意します。あなたのPCで作成したものをアップロードしても構いませんし、短いのでDebian上のテキストエディタ(nano)を使って作っても問題ありません。

私の場合は、次の手順で作成しました。保存場所にだけ注意しましょう。

  • Debian起動直後の位置で「mkdir test」をコマンド入力し、testフォルダを作成
  • 「cd test」をコマンド入力し、testフォルダへ移動
  • 「nano sample1.js」をコマンド入力し、以下に示す内容を記入後に保存(control+o)
sample1.js
console.log("Hello Node.js!");

このプログラムは、実行環境の指定する標準出力先(コンソール)にlog関数(及び、その引数に記された文字列)でテキストを出力する命令です。

Node.jsを起動する

作成したサンプルプログラムをNode.js環境上で実行します。Node.js環境は次のコマンドで示すように、nodeという文字に続けて事前に準備したJavaScriptファイルを指定すると、そのJavaScriptファイルの実行とともに起動します。実行前に以下の2点について間違いがないか確認しておきましょう。

  1. サンプルプログラムの設置してある場所(目的のフォルダ)にいる
  2. JavaScriptファイルのパス(ファイル名)に間違いがない
node sample1.js

実行に成功すると、CUI上に次の文字が出力されます。

Hello Node.js!

もし文法が間違っていた場合は、SyntaxErrorという表記などが出力されますのでそれらを参考にsample1.jsの内容を修正してください。

HTTP通信

先程のサンプルプログラムは、どちらかというと「演算や記憶をはじめとした様々な処理を行う機能」をNode.js(+JavaScript言語)で実現したものです。では、「クライアントと連携する機能」の部分はどのように実現すれば良いでしょうか?その答えはHTTP通信にあります。

HTTP通信とは

HTTP通信とは、多くのアプリケーションサーバにおいて「クライアントと連携する機能」を実現する手段として採用している技術です。もう少し具体的に説明すると以下のような構造をしています。

  • クライアントからサーバへ向かって「HTTPリクエスト」と呼ばれるデータの集まり(メッセージ)を送信
  • サーバは「HTTPリクエスト」に応じた情報を収集
  • サーバからクライアントに「HTTPレスポンス」と呼ばれるデータの集まり(メッセージ)を送信

クライアントとサーバ間でメッセージを投げ合うことで「連携する」部分を実現しているわけです。

HTTP通信の本質

HTTP通信にまだピンと来ない方は、「Webページを閲覧する」行為を想像してください。これが実はHTTP通信の構造そのものです。

Webブラウザにおける操作 対応するHTTP通信技術上の挙動
リンクをクリックする URLで指定したコンピュータに「HTTPリクエスト」を送信する
Googleで検索する 「HTTPリクエスト」に含まれた検索文字から必要な情報を収集する
Webページが表示される 受信した「HTTPレスポンス」から表示内容が作成された

HTTP通信は本来こういった「Webページを閲覧する仕組み」を実現する技術であったため、HTTP通信の機能を持つサーバは「Webサーバ」とも呼ばれます。

Node.jsのhttpモジュール

HTTP通信を実現するにあたり、サーバ側へ求められることは次の3点です。

  1. HTTPリクエストを通信の出入口にて待ち受ける事
  2. HTTPリクエストの内容を解析し、HTTPレスポンスを生成すること
  3. HTTPレスポンスを通信の出入口から元のクライアントに送信する事

Node.jsでは「httpモジュール」をプログラムへ導入するだけでこれら3要素を実現できます。

サンプルプログラム(その2)

では、「httpモジュール」を導入したサンプルプログラムを以下に紹介します。先程のサンプルプログラムと同様に、あなたのPCで作成したものをアップロードしても構いませんし、短いのでDebian上のテキストエディタ(nano)を使って作っても問題ありません。

sample2.js
var http = require('http');
var server = http.createServer( function(req,res){
 console.log( req.headers );
 res.write("Hello Node.js!");
 res.end();
});
server.listen(55555);

簡単に記載内容について説明します。

require() 関数

引数で指定したモジュールを導入する関数。今回のJavaScriptプログラムでは「httpモジュール」を指し示す文字列httpを記入しています。関数の結果を一旦変数に代入することで、そのモジュールの機能を後になって好きに引き出すことが出来ます。

http.createServer() 関数

クライアントからアクセスがあった時、「引数に指定した関数(アプリケーション)」を実行するサーバを生成する関数です。これがまさにアプリケーションサーバの中核を担う処理です。今回は生成されたサーバを変数名serverとして記録しておきます。

req.headers

req変数はHTTPリクエストのことを指します。req変数の中にリクエスト内容が入っているので、様々な方法で抽出して分析や場合分けに利用します。headersにはHTTPリクエスト内のヘッダーと呼ばれる部分が格納されています。今回はconsole.log関数と組み合わせることでheadersの内容を一覧表示します。

res.write() 関数、res.end() 関数

res変数はHTTPレスポンスのことを指します。レスポンスはhttpモジュールが自動で生成するので、クライアントに送信するデータはこのres変数に載せればOKという寸法です。write関数はHTTPレスポンスのボディと呼ばれる部分にデータを書き込む関数です。今回は単純に"Hello Node.js!"という文字列を指定しています。end関数はNode.jsに「HTTPレスポンスの準備が整ったのでクライアントに送信してください」と指示する関数です。

server.listen() 関数

生成されたサーバに対して引数で指定したポート番号(通信の出入口)でNode.jsに「クライアントからの通信を待ちなさい!」と指示する関数です。サンプルでは55555番と指定しています。(この番号は0~65535の中から指定しますが、0~49151番まではある事情で使用しない方が無難です。詳しくはwikipediaでどうぞ)

さて、それでは実行して・・・と、いきたいところですが事前に行うべき大事な作業が1つ残っています。

仮想環境の設定変更

HTTP通信は当然ネットワーク通信であるため、仮想環境に設定したファイアーウォールの影響を受けます。「通って良い」と設定されていなければ通信内容が仮想環境の出入口で弾かれてしまい、Node.jsで処理することが出来ません。そこで一度GCPコンソールに戻り、ファイアーウォールルールを更新します。

第2回の記事で操作した方法と同様の手段で、「VPCネットワーク」のファイアウォール設定画面まで移動します。続けて、画面上部にある「ファイアウォールルールを作成」をクリックし、以下の設定を施した新ルールを作成します。記載のないルールについては、デフォルトのままで問題ありません。

項目名 設定例 備考
名前 nodejs-allow-http 任意に設定可能
英小文字、数字、ハイフンのみ
ターゲットタグ nodejs-server 任意に設定可能
英小文字、数字、ハイフンのみ
ソースフィルタ IP範囲
ソースIPの範囲 0.0.0.0/0 CIDR表記と呼ばれる数字記号の組み合わせ
プロトコルとポート 指定したプロトコルとポート
tcp:55555
tcp:の右側数値は
listen関数の引数と対応させること

続けて、仮想環境のファイアウォールに作成したルールを適用します。一度GCEの管理画面にまで戻り仮想環境の名前(図では右下にあるmyserverの文字)をクリックします。


GCEの管理画面

「VMインスタンスの詳細」が表示されるので、続けて画面上部にある「編集」の文字をクリックします。


VMインスタンスの詳細画面

「ネットワーク タグ」の項目を探し、先程ファイアウォールルールで作成したターゲットタグの名称を入力します。例ではタグを「nodejs-server」としたので、「ネットワーク タグ」の空欄で「nodejs-server」とキーボードで入力し、Tabキーか押すか欄外をマウスでクリックします。次のように設定されていることを確認し、画面最下部の「保存」ボタンをクリックします。

サンプルプラグラム(その2)の実行と動作確認

それでは最後にNode.js環境を立ち上げて通信を試みます。再度、仮想環境を起動し、次のコマンドを順に入力します。実行する場所やsample2.jsへのファイルパスが間違いないことを確認してください。仮想環境は再起動するとフォルダ移動がリセットされるので、先にcdコマンドでtestフォルダまで移動しています。

 cd test
 node sample2.js

ここまで実行したところでCUI画面ではカーソルが点滅したまま何も起こりません。それもそのはずで、Node.jsは現時点でsample2.jsにてプログラムされたとおりに「HTTPリクエストを通信の出入口にて待ち受ける」状態となっているからです。ここにクライアントからのHTTPリクエストがやってこないので、何も起こらないわけです。

ではここで、あなたの手元のPCを「クライアント」に見立ててHTTPリクエストを送ってみましょう。やり方は簡単です。

  1. Webブラウザ(例:Google Chrome, Microsoft Edge ・・・etc)を立ち上げる
  2. 画面上部のURL欄に、http:// IPアドレス :55555/ と入力してEnterキーで決定する

「IPアドレス」の部分には、GCPコンソールにて仮想環境に設定してある「外部IP」(例:192.168.12.123)の数値、記号を転記します。(画像ではモザイク処理をかけています)


WebブラウザのURL欄に入力をしたところ


HTTPレスポンスが表示されたところ

Webブラウザで「Hello Node.js!」の文字が表示された頃には、仮想環境側で次のような表示が出力されています。


仮想環境側の画面サンプル(一部モザイク)

出力されているものは console.log( req.headers ) の内容になります。

もしも通信が成功しない場合、あなたのネットワーク環境下でtcp:55555の通信が制限されている可能性があります。この場合、以下のように変更してみてください。

  • 仮想環境の「VM インスタンスの詳細」にて「HTTP トラフィックを許可する」にチェックを入れる
  • 仮想環境の「VM インスタンスの詳細」にて「ネットワーク タグ」に「http-server」を追加する
  • sample2.jsの最後尾 server.listen(55555) → server.listen(80)に変更
  • 「node sample2.js」ではなく「sudo node sample2.js」を実行する

HTTP通信以外の選択肢

HTTP通信以外にも「クライアントと連携する機能」を実現する手段は存在します。HTTP通信を採用した場合は、「決まったフォーマットで通信が簡単に行える」一方で「通信速度」が犠牲になりがちです。クライアントとサーバ間で高速な通信が必要とされるオンラインゲームを実現したい場合は、以下に示す用語から検索を行なってみてください。

  • ソケット通信
  • アプリケーションサーバの製品名、技術名
  • 採用したいアプリケーション開発言語

当記事では、引き続きHTTP通信をベースに解説を行なっていきます。

まとめ

Node.jsの環境を利用することでお手軽にアプリケーションサーバを用意することが出来ました。「ソフトウェア1つのインストール」と「たった数行のプログラムが記載されているファイル」を用意しただけでアプリケーションサーバが用意できてしまった事には驚くばかりです。当然、より複雑なことをアプリケーションサーバへ要求する場合はNode.jsとJavaScript言語の深い理解が必要になるかもしれませんが・・・。

さて、次は「データベースサーバを用意する」作業です。(Node.jsも再度登場します)

Discussion