この記事は株式会社エス・エム・エスAdvent Calendar 2025の12月8日の記事です。
プロダクト開発部 人材紹介開発グループの田実です。
人材紹介サービスは今年から Career Portal という新しい社内基盤を開発・運用しています。
本記事ではこのCareer Portalの概要や利用技術について紹介したいと思います!
人材紹介サービスの課題とCareer Portal
エス・エム・エスでは医療・介護・保育などの領域で人材紹介サービスを提供しています。 人材紹介サービスは「キャリアパートナー」と呼ばれる、従事者(求職者)と事業者を繋ぐ転職・採用支援担当が双方とコミュニケーションをとり、より良いマッチングを創出しています。 より良いマッチングを提供するために、昨年末からキャリアパートナーにご協力いただきキャリアパートナーの全ての業務プロセスをヒアリングしました。
業務プロセス理解の取り組みに関しては以下の記事でも紹介しておりますので、こちらも読んでもらえると嬉しいです!
tech.bm-sms.co.jp
ヒアリングした業務プロセスを一部抜粋したものが以下になります。


ご覧の通り、キャリアパートナーの業務は非常に多岐に渡り業務量がとても多い状態でした。センシティブな情報を扱ったり不確実性が高い業務も多く、タスクの質・量ともに負担が大きい状況でした。
また、情報の粒度やストア先、コミュニケーションツール、ノウハウなどが各チームや個人で異なっているといった課題もありました。
このヒアリングを通じて より最適なマッチングを提供するためには業務プロセスの見直しや業務システムの改善を行う必要がある ということが改めてわかりました。
これらの課題を解決すべく、今年から開発チームが主体となりプロジェクトを発足しました。
このプロジェクトで作った社内業務アプリケーションが Career Portal です。1
ここからはこのCareer Portalの技術基盤について詳細を紹介していきます。
全体的な構成図
インフラはGoogle Cloudで構築しており、以下のような構成になっています。

ユーザー(キャリアパートナー)がLoad Balancer経由でWebアプリケーション(Cloud Run)にアクセスし、DB(Cloud SQL)からデータを入出力するような一般的な構成です。社内アプリケーションなのでLoad BalancerにはIAPによるアクセス制限も入れています。 Cloud SQLのデータは日次でBigQueryに連携しています。Looker StudioからBigQueryに接続してダッシュボードを作成しており、データやGAから利用状況の確認・分析を行っています。 上記の構成図からは割愛していますが、バッチ処理もあり、Cloud Run JobsのアプリケーションをCloud Schedulerで動かしています。
VertexAIを使った生成AIの機能は別アプリケーションとして実装しています。生成AIのレスポンスは数十秒以上かかるため、Webから直接AIアプリケーションにリクエストするのではなく、Cloud Tasksを使った非同期化やリトライ管理も行っています。 Celeryなどのジョブワーカーを利用する方法も案としてはありましたが、Career Portalリリース時点ではCloud Run Worker Poolsがパブリックプレビューだったことや、運用・保守の負荷の面を考慮しCloud Tasksを採用しました。
アプリケーションアーキテクチャ
WebアプリケーションはTypeScript / Next.js を使って実装しています。認証は NextAuth.js 2を使っており、社内GoogleアカウントのOIDCによる認証基盤を実装しています。ORMとして Prisma 、UIライブラリは shadcn/ui 、フォーマッタ・Linterは Biome 、テストは Vitest と最近よく見かけるような技術構成になっています。
AIアプリケーションはPython / FastAPI を使っています。パッケージ管理は uv 、型チェックは mypy 、フォーマッタ・linterは Ruff とこちらもPythonアプリケーションでよく見られる技術構成になっています。
バッチアプリケーションはNode.js (TypeStrippingを使ったTypeScript)で実装しており、CLIフレームワークとして Commander.js 、フォーマッタ・Linter・テストはWebと同じくBiome、Vitestを使っています。
リポジトリはインフラも含めてモノレポで管理しています。
ロギング
全てのアプリケーションで構造化ログを採用しています。 Webアプリケーションではロガーライブラリとして pino を使っています。開発環境ではコンソール上でJSON形式のログが見づらい問題があったため pino-pretty も使っています。
Pythonのロガーは標準のloggingライブラリを使ってJSON形式のフォーマッタを定義・適用しています。
class JsonFormatter(logging.Formatter): default_msec_format = "%s.%03d" def formatTime(self, record): dt = datetime.datetime.fromtimestamp(record.created, tz=JST) return dt.isoformat() def format(self, record): log_record = { "timestamp": self.formatTime(record), "logger": record.name, "level": record.levelname, "levelno": record.levelno, "message": record.getMessage(), "args": record.args, "created": record.created, "context": { "pathname": record.pathname, "module": record.module, "funcName": record.funcName, "lineno": record.lineno, }, } return json.dumps(log_record, ensure_ascii=False) def getLogger(name: str) -> logging.Logger: logger = logging.getLogger(name) handler = logging.StreamHandler() handler.setFormatter(JsonFormatter()) logger.addHandler(handler)
CI/CD
CI/CDはインフラも含め全てGitHub Actionsで行っています。GitHub Actionsのアクションは pinact でpinningしており、actionlint によるワークフローファイルの静的解析も行っています。Google CloudのAPIにアクセスする場合はWorkload Identity連携を使ってGitHub Actionsから認証しています。
CIの内容としてはWeb・バッチは Biome・tsc・Vitest、AIアプリケーションではRuff・mypy・pytest、インフラでは tflint・terraform fmt・trivy によるチェックを動かしています。さらに、lefthook を使ってローカル環境でコミット・プッシュする前にLinter・フォーマッタを動かして対応漏れを防いでいます。
アプリケーションの自動デプロイは
- mainブランチへのデプロイ => 開発環境への自動デプロイ
- GitHubのリリースを切る => 本番環境への自動デプロイ
としています。
production用のブランチを切って運用する方法もありましたが、その分プルリクエストやブランチが増えて管理性が悪くなるデメリットもあったので、Career PortalではGitHubでリリースを切る方式を採用しました。 mainブランチにマージして開発環境での動作確認した後は速やかにリリースする、といった開発・本番環境の乖離が発生しづらいフローを想定していたというのも理由の1つです。
GitHubのリリースを手動作成するのも面倒なので、mainブランチにマージしたらDraftリリースを自動で作成し、そのリリースをPublishするだけで自動デプロイができる仕組みも実装しました。 DraftリリースのリンクはSlackに通知しているためDraftリリースのリンクを探す手間も無くなるようにしました。

o11y
インフラのメトリクスをDatadogのダッシュボードで閲覧できるようにしたり、メトリクスが閾値を超えたらSlackに通知するような仕組みも実装しました。 Sentryも各アプリケーションに実装しており、予期せぬ不具合が発生した場合はSentry経由でSlackに通知されます。
RDBの特定のデータを監視して異常がないかどうかの確認もバッチ処理で実装しています。 MetabaseやRedashなどのBIツールを使ってデータを監視・通知する方法も実装案にあったのですが、総合的な実装・運用コストがバッチによるスクラッチ実装のほうが少ないと判断しました。
BIツールという観点だとアドホックなクエリを打てる環境が運用上求められることがありますが、こちらはCloud SQL Studioで対応しています。Cloud SQL StudioだとGoogle Groupを使ったIAM認証が利用できるため、クレデンシャル管理せずにデータアクセスができて非常に便利です。
検証環境
ローカル開発環境では検証しきれないため、本番相当のインフラ構成である検証環境も構築しました。 検証環境はプルリクエストごとに作成できると良かったのですが、IAPやCloud Storage、Eventarcの設定など純粋にアプリケーションの分離だけでは設定が不十分になってしまうことや、実質的に5つ程度の環境があれば十分開発が回る体制だったため、事前に構築した検証環境を各開発者に提供する形にしました。
Cloud Runはリクエストされていないときにコンテナを停止できて、コールドスタートも開発検証する上では許容範囲だったため、コスト的な心配や開発体験の影響もなく安定して運用ができています。
その他
Renovateによる定期バージョンアップ
Renovateによる定期バージョンアップも行っています。各アプリケーション・依存グループでグルーピングされたプルリクエストが週次で作成されるように設定していて、CIが通っていて動作確認上も問題なければ良きタイミングでリリースしています。
生成AIによる開発プロセス効率化
例に漏れず、弊社でも生成AIを活用し、開発プロセスをより効率的にしています。 具体的にはClaude Codeによるコード自動生成、GitHub Copilotによるコード補完、GitHub CopilotによるPRコードレビュー、Meetの録音からの議事録自動生成、Geminiによる設計の壁打ち・技術課題解決、NotebookLMによる情報整理・検索など、様々な業務で活用しています。 初回リリース時は3〜4名で2〜3か月で構築していますが、生成AIの活用無しではこの人数・スピード感でのリリースは難しかったのではないかと思います。
ふつうの技術基盤を作りたい
新しくアプリケーションを立ち上げたり、あるいは既存のアプリケーションの改善をする場合、「ふつう」な技術基盤を作ることを意識しています。 「ふつう」の水準はエンジニアのレベル感でだいぶ変わってくるのですが、自分にとっては
- シンプルなインフラ構成で
- シンプルなアプリケーション設計で
- ローカル開発環境はコンテナで用意されていて
- 本番相当の適切な検証環境も用意されていて
- テストがそれなりに書かれていて
- CI/CDも用意されていて
- インフラ・DBやログなどのメトリクスを閲覧したり異常を適切に検知できる環境があって
- インフラはIaCで管理されていて
- 分析できる環境が整っていて
- ライブラリは定期的にアップデートされて最新のものが利用できるようになっていて
- 開発プロセスも生成AIを適切に使っている
みたいな基盤です。 「ふつう」な技術基盤にすることで考えることを少なく・シンプルにして技術的な運用負荷を減らし、結果として、業務・ビジネスの課題解決によりフォーカスできるようになることを期待しています。
これら全てをやるにはそれなりの経験が必要ですが、ブログ記事などWeb上にサンプルコードのような「答え」を見つけるのが比較的容易な領域とも言えます。 今だと生成AIを使えば壁打ちしながらすぐに実装できるかもしれません。 結局コツコツと基盤を改善していくしかなく、この辺がサクッと導入できるくらいのスキルや経験も積み上げていくしかないのかなぁと思っています。
まとめ
社内業務アプリケーションのCareer Portalで使われている技術について紹介をしました。
Career Portalリリース後、「業務負荷が下がった」「最適なマッチングを提供できた」というポジティブなフィードバックを多くいただいています。
今後も業務理解・整理・システム改善を繰り返し、従事者と事業者の最適なマッチングを通じて 医療・介護/障害福祉の人手不足と偏在の解消 3を目指していきたいと思っています。
- 既存の業務システム基盤は、今後の機能追加や事業の変化に対応し続けることが困難だったため、システムを切り離して新たに実装しました。↩
- ぼちぼちBetter Authに切り替えようかなぁと思ってます。↩
- https://www.bm-sms.co.jp/service/career/↩
