MSのblogにコメントが掲載された話
この記事は SmartHRのカレンダー | Advent Calendar 2023 - Qiita シリーズ1の 9日目の記事です
2023/08に publishされた MSのblogにコメントが掲載されました。 techcommunity.microsoft.com
まだ正式版としてはリリースされていないですが、この機能の開発に当たって起きた出来事などを振りかえってみます。
ことのはじまり
- 2019年 MSのADのGMの方などが来社するイベントが発生。
- AADとのプロビジョニングの可能性についてなど
- 2020年のwrap up blog にロゴが掲載される。
時は流れ
- 2022/03 Oktaと HR Driven Provisioningの連携を開始
- 2022/10 Microsoft Ignite 2022 のとあるセッションの中でInbound Provisioning APIの Launch Partnerとして紹介
開発中におきたこと
- SCIM WGへのお誘い
- APIの開発遅延からの実装スタートの遅延
- Teamsで直接開発チームとやりとりできるストレスフリーさ
- 仕様の確認や、追加で欲しい機能などの要望を直接あげられる
- Office Hourでの英語がなきゃだめだ感.. (Teamsの auto transcriptでギリギリなんとか)
所管
- HRシステムの認証周りを担当していたら Microsoftのblogにコメントと名前が載りました
- こんなこともおきるのですねえ
B2B SaaS におけるプロビジョニング
これは、Calendar for SmartHRの村だよ | Advent Calendar 2021 - Qiita 17日目の記事です。
B2B SaaS のアカウント管理にもとめられるもの
- 必要なタイミングで必要とする従業員へ付与
- 不要となったタイミングで剥奪
アカウント発行/SSO
プロビジョニング
- アカウント発行・剥奪の自動化のためのプロビジョニング
- 通常のSaaSにおいては、
- IdPにアカウントがある前提
- IdPにアカウントがあり新規発行されるアカウントは各SaaSにプロビジョニングされアカウントが作られる
- 権限付与は対象のユーザーの属性によって管理されたりする
SCIM
- プロビジョニングの標準仕様としての SCIM
- https://datatracker.ietf.org/doc/html/rfc7644
OIDF-J の WGによる資料
従業員の情報の源泉
- 新しく社員が入社し、その対象の従業員の情報を得るタイミングはどこになるか
- 人事チームなどが握っている情報
- 人事チームが管理するHRシステムなどにまず情報が登録される
- IdPにどのタイミングで付与されるのか
- 人事チームから情シスチームなどに情報が伝搬され IdPのアカウントや社用のメアドなどが発行される
- IdPが アカウント管理の中心にはいることとになるが、その源泉たる従業員の情報は HRシステムが最初に持つことは多いと思われる
入社時のフロー概念
退職時のフロー概念
入社情報と退職情報を適切に捉える必要がある
- データの源泉となるところを起点とする方がよさそう
- HR Driven Provisioning と呼ばれる概念
- 従業員の入退社のイベントを捉えてprovisioningをする
SmartHRの例
- SmartHR のアカウントは入社前に発行されて退職後も使って欲しいという思想で出来ている
- 入社時に情報を入力・確認してもらう
- 退職後も在職期間中に発行された情報を閲覧する
- IdPとの連携は SAML/SSO機能として 2019年にリリースしているが、SSOのところのみをサポート
- プロビジョニングに関しては、要望をもらっていたものの、IdPとどちらのアカウントが先に発行されるべきなのかの結論が出ずpend状態
- Okta社からの提案で Provisioningの featureの実装を再開 (2021/07)
- HR Driven Provisioning でデータの源泉となるものを SmartHRから Oktaへインポートする
- Oktaでアカウントが発行されると SmartHRへ Okta経由で SSOできるようになる
- closed beta扱いで SmartHR社でのテストを実施予定
まとめ
ISUCON11 予選にでた #isucon
今年も ISUCONに参加した。 去年に引き続き一人で参加。
やったこと
- ruby 実装へ変更
- oj の導入
- newrelicの導入
- indexの追加
- dbサーバを別インスタンスへ
- mariadbだったのでmysql8もしくはpostgresql へのいれかえ実施を試みる
- うまくできず放置
失敗
- service 再起動処理がtypoしていて実はstopがうまく行っておらず読み込めてない事案
- しばらく経ってから気づいて、そうそうに仕込んだ変更でbugが混入していてあとから500に気づく事案
- 原因がわからなくてしばらく悩んだ..
- RDBMSの入れ替え作業
- ここ2年ぐらい実務ではpostgresqlだけなので mysqlのコマンドを忘れまくり
まとめ
- 今年も一人で出てみたけど、やっぱ一人じゃできない課題の大きさだった
- 運営の皆様ありがとうございました!
- 公式テーマソンググッと来るものがありました。
Pull Request に ラベルを付けたい話
これは SmartHR Advent Calendar 2020 - Qiita 7日目の記事です。 日々の開発時に Pull Request を作ったり、またそのレビューをしたり、レビューが終われば リリースしたりすると思います。 その際に、データパッチの実行を忘れないようにしたり、migration が含まれてるリリースになっているかを ひと目でわかりやすくするためのものを改て実装したので紹介です。
TL;DR
やりたいこと
- 特定のファイルが更新されたらその旨をラベルにつけたい
- db/migrate/* のファイルが更新されたら db:migrate ラベルを付けたい
- etc
- base branch が特定の条件であればラベルをつけたい
- 大きめの機能開発をする際に features/*** ブランチをきって開発しているので、そのさいに features/any-branch-name のラベルをつけたい
他の方法
- https://github.com/actions/labeler
- ただし、branch ベースのラベル付与ができない
実装的な課題と解決
- GitHub REST API は Pull Request にふくまれる change files が300までしか取得できない
- lambda の実装時は、 REST API を利用していたので、稀に対象の PR が大きいものになっていると change files が全て取得できない問題があった
- GitHub GraphQL API では300以上取得できる
- ruby 実装の際に、 change files は GraphQL API、 ラベルをつける実装は REST API の実装のハイブリッド構成にした
- また、社内の複数のレポジトリでも動くようにかつ、それぞれの設定ができるようにする
実行時のフロー
- Pull Request の Webhook を受け取る
- payload の event の状態を見る
- 対象の event であれば、 GraphQL API を call して、 change files を全て取得する
- 設定に応じて、ファイルをチェックし、ラベルのリストを作る
- REST API をつかって、 Pull Request にラベルを設定する
実行フローのサンプル
class Githubs::PrLabelerController < ApplicationController def create event = request.env['HTTP_X_GITHUB_EVENT'] unless event == 'pull_request' msg = { message: 'event is not pull_request.' } render json: msg return end # webhook signature github_webhook_secret = ENV['GITHUB_WEBHOOK_SECRET'] if github_webhook_secret http_x_hub_signature = request.env['HTTP_X_HUB_SIGNATURE'] payload_body = request.body.read signature = GithubWebhookSignature.new(secret: github_webhook_secret, http_x_hub_signature: http_x_hub_signature, payload_body: payload_body) if signature.invalid? msg = { message: 'webhook signature is invalid.' } render json: msg return end end payload = JSON.parse(params['payload']) if payload['action'] == 'opened' pr_number = payload['number'] full_repo = payload['repository']['full_name'] owner, repo = full_repo.split('/') change_files = GithubQueryHelper.call(pr_number: pr_number, owner: owner, repo: repo) ::Rails.logger.info("💡 #{change_files}") # extract changes files. file_labels = config.labels[repo.to_sym][:files] targets = {} if file_labels.present? file_labels.each_key do |label| regex = Regexp.new(file_labels[label]) targets[label.to_s] = change_files.select do |v| v unless v.match(regex).nil? end end end # add labels add_labels = targets.select { |_k, v| v.present? }.keys.uniq # extract base branch base_branch = payload['pull_request']['base']['ref'] branch_labels = config.labels[repo.to_sym][:branch] if branch_labels.present? branch_labels.each_key do |label| regex = Regexp.new(branch_labels[label]) next if base_branch.match(regex).nil? add_labels << if label.to_s == 'base-branch' base_branch else label.to_s end end end octokit = Octokit::Client.new(access_token: ENV['GITHUB_ACCESS_TOKEN']) octokit.add_labels_to_an_issue(full_repo, pr_number, add_labels) if add_labels.present? msg = { message: 'ok' } else msg = { message: 'action is not opened.' } end render json: msg end end
GraphQL API をつかった change files 取得サンプル
class GithubQueryHelper attr_reader :pr_number, :owner, :repo, :github_client def self.call(pr_number:, owner:, repo:) new(pr_number: pr_number, owner: owner, repo: repo).call end def initialize(pr_number:, owner:, repo:) @pr_number = pr_number @owner = owner @repo = repo @github_client ||= GithubApi::V4::Client.new(ENV['GITHUB_ACCESS_TOKEN']) end def call retrieve_files_from_pr end private def retrieve_files_from_pr per_page = 100 options = { pr_number: pr_number, per_page: per_page, owner: owner, repo: repo } files = [] total = github_client.graphql(query: init_query(options)) total_count = total['data']['repository']['pullRequest']['files']['totalCount'] end_cursor = total['data']['repository']['pullRequest']['files']['pageInfo']['endCursor'] page = (total_count / per_page.to_f).ceil files << total['data']['repository']['pullRequest']['files']['edges'][0]['node']['path'] i = 0 while i < page options[:endCursor] = end_cursor end_cursor, filelist = exec_query(list_query(options)) files.concat(filelist) i += 1 end files end def list_query(options) <<~QUERY { repository(owner: "#{options[:owner]}", name: "#{options[:repo]}") { pullRequest(number: #{options[:pr_number]}) { files(first: #{options[:per_page]}, after: "#{options[:endCursor]}") { pageInfo { endCursor startCursor } edges { node { path } } } } } } QUERY end def init_query(options) <<~TOTAL_QUERY { repository(owner: "#{options[:owner]}", name: "#{options[:repo]}") { pullRequest(number: #{options[:pr_number]}) { files(first: 1) { totalCount pageInfo { endCursor startCursor } edges { node { path } } } } } } TOTAL_QUERY end def exec_query(query) data = github_client.graphql(query: query) end_cursor = data['data']['repository']['pullRequest']['files']['pageInfo']['endCursor'] files = [] data['data']['repository']['pullRequest']['files']['edges'].each do |edge| files << edge['node']['path'] end [end_cursor, files] end end
おわり
おわり
ISUCON10 予選敗退した #isucon
今年も ISUCON の予選にでた。
今年は、一人で参加した。あと、個人スポンサーにもなってみた。
申し込んだぞい #isucon
— Takumi Kanzaki (@tknzk) 2020年7月22日
ISUCON10 個人スポンサー(特典付き) https://t.co/O2wbBoWsW8 via @PeatixJP
#isucon はじまるぞい pic.twitter.com/186WKQq1VE
— Takumi Kanzaki (@tknzk) 2020年9月12日
やったことメモ
- mysql をapp の instance とは別 instance へ
- app改善してみたけど、最終的にはほぼ revert
- db の index追加
- appのログ出力
- unicorn の unix socket 通信化
- nginx のログを ltsv にして alp でざっとログを確認
- newrelic agent を突っ込んで把握できるように
- unicorn を -E production で起動
最終的には、アプリの改善はほぼできず。。
起きたこと
- 103の instance をmysqlようにしていたら 17:30過ぎに突如 instance の応答が失われる..
- しばらく放置して運営にチケット投げてみる
- チケット投げたら、突如復旧...
感想
- やっぱり、一人じゃ厳しいので来年はチームででるぞ..!
- 運営のみなさま今年もありがとうございました!!
B2B SaaS (MTWA) でのアカウントモデルの検討事項
この記事は、SmartHR Advent Calendar 2019 の 9日目の記事です。
SmartHR に入社してから、その殆どの時間で、アカウント周りの改修やらなんやらを行っているのですが、 B2B SaaS におけるアカウントモデルの検討事項を洗い出してみました。
検討する上でのユースケース
- B2B
- 担当者が扱うアカウントで良い場合
- B2E
- 従業員のアカウントが必要な場合
一つのテナントに紐づくアカウントでよいか
サービス全体でアカウントを共有するモデル
- GitHub
- EMAIL などで一意のアカウントが発行される
- org には招待などで紐づけ
- GitHub
テナントごとにアカウントを作成するモデル
- Slack
- テナントごとにアカウントが発行される
- テナントの切り替え = アカウントの切り替え
- テナントの切り替えが簡単にできるUIをつくるだけで良いかも
- Slack
複数のテナントにまたがってログインができる機能は必要か
- サービスの特性によって、必要な場合もある
アカウントを作成するのは誰か
- 招待の概念が必要か
- サービスの特性による
- 従業員も使うサービスであるならば、従業員アカウントを管理者権限のアカウントが招待をする必要があるかもしれない
- もしくは、社員番号などと連動した従業員アカウントの発行などが必要かもしれない
- サービスの特性による
従業員それぞれのアカウントが必要なのか
- 従業員がサービスにアクセスする必要があるならば、必要
- 勤怠管理システムなど
権限設定が必要か
- 任意の権限を設定できる必要があるか
- 従業員がアクセスできるサービスの場合は必須
- 見えてはいけないものを見えないように
認証は独自でやるのか IdP と連携するのか
- 認証基盤を運用するのは大変
- できれば IdP と連携するものをリリース当初から持っておいたほうが楽
- そもそも IdP に投げる仕組みにしてあるほうが好ましいかも
- 退職したら、アクセスできないようにする必要があるかも
- できれば IdP と連携するものをリリース当初から持っておいたほうが楽
セッションの管理はテナントごとになっている必要があるか
- 複数のテナントにまたがったアカウントがなければ不要かも
- MTWA のアーキテクチャとしてテナントの切替方法がどの様になっているか
おわりに
サービスの成長や、方向性などで、無限に考えることが多くなりがちですが、 つどつど見極めつつ、検討するのがいいかと思います。
SmartHR でのアカウント周りについて、悲喜こもごももあるのですが、 書けない話も多いので、気になる方は、どこかで直接あったときにでも。
ISUCON9 予選敗退した #isucon
今年も ISUCON の予選にでた。
今年は、去年のチームメンバーの都合が合わずに新規で、社内で募集してチームを作って参加した。
メンバーは @ykarakita と @tak_wak_dev でチーム名は ウデムシlab 。
二人は初参加だったけど、事前練習をすることができずに、ぶっつけ本番となってしまった。
アプリの改善は二人に任せつつ、下回りをザクッと対応する方針で進めた。
やったことメモ
- ruby 実装に変更
- nginx のログを ltsv 化
- alp に食わせて必要なタイミングで共有
- nginx -> ruby 間を unixsocket に変更
- mysql 用の instance に変更
- mysql slow log の確認
アプリの改善の方は
初ISUCONだん…ひたすらJOINすることしかできなかった…
— wakasa (@tak_wak_dev) September 8, 2019
という感じで、 N+1 を潰す作業に追われてしまった様子だった。
あと、途中で オフィスに誰か来たなと思ったら LINEからきた人だった
会社に来てみたら当社のisucon戦士が戦ってました。がんばれー! #isucon
— yabucccchi(薮田@SmartHR) (@yabucccchi) September 8, 2019
最終スコアは、 3910イスコイン で惨敗だった。。
初参加の二人に ISUCON の楽しさをしってもらえたので、来年も、がんばるぞ..!
運営の皆様、ありがとうございました!