ゲーム開発
Unity
UnrealEngine
C++
Blender
ゲーム数学
ゲームAI
グラフィックス
サウンド
アニメーション
GBDK
制作日記
IT関連
ツール開発
フロントエンド関連
サーバサイド関連
WordPress関連
ソフトウェア設計
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
ラーメン日記
四コマ漫画
その他
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • Blender
    • ゲーム数学
    • ゲームAI
    • グラフィックス
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • IT関連
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • WordPress関連
    • ソフトウェア設計
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • ラーメン日記
    • 四コマ漫画
      • その他
        • おすすめアイテム
        • おもしろコラム
      1. ホーム
      2. 20240415_01_unity_ranking_api

      【Unity】GoでのランキングAPI実装とVPSへのデプロイ方法についてまとめる【Go言語】

      Unityサーバサイド関連GoNCMBVPS
      2024-04-14

      マイケル
      マイケル
      みなさんこんにちは! マイケルです!
      エレキベア
      エレキベア
      こんにちクマ〜〜〜
      マイケル
      マイケル
      これまで制作したいくつかのアプリについて、ニフクラ mobile backend (NCMB) というサービスを使用してランキング処理を実装していたのですが、2024/4よりサービスが終了してしまいました。

      ニフクラ mobile backend - サービス提供終了のご案内

      20240415_01_unity_ranking_api_02
      ▲NCMBを使用したランキング処理 (ゴーゴーゴロヤン)

      20240415_01_unity_ranking_api_03
      ▲NCMBを使用したランキング処理 (エレキシューティング)

      エレキベア
      エレキベア
      長らくお世話になったクマ・・・ おつかれさまクマ・・・
      マイケル
      マイケル
      そういうわけで移行先をどうしようかとしばらく悩んでいたのですが、 ここらでサーバも一台持っておくかと踏み切り、簡易的な自作のランキングAPIを作成することにしました。 実際に作成したAPIと実行サンプルは下記になります。
      20240415_01_unity_ranking_api_01
      ▲ランキング処理のサンプル

      GitHub - unity-go-ranking-sample

      エレキベア
      エレキベア
      APIも自分で作るとはまた思い切ったクマね
      マイケル
      マイケル
      サーバ台はかかるけどドメインを設定しておけばアプリ側の対応が不要になるし このブログのコメント機能など他にもサーバを使用したい用途があったのが決め手ですね・・・
      エレキベア
      エレキベア
      まあ確かに一台持っておくと便利そうクマ
      マイケル
      マイケル
      サーバに関しては今回は VPS を使用することにして、XServer VPSの2GBプランを契約しました! API実装の他、こちらのデプロイ方法についても軽く記載しておきますので、同様にサーバを立てたい方は参考にしてください!

      XServer VPS

      エレキベア
      エレキベア
      値段もそこまで高くないクマし、 固定料金な分、AWSとかと比べて個人開発で使いやすそうクマね

      GoでのランキングAPI実装

      マイケル
      マイケル
      まずはGoで実装したランキングAPIの内容について紹介します。 こちらはリポジトリの下記フォルダに格納してあります。

      GitHub - unity-go-ranking-sample / go-ranking-api

      エレキベア
      エレキベア
      Goは前の記事でも軽く触ったクマね
      【Go言語】Gin、GORMでシンプルなREST APIを作成する【CRUD】
      2024-03-26
      マイケル
      マイケル
      今回のAPIも上記のコードをベースに作成しています。 GoでのAPI作成の基礎知識について知りたい方は上記記事もご参照ください!

      API設計

      マイケル
      マイケル
      まずテーブル定義、API定義に関しては下記のように設定しました。 立ち上げるサーバを最低限で済ませられるよう、アプリIDを渡すことで複数アプリで使用できるようにしています。
      テーブル定義
      mst_apps(アプリ情報マスタ)
      Id
      Name
      ClientKey
      アプリID
      アプリ名
      クライアントキー
      integer
      string
      string
      scores(ランキングスコアテーブル)
      Id
      AppId
      Type
      Name
      Score
      スコアID
      アプリID
      スコア種類
      プレイヤー名
      スコア
      integer
      integer
      integer
      string
      integer
      API定義
      URL
      Method
      Description
      Requests
      ranking/scores
      GET
      ランキング情報取得
      app_id: アプリID
      type: スコア種別
      limit_count: 取得最大数
      order_desc: 値降順で取得するか?
      ranking/scores
      POST
      ランキング情報登録
      ※mst_appsのテーブル定義と同様
      エレキベア
      エレキベア
      最低限の構成って感じクマね scoresテーブルにゲーム内のスコアを登録するクマね

      実装内容

      マイケル
      マイケル
      次に実装内容について見ていきます。
      ルーティング処理
      マイケル
      マイケル
      先ほど載せたルーティングについては、controllerに定義してあります。 リクエストパラメータを受け取った後にDBアクセスする流れで、そちらはrepositoryの方に記述してあります。
      package controller
      
      import (
      	"fmt"
      	"github.com/gin-gonic/gin"
      	"net/http"
      	"strconv"
      	"vps-general-api/repository"
      )
      
      type ScoreController struct{}
      
      func (ctrl ScoreController) Routes() []Route {
      	return Routes{
      		Route{
      			GroupType:   Ranking,
      			MethodType:  GET,
      			Path:        "/scores",
      			HandlerFunc: ctrl.Index,
      		},
      		Route{
      			GroupType:   Ranking,
      			MethodType:  POST,
      			Path:        "/scores",
      			HandlerFunc: ctrl.Create,
      		},
      	}
      }
      
      func (ctrl ScoreController) Index(c *gin.Context) {
      	// リクエストパラメータ取得
      	appId, err := strconv.Atoi(c.Query("app_id"))
      	if err != nil {
      		c.AbortWithStatus(http.StatusBadRequest)
      		fmt.Println(err)
      		return
      	}
      	scoreType, err := strconv.Atoi(c.Query("type"))
      	if err != nil {
      		c.AbortWithStatus(http.StatusBadRequest)
      		fmt.Println(err)
      		return
      	}
      	limitCount, err := strconv.Atoi(c.Query("limit_count"))
      	if err != nil {
      		c.AbortWithStatus(http.StatusBadRequest)
      		fmt.Println(err)
      		return
      	}
      
      ・・・略・・・
      
      	// パラメータからレコードを取得
      	var r repository.ScoreRepository
      	scores, err := r.Get(appId, scoreType, limitCount)
      	if err != nil {
      		c.AbortWithStatus(http.StatusBadRequest)
      		fmt.Println(err)
      	} else {
      		c.JSON(http.StatusOK, gin.H{"scores": scores})
      	}
      }
      
      func (ctrl ScoreController) Create(c *gin.Context) {
      	var r repository.ScoreRepository
      	book, err := r.Create(c)
      	if err != nil {
      		c.AbortWithStatus(http.StatusBadRequest)
      		fmt.Println(err)
      	} else {
      		c.JSON(http.StatusOK, book)
      	}
      }
      
      
      ▲ルーティング定義
      package repository
      
      import (
      	"github.com/gin-gonic/gin"
      	"vps-general-api/model"
      )
      
      type ScoreRepository struct{}
      type Score model.Score
      
      func (r ScoreRepository) GetAll() ([]Score, error) {
      	var scores []Score
      	err := Db.Find(&scores).Error
      	if err != nil {
      		return nil, err
      	}
      	return scores, nil
      }
      
      func (r ScoreRepository) Get(appId int, scoreType int, limitCount int) ([]Score, error) {
      	var scores []Score
      	err := Db.Order("score").Limit(limitCount).Find(&scores, "app_id = ? AND type = ? ", appId, scoreType).Error
      	if err != nil {
      		return nil, err
      	}
      	return scores, nil
      }
      
      func (r ScoreRepository) Create(c *gin.Context) (Score, error) {
      	var score Score
      	err := c.ShouldBindJSON(&score)
      	if err != nil {
      		return score, err
      	}
      	err = Db.Create(&score).Error
      	if err != nil {
      		return score, err
      	}
      	return score, nil
      }
      
      
      ▲DB操作処理
      エレキベア
      エレキベア
      SQL実行くらいで難しいことは特にしていないクマね
      Basic認証処理
      マイケル
      マイケル
      そして認証方式については、今回はBasic認証を使用しています。 簡易的なAPIなのでこの方式を採用しましたが、重大なデータを扱う場合にはよりセキュリティ強度の高い方式を選択したほうがいいかもしれません・・・ また、Basic認証は通信盗聴の恐れがあるので、最低限HTTPSでの通信は必須にしましょう。
      エレキベア
      エレキベア
      Basic認証はユーザID、パスワードの組み合わせを リクエスト時に付与することで認証する方式だったクマね

      Basic認証 - Wikipedia

      マイケル
      マイケル
      今回はアプリ側にユーザ作成は行わないため、代わりにアプリIDとそれに対するトークンをセットにして送信しています。 Ginの機能でBasic認証を設定し、ルーティングに紐づけたRouteGroup単位で設定するようにしました。
      
      ・・・略・・・
      
      type RouteGroupType int
      
      const (
      	None RouteGroupType = iota
      	Ranking
      )
      
      type RouteGroup struct {
      	Path         string
      	AuthAccounts map[string]string
      }
      type RouteGroupMap map[RouteGroupType]RouteGroup
      
      var routeGroupMap RouteGroupMap
      
      // GetRouteGroupMap all controller route groups.
      func GetRouteGroupMap() RouteGroupMap {
      	var r repository.MstAppRepository
      
      	// routeグループと認証情報を定義
      	routeGroupMap = RouteGroupMap{}
      	routeGroupMap[Ranking] = RouteGroup{
      		Path:         "/ranking",
      		AuthAccounts: r.GetRankingAuthAccounts(),
      	}
      	return routeGroupMap
      }
      
      
      ▲ランキング処理のRouteGroup返却
      package server
      
      import (
      	"github.com/gin-gonic/gin"
      	"vps-general-api/controller"
      )
      
      var router *gin.Engine
      
      func Init() {
      	router = CreateRouter(controller.GetAllRoutes(), controller.GetRouteGroupMap())
      }
      
      func CreateRouter(routes controller.Routes, routeGroupMap controller.RouteGroupMap) *gin.Engine {
      	r := gin.Default()
      
      	routerGroup := map[controller.RouteGroupType]*gin.RouterGroup{}
      	for key, value := range routeGroupMap {
      		routerGroup[key] = r.Group(value.Path, gin.BasicAuth(value.AuthAccounts))
      	}
      
      	for _, route := range routes {
      		if group, ok := routerGroup[route.GroupType]; ok {
      			switch route.MethodType {
      			case controller.GET:
      				group.GET(route.Path, route.HandlerFunc)
      			case controller.POST:
      				group.POST(route.Path, route.HandlerFunc)
      			case controller.PUT:
      				group.PUT(route.Path, route.HandlerFunc)
      			case controller.DELETE:
      				group.DELETE(route.Path, route.HandlerFunc)
      			}
      		} else {
      			switch route.MethodType {
      			case controller.GET:
      				r.GET(route.Path, route.HandlerFunc)
      			case controller.POST:
      				r.POST(route.Path, route.HandlerFunc)
      			case controller.PUT:
      				r.PUT(route.Path, route.HandlerFunc)
      			case controller.DELETE:
      				r.DELETE(route.Path, route.HandlerFunc)
      			}
      		}
      
      	}
      	return r
      }
      
      func Listen() {
      	err := router.Run(":8080")
      	if err != nil {
      		panic(err)
      	}
      }
      
      
      ▲GinでのBasic認証登録
      エレキベア
      エレキベア
      gin.BasicAuthで設定しているクマね
      マイケル
      マイケル
      アプリに紐づくトークン情報はmst_appsに設定しているため、repository内で取得して返却しています。 今回はIDの番号によってランキング対象のアプリかどうかを判断するようにしました。
      package repository
      
      import (
      	"strconv"
      	"vps-general-api/model"
      )
      
      type MstAppRepository struct{}
      type MstApp model.MstApp
      
      func (r MstAppRepository) GetRankingAuthAccounts() map[string]string {
      	var apps []MstApp
      
      	// 1000番台がランキング対象アプリ
      	err := Db.Find(&apps, "id BETWEEN ? AND ?", 1000, 1999).Error
      	if err != nil {
      		panic(err)
      	}
      
      	// mapにして返却
      	authAccounts := map[string]string{}
      	for _, app := range apps {
      		key := strconv.FormatUint(uint64(app.Id), 10)
      		authAccounts[key] = app.ClientKey
      	}
      	return authAccounts
      }
      
      
      ▲mst_appからのクライアントキー取得
      
      ・・・略・・・
      
      func (ctrl ScoreController) Index(c *gin.Context) {
      
      ・・・略・・・
      
      	// AppIdが認証キーと一致しているか?
      	authUserId := c.MustGet(gin.AuthUserKey).(string)
      	if authUserId != strconv.Itoa(appId) {
      		c.AbortWithStatus(http.StatusUnauthorized)
      		return
      	}
      
      ・・・略・・・
      
      }
      
      ・・・略・・・
      
      
      ▲アプリ情報との整合性チェック
      エレキベア
      エレキベア
      これで簡易的な認証の実装も完了クマね

      Unity側での実装

      マイケル
      マイケル
      ランキングAPIをUnityで実行するサンプルについても作成しました。 こちらは以前作成したUnityWebRequestのサンプルをベースに修正したものになります。
      20240415_01_unity_ranking_api_01
      ▲ランキング処理のサンプル

      GitHub - unity-go-ranking-sample / unity-ranking-project
      ▲リポジトリ

      【Unity】UnityWebRequestを使ってCRUD機能を実装する【Ruby on Rails】
      2021-12-12
      マイケル
      マイケル
      ランキングAPI実行処理は主にRankingApiSettings、RankingApiServiceになります。 特に特殊なことはしておらず、UnityWebRequestで作成したAPIを叩いているだけです。
      using UnityEngine;
      
      namespace Molegoro.Ranking
      {
          [CreateAssetMenu(fileName = "RankingSettings", menuName = "Molegoro/RankingSettings")]
          public class RankingApiSettings : ScriptableObject
          {
              /// <summary>
              /// ランキングAPI URL
              /// </summary>
              [SerializeField]
              private string _apiUrl = "http://localhost:8080";
              public string ApiUrl => _apiUrl;
      
              /// <summary>
              /// アプリケーションID
              /// </summary>
              [SerializeField]
              private int _appId;
              public int AppId => _appId;
      
              /// <summary>
              /// クライアントキー
              /// </summary>
              [SerializeField]
              private string _appClientKey;
              public string AppClientKey => _appClientKey;
          }
      }
      
      
      ▲ランキング処理設定クラス
      using System;
      
      namespace Ranking
      {
          /// <summary>
          /// ランキングサービス interface
          /// </summary>
          public interface IRankingApiService
          {
              [Serializable]
              public class ScoreSchema
              {
                  /// <summary>
                  /// ID
                  /// </summary>
                  public int Id;
      
                  /// <summary>
                  /// アプリケーションID
                  /// </summary>
                  public int AppId;
      
                  /// <summary>
                  /// スコア種別
                  /// </summary>
                  public int Type;
      
                  /// <summary>
                  /// プレイヤー名
                  /// </summary>
                  public string Name;
      
                  /// <summary>
                  /// スコア
                  /// </summary>
                  public int Score;
              }
      
              [Serializable]
              public class ScoreSchemaArray
              {
                  public ScoreSchema[] scores;
              }
      
              /// <summary>
              /// スコアレコードの取得
              /// </summary>
              /// <param name="type">スコア種別</param>
              /// <param name="limitCount">最大取得数</param>
              /// <param name="isOrderDesc">値降順で取得するか?</param>
              /// <param name="callback">コールバック</param>
              public void GetScores(int type, int limitCount, bool isOrderDesc, Action<bool, ScoreSchema[]> callback);
      
              /// <summary>
              /// スコアレコードの追加
              /// </summary>
              /// <param name="type">スコア種別</param>
              /// <param name="name">プレイヤー名</param>
              /// <param name="score">スコア</param>
              /// <param name="callback">コールバック</param>
              public void AddScore(int type, string name, int score, Action<bool, string> callback = null);
          }
      }
      
      ▲ランキング処理サービスインターフェイス
      using System;
      using System.Collections;
      using System.Text;
      using UnityEngine;
      using UnityEngine.Networking;
      
      namespace Molegoro.Ranking
      {
          /// <summary>
          /// ランキングサービスクラス
          /// </summary>
          public class RankingApiApiService : IRankingApiService
          {
              /// <summary>
              /// ランキング設定
              /// </summary>
              private readonly RankingApiSettings _rankingApiSettings;
      
              /// <summary>
              /// MonoBehaviour(コルーチン実行用)
              /// </summary>
              private readonly MonoBehaviour _monoBehaviour;
      
              /// <summary>
              /// ランキングAPI URL
              /// </summary>
              private string ApiUrl => $"{_rankingApiSettings.ApiUrl}/ranking";
      
              /// <summary>
              /// アプリケーションID
              /// </summary>
              private int AppId => _rankingApiSettings.AppId;
      
              /// <summary>
              /// アプリケーションクライアントキー
              /// </summary>
              private string AppClientKey => _rankingApiSettings.AppClientKey;
      
              /// <summary>
              /// コンストラクタ
              /// </summary>
              /// <param name="rankingApiSettings">ランキング設定</param>
              public RankingApiApiService(RankingApiSettings rankingApiSettings)
              {
                  _rankingApiSettings = rankingApiSettings;
      
                  // MonoBehaviourハンドラの生成
                  var monoBehaviourHandlerObject = new GameObject(nameof(RankingApiMonoBehaviour));
                  var monoBehaviourHandler = monoBehaviourHandlerObject.AddComponent<RankingApiMonoBehaviour>();
                  UnityEngine.Object.DontDestroyOnLoad(monoBehaviourHandlerObject);
                  _monoBehaviour = monoBehaviourHandler;
              }
      
              /// <summary>
              /// スコアレコードの取得
              /// </summary>
              /// <param name="type">スコア種別</param>
              /// <param name="limitCount">最大取得数</param>
              /// <param name="callback">コールバック</param>
              public void GetScores(int type, int limitCount, Action<bool, IRankingApiService.ScoreSchema[]> callback)
              {
                  var queryString = System.Web.HttpUtility.ParseQueryString("");
                  queryString.Add("app_id", AppId.ToString());
                  queryString.Add("type", type.ToString());
                  queryString.Add("limit_count", limitCount.ToString());
      
                  var requestUrl = $"{ApiUrl}/scores";
                  var uriBuilder = new System.UriBuilder(requestUrl)
                  {
                      Query = queryString.ToString()
                  };
      
                  var request = UnityWebRequest.Get(uriBuilder.Uri);
                  _monoBehaviour.StartCoroutine(SendRequestCoroutine(request, (isSuccess, response) =>
                  {
                      if (isSuccess)
                      {
                          var schema = JsonUtility.FromJson<IRankingApiService.ScoreSchemaArray>(response);
                          callback.Invoke(true, schema.scores);
                      }
                      else
                      {
                          callback?.Invoke(false, null);
                      }
                  }));
              }
      
              /// <summary>
              /// スコアレコードの追加
              /// </summary>
              /// <param name="type">スコア種別</param>
              /// <param name="name">プレイヤー名</param>
              /// <param name="score">スコア</param>
              /// <param name="callback">コールバック</param>
              public void AddScore(int type, string name, int score, Action<bool, string> callback = null)
              {
                  var schema = new IRankingApiService.ScoreSchema()
                  {
                      AppId = AppId,
                      Type = type,
                      Name = name,
                      Score = score,
                  };
                  var json = JsonUtility.ToJson(schema);
                  var bodyRaw = Encoding.UTF8.GetBytes(json);
      
                  var requestUrl = $"{ApiUrl}/scores";
                  var request = new UnityWebRequest(requestUrl, "POST");
                  request.uploadHandler = new UploadHandlerRaw(bodyRaw);
                  request.downloadHandler = new DownloadHandlerBuffer();
                  request.SetRequestHeader("Content-Type", "application/json");
      
                  _monoBehaviour.StartCoroutine(SendRequestCoroutine(request, (isSuccess, response) =>
                  {
                      callback?.Invoke(isSuccess, response);
                  }));
              }
      
              /// <summary>
              /// リクエスト送信
              /// </summary>
              /// <param name="request">リクエスト</param>
              /// <param name="callback">コールバック</param>
              private IEnumerator SendRequestCoroutine(UnityWebRequest request, Action<bool, string> callback = null)
              {
                  request.SetRequestHeader("Authorization", GetBasicAuthToken());
                  yield return request.SendWebRequest();
      
                  if (request.result == UnityWebRequest.Result.ConnectionError
                      || request.result == UnityWebRequest.Result.ProtocolError)
                  {
                      callback?.Invoke(false, request.error);
                      yield break;
                  }
      
                  callback?.Invoke(true, request.downloadHandler?.text);
              }
      
              /// <summary>
              /// Basic認証トークン取得
              /// </summary>
              /// <returns></returns>
              private string GetBasicAuthToken() {
                  var auth = AppId + ":" + AppClientKey;
                  auth = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(auth));
                  auth = "Basic " + auth;
                  return auth;
              }
          }
      
          /// <summary>
          /// MonoBehaviour実行用
          /// </summary>
          public class RankingApiMonoBehaviour : MonoBehaviour
          {
          }
      }
      
      
      ▲ランキング処理サービスクラス
      エレキベア
      エレキベア
      アプリID、トークンを設定した設定ファイルをサービスに渡して実行するクマね 一般的なAPIリクエストクマ〜〜
      マイケル
      マイケル
      これでAPIとの繋ぎ込みまで確認することができました。

      XServerVPSへのデプロイ

      マイケル
      マイケル
      最後におまけにはなりますが、作成したAPIをVPSで起動するまでの手順についても紹介します。

      VPS構成

      マイケル
      マイケル
      今回、VPSは下記構成のものを契約しました。 XServerはスケールアップ・ダウンが可能なため、もしスペックが足りなくなったら拡充する戦法です。
      種類
      概要
      VPS
      XServer VPS
      CPU
      3Core
      メモリ
      2GB
      SSD
      50GB
      イメージタイプ
      Ubuntu 22.04
      エレキベア
      エレキベア
      費用も最低限に抑えたいクマからね〜〜

      Docker環境構築

      マイケル
      マイケル
      Docker環境の構築についてですが、イメージタイプはUbuntuを使用しているため 一般的なUbuntuのDocker環境構築手順と同様の手順になります。

      Docker - Install using the apt repository

      # Add Docker's official GPG key:
      sudo apt-get update
      sudo apt-get install ca-certificates curl
      sudo install -m 0755 -d /etc/apt/keyrings
      sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
      sudo chmod a+r /etc/apt/keyrings/docker.asc
      
      # Add the repository to Apt sources:
      echo \
        "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
        $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
        sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
      sudo apt-get update
      
      ▲Docker環境のインストール
      # Git Install
      sudo apt-get install git-all
      
      # Git Settings
      git config --global user.name [name]
      git config --global user.email [email]
      
      # リポジトリのクローン
      cd [workspace dir]
      git clone [repository]
      
      # Dockerの立ち上げ
      cd [repository dir]
      docker compose up -d
      
      ▲Gitのインストール
      エレキベア
      エレキベア
      Ubuntu上にDockerを導入してリポジトリをクローンする流れクマね

      ドメイン設定

      マイケル
      マイケル
      次にドメインの設定についてです。 こちらはVPSのサーバ名を直接指定してもよいですが、今後の移行のしやすさも考えて独自ドメインを取得・設定しておくことにしました。
      エレキベア
      エレキベア
      アプリから呼び出すとなると再提出も面倒クマからね・・・
      ドメインサービス側の設定
      マイケル
      マイケル
      まずはドメインの取得とVPSとの紐付けについてです。 今回は お名前com で取得したので、下記のようにドメイン側のネームサーバ設定にVPS指定のものを設定します。

      お名前.com - 公式ページ

      XServer VPS - ネームサーバについて

      20240415_01_unity_ranking_api_04
      ▲VPSのネームサーバを設定

      エレキベア
      エレキベア
      ドメインサーバ側で認識するための設定クマね
      VPS側の設定
      マイケル
      マイケル
      次にVPS側で独自ドメインの追加を行います。 デフォルトで設定されるNSレコードの他、Aレコード、CNAMEレコードを設定します。
      ホスト名
      種別
      内容
      [独自ドメイン]
      A
      [VPSのIPアドレス]
      www.[独自ドメイン]
      CNAME
      [独自ドメイン]
      20240415_01_unity_ranking_api_05
      ▲VPS側でNSレコード、Aレコードを設定する

      マイケル
      マイケル
      この状態でVPS側のポートを解放してURLにアクセスすると Ubuntuのトップ画面が表示されるようになっているはずです。
      20240415_01_unity_ranking_api_07
      ▲VSP側で必要なポートを空けておく

      20240415_01_unity_ranking_api_08
      ▲この状態で独自ドメインにアクセスするとApacheの画面が表示される

      エレキベア
      エレキベア
      これでドメインとの紐付けが完了したクマね
      apacheの独自設定
      マイケル
      マイケル
      基本的にはこれで使用できるようになったのですが、今回は費用を抑えるために一つのサーバ内でドメインやポートを振り分けれるようにします。 そのためApache設定でVirutualHostを使用して設定します。
      エレキベア
      エレキベア
      メモリ容量は心配クマが、複数アプリ立ち上げることも可能にするクマね
      マイケル
      マイケル
      UbuntuのApacheフォルダは/etc/apache2になります。 その中のapache2.confがルートになっていて、ここから各設定を読み込んでいます。
      # Include list of ports to listen on
      Include ports.conf
      
      ・・・中略・・・
      
      # Include generic snippets of statements
      IncludeOptional conf-enabled/*.conf
      
      # Include the virtual host configurations:
      IncludeOptional sites-enabled/*.conf
      
      ▲apache.conf内で各設定を読み込んでいる
      マイケル
      マイケル
      デフォルトで用意されているsites-enabled/000-default.confをコピーして独自設定ファイル(今回はvhosts.conf)を作成し、下記のようにリバースプロキシでアクセスするようにします。
      cd /etc/apache2/
      sudo cp sites-enabled/000-default.conf sites-enabled/vhosts.conf
      sudo vim sites-enabled/vhosts.conf
      
      ▲000-default.confをコピーしてvhosts.confを作成
      <VirtualHost [独自ドメイン]:80>
      	ServerName [独自ドメイン]
      	DocumentRoot /var/www/[独自ドメイン]/public
      	ErrorLog ${APACHE_LOG_DIR}/error.log
      	CustomLog ${APACHE_LOG_DIR}/access.log combined
      
      	ProxyPass        /api http://localhost:8080
      	ProxyPassReverse /api http://localhost:8080
      </VirtualHost>
      
      
      ▲8080ポートでの接続設定
      エレキベア
      エレキベア
      独自ドメイン/apiでアクセスされた時に自身の8080ポートに接続しているクマね
      マイケル
      マイケル
      あとはproxyモジュールをインストールしてApacheを再起動すれば完了です!
      # proxyモジュールインストール
      sudo a2enmod proxy_http
      
      # サービス再起動
      sudo systemctl restart apache2.service
      
      # サービスの状態確認
      sudo systemctl status apache2.service
      
      ▲proxyモジュールの導入
      マイケル
      マイケル
      http:[独自ドメイン]/apiのURLでアクセスすると、localhostの時と同様にAPIアクセスできるようになっていると思います。
      エレキベア
      エレキベア
      HTTP経由でのアクセスはこれでクリアクマね

      SSL設定

      マイケル
      マイケル
      最後に、HTTPS経由でアクセスできるよう証明書とApacheの設定を行います。
      証明書のインストール
      マイケル
      マイケル
      VPS上での証明書インストール手順については公式がまとめてくれています。 今回はpython3-certbot-apacheを使用して証明書を作成しcronで更新処理を設定しました。

      XServer VPS - 証明書のインストール手順

      # certbotのインストール
      apt update -y
      apt install certbot python3-certbot-apache
      
      # ドメインの設定
      certbot --apache -d [独自ドメイン]
      
      # 作成された証明書の確認
       ls /etc/letsencrypt/live
      
      ▲certbotのインストール
      # nanoエディタでcron処理を開く
      nano /etc/cron.d/letsencrypt-renew
      
      # 下記内容を記述(毎月1日に更新するcron処理)
      0 0 1 * * root certbot renew --pre-hook "service apache2 stop" --post-hook "service apache2 start"
      
      ▲証明書更新のcron設定
      エレキベア
      エレキベア
      証明書は一ヶ月で切れるクマから定期的な更新処理が必要クマね
      apacheの設定
      マイケル
      マイケル
      次にApache側の設定も変更します。 下記サイトを参考にさせていただきながら設定しました。

      Apacheのリバースプロキシをhttps化する手順について - Rainbow Engine

      # rewrite, ssl の有効化
      a2enmod rewrite
      a2enmod ssl
      
      ▲有効にした時点でvhosts.confを元にvhosts-le-ssl.confが生成される
      <IfModule mod_ssl.c>
      <VirtualHost [独自ドメイン]:443>
      	ServerName [独自ドメイン]
      	DocumentRoot /var/www/[独自ドメイン]/public
      	ErrorLog ${APACHE_LOG_DIR}/error.log
      	CustomLog ${APACHE_LOG_DIR}/access.log combined
      
      	ProxyPass        /api http://localhost:8080
      	ProxyPassReverse /api http://localhost:8080
      
      	# SSL settings
      	SSLEngine On
      	SSLProxyEngine On
      	SSLCertificateFile     /etc/letsencrypt/live/[独自ドメイン]/fullchain.pem
      	SSLCertificateKeyFile  /etc/letsencrypt/live/[独自ドメイン]/privkey.pem
      
      	Include /etc/letsencrypt/options-ssl-apache.conf
      </VirtualHost>
      </IfModule>
      
      
      ▲HTTPS接続設定
      エレキベア
      エレキベア
      作成した証明書を指定するクマね
      マイケル
      マイケル
      今回はBasic認証を使用してるので、HTTP経由でのアクセスはできないように対応します。 最初に作成したvhosts.confの内容はコメントアウトしましょう。
      マイケル
      マイケル
      最後に確認として、HTTPS経由でのみアクセスできることを確認できれば、設定は完了です!!
      エレキベア
      エレキベア
      長かったクマ〜〜〜

      おわりに

      マイケル
      マイケル
      というわけで今回はランキングAPIの作成とVPSへのデプロイについてでした! どうだったかな??
      エレキベア
      エレキベア
      サーバ構築は難しそうなイメージだったクマが、 これくらいなら個人でも全然立てれるクマね
      マイケル
      マイケル
      自分だけのサーバ環境が出来たからこれからいろいろ遊べそうだね! 今回は用途が限られていたので簡易的な仕組みで構築しましたが、がっつりサーバエンジニアを目指したい方は認証周りももう少し踏み込んで挑戦してみてください!
      マイケル
      マイケル
      それでは今日はこの辺で! アデューー!!
      エレキベア
      エレキベア
      クマ〜〜〜〜〜

      【Unity】GoでのランキングAPI実装とVPSへのデプロイ方法についてまとめる【Go言語】〜完〜


      Unityサーバサイド関連GoNCMBVPS
      2024-04-14

      関連記事
      【Unity】Timeline × Excelでスライドショーを効率よく制作する
      2024-10-31
      【Unity】Boidsアルゴリズムを用いて魚の群集シミュレーションを実装する
      2024-05-28
      【Unity】第二回 Wwiseを使用したサウンド制御 〜インタラクティブミュージック編〜
      2024-03-30
      【Unity】第一回 Wwiseを使用したサウンド制御 〜基本動作編〜
      2024-03-30
      【Unity】第二回 CRI ADXを使用したサウンド制御 〜インタラクティブミュージック編〜
      2024-03-28
      【Unity】第一回 CRI ADXを使用したサウンド制御 〜基本動作、周波数解析編〜
      2024-03-28
      【Unity】サウンドミドルウェアに依存しない設計を考える【CRI ADX・Wwise】
      2024-03-27
      【Go言語】Gin、GORMでシンプルなREST APIを作成する【CRUD】
      2024-03-26