トレタ開発者ブログ

飲食店向け予約/顧客台帳サービス「トレタ」、モバイルオーダー「トレタO/X」などを運営するトレタの開発メンバーによるブログです。

入社後にAWSアカウントの整理とAWS SSOを導入した話

こんにちは、2019年7月よりトレタにJOINした @aibou です。 本記事はトレタ Advent Calendar 2019の16日目の記事です。

趣味はNFL観戦とボルダリングです。NFLは今年11月にマイナス気温の屋外で現地観戦してきました。 最近リードクライミングの講習を受けまして、ガシガシと岩を登っております。

さて、今回はAWSアカウントとAWS SSOのお話をしようと思います。 既に社内エンジニアへの共有や社内WikiにAWS SSOの利用マニュアルを残していますが、経緯や変遷について記載していないので、トレタ社員の方にも読み物として読んでいただければなと思っています。

免責事項

本記事を参考に実施したことで発生した金銭・セキュリティ等あらゆる問題について責任を負いかねますので、自己責任のもと実施していただくよう、よろしくお願いいたします。

また、誤り等あればはてブ等でご指摘いただけますと幸いです。

入社直後の状態

トレタはしばらくインフラ担当者がごく少数な時期が続いたこともあり、いろいろ改善しないといけないところがあるな、というのが入社直後の印象でした。 とくにAWSアカウント構成の改善はすぐに取りかかれそうだと思ったので、着手することにしました。 当時の状況はこんな感じです。

  • AWSアカウントは2つあり、本番環境用(以降本番アカウント)とステージング用(以降ステージングアカウント)という分け方
  • 本番アカウントが親アカウントとなり、ステージングアカウントと一緒に利用料金を支払っている
  • 監査ログは本番アカウントで保管されている
  • 検証用にリソースを作成する場合はステージングアカウントに作成する
  • インフラ担当者がそれぞれのアカウントに社員ごとにIAMユーザを作成し、クレデンシャルを払い出す

図に起こすとこんな感じです

f:id:yo_aibou:20191212142125p:plain

おそらくAWS利用初期の組織はこういった運用をされている企業が多いと思います。 ただ、ある程度規模が大きくなり運用年数も経っていたトレタでは、以下のような問題が起きていました。

  • ステージングアカウントで検証用のリソースが放置され、プロダクトとして利用されているのかどうかわからない
    • これにより、何人もうかつにAWSリソースを触ることができない
    • よって、AWSのオペレーションがインフラ担当者に集中し、権限移譲が難しい状態に陥っていた
  • IAMユーザについて、本番アカウントとステージングアカウントで管理上のズレが発生している
  • 本番環境と監査ログ保管が同居していることで様々なリスクがある

ちなみに本番アカウントのアカウント名も人の名前になってたりして歴史がありますね笑

f:id:yo_aibou:20191212142158p:plain

とくに「うかつにAWSリソースを触ることができない問題」「本番環境と監査ログ保管の同居」については早急になんとかするべきだと判断し、まずはAWSアカウントをきちんと構造化して、プロダクトが運用されているアカウントに検証用リソースを作らなくてもすむ、監査用のアカウントを作成し監査ログを別にできるような状態にしようと思いました。

AWSアカウントの整理

AWSのマルチアカウントベストプラクティスは、2017年のAWS Summit Tokyoにて「AWS におけるマルチアカウント管理の手法とベストプラクティス」というセッション名でAWSの高田様が登壇されております。
https://d0.awsstatic.com/events/jp/2017/summit/slide/D4T2-2.pdf

スライド終盤の「本日のまとめ」 以降がわかりやすくておすすめです。

今回の構造化もこのベストプラクティスに則り、AWS Organizationを利用し以下のアカウントに分けることにしました。

アカウント名 内容
支払い用アカウント 以下の全ての子アカウントの親として、AWSで発生した料金を支払うアカウント
本番アカウント トレタの台帳システムが稼働している本番環境のアカウント
ステージングアカウント トレタの台帳システムが稼働しているステージング環境のアカウント
検証用アカウント AWSのサービスをお試しで利用したいというときに使うアカウント。消されても基本文句言わない
監査用アカウント 各アカウントのCloudTrailログ等監査に関するログを保管するアカウント

f:id:yo_aibou:20191212142218p:plain

前職でのAWSアカウント運用経験から、将来情シス用のアカウントが必要になるだろうと予想していますが、近い将来に関してはニーズがないと判断し一旦作成対象から外しました。 AWS Organizationを利用すれば、秒でアカウントを作成できるのでそのときに作れば問題ありません。

プロダクトのアカウントに情シスのリソースを作ってしまうと移行がマジで大変なのでみなさん気をつけましょう

アカウント構造化に合わせてアカウント名やメールアドレスもきちんと規則性をもたせるようにし、誰がみても理解できるような状態を目指した結果、以下のようになりました。

f:id:yo_aibou:20191212142246p:plain
(メールアドレスは伏せさせていただきます)

これにより、当初望んでいた「プロダクトから隔離された好き勝手触れるAWSアカウント」と「監査用の専用のAWSアカウント」の用意が完了しました。

ここではCloudTrail等のログを中央集権アカウントに集約する設定については割愛します。ググってください

おまけ:支払いアカウント切り替え

今回の構造化に伴い、AWSの支払いアカウントが本番アカウントから支払い用アカウントに変更となりました。 AWSの営業担当の方にアドバイスをいただき、以下の手順に沿って実施しました。

  1. 支払い用アカウントの作成&AWS Organizationの有効化
  2. 本番アカウントのAWS Organization解散(このとき、ステージングアカウントに支払い方法を設定する必要があります)
  3. 支払い用アカウントのAWS Organizationに本番アカウント、ステージングアカウントを招聘、完了

(この情報については記事冒頭の免責事項も合わせてご確認ください)

2実行後〜3実行前まで、AWSアカウント単体で請求が発生するためご注意ください。 私は事前に社内経理担当者に連絡しており、スムーズに作業ができました。

AWS SSOの導入

さて、上述の通りAWSアカウントを細分化することができましたが、各AWSアカウントへのログインに関して問題が見え始めました。 社内のエンジニア全員に検証環境を触ってほしいと言いつつ、検証用アカウント用のIAMユーザを発行する運用負荷やAWSのアカウント切り替え作業など、煩雑な作業がさらに増えてしまいました。 一般的に、「ログイン専用アカウントを用意し、ログイン後にSwitchロールする」という方法が普及しており、今回もその方法を採用しようかと考えていましたが、何かしらのSingle Sign On(SSO)を導入できないかと検討し始めました。

前職ではとあるIDaaSのSSO機能を利用してAWSにログインしており、SSOの管理画面上で各AWSアカウントへのフェデレーションログインを設定することで、容易にAWSマネジメントコンソールにログインできるような環境ができていました。 つまり、「1度SSOのログイン時に認証するだけでSSOのポータル画面から様々なAWSアカウントにクリックするだけでログインできる」という状態です。 現在残念ながら社内にSSOを導入できていないため、こういった状況には至れていません。 欲を言えば上記SSOサービスを利用しAWSだけでなく様々なSaaSへのログインできる状態を望んでいるのですが、せめてAWSアカウントだけはどうにかしたいと考え、AWS SSOを試験導入(後に本採用)することにしました。

現状、社内でActiveDirectoryやLDAPを運用していないため、ミニマムスタートということでAWSのSimpleADを利用してSSOのユーザ管理を行い、利用を開始しました。 設定についてはAWSのドキュメントを参考にして作業した以上のことをやっていないので説明は割愛します。

AWS SSOの設定が完了し、AWS SSOにログインするとOrganizationsに紐づくログイン可能なアカウント一覧が表示され、マネジメントコンソールへのログインや一時クレデンシャルの払い出しをすることができます。 1度AWS SSOにログインすればセッションが切れるまでの間はアカウントの切り替えは容易です。都度IAMユーザログイン時の多要素認証をする必要はありません。

f:id:yo_aibou:20191212144842p:plain

アプリケーションの追加とIAMポリシー

前職で利用していたIDaaSのSSO機能では、「SAML認証するためにAWSにIDプロパイダーを作成し、ログイン後にどのIAMロールにAssumeRoleするか」という項目を設定する必要がありました。 そのため、新しいAWSアカウントを作成すると

  • ログイン後に適用するIAMロールを作成する
  • SSOにアプリケーションを作成する&SAMLメタデータドキュメントのダウンロードする
  • AWSにIDプロパイダーを作成する
  • IDプロパイダーにSAMLメタデータドキュメントを登録する
  • SSOのアプリケーションにIDプロバイダーおよびIAMロールを設定する

といった作業が必要でした。 例えばOneLoginの設定について、Serverworksさんの以下の記事をご覧いただくとイメージがわくかもしれません。

OneLoginを利用してAWSにSAMLでログインする – サーバーワークスエンジニアブログ http://blog.serverworks.co.jp/tech/2018/06/22/onelogin-aws-saml/

この作業はある程度自動化できるとはいえ、(IDaaS側のAPIが不十分なこともあり)SAMLメタデータドキュメントの交換は自動化できず、ちょっとした運用負荷がありました。 それに対してAWS SSOは、AWS SSO側でSAML認証の設定を自動化できているため、AWS Organizationに新しいAWSアカウントを追加したあとは「どのSSOグループに(どのSSOユーザに)」「どのアクセス権限セットでログイン権限を与えるか」という設定をAWS SSO管理画面上で30秒ほどポチポチするだけでAWS SSOポータル画面からAWSにログインすることができます。

この「アクセス権限セット」というのはいわゆるIAMロールのようなもので、AWS SSO側でIAMポリシーを一括管理しています。 AWS SSOとAWSログインアカウントを紐付ける作業を実施すると、アクセス権限セットに登録されたポリシーを元にAWSログインアカウント側でIAMロールが生成され、IDプロバイダーの生成・紐付けもやってくれます。 細かなリソースの管理は難しいですが(後述)、SSOにログインするユーザのグループをある程度まとめられるならとても便利な機能となっています。

トレタでの現状のログイン先アカウントとアクセス権限セットについては、各職種ごとにグループを作って以下のように分けています。 (やや不正確なところはありますが概ねこのとおりです。)

AWSアカウント 職種 付与権限セット
本番アカウント 正社員インフラ担当 Admin用権限セット
本番アカウント 正社員開発者 Developer用権限セット
ステージングアカウント 正社員インフラ担当 Admin用権限セット
ステージングアカウント 正社員開発者 Developer用権限セット
検証用アカウント 正社員インフラ担当&開発者 Admin用権限セット
支払い用(SSO管理用)
監査アカウント
SREリーダーなど限られた人のみ Admin用権限セット
支払い用アカウント 正社員経理担当 支払情報閲覧権限セット

多要素認証方法の追加(Email → 認証アプリ)

導入当初、AWS SSOはユーザ管理DBにSimpleADを利用するとEmailで認証コードが送られる形式の多要素認証しか選択できませんでした。 ログインするたびにメールの受信箱を確認し番号をコピペするという作業が必要でした。

f:id:yo_aibou:20191212154531p:plain
こういったHTMLメールが送られてきます

が、今年11月に入ってから認証アプリ(Google Authenticatorなど)を利用した多要素認証を設定することができることを気づきました。

https://aws.amazon.com/jp/about-aws/whats-new/2019/10/increase-aws-single-sign-on-security-with-multi-factor-authentication-using-authenticator-apps/

AWS SSOを使い始めてから切実に願望していた機能のひとつです。 設定の変更はAWS SSO稼働アカウント(要は親アカウント)のマネジメントコンソールにログインし、ボタンポチですぐにできます。

f:id:yo_aibou:20191212145927p:plain
見慣れたMFA設定画面 in AWS SSO

しかも素晴らしいことに、

  • 認証アプリを利用しない場合でも多要素認証を強制でき、その場合はメールアドレス宛に認証コードを送信する
  • 管理者だけでなくユーザ自身が認証アプリを登録することが可能

という設定が織り込まれています。 新入社員用等に、SSOユーザ作成時はメールアドレスによる認証で設定しておき、本人が望めば認証アプリを自分自身で設定してもらうというルールにしておくことで、SSOユーザも管理者も楽な状態を維持することができました。

awscli v2(preview)がSSOログインに対応

2019年10月後半頃に「awscli v2のインストール方法」について紹介された記事が上がってきました。 このときは「ふーん、Pythonがバンドルされるのか」ぐらいの気持ちでしかなかったのですが、直後に驚きのリリースが発表されました。 そう、awscli v2とAWS SSOの連携です。

https://aws.amazon.com/jp/about-aws/whats-new/2019/11/use-the-aws-cli-v2-preview-with-aws-single-sign-on-to-increase-developer-productivity/

それまでは、AWS SSOの画面に表示されるクレデンシャルを ~/.aws/credentials にコピペするという運用をしており、作業の手間とプロファイル名書き換え等による事故のリスクを抱えた状態で運用していました。

f:id:yo_aibou:20191212142405p:plain
AWS SSOの画面にAWS_ACCESS_KEY_IDなどが表示される

できるだけ事故を減らすために ~/.aws/config で role_arn と source_profile を使うやや黒魔術的な取り組みをしてたりしました。 説明がめんどくさいので、気になる方は勉強会等で捕まえていただければお話します。

実際にawscli v2とAWS SSOの連携機能を使ってみて利便性は格段に向上しました。 イメージをお伝えすると firebase login:ci とほぼ同等のことを実現しています。 手元のコンソールで aws2 sso login --profile [プロファイル名] と打つことでブラウザの認可画面に遷移し、許可ボタンを押すとSSOのアクセストークンが発行され、そのアクセストークンを使ってフェデレーションログインを実現しています。

f:id:yo_aibou:20191212161925p:plain
SSOログインに対応した ~/.aws/config の設定

f:id:yo_aibou:20191212162032p:plain
aws2 sso login コマンドを打つとAWS SSOへのログイン画面を通過した後にこの認可画面が表示される。認可するとアクセストークンがファイルに保存される

f:id:yo_aibou:20191216114952p:plain
aws cli(v2)の実行コマンド。SSOのアクセストークン発行のみなのでprofile毎にsso loginする必要はありません

しかし、普段よく利用するAWS操作系のツール(Terraformやeksctlなど)が残念ながらSSO用のプロファイル設定に対応していないため、引き続きAWS SSOの画面から一時クレデンシャルをコピペする作業が必要となります。

AWS Organization外のAWSアカウントをAWS SSOへ参画させる

株式会社トレタはトレタCC株式会社(以降トレタCC)という子会社をもち、トレタCCのサービス展開にもAWSを利用しています。 しかしながら、会計上の理由によってトレタCCのAWSアカウントを株式会社トレタが管理しているAWS Organizationには所属していません。 そのため、トレタCCにログインする必要のある社員にのみIAMユーザを発行しているという状況です。 ですが、トレタアカウントとトレタCCアカウントの両方を行き来する社員も存在するため、可能な限りログインの窓口を1つに絞りたいと考えています。 この状況は多くの会社に当てはまるのではないでしょうか。

AWS SSOでは、Organizationに紐付いたAWSアカウントだけでなく、Organization外のAWSアカウントにフェデレーションログインすることも可能のようです。 SAML ProviderやSAMLメタデータアップロードなど、通常のSAML連携の設定を実施すれば連携可能だと思われます。 (本当は設定済みのものをお見せしたかったのですが、現在トレタCCのアカウントとAWS SSOの連携は実施していません。社のポリシーや法務・経理周りの許可がおり次第実施する予定です。)

f:id:yo_aibou:20191212154324p:plain
SSO管理画面のアプリケーションより、External AWS Accountから連携設定できます

AWSタグを利用した社外協力者への限定的な権限付与

AWS IAMでは、これまで多くの利用者に利用されてきたRBAC(ロールベースアクセス制御)だけでなく、ABAC(属性ベースアクセス制御)を提唱しはじめました。 これは、今まで「ロールやユーザごとにポリシーをわける制御」であったのが、「ユーザやロールで同じポリシーを共有し、IAMとリソースに付与されたタグ(属性)の一致をもって制御する」という方式です。 ABACを実現するには、各ロールやユーザにタグを付与するか、re:Invent2019直前に発表されたSession Tagsを利用する必要があります。

RBAC、ABACやSession Tagsの説明はクラスメソッドさんのブログに詳しく記載されていますので、そちらをご参照ください。

[UPDATE]フェデレーションでタグを渡せるSTSの新機能Session Tagがリリースされました #reinvent | Developers.IO

https://dev.classmethod.jp/cloud/aws/session-tag-on-id-federation/

ただ、残念ながらAWS SSOでABACを実現することは難しいです。 何故ならば

  • アクセス権限セットによって作成されるIAMロールにタグを付与することが現状できない
  • SessionTagsを利用するためのSAMLアサーションの属性情報を設定できない

という状況のためです。 AWSアカウントが少なければ、自動生成されるIAMロールを追従してタグを付与することでABACを実現できますが、AWSアカウントやアクセス権限セットの組み合わせが多くなるととてもじゃないけど運用が回りません。

「おそらく近い将来AWS SSOのSession Tagsサポートがリリースされるだろう」という予想のもと、一旦リソースに特定のタグを付与することで「ABACっぽい記述のRBAC」を実現しました。 モデルとして「トレタNowチーム担当の業務委託にSSM Session Managerを特定のEC2にのみ実行可能」を考えます。

今回は SsoPermissionForContractEngineers:toreta-now というタグを対象のEC2インスタンスに付与し、アクセス権限セットのポリシーの一部(ステートメント)を以下のように設定しました。

{
    "Effect": "Allow",
    "Action": [
      "ssm:StartSession", "ssm:TerminateSession"
    ],
    "Resource": "*",
    "Condition": {
        "StringEquals": {
            "aws:ResourceTag/SsoPermissionForContractEngineers": "toreta-now"
        }
    }
}

これにより、SsoPermissionForContractEngineers:toreta-now が付与されたEC2インスタンスにはSession Managerでログインでき、そうでないインスタンスにはログインできないことを確認しました。 現状まだ実務での利用に至れていませんが、対象のリソースにこのタグを付与したり許可アクションを設定できれば、実運用を始めようと考えています。 (Session Tags対応がリリースされれば、ステートメントの toreta-now${aws:PrincipalTag/SsoPermissionForContractEngineers} に置き換えることで同様のことができると思われます。)

この検証で思い当たる点、気をつける点についてわかったことがあるので以下にまとめます。

まずリソース側のタグ管理は、できるだけコードベース管理されていることが望ましいです。 最近だとTerraformやCloudFormationを利用してリソース管理をされている方が多いと思いますが、ABACを実運用する上で必要になると感じました。

また、ポリシーのステートメント文内で利用する条件キーが、AWSサービスによって異なる点に気をつけてください。 例えば、同じEC2にアタッチしているタグであったとしても、ssm:StartSessionaws:ResourceTag であるのに対し、 ec2:StartInstancesec2:ResourceTag だったりします。 各アクションと条件キーの対比については、以下のページに各サービスごとにまとまっていますので、ポリシーを作成するときに参考にしてください。

AWS のサービスのアクション、リソース、および条件キー AWS Identity and Access Management

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_actions-resources-contextkeys.html

とくにS3の条件キーはかなり複雑で、s3:GetObject の場合は s3:ExistingObjectTag 条件キーが必要で、s3:PutObject の場合は s3:RequestObjectTag 条件キーが必要になります。 この s3:RequestObjectTag 条件キーがなかなか厄介で、この条件に合致した場合アカウント内のあらゆるS3バケットにPutObjectできてしまうということを観測しました。

そのため、どうしてもポリシー側でリソース名を記述必要があると判断し、一旦PutObjectを否認するようにしています。 どうしても必要な場合は sts:AssumeRole で専用ロールになってから作業するという逃げ道を考えています。

考察と今後の展望

以上、AWS SSOを導入したお話について執筆しました。 とくにAWS SSOを導入するべきだと思われる状況について私見を述べます。

  • AWSへログインする人が20名を超えている
  • 管理しているAWSアカウントが複数(3以上)あり、それらは1つのAWS Organizationにまとめられる
  • それらのアカウントにログインする役割の人が3種類以上(例:AWS管理者、社員エンジニア、業務委託エンジニア)
  • OneLoginやOktaといったIDaaSのSSO機能を導入していない

これに当てはまる組織に対しては大きな運用負荷軽減効果を望むことができますので、ぜひAWS SSOをご検討されてはいかがでしょうか? とくに「AWSアカウントが5以上あり、ログイン用アカウントからSwitchロールして目的のアカウントにログインしている」という組織の方にはおすすめしたいです。

また、すでにIDaaSを導入されている組織におきましても、AWSアカウントを非常に多く管理されている場合(目安として10以上)、AWS SSOを利用することでコネクター設定の作業負荷が軽減できるかもしれません。 私の経験としても、前職で利用していたIDaaSよりAWS SSOの設定作業が非常に楽だと感じました。 AWSのみAWS SSOで他はIDaaSのSSO、といった使い分けをすることも視野に入れておけばよかった、という気持ちもあります。 AWS SSOでも既に設定されているActive Directoryと連携することができるため、ぜひ検証してみてください。


ところで、本記事を執筆中にFinatextさんが興味深いアドベントカレンダーを公開していました

FinatextにおけるAWSのガードレール戦略の紹介 - Finatext - Medium
https://medium.com/finatext/finatext-aws-guardrail-2705cd1e3679

この記事を拝見させていただき、すぐに「これはAWS SSOを導入したら相当楽になるはず!」と直感しました。 ぜひご検討いただけますと幸いです笑(と言いつつ免責事項をご確認の上、きちんとご調査いただきますようよろしくお願いいたします。)

逆に、「AWS OrganizationのSCPを導入した」ことについては、 その機能があったことを完全に失念しており はっとさせられました。 アカウントレベルで制限したいアクションをアクセス権限セットで封じるのはかなりめんどくさかったので、大変参考になりました。後日設定してみます。

これとABACを組み合わせることでいい感じのアクセス権限払い出しができそうな気がしています。


ここまでAWS SSOのポジティブな話を記載してきましたが、もちろん問題もあります。

まず、ひとつのAWS OrganizationにひとつしかAWS SSOを設定できません。 至極真っ当な仕様であるとは思いますが、これによりAWS SSOの検証・実験のハードルが高いことが問題点であると感じました。 完全に独立したアカウントを用意すれば検証は可能ですが、稟議・経費精算といったワークフロー上の手間が増えることになります。

もうひとつは、前述の通りABACが現状難しいことと、アクセス権限セットのポリシーをコードベース管理できないことです。 前者はおそらくそのうちリリースされるとして、後者は管理のためにAPIの提供を望みます。

また、現場のエンジニアが片手間でSSOを設定することは負担が大きく、せめてユーザ管理をActiveDirectoryやLDAP等を利用して情シス側に移管できればと思っています(何卒宜しくお願いします)。


最後にアドバイスとして、組織の運用を大きく変えるような仕組みを導入したいときは「まず自分を生贄に捧げる」という覚悟を持って取り組むと、それを運用し始めたときの利点欠点の理解が進みます。 私の場合、それまで使っていたIAMユーザを捨ててすべてAWS SSOに寄せることで良い点・悪い点を実感として得ることができ、これらを同僚に共有することで本当に導入すべきかどうかをきちんと議論できたと思います。 また、良い点ばかりに目を向けていると導入後に困難な事象に遭遇した場合に不幸を生む可能性があるので、「きちんと悪い面にも目を向ける」「どうしてもやめたくなったらどうするか」ということをきちんと考えて、共有しておけば、自分がいなくなったとしても

終わりに

この記事が投稿される頃には Bears @ Packers が終わっているはずで、その試合の結果次第では地区優勝に大きく近づきます。 Packers勝利&Rams敗北でプレーオフが確定しました!残り2戦のうち1勝すれば地区優勝です。 ただ、プレーオフに向けて不安要素がかなり多い(とくにラン守備とフリーになれないWR陣)ですが、1発勝負ぜひ勝ってスーパーボウルに行けたらと思ってます。 (でもWeek12でボコボコにされた49ersと飛び道具テイサム・ヒル擁するセインツには勝てる気がしない)

Go Pack Go!

店舗検索APIにページネーションを追加した話

トレタ Advent Calendar 2019 の14日目の記事です。

こんにちは。ニュージーランドで1週間息抜きをしてきた2日後に、ボケボケの頭のまま 社内テックトーク で決して得意とは言えない競技プログラミングに関して話すことになってしまい、自分の計画性のなさを恨んでいるモバイルアプリエンジニアの大野です。 帰国した2日後に火山が噴火して驚きました。
本Advent Calendarには実に3年ぶりの登場となります。


私が主に関わっている Toreta now では、先日公開されたver1.2.0 から店舗を検索する機能にページネーションが追加されました。ページネーションが必要なほどサービスが成長してきたんだなーと思うと嬉しい限りです。
が、そのページネーションを実現するためのAPIの設計で一悶着あったので、本記事ではそのことについて紹介します。

登場人物

  • サーバサイドエンジニア (以下 「サ」)
  • 私とは別のモバイルアプリエンジニア (以下「モ」)

なお、GETのAPIなので実際にはリクエストパラメータはqueryで渡していますが、可読性のためにここではjson形式で表示することとします。 また、実際のAPIとはパラメータやデータのフォーマットなどが異なります。

最初期のバージョン

私「店舗検索は(中略) な感じだし、offsetベースよりはcursorベースのページネーションのほうが良いですよね。ってなわけで、こんな感じでリクエストパラメータとレスポンスを考えてみたんですがどうでしょ。」


北緯45°, 東経135°の位置にいるユーザーが、付近の店舗を検索する場合

最初のリクエストには、席数と予約の開始時間とユーザーの現在地を入れる。

# 1回目のリクエスト
{
    "seats": 4,
    "starts_from": "2019-12-14T21:00:00+09:00",
    "location": {
        "latitude": 45.0,
        "longitude": 135.0
    },
    "next_token": null
}

最初のリクエストへのレスポンスには、店舗一覧の情報と次回の検索用の next_token が入る。 next_token がnullだったら、ページネーションが完了したと判断する。 next_token には次回の検索の先頭のindexが入る。

# レスポンス
{
    "restaurants": [
        {
            "name": "レストラン1"
        },
        {
            "name": "レストラン2"
        }
    ],
    "next_token": "5"
}

2回目以降のリクエストでは、next_token 以外には最初のリクエストと同じ値を入れる。 next_token には前回のリクエストへのレスポンスに入っていた値をそのまま入れる。

# 2回目以降のリクエスト
{
    "seats": 4,
    "starts_from": "2019-12-14T21:00:00+09:00",
    "location": {
        "latitude": 45.0,
        "longitude": 135.0
    },
    "next_token": "5"
}

next_token の実装がどうなっているかはクライアント側では意識せず、単に受け取った値をそのまま使うだけです。 こうすると、UI以外のページネーションのロジックを全てサーバ側に押し付けることが出来るので、クライアント側では楽ができます。

サ「頭の中では大丈夫そうという気持ちになっています」 (原文ママ)
私「あざっす」

モさんによる指摘

モ「ちょっと待ってそれだと最初のリクエストと2回目以降のリクエストで location の値が違ったらどうなんの。ユーザーの位置が変わったら出てくる店舗も変わっちゃうでしょ。」

私「確かに。必ず同じ値のlocationを入れるようにする、って制限を付けるのもAPIとしては微妙っすね。じゃあこんな感じでどうでしょ。」


北緯45°, 東経135°の位置にいるユーザーが、付近の店舗を検索する場合 改

最初のリクエストには、席数と予約の開始時間とユーザーの現在地を入れる。

# 1回目のリクエスト 改
{
    "seats": 4,
    "starts_from": "2019-12-14T21:00:00+09:00",
    "location": {
        "latitude": 45.0,
        "longitude": 135.0
    },
    "next_token": null
}

最初のリクエストへのレスポンスには、店舗一覧の情報と次回の検索用の next_token が入る。 next_token がnullだったら、ページネーションが完了したと判断する。 next_token には、次回の検索の先頭のindexと検索のlocationが入る。

# レスポンス 改
{
    "restaurants": [
        {
            "name": "レストラン1"
        },
        {
            "name": "レストラン2"
        }
    ],
    "next_token": "5, latitude=45.0, longitude=135.0"
}

2回目以降のリクエストでは、seats, starts_from には最初のリクエストと同じ値を入れる。 next_token には前回のリクエストへのレスポンスに入っていた値をそのまま入れる。 サーバ側では next_token の値をparseして使う。

# 2回目以降のリクエスト 改
{
    "seats": 4,
    "starts_from": "2019-12-14T21:00:00+09:00",
    "next_token": "5, latitude=45.0, longitude=135.0"

}

私「というかこれだと2回目以降のリクエストに必要なパラメータがどれかわかりにくいし、いっそのこと全部 next_token に入れちゃっていいんじゃね? 」

サ, モ「確かに」


北緯45°, 東経135°の位置にいるユーザーが、付近の店舗を検索する場合 改々

最初のリクエストには、席数と予約の開始時間とユーザーの現在地を入れる。

# 1回目のリクエスト 改々
{
    "seats": 4,
    "starts_from": "2019-12-14T21:00:00+09:00",
    "location": {
        "latitude": 45.0,
        "longitude": 135.0
    },
    "next_token": null
}

最初のリクエストへのレスポンスには、店舗一覧の情報と次回の検索用の next_token が入る。 next_token がnullだったら、ページネーションが完了したと判断する。 next_token には、次回の検索の先頭のindexと全ての検索条件が入る。

# レスポンス 改々
{
    "restaurants": [
        {
            "name": "レストラン1"
        },
        {
            "name": "レストラン2"
        }
    ],
    "next_token": "5, latitude=45.0, longitude=135.0, seats=4, starts_from=2019-12-14T21:00:00+09:00"
}

2回目以降のリクエストでは、next_token に前回のリクエストへのレスポンスに入っていた値をそのまま入れる。 サーバ側では next_token の値をparseして使う。

# 2回目以降のリクエスト 改々
{
    "next_token": "5, latitude=45.0, longitude=135.0, seats=4, starts_from=2019-12-14T21:00:00+09:00"
}

私, サ, モ「良さそう」


というわけで最終的にはこの内容で落ち着きました。

サーバ側のロジックがちょっとややこしいことになってしまい、実際に実装途中でいろいろバグりましたが、仮にバグがあった場合サーバ側はすぐに直して反映させることが出来るのに対し、モバイルアプリは審査の期間が挟まれたり何よりユーザーが最新版にアップデートしてくれるとは限らなかったりするため、クライアントにモバイルアプリが含まれることがわかっている場合にはややこしいロジックをサーバー側に押し付けるのは正しい設計だと信じています。

加えて、こうすることでサーバ側、クライアント側ともに状態を持たずにやりとりが出来るので何かと都合が良いことに気付きました。 (クライアント側は一旦 next_token を保存しないといけませんが。) 何となく、関数型プログラミングと近い考え方なのかな、と思ったりしています。

終わりに

ややこしいロジックを正しく実装してくださったサさん、及び的確な指摘でver1.2.0をリリースまで導いてくださったモさんに、この場を借りてお礼申し上げます。 明日はフロントエンドエンジニアの上垣がなにか書いてくれます。

トレタでチームリーダー始めて半年が経ちました

この記事はトレタ Advent Calendar 2019の13日目です。Friday the 13th q(-::-)

どうもどうも!先週ボウリングたったの1ゲームで腰を痛めて歩けなくなったサーバーサイドエンジニア 石谷です! ちょうど先週はトレタリモートウィークのトライアル期間だったので家から出ず、ちょくちょく休暇も頂きつつ乗り切りました! 安静にしていたおかげで今週は元気に出社しております (´ω`)

自己紹介的なのは以前社員インタビューして頂いたものがあるのでご参照下さい。

さて今回のお題ですが、僕はトレタで「メディア連携開発チーム」というチームのリーダーを任せて頂いています。チームリーダーを始めてちょうど半年くらい経つので、来期に向けての振り返りなど書いていければと思います。 記事の中から少しでもトレタの職場の雰囲気が伝われば幸いです。

メディア連携開発チームってなに?

「飲食店と飲食法人の繁盛のためにトレタを介してあらゆるメディア活用を実現する」というミッションを担っているのがこのチームです。店舗の繁盛最大化のためには広告メディアも非常に大事な集客元になってきます。

ひとつの例として想像してみて欲しいのが、もしあなたが複数の友人から断続的にメッセージが送られてきて、それぞれのメッセージを確認してカレンダーに24時間365日みっちり予定を登録することを考えたらどうでしょう?僕なら発狂しそうです (ヽ´ω`)

この例は実際に店舗が抱える課題としてチームが取り組んでいるものの一つです。ある飲食店が広告メディアとして5つの予約受付が可能なグルメサイトを使用している場合、それぞれのグルメサイトから入った1予約に対して1件ずつメールが送られてくるとしたら、それを予約台帳に転記するのってかなり大変そうですよね。 トレタは台帳アプリを中心とした予約管理システムを飲食店に提供しています。広告メディアとトレタがシステムとして連携していれば、わざわざ転記する必要がないので楽ちんですね。 そういった広告メディアの活用のためにシステムを開発・運用するのが「メディア連携開発チーム」です。

チームリーダーってなにするの?

なにをするんでしょう?そこを考えるところから始めました。

トレタの技術組織がチーム体制を敷いたのは今年に入ってからのことで、初めは複数チームを1人のマネージャーが管理する体制でした。しばらく運用して次第に課題が見え始めたので1チーム1リーダーの体制へと移行しました。僕がチームリーダーになったのもこのタイミングです。もともとチームが担当するプロダクトは決まっていて、4つのプロダクトをうちのチームで開発・運用していました。僕を含め4人のチームメンバーのうち3人は入社日が浅く、自分自身も習熟度が低いなかでチームリーダーとして何をするべきか考えました。

もともとチームに所属していたメンバーはみんなめちゃくちゃ優秀だし、自走できるし、お互いフォローし合える人たちなので「いやいやチームリーダー要らないのでは?」っていうのが頭をよぎった瞬間もありました。でもいろいろ考えたり、HIGH OUTPUT MANAGEMENTを読んだりした結果「チームのアウトプットを最大化できる状態を作る人」がチームリーダーの役割だなと考え、そのために何が必要かを逆算していくと、色々と必要な要素が見えてきました。ちなみに「チームのアウトプットを最大化できる状態を作る人」というのは今振り返ってもチームリーダーの役割としては間違っていないかなと思います。

またこのときマインドマップを使って思考を整理していました。整理の仕方としては「目指したい事柄」に対して「正の要因」と「負の要因」をリストアップしていくと大体なにが大事か見えてきます。 最近はwhimsicalでマインドマップ書いてるんですが、使いやすくてめっちゃ良い感じです!

f:id:issuy:20191211194241p:plain
マインドマップ

チームのアウトプットを最大化できる状態を作るために必要なことって?

思考を整理した結果「本人が動機をもってタスクに取り組めていること」と「判断基準が合っていること」さえクリアしていれば、それ以上の余計な管理は不要かなと考えました。

例えばあなたが資料作成のタスクを担当しているとします。あなたは1時間の集中タイムを取っていてこの時間は筆が乗る貴重な時間です。そんな中、10分に1度「どう?捗ってる?」ってリーダーに聞きに来ます。どうですか?すごく嫌ですよね?僕がもしプログラミングに集中できてる時間にそれをやられたら「あれ?どこまで考えてたっけ?」とか「うっ、、、今ノッてたのに。。。」とか「ま、また来たのですか?」ってなっちゃうと思います(ヽ´ω`)

この例ではなにが起こっていたのでしょうか?まず「筆が乗る状態」について考えてみると、本人が「自分が何を書きたいか、何を書けば良いかを分かっている状態」だと思います。つまり「動機をもって取り組んでいる状態」にあって、このような「集中できている状態」を作ることがリーダーのメンバーに対する最大の仕事だと考えました。

次になぜリーダーは「どう?捗ってる?」って聞きに来るのでしょう?9割くらいは「期待するアウトプットが出そうか、本人が何か課題を抱えていないか、タスクが納期までに終わりそうか」などのチェックだと思います。 チームリーダーはチームのミッションに責任を持っているので、メンバーのアウトプットはそのミッションを果たせるものである必要があります。そのためにクオリティラインがズレないように軌道修正し、課題を抱えていそうであれば解決を一緒に考え、納期に間に合わなそうであれば改善策や納期の調整・交渉などを検討するのもリーダーの仕事です。このときリーダーとメンバー本人が「同じ判断基準」を持っているとしたら、アプローチは違えど同じ結果がアウトプットできそうです。それならばリーダーは「どう?捗ってる?」って聞きに行く必要がなくなるので、メンバー本人は「集中できている状態」をより長く保つことができるはずです。

つまり「集中できている状態」を作ること、そしてその時間をより長く保つことが「チームのアウトプットを最大化できる状態を作るために必要なこと」になりそうだなと整理しました。

※ちなみに「どう?捗ってる?」って聞きに来るのの残りの1割は恐らくコミュニケーション取りたいだけです。。。いつも雑な話ばっかで申し訳ない( ˘ω˘)

実際にやってみてどうだった?

チームとして結果が出せたし、体感では7割はうまくいったのではと思っています。本当に余計な管理を必要とせず、自走できるチームだったと思います。むしろチームメンバーの皆さんに助けてもらう方が多かったことに感謝しかないです(´ω`)

[Good]みんな動機をもってタスクに取り組めていた

そもそもみんな優秀だしモチベーションが高かったのがあります。今期の注力プロジェクトではみんな主体的に考え行動してくれていたし、かなり複雑な仕様のものをなんとか運用に乗せることができたのはメンバーの皆さんが主体的にチーム内および隣接するチームの皆さんと連携してくれていたからでした。隣接するチームの皆さんにも感謝です。僕が貢献できたところと言えば「誰が何をやるか?」や「どうやったら課題を解決できそうか?」を理由とセットで提示できた部分でしょうか?

[Good]判断基準を合わせる動きが上手く回っていた

「必要なタイミングでガッツリ会話し、方針が固まったら手を動かすだけ」というワークフローが自然とできており、それがうまく作用して質の高いアウトプットが多かったなと思います。また、来期に向けた新しいプロジェクトを開始するタイミングでも「なぜやるのか?なにを大事にするのか?」など認識を合わせながら始動できており、実際そのプロジェクトはいま軌道に乗って開発を進めてくれています。先頭に立って進めてくれているみなさんに感謝。みんな優秀なので判断基準を揃えるのに大したハードルを感じなかったように思います。むしろそうしたやり取りの中でみんなが持つ知識や知恵が共有できてすごく学びが多かった。

[Bad]チームリーダーである自分自身が集中できる状態を構築するのに出遅れた

チームリーダーである僕自身もメンバーとしての役割も持っているため、急な状況の変化に対応しきれなかったタイミングがありました。トレタはまだまだ発展途上というのもあり、短い期間に状況が変わることもあります。組織の構成が変わったり、人の配置が変わったり、チームが持つプロダクトが変わったり。その変化に合わせてチームメンバーの役割分担などを考えるのですが、1人が持つプロダクトの数が増え過ぎると「集中できない状態」を作ってしまいます。これは先程の「10分に1度進捗を聞かれる」を「10分に1度問い合わせが来る」に置き換えたイメージです。実際にプロダクトが増えたことがあり、今のチームの状態に沿わないと判断したため委譲手段を模索しつつ僕自身が担当することに。今期の注力プロジェクトも担当していたことも相まって、見事に「10分に1度問い合わせが来る」状態に陥りそれ以外のことに手が付けられなくなってしまいました。これはチーム外からの助力もあり現時点では徐々に解消している部分もありますが、状況が変わればまた起こるので継続的に対策を打っていく必要がありそうです。

[Bad]一部のメンバーに集中できる開発環境を用意できなかった

社外から協力頂いているメンバーもおり、開発環境の制約などの関係で万全の開発環境を用意できていない現状があります。前述の理由もあり時間を取って整備する余力がないタイミングだったので、これは来期改善したい項目です。折角協力してくださっているのに「本人も最大の力が発揮できない、こちらも解決したい課題をお任せできない」という状況ではお互い不幸ですよね。優先度高く対処すべきだと認識しています。

[Bad]隣接するチームの負荷を把握できておらず、そのメンバーにある期間高い負荷をかけてしまっていた

これは個人的にかなり反省しています。今期の注力プロジェクトの繁忙期に、自分のチームと連携して動いてくれている隣接するチームのメンバーに高い負荷をかけてしまっていたこと、そしてそれをちゃんと認識できていなかったことがありました。まず把握すること、そしてケアや改善策を検討できていることが理想だと思うので、以後注意しなければと思っています。

[Bad]一部のメンバーが手持ち無沙汰なタイミングを作ってしまった

今期の後半、チームメンバーの人数が自分を含めて7人になった辺りに起こった現象です。タスクはガンガン消化されるのですが、将来に向けた新規プロダクトの要件検討や既存プロダクトのタスク整理が進んでおらず「手が空いたので次は何をしましょうか?」に即座に対応できなかったことがありました。「やることがない状態」が「集中できている状態」と最もかけ離れているので、チームリーダーとしては見事に責務を果たせていないことになります。なので常にタスクの残弾を持った状態にすることが来期の目標です。

まとめ

こうやって書き出してみると、半年チームリーダーをやってみて上手くいった部分もそうでない部分も色々とありました。もちろんここに書けなかったこともまだまだあります。その中で色々と学ぶ部分もあり、思考整理することで沢山の気付きもありました。 来期に向けての課題も整理できたので、(自分も含め)みんなが集中できる環境を作り出してチームがより高いアウトプットを出せる状態に持っていければと思います!

ここまで読んで頂けたみなさん、いかがでしたか?少しでもトレタの職場の雰囲気が想像できたでしょうか?僕はもうすぐトレタに入社して1年が経ちます。入社後数ヶ月の僕がリーダーを任せてもらえてやってこれたのも、来期に向けて頑張っていこうと思えるのも、トレタで一緒に働くみなさんのお陰だなぁとつくづく思うばかりです。 そんな職場に少しでも興味をもって頂けたなら、是非みなさんのお力を貸して頂ければ幸いです。 募集職種一覧はコチラ

© Toreta, Inc.

Powered by Hatena Blog