IAM Identity Centerでもaws-vaultでセキュアにAWS CLIを使う

こんにちはSREチームの宮後(@miya10kei)です。最近、トリュフナッツにハマりビール🍺の消費量が増えています。

AWS CLIを使用する時にaws-vaultは使っていますか?
AWSのユーザ管理をAWS IAM Identity Centerに移行した際にaws-vaultの設定でつまずいたので解決方法を紹介したいと思います。

AWS IAM Identity Centerとは?

複数の AWSアカウントやアプリケーションへのワークフォースのアクセスを一元管理するためのサービスです。外部IDプロバイダーと接続しSSO(シングルサインオン)連携をすることができます。ニーリーではGoogle Workspaceと連携させGoogleアカウントでログインできるようにしています。

aws-vaultとは?

aws-vaultはAWS CLIを使用する際の認証情報を安全に保存し、アクセスするためのOSSです。認証情報はOSのキーストアに保存されるため、よりセキュアにAWS CLIを使用することができます。AWS CLIには外部プロセスの標準出力から認証情報を取得するcredential_processという機能があります。credential_procesとaws vault を介して、キーストアに認証情報を保存しながら透過的にAWS CLIを利用することができます。

AWS IAMでユーザ管理をしていたときの設定

まずはAWS IAMでユーザ管理をしていた頃のaws-vaultの設定について紹介します。

次のコマンドを実行すると$HOME/.aws/configというファイルが作成され<profile名>のテーブルが書き込まれます。

$ aws-vault add <profile名>
Enter Access Key ID: AKIAXXXXXXXXXXXXXXXX
Enter Secret Access Key:
Added credentials to profile "<profile名>" in vault

入力したAccess Key IDとSecret Access Keyはキーストアに保存されます。
キーストアにpassを利用している場合は次のように保存されます。

$ aws-vault add sample
Enter Access Key ID: sample-access-key-id
Enter Secret Access Key: ************************
Added credentials to profile "sample" in vault

$ pass ls
Password Store
└── aws-vault
    └── sample

$ pass aws-vault/sample | jq .
{
  "Key": "sample",
  "Data": "eyJBY2Nlc3NLZXlJRCI6InNhbXBsZS1hY2Nlc3Mta2V5LWlkIiwiU2VjcmV0QWNjZXNzS2V5Ijoic2FtcGxlLXNlY3JldC1hY2Nlc3Mta2V5IiwiU2Vzc2lvblRva2VuIjoiIiwiU291cmNlIjoiIiwiQ2FuRXhwaXJlIjpmYWxzZSwiRXhwaXJlcyI6IjAwMDEtMDEtMDFUMDA6MDA6MDBaIn0=",
  "Label": "aws-vault (sample)",
  "Description": "",
  "KeychainNotTrustApplication": true,
  "KeychainNotSynchronizable": false
}

$ echo "eyJBY2Nlc3NLZXlJRCI6InNhbXBsZS1hY2Nlc3Mta2V5LWlkIiwiU2VjcmV0QWNjZXNzS2V5Ijoic2FtcGxlLXNlY3JldC1hY2Nlc3Mta2V5IiwiU2Vzc2lvblRva2VuIjoiIiwiU291cmNlIjoiIiwiQ2FuRXhwaXJlIjpmYWxzZSwiRXhwaXJlcyI6IjAwMDEtMDEtMDFUMDA6MDA6MDBaIn0=" | base64 -d
{"AccessKeyID":"sample-access-key-id","SecretAccessKey":"sample-secret-access-key","SessionToken":"","Source":"","CanExpire":false,"Expires":"0001-01-01T00:00:00Z"}

MFAを設定している場合には次のようにmfaデバイスの情報を設定します。

[profile <profile名>]
mfa_serial=arn:aws:iam::<AWSアカウントID>:mfa/<mfaデバイス名>

credential_processを次のように設定することでAWS CLIから透過的にaws-vaultを利用することができます。

[profile <profile名>]
credential_process=aws-vault exec <profile名> --duration 12h --json

最後に次のコマンドを実行しidentity情報が取得できれば設定完了です!

$ aws sts get-caller-identity

AWS IAM Identity Center移行後の設定

ここまではネットにも多くの情報があり、よく利用されている設定かと思います。
続いてAWS IAMからAWS IAM Identity Centerに移行し、SSOを有効化した場合の設定について紹介します。

まずは$HOME/.aws/config設定の全量を載せます。

[sso-session sso]
sso_start_url = https://xxxxxxxxxxxx.awsapps.com/start
sso_region = ap-northeast-1
sso_registration_scopes = sso:account:access

[profile sample-sso]
sso_session = sso
sso_account_id = xxxxxxxxxxxxx 
sso_role_name = xxxxxxxxxxxxx

[profile sample]
credential_process=aws-vault exec sample-sso --json
region = ap-northeast-1
output = json

[sso-session sso] テーブルの設定

こちらは認証セッションについての設定になります。
AWS Identity Centerを導入するとAWS アクセスポータルの URLが払い出されます。
sso_start_urlには払い出されたURLを設定してください。

[profile sample-sso]テーブルの設定

こちらはどのAWSアカウントにどのロールで認証するかの設定になります。
sso_sessionには[sso-session sso] テーブルの設定を参照するように設定してください。
sso_account_idには対象のAWSアカウントのIDを設定してください。
sso_role_nameにはAWS Identity Centerで作成した許可セット名を設定してください。

[profile sample]テーブルの設定

こちらはaws-cli利用時に指定するprofileの設定になります。
credential_processでaws-vaultのコマンドを実行するのはAWS IAM利用時と変わらないですが、profileには[profile sample-sso]テーブルのprofile名を設定してください。 このように設定するとAWS IAMを利用していた時と同様にaws-cliから透過的にaws-vaultを利用することができます(セッションが無効な場合にはブラウザでSSOのログイン画面が自動起動します)。

設定後は次のコマンドを実行しidentity情報が取得できるか確認してみてください。

$ aws sts get-caller-identity

Tips

コンテナ内などブラウザが自動起動しない環境で利用したい場合には次のように設定することでログインURLが標準出力されるようになるので試してみてください。

credential_process=bash -c "aws-vault exec sample-sso --duration=12h --stdout --json 2> /dev/tty"

まとめ

今回はTips的な紹介になりましたが、探してもあまり情報が見つからない部分でしたので紹介してみました!まだ、aws-vaultを使われていない方は是非試してみてください!よりセキュアな世界を目指していきましょう💪💪💪

AWS Configのコストを95%削減しつつ記録を残すことを諦めない

はじめに

SREチームの大木( @2357gi )です。 ECS Serviceのオートスケーリングやバッチなど、ECS Taskの起動停止が頻繁に行われる環境でAWS Configを有効にしていると、AWS Configのコストが無邪気に跳ね上がってしまうことがあります。

インターネット上では特定のリソースを対象外にすることによりコストを抑える手法が多くの記事として見かけますが、対象外にするとAWS Config側で「リソースタイムラインの表示」ができなくなったり、Security hubで使用する情報の記録を行うことができなくなってしまいます。

そこで、特定のリソースを「記録から除外」するのではなく、「日時記録に設定」することにより前述した懸念点を解消しつつ、コスト削減をすることができたので紹介します。

経緯

我々のプロダクトでもサービスのスケールや機能拡大に伴い AWS Configのコストが無視できない値になってきました。

前述した通りECS Serivceやスケジューリングバッチ用のECS Taskの起動停止が積み重なっており、主に以下のリソースの変更が多く記録され、コスト増加に繋がっていました。

  • AWS EC2 NetworkInterface
  • AWS EC2 SecurityGroup
  • AWS EC2 Subnet
  • AWS EC2 VPC

縦軸が記録された変更数。上記グラフの作り方は後述

2月初めからレコード数が爆増していることがわかります。 機能追加によるバッチ実行数の増加の影響です。

当初はこれらのリソースタイプの記録を除外しようとしたのですが、前述した「リソースタイムライン」や Security Hubなどの懸念点がありました。

が、2023/11/27 のアップデートで AWS Configの記録期間を「継続的な記録」だけでなく「日次での記録」を指定できるアプデが追加されていることを発見しました。

aws.amazon.com

「日次での記録」は「継続的な記録」に比べてリアルタイム性が落ちたり、1日に複数回の変更が行われたとしても最終的な日次の記録しか残らないと言ったデメリットがあります。

しかし、その日次データもSecurity Hubやリソースタイムラインのデータとして使用することはできるので、今回はメリデメを比較し変更量が多いリソースタイプに限って日次に変更することにしました。

先述したリソースを日次記録に変更

結果として、懸念点が解消された状態で、AWS Configのコストを95%ほど抑えることができました。やったね

日次にしたことによりレコード数を大幅に抑えられている

コストの変化

おまけ

上記のようなレコード数グラフの作り方

  1. CloudWatch コンソールに移動します。
  2. 左のナビゲーションメニューで、[ダッシュボード] をクリックします。
  3. [ダッシュボードの作成] をクリックします。
  4. ダッシュボードに「aws-config-dashboard」という名前を付け、[ダッシュボードの作成] をクリックします。
  5. [棒] グラフを選択します。
  6. [メトリクス] を選択し、[次へ] をクリックします。
  7. 右側のセクションで、[数式] を選択し、[空の式で始まる] を選択します。
  8. 以下の数式を入力し、[適用] をクリックします。
SORT(SEARCH('{AWS/Config,ResourceType} MetricName="ConfigurationItemsRecorded" NOT ResourceType="All"', "Sum", 86400), SUM, DESC, 10)
  1. [ラベル] 列で、名前を「Resource Type」に変更します。

参考: AWS Config がリソースタイプによる記録の除外に対応 | Amazon Web Services ブログ

学生向けバグバウンティイベント P3NFEST にプログラムを提供しました!

こんにちは、菊地(@_tinoji)です。GW終わっちゃいましたね・・・。

この度、IssueHunt社主催の 学生のためのサイバーセキュリティカンファレンス P3NFEST Conf 2024 に参加し、同時開催された学生限定バグバウンティイベント P3NFEST Bug Bounty 2024Park Directをプログラム提供させていただきました!

※オフラインのカンファレンスイベントに加え、同時開催でオンラインのバグバウンティイベントがあるという形式でした。カンファレンスでバグバウンティイベントについて紹介するパートがあったので、カンファレンスにも参加させてもらいました。

issuehunt.jp

学生さん向けイベントの参加も初、バグバウンティを実施するのも初、と何から何まで初めてづくしでしたが本当に有意義な経験となりました。

目次のとおり長くなってしまいそうですが、記憶の新しいうちにレポートしておこうと思います。バグバウンティの導入を検討する方の一助になれば幸いです。

バグバウンティ導入を心に決めるまで

セキュリティ対策の状況

Park Directは2019年にローンチしたサービスですが、個人情報をお預かりしていますし決済機能もあるアプリケーションなため、かなり早くから外部の企業に脆弱性診断をお願いしていました。また、コンテナイメージやTerraformファイルのスキャンもCIに導入していました。

そのため 診断/スキャン→トリアージ→対策 というプロセスは実施できていたのですが、一方で「攻撃者視点での対策」ができているかというと・・・、社内にホワイトハッカーがいるわけでもないですし、あまり現実的ではありませんでした。ペネトレーションテストも一度検討したことがあったのですが、紆余曲折あって実施には至りませんでした。

「一定やれているけど、実際攻撃の標的になった場合にどうなんだ??」というのは、プロダクトを開発している人ならば一度は考えたことがあるのではないでしょうか。

IssueHuntとの出会い

もちろんバグバウンティという選択肢は考えたことはあったのですが、バグバウンティのプラットフォームは海外で活発なイメージが強く、日本国内対象のPark Directを登録してもなぁ、と思いながら過ごしていました。

そんなある日、東洋経済オンラインのすごいベンチャー100という記事を何気なく見ていたら、「バグバウンティサービス展開」の文字が!!!

それがIssueHuntさんでした。一緒に見ていたメンバーがその日のうちに資料請求をしていたことを覚えています(笑)

一度打ち合わせで仕組みやコストについて説明していただき、導入はかなり現実的だと感じました。ただ、当然ながらその年の予算計画になかったので、2024年度の導入を目指してとりあえず予算確保に動き始めました。

イベント参加に至るまで

無事予算が取れそうではあったのですが、1つ大きな心配事として「めっちゃ報告来て運用回らなかったらどうしよう」というものがありました。

まぁ腹を決めてやるしかないか〜と思っていたのですが、IssueHunt Loungeというコミュニティイベントに参加した際に、代表の横溝さんに学生向けイベントの提案をしていただきました。

招待制イベントで参加者が限られているため一般公開するよりも報告数は少なくなる、ということでトライアルに最適だと思い、参加を決めました。

参加を決めて社内で調整を始めてからそれなりに苦労した点もあったのですが、それは振り返りイベントでお話しさせていただいたので後述します。

カンファレンス当日

コロナでオフラインイベントが少なくなって以降あまり大人数のイベントに参加していなかったのですが、会場参加もオンライン参加もそれぞれ150人以上という大盛況ぶりで、とても懐かしい気持ちになりました。基調講演とパネルディスカッションも拝聴させてもらい、「すげー!本物の徳丸先生だ!」と学生さんよりも興奮していました。 boost.connpass.com

バグバウンティプログラムの紹介パートもあったので自分も登壇させてもらったのですが、他の企業さんと比べて会社・サービスともに知名度が圧倒的に低く、もっと頑張らねばと思いました・・・!

IssueHunt代表の横溝さんと記念撮影

バグバウンティ期間中の話

バグバウンティは2/19(月)からスタートしたのですが、なんと最初の週から複数件の報告がありました・・・!

↓最初の報告をしていただいた学生さんの記事。お互い初めてのことでしたが、IssueHuntのプロセスに沿って進めればよく、スムーズに報奨金支払いまで行けました。 l3ickey.github.io

自分は以下のような流れで対応していました。

  1. 報告を受ける。
    1. 脆弱性の種類、再現手順、CVSSスコアから想定される報奨金の額などが記載されます。
    2. 内容を確認したら一旦「これから調査します〜」と返信。※返信日数の目安を設定可能です。
  2. 再現検証と既知調査を行い有効かどうか判定する。
    1. 記載された手順で再現し実際に攻撃が可能、過去の脆弱性診断で報告がない、CVSSスコアが基準以上、の3点で判断。
    2. 決まりはないですが、1週間以内を目安に判定していました。
  3. 報奨金を支払う。
    1. 報告者が設定したCVSSスコアとは別に、ホスト側でも算出する機能があるので項目を埋めて確認。
    2. 目安の金額が出力されるので、それをベースにお支払いする。
  4. 対応期日を決めて開発チームに修正を依頼。
    1. CVSSスコアによって対応期日の目安を設定して、チケットを開発チームにアサイン。
    2. ※ということをやりたかったのですが、結局期間中に運用に乗せられず・・・。緊急度の高いもの以外は一度貯める感じになってしまいました。

全体的な流れはスムーズだった一方、トリアージや報告者への対応にはいくつか反省点も残りました。こちらも振り返りイベントで話したので後述します。

その後もコンスタントに報告をいただき、最終的に7件の報告があり、全て有効認定とし報奨金をお支払いしました。

振り返りイベントでの登壇

バグバウンティ期間が3月末で終わり、4月にカンファレンスとバグバウンティについて振り返るイベントが開催されました。自分も他の企業さんのバグバウンティについて知りたいと思っていたので、振り返りがあるのは非常に助かりました。 boost.connpass.com

自分は「バグバウンティイベント振り返り」のパートで登壇させていただき、いくつかの質問に回答したので、加筆修正しつつ簡単にまとめておきます。

左: 日本経済新聞社 藤田さん、中央: 自分、右: Sansan 黒澤さん

本イベントを社内に提案した際、どのような反応だったか、どのように社内を説得したか

基本的にウェルカムで、「やるのかどうか」ではなく、「やる前提で懸念を潰していこう」という方向で早い段階で議論を進められました。

ニーリーの場合、CTOだけでなくCEOとCISOもエンジニア出身なのでこういった説得に全く苦労しないというチートがあってズルいのですが(笑)、事前にコスト面や利用規約について詳しく把握しておいたのでスムーズでした。 特にIssueHuntはプログラムごとに予算額を設定してそれを超過したら停止できるので、従量課金ではありつつも上限を決めて「これ以上は行かないので!」という説得がしやすいと思います。

もちろん頭を悩ませたポイントもあり、以下の2点は何度も議論しました。

  • ①実施環境をどうするのか?
    • 検証だとしても攻撃が成功して個人情報が見えてしまうのは避けたい。
    • DEV/STGを使っても環境差分はほぼないので、診断に影響はないのでは?
    • → DEV環境を使うことに決定!
  • ②toCだけでなくtoBのアプリケーションも対象にするのか?
    • toCの画面だけでなく、不動産管理会社が利用するtoB用の管理画面も存在する。
    • サインアップはできずアカウントは個別に発行するため、期間中は共有アカウントを作ってプログラム説明欄にID/パスワードを記載する必要がある。
    • DEV環境、身分が保証された招待制イベント、期間が終わればアカウントを停止すればOK、という3点でリスクは低い。
    • → toBも対象にすることに決定!

余談ですが、自分もCTOもIssueHunt社が提供しているBoost Noteというアプリの元ヘビーユーザーで、それで会社への信用度が上がったという寄与が若干あるかもしれません(笑)

どのような類の脆弱性報告を受け取ったか

すでに修正対応済みで話せるものを1つ紹介します。とあるAPIのパラメータに適当なIDを入れると、権限確認の処理が抜けていて自分のアカウントが所有していないものが取れてしまうという、非常に初歩的なアクセス制御のミスがありました。

こういったものはかなり気をつけており過去にも網羅的に確認していたのですが、(詳しくは書きませんが)確かに気づきづらい理由がありました。脆弱性診断でも報告は上がっていませんでした。

こういった「簡単だけど気付けないもの」に対してバグバウンティは非常に有効な手段だと感じています。

印象に残っている報告や、その他エピソードなど

具体的にどういう脆弱性かは割愛しますが、とある報告に対して「toB画面から自分のアカウントに対してしか実行できず、外部からの攻撃はできないので脆弱性として扱いません」というような回答をしたケースがありました。しかし、その後詳しく調べてみると外部からの攻撃が通ることが分かり、回答を撤回して追加の報奨金をお支払いしました。

今振り返ると、再現検証のフローが曖昧だったという反省になります・・・。初めてではあったものの、どこまで検証してどうなれば有効判定にするかについては事前に決めておくべきでした。複数人で対応可能なら、判定や報奨金額に対するクロスチェック or レビューも効果的だと思います。

次回のイベントにて、プログラム提供を悩んでいる企業様に対し、背中を押すコメント

費用対効果が非常に高いと感じました。今回7件の有効報告がありましたが、トータルの費用は10万円未満でした。そこまで重大な脆弱性が見つからなかったというのはもちろんありますが、それでも全て未知の脆弱性でトリアージ対象になるものもあったので、かなりお得だったと思っています。

これから

もともとは今回のイベントが終わったらすぐ一般公開のプログラムに変更しようと思っていたのですが、実際にやってみて、まだ自分たちは一般公開した場合の報告数に耐えられる体制ではないなと感じました。報告後すぐに再現検証の時間が取れなかったり、修正対応を開発チームに差し込むのが難しかったりと、課題が多く見つかりました。また、一般公開の場合は海外のユーザーも多いので、英語でのコミュニケーションで若干対応コストが増えることも念頭に置く必要があります。

セキュリティチームやPSIRTという訳ではないですが、バグバウンティの運用も守備範囲にする新しいチームを現在立ち上げ中で、最近メンバーも増えたので、あと何回か期間限定のプログラムでチームの練度を上げて、一般公開に向けてステップアップしていこうと考えています。夏にもP3NFESTが開催される予定とのことなので、またお世話になろうと思っています!

また、VDPとバグバウンティを組み合わせて運用している企業の事例をお聞きして、一般公開プログラムにしなくても間口を広げる方法があることも知り、そちらも候補に挙がっています。あらゆる手段を活用してPark Directをよりセキュアにしていきます!!

Autifyのコミュニティイベントで「Autify導入からこれまでの歩み」について登壇してきました!

こんにちは、QAチームの関井(@ysekii_)です。

今回は、3月27日に開催されたAutifyコミュニティイベントに参加・発表してきたので、そのレポートをお届けしたいと思います。

Autifyコミュニティイベントとは

オーティファイ株式会社が提供するノーコード自動テストプラットフォームのAutifyを利用している企業を対象にしたオフラインコミュニティイベントです。

昨年末にも開催されていたようで、今後も定期的に開催されるとのことでした。 毎回トークテーマが変わるようですが、今回のトークテーマは、

「どのようにAutifyでテスト自動化を実現してきたか - Autify導入からこれまでの歩み -」

ということで、Autifyユーザー3社から、導入当初の目標や抱えていた課題に対してどのようにアプローチしてきたかなどをLT形式で発表されました。

タイムテーブル

  • 19:00:イベント開始
  • 19:00~19:10:オープニング(会場案内、イベント説明等)
  • 19:10~19:20:ニーリー 関井(LT 7~10分 Q&Aも含む)
  • 19:20~19:30:株式会社レコチョク 清崎さん(LT 7~10分 Q&Aも含む)
  • 19:30~19:40:株式会社カインズ 田村さん(LT 7~10分 Q&Aも含む)
  • 19:40~20:30:交流会
  • 20:30:クローズ

各社LT

トップバッターは自分に

実は、発表順は事前に伝えられておらず、当日会場に入ったタイミングで一番目の発表を言い渡されました笑 (最初であれば流れを自由に作れるので、意外と楽だったりはします)

今回の発表タイトルは「Autify活用による高頻度リリースの実現」ということで、ニーリーでのAutifyの歴史を語りつつ、最終的には高頻度リリースに対して大きく貢献してもらったという内容でお話しました。

発表に10分フルに使ってしまいQ&Aの時間が残っていないかなと思ったのですが、スケジュールが前倒しで進んでいたこともあり、たくさんの質問をいただけました(司会の方に感謝!)

speakerdeck.com

株式会社レコチョク 清崎さん

レコチョクの清崎さんの発表では、Autifyの利用に向いているテストと向いていないテストの整理をきちんとされているのが印象的でした。 音楽や動画を扱っているサービス特有の、人の感性が求められる部分では手動でのテストが必要になるという点は納得感がありました。

株式会社カインズ 田村さん

カインズ田村さんの発表では、テスト自動化の前に目的やスコープの整理をしっかりとされているのが印象的でした。 当たり前のことかもしれませんが、施策の実施やツール導入の際には目的や想定される効果などを整理した上で実行することで、「やっぱり違った」みたいなことが減ると思うので、ちゃんと忘れないようにしたいですね。

また、両社に共通したこととして、スモールスタートでテスト自動化を行ったという点がありました。 Autifyを導入したタイミングではまだ私は入社していなかったため、ニーリーでの当時の状況はわかりませんが、ニーリーでも最初は担当者1人で細々とリグレッションテストの自動化を行っていたらしいので、そういう意味ではスモールスタートだったのかもしれません。 成功するか失敗するか見えていない状況で、一気にテスト自動化を進めるというのはリスクも高いと思うので、スモールスタートで始めて、徐々に仲間を増やしたり、規模を大きくしていくのがいいのだろうと思います。

Autifyユーザーとの情報交換

LTのあとは交流会の時間がありました。参加されている方はみなさんAutifyユーザーなので、Autifyで困っていることなどを共有・議論したり、Autifyスタッフの方も参加されていたのでその場で要望を伝えたりと有意義に過ごすことができたと思います。

お話を伺っていると、どのくらいAutifyのシナリオを作ったらいいかという点は多くの企業が悩んでおり、ニーリーでも同様の課題感を持っています。 各社状況が異なるので、一概にこのくらい作ったらいいという基準はないと思いますが、ニーリーとしてはインシデントに繋がりそうなバグは防げるくらいという基準を設定しています。 これが正解かはわかりませんが、シナリオを作りすぎてもメンテナンスが大変になるため、自動テストの効果とメンテナンスコストのバランスを考えて基準を設定したらいいのではないかと考えています。

交流会の時間としては1時間弱くらいだったと思いますが、参加人数も多かったこと*1もあり、当日お話できなかった方も多くいたのが心残りでした。

参加してみての感想

コロナ禍以降、オフラインイベントが増えたこともありなかなかオフラインイベントに参加できていなかったのですが、今回久しぶりに参加してみてオフラインならではの良さを感じました。

特に、懇親会や交流会はオフラインでないと出来ないものだと思いますので、今後はつながりを作るためにも定期的にオフラインイベントに参加していきたいと思います。

*1:正確な人数は把握していませんが、30名くらいは参加されていたと思います

CeleryのMessage Priorities機能を利用した処理遅延の低減

こんにちは、SREチームの宮後(@miya10kei)です。
バイクに乗っていて気持ちが良い季節になってきましたね🌸

メッセージキューを利用した非同期タスクを扱っていて、誰しも優先度順にタスクを処理したいなと思ったことがあるのではないでしょうか?

今回はCeleryの機能を利用して実現することができたので紹介したいと思います。

Celeryってなに?

Celeryは分散メッセージキュー機能を提供するPythonベースのOSSです。
メッセージキューのBrokerとしてRedisやRabbitMQ、Amazon SQSなどを使用でき、分散環境での非同期タスクの実行を実現しています。

公式サイトを引用すると次の説明になりますね。

Celery is a simple, flexible, and reliable distributed system to process vast amounts of messages, while providing operations with the tools required to maintain such a system. It’s a task queue with focus on real-time processing, while also supporting task scheduling.
引用元:https://docs.celeryq.dev/en/stable/index.html#celery-distributed-task-queue

Celery自体はPythonで書かれていますが、他にもNode.jsやPHP向けのクライアントライブラリも提供されています。

Park DirectでのCeleryの使い方

Park Directでは非同期タスクの処理にCeleryを採用し次の構成で利用しています。

BrokerのバックエンドとしてElastiCache for Redisを使用しています。
Celery ClientとしてバックエンドのAPIとバッチからタスクを登録し、Celery Workerとして専用のECSタスクが処理をおこなう構成になっています。

課題

機能拡張とサービス成長に伴い、定期実行される処理や対象データが増加したことで短時間で大量のタスクがQueueに登録されはじめました。これにより、長い時は数時間単位でタスクが遅延してしまうこともあり事業に少なからず影響を及ぼしていました。

最初はECSタスクのオートスケーリングでスループットの改善を試みたものの、RDSの負荷を考慮するとスケールアウトにも限界があり別の対策を講じる必要がでてきました。

そこでタスクに優先度を設定する方法を検討し、CeleryのMessage Priorities機能を採用することにしました。

Message Priorities機能

それでは本題のMessage Priorities機能の話に移ります。

Celeryではタスクに対してPriorityを設定することで、処理順序を組み替える機能が提供されています。(Message Priorities機能

この機能を利用するとPriority毎に独立したQueueが作成されます。 ClientはPriorityを指定してタスクを登録することで該当するQueueにタスクを積むことができます。WorkerはよりPriortyの高いQueueから優先してタスクを取得し処理がおこなわれるようになります。

ClientとWorkerのサンプル実装は次のようになります。

Worker

from celery import Celery

app = Celery("task", broker="redis://localhost/0")
app.conf.broker_transport_options = {
    "priority_steps": list(range(3)),  # priorityを3つに区切る設定
    "sep": ":",
    "queue_order_strategy": "priority",
}


@app.task
def hello(name: str):
    print(f"Hello ${name}")

Client

from task import hello

for i in range(10):
    hello.apply_async(kwargs={"name": "miya10kei(2)"}, priority=2)

for i in range(10):
    hello.apply_async(kwargs={"name": "miya10kei(1)"}, priority=1)

for i in range(10):
    hello.apply_async(kwargs={"name": "miya10kei(0)"}, priority=0)

これを実行すると次のような出力となりPriorityの小さいタスクが優先して処理されることが確認できます。

[2024-03-27 20:26:58,723: WARNING/ForkPoolWorker-1] Hello $miya10kei(2)
[2024-03-27 20:26:59,730: WARNING/ForkPoolWorker-1] Hello $miya10kei(2)
[2024-03-27 20:27:02,741: WARNING/ForkPoolWorker-1] Hello $miya10kei(0)
[2024-03-27 20:27:03,749: WARNING/ForkPoolWorker-1] Hello $miya10kei(0)
...
[2024-03-27 20:27:11,794: WARNING/ForkPoolWorker-1] Hello $miya10kei(0)
[2024-03-27 20:27:12,797: WARNING/ForkPoolWorker-1] Hello $miya10kei(1)
[2024-03-27 20:27:13,805: WARNING/ForkPoolWorker-1] Hello $miya10kei(1)
...
[2024-03-27 20:27:21,855: WARNING/ForkPoolWorker-1] Hello $miya10kei(1)
[2024-03-27 20:27:22,861: WARNING/ForkPoolWorker-1] Hello $miya10kei(2)
[2024-03-27 20:27:23,868: WARNING/ForkPoolWorker-1] Hello $miya10kei(2)
...
[2024-03-27 20:27:27,891: WARNING/ForkPoolWorker-1] Hello $miya10kei(2)

Park Directでの使い方

Park Directでは以下の3種類のPriorityを使用できるようにしました。

Priority 説明
High (priority=0) 緊急度の高いタスク
例:問い合わせに対する返信メールを送信するタスクなど
Middle (priority=1) High/Lowでないタスク
Low (priority=2) 短時間に大量に発生する可能性のあるタスク
例:一括処理系のタスクなど

※ 現状はMiddleとLowの2種類のみを使用しており、Middleについては許容可能な遅延の範囲で運用できています。
※ Highは今後より優先して処理したいタスクが発生した場合に備えて用意しました。

また、DefaultのPriorityをpriority=1にすることでdelayメソッドでタスクを登録した場合にMiddleの優先度が設定されるようにしています。

設定方法は以下になります。

from celery import Celery

app = Celery("task", broker="redis://localhost/0")
app.conf.broker_transport_options = {
    "priority_steps": list(range(3)),
    "sep": ":",
    "queue_order_strategy": "priority",
}
app.conf.task_default_priority = 1  # Default Priorityの設定

※ Client側のタスク登録時のpriority指定はapply_asyncメソッドでのみ可能です。そのため、delayメソッドを使用している箇所の修正を最小限にしたい場合はおすすめです!

まとめ

最後にこの機能を導入した事による効果をお伝えして終わります。

導入前は数時間単位で遅延が発生することがありましたが、導入後はMiddleのタスクの遅延を数秒単位まで抑えることができ、十分に許容できるレベルまで改善されました。

Priority毎のタスクの処理件数とQueue内の滞留時間(上:Middle、下:Low)

同じ悩みを持ったことがある方は是非導入を検討してみてください!

モビリティSaaSの会社って何?ニーリーで1年半長期インターン生として働いてみて【長期インターン体験記】

はじめに

こんにちは!サクセスエンジニアリングチーム インターンの齋藤仁です。🙇‍♂️

今回の記事は、私がニーリーでの1年半のインターンとしての就業を終えるため、インターン体験記を書かせていただきます。

自己紹介☘️

  • 大学4年生
  • 専攻は英語英米文学
  • プログラミング歴は3年弱
  • 2022年10月にニーリー入社

インターンをすることになった経緯🚗

大学3年生の2022年夏、エンジニアとして就職活動を控えていたものの、学生同士や個人での開発でしかプログラミング経験がなく自信がなかった私は、エンジニアとして就業する経験を積みたいと思いWantedlyで会社を探す中でニーリーに初めて出会いました。

ニーリーはPark DirectというモビリティSaaSを提供する企業ですが、それまでモビリティSaaSについての知見は全く持っていませんでした。しかしその業界シェアの高さから考えられる社会的インパクトに加え、会社が拡大中で過渡期の状況であることにとても興味を惹きつけられ、CTOの三宅さんに仕事をお願いしたいと直接言われたことで、この会社で頑張りたいと思い入社を決めました。

実際の業務💻

私はインターン生としてサクセスエンジニアリングチーム(当時はEUCチームという名前でした)に配属になり、以下の業務を主に担当しました。

  • Amazon Connectを使用したコンタクトセンターの開発・運用📞
  • 社内ツールの開発・運用💬
  • ホームページとLPの開発・運用🌐

サクセスエンジニアリングチームに関しては、弊社のnoteをご覧ください!(ぜひスキもお願いします❤️‍🔥)

note.nealle.com

Amazon Connectを使用したコンタクトセンターの開発・運用📞

インターンの中で私が一番時間をかけて、最もサクセスエンジニアリングチームで先頭に立って進めることができた業務がAmazon Connectを使用したコンタクトセンターの開発・運用でした。Amazon ConnectとはAWSが提供するクラウド型コンタクトセンターサービスで、駐車場を借りている人・貸している人からの電話を受けたり、反対にニーリーから電話をかけたりする業務を一括で担っています。

電話をかけてきた人がいくつかのステップを踏んでオペレーターにつながるまでの流れを”フロー”として管理しているのですが、そのフローの新規開発や既存フローの編集を主に担当していました。今年は架電側の業務にも多く携わり、フローの開発だけでなくBaseMachinaを利用して開発以外のメンバーも任意のタイミングで架電できるように業務の引き継ぎなどを行いました。顧客とニーリーを繋げる窓口としてサービス全体に響いてしまう箇所なので、そこでシステムに詳しい人間になって、実際にサービスを使うビジネス側の部署の方々と会話を重ねて運用ができたことはとても印象的です。

Amazon Connectのフロー

社内ツールの開発・運用💬

ビジネス側の方が使う社内ツールの開発と運用にも多く携わり、その中でも特に印象深いのはAmazon Connectで構築したコールセンターに待ち顧客が発生している場合にLambdaを用いてSlackに通知するツールの開発です。入社してまだ2ヶ月ほどでしたが、ビジネス側の方々と議論をさせてもらったり、SREチームの方にAWSのサポートをしてもらいながら開発を行いました。

待ち状態になっていることを担当のチームにメンション

現在も運用しており、自分の開発したツールが業務効率化に直接寄与できたことを、社内懇親会などで直接お話を伺った際に実感し、非常に嬉しかったです。 また、チームとしてビジネス側の課題を吸い上げ、それを技術領域に捉われることなくエンジニアリングで解決していくという、サービスのグロースを地道に達成していく経験を積めてとてもよかったです。

ホームページとLPの開発・運用🌐

入社してすぐは、ホームページとLPの開発や運用に携わっていました。その中でも入社して5ヶ月ほどで、管理を委託していたコーポレートサイト(https://nealle.com/)を内製化するプロジェクトに携わったことはとても印象的な業務です。

nealle.comのトップページ

移管の準備から委託先のエンジニアの方と会話しながら実際にサイトを移管し、さらには存在しなかったSTG環境の構築まで携わり、とても多くの経験を積むことができました。学生の開発では経験できない、社外との連携が必要で、会社全体に影響するプロジェクトで、さらに納期があるなどの条件でお仕事ができたのは貴重な経験でした。また、私主導でサイトのコンテンツ更新のシステムも変更することになり、エンジニアを介さずに広報チームが任意のタイミングでサイトを編集できるようになり業務効率化にもつながりました。

働いてみて得た、大事にしたい考え方

私がニーリーで働いて得た、大事にしたい考え方は以下の3つです。

  • 顧客が欲しいものを作らないこと
  • チームでアウトプットを出すこと
  • 一歩先・次に何が起こるか考えること

顧客が欲しいものを作らないこと🙅‍♂️

これは前述した、入って2ヶ月ほどで社内ツールを開発することになった時のことですが、私はビジネス側の方とのミーティングの時に「こんな機能が欲しくて」「Slackのここにボタンが欲しくて」という話ばかりを聞いてそれを愚直に実装しようとしていました。

そんなことを実装できるわけもなく時間ばかりが過ぎていた頃、CTOの三宅さんに「顧客が欲しいものを作らないこと」と話をいただきました。これは「顧客が欲しいものを作ること」を目的にするのではなく、「顧客の課題を解決すること」を目的にしなさいという話でした。その時に言われたのは、「顧客の欲しいものを作るのがエンジニアではなく、顧客が抱えている課題を解決するために僕らのエンジニアリングのスキルや知識を持って、一番スマートな方法で課題の解決策を提示するのがプロフェッショナルとしてのエンジニアだよ」と言われたことを強く覚えています。

その時から私は「ものづくりをする」人がエンジニアではなく、「エンジニアリングスキルを持って(スマートな解決策のためには使わなくたっていい)課題の解決をする」人がエンジニアなんだと思うようになり、これは私の根幹になる考え方になりました。

チームでアウトプットを出すこと🤝

これは私が所属するサクセスエンジニアリングチームの一員として、さらにニーリーの一員として働く中で感じたこととして、ニーリーには、常に相手にリスペクトの心を持っているけど、仕事に熱量を持っていて、言うべきことはしっかり言う。そんな方が多い気がしました。その根幹にあるのは、私たち一人一人がニーリーという会社で一丸となって事業の成長に繋がっているという意識ではないかと思いました。

チームリーダーである中村さんは、チームに対して「僕から何か言ってそれをそのまま実行したり、アサインしたタスクをそのまま何も考えずに取り組むのではなく、一緒に議論して、一緒に考えて欲しい」とよく話をしています。これが私にとってはすごくありがたく、このおかげでインターン生ながらもチームで同じ目線に立ってタスクの進め方やそもそもタスクをやる意義について意見を常に挙げ続けることができました。さらにはタスクの進め方や詰まってしまった時の相談もチームでアウトプットを出すために必要なことだと感じるようになり、その重要性を強く意識するようになりました。

一歩先・次に何が起こるか考えること🧐

これは私が仕事している中で感じていて、今でも足りないなと日々思い続けていることです。それは、「一歩先・次に何が起こるか考えること」。例えば、誰かにメッセージを送るにしても送られた相手はどう思うだろうか?そういったことを考えることです。中村さんは「相手に何かを伝えるということは、相手に何かアクションを起こしてもらいたいとき。じゃあ相手がそのアクションを迷わず起こしてくれるようにどう導けるかというものを考える必要があって、そうなると自分のアクションについて考える必要がある」という話をしていただきました。

レビューをお願いする時にも、じゃあこの人はこの後何をするだろうか?何か足りない情報があって質問してくるだろうか?という考えをいつも巡らせ、相手の立場に立って考えてみることの重要さや、コミュニケーションの取り方を改めて考える機会になりました。これは自分が作業する際にも同様で、常にタスクを分解して、何が終わっていて、終わっていなくて、次に何をする必要があるのか、そんな頭の使い方を少しづつできるようになりました。

最後に🙇‍♂️

ニーリーでは、事業の成長に繋がることをとにかくやっていました。そんな環境では、自分のやっていることや自分にできることが事業に繋がっていると思える瞬間が多くありました。とても楽しかったです。

私を採用してくれて、たくさんの学びを得ることができたこの会社には恩しかありません。ニーリーでインターンをしなければ、ニーリーで働く方々から数々の言葉をもらうことも、自分がどんなエンジニアで、どんな人間でいたいのかをここまで考えることもできなかったと思います。

またどこかで何か一緒にやりたいです。1年半もの間、大変お世話になりました。次のステージでも頑張ります💪

ニーリーのことが気になったらぜひカジュアル面談へ〜!サクセスエンジニアリングチームでぜひ事業成長をエンジニアリングで加速させていきましょう❤️‍🔥

herp.careers

pull requestを利用したいい感じのECS feature環境管理方法を考えた

はじめに

SREチームの大木です。スノボの季節がもう終わりかけており、さみしい限りです。

feature staging環境*( 以下 feature環境 )自体のライフサイクルや管理をどうするか問題、なかなかどこも苦労していると思いますが、その中で今回それなりにいい感じの回答を出せたと思うので共有したいと思います。

*呼び方はpre-staging環境、pull request環境、テスト環境などいろいろありそうですが、私たちはfeature環境と呼んでいます。

どこが「いい感じ」なのかというと、PRのラベル付与によって環境の生成/削除を制御できる点です。PR画面上で楽々とfeature環境の管理ができたり、PR一覧からどのブランチでfeature環境が立っているかが分かりやすくなります。

feature環境について

feature環境を当社のプロダクトであるPark Directの開発にも導入しました。各開発者の作業ブランチのコードを、そのままアプリケーションとしてインフラにデプロイすることができるアレです。

Park DirectのアプリケーションはECS serviceで動作しており、Dockerfileやタスク定義ファイルもアプリケーションのリポジトリに格納されていたので、それらをfeature環境でも使用することにしました。

作成はGitHub ActionsをトリガーとしてCloudFormationを用いて行い、作成されるリソースとしてはECS task定義、ECS serviceのほか、ALB target group ( ALB 自体は共有 ) 、その他リソース(後述)があります。

独自ドメインも払い出されるため、そのドメインを使用して他の開発者や非エンジニアもスムーズに確認と検証ができるようになります。

また、通常ではdevelopのDBにそのまま繋いでいるのですが、ECSタスク定義をよしなに書き換えることにより、別のDBに接続することも可能です。

環境の作成方法

この辺が工夫したポイントです。

feature環境はpull request単位で立ち上げる必要がありますが、全てのpull requestで等しく環境が欲しいわけでもなく、またpr自体は長期間オープンにされ続けることもあるので、pull requestのライフサイクルとfeature環境を一緒にするのは難しいものがありました。

そこで、pull requestのラベル付与/剥奪をトリガーにfeature環境作成/削除を行うことにしました。冒頭でも触れたとおり、PR画面上で管理ができるのが嬉しいポイントです。

また、feature環境はFargate Spotを採用することによりコスト圧縮も行っています。

しかし、長時間処理の検証で使いたいなど、サービスの停止が許容できない環境もあるので、付与するラベルによりFargate or Fargate Spotどちらを使用するのか選べるようになっています。

ラベルを付与することで環境が立ち上がる。-ha を付与すると通常のFargateで起動

GitHub Actionsを使用している場合に限りますが、feature環境をpull request のラベルで管理するのは操作もしやすくカスタマイズ性も高いのでオススメです。

また、21時に定期的にfeature環境用ラベルを削除する処理も実装し、feature環境の立ち上げっぱなしを防止しています。

技術的なお話

feature環境の1単位としては以下を作成する必要があります。

  • 独自ドメイン用のRoute53 record
  • ALB target group
  • ECS各種 ( ECS service, ECS task definition)
  • CloudWatch Event rule

ALB自体はfeature環境用を1台用意し、そのALBのリスナールール上でhost名を確認することによってどのfeature環境へアクセスするかを振り分けています。 これにより、1本のALBで複数のfeature環境を管理することができます。

(優先度の重複が認められないので、pull request 番号を使用しています。pull request単位で立ち上がるので重複の心配がなく幸せです)

実際の構築の流れは以下の感じです。

  1. pull requestにラベルが付与される
  2. ラベルの追加 ( or ラベルが追加されたprにpushがあった時) をトリガーでGitHub Actions workflowが発火
  3. ALB listener ruleのpriorityなどの動的な値をパラメータとして、CloudFormation stack作成

作成時のworkflow job発火条件はこんな感じ

    if: >

      (github.event.label.name == '[feature] backend' && github.event.action == 'labeled')

      ||

      (github.event.label.name == '[feature] backend-ha' && github.event.action == 'labeled')

      ||

      (

        (

          contains(github.event.pull_request.labels.*.name, '[feature] backend') 

          || contains(github.event.pull_request.labels.*.name, '[feature] backend-ha')

        )

        && github.event.action == 'synchronize'

      )

CloudFormation stack名をブランチ名とすることで、どのブランチの環境なのかが明確だったり、更新や削除の際にもブランチ名からCloudFormation stackを引っ張ってくれば良いので幸せです。

  delete_resources:

    name: Delete resources

    runs-on: ubuntu-22.04

    if: >

      (

        (github.event.label.name == '[feature] backend' || github.event.label.name == '[feature] backend-ha' )

        && github.event.action == 'unlabeled'

      )

      || (

        github.event.action == 'closed' 

          && (

            contains(github.event.pull_request.labels.*.name, '[feature] backend') 

            || contains(github.event.pull_request.labels.*.name, '[feature] backend-ha')

          )

      )

    permissions:

      contents: read

      id-token: write

    timeout-minutes: 5

    steps:

      - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 commit hash

      - name: Configure AWS Credentials

        uses: aws-actions/configure-aws-credentials@8c3f20df09ac63af7b3ae3d7c91f105f857d8497 # v4.0.0 commit hash

        with:

          role-to-assume: ${{ env.AWS_ASSUME_ROLE }}

          aws-region: ${{ env.AWS_REGION }}

      - uses: ./.github/actions/set-env-variables

      - name: delete stacks

        run:

          aws cloudformation delete-stack --stack-name "pd-backend-feat-${{env.task_id}}"

このように、『各環境とCloudFormation stackが1:1で紐づいて管理ができる』ところがGitHub Actions上で動的に環境を作成/削除する際にとても親和性がよかったです。

また、CloudFormationを使用することによりECS関連以外のリソースも作成することができます。 アプリケーションの都合上本来であれば1 feature環境につき1 Lambda を用意しなければいけない仕様でした。 各環境固有の値はEventで渡せるようにコードを直した feature環境用 lambda を用意し、CloudFormation でEventBridge ruleを作成することで実現しました。 このように ECS 以外のリソースも柔軟に作成することができるのは amazon-ecs-deploy-task-definition や ecspresso などにはない利点なのかなと思います。

終わりに

当時は弊社の他の場所でCloudFormationを使用したことがなかったので採用は迷ったのですが、feature環境の構築というごく一部で、且つ管理対象リソースも少ないことから得られるメリットの方が大きいと判断し採用しました。結果としては、CloudFormation template自体はほとんど変更されることもなく ( = CloudFormationのメンテナンスコストを払うことなく ) 、恩恵だけを得ることができているので採用してよかったです。 また、ラベルでの管理も「このブランチの環境は立っているのかどうか」がすぐ分かり、また簡単に管理ができるのでとても使用感が良いです。

想像以上に使用されたのでfeature環境のコスト削減に取り組む必要があり、夜間停止を導入することになったのですが、その際も「n時以降自動でラベルを剥奪」という workflow を組むことで簡単に対応することができました。 feature環境を管理するにあたって「pull requestのラベルをトリガーとした環境の操作」だったり「CloudFormationを用いた環境の管理」を選択したことでだいぶ幸せになれました。おすすめです。

そして、9月ぐらいからfeature環境を導入したのですが、9月から12月までの数ヶ月間で実に200近くのfeature環境が使用されていました。確実な需要を元に環境を整備しましたが、実際にこうやって使用されているのは嬉しい限りです。