asoview! TECH BLOG

アソビュー株式会社のテックブログ

スケールできなかったオフショア開発拠点をなんとかした話

この記事は アソビュー! Advent Calendar 2019 - Qiita 21日目の記事です。
アソビューにてバックエンドエンジニアの服部と申します。
今年の夏よりベトナム開発組織 ASOVIEW VIETNAMのCTOに任命され、組織のスケールアップをメインミッションとして日々奮闘してます。
最近はベトナムにくると「帰ってきた...」というホーム感がでるようになってきました、不思議です。

f:id:takayasu-hattori:20191221102109j:plain:h250
無秩序なバイクのイメージが強いホーチミンですが、テト明けは人も少なく装飾が綺麗です。

はじめに

オフショア開発を初めてみたはいいが、どこかうまくいってない。
開発は進んでるしリリースもできてるんだけど、人の入れ替わりは多いし
どこか日本と現地で噛み合っていないような。。。

アソビューでは2018年にベトナムにオフショア開発拠点を自社で立ち上げおります。
ベトナム開発拠点の立ち上げから1年。
新規開発系のプロジェクトはうまく回っていたが、エンハンスメント案件系の対応チームがこの問題に直面しており、スケールしづらくなってました。
それを少しだけ改善した話。

ここで説明しないもの

スクラムガイドに記載されている事柄について

なんかうまくいってない

ベトナム開発拠点が立ち上がってちょうど1年たった頃くらいから、
日本のメンバーから下記のような声がよくあがるようになってきました。
もちろん自分も同じように言っていた1人です。

  • 依頼通りのものが出てこない
  • 各タスクの進捗がわからない
  • コード品質が悪い
  • プロダクトバックログに積まれたタスクが山ほどあるのにタスクがないと言われる
  • PO/決裁権のある人が知らないないものが開発されている
  • 数ヶ月で人がすぐ辞めていく
    etc...

(これってベトナム組織の問題なの...?というものが多数混ざってますが一旦。)
離職率も上がっているように見え、ベトナム開発組織でなにが起きているのかみんないまいちちゃんと把握できてない状態でした。
ということで、ベトナムに参りました。

何が起きていたか

ベトナムに来て最初やっていたことは、とにかく毎日観察/調査です。

  • 日本側と「どのような」やりとりを「どこで」やってるのか
  • どんなイベント(朝会/定例等)をやっているのか
  • だれがリーダー的なポジションで、だれがコミュニケーションの中心にいるのか
    etc...

訪越して2-3週目くらいたってようやく、どうやらベトナム組織単体の課題ではなく、
日本組織にも大いに課題がありそうということが見えてきました。

下記一例ですが、

  • 複雑なレポートライン
    • 事業をまたがり自由に依頼
    • ブリッジエンジニアを通さない依頼
  • 不確実性の高いタスク
    • 要件定義がきちんとされていないタスク
    • POを通さずに要件定義されたタスク
    • いろんな人が自由に作ったtodoレベルのタスク
    • 何に貢献しているのか説明されていないタスク
  • 柔軟すぎる開発体制
    • 開発計画のない作業体制
    • 作業の振り返りをしていない
    • チーム間のメンバー移動がフレキシブルに行われている

f:id:takayasu-hattori:20191220234144p:plain
レポートラインが多すぎて誰も管理できてない状況に
なかなか渋い状況ですね。。。日本で管理できていると思っていましたが、見えないところでいろんな作業が発生していました。
もちろんベトナム組織からも下記の不満が出てきていました。

  • 案件依頼にルールがない
  • タスクをやってもリリース直前で止められる
  • タスク完了直前で仕様変更がある
  • 事業貢献度がわからない
  • 開発の進め方にルールがない

これではベトナム組織も疲弊するのは当然です。

これ、ほとんどスクラム開発で解決できそう

課題としては、日本組織は開発依頼体制が、ベトナム組織は開発受け入れ体制が整備されていないこと。
スクラムガイドラインに則った開発ができるように整備すれば、両方とも解決できそう。

日本組織では2年ほど前よりスクラム開発を導入し、知見も溜まってきております。
採用/教育含めスクラムマスターをベトナムで急遽起用するのは現状難しそうだと判断し、
CTO兼スクラムマスターとしてスクラム開発を数チームに導入し、改善を図ってみることにしました。

スクラム導入の下準備

とはいえ、「スクラム開発やろうぜ!」といって始められるわけではないので下準備(スプリント0の実施)をしていきます。
巻き込まないといけな人が多いので、実際にスプリントを回し始めるより大変です。

ベトナム開発チーム内での準備

ベトナム開発組織のメンバーは全員スクラム開発をやったことがないとのこと。
スクラムガイド にベトナム語訳があったので、1週間以内に読んでくることを依頼。
また、下記を並行で実施しました。

  • 日報の提出依頼
  • 毎朝、同じ時間に朝会の実施(のちにデイリースクラムに移行)
  • スクラムガイドの読み合わせ
  • スクラムイベントの簡単なシミュレーション
  • タスク管理ツールのルール化

POとの調整

チームの整理および当時のプロダクトバックログや今後のタスクに関してはPOと整理しました。

  • チームの整理
    • ベトナム開発チームの数/メンバー構成は変えない
    • 事業ごとにベトナム開発チームをつけ管理することを合意
  • プロダクトバックログの整理
    • 不要なもの(個人のtodoや決済権を通ってないタスク)は削除
    • 要件定義できていないものはICEBOXへ
    • 必要なタスクの作成
  • タスクの依頼ルートの整理
    • 基本的にPOがタスクを作成する
    • POがOKを出した場合のみ、そのタスクに関してのみ代理でスクラムマスターがタスクを作成することができる(これはスクラムガイドに反してますが今回OKとしてます)
    • メンバー/サポートからの依頼の吸い上げはPOが参加するミーティングでやるやらを判断する

f:id:takayasu-hattori:20191220234026p:plain
メッシュになってたのが嘘のよう。レポートラインが明確化されました

そしてスクラムへ…

初めてのスクラムイベント

スクラムイベントの初回実施時は、どのチームでも質問責めにあいます。
1つ1つの行動や発言に対して

  • なんでやるのか
  • 今のはどう言う意味か
  • こうした方が効率がいいと思う

いろんな意見が出てきますが、ちゃんと意味を説明し、また初回なので教科書通りにやっていくことを伝えます。
とはいえみんなポジティブで、「スクラム開発やってみたい!だから教えて欲しい!」という姿勢なので、
やりにくさや進めづらさはなかったです。

導入してどうなったか...

課題としてあげた部分に関してはほぼ解消。
また以前は個人プレーが割と多いと感じてましたが、イベントをこなすにつれてチームでゴールを達成するにはどうすればいいか。という良い空気も出てきました。

ここ、さらっと書いてしまっていますがスクラム導入までの整理や調整が本当に大変で、導入後は日々みんなで改善を繰り返していくだけです。

f:id:takayasu-hattori:20191220234732j:plain:h250
レトロスペクティブ後のようす。最初は恥ずかしがってましたが最近は楽しそう!

とはいえ、課題はまだまだ

以前よりよくなった!とはいえ開発チームが自走し、かつ組織がスケールしていくには課題がまだまだあります。
日本の遊びをもっと世界に広めたい!、オフショア開発や海外経験してみたい。もしそういうことに興味がある方はぜひ一緒に働いてみませんか?

というわけで、アソビューでは一緒に働ける、世界をワクワクさせたい仲間を募集してます。
hrmos.co

Next.jsアプリをNowで公開する

アソビューAdvent Calendar 2019の20日目の記事になります。

フロントエンドエンジニアの野口です。 今日は、Next.jsアプリを最近話題のNowで公開する方法を紹介します。
ほんとに一瞬で公開できます。 ​

Next.jsとは

​ Reactで簡単にSSR(server side rendering)を可能とするフレームワークです。
SSRはもちろん、高速ページロードのための自動コードスプレティングやHMR(Hot Module Replacement)をサポートしたWebpackベースの開発環境などをデフォルトで用意してくれています。 ​

Nowとは

Nowは、Next.jsを開発しているZeit社が提供するPaasです。
Nowを使うことで非常に簡単にデプロイすることができます。 ​

Next.jsアプリを作成

​ まずはNext.jsアプリを作成します。

create-next-appを使うことで、簡単にアプリケーションの雛形を作成可能です。​
かなり多くのExamples(雛形)が用意されているので、用途に合わせて選ぶことができます。
next.js/examples at canary · zeit/next.js · GitHub

create-next-appを使わない場合でも、Reduxの設定など、単純なReactアプリとは少し異なる設定をしないと行けない時もあり、そんな時にここのExamplesはかなり参考になります。

  • アプリ作成 ​
npx create-next-app {app-name}
  • アプリ起動 ​
yarn dev

http://localhost:3000/にアクセスするとデフォルトで用意されているページを確認できます。 ​

Next.js デフォルト初期起動画面
nextjs prepared page

Nowで公開

​ さきほど作成したアプリを公開していきます。
CLIの場合と、ブラウザでポチポチ操作していく場合をそれぞれ紹介します。 ​

CLIの場合

  • Nowにサインアップ

まずは、こちらからNowにサインアップします。
Githubアカウントで登録できます。

  • Now CLIをインストール
npm i -g now
  • Now login

以下コマンドを実行し、サインアップ時に登録したメールアドレスを入力。

now login
> We sent an email to hoge@hoge.co.jp. Please follow the steps provided
  inside it and make sure the security code matches Gentle Tasmanian Devil.
  ⠦ Waiting for your confirmation

​ メールの認証を求められるので、届いたメールの認証ボタンを押します。 認証完了すると以下のメッセージが表示されます。 ​

✔ Email confirmed
> Congratulations! You are now logged in. In order to deploy something, run `now`.
  • Nowにデプロイ

これで準備は完了です。
あとは以下のコマンドを実行するだけでデプロイされます。

now
> Deploying ~/Documents/work/next-now-sample under {account name}
> Using project next-now-sample
...
> Ready! Deployment complete [2m]
- https://next-now-sample.now.sh

https://next-now-sample.now.shにアクセスするとNext.jsアプリが公開されているのを確認できます。 ​

GUIの場合

​ コマンドではなく、画面を操作するだけでもデプロイできます。

  • サインアップ

CLIと同じく、こちらからサインアップします。

  • Nowのセットアップ

デプロイ対象のリポジトリを選択。 ​

対象のリポジトリを選択
select target repository

  • テンプレートを選択

続いてテンプレートを選択します。今回の場合は、Next.jsを選択。

フレームワークを選択
select target framework

その後、プロジェクトネームを設定し、デプロイを実行。
しばらくすると、こんな感じでデプロイが完了します。

デプロイ完了画面
done deploy
​ ​ ボタンをぽちぽち押していくだけで簡単にデプロイできます。 ​ ​ ​

最後に

Nowは無料枠でいろいろできます。
個人的にいいなと思ったのは、カスタムドメインの設定。これも無料でできます。
デプロイも100/per dayなので十分です。

てっとり早くアプリを公開したいときにはかなりいい選択肢になるんじゃないかなと思います。

おしまい。

精算システムについて

アソビュー Advent Calendar 2019の19日目。
こんにちは。アソビューで精算システムやSREチームを担当している並木です。
マイクラ歴3年になるのに、いまだに豆腐建築から抜け出せません。
建築センスをください。

簡単な経歴

・SIerとして、銀行の勘定系システムを担当
・Webベンチャーの営業っぽい雑用係を担当
・アソビューにJOIN
元々銀行のシステムを担当していたこともあり
アソビューでは主に精算周りのシステムに関わらせていただいております。
今回は、精算システムにおける計算処理の注意点などを書きたいと思います。
※アソビューのシステムに関連する部分もありますが、基本的には一般論として書かせていただきますので、ご容赦ください。

法改正対応

記憶にも新しい

消費税の改正 8% → 10%

実際にシステムにどんな影響があるか?

税率計算の処理

  • 増税のたびに影響範囲の確認、修正規模が多くなりがちな悪い実装例
  • 影響範囲は変わらないが、修正規模が小さく、結果テストなどに掛かる工数も減らせる良い実装例
悪い実装例
  • ※言語はCOBOLにて実装した場合のイメージとなります。あくまでイメージです。

料率をDBでの管理ではなく、code にベタ打ち固定で記載している場合は
全ての固定箇所に修正が必要になり、修正範囲の確認から修正、テストと掛かる工数も大きくなりがちです。
このような例はミスも起こりやすく、「お金を取り扱うシステム」としてはよくない例となります。

  WORKING-STORAGE  SECTION.
     01  ONIGIRI     PIC 999 VALUE 150.
     01  SYOUHIZEI    PIC 9999.
     01  ZEIKOMI     PIC 9999.
     01  ZEIRITSU    PIC 9   VALUE 8.  -- 消費税率 計算時は /100 で使用
~~
         COMPUTE     SYOUHIZEI =  ONIGIRI * (ZEIRITSU / 100).
         COMPUTE     ZEIKOMI =  ONIGIRI + SYOUHIZEI
~~

実際に計算してみると
150円のおにぎりの消費税を固定で8%税率で計算し
それを加算することで税込金額として算出。

   150 + 12 = 162円 

が計算結果となります。

良い実装例

レコードの構成イメージ

f:id:yusuke-namiki:20191215221008p:plain

DBで料率を管理し、増税時に掛かる作業としては

最新レコードの挿入(inesrt)
直近レコードの終了日の更新(update)

のみであり、このDBを呼び出している箇所全てに適用されるため
影響範囲の特定も容易で、テストも機械的に実施できることになります。

  WORKING-STORAGE  SECTION.
     01  ONIGIRI     PIC 999 VALUE 150.
     01  SYOUHIZEI    PIC 9999.
     01  ZEIKOMI     PIC 9999.
     01  ZEIRITSU    PIC 99.  -- 消費税率 計算時は /100 で使用
~~
     select zeiritsu
       from db-syouhiziei
        where now() between start_day and end_day 
-- 今日がどの税率なのかを判定させる条件
     MOVE zeiritsu TO ZEIRITSU

         COMPUTE     SYOUHIZEI =  ONIGIRI * (ZEIRITSU / 100).
         COMPUTE     ZEIKOMI =  ONIGIRI + SYOUHIZEI
~~

これであれば今の日付を見て、最新の税率を取得して計算しているので
税率の最新レコードの insert と一つ前のレコードの update 処理さえ忘れていなければ
実際の計算処理には影響が出なくなります。

帳票類(レシート、振替用紙などなど)

消費税(税率8%) ¥108 

といった形で固定で出力している場合には、計算処理同様に
最新の税率を取得して出力するなどの工夫が必要になります。
キャッシュレスに先んじてペーパレスは事前に対応していることが多かったため
そこまで大掛かりな回収は必要ではなかった認識でいます。

今回は触れていませんが 軽減税率の場合のレシート出力については、政府からも対応例が出ていますね。 https://www.gov-online.go.jp/tokusyu/keigen_zeiritsu/jigyosya/kubunkisai.html

まとめ

いかがでしたでしょうか。
今回は消費税のことにのみ触れていますが、法改正に関わるシステム改修に関して
一部ではありますが、サンプルを記載してみました。

昨今では法改正に関わるシステム改修がテレビなどでも取り沙汰されるようになりましたので
「どんなことしてるんだろう?」の理解の一助になれば幸いです。

最後に

記載した内容について、開発と運用の責任者を募集中です!
※COBOLは必須スキルではありません。むしろいらな・・ry
成長著しいベンチャーで社内基幹システムの開発・運用責任者大募集! | アソビュー株式会社

今すぐやめようssh! AWS Session Managerを検証・導入してみた

この記事は アソビュー! Advent Calendar 2019 - Qiita 17日目の記事です。

アソビューにてバックエンドおよびSREを担当している寺岡(@toda_kk)と申します。よろしくお願いします!

最近はリングフィットアドベンチャーをやり始め、スクワットをする度に「いいぞ!」「すごいぞ!」と褒められることで自己肯定感を高めています。

はじめに

さて、SREの業務というと、どの企業でも多岐にわたるかと思います。インフラアーキテクチャの設計および構築、モニタリング環境の整備、開発フローや運用業務の効率化……などなど、ビジネス上あまり目立たないけれど実は大事な役割を担っていたりします*1

そんな中で、今回は開発や運用の効率化のためにAWSのSession Managerという機能を検証・導入した話を取り上げたいと思います。

Session Managerとは?

AWS Systems Manager Session Managerとは、マネジメントコンソールやAWS CLIを介してEC2インスタンスを管理するための機能です。IAMポリシーでアクセス制御を設定できるなど、セキュリティ面での向上が期待できます。

その他、サービスの特徴や導入のメリットについては公式ドキュメントをご参照いただきたいのですが、多くの場合はEC2インスタンスに対するssh接続の代替となる機能として認識されることが多いのではないかと思います。

課題感

弊社ではインフラリソースとしてAWSを利用しており、特に最近はECSやEKSの導入をはじめアプリケーションのコンテナ化を進めています。

しかしながら、サービス開始当初から動いている一部のアプリケーションについてはまだコンテナ化が追いついておらず、EC2インスタンス上で直接 jarファイルを実行してアプリケーションを起動しているケースがあります*2

こうしたケースでは、サービスの再実行やインフラリソースの整備などでインスタンスにsshして作業することが少なくありません。そんな中で、日々の業務のなかで下記の点で課題が発生していました。

  • EC2インスタンスにssh接続するための秘密鍵の管理が必要になる。
  • 誰がどういった作業を実施したのかロギングしづらい。

ssh認証の秘密鍵(pemファイル)の管理

EC2インスタンスへのssh認証方式として、多くの場合はパスワードを使用する方式と公開/秘密鍵を使用する方式を用いると思います。特にEC2をそのまま使う場合、 hoge.pem のようなファイル形式で秘密鍵を使った認証を選択する場合が多いのではないでしょうか。どちらの方式を選択するにせよ、パスワードや秘密鍵をどのように管理するかというところが運用上の課題になるでしょう。

秘密鍵をどういう単位で生成するのか、個々のインスタンスで別にするのか、VPC内で同一にするのかなど、さまざまな方針があると思います。

しかしいずれにせよ、どのインスタンスがどの秘密鍵とひもづくのか、またどの秘密鍵をどのメンバーに共有しているのかなどの管理が煩雑になりがちです。スタートアップ創業当初で限られたメンバーでサービス開発をしているフェーズあれば大きな問題にはならないかもしれませんが、サービスや組織が大きくなるに連れてセキュリティ面を強化することは必須の課題となってきます。そうしたときに、ひとつの課題としてインスタンスにおける認証方法の管理という点が浮上してくることでしょう。

作業のロギング(監査ログの取得)

またセキュリティの強化を考えた場合、機密性(Confidentiality)を確実にすると共に責任追跡性(Accountability)を担保することも重要となってきます。そこで、インスタンス上でどのような作業を行ったのか、さらにいえば誰がどのような操作を行ったのか、という監査ログを取得することが必要になってくると思います。

Linux上であれば auditd を用いるのが一般的かと思いますが*3、企業によってはEC2上でWindows OSを動かしている場合もあるでしょう。このような場合も含めて、OS上の監査ログ取得の選択肢としてはさまざまな方針が考えられるかと思いますが、多くの場合はログの連携や確認が容易にできないといったケースもあるのではないでしょうか。

検証と導入

こうした課題に対して、弊社では解決策の一つとしてSession Managerの検証および導入を行いました。なお、今回の検証にあたっては下記の記事を大いに参考に実施しました*4

dev.classmethod.jp

導入の手順

導入の手順は非常に簡単で、下記の3ステップを実施するだけで完了します。

  • Session Manager自体の設定を行う。
  • EC2インスタンスの amazon-ssm-agent を最新化する。
  • EC2インスタンスにIAMポリシーを設定する*5

Session Managerの設定

まず、対象となるAWSアカウントでSession Managerを使えるように設定を行います。今回はマネジメントコンソール上から設定する方法を紹介します。

といっても、実はSession Manager自体の設定について手順は多くはありません。とりあえず導入するだけなら、下記のように設定するだけでOKです。

Session Managerの設定
Session Managerの設定

なお、Session Managerでセッションを開始すると通常は ssm-user というユーザー名が表示されるのですが、 Enable Run As support for Linux instances の設定を有効にしておくと任意のOSユーザーでSession Managerの利用が可能となります。

EC2インスタンスの設定

次に、対象のEC2インスタンスでSession Managerからセッションを開始できるようにするための設定を行います。

まずは、EC2インスタンス上で amazon-ssm-agent をインストール or 最新化しておきます。Amazon Linux 2 を利用している場合はデフォルトでインストールされていますが、古いインスタンスだと 2 ではなく以前の Amazon Linux で運用していることもあるかと思います。そのような場合は、新たにインストールしておく必要があります。

そして、Session Managerを利用するために必要となるIAMポリシーを設定します。AWSではあらかじめ AmazonSSMManagedInstanceCore というポリシーが用意されており AmazonSSMRoleForInstancesQuickSetup というロールにアタッチされています。このロールをそのままEC2インスタンスに適用することもできますが、Systems Managerの機能を全て許可する設定になっているので本番適用するのは控えた方が良さそうです。

Session Managerを導入するための最低限のIAMポリシーとしては、いろいろと試してみたところ下記のようになるようです。この記述を元に、用途に合わせて設定を変更していただくのが良いかと思います。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:UpdateInstanceInformation",
                "ssmmessages:CreateControlChannel",
                "ssmmessages:CreateDataChannel",
                "ssmmessages:OpenControlChannel",
                "ssmmessages:OpenDataChannel"
            ],
            "Resource": "*"
        }
    ]
}

監査ログの取得

Session Managerを導入するための手順は、上記で完了となります。この程度であれば、マネジメントコンソールからでもAWS CLIからでもサクッと導入できちゃいますね!

加えて、Session Managerで実行した操作のログを取得する方法を紹介します。

といっても、上述したSession Managerの設定にある項目を有効化するだけ済みます。めっちゃ簡単やんけ。

Session Manager監査ログの有効化
Session Manager監査ログの有効化

CloudWatch Logsにログ出力するように設定すると、指定したロググループに対してセッション終了後にログストリームが作成されます。

セッション履歴からCloudWatch Logsを確認できる
セッション履歴からCloudWatch Logsを確認できる

下図のように、Session Managerから作成されたログストリームの名前にはプレフィックスとしてIAMユーザー名が付きます。そのため、どのメンバーがどのような操作を実行したのかが簡単に追えるようになります。

ログストリームにIAMユーザー名が付く
ログストリームにIAMユーザー名が付く

今回はCloudWatch Logsに連携するように設定しましたが、設定画面にあるようにS3にログを送信することもできます。用途に応じて使い分けられると良さそうです。

ちなみに、CloudWatch Logsに連携する場合はEC2インスタンスに設定するIAMポリシーに下記のような権限を加える必要があります。ただ、通常はインスタンス上のOSログやアプリケーションログなどをCloudWatch Logsに出力している場合が多いかと思いますので、特別にポリシーを変更する必要はないかもしれません。

        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:DescribeLogGroups",
                "logs:DescribeLogStreams"
            ],
            "Resource": "*"
        }

さらなる検証

Session Managerを導入することで、はじめに挙げた2つの課題を解決することができました!

まず、開発メンバーに対してpemファイルを配布する機会を減らし管理しやすくすることができました。特に新たに、ジョインしたメンバーに対しては、pemファイルを配布せずにEC2インスタンスの管理をお願いする準備を整えることができました。

また、どのメンバーがEC2インスタンス上でどういった操作をしたのか記録できるようになりました。こうした形でセキュリティ面の強化を着実に進めることができたと思います。

導入手順としては以上となりますが、弊社ではさらなる検証を実施しました。

ひとつは、EC2インスタンスに設定するセキュリティグループについてです。上記の導入手順ではセキュリティグループの設定はありませんでした。Session Managerの利用においてセキュリティグループは関係ないのでしょうか?

続いて、AWS公式がサポートしている session-manager-plugin についてです。これはAWS CLIを通じてSession Managerを介しEC2インスタンスへのセッションを開始するためのツールですが、今回の目的にどの程度適しているのかを検証してみました。

必要なセキュリティグループについて

Session Managerはsshの代替となるものではありますが、実際にはsshを実行しているわけではありません。詳細は公式ドキュメントを参照していただきたいのですが、sshしているわけではないのでセキュリティグループのインバウンドで 22ポート を開放する必要はありません

ところで、インバウンドで 22 以外のポートを閉じてしまうとSession Managerでも接続できなくなるのではないでしょうか? そこで、どの程度ポートを開けておく必要があるのか検証してみました。

結論としては、インバウンドのポートは全て閉じた状態でもSession Manager経由で接続できます

なお、当たり前ですがアウトバウンドのポートを全て閉じてしまうとSession Managerでも接続できませんでした。正確には、セッションは開始できるものの、出力を受け付けないためインスタンス上でなにが起きているのか把握できない状態となってしまっていました。アウトバウンドは基本的に全て許可にするケースが多いかと思いますが、Session Manager導入にあたって特別に設定変更する必要はないかと思います。

session-manager-pluginについて

基本的にSession Managerを利用する際にはマネジメントコンソールからセッションを開始します。ただし、ブラウザ上からコマンドを叩くのはぶっちゃけちょっと面倒ですし、人によってはつらい前世の記憶が呼び起こされることになるでしょう。

そこで、AWS CLIからセッションを開始する方法として session-manager-plugin というツールが公式でサポートされています。ローカルのマシンにインストールすることでCLIからSession Managerを利用できます。導入の手順などは公式ドキュメントをご参照ください。

ツールのインストール後は下記コマンドを実行するだけでセッションを開始できます。便利ですね!

# インスタンスのパブリックIPではなくインスタンスIDを指定するので注意!
aws ssm start-session --target [instance-id]

これで全部ええやん!……と思いきや、ひとつ問題が発生しました。

弊社ではAWSアカウントを複数所持しており、例えば本番環境と開発環境を分離するなどの目的でマルチアカウントで運用しています。そのため、マネジメントコンソールを利用する際には、まず大元となるAWSアカウントにログインして、そのあと別アカウントのロールとしてスイッチロールする形で運用しています。また、スイッチ先のロールはメンバーの権限に合わせて admin / developer / operator などを設定しており、IAMユーザーもそのように作成しています。

そこで問題になるのがスイッチロールの際にはスイッチ元のIAMユーザー名は引き継がれないという点です。

上述の通り、Session Managerを介した操作のログはCloudWatch Logsに出力しており、ログストリームのプレフィックスにIAMユーザー名が付くことで監査ログとして機能できると考えていました。しかし session-manager-plugin からスイッチロールした先のAWSアカウント上のインスタンスに対してセッションを開始すると、ログストリームのプレフィックスに一律で botocore-session という文字列が付与されてしまい、どのメンバーが操作したのか追いづらくなってしまいます。

スイッチロールした際のユーザー名
スイッチロールした際のユーザー名

ログストリームのプレフィックスにも付いてしまう
ログストリームのプレフィックスにも付いてしまう

plugin内部の処理を見てみると、どうやらセッション開始の度に botocore-session-XXXXXXXXXX という形式でIAMユーザーを作成している? ように見えます*6。そのため、ログストリームにそのユーザー名が入るようになってしまっているようです*7

Session Managerの導入後にこのような問題が発覚してしまいました。とはいえ、単にEC2インスタンス上の作業を記録する目的としては、上記の通りCloudWatch Logsへのログ出力で十分と考えることもできます。弊社ではひとまずこの状態まで持っていくことをゴールとして、Session Managerの導入と開発メンバーへの共有を進めました。

おわりに

以上、Session Managerを導入することで、開発メンバーに対してpemファイルを配布する機会を減らし管理しやすくすることができました。特に新たに、ジョインしたメンバーに対してはpemファイルを配布せずにEC2インスタンスの管理をお願いする準備を整えることができました。

また、どのメンバーがEC2インスタンス上でどういった操作をしたのか記録できるようになりました。こうした形でセキュリティ面の強化を着実に進めることができたと思います。

このようにアソビューSREチームでは、開発チームとの連携を強化したり運用を改善するための施策をたくさん進めております! とはいえ、手が足らずに思うように実行できていないのも事実です。

そこで、下記の通り新たなメンバーを積極的に募集中ですので、ぜひ応募いただけると嬉しいです!

hrmos.co

みんなどんどん応募してくれよな!!

*1:このアドベントカレンダーでもCloudFormationの話EKSの話など、SREチームのメンバーが書いた記事がいくつかあります。

*2:アソビューでは開発言語としてJavaを利用しているアプリケーションが多いです。Javaを選択する理由についてはこちらの記事をご参照ください。

*3:弊社では実施しませんでしたが、Amazon Linux上でauditdを使って監査ログを取得する際は、こちらの記事が参考になるかと思います。EC2でファイル監査を設定する(auditd設定編) | ナレコムAWSレシピ

*4:クラスメソッド様、いつもありがとうございます!

*5:正確には「IAMポリシーをアタッチしたIAMロールをインスタンスプロファイルとして設定する」ということになるでしょうが、記述の簡略化のため上記のように表現しています。

*6:この辺りはまだしっかりと検証できていない部分です。また私が調べた限りでは、この辺りの詳細について解説したページは見当たりませんでした。どなたかご存知の方がいらっしゃれば、ご教示いただけると助かります!

*7:公式ドキュメントのこの辺りを参照すれば解決しそうな気がしますが、まだ十分に検証できていません。

履歴管理ができるテーブル構造を考えてみた

アソビュー! Advent Calendar 2019 の16日目の記事です。

アソビュー株式会社でバックエンドエンジニアをしている土屋です。
最近はスマブラにハマっており、大会に向けて日々練習中です。

本記事の概要

DBでデータを効率的に管理するためのテーブル構造と、そのメリット・デメリットを考えてみたお話です。

背景

アソビュー!では様々なデータをRDBで取り扱っていますが、テーブル構造はメンテナンスが難しいです。
モノリシックなマスターテーブルが存在していて、更新日カラムが付属しているけどどこを更新したのかわからない。
また、過去のデータがほしいけどマスターテーブルなので元々がどんなデータだったかわからない。
ということがたまに発生したりします。

上記から履歴管理ができるテーブル構造を作る必要があったため、検討をしました。

検証

ショップと商品というデータの格納方法について、以下の2つの方法でメリット・デメリットを検討してみます。

  • マスターテーブルにまとめた場合
  • 履歴管理を視野に入れて設計した場合

マスターテーブルにまとめた場合

DDL

create table sandbox.SHOP_MASTER
(
    ID          serial primary key comment 'ショップID',
    NAME        varchar(128) not null comment 'ショップ名',
    INSERT_DATE datetime     not null default CURRENT_TIMESTAMP() comment 'システム用 登録日時',
    UPDATE_DATE datetime     not null default CURRENT_TIMESTAMP() comment 'システム用 更新日時'
) comment 'ショップ';

create table sandbox.SHOP_ITEM_MASTER
(
    ID          serial primary key comment '商品ID',
    SHOP_ID     bigint unsigned not null comment 'ショップID',
    NAME        varchar(128)    not null comment '商品名',
    PRICE       int unsigned    not null comment '店頭価格',
    NET_PRICE   int unsigned comment 'ネット価格',
    INSERT_DATE datetime        not null default CURRENT_TIMESTAMP() comment 'システム用 登録日時',
    UPDATE_DATE datetime        not null default CURRENT_TIMESTAMP() comment 'システム用 更新日時',
    constraint fk_shop_item_master_SHOP_ID foreign keY (SHOP_ID)
        references sandbox.SHOP_MASTER (ID)
) comment '商品';

ER

f:id:mtsu724:20191212155917p:plain

メリット

  • テーブル数が少ないので、ぱっと見でどういうデータ構造かがわかりやすい
  • テーブル数が少ないということは、検索のSQLが組みやすい

デメリット

  • 更新日をもっていても、何を更新したかがわからない
  • 情報を増減させるために、ALTER TABLE文を使う可能性がある
    • 例1:店頭料金、ネット料金以外に宅配料金を追加したい場合
    • 例2:店舗に住所を保持させたい
  • ALTERを続けることにより、データにnullが入る可能性が高くなります

履歴管理を視野に入れて設計した場合

DDL

create table sandbox.SHOPS
(
    ID          serial primary key comment 'ID',
    INSERT_DATE datetime not null default CURRENT_TIMESTAMP() comment 'システム用 登録日時'
) comment 'ショップID';

create table sandbox.SHOP_REVISIONS
(
    ID          serial primary key comment 'ショップリビジョンID',
    SHOP_ID     bigint unsigned not null comment 'ショップID',
    INSERT_DATE datetime        not null default CURRENT_TIMESTAMP() comment 'システム用 登録日時',
    constraint fk_shop_revisions_SHOP_ID foreign key (SHOP_ID)
        references sandbox.SHOPS (ID)
) comment 'ショップリビジョン';

create table sandbox.LATEST_SHOP_REVISIONS
(
    SHOP_ID     bigint unsigned not null comment 'ショップID',
    REVISION_ID bigint unsigned not null comment 'ショップリビジョンID',
    INSERT_DATE datetime        not null default CURRENT_TIMESTAMP() comment 'システム用 登録日時',
    constraint fk_latest_shop_revisions_SHOP_ID foreign key (SHOP_ID)
        references sandbox.SHOPS (ID),
    constraint fk_latest_shop_revisions_REVISION_ID foreign key (REVISION_ID)
        references sandbox.SHOP_REVISIONS (ID)
) comment '最新のショップリビジョン';

create table sandbox.SHOP_NAMES
(
    REVISION_ID bigint unsigned not null comment 'ショップリビジョンID',
    NAME        varchar(128)    not null comment 'ショップ名',
    INSERT_DATE datetime        not null default CURRENT_TIMESTAMP() comment 'システム用 登録日時',
    constraint fk_shop_names_REVISION_ID foreign key (REVISION_ID)
        references sandbox.SHOP_REVISIONS (ID)
);

create table sandbox.SHOP_ITEMS
(
    ID          serial primary key comment '商品ID',
    REVISION_ID bigint unsigned not null comment 'ショップリビジョンID',
    INSERT_DATE datetime        not null default CURRENT_TIMESTAMP() comment 'システム用 登録日時',
    constraint fk_shop_items_REVISION_ID foreign key (REVISION_ID)
        references sandbox.SHOP_REVISIONS (ID)
);

create table sandbox.SHOP_ITEM_NAMES
(
    ITEM_ID     bigint unsigned not null comment '商品ID',
    NAME        varchar(128)    not null comment '商品名',
    INSERT_DATE datetime        not null default CURRENT_TIMESTAMP() comment 'システム用 登録日時',
    constraint fk_shop_item_names_ITEM_ID foreign key (ITEM_ID)
        references sandbox.SHOP_ITEMS (ID)
) comment '商品名';

create table sandbox.SHOP_ITEM_PRICES
(
    ITEM_ID     bigint unsigned not null comment '商品ID',
    AMOUNT      int unsigned    not null comment '価格',
    INSERT_DATE datetime        not null default CURRENT_TIMESTAMP() comment 'システム用 登録日時',
    constraint fk_shop_item_prices_ITEM_ID foreign key (ITEM_ID)
        references sandbox.SHOP_ITEMS (ID)
) comment '商品店頭価格';

create table sandbox.SHOP_ITEM_NET_PRICES
(
    ITEM_ID     bigint unsigned not null comment '商品ID',
    AMOUNT      int unsigned    not null comment '価格',
    INSERT_DATE datetime        not null default CURRENT_TIMESTAMP() comment 'システム用 登録日時',
    constraint fk_shop_item_net_prices_ITEM_ID foreign key (ITEM_ID)
        references sandbox.SHOP_ITEMS (ID)
) comment '商品ネット価格';

ER

f:id:mtsu724:20191212155919p:plain

メリット

  • 履歴管理をすることで、いつどんな変更があったかを確認することができます
    • この情報を機械学習で利用することで、何らかの相関関係を見つけて業務に利用できる可能性もあります
  • 宅配価格や住所を追加したい場合、CREATE TABLEだけでOKになります
  • 1つのテーブル情報が肥大化しないので、変更容易性に富みます
  • データが独立しているので、nullのデータが入りにくいです

デメリット

  • テーブル数が増えるため、命名にルールがないと管理が大変になります
  • 履歴を管理するという要件の特性上、レコード数が膨大になりやすいです
    • リビジョンを上げるタイミングなどは要件として考慮する必要があります

おわりに

履歴管理を行うことで、どんな操作が行われたかわかるようになるので、利用者も開発者にも恩恵があると思っています。
とはいえ不要なデータを管理する必要はないので、履歴管理の対象はどれなのかを都度検討できるとよいかなと思います。

以上です。お疲れ様でしたー。

参考

gatlingで負荷試験を実施する方法

これはアソビュー Advent Calendar 2019の15日目です。

こんにちは、アソビューでBEエンジニアのThang(タン)です。
Gatlingで負荷試験を試したので備忘録です。

今回試したのは Reserve with Google APIの負荷試験です。
APIインタフェースはこちらでご参照して頂けます。

Gatlingとは

  • scalaベースの負荷試験ツールの一種です
  • 特徴:
    • テストシナリオが Scalaで書ける
    • テスト結果はhtmlで出力され、出力結果がキレイ
    • Scala知識が低くても意外と簡単に書ける

Gatling インストール

JDKが必要なので、インストールします。

$ brew install java

公式HPからzipファイルをダウンロードします。
私はバージョン 2.3.1で試しました。

Gatling 使い方

とりあえず bin にgatling.shがあるので実行して、サンプルの負荷試験を実施してみます。

$ bin/gatling.sh 

シナリオを選択します。1のAdvancedSimulationStep01を選択してみましょう。 f:id:dqth:20191215131913p:plain

実行が始まります。 f:id:dqth:20191215132015p:plain

実行完了され、結果が出力されます。
結果はこんな感じです。 f:id:dqth:20191215132106p:plain f:id:dqth:20191215132129p:plain

シナリオを書いてみる

gatlingはシナリオ自動生成機能を使っても良いのですが、今回は自分で1からシナリオを作成してみます。

シナリオの置き場所はデフォルトで user-files/simulationsになります。
ちなみに、置き場所は conf/gatling.confdirectory.simulations項目で変えられます。

書いたシナリオはこんな感じです。

class RecordedSimulation extends Simulation {

    val httpProtocol = http
        .baseURL("http://localhost:8080")
        .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
        .acceptEncodingHeader("gzip, deflate")
        .acceptLanguageHeader("ja,en-US;q=0.7,en;q=0.3")
        .doNotTrackHeader("1")
        .userAgentHeader("Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Version/10.0 Mobile/14D27 Safari/602.1")

    val order_feeder = csv("order.csv").queue

    val scn = scenario("RecordedSimulation")
        .feed(order_feeder)
        .exec(http("order_fulfillability")
            .post("/v3/CheckOrderFulfillability")
            .body(StringBody("""{"merchant_id":"${merchantId}","item":[{"service_id":"${serviceId}","start_sec":${startSec},"duration_sec":${durationSec},"tickets":[{"ticket_id":"${ticketId}","count":${count}}],"price":{"price_micros":${price},"currency_code":"JPY"}}]}"""))
        )

    setUp(scn.inject(constantUsersPerSec(1) during(5 seconds))).protocols(httpProtocol)

このシナリオは /v3/CheckOrderFulfillability APIに1秒に一回 postリクエストを送ります
リクエストbodyはStringBodyを使って設定します。
bodyのパラメータは CSVファイルのデータを使って設定します。
API IFはこんな感じです。

{
    "merchant_id": String,
    "item": [
        {
            "service_id": String,
            "start_sec": long,
            "duration_sec": long,
            "tickets": [
                {
                    "ticket_id": String,
                    "count": int
                }
            ],
            "price": {
                "price_micros": long,
                "currency_code": String
            }
        }
    ]
}

CSVファイルはこんな感じです。 ファイルの置き場所はuser-files/dataになります。

merchantId,serviceId,startSec,durationSec,ticketId,count,price
149990,service0000001,1569805200,36000,0001,2,3200000000
149990,service0000002,1569805200,36000,0002,2,3200000000
149990,service0000003,1569805200,36000,0003,2,3200000000
149990,service0000004,1569805200,36000,0004,2,3200000000
149990,service0000005,1569805200,36000,0005,2,3200000000

gatling.shを実行すると、シナリオ選択のところで今回書いたシナリオ(test)が追加されます。
それを選択すると、追加シナリオを実行できます。 f:id:dqth:20191215132458p:plain

アソビュー! CTOが考えるエンジニアの働き方

アソビュー !Advent Calendar 2019 の14日目の記事です。

こんにちは。アソビュー!で執行役員CTOをしている山崎賢(やまけん)です。 今日は偉そうにも、勝手に自分がずっと想像しているエンジニアの働き方を勝手にツラツラと発信しようと思います!

私が思う。エンジニアの定義

f:id:yk422:20191212164209p:plain

全ての職種においてそうですが、「エンジニア」「デザイナ」「プロダクトオーナー」などなどは、一般的に「職種」と言われていますが 産業構造の進化と労働環境の進化による分業効率性の観点で生まれたロールだと思っています。

それでは、職種ロールの違いってなんでしょうか・・・? 以下は私の個人的な解釈です。

■職種
それ単体で職業として成り立つ種別

■ロール
ある成果/結果を出すために必要となる機能的な役割

エンジニアという職種は、本来は機能的な役割(=ロール)であるはずが、ニーズが大きくそれ単体だけを実行する役割が市場的に必要とされているので 職種としてなりたっているのだと思います。

一方で人はそんな単純なものではなくて、たくさんの個性の集合体です。 得意/不得意、好き/嫌い、それらを含めた全てが個性であり、人だと思っています。

f:id:yk422:20191212150217p:plain
エンジニア
f:id:yk422:20191212150656p:plain
多様性


私が思う。職種の違和感

職種は社会ニーズに基づいて定義されています。「エンジニア」という職種が成り立っているのは、それ単体の塊で社会的なニーズがあるから。

ただ、いつも思います。誰かが決めた枠組みである「エンジニア」「デザイナ」「プロダクトオーナー」などなど。 その枠組みは心地よいものですが、仕事の本質は「課題の解決」です。

本質的な仕事の価値は、その心地よい枠組みを超えた先にあるのではないか。と思っています。


たとえばドラクエだったら。

f:id:yk422:20191212164018p:plain

ドラクエに例えると超シンプルです。

「世界が平和ではない」という課題の原因に「魔王の存在」があり、「魔王の存在を打ち消す」ために、「勇者と仲間を募集する」という手段があるわけす。 f:id:yk422:20191212151625p:plain

そして、ドラクエの世界だと以下のような職種が生まれるわけです。

職種 スキル
勇者 戦闘力が高い
攻撃魔法が使える
回復魔法が使える
武闘家 戦闘力が高い
戦士 戦闘力が高い
僧侶 回復魔法が使える
魔法使い 攻撃魔法が使える

あれ、、、ゲームだから魔王を倒すことが決まっていますが、もしかしたら魔王を改心させることが出来るコミュニケーターがいれば世界は平和になるかもしれません。

仮に魔王を倒すとしても、勇者が100人居たほうが課題解決かんたんですよね。


エンジニアよ。勇者であれ。

つまり、仕事の上では「エンジニアである」ということよりも「課題を解決するために、なんのスキルをどれだけ持っているか」の方が重要です。 そのためのロールの1つがエンジニアリングなのであり、エンジニアリングでなくてももっと簡単に課題解決出来るならそれでいいわけです。

「ソフトウェアを作る人」というエンジニア職種要件の見えない壁をぶち壊して、「課題解決人」としてエンジニアリング+αのαの部分をどれだけ積み重ねていけるか。

戦えて魔法も使える勇者のように、課題解決に最適な職種を自分で定義して自分がそれを目指していく。

それこそが本質的な成長であり、課題解決へのアプローチであり、エンジニアとしての成長であり、自分自身の市場価値の引き上げなのだと思っています!


最後に。

そんなアソビュー!では楽しく課題解決して成長しまくる仲間を募集しています。

www.wantedly.com

勇者になりたい方、ぜひ募集を!