菅野 明洋

第4回 Game Gatling LTに登壇してきました!


はじめに

初めての人は初めまして、前回の記事(社内でエンジニア読書会をやってみた!)見てくれてる人こんにちは!
大阪スタジオ インフラチームの菅野明洋です。
業務では、大阪スタジオのサービスインフラを担当させていただいております。

今回は、2017年12月9日の土曜日に大阪で開催されました第4回 Game Gatling LTにて、スピーカーとして私と藤井が登壇させていただきましたので、レポートとしてまとめました。

GGLTとは

関西でゲーム開発に携わるエンジニア、デザイナー、プランナー等の人が集まり、ライトニングトークを行うイベントになります。

今回発表させていただいた内容について

今回の発表は、ミドルウェアの性能試験などの話になります。

■菅野明洋:我流ミドルウェア性能・障害試験の心得

■藤井章暢:仕事以外でプログラミングのモチベを上げる方法

私の発表ではミドルウェアの性能試験を中心に発表しました。今回は後進向けの教育を想定した作りにしております。
プロジェクトでミドルウェアを選定する場合、枯れていないミドルウェアを用いると情報が少ないケースが非常に多いと思います。その中で、少しでも多くのノウハウを積むための考えみたいな物をまとめてみました。ミドルウェアを適切に選定や使用できなかった場合は単純に開発・検証する以上のコストがかかるケースが多いため、きちんと精査していきたいと思います。

藤井の発表では、業務外でのプログラミング活動についての話を発表しました。
日々の情報収集などをどのように行うか、通勤途中の時間や休日をどう活かすかなどの実例の紹介になります。業務上で知識を得るのも重要ですが、課外活動や日々の行動改善で知見を得るのも重要かなと思います。
詳しくはスライドをご覧ください。

■発表中の様子

発表中の菅野

発表中の藤井

感想

今回は、登壇だけでは無く運営の参加にも挑戦してみました。
業務を行いながら並行で準備するのは大変でしたが、勉強会の裏側が見れて良い経験ができたと思います。

また、登壇については、やや時間配分を間違えて残りのページを話しきれなかったのが残念でした。次回はそのようなミスが無いようにしていきたいと思います。

最後に

GGLT運営の皆様、会場に来場した方々、登壇の機会を頂きありがとうございました。色々と面白い活動の話や、明日から実践してみようと思えるような話等が聞けて良かったです。
今後も皆様とこのような場で情報交換できると幸いです。


oguma

Aimingインフラチームを支え(てくれてい)る技術と設計(3/3)


この記事を読むのに使用する時間の目安 = 5〜10分

你好!
Magandang hapon po!
こんにちわ! インフラチームの 小熊 です。

前回 から大分時間が経ってしまいましたが、Aimingのインフラ事情をいくつか紹介したいと思います。

TL;DR

必要だと思える時にTDIでインフラ構築をするようにしています。
また、インフラのテストもCIで回すようにしています。
普段はGithubで、その結果とReview commentを見てmergeといった流れになります。
とくに新規性のある内容はありませんが、誰かの何かのヒントになればということで、Tipsなども少し載せます。

本題

TDI

TDI (Test driven infrastructure) = テスト駆動インフラストラクチャ
を指します。

インフラの「品質管理, 事故防止, 安定したリリース管理」を行うため
定義したインフラが適切に動作するかをテストしながら構築していく手法になります。

その考え方自体は古くからあり、以下のようなサイトが参考になります

前回紹介したとおり、弊社ではServerspecを使用しています。

AnsibleやKubernetes など、もともとインフラを定義するもので、さらに重ねてインフラを定義してテストを行う必要はあるのか? という議論もあると思いますが、以下の条件の際は出来る限りテストを書くようにしています。

  • private関数, local変数 のような scopeが限定的なものは基本的にテストしないが、逆にscopeが広い場合
    • Ansibleは基本的にglobal scopeになります
  • roleなどの汎用性を高くしていて組み合わせて利用している上で、それぞれのroleで依存関係もある場合
  • 全てのエラーで停止するわけではなく、楽観的なエラーハンドリングが存在する場合
    • e.g,) Ansibleだと ignore_errors, failed_whenを使っている箇所がある
  • 複数のtoolや環境を組み合わせていて、統合的なテストがしたい場合
    • e.g,) Ansible on the Vagrant, Containers + VM Instances

他にも、重要な箇所で安心感を得るために書いた方が良いときは書くようにしていて、Jenkins等のCIでテストを回すようにしています。

pipelineで全ての環境変数による設定のテストを行い、それで問題を発見した場合はmerge前にfix commit を入れるようにしています。

Serverspec Tips

基本

以下を参考にroleで分けるようにしておくと扱いやすいです。
http://serverspec.org/advanced_tips.html#how-to-use-host-specific-properties

また、一般的ではありますが環境変数で制御するようにすると汎用的に扱えて良いです。
例えば、Jenkins によるテストの場合はどうするとか、タイトルにより特別な設定があるといったこともテストができるようにした方が良いと思います。

Rakefile サンプルコード

require 'rake'
require 'rspec/core/rake_task'
require 'yaml'
require 'highline/import'
require 'json'

unless ENV['ANSIBLE']
ENV['TARGET_HOST_YML'] = ask("Enter servers.yml path (spec/env/servers.yml): ")\
  if ENV['JENKINS_SERVER_COOKIE'].nil?
end
ENV['TARGET_HOST_YML'] = 'spec/env/servers.yml'\
  if ENV['TARGET_HOST_YML'].nil? || ENV['TARGET_HOST_YML'].empty?

desc "Run serverspec to all hosts"
task :spec => 'serverspec:all'

namespace :serverspec do
  properties = YAML.load_file(ENV['TARGET_HOST_YML'])
  task :all => properties.keys.map {|key| 'serverspec:' + key.split('.')[0] }

  properties.keys.each do |key|
    msg_for_test="on Vagrant" if "#{key}" === "wordpress"
    desc "Run serverspec to #{key} #{msg_for_test}"

    RSpec::Core::RakeTask.new(key.to_sym) do |t|

      # 環境変数チェック #########

      # Setされているか検証したい環境変数
      verify_env_vars = %w[PJCODE TARGET_ENV SERVER_NAME]

      # verify_env_vars の環境変数 を使用していないServerspec roles = 環境変数Set有無の検証をしたくないrole
      no_verify_roles = %w[vuls] # このroleは上記の環境変数は使っていないから存在チェックもしない

      properties[key]['roles'].each do |role|
        no_verify_roles.each do |no_verify_role|
          if !role.include?(no_verify_role)
            verify_env_vars.each { |env_var| raise NameError, "環境変数 #{env_var} がセットされていません" if ENV[env_var].nil? }
          end
        end
      end

      ENV['TARGET_HOST'] = key
      t.pattern = 'spec/{' + properties[key]['roles'].join(',') + '}/*_spec.rb'
    end
  end

  properties.values.each do |role|
    json = JSON.load(role.to_json)
    ENV['SERVERSPEC_ROLES'] = json['roles'].to_s
  end
end

Serverspecで、あいまい(範囲)判定がしたいとき

厳密性が必要なく、あいまいな判定にしておきたいときもあると思います。

たとえば、サーバのSpecで自動的に設定される値をテストしたい場合
(e.g,  リソースが異なるサーバが数種類あるけど、計算上は全てこの範囲に収まっていれば問題ない。など)

そのようなときは以下のようにすると判定ができます。

describe command("awk '\$1~/start_servers/{ print \$3}' /etc/php-fpm.d/#{ENV['PJCODE']}.conf") do
  its('stdout.to_i') { should be_within(14).of(20) }
end

この場合、14が差分になります。
20に対して、+-14 以内が許容される という意味になります。

上記の例ですと、
下が6 までok, 上が 34 まで ok
となります。

mariadbのinnodb_buffer_pool_size を自動的に設定している場合も範囲で判定しています。

mariadb10のspec sample

# spec/mariadb_10/variables_spec.rb
require 'spec_helper'

describe command("awk '\$1~/innodb_buffer_pool_size/{ print \$3}' /etc/my.cnf") do
  its('stdout.to_i') { should_not be_within(99.9).of(100) }
end

上記の例は、should_not判定なので
俺の innodb_buffer_pool_size が 0.1 から199.9 の範囲に収まるわけはない
もっと多く割当たるはずだ
という判定になります。

ちなみに上記で、
innodb_buffer_pool_size         = 2000MB
など、MBなどの単位が入っていても問題なく判定できます。

https://ja.stackoverflow.com/questions/23703/serverspec-%E3%81%A7-%E7%AF%84%E5%9B%B2%E6%8C%87%E5%AE%9A%E3%82%92%E3%81%97%E3%81%9F%E3%81%84-rspec-%E3%81%A7%E3%81%84%E3%81%86-be-within-of-%E3%82%84-start-with

その他のテストツール: infrataster

インフラ面から見てエンドツーエンドのテストを簡単に行えるように出来るツールです。
CapybaraやSelenium のようなインフラ用のツールです。
deploy後のチェックに使ったりします。

こちらも割と多くの記事が出ていますので
細かい説明や手順は載せません。

この記事は、要所だけということで。

infratasterの設定について

通常は以下のように設定します。

Infrataster::Server.define(
  # Name of the server, this will be used in the spec files.
  :proxy,
  # IP address of the server
  '192.168.0.0/16',
  # If the server is provided by vagrant and this option is true,
  # SSH configuration to connect to this server is got from `vagrant ssh-config` command automatically.
  vagrant: true,
)

これを汎用的に使いたい場合は、同じく環境変数でハンドリングするようにしますが、設定情報はyamlでまとめておいた方が楽なので、以例えば、以下の様に環境変数 SERVER_NAME にSETするようにして、該当するYAML内の残りの設定を読み込ませるようにすると良いと思います。

spec/spec_helper.rb の例

# spec/spec_helper.rb
require 'infrataster/rspec'
require 'highline/import'
require 'yaml'

raise "Error: 環境変数 SERVER_NAME が入っていません\n\
`コマンドラインからecho $SERVER_NAME` で確認の上、`export SERVER_NAME=xxx を実行してください`\n\
e.g.) export SERVER_NAME=wordpress  # for vagrant"\
  if ENV['SERVER_NAME'].nil? || ENV['SERVER_NAME'].empty?

# Jenkinsのテストでは対話式にしない
ENV['TARGET_HOST_YML'] = ask("Enter servers.yml path (spec/env/servers.yml): ")\
  if ENV['JENKINS_SERVER_COOKIE'].nil?

ENV['TARGET_HOST_YML'] = 'spec/env/servers.yml'\
  if ENV['TARGET_HOST_YML'].nil? || ENV['TARGET_HOST_YML'].empty?

@properties = YAML.load_file(ENV['TARGET_HOST_YML'])

Infrataster::Server.define(:"#{ENV['SERVER_NAME']}") do |server|
  server.address = @properties[ENV['SERVER_NAME']]['address']
  server.vagrant = @properties[ENV['SERVER_NAME']]['vagrant']
end

spec/env/servers.yml の例

# For infrataster

example.com:
  address: '192.168.1.1/24'
  vagrant: false

# { Vagrant
wordpress:
  address: 'wordpress'
  vagrant: true

# } End of Vagrant

例えばこれで、
SERVER_NAME=wordpress bundle exec rspec
など。

こうしておくことで汎用的かつ、特定の対象をテストしやすくなります。

もちろんspec側でもその環境変数は有効に利用できます。

require 'spec_helper'

_uri = "http://#{ENV['SERVER_NAME']}/"

describe server(:"#{ENV['SERVER_NAME']}")do
  describe http(_uri) do
    it "reponse status" do
      expect(response.status).to eq(301)
    end

    it "responds with content inflated automatically" do
      expect(response.headers['content-encoding']).to be_nil
    end
  end

  describe capybara(_uri) do
    it 'shows welcome page' do
      visit '/'
      expect(page).to have_content 'WordPress へようこそ'
    end

    it 'shows sample page' do
      visit '/?page_id=2'
      expect(page).to have_content '新しく WordPress ユーザーになった方は、ダッシュボードへ行ってこのページを削除し、独自のコンテンツを含む新しいページ
  作成してください。'
    end

    it 'shows unclassified page from sample page' do
      visit '/'
      click_on '未分類'
      expect(page).to have_content 'カテゴリー: 未分類'
    end
  end
end

各ツール共通で使える値は、共通で扱える環境変数名にしておくとintegrationしやすいです。

e.g,)
export SERVER_NAME=”wordpress”
docker, k8s, ansible, serverspec, infrataster でも同じ環境変数を利用

Security check

vuls や専用ツールで定期的にチェックを入れています。

Kubernetes

最近、弊社でもKubernetesを扱うようになってきました。

Kubernetes-helmで YAML生成するのも良いと思うのですが、以下のようなsnippetツールを見つけたので、紹介させていただきます。

https://github.com/ipedrazas/kubernetes-snippets

少しは楽できるかな?と思い、使い始めたところです。

さいごに

誰かにとって少しでもお役に立てれば幸いです。
Aimingではインフラエンジニアも募集しております。
ご興味のある方がいましたら、ぜひご応募ください。


吉田 正広

ルームの機能をモジュール化して、もうこれ以上コピペしなくて済むようにした話


はじめまして!

東京スタジオでエンジニアをしている、吉田と申します。

リリース後も、アップデートを繰り返していくオンラインゲーム。
安定したサービスを提供しつつ、常に新しい遊びを創造していくというのは、なかなか難儀なものです。

アップデートで機能追加をする度に、プログラマを悩ませるのが、

  • 安全のために、動作実績のある既存のプログラムはできるだけ変更したくない。
  • でも、コードの保守性や可読性のために、共通機能を関数化/クラス化するなどのリファクタもしたい。

こんなジレンマ。エンジニアのあなたも、身に覚えがありませんか?
それとも、「動いているコードは触ってはいけない」というルールを頑なに守って、リファクタを避けてはいないでしょうか?

でもそれだと、既存の仕組みに縛られて、新しい機能の実装が難しくなるし、アップデートと共に少しずつ大きくなって、今やなんの機能なのかわからないクラスなどがあると、バグの対応にも一苦労ですよね。

その上担当者も変わったりして、もう誰も中身を把握していない・・・、なんて状態になる前に、上手にリファクタしつつ、安全性も保ちながら、コードを保守していきたいものです。

先日4周年を迎えた「幻塔戦記グリフォン」(以下グリフォン)では、C++で書かれた、リアルタイム通信機能を担うサーバーがあります。
やはり4年もたつと、古いソースも多々あり、上記のような問題に常に直面しています。
今回は、新しい機能を実装するにあたって、私が行った取り組みについて紹介します。

現状のソース

グリフォンでは、いろんなタイプのクエストやバトルがあり、それぞれがクラスで表現されています。

CRoomBase          -共通で使う基本的な処理
  +- CQuestRoom         -シナリオなどのクエスト
  +- CPvPRoom           -定期的に開催されるPVP対戦
  +- CMockBattleRoom    -いつでもバトルが試せる、模擬戦
  +- CBattleRoyalRoom   -バトルロイヤル
  +- CColosseumRoom     -コロシアム

+- は継承を意味します。

CRoomBaseには、共通機能(主にゲーム内のオブジェクトの管理)が入っています。

それぞれの子クラスには、大まかに

  1. 入室までの処理
  2. ゲーム開始までの処理
  3. ゲームのルール処理
  4. ゲーム戦績の保存
  5. ゲーム終了後の処理

こんな機能が実装されています。

現状のソースコードを眺めてみると、こんな状態になっていました。

  • CPvPRoomCMockBattleRoomは、入室してゲーム開始するまでの流れが違うが、ゲームルールは一緒
  • CBattleRoyalRoomは、CMockBattleRoomをコピペし、一部機能を修正して作ったっぽい
  • その後、CMockBattleRoomにも独自の機能が追加されてるっぽい
  • その他、同じ様でちょっと違う処理が随所にみられる

だんだんつらくなってきた・・・もう既存のソースは見たくない・・ってなりました。
エンジニアの方なら、こんな気持ちをわかってくれるはず!!

そして今回

新しいゲームを提供するため、新たにルームクラスを作ることになりました。(仮にCNewBattleRoomとします)
その要件は、

  • 入室&ゲームスタートまでの流れはCBattleRoyalRoomとだいたい一緒
  • ゲームルール、戦績保存は、CQuestRoomとだいたい一緒
  • 加えて、今回初めて実装する機能

さてどうしたものか・・・
まず簡単に思いつく方法は、

  1. CBattleRoyalRoomをコピーして、CNewBattleRoomを作る。
  2. できたCNewBattleRoomのうち、いるものだけ残していらない物は削除する(もしくは使わないなら放置)
  3. CQuestRoomの一部機能をコピーしてCNewBattleRoomに入れる
  4. あと、足りない機能を入れる

こんな感じでしょうか。またコピペが増える・・・・
このアップデートをもって、サービス終了! ならこれでいいのですが、グリフォンはまだまだ続くのです・・!!

じゃあ、共通の機能を1つにまとめる? でも、共通化できそうな機能も、それぞれのルームで微妙に違ってたりするし(どうしてこうなったのか、もう分からないし)
これもつらいのです。

—-そして悩んだ結果、
よし、今回だけは、コピペを許そう。ただし、今後はコピペしないで済むようにする。

と決めました。

そのために、各機能をモジュール化して、そのモジュールたちを付け替える事で、ルームを表現できるようにします。

新しいクラス構成は

CRoomBase               -共通で使う基本的な処理
  +- CModularRoomBase        -各モジュールを乗せるためのベースクラス
      +- CNewBattleModule    -新しいルーム特有の機能を実装したモジュール
      +- CPvPCommonModule    -対戦共通モジュール
      +- CCommonModule       -共通モジュール

今回だけは、既存ソースからコピペして、各モジュールを作ります。

そして、モジュールを組み合わせたルームを生成するためのテンプレートクラスを

template <typename... Modules>
class CModularRoom : public virtual CModularRoomBase, public Modules...
{
...
};

こんな風に定義します。

例えば、Common,PvPCommon,NewBattleの3つの機能を実装した CNewBattleRoom を作成するには、

typedef CModularRoom<
    CCommonModule,           //テンプレート引数に、入れたいモジュールを羅列する
    CPvPCommonModule,
    CNewBattleModule>
        CNewBattleRoom;

こんな感じです。

こうしておけば、例えば今後、新しいルームクラス CHogeBattleRoom を作るときには、CHogeBattleModuleを作り、CNewBattleModuleと差し替えて、

typedef CModularRoom<
    CCommonModule,
    CPvPCommonModule,
    CHogeBattleModule>       //←ここだけ差し替える
        CHogeBattleRoom;

とすればいい訳ですね。

もし、CHogeBattleRoomを作るなかで、「CNewBattleRoomのこの部分は共通化したい」となった場合は、新しい CFugaCommonModule を作って、機能を分離すれば良いのです。

typedef CModularRoom<
    CCommonModule,
    CPvPCommonModule,
    CFugaCommonModule,     //←CNewBattleModuleから一部機能を分離
    CNewBattleModule>
        CNewBattleRoom;

typedef CModularRoom<
    CCommonModule,
    CPvPCommonModule,
    CFugaCommonModule,     //←分離して作ったモジュールをこっちにも入れる
    CHogeBattleModule>
        CHogeBattleRoom;

こうなります。

ところで、class CModularRoom がいろんなモジュールを多重継承してるけど、ひし型継承問題が起こるんじゃ?と思いましたか?

そんな時のために、C++には仮想継承という機能があります。

各モジュールに

class CHogeModule : public virtual CModularRoomBase
{
};

こんな感じで virtual 指定で継承すれば、ひし形問題は起こらずに済むんですね。

終わりに

今回の記事では、アップデートを繰り返していると必ず直面する、安全性 vs 保守性 のジレンマに対する弊社での取り組みについて紹介させて頂きました。
予測不能?なプランナーさんの考える企画を、リスクを回避しつつ、最大限の効果が得られるように、どうやって実現するか? エンジニアの腕のみせどころです。
Happy Programming!


t_fujino

「GameServerDevelopers Vol.1」に参加してきました!


こんにちは、Aimingエンジニアの藤野です!

5/28(土)に大阪で開催された、「GameServerDevelopers Vol.1」に参加しましたので、
その記事を書かせていただきます。

サーバ周りに特化した講演会

GameServerDevelopersということで、サーバに関するお話しを聞けるのが大きな特徴です!入社2カ月目の私でも理解できるような わかり易い導入から、負荷試験の種類と それぞれの実施方法について等の、とても濃いお話まで!色々盛り沢山でした!

弊社の山藤も登壇しました

GameServerDevelopersVol1_1
実はこの会、弊社の山藤も登壇させていただきました!お話しさせていただいた内容は、
Aimingで実際に使われている通信技術について!大規模MMORPGを動作させるために
必要な通信の基盤は、どんな設計をしているのかを紹介いたしました。

当日使用した資料です。

まとめ

今回が初の開催となる 「GameServerDevelopers Vol.1」 なんと100名以上の参加があり、これからも開催したいと締めくくりがありました!私もこの会で、新たな知見を得ることができ、知り合いが増え、グッズも貰い(これが目的じゃないですよ!)、本当に楽しい1日となりました!

個人的に、特に面白かったのは、負荷試験のお話しです。負荷試験とは、サーバ群の性能を図るテストです。組み上げたサーバ群が、どこまで耐えられるか?どこかに無駄がないか?等を知るために行います。
負荷試験って楽しくないですか?楽しいですよね!というところに共感しました!
本題は、サーバのスペックを正しく把握することで、必要最低限のサーバ構成にすることや、スムーズにスケーリングできること等のメリットがあるから、負荷試験大切!を学びました!

次回も是非、参加させていただきたいと思います!