TECH PLAY

株式会社ZOZO

株式会社ZOZO の技術ブログ

987

.entry .entry-content .table-of-contents > li > ul { display: none; } はじめに こんにちは、ブランドソリューション開発本部プロジェクト推進部PMOブロックの三谷です。普段はPMOとして、 ファッションコーディネートアプリ WEAR の開発組織が企画を実現する上で発生する様々な課題の解決サポートを行っています。 WEARは2014年のローンチ以来アップデートを繰り返し、様々な機能をリリースしてきました。その中で、1つ1つの案件が大きくなってしまうことがしばしばあり、リリースまでのリードタイムや価値検証のサイクルが長くなりすぎてしまうことがありました。この課題を解決すべく、2024年5月より私たちPMOブロックは開発責任者 @tsuwatch のもと、スクラム開発の導入を推進しました。本記事では、スクラム導入におけるPMOの取り組みをご紹介します! なお、今回ご紹介する内容はWEARのアプリ開発に限った話であり、Webサイトの開発は対象外としています。 目次 はじめに 目次 背景 スクラム開発の導入 WEARの内情に合わせたスクラム体制の構築 チームとロール スクラムマスター 横断的に3チームを俯瞰するスクラムマスター チーム専属でスクラムを進行するスクラムマスター 開発者 スプリントの動き リファインメント 開発〜リリース ツールと運用方法 ストーリーポイント計算の自動化 リードタイム計測の自動化 実運用で発生した課題とその解決策 PBIの方針がチーム内で決まらずスピード感が損なわれる QAのリソース不足によるリリース先送り スクラムを導入した効果 リリーススピードが向上 チームの自己組織化 今後より良い開発チームを目指してPDCAを行なっていくために 質と量を意識した開発 まとめ 背景 これまでWEARでは、ビジネス的な観点やUXの観点などから様々な要件が盛り込まれて工数が膨れ上がり、最終的な成果物によって得られる効果に見合わないほど工数が大きくなってしまうことが多々ありました。そこで、2024年5月のフルリニューアルが完了したことを機に、その後の機能開発は最低限の要件に絞って早くリリースし、ユーザーの反応を見て次の手を打つスクラム開発を導入しました。また、これまでの開発では企画〜リリースまでの各プロセスのリードタイムやリソースの効率などの各種メトリクスが可視化されておらず、チームの成長や積み上げ、生産性が向上しているかがわからない状態でした。スクラムの導入と共に、これらのメトリクスを可視化する仕組みも取り入れ、チームが振り返りながらより早くユーザーに価値を届けていける体制を構築しました。 スクラム開発の導入 WEARの内情に合わせたスクラム体制の構築 WEARの開発には、「PdM」「エンジニア」「デザイナー」「QA」など様々なロールのメンバーが関わっています。 その中でも特にWEARひいてはZOZO社(以下、ZOZO)の開発において特徴的と言えるロールが、「デザイナー」です。ZOZOではZOZOTOWN、WEAR、FAANS、ZOZOマッチなど様々なサービスを展開していますが、それらのユーザー体験はすべてデザイナーによってデザインされています。また、各サービスは独立してデザインされるのではなく、デザイナー組織のディレクター(以下、便宜上「デザインディレクター」と呼びます)が各サービスを統括して全体の世界観を保っています。そのため、WEARのデザインにおいてもデザインディレクターの承認を得る必要があり、そのこともスクラム体制に盛り込む必要がありました。 また、もう1つの特徴として「QA」がエンジニアから独立している点があります。エンジニアだけでテストを行う会社もあると思いますが、ZOZOではQA組織がエンジニアと独立して存在しており、開発が完了した成果物はQAによって検査することで品質を担保しています。この点もスクラム体制に盛り込みました。 このようなWEARの事情を考慮して構築したスクラム体制を以降で紹介していきます。 チームとロール WEARではスクラム導入以前から、個別のKPIを立てた3つの開発チームが存在していました。スクラム導入ではこのチームをそのまま引き継ぎ、3チームそれぞれ個別のバックログを持った1つのスクラムチームとなるようにしました。1つのチームのメンバー構成は、主に以下の通りです。 ロール 人数 主な役割 PdM 1名 スクラムではプロダクトオーナーを担います デザイナー 1〜2名 デザインの作成を担います エンジニア BEエンジニア 2〜3名 BEの開発を担います iOSエンジニア 2〜3名 iOSの開発を担います Androidエンジニア 2〜3名 Androidの開発を担います QA 1名 品質管理を担います アナリスト 1名 ユーザーの行動を分析し、チームの意思決定のサポートをします また、開発責任者(1名)とPMOが3チームを横断的に支援する構成にしています。上記の各ロールをスクラムの3つの役割である「開発者」「プロダクトオーナー」「スクラムマスター」に割り振ってスクラムを行っています。以降では、その中でもWEAR特有の構成である「スクラムマスター」と「開発者」について具体的に説明します。 スクラムマスター 一般的なスクラムと比べると特殊かもしれませんが、以下のようにスクラムマスターの役割を2ロールで分担して担っています。 横断的に3チームを俯瞰するスクラムマスター スクラム導入期には、3チームでの一斉導入にあたって本記事で紹介している体制の検討や基盤となる仕組みやルールの整備、メンバーへの教育など、様々な導入支援を行う必要がありました。この役割は、私の所属するPMOブロックが担いました。私を含めブロック内でスクラム開発に詳しい者がいなかったため、Scrum Allianceの「認定スクラムマスター(CSM)」を取得して学習したり、社外のスクラムコーチに相談したりして進めました。様々な観点を検討した上で、開発に関わるメンバー全員へスクラム導入のメリットが伝わるよう説明をしました。 スクラム導入後は、実運用で発生した課題に対する解決案の検討やそれに伴う仕組みの管理、スクラムチームがより良い開発をしていくための提案などを行っています。スクラムチームが直面する課題には、チーム外のステークホルダーが関わっていたり、複雑なため解決策を時間をかけて整理する必要があるなど、チームが単独で解決するにはコストのかかるものがあります。そういった課題に対して、実際に手や頭を動かして解決に導く役割を担っています。後述する、 実運用で発生した課題とその解決策 や 今後より良い開発チームを目指してPDCAを行なっていくために も、私たちPMOブロックが担った課題解決の一例ですので、ぜひご覧ください! チーム専属でスクラムを進行するスクラムマスター 今回ゼロからスクラムを始めるにあたり各チームには進行役のスクラムマスターが必要でしたが、PMOはスクラム以外の課題も担当しており、さらに人数も不足していたため、アサインできませんでした。そのため、各チームにはスクラム導入以前から開発進行を管理する「チームリーダー」がエンジニアから1名設定されていましたが、彼らにスクラムマスターの役割もお願いしました。スクラムイベントの進行やチーム改善などを行ってもらっています。 また、PMOとチームリーダーは毎週定例会を実施しています。そこでは、各チームの課題相談やナレッジの共有、必要に応じて全体の共通ルールの検討なども行い、改善活動を行っています。 開発者 エンジニア、デザイナー、QAが担います。WEARではデザイン、開発、QAがそれぞれ専門のロールに分かれているため、スクラムチームではそれぞれのロールが専任で担当します。ただし、役割やタスクを綺麗に線引きするのではなく、互いに補完し合ってチームとして必要なことを全員で実施していくようにしています。 エンジニアについて補足すると、WEARはiOS、Androidで同様のアプリを提供しているため、機能開発を行う際は基本的に両OSで同じ機能を同時に開発します。そのため、1つのチームに両OSのエンジニアがそれぞれアサインされています。 また、WEARの特徴的なロールとして先述したデザイナーとQAもスクラムチームに含めて開発しています。 スプリントの動き 各チームが2週間のスプリントで開発し、スプリントプランニング、デイリースクラム、スプリントレビュー、レトロスペクティブ、リファインメントを実施しています。その他、一般的なスクラムと大きく差がない部分は割愛し、以降ではWEAR特有の仕組みをメインで紹介します。 リファインメント 毎週定期で開催(例:1時間 × 2回)し、次のスプリント以降で開発着手したいプロダクトバックログアイテム(以下、PBI)を着手できる状態(Ready)にしています。 デザインの作成もReadyの条件に含めていて、リファインメントで完了させています。先述したように、デザインの完了にはデザインディレクター承認が必要ですが、開発着手までにとる構成にしています。 また、リファインメントでは、バグとなりうる仕様をあらかじめ排除する等の目的で、QA観点での仕様検討も行っています。 開発〜リリース リリースタイミングは定期で定めていて、スプリント終了後の翌営業日としています。開発が完了したPBIに対して順次、デザインレビュー・QAを行い、スプリント内でデザインレビュー・QAまで完了したものを次のリリースタイミングでリリースしています。以下の図は、いくつかのPBIの進行例を示しています。その中でも【A】と【D】のように、1週目で開発を完了させて2週目でデザインレビュー・QAを完了させることで、1スプリントでリリースできることを目指してPBIのボリュームを調整するようにしています。 ツールと運用方法 WEARでは、スクラムの進行にJiraを使用しています。Jiraにはバックログやスプリントの管理など、オンラインでスクラムを行う上で必要な機能が一通り備わっています。ここでは一般的なJiraの機能の紹介は割愛しますが、WEAR特有の使い方をしている部分を紹介します。 ストーリーポイント計算の自動化 PBIで必要な作業を各ロールがJiraの作業項目を作成して管理していますが、各作業のストーリーポイントを同名のフィールドにつけることで、スプリントプランニングやベロシティ計測に使用しています。WEARでは、1つの作業項目(親)をさらに細かい粒度のサブタスク(子)に分割して作業進捗を管理しており、サブタスク一つひとつにもストーリーポイントをつけています。 作業項目の粒度の例:〇〇画面のUI改修 サブタスク粒度の例:ボタンのレイアウト追加、〇〇処理にログを追加 Jiraの仕様では、ベロシティに計上されるストーリーポイントは作業項目のもののみで、サブタスクのストーリーポイントはベロシティに計上されません。そのため、親の作業項目にサブタスクのストーリーポイントの合計値を計算してつけなければなりませんが、デフォルトでは、サブタスクを追加やストーリーポイントを修正する度に手動で計算する必要があります。WEARでは、Jiraの「自動化」機能を用いて、サブタスクのストーリーポイントが変更されたのをトリガーに自動で親の作業項目とストーリーポイントを同期するシステムを組み込んでいます。 リードタイム計測の自動化 WEARでは、スクラムにおける各工程のリードタイムを計測して振り返りを行っています。作業項目やサブタスクの「実施開始日」「実施終了日」というフィールドに、作業した実際の日付を入力することで計測していますが、通常は手動で入力する必要があります。こちらもJiraの「自動化」機能を用いて、作業項目やサブタスクのステータスが変更されたのをトリガーにして自動で実施開始日・実施終了日を入力するシステムを組み込んでいます。 実運用で発生した課題とその解決策 PBIの方針がチーム内で決まらずスピード感が損なわれる PBIの方針は、前述の WEARの内情に合わせたスクラム体制の構築 で説明したように、スクラム外のデザインディレクターの承認を得る必要があります。そのため、チームのリファインメント内で決定ができず別枠でMTGを設けてデザインディレクターを含めて検討することになりましたが、なかなか予定が合わず、決定までに時間がかかってしまうことがありました。解決策として、デザイナーがデザインディレクターとの定期MTGにて相談した上で次回のリファインメントに持ってきてもらうようにしました。また、PBIの方針案を固めてからディレクターに相談するのではなく、検討の初期段階から相談するようにして後から方針が再検討になることを防ぎました。これによって、リファインメントのスピードが向上しました。 QAのリソース不足によるリリース先送り QAのタイミングについて スプリントの動き - 開発〜リリース で紹介しましたが、もともとはPBIごとではなくそのスプリントで開発完了したものをスプリントの後半1週間でまとめてQAをする運用でした。そのため、開発完了するPBIが多いスプリントではQAリソースが足りず、開発完了していてもリリースは次のスプリントに先送りというもったいない事態になることがありました。解決策として、QAでのバグ起票の多さがQAコストにつながっていたので、レトロスペクティブにバグを振り返る時間を作り、ユニットテストレベルのバグは開発段階で潰すことを目指しました。また、QA開始のタイミングをQA担当者に調整してもらい、PBIの開発完了タイミングでPBIごとに並行してQA開始できるようにしてもらいました。イメージは スプリントの動き - 開発〜リリース の図を参照ください。結果として、QAのリソース不足は解消され、リリースが先送りになることがなくなりました。 スクラムを導入した効果 リリーススピードが向上 スクラム導入以前はリリーススピードを計測していなかったため正確な評価ではありませんが、以前の体制で実施した一定期間の案件実施数と比較すると、案件実施数が増えました!また、PdMからも明らかにリリース件数が増やせたとコメントをいただきました! チームの自己組織化 定期的なスクラムイベントの実施により、チーム内のコミュニケーションが円滑化し関係値も構築されました!また、PdMやアナリストからユーザーの行動やチームKPIの進捗が共有されることで、目標に対するチーム全体の意識が高まり、チームで振り返り→その後のアクションを立てられるようになりました! 今後より良い開発チームを目指してPDCAを行なっていくために 質と量を意識した開発 前述の通り、リリーススピードが向上し、従来よりもたくさんのアウトプットをユーザーへ届けられるようになりました。質も当然意識しつつ、ユーザーの反応を見ながら開発を進めていましたが、まずはアウトプットの量を増やすことを目標としていました。その点は一定達成できたため、次のステップとしては、質と量の両方を意識して効率よく大きなアウトカムを生み出すことを目指しています。具体的な取り組みとしては、PBIの期待度をスコア化して期待度と工数のバランスを見てチームの動きを改善していく仕組みを検証しています。今後、良い仕組みが確立できたら別の記事にて紹介します! まとめ 本記事ではWEARのスクラム導入について、私たちPMOブロックが行ってきた支援について紹介しました。一筋縄ではいかない課題がたくさんありましたが、一つひとつ検討を繰り返して今のスクラム体制を築くことができました。開発チームに同じような課題を持たれている方がいれば、ぜひ参考にしてみてください! ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
.entry .entry-content .table-of-contents > li > ul { display: none; } 1. はじめに 検索基盤部 検索基盤ブロックの佐藤( @satto_sann )、岡田( @ryokada33 )、SRE部 検索基盤SREブロックの富田( @kei_gnu622 )です。 ZOZOTOWNアプリ用に検索機能を提供していたオンプレサーバー上のレガシーなAPIを、約1年かけてクラウド環境へ全面リプレイスしました。 このリプレイスプロジェクトでは スパイラル開発の導入 や 開発初期からの環境整備 といった工夫を積み重ねることで、当初のスケジュールどおり移行を完了しています。 また、副次的な効果として、 検索速度が約2倍に向上 しユーザー体験の改善にもつながりました。 本記事では、 大規模リプレイスを円滑に進めるために実践したプロジェクト運営の取り組み を、以下の流れで紹介します。リプレイスをはじめとした、ソフトウェアやマイクロサービス開発プロジェクトの参考になれば幸いです。 【2章】リプレイスプロジェクトの概要 今回のプロジェクトが直面した技術的・運用的な難しさと、限られたリソースの中でどのような制約があったかを説明します。 【3章】なぜスパイラル開発なのか 過去のWeb版リプレイスで直面した課題を振り返り、その学びから今回スパイラル開発を選択した理由と背景を解説します。 【4章】スパイラル開発を実践するための4つの工夫 2週間サイクルでの開発、mainブランチへの積極的なマージ、機能フラグの活用、開発初期からの環境整備という4つの実践ポイントを紹介します。 【5章】スパイラル開発を支えたテストプロセス 開発サイクルに組み込んだテスト戦略と、品質を維持しながらスピードを保つために設計したテストプロセスの全体像を説明します。 【6章】デプロイ基盤とリリース戦略 頻繁なリリースを支えた自動デプロイの仕組みと、本番環境への影響を最小限に抑えたリリース戦略を紹介します。 目次 1. はじめに 目次 2. リプレイスプロジェクトの概要 2.1. プロジェクトの難しさ 2.2. 制約と方針 3. なぜスパイラル開発なのか 3.1. 過去のリプレイスで直面した課題と学び 3.2. スパイラル開発の選択 4. 大規模リプレイスをスパイラル開発で回すポイント 4.1. 2週間ごとに「動くもの」をつくる 4.2. mainブランチへの積極的なマージ 4.3. 機能フラグを使って安全に本番リリース 4.4. 外部サービス連携部分のモック化 5. スパイラル開発を支えた「テストプロセス」 5.1. テストを開発のサイクルに組み込む 5.2. スパイラル開発のために設計したテストプロセス全体像 5.3. テストプロセスのポイント 5.3.1. E2E動作確認:実装直後から「プロダクトとしての挙動」を確かめる 5.3.2 レスポンス比較リグレッションテスト:守るべき仕様の破壊を防ぐ“自動チェック” 5.3.3. レビュー会:ユーザー目線での齟齬をスプリント単位で解消する 5.4. 成果と学び 6. デプロイ基盤とリリース戦略 6.1. 安全かつ効率的なデプロイの仕組み 6.2. 非機能面の品質確保 6.2.1. 負荷試験・障害試験を実施 6.2.2. SRE部のテックリードによるリリース前レビュー(プロダクションレディチェック) 6.2.3. リリース前に開発者とSREで監視指標の認識合わせ 6.3. リプレイスAPIへの安全な切り替え 6.3.1. 段階的なトラフィック切り替え 6.3.2. 即座に切り戻せる安心感 7. おわりに ちなみに 紹介 2. リプレイスプロジェクトの概要 本題へ入る前に、今回のリプレイスプロジェクトの概要を紹介します。ZOZOTOWNではWeb・アプリともに継続的にリプレイスを進めています。 techblog.zozo.com 今回のプロジェクトでは、ZOZOTOWNアプリに検索機能を提供するVBScriptで書かれたレガシーなオンプレAPI(以下、レガシーAPI)をリプレイスしました。リプレイス先は、クラウド環境で稼働するSpring Boot(Java)で構築されたAPI(以下、リプレイスAPI)です。 2.1. プロジェクトの難しさ このプロジェクトの難しさを少しお伝えすると、以下3つの技術的・運用的な難しさがありました。 課題1:膨大な検索条件の組み合わせ 検索に関わるAPIパラメータは70項目以上にのぼります。これらは自由に組み合わせられるため、考慮すべきパターンは「組み合わせ爆発」と言えるほど膨大です。あらゆる条件の組み合わせに対して正しく結果を返せることが求められました。 課題2:高トラフィック環境での安定稼働 検索はZOZOTOWNのほとんどのユーザーが日常的に利用する主要な機能であり、常に高トラフィックに晒されています。高負荷な環境下であっても安定した動作やサービスを止めることなくリプレイスすることが必須条件でした。 課題3:レガシーコードの移行 十数年にわたり機能追加を重ねてきた数万行のVBScriptコードを、Spring Boot(Java)へ移行するという大規模な技術的チャレンジがありました。レガシー資産を正確に読み解きつつ、新しいアーキテクチャへ落とし込む作業は想像以上に複雑で、多方面での工夫と知見が求められました。 2.2. 制約と方針 ソフトウェア開発プロジェクトでよく知られる「 アジャイルの鉄の三角形 」では、以下3要素がトレードオフの関係にあるとされています。 スコープ(範囲) スケジュール(期間) リソース(品質・コスト) 今回のリプレイスプロジェクトでは、この三角形の各要素について以下のような制約がありました。 スケジュール ZOZOTOWNの事業計画や、オンプレサーバーの縮退スケジュールを含めたリプレイス全体のロードマップとの兼ね合いから、期限を大幅に延長することは困難な状況でした。 スコープ “リプレイス”という特性上、すでに稼働しているZOZOTOWNアプリの「検索機能」を過不足なく実装する必要がありました。また、検索の機能全体を見渡すと、ネイティブアプリ、BFF(Backend For Frontend)、検索エンジンなど、複数のレイヤーが関わっています。しかし、リプレイス期間の肥大化を抑えるため、今回のスコープはBFFレイヤーのAPIリプレイスのみに絞り込みました。具体的には、ネイティブアプリ側のコードは一切変更せず、APIのインタフェース(リクエスト/レスポンス形式)も原則として維持する方針としました。 リソース 5〜7名体制と限られた開発リソースの中での開発となりました。 このように、スケジュールとスコープが固定された状態で、限られたリソースを最大限活用しながら品質を確保することが求められました。 3. なぜスパイラル開発なのか 過去のリプレイスプロジェクトでは、属人化や結合の遅れによって手戻りが多発し、大規模リプレイスの難しさを痛感しました。 今回も扱う範囲は広く複雑で、初期に全体計画を固めることが難しい一方、スクラムのような価値検証型の進め方とも相性が良くないと考えていました。そこで、実装する機能単位を区切って品質を積み上げられるスパイラル開発が最も適していると判断しました。本章では、その判断に至った背景と学びを簡潔に整理します。 3.1. 過去のリプレイスで直面した課題と学び 私たちは過去に、Web版ZOZOTOWNの検索結果ページの大規模なリプレイスを実施しました。(Web版のZOZOTOWNとアプリ版のZOZOTOWNの検索機能を担うロジックは別のマイクロサービスで動いていました)。 当時のリプレイスでは、度重なる手戻りが発生し、全体として大きく工期が延びてしまいました。最終的には無事リリースに至りましたが、その過程で「大規模リプレイスの難しさ」を強く実感する結果となりました。 課題1: 実装する機能単位での分担による属人化と認識齟齬 過去のリプレイスでは、機能単位(スコープ)ごとに設計からリリースまで全期間で担当者を割り当てる形で進めていました。この方式により、各担当者が自分の範囲に強い責任感を持って開発を進められた一方で、機能ごとの仕様理解、実装方針や設計思想が分断され、チーム全体での整合性を保つことが難しくなりました。 その結果、部分的には完成していても全体として連携できない状態が生まれ、後任者や他メンバーへの知識共有が難しくなるなど、属人化が進行しました。また、仕様の整合性確認やレビューにも余計な時間がかかる要因となりました。 課題2: フロントエンドとの結合の遅れによる課題 過去のリプレイスでは、チームが大きかったこともありバックエンド側の実装がある程度進んでから、結合テストを行う方針でした。フロントエンドやクライアントアプリとの結合は後回しになっていました。しかし、API設計段階でのレビューやモック連携は行っていたものの、終盤の結合タイミングで、想定外の仕様差分や不具合が一気に顕在化しました。 実際に動作する環境でのユーザーストーリーベースのテストをおこなった際に、バックエンドとクライアントの間で整合性を欠き、検索結果の挙動や表示内容に不具合が多く見られました。 E2E(エンドツーエンド)の動作を通して初めて明らかになる不具合が多く、設計段階では検知できなかった差分対応が後半に集中しました。その結果、仕様調整や修正対応がテストフェーズの負担を増大させる要因となり、リリース準備全体に遅延を引き起こしました。 学び これらの経験を通して、チームは次の3点を学びました。 早い段階での結合と検証が重要であること 実際の動作環境で早期に結合テストを行い、ユーザーストーリー単位で整合性を確認することが品質向上につながると学びました。 プロダクトとしての品質検証は初期の段階からプロセス全体に組み込むべきこと テストを後工程ではなく、開発の反復サイクル内に取り込むことで安定した品質を保てると考えました。 担当機能を固定しすぎないこと 長期間同じ機能の開発を担当し続けると属人化や認識齟齬が生じやすくなるため、メンバー間で担当する機能を固定せず、知識を共有しながら進めることが重要だと感じました。 3.2. スパイラル開発の選択 前回のリプレイスでは、広い範囲を一度に開発・結合しようとしたことで、認識齟齬や手戻りが増え、チーム全体の負荷が高まってしまいました。 今回のリプレイスは「新しい検索体験をつくる」プロジェクトではなく、既存検索の同等挙動を新システム上で再現するという性質を持つプロジェクトです。要件自体は明確でしたが、ZOZOTOWN20年の歴史を支えてきた大規模で複雑なコード群を扱う必要があり、全体を見通して計画を立てにくい構造的な複雑さが存在していました。 ※このような状況は、クネビンフレームワークにおける「Complex(複雑)な領域」に近い状態といえます。 .w70p .figure-image img { width:70%; } Tom@thomasbcox.com , CC BY-SA 4.0 , via Wikimedia Commons そのため、ウォーターフォール型のように初期段階で全体を計画しきる進め方では対応が難しく、一方で、スクラムのように仮説検証を繰り返す新規事業型のアプローチとも性質が異なっていました。そこで私たちは、 スパイラル開発 を採用することにしました。 スパイラル開発は、チームで集中して実装する機能を期間で明確に区切り、その期間でリリース品質まで集中して仕上げるサイクルを繰り返す開発手法です。 スクラムが「価値検証(アウトカム)を中心に、仮説を試しながら仕様を磨く」手法であるのに対し、スパイラル開発は「あるべき挙動が明確な機能を、確実に積み上げていく」ことを目的としたアプローチです。 今回のリプレイスプロジェクトでは、スクラムが得意とする価値検証型のサイクルを必須とせず、仕様の再現性と高品質なアウトプットを積み重ねるスパイラル開発のほうが適していると判断しました。 下表に、各手法と本プロジェクトとの相性をまとめます。 手法 特徴 今回のプロジェクトとの相性 ウォーターフォール 設計からリリースまでを一方向に進める。計画が明確な反面、変更や手戻りに弱い。 ✗ 既存仕様の再現範囲が広く複雑。終盤の結合で手戻りが発生しやすい。 スクラム アウトカムを中心据えて、機能単位の短期開発と価値検証を繰り返す △ 要求が明確で仮説検証を必要としないため、スプリント単位での価値検証が適さない。 スパイラル開発 設計・実装・テストを反復し、段階的にシステムを完成させる ◯ 既存検索の同等挙動を機能単位で再現し、品質を積み上げる進め方として適している。 社内の別の大規模案件でスパイラル開発を採用していたことも、選択を後押ししました。 最終的には予定していたすべての仕様をリプレイスできました。ただし、この仕組みを導入した当初は別の意図もありました。スケジュールの制約で対応の難しい仕様が生じた場合に、安全に後ろ倒しやドロップができる状態を作ることです。 4. 大規模リプレイスをスパイラル開発で回すポイント 本章では、スパイラル開発を円滑に進めるために私たちが実践した以下4つのポイントについて紹介します。 2週間ごとに「動くもの」を作る mainブランチへの積極的なマージ 機能フラグを使って安全に本番リリース 開発初期からアプリ上で動かせる環境整備 4.1. 2週間ごとに「動くもの」をつくる 前述の通り、スパイラル開発の核心は短いサイクルで「 実際に動く成果物 」を完成させ、チーム全体で動作確認を繰り返すことです。 実際に動く成果物=検索機能 として、私たちはプロジェクト全体で実装しなければならない機能を複数のスプリントに分割し、各スプリントで以下のように検索機能を順番に実装していきました。 スプリント1: ショップ検索 スプリント2: カテゴリ検索 スプリント3: 価格検索 ... 各スプリントでは、対象機能の設計から実装、テストまでを2週間で完了させることを目標としました。ここで重要なのは、「コードを書き終わる」ことではなく、「アプリ上で実際に動作する状態にする」ことをゴールに設定した点です。 この進め方によって、以下のメリットを得られました。 全レイヤーに関わることで属人化を防止 ひとつの機能を完成させるには、APIのプレゼンテーション層やビジネス層、データアクセス層など全レイヤーに手を入れる必要があります。また、スプリントごとに担当機能を入れ替えながら共同開発を進めることで、特定のメンバーだけが詳しい領域を作らず、チーム全体で知識を共有できました。 成果物が明確になり、進捗が見える化される 各スプリントの終わりには「使える検索機能がひとつ増える」という目に見える成果が生まれます。これにより、スケジュールに対して順調なのか遅れているのかを早い段階で把握でき、必要に応じて計画を調整することも可能になりました。 4.2. mainブランチへの積極的なマージ スパイラル開発を円滑に進めるうえで、ブランチ戦略も重要なポイントでした。私たちは、開発中の機能であっても積極的に 本番環境で動作しているコード=mainブランチ へマージする方針を採用しました。 なぜ開発用ブランチを長期間維持しないのか ZOZOTOWNでは、今回のリプレイス以外にも日々様々な開発が並行して進められています。このような状況で、リプレイス用の統合ブランチを長期間維持し続けると、mainブランチとの差分が大きくなることは明確でした。実際、過去のWeb版検索リプレイスでも、終盤の統合作業で予想外の時間を取られた経験がありました。 スプリントごとにmainへマージする運用 今回のリプレイスでは、各スプリントで実装した機能が一定の品質に達したら、速やかにmainブランチへマージするルールを設けました。これにより、並行して進む他の開発との差分を最小限に抑え、常に最新のコードベースで作業できる状態を維持しました。なお、「一定の品質に達した」ことをどのように担保したかについては、次章のテストプロセスで詳しく紹介します。 振り返りで見えた成果 プロジェクトの振り返りでも、以下のようなポジティブな声が上がっており、開発効率に大きく貢献していることが分かりました。 「ベースブランチへのマージを定期的におこなったので、同期負荷がWeb版リプレイスに比べて格段に下がった」 「他チームとのコードに関する調整がほとんどいらなかった」 4.3. 機能フラグを使って安全に本番リリース 開発途中の機能をmainにマージすることには「本番環境に影響を与えないか」という懸念が伴います。mainブランチへの積極的なマージを可能にしたのが、機能フラグ(Feature Flag)の活用です。 機能フラグの基本的な考え方 機能フラグとは、コード上に実装された新機能の有効・無効を、設定値によって切り替えられる仕組みです。機能フラグがOFFの状態では、新しいコードが本番環境に存在していても、実際のユーザーリクエストには影響を与えません。 機能フラグOFFで本番リリース 私たちは、開発中の機能をすべて「フラグOFF」の状態でmainにマージし、本番環境へのデプロイまでしていました。これにより、「コードは本番に乗っているが、機能としてはまだ有効化されていない」という状態を実現しました。 なお、フラグOFFの状態で、ユーザーのリクエストによってリプレイスコードにアクセスされた場合は、あえて明示的にエラーを返す仕組みにしました。エラーが発生した場合はアラートが発報される監視体制は整備済みのため、開発者が気づかないうちに未完成なコードが利用されてしまう事態を防止しました。 4.4. 外部サービス連携部分のモック化 機能フラグにより安全にコードを本番環境へ配置できるようになりましたが、開発段階で「実際に動くもの」を作るためには、もう1つ重要な課題がありました。それが、外部サービスとの連携です。 リプレイス後の検索機能は、複数のマイクロサービスと、それらが提供する50以上のエンドポイントに依存しています。商品情報、ショップ情報、お気に入り情報など、検索結果の表示に欠かせないデータは、すべてこれらのサービスから取得しています。 しかし、開発初期からすべての外部サービスに接続して開発を進めるのは、コストが高く現実的ではありません。 そこで、実装がまだ完了していない部分の連携にはモックを用意し、まずはAPI全体として「ひと通り動く」状態を早期に作りました。 この仕組みにより、スプリントごとに新しく実装した機能を、実際の端末からすばやく動作確認できるようになりました。これらを活用したテストプロセスについては、次章で詳しく説明します。 5. スパイラル開発を支えた「テストプロセス」 本章では、検索リプレイスを成功へ導いた要因の1つである「テストプロセス」について解説します。スパイラル開発において、複雑な検索機能を段階的に実装しながらも品質を維持できた要因、開発リズムにテストを組み込むための具体的な仕組み、さらにそれによって得られた成果と学びを、実践的な視点で紹介します。 5.1. テストを開発のサイクルに組み込む スパイラル開発では、短いサイクルの中でリリースを繰り返しながら品質を高めていくことが求められます。各スプリントで異なる関心領域を扱うなかでも、既に実装済みの部分も含めた品質を維持し続ける必要があります。 3章で紹介した過去のリプレイスプロジェクトのような「開発の後工程としてテストを行う」進め方では、十分な品質とスピードを両立することは困難でした。そこで私たちは、プロジェクト初期からテストを開発プロセスの中心に位置づけ、開発・検証・改善を同じリズムで進める体制を整えました。 5.2. スパイラル開発のために設計したテストプロセス全体像 今回のリプレイスは複雑な検索ロジックを扱う一方で、スプリントごとに実装する機能を区切りながら実装を進めます。そのため、部分ごとの開発を進めつつも、全体の品質を破綻させない状態をどのように作るかが重要なポイントとなりました。 これら背景を踏まえ、私たちは"いつ・何を・どの粒度で検証するか"を明確にした段階的テストプロセスを設計し、開発の初期段階から導入しました。 以下の表は、実際に運用していたテストプロセスをフェーズ別にまとめたものです。 テストグループ 実施タイミング テスト内容 目的 開発中テスト 開発中に随時・マージ前CI ユニットテスト コードレベルの正しさを即時に確認。境界値・異常系までカバーし、仕様の確認 開発中に随時 アプリE2E動作確認 実機/エミュレータを使い「プロダクトとして提供する形」で動作確認し、UI/UXレベルの齟齬を確認。 スプリントごとのテスト 2週間に1回 アプリE2E結合テスト スプリントで実装した範囲の結合テストを実施。 2週間に1回 新旧APIレスポンス比較リグレッションテスト 上記結合テストのケースについて、自家製自動APIテストツールを用いてリプレイス前後で比較し、守るべき仕様が維持されているか確認 2週間に1回 レビュー会(モンキーテスト) 成果物をチーム全員で俯瞰し、ユーザー視点で動作の違和感や齟齬を調整する リリース前テスト 全機能の開発完了後 受入結合テスト 開発チームによる、最終コードでの実機テスト。「実装したことがすべてできているか」を確認 全機能の開発完了後 QAリグレッションテスト 第三者QAチームによる、ユーザーストーリーに基づいた網羅的な品質確認 全機能の開発完了後 負荷試験 高負荷を想定した本番耐性の確認 5.3. テストプロセスのポイント プロセスの中でのポイントは、以下の3つのプロセスです。 開発中のE2E動作確認 新旧APIレスポンス比較リグレッションテスト レビュー会(モンキーテスト) 以下では、それぞれの役割と効果を説明します。 5.3.1. E2E動作確認:実装直後から「プロダクトとしての挙動」を確かめる 開発フェーズでは、開発者が実装したコードをエミュレータまたは実機から直接操作し、ZOZOTOWNアプリ上での実際のユーザー体験としてどう動作するかを随時確認できるような環境を整備しました。 検証端末の全メンバーへの配布 Android端末とiPhoneを開発メンバー全員に配布し、誰でもすぐに実機で動作確認できる体制を整えました。 ローカル実行のAPIと実機アプリの接続環境の整備 開発者がローカルで起動したAPIに対して、ZOZOTOWNアプリ(インハウス版)から直接リクエストを送れるようにしました。これにより、開発しながらアプリ上で挙動を気軽に確認できるようになりました。 このプロジェクトの実装レイヤーはバックエンドの開発ですが、単なる「APIが正しく返るか」を見るテストにとどめません。画面遷移・検索条件の適用・表示ロジックなど、“実際のユーザーが触れる状態”の挙動を初期段階から検証することが目的でした。 5.3.2 レスポンス比較リグレッションテスト:守るべき仕様の破壊を防ぐ“自動チェック” 今回のリプレイスでは、「旧サービスと同じ結果が返ること」が絶対条件でした。そのため、旧APIと新APIのレスポンス差分を自動比較できる自家製ツールを作成しました。スプリントごとに蓄積してきたすべてのテストケースを用いてリグレッションテストを実施しました。 特に効果が大きかったのは、 「スプリントごとに異なる機能を開発しても、全体として同じ振る舞いであることを機械的に保証できた」 ことです。 このプロセスを継続的に運用した結果、最終的にはテストケースが約1,000件まで蓄積され、それらを高頻度で実行できる状態を構築しました。その過程では、開発中に70件以上のリグレッションを検出しており、この自動チェックの有効性を十分に示す結果となりました。 5.3.3. レビュー会:ユーザー目線での齟齬をスプリント単位で解消する 2週間に1度のレビュー会では、開発チームに加え、チーム外の関係者もアプリでの挙動を実際に確認し、 成果物をユーザーの視点で体験する ことを重視しました。 レビュー会の目的は、単なる「動作チェック」ではなく、認知のズレをなくすことにありました。 デモを通じてチームの認識をそろえる 「実装者の意図」と「ユーザーが体験する挙動」が一致しているか 想定していない分岐・遷移がないか また、副次的な効果として「徐々に完成していく様子を共有できるためモチベーションが維持できる」「Tipsが共有されチームとしての開発効率が良い」効果もありました。振り返ってみると、この点こそがプロジェクト全体に大きな効果をもたらしていたと感じます。 5.4. 成果と学び 最終QA段階で不具合ゼロ 検索リプレイスでは、リリース前の最終QAで一件の不具合も発生せず、品質面で非常に安定した状態を保てました。 スケジュール遅延ゼロ リリース開始までの工程はすべて計画通りに進行し、想定していた作業を予定どおり完了できました。 リリース後のリグレッションもゼロ リリース後に細かなバグが見つかりましたが、その修正においてもリグレッションは一件もなく、整備したテストプロセスの再現性と堅牢性が確認できました。 この成果を通じて、私たちはテストを後追いで行うのではなく、開発リズムに組み込むことが品質とスピードの両立に直結することを実感しました。 テストは一部の担当者だけが実施する仕組みではなく、チーム全員が関わるプロセスとして設計することで、実際のユーザーへの影響を意識した実装や、認識齟齬・不具合を早い段階で修正できる文化が根づきました。 6. デプロイ基盤とリリース戦略 この章では、スパイラル開発を支えたインフラ構築と安全なリリースの仕組みについて紹介します。 ZOZOTOWNの検索機能は、1日あたり数百万回も利用される重要なサービスです。そのため、少しのダウンタイムも許されず、デプロイの安全性と効率性の両立が不可欠でした。SREは、それらの要件に基づいたデプロイ基盤を構築し、スパイラル開発における頻繁なリリースサイクルを支えました。 6.1. 安全かつ効率的なデプロイの仕組み ZOZOTOWNのプラットフォーム環境はEKSで稼働しており、FluxやFlaggerといった安全かつ効率的なデプロイを実現するための仕組みが導入されています。以下は弊社が KubeCon + CloudNativeCon Japan 2025 にスポンサーブースを出展した際に展示したアーキテクチャ図です。 引用:KubeCon + CloudNativeCon Japan 2025参加&協賛レポート 今回のリプレイスAPIのデプロイ基盤は、このプラットフォーム上にあるFluxとGitHub Actionsを組み合わせて 開発者がPRのマージをするだけ で完結するように設計されています。 詳細を以下に記載します。 ECRへのイメージプッシュで自動的にPR作成 開発者がリプレイスAPIリポジトリでDockerイメージをECRへプッシュすると、FluxのImageUpdateAutomationが新しいイメージを検知します。検知後、Kubernetesマニフェストを管理するインフラリポジトリへイメージタグを更新するcommitを自動的にpushします。その後、リリースブランチへ自動でPRを作成し、Slackで開発者へ通知します。 安全性を担保する自動承認 作成されたPRは、内容に応じて自動承認されます。自動化によるコミットのみの場合は、イメージ更新と判断しGitHub Actionsで自動承認されます。自動化によるcommit以外を含む場合はイメージ更新以外の変更を含むため、SREがPRをレビューします。 PRマージと自動デプロイの実行 2で自動的に承認されたPRは、開発者によってPRをマージします。マージ後はEKSクラスタ上のFluxが自動的に変更を検知し、変更内容をリプレイスAPIのDeploymentに反映します。反映後はFlaggerによるプログレッシブデリバリーが開始され、エラーが発生した場合はロールバックする仕組みもあるので、安全にリリースすることが出来ます。 本番環境は別途SREによる承認 開発環境(dev)やステージング環境(stg)は、マージと同時に自動デプロイされます。一方、本番環境(prd)にはRelease Gateという仕組みを導入しています。 techblog.zozo.com これはイメージ変更PRをマージ後に、SREによる承認を得なければprdへデプロイできない仕組みです。この承認プロセスにより、prd環境への影響はSREが確認しつつも、dev/stg環境では開発者自身で迅速にデプロイができるようになっています。 6.2. 非機能面の品質確保 今回の検索リプレイスはスパイラル開発による段階的な開発・リリースを進めてきましたが、最終的にはユーザーのトラフィックをリプレイスAPIにルーティングする必要があります。 特に、ZOZOTOWNの検索機能は日常的に高トラフィックを処理しておりビジネスの面でも重要な機能であるため、ユーザー影響を抑えるために慎重な対応が求められました。 そのため、本番リリース前にSREが中心になって以下のような非機能面の品質検証および運用体制の強化を実施しました。 負荷試験・障害試験を実施 SRE部のテックリードによるリリース前レビュー(プロダクションレディチェック) 開発者とSREとのリリース前の監視指標の認識合わせ 6.2.1. 負荷試験・障害試験を実施 ここではリプレイス前のセール時の最大リクエスト数の負荷を掛け、レイテンシや各種メトリクス指標で大きな性能悪化が出ないかを確認します。また事前に本番同等の負荷を掛けることで、本番環境へ出す前にリプレイスAPIのインフラ増強をしておくべきかを確認できます。 障害試験では、各マイクロサービスの呼び出し時に500系エラーやレスポンス遅延を意図的に発生させ、想定通りの挙動となるかを確認します。500や遅延はIstioのFault Injectionを使って実現しています。 これにより事前に呼び出す際のマイクロサービスで障害が発生しても意図通りな挙動であるかを確認します。 6.2.2. SRE部のテックリードによるリリース前レビュー(プロダクションレディチェック) 本番リリース前には、SRE部が独自に整備している「プロダクションレディマイクロサービスチェックリスト」を用いて、SRE部のテックリードによるレビューを実施しています。このチェックリストは、セキュリティやCI/CD、障害・負荷試験、監視、ドキュメント整備など、インフラ運用に必要な観点を網羅したものです。 チェックリストは形式的なものでなく、実際の運用・障害対応まで見据えた具体的な項目が並びます。「graceful shutdownの実装」「カナリアリリースの仕組み」「DatadogやSentryによる監視・アラート設計」「オンコール体制の整備」などです。 最終的に、SRE部のテックリードがこれらの項目をレビューし、「本番リリースOK」と判断できた段階でリリースを進めます。このプロセスを通じて、リリース後のトラブルや運用負荷を大幅に低減できるだけでなく、開発チームとの連携強化やナレッジの共有にもつながっています。 6.2.3. リリース前に開発者とSREで監視指標の認識合わせ これまでバグを出さないためのテストを行ってきましたが、検索リプレイスのような大規模な変更では、予期しない問題が多様な形で現れる可能性があります。 そのため開発チームとSREチームで、事前にリリース時の監視体制と切り戻し方針について議論し合意しました。また、数値基準を厳密に決めすぎると、「この数値は基準を超えているが、本当に切り戻すべきか?」という議論がリリース当日に発生し、判断が遅れる可能性もあります。 そこで私たちが合意したのは、具体的な数値ではなく、「判断の枠組み」でした。 【合意した切り戻しの方針】 断続的・継続的なエラー発生・単発のエラーではなく継続する場合 リプレイス前後で比較して明らかに体感できるレベルのレイテンシ悪化 ビジネス影響の兆候 検索実行数の不自然な減少 カスタマーサポートへの問い合わせ増加 売上影響の可能性 社内で策定したSLOに違反 重要なのは、「何かあったらひとまずレガシーAPIにリクエストを戻す」というシンプルで明快な方針を共有したことです。後述の6.3で説明するFlaggerのManual Gatingの利点は「いつでも安全に戻せる」ことです。この利点を活かし、迷ったら切り戻して安全確保する判断を優先できるようにしました。 【監視体制と判断プロセス】 合意した切り戻し方針を元に、N%リリース期間中は開発チームとSREチームが「同じ監視指標の画面を見て、同じ認識で判断できる状態」も整えました。 例えば、「断続的・継続的なエラー発生やレイテンシ悪化の確認」はDatadog、「リプレイス前後の比較」はレガシーAPIからログを送信しているSplunkの画面を見るといった認識を揃えました。 これらを常時モニタリングし、異常の兆候が見られた段階で開発チームとSREチームが即座に協議できる体制を整えました。 6.3. リプレイスAPIへの安全な切り替え 6.3.1. 段階的なトラフィック切り替え 本番リリースではユーザー影響を最小限に抑え、かつ長い期間をかけて段階的にリプレイスAPIへ切り替えることが求められました。ZOZOTOWNではFlaggerによる自動カナリアリリースを採用していますが、自動でリクエスト比率が上がってしまうため長い期間をかけたリリースには向いていませんでした。 そこで今回は、FlaggerのManual Gating機能を活用した段階的にリプレイスAPIへ切り替えました。 まず、機能フラグをONにしてリプレイスAPIを本番環境にデプロイした後、リクエストの99%をレガシーAPI、1%をリプレイスAPIにルーティングする設定で運用を開始します。この間、開発チームとSREチームが事前に合意した監視指標(レイテンシ、エラー率、ビジネス影響など)をもとに、リアルタイムで状況を確認しました。数日間問題がなければ、段階的にリプレイスAPIの比率を引き上げ、最終的に100%切り替えを完了します。この比率は以下の基準で設定しました。 このルーティング比重操作の詳細な技術は、以下のテックブログもご参照ください。 techblog.zozo.com 6.3.2. 即座に切り戻せる安心感 この仕組みの最大のメリットは、問題発生時にすぐ切り戻せることです。 もしバグやパフォーマンス劣化などの異常が検知された場合は、スクリプトを実行してトラフィックをすぐさまレガシーAPIへ戻せるため、ユーザー影響を最小限に抑えられます。その後、開発者が修正をデプロイし、再度Manual Gatingで段階的リリースを再開する、というサイクルを安全に回すことができました。 この「即座に切り戻せる安心感」があったからこそ、私たちは本番環境で大胆にリリースを進められました。実際のリリースでも、細かな挙動の違いを検知した際はすぐ切り戻し、修正後に再度段階的にリリースすることで、安全でスピーディな運用を実現できました。 7. おわりに 本記事では、リプレイスの過程でスパイラル開発をどのように取り入れ、実際のプロジェクト運営の中で活かしたのかを紹介しました。 小さく動かしながら品質を積み上げる仕組みづくり、早い段階で実環境に近い検証をする文化、そして安全にリリースできる基盤が、今回のリプレイス成功を支えた大きな要因でした。本記事が、同じようにレガシーシステムのリプレイスや段階的移行に取り組む方の参考になれば幸いです。 ちなみに 今回のリプレイスでは副次的な効果として検索速度が約2倍に向上しました。 ぜひZOZOTOWNアプリで検索してみてください!以前よりサクッと動く感覚を、体感していただけると思います。 紹介 このプロジェクトの完了により、Web・アプリ双方の検索基盤リプレイスがひと区切りとなり、検索UI/UXの改善をよりスピード感をもって進められる環境が整いました。今後は、この新しい基盤を活かして検索体験そのものをさらに良くしていくフェーズに入ります。 ZOZOでは、一緒にサービスをつくり上げてくれるエンジニアを募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
img[src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20251222/20251222150607.jpg"] { height: 600px; width: auto; } img[src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20251230/20251230110649.png"], img[src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20251230/20251230110658.png"], img[src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20251230/20251230110708.png"], img[src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/vasilyjp/20251230/20251230110716.png"] { border: 0.5px solid; } はじめに こんにちは、SRE部 検索基盤SREブロックの 花房 です。2025年12月8日〜10日の3日間、東京の虎ノ門ヒルズで開催された「 Open Source Summit Japan 2025 」に参加しました。 オープンソースの技術とコミュニティ をテーマとする本カンファレンスでは、AI・Cloud Native・Observability・Automotive Linuxといったトレンド技術や、コミュニティ運営に関連するリーダーシップ・組織論など、多岐にわたる領域の発表がありました。また、カンファレンスの中ではLinuxとGitの生みの親であるLinus Torvalds氏のインタビュー形式の基調講演があり、それを間近で聴けたことはエンジニアとして忘れられない貴重な体験になりました。 本記事では、カンファレンスの参加レポートを会場の様子や印象に残ったセッションを交えてお届けします。 目次 はじめに 目次 Open Source Summit Japanとは 特徴 会場の様子 セッションレポート Securing the Invisible Hands: Policy and Guardrails for AI-Driven Infrastructure Scaling GitHub Actions To the CNCF You Never Know When You Need a Fork: Lessons Learned Building Valkey Panel: OSPOjp: Ways To Level up OSPO Stages With Knowledge and Strategies From Local Meetup Japan おわりに Open Source Summit Japanとは Open Source Summit Japan は、オープンソースをテーマとする国際カンファレンスです。オープンソースに関わるメンテナ・コントリビュータ・開発者・運用担当者が参加し、最新の技術動向やベストプラクティス、実践的なソリューションをお互いに共有します。また、技術面だけでなく、オープンソース技術を社会やビジネスにどのように役立てていくかも議論されます。そのため、コミュニティリーダーやビジネスリーダーにより、オープンソース活用における成功事例や推進方法も共有されます。 Open Source Summit Japan 2025のキービジュアル 本カンファレンスはLinux Foundationが主催しており、日本だけでなく海外でも開催されています。スポンサーにはオープンソースを活用する企業が名を連ねており、今年は以下の企業がスポンサーとして参加していました。 スポンサー 以上のように、役割を超えて多くの参加者・スポンサーが集まり、オープンソースに関する知見共有やネットワーキングが行われる場となっています。 特徴 本カンファレンスの特徴は セッショントラックの多さ です。 オープンソースに関連する分野や技術領域は多岐にわたるため、今回は以下の8トラックに細分化してセッションが開催されました。 Automotive Linux Summit Cloud & Containers Linux Open Source Leadership Operations Management OSPOCon Safety-Critical Software Zephyr さらに、本カンファレンスに留まらず、それぞれの技術領域に特化した以下の関連イベントも同時開催されました。 Cloud Native Community Japan OpenSearchCon Japan 2025 Rust Global: Tokyo Zephyr Project Meetup OpenSearchCon Japan 2025 の参加レポートは以下で公開しています。 techblog.zozo.com 来年も東京での開催が予定 されていますが、上記のように扱われるトピックの範囲が非常に広いため、参加する場合は事前に興味のあるトピックやセッションを絞り込んでおくことをおすすめします。当日、どのセッションに参加するかを決めるのは大変だと感じました。 会場の様子 本カンファレンスは、2025年12月8日〜10日の3日間、東京の虎ノ門ヒルズフォーラムで開催されました。 基調講演を含む各セッションは、5階のメインホール、5階の2部屋、4階の4部屋の計7部屋で行われました。各部屋ではリアルタイム翻訳を利用でき、英語が苦手な方でも参加しやすい環境が整っていたと思います。 メインホール 受付とSolutions Showcaseエリアは5階にありました。Solutions Showcaseエリアでは休憩時やランチタイムに食事が配布され、時間帯によっては非常に混雑するため注意が必要です。 受付 Solutions Showcaseエリア セッションレポート 特に印象に残ったセッションを紹介します。 Securing the Invisible Hands: Policy and Guardrails for AI-Driven Infrastructure 本セッションでは、人間の介入なしでAIエージェントにインフラを管理させる場合のセキュリティ確保の方法について、具体的なアーキテクチャとオープンソースツールの紹介を交えて解説されました。 AIエージェントは、プロビジョニングからスケーリングに至るまでインフラの管理も担えるようになってきています。しかし、現状の課題として人間による変更のレビューが必須になってしまい、AIエージェントに全てを任せることはできません。AIは想定外の挙動をしたり、人間と同様に設定ミスを作り出したりしてしまうことがあるためです。また、攻撃者によりAIエージェントが操作され、脆弱性を埋め込むなど悪意のある行動を取らされる可能性もあります。 AIエージェントは有用ですが人間が常に動作をチェックする必要があるため、そこがボトルネックになってしまいます。また、人間の介入がないと不正な設定や悪意のある行動の影響が瞬時に拡散し、抑えられなくなってしまいます。さらに、AIの変更スピードにリアルタイムで対応するようなモニターやポリシーがないために、異変に気付くことが遅れる、もしくは気付けないという問題も発生します。 上記の課題に対する解決策は以下の2つです。 セキュリティポリシーの宣言的定義と適用 リアルタイム監視とワークロードの隔離 1つ目の解決策では、ポリシーをコードとして管理し、専用ツールによって常にインフラで有効な状態にしておきます。ツールは、Kubernetesリソースに対してはKyvernoやOPA、クラウドリソースに対してはCrossplaneが存在します。以下の図のように役割が分かれます。 公開資料 「Securing the Invisible Hands: Policy and Guardrails for AI-Driven Infrastructure」 P.5より引用 Kyvernoでは、新規リソースの内容がコード管理されたポリシーに従っているかを判定し、不正なリソースが作成されることを未然に防ぎます。Crossplaneでは作成後のリソースのポリシー違反を検知した場合、ポリシーに従うように自動修正する機能が存在します。これらの機能により、AIにより生成された不正な設定がそのまま適用されることを自動的に防ぎます。 2つ目の解決策では、ワークロードの不審なアクションをリアルタイムで検知し、即座にネットワーク的に隔離することで被害の拡散を防ぎます。例えば、Falcoがワークロードの不審なアクションを検知した際、IstioやNetwork Policiesを利用してワークロード自体や侵害されたサービスを即座に隔離します。これにより、悪意のある行動やAIにより生成された脆弱性の拡散を自動的かつ迅速に防ぎます。 本セッションで紹介されたアーキテクチャとツールは、AIエージェントが自律的かつ安全にインフラを管理できる環境を実現していました。以下が全体像を図にしたものです。 公開資料 「Securing the Invisible Hands: Policy and Guardrails for AI-Driven Infrastructure」 P.20より引用 Crossplane, Kyverno, Falco, Istioといったツールはオープンソースという点ですぐに導入できます。そのため、AIネイティブで安全なインフラを実現するためのハードルは高くないと感じました。また、本セッションの内容は、AIエージェントだけでなく人間によるインフラ変更にも有効だと考えます。AIほどのスピードは出せませんが、不正な設定や悪意のある行動は人間にも起こり得るためです。 本セッションで紹介されたアーキテクチャは、AI時代のインフラ管理の最先端であり、今後のスタンダードになっていくであろうと感じました。 Scaling GitHub Actions To the CNCF 本セッションでは、CNCF (Cloud Native Computing Foundation)におけるGitHub Actions Self-hosted Runnerの導入事例について紹介されました。 CNCFに所属するオープンソースプロジェクトは230を超えており、そのほとんどがソースコード管理とCIのプラットフォームとしてGitHubを利用しています。CIワークロードに関しては、CNCFのGitHub Enterpriseアカウントに集約しており、共通のインフラを利用しているような状態でした。そのような中で、GitHubから無料提供されていた「Large Runner」が2025年6月から有償化されるというアナウンスが、1年前である2024年6月にありました。有償化されるとCIの費用の全てがCNCFのアカウントにかかります。そこで、CI用のインフラをCNCFが所有しているオンプレ環境であるEquinix Metalに移行する話もあったようですが、Equinix Metalはサービス終了予定になったため行き場がなくなってしまいました。そのため、CNCFではCIを支えるインフラの新規構築が急務となりました。 CNCFは検討の結果、 Kubernetes上でGitHub Actions Self-hosted Runnerを運用する方法を採用 しました。ツールとしては、 Action Runner Controller (ARC) を利用しています。ARCはオープンソースのKubernetes Operatorで、その名の通りGitHub ActionsのSelf-hosted Runnerを運用管理してくれます。さらに、 Argo CDを用いたGitOps によりARCやKubernetesクラスタを管理しています。これにより、オープンソースプロジェクトのメンテナー自身が各々のSelf-hosted Runner環境をアップデートできる仕組みが整備されました。 ARCの導入後、新たな問題が発生しました。ARCはコンテナ環境でCIワークロードを実行しますが、オープンソースプロジェクトによってはコンテナでは動作しない、マシンレベルの仮想化や低レイヤーのネットワーク設定が必要なワークロードも存在しました。既存のARCはコンテナ環境を前提としていたため、CNCFでは VM環境でCIワークロードを実行できるような仕組み を追加しました。さらに、 CIワークロードで利用するための独自のVMイメージを構築するツール も開発しています。 最終的に以下の図のような構成になりました。 公開資料 「Building CNCF-Grade GitHub Action Runners」 P.21より引用 CNCFでは、ARCや独自のCIワークロード用VMを活用してGitHub Actions Self-hosted Runnerを運用しています。ARCとArgo CDを組み合わせることでスケール性とメンテナンス性を両立し、各プロジェクトのメンテナーも使いやすいプラットフォームを構築していることが参考にすべき姿だと感じました。また、独自のVMイメージを構築するツールはオープンソースで公開されているため、ぜひ試してみたいと思いました。このようにオープンソースで公開しているとユーザに気軽に試してもらえるため、自分で作成したツールも、たとえニッチなものであってもオープンソースとして公開していきたいと思いました。 You Never Know When You Need a Fork: Lessons Learned Building Valkey 本セッションでは、Redisをフォークして「Valkey」を立ち上げた際に実施した内容について紹介されました。 Redisのライセンス変更に伴いValkeyが誕生した当時、Redisの運営体制は不透明でクローズドでした。デプロイは独自の環境かつRedis Inc.だけが実施可能であり、ベンチマークテストも同様のクローズドな独自システムで行われていました。会議や意思決定もクローズドな場で行われ、コミュニティの活動は活発になりにくい状況でした。 そこで、Valkeyはフォーク前までのコミュニティの形は残し、全ての開発プロセスをオープンにするという運営体制を築きました。さらに、3つの工夫によりオープンであることの維持と継続的なリリースを実現しました。 ブランチとタグの保護ルールの有効化 : リリースの際には必ずPRをオープンして変更を誰かにチェックされるという文化を作り出す ドキュメントのバージョン管理と更新の自動化 : リリースやコントリビューションの方法、セキュリティに関する情報が常に最新の状態で公開される Valkeyのパッケージ提供先とビルドスクリプトのメンテナンス : Valkeyが各パッケージとして問題なく再配布されるようにすることで、Valkeyに触れる人が増え、コミュニティの発展に繋がる Valkeyのメンテナがコミュニティを非常に大事にしていることから、大規模なオープンソースはコミュニティなしで成り立たないことを改めて実感しました。また、コミュニティに還元するにはリリースを継続的に続ける必要があり、そのためにはコミュニティをオープンにして誰もがプロジェクトに参加しやすく、活発にし続けることが重要だと感じました。 Panel: OSPOjp: Ways To Level up OSPO Stages With Knowledge and Strategies From Local Meetup Japan 本セッションは、日本におけるOSPO (Open Source Program Office) 活動のナレッジと戦略を共有することを目的として開催されました。 企業でOSS貢献に取り組む際、推奨されているため貢献を始めるものの、他の取り組みよりも優先度を下げてしまい続かないことは多々あると思います。OSS活動の課題として、組織のOSS戦略が欠如していると、メンバーが貢献の価値を証明できずOSS活動を縮小してしまう「OSSの落とし穴ループ」に陥ることがあります。 公開資料 「Ways To Level up OSPO Knowledge and Strategies From Local Meetup Japan」 P.5より引用 解決策の1つは、組織内にOSPOを設置し、成熟度モデルに基づいて組織の意識を「利用のリスク管理」から「貢献の価値創出」へとシフトさせることです。OSPOとは、組織のオープンソース運用の中核を担う専門部署です。具体的な活動としては、オープンソース活用の戦略やポリシーを作成し適用すること、オープンソースに関するトレーニング、外部のオープンソースコミュニティとの関係構築などが挙げられます。 以下の図では OSSおよびOSSコミュニティ活動のメリット を成熟度ごとに示しています。成熟度が上がると、そのメリットが 組織内 に留まらず エコシステム へとより範囲が広くなります。 公開資料 「Ways To Level up OSPO Knowledge and Strategies From Local Meetup Japan」 P.15より引用 以下の図では OSSに対するマインドセット を成熟度ごとに示しています。成熟度が上がると、組織のマインドセットは OSSのリスク から 価値創出 へと変化します。 公開資料 「Ways To Level up OSPO Knowledge and Strategies From Local Meetup Japan」 P.16より引用 以下の図では 組織内でのOSPOの役割 を成熟度ごとに示しています。成熟度が上がると、 ルール整備や教育 から 組織内外の関係構築や戦略的な意思決定のサポート へと変化します。 公開資料 「Ways To Level up OSPO Knowledge and Strategies From Local Meetup Japan」 P.17より引用 OSPOの在り方は組織によって様々で正解はありませんが、以上が既存のナレッジから構築された成熟度モデルでした。「OSSの落とし穴ループ」を脱するために参考となるモデルです。 本セッションを通して、OSPOとは何か、その役割と価値について知ることができました。パネルディスカッション形式で、日本でOSPOを実践している方々の話を伺うこともでき、非常に貴重で学びになる時間でした。今後、自身のオープンソース活動を展開していく上で本セッションの内容を活かしていきたいと思います。 おわりに 本記事では、「Open Source Summit Japan 2025」の参加レポートをお届けしました。 3日間、オープンソースの世界に没頭し、刺激を受け続けた貴重な機会になりました。技術の最前線に触れられるだけでなく、コミュニティ運営やビジネスでの活用方法など多角的な知見を得ることができ、非常に有意義だったと感じています。また、長期的に「どのようにオープンソースと関わっていくか」を考えるきっかけにもなりました。今後は、オープンソースの戦略的な活用に加え、コミュニティへの還元・貢献の頻度を増やしていきたいと考えています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからご応募ください。 corp.zozo.com
はじめに こんにちは、ビジネス・アナリティクス部マーケティング・サイエンスブロックの 茅原 です。普段はマーケティング施策の効果検証を担当しています。マーケティング・サイエンスブロックではAI協働型分析フロー構築の取り組みをしています。本記事では本取り組みの詳細や、この中で得られた知見をご紹介します。 目次 はじめに 目次 背景・課題 取り組みの紹介 分析環境の標準化 AI活用フロー検証のためのGitHubリポジトリ構築 分析設計書の活用 分析作業フローの定型化 再利用性を高める工夫 文化の醸成 まとめ 背景・課題 近年、生成AIの発達が目覚ましく、開発をはじめとするエンジニアリング組織ではその活用事例が多く見られるようになってきました。一方でデータ分析組織においては、エンジニアリング組織ほど生成AIとの協業事例が見られないという所感があります。これは分析組織にとって、 生成AIの持つ不確実性の高さが致命的である ため、と考えています。例えば、生成AIは「ただ数値を転記する」といった簡単なタスクにおいてもハルシネーションを起こし、誤ったアウトプットを生成してしまうことがあります。データ分析においては、アウトプットの正確性が非常に重要であるため、こうした不確実性の高さは大きな障壁となっています。 本記事ではその不確実性を低減したAI協働型分析フロー構築の取り組みについてご紹介します。またこうした取り組みは、 生成AIに限定せず、人間も含めた組織全体のアウトプット品質向上にも寄与する ものと考えています。以下は現在検証中の内容ですが、同様の課題を抱える方の参考になれば幸いです。 取り組みの紹介 分析環境の標準化 まず前準備として、分析環境の標準化を行いました。前提として、弊チームでは環境としてはローカル/クラウド、IDEとしてはVS Code/JupyterLabなど、メンバーによって作業環境が統一されていない状態でした。こういった多様な環境によって各環境に合わせた設定が必要となり、AI活用ナレッジの転用が難しくなってしまう、という状況となっていました。そのため、Google CloudのCompute Engine (GCE)インスタンスへVS CodeからリモートSSH接続する形式に統一しました。これによりAI活用ナレッジの横展開が可能になりました。 AI活用フロー検証のためのGitHubリポジトリ構築 こちらも前準備として分析環境の標準化に加え、AI活用フロー検証のためのGitHubリポジトリ構築も行いました。具体的には、20人規模のチームで使用していたGitHubリポジトリを、4〜5人規模のチーム単位に分割しました。 旧来のリポジトリでは、リポジトリ全体へ影響を及ぼす設定変更に他チームとの合意が必要であり、検証/改善をスムーズに進められない状況になってしまっていました。チーム単位で分割することで、各チームは独自に設定を変更できるようになりAI活用ナレッジの試行錯誤がしやすくなりました。AI協働型分析フローを検証する段階においては、GitHubリポジトリの共有範囲は「ルールやフローを共有する範囲」で区切ることが検証スピードの観点から望ましいと考えています。 以下は実際のリポジトリのディレクトリ構成例です。 aia-ms/ ├── projects/ # 依頼部門ごとに案件を管理するディレクトリ │ ├── marketing_div/ # マーケティング部依頼案件ディレクトリ │ │ └── project_001/ # 案件ディレクトリ │ │ ├── overview.md # 分析設計書ファイル │ │ ├── data/ # CSV等のデータファイルディレクトリ │ │ ├── sql/ # SQLファイルディレクトリ │ │ ├── scripts/ # Python, Rの分析コードファイルディレクトリ │ │ ├── notebooks/ # Jupyter Notebookのノートブックファイルディレクトリ │ │ ├── reports/ # Markdown等のレポート関連ファイルディレクトリ │ │ └── etc │ └── etc ├── labs/ # メンバーごとに自由に使用するディレクトリ │ ├── kayahara/ │ └── etc ├── scripts/ # 再利用可能なスクリプトを管理するディレクトリ │ ├── bq_utils/ # BigQueryを操作するスクリプトをまとめたディレクトリ │ └── etc └── .github/ # GitHub関連設定ディレクトリ │ ├── workflows/ # GitHub Actionsワークフローを管理するディレクトリ │ ├── prompts/ # GitHub Copilot Chat用プロンプトファイルを管理するディレクトリ │ ├── agents/ # GitHub Copilot Custom Agents設定ファイルを管理するディレクトリ │ └── copilot-instructions.md # GitHub Copilot Chat設定ファイル 分析設計書の活用 以前から部内で使用していた分析設計書について、AI協働のためさらに活用するようになりました。前提として、弊チームでは「良い分析のためには、良い分析設計書が不可欠である」と考え、AI協働以前の2020年ごろから分析設計書を重んじるフローを構築していました。これはデータ分析が「1つの問題に対してアプローチが無数にある」タスクであり、「何を解くか」「なぜ解くか」「どう解くか」を関係者全員で合意形成しておくことが重要、という思想からです。AIとの協働においては、疑似的に「関係者」の一員としてAIが加わるため、AIに対しても「なぜこの分析をするのか」「何をもって成功とするのか」を明確に伝える必要があります。 分析設計書を活用することで、AIに対してもこれらの情報を正確に伝えることができ、アウトプットの不確実性を低減できました。具体的には、設計書の観点の見直し・充実化を行い、全案件で設計書を作成・レビューするような運用ルールを設定しました。 設計書には、主に以下の項目を記載しています。 依頼の概要 分析対象に関する基礎情報や依頼内容 依頼部門の理想/課題 依頼の背景として依頼部門が抱える理想や課題 分析対象の理想/課題 依頼部門が抱える理想や課題を踏まえた、分析対象に関する理想や課題 本案件で取り組む課題とゴール 分析対象の理想や課題に対して、本案件で取り組む具体的な課題と分析におけるゴール アウトプット予定項目 ゴールを達成するために必要なアウトプット項目 また、以前はGoogle Sheetsで管理していた分析設計書を、GitHubでMarkdown形式として管理する方式に変更しました。これにより、作業時にはAIへ設計書の内容を容易に参照させることができる上、設計書のレビュー時にもプルリクエスト上でGitHub Copilot Reviewを活用できるようになりました。結果として、設計書の品質向上にも寄与しました。 分析作業フローの定型化 AIのアウトプットの不確実性を下げるために、分析作業フローの定型化も行いました。AI協働型分析の良くない典型例として、「課題感を共有してAIに自由に分析させた結果、意図しないフローで進められ、期待したアウトプットが得られない」というものがあります。 具体的には、「Pythonファイルの中に長大なSQLを書いてしまう」「本来Pythonですべきような複雑な集計処理をSQLで行ってしまう」といったケースです。これを防ぐために、AI協働型分析での分析作業フローを下記のように定型化し、フローと各ステップでのアンチパターンをプロンプト内で明示的に伝えるようにしました。 社内Wikiから資料をMarkdown形式でダウンロードする 資料を基にBigQueryからSQLでデータを抽出する 抽出したデータをPythonで加工・モデル適用し、分析結果を得る 分析結果をMarkdown形式のレポートにまとめる 各ステップで再利用可能なPythonスクリプトを開発し、安定して各ステップの作業ができるようにしました。具体例としては、ステップ4では「CSVを自動でMarkdown形式の表に変換する」スクリプトを作成しました。非常にシンプルなタスクであり、AIにも可能そうに見えますが、実際にこのようなタスクを任せた際にハルシネーションが発生し「元のCSVと全く違った数値がレポートに転記される」といった事象が発生しました。そのため、このようにフローを定型化し、各ステップで再利用可能な仕組みを構築することで、AIのアウトプットの不確実性を低減しました。 再利用性を高める工夫 AI協働型分析の大きな強みの1つは、 「再利用性の高さ」 です。例えば、同じような分析タスクが発生した場合、過去の分析コードやプロンプトを再利用し、効率的に分析を進めることができます。これを最大限活用するため、LLMツールの機能を積極的に活用する方針を採りました。 具体的には、GitHub Copilotの以下の機能を活用しています。これらの設定ファイルはいずれもMarkdown形式で保存されています。リポジトリ内で共有可能なため、チーム内での横展開が容易です。 Prompt Files : あらかじめ定義したプロンプトをファイルとして保存し、再利用可能にする機能。これにより、分析フローの各ステップで使用するプロンプトを標準化し、チーム内で共有できるようになった。 Custom Agents : 複数のプロンプトやツールを組み合わせて、特定のタスクに特化したエージェントを作成できる機能。これにより、フローにおける各ステップを自動化するエージェントを構築し、効率的に分析を進められるようになった。 以下は分析設計書をレビューするプロンプトファイルの例です。以下のようなファイルを作成し所定のフォルダに保存することで、GitHub Copilot Chatからスラッシュコマンド形式ですぐ呼び出すことが可能となります。 --- agent: 'edit' description: 'Review the analysis design document for clarity, consistency, and completeness, then propose specific revisions and improvements to its structure, logic, and wording.' --- - 回答は **必ず** 日本語で記載してください - 添付された文章を以下の観点でレビューし、修正を提案してください # 全体 - 論理の流れがマクロからミクロへ分断なく接続できているか - 解釈が揺れる表現、不明確な表現を使用していないか ## 1. 施策/依頼の概要 - 分析対象について、十分な情報があるか - 例: 施策意図、対象者、訴求方法、施策内容、検証設計(RCT有無) ## 2. 事業部の理想/課題 - 事業部の長期的な理想/課題が理解できる記述か / 短期的であったりタスクベースであったりしないか ## 3. 施策の理想/課題 - 本施策が何を目指しているか、KPIベースで記載できているか ## 4. 本案件で取り組む課題とゴール ### 課題 - タスクリスト等ではなく課題の形式(~できていない 等)で記載できているか ### ゴール - タスクリスト等ではなくゴールの形式(~によって○○を××する 等)で記載できているか - 課題に紐づいたゴールとなっているか - どのようなビジネスアクション or 貢献に繋げたいか記載できているか ### 問 - タスクリスト等ではなく問の形式(~か?)で記載できているか - ゴールに紐づいた問となっているか ### 判断指標/基準 - 判断指標とその定量基準を記載できているか(暫定でも可) - 判断指標/基準がない場合、代替となるもの(目標ライン等)を記載できているか ## 5. アウトプット予定項目 - アウトプット項目形式として、名詞の箇条書きにできているか - 具体的に算出する指標を記載できているか (主要なもののみでOK) これらの作業単位で組み上げたプロンプト&エージェントと、各案件の分析設計書を組み合わせることで、効率的かつ安定的にAI協働型分析を進められる仕組みの構築を試みています。 文化の醸成 上記のような仕組みの整備に加え、文化の醸成にも取り組みました。日夜進歩するAIへの理解度やLLMツールの活用状況は、どうしても各メンバーの興味や意欲に依存してしまう部分があり、差が付きやすいという課題があるかと思います。この差を放置すると、AIに明るくないメンバーにとっては「気が付いたら複雑なフローを組まれていて着いていけない」といった状況になり、悪循環に陥りかねません。そこで、下記の2施策を実施しました。 1点目は事例共有会の開催です。実際の案件でのAI活用事例を共有し、「どんな良さがあったか」「うまくいかないときにどうすればいいか」といったポイントをセットで共有しました。これにより、各メンバーにまず「やってみたい」と思ってもらう、使ってみて躓いたときに「もう一度試してみよう」と思ってもらうことをターゲットとしました。 2点目は初心者向けレクチャの開催です。「そもそもどんなツールがあるのか」「各ツールでどんな操作をすれば何ができるのか」という基礎的な内容を、弊チームの業務フローに合わせた形でレクチャしました。これにより、各メンバーが「やってみたい」と思ったときにすぐに試せるような土台を作りました。 上記の主要施策の他にも、Slackのtimesチャンネルやチームの朝会での情報共有など、継続的に文化醸成を図る取り組みを行っています。 まとめ 本記事ではAI協働型分析フロー構築の取り組みを紹介しました。AI協働型分析フローの導入を検討している方がいれば、ぜひ参考にしてみてください。上記はまだ検証中のステータスであり、今後も改善を続けていく予定です。新たな知見が得られ次第、改めて共有いたします。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
.table-of-contents > li > ul > li > ul { display: none; } .w70p .figure-image img { width:70%; } 12月1日〜12月5日にラスベガスで AWS re:Invent 2025 が開催され、ZOZOのエンジニアが5名参加しました。この記事の前半では熱気に溢れる会場の様子を、後半では面白かったセッションについてご紹介します! AWS re:inventとは 現地の様子 メイン会場 朝食や昼食 Sports Forum 無料のStarbucks Coffee AWS主催のパーティ セッションレポート Build observable AI agents with Strands, AgentCore, and Datadog (AIM233) AIエージェント運用の課題 Strands AgentsとAgentCore デモ:AWS Newsletter Agent AWS Well-Architected Framework - Generative AI Lens DatadogによるLLM Observability まとめ SEC320-R: The AWS Security Incident Response Challenge: Defend the Cake! ストーリー設定:Unicorn Cake Companyを守れ! インシデント対応のアプローチ(NIST SP 800-61 Rev.3) 環境のアーキテクチャ CISOからの指示 取り組んだセキュリティ課題 1. アクセス制御の強化 2. クラウンジュエル(秘密のレシピ)の保護 3. 検出と警告の設定 4. ウェブサイトの保護 攻撃者の手口:知っておくべき統計 IAM設定の問題点 Shift Left:開発段階からのセキュリティ AWS CIRT リソース Workshopの様子 まとめ Accelerate platform engineering on Amazon EKS (CNS301-R1) Workshopの概要 プラットフォーム基盤構築 生成AIを活用したプラットフォームエンジニアリング Progressive Deliveryの実践 感想 Using Amazon Q to Cost Optimize Your Containerized Workloads (CMP348) Workshopの概要 Amazon Qによる移行分析 負荷試験でGravitonへの移行の効果測定 段階的な移行アーキテクチャ Phase 1: 移行用のGraviton NodePoolを用意し、pilot環境での移行検証 Phase 2: capacity-spreadの導入 Phase 3: トラフィック分散 感想 Infrastructure Innovations (KEY004) 講演のはじめに 主なアップデート #1 Graviton5 主なアップデート #2 Lambda Managed Instances 主なアップデート #3 Vectors ゲストセッション 講演を通じて感じたこと おまけ Advanced AWS Network Security : Building Scalable Production Defenses [REPEAT] (SEC303-R1) 主な内容 AWS Network Firewallとは Route 53 Resolver DNS Firewallとは Labのアーキテクチャ図 AWS Network Firewallでの通信制御 DNS Firewallでの通信制御 Workshopまとめ The Kiro coding challenge (DVT317) Workshopの概要 実践 まとめ Ditch your old SRE playbook: AI SRE for root cause in minutes (sponsored by Resolve AI) (AIM260-S) 概要 内容 ソフトウェアエンジニアリングの進化 AI for prod Resolve AIの3つの重要な特徴 AIエージェント構築のアプローチ比較 感想 [NEW LAUNCH] Resolve and prevent future operational issues with AWS DevOps Agent [REPEAT] (DVT337-R1) AWS DevOps Agentができること - AWS上のリソースの可視化 AWS DevOps Agentができること - 障害調査 実際の使い所 おわりに AWS re:inventとは re:Invent はAmazon Web Services(AWS)が主催するAWS最大のカンファレンスです。このイベントでは、AWSが提供する様々なクラウドサービスに関する新サービスやアップデートが発表されます。 2025年は世界中から約60,000人以上が、日本から昨年と同程度がラスベガスに集まりました。今年のre:Inventでは、「 AIアシスタントからAIエージェントへ 」という転換点を象徴するイベントでした。 現地の様子 メイン会場 The Venetian Resort Las Vegasがメインの会場となっており、KeynoteやEXPO、Swag(ノベルティ)など様々なコンテンツがここに集合しています。そんな会場で出会った様々なコンテンツをご紹介いたします! メイン会場では、AWS re:Invent会場への入口案内があり、この時点ですでに私たちはテンションがとても上がっていました。周りの参加者からも笑い声や笑顔で写真を撮る人も多くとてもよい空間となっていました。 エントランスの様子 メイン会場に入ると、大きな黒板がありチョークで好きなように描けました。たくさんの参加者がここで社名を書き記念撮影を行なっていました。イベントが行われる5日間このブースもとても盛り上がっていました。 メイン会場にある大きな黒板 ZOZOTOWNの名を記しました! メイン会場で特に盛り上がっていたブースはKiroのお化け屋敷でした。筆者もお化け屋敷に入りました! コーディングによるバグやエラーなどがホラー演出として現れ、それをKiroが救ってくれるといった内容だったと思います。このお化け屋敷を抜けた後に、記念写真をとってもらい、さらにKiroのSwagもいただきました! Kiroのお化け屋敷 メイン会場にはAWSの公式グッズが売っているショップもありました! シャツやパーカー、リュック、タンブラーなど様々な商品が売っておりどれも魅力的でした! 来年参加される方はぜひ立ち寄ってみてください! AWS公式ショップ「AWS Merch Store」 メイン会場には2日目から世界的な企業がブースを構えているEXPOエリアがオープンします! EXPOエリアの様子 各企業のブースに行って、議論を交わしたりサービスの説明を受けたりしました。 EXPOエリアにある企業ブースの様子 アンケートに答えたりゲームをクリアしたりするとSwagがもらえます。現地参加したメンバーもSwag集めを楽しんでいました! 特に Resolve.ai さんのブースでは、オリジナルのパーカーに目の前で自分の名前を印字してくださる企画が開催されており、長蛇の列ができていました! 長蛇の列ができていたResolve.aiさんのブースの様子 どの企業もブースやSwagに力を入れており、持って帰るのがかなり大変でしたが、様々な企業の話も聞けて楽しかったです! 現地でいただいた数々のSwagたち! 朝食や昼食 re:Inventの会期中は、朝食や昼食が各会場で提供されていて、参加者なら無料で食べられます! ビュッフェ形式なので好きなものを食べられました! 現地でいただいたビュッフェの様子 さらに、セッション会場の通路で定期的に軽食も提供されます。Workshopなど長めのセッションに参加する時は、軽食やコーヒーを持ち込んで栄養補給しながら臨みました! セッション会場の通路で定期的に提供される軽食 Sports Forum AWSがスポーツ領域でどのようにクラウドやAIを活用しているか、展示・セッション・体験ブースを通じて体感できるSports ForumというイベントゾーンがCaesars Forum会場で開催されていました! Caesars Forum 会場で開催されていたSports Forumというイベントゾーン F1からNBAまで様々なスポーツでの技術活用例が展示されており、セッションの合間のリフレッシュにもなり、とても楽しい空間でした。eSportsのRiot Games社も展示しており、ゲームを体験できるスペースも用意されていました。 eSportsのRiot Games社の展示 ゲームを体験できるスペース 無料のStarbucks Coffee re:Invent参加者には、会場であるVenetian内のスターバックスでドリンクとフード、合わせて4点までが無料で提供されます! 朝8:30くらいに行った際は30分ほど並んだので、時間に余裕のある方はぜひお立ち寄りください! re:Invent参加者には、Venetian内のStarbucks Coffeeでドリンクとフードが無料で提供される AWS主催のパーティ APJ Kick Off Partyとre:Play会場内の様子 初日(12/1)は、「 APJ Kick Off Party 」に行ってきました。本イベントはアジア太平洋地域と日本からre:Inventに参加する方向けのAWS公式のネットワーキングイベントです。ラスベガスのナイトクラブで行われます。今年の出演はマーク・ロンソンさんでした。ブルーノ・マーズさんとの「Uptown Funk」など、大ヒット曲を生み出しています。 また、最終日前日、12/4には「 re:Play 」というパーティーが行われました。これはre:Inventのフィナーレのようなもので、大きな広場を使い行われました。日付が変わるまでパーティーでした。最後を締め括ったのはDJのKASKADEさんで、会場は大盛り上がりでした! セッションレポート ここからはre:Inventの参加メンバーが気になったセッションを紹介します。 Build observable AI agents with Strands, AgentCore, and Datadog (AIM233) SRE部 カート決済SREブロックの小松です。ZOZOTOWNのカート決済機能のリプレイス・運用・保守に携わっています。また、全社のAWS管理者としての役割も担っています。 今回は興味を持っていたサービスの1つであるStrands AgentsとAmazon Bedrock AgentCoreについて、AIエージェントの本番運用に向けたObservability(可観測性)の観点から紹介されたセッションに参加しました。 DatadogのSr. Technical AdvocateであるKunal Batra氏と、AWSのSr. Developer AdvocateであるDu'An Lightfoot氏による共同セッションで、その内容について簡単ではありますがご紹介します。 AIM233 セッションの様子 AIエージェント運用の課題 セッション冒頭では、AIエージェントをスケールで運用する際の課題が紹介されました。 AIエージェント運用の課題 Hallucinations & Quality : LLMが不正確な情報を生成する可能性があり、継続的な評価と修正が必要 Complexity : エージェントは複合システムであり、内部の動作が見えないとトラブルシューティングが困難 LLM Costs : 本番ワークロードではコストが容易に膨らむ Security & Safety : プロンプトインジェクションやデータ漏洩のリスク 特に印象的だったのは、エージェントが「非決定的(non-deterministic)」であるという点です。同じ入力でも異なる出力を返す可能性があり、従来のソフトウェアとは異なるアプローチでの監視が必要になります。 印象的だったエージェントが「非決定的(non-deterministic)」であるという点 Strands AgentsとAgentCore 続いて、AWSが提供する、エージェント構築・運用ツールが紹介されました。 Strands Agents は、わずか数行のコードでエージェントを構築できるオープンソースのPython SDKです。モデルに依存せず、MCPやA2Aプロトコルもサポートしています。 Strands Agents Amazon Bedrock AgentCore は、エージェントを本番環境にデプロイするための包括的なプラットフォームです。 Amazon Bedrock AgentCore AgentCoreは以下のコンポーネントを提供します。 Runtime : エージェントのホスティング環境。DockerfileまたはCode-zipでデプロイ可能 Memory : 短期記憶(チャット履歴、セッション状態)と長期記憶(セマンティック、ユーザー設定、エピソディック) Observability : OpenTelemetry(OTEL)ベースのテレメトリ収集 Identity/Policy/Gateway : セキュリティとアクセス制御 デモ:AWS Newsletter Agent セッションではAWS Newsletter Agentを使ったデモが行われました。このエージェントはAWSの最新ニュースを取得し、AIに関連するアップデートをフィルタリングしてニュースレターとして配信します。 AWS Newsletter Agentのデモ デモでは、以下のような流れが紹介されました。 ユーザーが「What's new in AWS today?」と質問 エージェントが current_time と fetch_aws_news ツールを実行 AI/MLに関連するニュースを抽出してサマリーを生成 ニュースレターをメールで送信 デモの流れ さらに、エージェントが自然言語の指示だけでEventBridgeのスケジュールを作成し、毎日自動でニュースレターを配信する設定まで行っていました。 AWS Well-Architected Framework - Generative AI Lens セッションでは、AWS Well-Architected Frameworkに Generative AI Lens が2025年に追加されたことも紹介されました。 AWS Well-Architected FrameworkにGenerative AI Lens 6つの柱それぞれにAIエージェント向けのベストプラクティスが定義されています。 Operational Excellence : 包括的なObservabilityの実装 Security : 安全で責任あるアウトプットのためのGuardrails Cost Optimization : 長時間実行ワークフローにおける停止条件の設定 DatadogによるLLM Observability セッション後半では、Datadogを使ったAIエージェントの監視デモが行われました。 Datadogを使ったAIエージェントの監視デモ Datadogの LLM Observability では、以下のような情報を一元的に確認できます。 Summary : エラーレート、LLM呼び出し回数、トークン使用量の概要。 Evaluations : エージェントの応答品質を評価。「Failure to Answer」の割合や、カスタム評価(Financial Advisory Compliance Checkなど)の結果を確認できる。 Cost : トークン使用量とコストの推移。最もコストがかかっているモデル呼び出しを特定できる。 Traces : エージェントの処理フローを詳細に追跡。特に印象的だったのは、マルチエージェント構成(Budget Guru → Triage Agent → Prompt Injection Agent → PII Detection Agent)の各ステップが可視化されている点。 Security & Safety : プロンプトインジェクション、出力の有害性(Toxicity)、PII(個人情報)の検出状況を監視できる。 Experiments : 異なるモデルやプロンプトを比較して、品質・コスト・レイテンシのトレードオフを分析できる。Claude Haiku 4.5など複数モデルが比較されていた。 まとめ AIエージェントを本番運用する上で、従来のアプリケーション監視とは異なる観点が必要だと実感しました。特に以下の点が印象に残っています。 非決定性への対応 : 同じ入力でも出力が変わるため、品質評価の自動化が重要 コスト可視化 : トークン単位でのコスト追跡により、予期せぬコスト増加を早期に検知 セキュリティ監視 : プロンプトインジェクションやPII漏洩のリアルタイム検出 マルチエージェントのトレース : 複数エージェントが連携する場合、処理フローの可視化が不可欠 弊社でもAIエージェントの活用を進めていますが、本番運用に向けてはObservabilityの整備が重要な課題になりそうです。StrandsとAgentCore、そしてDatadogの組み合わせは、その解決策の1つとして検討していきたいと思います。 SEC320-R: The AWS Security Incident Response Challenge: Defend the Cake! このWorkshopは、架空の企業「Unicorn Cake Company」のセキュリティチームとして、攻撃者(Grumpy Cats)から秘密のレシピを守るという、ゲーム形式で楽しみながらセキュリティインシデント対応を学べる内容でした。 SEC320-R Workshopの様子 このWorkshopでは、Blue Teamとして、AWS Lambdaで実装されたRed Teamの攻撃を防御するシナリオをシミュレートします。30分の準備期間の後、攻撃が開始され、リアルタイムで対応していく形式です。 Workshopの概要 画像引用元: Workshop参加者向けに用意されたページの画面をスクリーンショット ストーリー設定:Unicorn Cake Companyを守れ! 参加者は「Unicorn Cake Company」という架空のケーキ製造会社のクラウドセキュリティチームとして参加します。この会社はユニコーン向けの魔法のケーキを製造しており、その秘密のレシピ「UnicornCakesSecretRecipe.txt」が攻撃者に狙われています。 攻撃者は「Grumpy Cats(気難しい猫たち)」と呼ばれる集団で、レシピを盗むか、盗めなければ破壊しようとしています。 インシデント対応のアプローチ(NIST SP 800-61 Rev.3) Workshopでは、 NIST SP 800-61 Rev.3 に基づいたインシデント対応のフレームワークが紹介されました。 インシデント対応のフレームワーク Preparation(準備) : インシデント対応能力の確立、ツールの準備、スタッフの訓練 Detection & Analysis(検出と分析) : 脅威の検出と影響範囲の分析 Containment, Eradication & Recovery(封じ込め、根絶、復旧) : 被害の拡大防止と復旧 Post-Incident Activity(事後活動) : 振り返りと改善 Incident Response 特に印象的だったのは「 Incident Response is a TEAM SPORT! 」というメッセージです。セキュリティインシデント対応は一人で行うものではなく、チーム全体で取り組むべきものだという点が強調されていました。 特に印象的だった「Incident Response is a TEAM SPORT!」というメッセージ 環境のアーキテクチャ Workshopの環境は単一リージョン(us-east-1)で構成されており、以下のコンポーネントで構成されています。 環境のアーキテクチャ図 画像引用元: Workshop参加者向けに用意されたページの画面をスクリーンショット 主要コンポーネント CloudFront Distribution → Lambda (Competition Upload) → S3 (Competition Uploads) VPC内 Unicorn Cake Company Website → S3 (Unicorn Production Bucket) WorkshopHints (本番環境外、ヒント用) Unicorn Logging (ログ保存用) 組織構造 25人のユーザーが5つのチームに分かれている 営業、HR、開発、食品工学、レストラン経営者 全員がアクセスキーと管理者権限を持っている (これがセキュリティ的にヤバい設定) 全員がアクセスキーと管理者権限を持っている設定! 画像引用元: Workshop参加者向けに用意されたページの画面をスクリーンショット CISOからの指示 Workshopでは、CISO(Rainbow Sparkles氏)からの緊急ブリーフィングという形で、取り組むべき課題が提示されます。 CISOからの指示 画像引用元: Workshop参加者向けに用意されたページの画面をスクリーンショット 最重要ルール:「生産を停止してはいけません」 ユニコーンたちのために魔法のケーキを作り続けなければならないという制約の中で、セキュリティを強化する必要があります。これは実際の本番環境でも同様で、セキュリティ対策のために事業を止めることはできないという現実的な制約を体験できます。 取り組んだセキュリティ課題 CISOから提示された課題は大きく4つのカテゴリに分かれていました。 1. アクセス制御の強化 MFAの有効化 : 全ユーザーにMFAを設定(まずは「Unicorn_Cakes_Sales_1」で実演) IAM認証情報レポートのダウンロード : 現状の把握 2. クラウンジュエル(秘密のレシピ)の保護 削除・ダウンロードの追跡 : 誰がレシピにアクセスしたかを特定できるようにする 改ざん検知 : レシピが変更されていないことを確認する方法 不変ストレージ : S3 Object Lockを使用して、管理者でも削除できないようにする 多層防御 : S3バケットポリシーでAWSアカウント内からのみアクセス可能にする 3. 検出と警告の設定 Amazon GuardDutyの有効化 : 24時間365日の脅威検知 即時アラートの設定 : EventBridgeを使用してレシピへのアクセスをリアルタイムで通知 CloudTrailの設定確認 : ログが正しく収集されているかの確認 4. ウェブサイトの保護 可用性の維持 : ウェブサイトが継続して動作することを確保 復旧手順の確立 : 万が一破損した場合の復旧方法 攻撃者の手口:知っておくべき統計 Workshopでは、実際の攻撃者がどのような方法で初期アクセスを獲得するかという統計も紹介されました。 実際の攻撃者がどのような方法で初期アクセスを獲得するかという統計 66% : 有効なIAM認証情報を使用(うち約1/3がroot認証情報) 13% : 公開されたEC2インスタンス経由 この統計からも、IAM認証情報の管理がいかに重要かがわかります。 IAM設定の問題点 Workshopの環境には、意図的にセキュリティ上の問題が含まれていました。 IAM設定の問題点 Long-term Security Credentials : 長期間有効なアクセスキーの使用 MFA未設定 : MFAトークンが設定されていない(×マーク) 過剰な権限 : 全ユーザーがInline Policyで管理者権限を持っている また、「Overprivileged website」として、EC2インスタンスがIMDSv1を使用して過剰な権限を持つIAMロールがアタッチされているという、典型的な攻撃対象となる構成も紹介されました。 典型的な攻撃対象となる構成 Shift Left:開発段階からのセキュリティ Workshopの最後には、「Shift security left in your development flow」というメッセージとともに、 Kiro が紹介されました。 Shift security left in your development flow 開発段階からApplication Security Testingを導入することで、本番環境に脆弱性を持ち込まないようにするアプローチです。 AWS CIRT リソース セッションの最後には、AWS CIRT(Customer Incident Response Team)が提供する、有用なリンク集がQRコードで共有されました。インシデント対応についてさらに学びたい方は、こちらのリソースも参考になります。 builder.aws.com Workshopの様子 会場では多くの参加者がチームごとに分かれて、リアルタイムで攻撃に対応していました。スコアボードには各チームのポイントが表示され、競争形式で楽しみながら学べる環境でした。 まとめ このWorkshopを通じて、以下の点が特に印象に残りました。 セキュリティと事業継続のバランス :「生産を停止してはいけない」という制約の中でセキュリティを強化する経験は、実際の本番環境でも直面する課題そのものでした。 多層防御の重要性 : IAMポリシー、S3バケットポリシー、MFA、GuardDuty、CloudTrailなど、複数のレイヤーで防御することの重要性を実感しました。 検知と対応の速度 : 攻撃はいつ来るかわかりません。GuardDutyやEventBridgeを使ったリアルタイム検知の仕組みが、早期対応には不可欠。 チームワーク : 「Incident Response is a TEAM SPORT!」というメッセージ通り、一人ではなくチームで対応することの重要性を学びました。 弊社でもAWS環境のセキュリティ強化を進めていますが、このWorkshopで学んだ実践的な知識を活かして、より堅牢なセキュリティ体制を構築していきたいと思います。 Accelerate platform engineering on Amazon EKS (CNS301-R1) SRE部 プラットフォームSREブロックのさかべっちです! 普段はZOZOTOWNのマイクロサービス基盤をEKSで構築・運用しています。 最先端のプラットフォームエンジニアリングを学ぶため参加してきました! 多くの人が帰国する最終日に開催されたセッションにもかかわらず、予約していない人はほとんど入れていなかったとても人気のセッションでした。 Workshopの概要 BackstageやArgo CDなどの最先端ツールやKiroを駆使して、ハンズオン形式で開発プラットフォームを構築するものでした。Workshopで使用したプラットフォームは、EKS Auto Mode上に構築されており、非常に豪華な構成でした。 ハンズオン形式で開発プラットフォームを構築するWorkshop 主なコンポーネントは以下の通りです。 Backstage:開発者ポータル Argo CD:GitOpsエンジン Argo Workflows:CI/CDワークフロー Argo Rollouts:Progressive Delivery KubeVela:OAM(Open Application Model)ベースのアプリケーション管理 kro:Kubernetes Resource Orchestrator Kargo:環境間のプロモーション管理 Amazon Q:生成AIアシスタント 今年発表されたEKS Capabilitiesについてもここで紹介されました。 発表されたばかりのEKS Capabilitiesについても紹介 Argo CD、ACK(AWS Controllers for Kubernetes)、kroがマネージドで提供されるようになり、運用負荷が大幅に下がりそうです。VPC CNIやFluent BitなどのEKS addonとは別物で、詳しくは下記のbreakout sessionで紹介されているのでご覧ください。 www.youtube.com プラットフォーム基盤構築 Workshopの前半は、Backstageを使ってプラットフォーム基盤を構築していく内容でした。Backstageを使うと、サービスカタログ、API一覧、ドキュメント、CI/CDパイプラインの状態などを一元管理できます。特に感動したのは、テンプレートからCI/CDパイプラインを数クリックで構築できる点です。Application Name、AWS Region、EKS Cluster Nameなどを入力して「CREATE」を押すだけで、GitLabリポジトリの作成からArgo CDの設定まで自動で行ってくれます。 たった数分でGitOpsワークフローの構築が完了する たった数分でGitOpsワークフローの構築が完了しました。GitOpsとは、インフラやアプリケーションの状態をGitで宣言的に管理する手法です。Gitにマニフェストをpushすると、Argo CDが差分を検知して自動でEKSクラスターに反映してくれます。 生成AIを活用したプラットフォームエンジニアリング Kiroをプラットフォームエンジニアリングに活用する方法も紹介されました。Kiroを活用して先ほどのプラットフォーム基盤にアプリケーションをデプロイします。 KiroにRustアプリケーションのDynamoDBテーブルとデプロイマニフェストを作成してもらいました。 Please use the Kubevela OAM component definition ...(中略) as a template and create a new Kubevela OAM component definition with the name of ddb-table using this dynamodb table CRD yaml Create an OAM manifest ...(中略) using Kubevela templates Include in strict order of dependency using dependsOn between components: - DynamoDB table with component-policy trait for permissions - Service account - Rust app (deploy last) - Path based ingress with /rust-app routing KubeVelaを用いたアプリケーションマニフェストが生成されます。KubeVelaを使うと、OAM(Open Application Model)に基づいてマイクロサービスを1つのファイルで定義できます。通常であればDeployment、Service、Ingressなどを個別に書く必要がありますが、KubeVelaのOAMを使えばこれだけでOKです。 apiVersion: core.oam.dev/v1beta1 kind: Application metadata: name: rust-application spec: components: - name: rust-webservice type: appmod-service properties: image: <image> image_name: java-app port: 8080 targetPort: 8080 replicas: 2 appPath: "/app" env: - name: APP_ENV value: "DEV" readinessProbe: httpGet: path: /app/ port: 8080 initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 5 resources: requests: cpu: "500m" memory: "256Mi" limits: memory: "512Mi" functionalGate: pause: "20s" image: "httpd:alpine" extraArgs: "red" performanceGate: pause: "10s" image: "httpd:alpine" extraArgs: "160" traits: - type: path-based-ingress properties: domain: "*.elb.us-west-2.amazonaws.com" rewritePath: true http: /app: 8080 また、kro(Kubernetes Resource Orchestrator)を使うと、cicdpipelinesやeksclusterといった新しいカスタムAPIを作成でき、KubernetesとAWSリソースのデプロイをさらにシンプルにできます。上記のコードをGitにpushすると自動でビルド・テスト・デプロイが実行されます。 git pushすると自動でビルド・テスト・デプロイが実行される Progressive Deliveryの実践 Workshopの後半では、Argo Rolloutsを使ったProgressive Deliveryを体験しました。Argo Rolloutsの詳細な設定はプラットフォームエンジニアが担当し、開発者はKubeVelaのコンポーネントを使ってgit pushするだけで段階的デプロイを実現できるようにします。 CI/CDパイプラインと組み合わせると、Progressive Deliveryは以下のようなフローになります。 開発者がGitリポジトリにコードをプッシュ Argo Workflowsがコードをコンパイル、イメージをビルド、ECRにプッシュし、マニフェストのイメージタグを更新 Argo CDがマニフェストの変更を検知し、DEV環境へのデプロイをトリガー Rolloutが新バージョンを20%に展開し、機能テストを実行 40%、60%、80%と段階的に展開し、パフォーマンステストを実行 テスト失敗時は自動で最後の安定バージョンにロールバック 実際にわざとエラーになる変更を入れてテストしてみました。 わざとエラーになる変更を入れてテストする revision:4のAnalysisRunが「Failed」となり、自動でロールバックされています。修正を加えて再度git pushすると、無事Successfulになりました。 修正を入れて再度git pushすると、無事Successfulになる DEV環境へのデプロイが成功した後はPROD環境へのデプロイです。DEVからPRODへの昇格管理にはKargoを使いました。Kargo UIで「Promote」をクリックすると、先ほどDEVでデプロイが成功したイメージを使用して、自動でK8sマニフェストを更新してくれます。 DEVでデプロイが成功したイメージを使用して、自動でK8sマニフェストを更新してくれる DEVで成功したイメージをそのままPRODに適用できるので、環境間の差異による問題を防げます。 さらに、DevOps Research and Assessment (DORA) メトリクスも自動で計測・可視化されます。これにより、チームや組織のソフトウェアデリバリのスピードと品質の改善点を特定できます。 チームや組織のソフトウェアデリバリのスピードと品質の改善点を特定できる 最終的に構築したDevOpsアーキテクチャの全体像は下記の通りです。 最終的に構築したDevOpsアーキテクチャの全体像 感想 Backstageの導入を検討していたのですが、実際に触ってみてCI/CDパイプラインをテンプレート化して開発者に提供できる点が便利であることを実感しました。また、EKS Capabilitiesで今後Argo CDなどがマネージドで提供されるようになると、プラットフォームの構築・運用コストが大幅に下がりそうです。 ただ、弊チームでは現在FluxCDを使ってGitOpsを運用していますが、今回のre:Inventの発表を見る限りArgo CDがかなり推されている印象だったので、中長期的にはArgo CD移行も視野に入れて検討していく必要がありそうです。今後様々なEKSエコシステムのEKS Capabilitiesも使えるようになると嬉しいです! WorkshopのソースコードはGitHubで公開されているので、興味のある方はぜひ試してみてください! github.com Using Amazon Q to Cost Optimize Your Containerized Workloads (CMP348) 引き続きさかべっちが参加したWorkshopをご紹介します! こちらはAmazon Qを活用してEKSワークロードをGravitonに移行し、コスト最適化を行うWorkshopです! 私が入社前のアルバイトをしていた頃に、チーム内でGravitonの移行について話題が上がっていたので、今後移行の予定ができたときのためにキャッチアップしておこうと考えて参加しました! AWS GravitonはAWSが独自開発したARMベースのプロセッサです。x86インスタンスと比較して最大40%優れたコストパフォーマンスを実現し、さらに最大60%少ないエネルギー消費という特徴があります。 Graviton移行の流れは以下の5ステップです。 Graviton移行の流れ Workshopの概要 サンプルのチャットボットアプリケーション(Java、Python、Go、.NET Coreから選択)をx86からGravitonへ移行する流れを体験しました(自分はGoを選択して進めました)。Amazon QにGraviton移行の専用agentがすでに組み込まれており、Amazon Qに質問しながら移行の分析や実装を進めていくスタイルでした。 Amazon Qによる移行分析 まず、Amazon Qに現状のEKSインフラがGraviton移行可能かを分析してもらいました。 Q. Analyze the EKS infrastructure for Graviton readiness すると、Karpenter NodePoolの設定やアプリケーションマニフェストの問題点、コンテナビルドの準備状況などを詳細に分析してくれます。さらに言語ごとの移行複雑度をスコアリングしてくれるので、どのアプリから移行すべきかの判断材料になりました。 コスト分析もAmazon Qにやってもらいます。 Q. Perform EKS Graviton migration cost analysis for my go app 現在のx86コストとGraviton移行後の予測コストを比較したレポートを作成してくれました。月額約14%の削減、年間では約$600の削減が見込まれるという結果でした。 負荷試験でGravitonへの移行の効果測定 Workshopでは事前に用意されていた負荷試験ツールを実行して、Graviton移行前後のパフォーマンスを比較しました。 Graviton移行前(x86) #[Mean = 2192.524, StdDeviation = 47.983] Requests/sec: 0.45 Amazon Qから言われた通りにGraviton移行で必要な下記の変更を加えてから負荷試験を実施します。 nodepool: kubernetes.io/arch に arm64 を追加 instance-familyを c7g に変更 Graviton移行後 #[Mean = 1593.935, StdDeviation = 57.964] Requests/sec: 0.63 レイテンシが約27%改善、スループットは約40%向上という結果になりました! Gravitonはコスト最適化のイメージでしたが、パフォーマンスもかなり向上していることを知りました。 段階的な移行アーキテクチャ 本番環境での移行時は、いきなり全てをGravitonに切り替えるのではなく、段階的に進めることが推奨されていました。これにより、問題が発生した場合でも影響範囲を限定できます。 Amazon Qに段階的な移行方法を教えてもらいます。 Q. Can you help me create a migration runbook based on the progressive Graviton migration? 3フェーズ(Dev → Staging → Production)の段階的移行計画、バリデーション基準、ロールバック手順まで含んだ手順を生成してくれます。 また、マイクロサービスごとの移行は下記の手順で実行します。 Phase 1: 移行用のGraviton NodePoolを用意し、pilot環境での移行検証 Phase 1: 移行用のGraviton NodePoolを用意し、pilot環境での移行検証 Phase 2: capacity-spreadの導入 Phase 2: capacity-spreadの導入 Phase 3: トラフィック分散 Phase 3: トラフィック分散 具体的には topologySpreadConstraints を使って、x86とGravitonの両方にPodを分散させながら徐々に移行していく方法です。 topologySpreadConstraints: - labelSelector: matchLabels: app.kubernetes.io/name: goapp maxSkew: 1 topologyKey: capacity-spread whenUnsatisfiable: DoNotSchedule 感想 Graviton移行用のagentを使うと、インフラの分析からコスト試算、実装の提案、段階的な移行まで一貫してサポートしてくれます。Graviton移行の取り掛かりとしてはハードルがかなり下がった印象です。 特に印象的だったのは、負荷テストでパフォーマンスが大幅に向上した点です。Gravitonは「コスト削減」のイメージが強かったのですが、実際にはレイテンシ27%改善・スループット40%向上と、パフォーマンス面でも大きなメリットがあることを体感できました。 弊チームではZOZOTOWNのマイクロサービス基盤をEKSで運用していますが、マイクロサービスごとに使っている言語が異なるため、今回学んだ段階的な移行アプローチは非常に参考になりました。 具体的には、環境ごと(Dev → Stg → Prd)、NodePoolごと(x86とGravitonを併用)に段階リリースしていく方法は、弊チームでも必須になると思います。移行の予定が決まった際はAmazon Qに現状のインフラを分析してもらい、移行の優先度を決めるところから始めてみたいと思います。 Infrastructure Innovations (KEY004) SRE部 基幹プラットフォームSREブロックの若原です。普段はZOZOの倉庫システムやブランド様向けの管理ページなどのサービスのオンプレミスとクラウドの構築・運用に携わっています。 現地時間12/4の朝に行われた、AWS SVPのPeter DeSantis氏とVice President of Compute and Machine Learning ServicesであるDave Brown氏によるKeynoteを聴講してきましたので、この基調講演の概要と印象に残ったポイントをお伝えします。 www.youtube.com 講演のはじめに 講演では、AIがクラウドと開発者コミュニティに何をもたらすのかという問いから始まりました。Peter氏はAIトランスフォーメーションがもたらす未来を語る前に、まず変わらない大切な価値に目を向けるべきだと強調しました。 どれだけ世の中が変化しようとも、AWSが創業以来一貫して大切にしてきたSecurity(セキュリティ)、Availability(可用性)、Elasticity(弾力性)、Cost(コスト)、Agility(俊敏性)の5つのAttributes(特性)は、クラウド基盤の核心であり、AI時代においてさらに重要になると述べていたのが印象的でした。 主なアップデート #1 Graviton5 AWSが自社開発する次世代CPU「Graviton5」が発表されました。高いコア数やキャッシュ性能を備えながら省電力・高性能を両立している点が特徴で、複雑なデータ処理やAIワークロードにも適した設計になっています。 主なアップデート #1 Graviton5 主なアップデート #2 Lambda Managed Instances Lambda関数をEC2インスタンス上で実行可能にする新機能として紹介され、サーバーレスの簡易さを保ちながらEC2の柔軟なインスタンスタイプ選択と割引価格モデルを活用し、柔軟なトラフィックワークロードのコスト最適化と性能制御を可能にすると説明されました。 主なアップデート #2 Lambda Managed Instances 主なアップデート #3 Vectors Amazon S3 Vectorsがネイティブなベクトルデータの保存・検索をサポートするクラウドストレージとして紹介され、生成AIや意味検索(RAG)など大規模AIワークロードのコストを最大90%削減しつつ効率的に処理できる基盤として発表されました。 主なアップデート #3 S3 Vectors ゲストセッション 新機能のアップデート以外にも、AppleのPayam Mirrashidi氏が登壇し、AWS Gravitonを活用してSwiftベースのバックエンドでパフォーマンスとコスト効率を大幅に向上させた事例を紹介しました。また、TwelveLabs社のJae Lee CEOはS3 Vectorsを使って大規模な動画データを効率的にインデックス化・検索するAIシステムの事例を共有し、さらにDecart社のDean Leitersdorf CEOはTrainium3を用いたライブ・ビジュアル・インテリジェンスの実演を披露するなど、大変興味深い内容となりました。 ゲストセッションの様子 講演を通じて感じたこと AWSはAI時代の最前線で先進的なサービス・機能を展開しつつも、クラウドの原点となる価値を創業以来常に大切にしているというメッセージが強く伝わってきました。 今回発表されたGraviton5、Lambda Managed Instances、S3 Vectorsなどは、単に追加された新機能ではなく、基本となる要素であるAgilityやCostといった基本価値をさらに強化するものに感じられました。今後のAWSとクラウドの進化がますます楽しみです。 セッションの様子 おまけ 講演の冒頭で、Peter氏は学部時代に愛読していた「CSの教科書」の第7版が最近刊行され、その中にNitroシステムとGravitonプロセッサに関する新しい章が追加されたことを紹介していました。 これを記念して、会場に投影されたQRコードを読み取った先着1000名に、その最新版の教科書を講演後にExpoホールでプレゼントするというサプライズ企画があり、私も運よく受け取れました。ひとつ想定外だったのは、喜んで受け取ったのは良いものの、この本がほぼ2kgある大著で思いのほか重く、帰りの預け荷物の重量制限が頭をよぎり少しハラハラしてしまいました。無事に持ち帰れたので、これから大切に読ませていただこうと思います。 サプライズ企画で手に入った「CSの教科書」第7版 Advanced AWS Network Security : Building Scalable Production Defenses [REPEAT] (SEC303-R1) AWS環境におけるネットワークセキュリティ強化方法を実践的に学ぶ2時間のWorkshopに参加しました。 最初に講師の方から約30分ほどの座学があり、AWS Network FirewallやDNS Firewallの基礎、今回のLabのアーキテクチャについて解説がありました。その後は用意された環境を使い、参加者が自分の手で設定を進めながら学ぶハンズオン形式で進んでいきます。 SEC303-R1セッションの様子 #1 主な内容 冒頭の座学では、オンプレミス環境のように単一のファイアウォールで全通信を集約するモデルではなく、AWSではユースケースごとに適切な境界を組み合わせて防御する、という設計思想が強調されていました。 本WorkshopではEgress(外向き通信)とEast-West(VPC間通信)に焦点が当てられ、それぞれの通信に対して多層的に検査・制御するセキュリティを構築するという内容でした。 Ingress(内向き通信)ではWAFによるアプリケーションレイヤー保護が主役となる一方、本Workshopで取り扱ったEgressやVPC 間通信ではAWS Network FirewallとRoute 53 Resolver DNS Firewallの2つのサービスが中心的な役割を果たし、それぞれどのように組み合わせて環境を保護すべきかについて学びました。 SEC303-R1セッションの様子 #2 AWS Network Firewallとは AWS上のネットワークトラフィックを保護するためのマネージド型ファイアウォールサービスで、AWS環境のネットワークを総合的に守るためのセキュリティ基盤として利用できるサービスです。侵入防止(IPS)、ステートフル/ステートレスルール、ドメインフィルタリングといった機能を備えており、既存のセキュリティ運用に合わせた詳細なトラフィックの検査が可能です。Suricataというルール形式に対応している点も特徴で、既存のセキュリティ運用で利用している独自ルールやコミュニティルールをそのまま適用できます。 Route 53 Resolver DNS Firewallとは AWS環境内のDNSクエリを保護するためのマネージド型ファイアウォールサービスで、悪性ドメインへのアクセスやDNSを悪用した攻撃をDNSレイヤーで防止するための基盤として利用できます。AWSが提供する脅威ドメインリストや独自のカスタムリストを使ってドメイン単位で通信を制御でき、DNSトンネリングや不正なクエリを早期に検知・遮断できます。VPCのRoute 53 Resolverと統合されており、アプリケーションを変更することなくDNSトラフィックを一元的に監査・制御できるのが特徴です。 Workshop参加者には複数のVPCがTransit Gatewayで接続された環境を渡され、その上でネットワークセキュリティ構築を段階的に体験できる内容となっていました。 Labのアーキテクチャ図 Labのアーキテクチャ図 VPCの内部から外向きの通信状態を確認するテストスクリプトが用意されており、Labの開始時点では、画像のようにあらゆる通信が通ってしまう状態になっています。 Labの開始時点では、あらゆる通信が通ってしまう状態になっている ここからAWS Network FirewallとDNS Firewallを使って、攻撃に悪用されるポイントを1つずつ対策しながらセキュリティを強化していきます。 AWS Network Firewallでの通信制御 ここでは例としてAWS Network FirewallのGeoIP機能を使って、特定の国や米国・カナダ以外への通信をブロックするルールを追加し、地域ベースでの通信制御を実践します。 コンソール上のVPCサービスから、Lab用に用意されているFirewallを選択します。Firewall policy settings→StatefulRuleGroupへ移動し、RulesセクションからEditします。 Edit rulesからルールを追加する USとカナダ以外への通信をブロックするSuricataルール表記を追記してSaveします。 # Block Traffic To/From Any Country Besides the US or Canada drop ip any any -> any any (geoip:any,!US,!CA; msg:"Drop traffic to countries other than US and Canada"; sid:10000009;) 1分ほど待つとRuleが適用され、先ほどまで通っていたcurlコマンドが無事Blockされるようになりました。 curlコマンドがBlockされるようになった DNS Firewallでの通信制御 次に、DNS Firewallを用いて、独自のドメインブロックリストを作成し、攻撃者に悪用されやすいTLDをまとめてブロックする設定例を紹介します。 VPCの画面からDNS Firewallを選択、Rule groupsへ移動し、新規でRule groupを作成します。作成したRule groupに対してAdd ruleでブロックしたいドメインのリストを記載します。 Rule groupを作成する 設定例は以下の通りです。 Rule name: <Rule名は自由に設定> Domain list: Custom domain list and Create new domain list Domain list name: Domains: 以下を記載 *.ru *.cn *.xyz *.cyou *.pw *.ws *.gq *.surf *.cf *.ml Domain redirection setting: Select Inspect all (Default) Action: Block Block response: OVERRIDE Record type: CNAME Record value: dns-firewall-block TTL (seconds): Enter 0 適用後、該当ドメインへのリクエストがBlockされることを確認できました。 該当ドメインへのリクエストがBlockされることを確認できた Workshopまとめ 今回紹介した内容以外にも、プロトコルとポートのミスマッチを利用した攻撃を検出するPort/Protocol Enforcementや、AWS Managed Domain Listsを利用したDNS Firewallの設定などもありました。 また、上記で設定した検知のログをCloudWatch Logsで確認したり、VPC間通信のトラフィック制御を試したりするなど、他にもいくつかのシナリオを想定したLabもありました。内容が盛りだくさんで時間内に到底収まらないほどのボリュームでした。 普段意識しづらい外向き通信のネットワークセキュリティについて、実際に手を動かして学ぶ、とても良い機会となりました。 The Kiro coding challenge (DVT317) ZOZOMO部 SREブロックの中村です。ZOZOMOなどのマイクロサービスのSREを担当しています。ZOZOMO部ではAI Agentを活用した業務の効率化や本番運用にAIへの積極的活用に力を入れており、re:Inventでは多数のAgentサービスの新発表や既存サービスのアップデートが行われ、それに関するセッションも多く行われました。 Kiroを用いた仕様書駆動開発のWorkshopに参加したのでそのセッション内容をご紹介します。また、「Ditch your old SRE playbook: AI SRE for root cause in minutes」のセッションも合わせて紹介します。 Workshopの概要 このWorkshopは、KiroというエージェントネイティブIDEを使った実践的なコーディングチャレンジです。難易度が徐々に上がる10個のコーディングチャレンジをKiroの支援を受けながら課題を解決していきます。参加者は約70〜80人ほどで、他のWorkshopと比較してもとても人気のセッションとなっていました。 The Kiro coding challengeの会場 Workshopの冒頭ではKiroや仕様書駆動開発についての簡単な説明が行われました。仕様書駆動開発では、PLANNING DESIGN → IMPLEMENTATION → TESTING & QA まで一貫してカバーし、仕様を先に定義してから開発するので、手戻りが少なく高品質な製品を出荷できるような説明が図とともに行われました。 仕様書駆動開発に関する説明 実践 Kiroの説明と、Workshopの説明が終わった後、AWSが用意してくれたWorkshopの環境に接続し、今回のWorkshopの概要等を確認できます。必要に応じてセットアップなど行なっていきます。 今回のWorkshopでは1から10のタスクが用意されており、それらをKiroを使って解決していきます。 Workshopの様子 #1 Workshopの様子 #2 タスクを解決するプロンプトをKiroで叩き、解決していってもらいます。序盤はプロジェクトのセットアップをメインに行うタスクが多く存在しました。 タスク6では、ついに仕様書駆動開発を使用するタスクがでてきました。Kiroに対し、仕様を伝えそれを仕様書として作成するように依頼することで、Kiroの管理下に仕様書が作成されます。仕様書を作成したのち、Kiroは仕様書を元に実装を行なっていきます。 Workshopの様子 #3 Workshopの様子 #4 タスク8では、AgentHooksを利用しコードの変更をトリガーに、対応のドキュメントを併せて更新するような指示を追加できました。AgentHooksを利用するとドキュメント更新以外のアクションも実行できるので色々と夢が広がる機能ですね! Workshopの様子 #5 また、このWorkshop中は常にTOP10の順位が前のスクリーンに投影されていて、エンジニア全員で競い合い会場が熱気に包まれていました。 Workshop中は常にTOP 10までの順位がスクリーンに投影されていた まとめ 本セッションを通じて、仕様書駆動開発がどのように行われていくのか、またKiroを利用した仕様書駆動開発をどのように行なっていくのかを学べました! 実際にWorkshopで利用したリポジトリはこちらです。どのような仕様書が作られたか気になった際はぜひ確認してみてください! github.com Ditch your old SRE playbook: AI SRE for root cause in minutes (sponsored by Resolve AI) (AIM260-S) 概要 このセッションでは、 Resolve AI社 により、SRE領域におけるAIエージェントの活用についてBreakout session形式で実施されました。本番環境の運用やインシデントの根本的な原因の分析を数分で実行するなど、開発でなく運用面にフォーカスをおいていました。SREという文字が含まれるセッションは少なく、こちらもとても人気でした。 AIM260-S セッション会場の様子 内容 ソフトウェアエンジニアリングの進化 セッション冒頭では、数年ほど前からAIがソフトウェアエンジニアリングに与えている大きな影響、進化を3段階に分けて説明されました。 1段階目ではAIがコードを書くのでなく、人間が書いたものを自動補完するようなところから始まり当初は人間がオペレーターとなって作業を行なっていました。 2段階目ではAgentが登場した頃で、AIがオペレーターとなり作業し、それを人間が管理、監視するという、現段階で多くのエンジニアが行なっていることなのだと思います。今はこの段階の進化レベルにあると考えられています。 3段階目はすでに移行の過程である、またはこれからAIの進化が到達すると考えられており、AIが開発だけなく、今までエンジニアが扱ってきた様々な監視ツールや本番でのインフラ構成など運用に必要なあらゆるツールを人間同様に使いこなせるという話がされました。 セッションの様子 AI for prod Resolve AI社が提供するAI Agentを用いて実際にインシデント対応するデモが実施されました。このAgentは事前にアプリのコードはもちろん、インフラの構成等全てを知識として把握しています。GrafanaからSlackのアラートチャンネルにエラーが通知された時にそれをトリガーにAgentが起動しエラーを調査します。 Slackでは何が原因でどうすれば解決できるかを提案してくれます。しかしなぜその結論になったのかを知りたい場合は、Resolve AIのWeb UIを確認しAgentがどのような調査を行なったか詳細な情報を確認できます。 デモでの調査内容は次の通りです。 ログクエリの生成 履歴の確認 トレースの調査 インフラストラクチャイベントの確認 チャート・ダッシュボードの分析 コードのチェック Resolve AIの3つの重要な特徴 セッションではResolve AIの構築にあたり、3つの重要な特徴について説明されました。特にマルチエージェントを生かして本番環境での実用性を説明しているように感じられました。 理解(Understand) Knowledge graphを用いてシステム全体を理解する 優秀なプリンシパルエンジニアが持つような知識を再現 システムの歴史 障害パターン 過去のポストモーテム 学習(Learn) ツール使用履歴からの学習 ダッシュボードの使用頻度など、ツールの使われ方から学習 アラートの優先度ラベル(P1など)を評価基準として活用 インシデントチャンネルでの議論から原因究明・修復プロセスを学習 チーム・組織レベルでカスタムコンテキストを追加可能 推論(Reason) マルチエージェントアーキテクチャ プランナーが「何をすべきか」を判断 専門化されたサブエージェントがエビデンスを収集 レート制限やペイロードの処理を理解 人間のフィードバックループを組み込み、継続的に改善 AIエージェント構築のアプローチ比較 このセクションでは、インシデント対応を自動化するAIエージェントの構築には、大きく4つのアプローチがあると説明をされました。セッションを聞いた内容を簡単に要約してみました。 LLMs エラーをそのままLLMに投げる最もシンプルな方法。時には正解するが、複雑なシステムではコンテキスト不足で機能しない。 セッションの様子 LLMS + tools(MCP) MCPサーバー経由でObservabilityツールにアクセス。データは取れるが、状態管理がなく「1時間分見るべきか、1週間か」の判断ができない。 Single Agent ランブックやコンテキストを詰め込んだ単一エージェント。肥大化して遅くなり、未知のインシデントに対応できなくなる。 Multi-Agent Systems 専門化されたエージェント群の協調動作。最も柔軟だが、オーケストレーションの複雑さがマイクロサービス同様の課題になる。 セッションの様子 感想 今回のセッションでSREにフォーカスを当てたAIエージェントのサービスを初めて知りました。確かにインシデントの調査や一部対応は既存のAIエージェントでもできますが、既存の情報のみでしか判断等できずに自律的な成長などをエージェントにさせようとすると、とても難しいことだと改めて感じました。 セッションの内容はAWS公式のYouTubeチャンネルに公開されているので、さらに細かい内容を知りたい方はぜひそちらの動画をご覧ください! www.youtube.com [NEW LAUNCH] Resolve and prevent future operational issues with AWS DevOps Agent [REPEAT] (DVT337-R1) 計測システム部 バックエンドブロックの髙橋です。普段はZOZOMETRY、ZOZOGLASS、ZOZOMATなどの計測プロダクトのバックエンドの開発・運用を担当しています。 今回、Keynoteで新たに発表された「 AWS DevOps Agent 」について、Keynoteをリアルで聞きながら興味を持ちました。発表後にハンズオンを受けてきましたので、ハンズオンの内容とAWS DevOps Agentの使いどころについてご紹介します。 まず、AWS DevOps Agent(以下、DevOps Agent)とは、今回のre:Inventで発表された Frontier Agents の一角を成すAIエージェントです。主に運用上発生する障害などのインシデントに対し迅速に原因を調査し、それに対する解決策を提案してくれます(2025年12月の記事公開時ではPublic Preview)。その上で、今回のハンズオンでは用意されたシナリオを通じ、DevOps Agentの実際の使い所について学んできました。 AWS DevOps Agentができること - AWS上のリソースの可視化 現在ログイン中のアカウントのAWSリソースのトポロジが表示される DevOps Agentでは、最初のステップとしてAgent Spaceというスペースを作成します。作成し、用意されたWebポータルにアクセスすると、現在ログイン中のアカウントのAWSリソースのトポロジが表示されます。 AWS DevOps Agentができること - 障害調査 今回のWorkshopでは、DynamoDBの書き込みキャパシティを意図的に大幅に絞り、システムに障害を発生させました。すると、予めセットされていたCloudWatch Alarmが発報します。 予めセットされていたCloudWatch Alarmが発報する 発報された後、DevOps Agentの「Start an investigation」ページで、「Investigate the DynamoDBWriteThrottleAlarm(CloudWatchで発報されたアラーム名)alarm」と入力し、調査してもらいます。 Start an investigationで調査してもらう 調査が進むと、「Root cause」という部分でDynamoDBの書き込みキャパシティの小ささが根本原因として書かれていることがわかります。 DynamoDBの書き込みキャパシティが小さい旨が根本原因とわかる さらに、「Mitigation Plan」で実際の復旧に必要な作業の計画を立ててくれます。 実際の復旧に必要な作業の計画を立ててくれる このように、今までは人間がやっていた障害内容の調査→原因究明→復旧のための計画までをDevOps Agentにアウトソースできます。 実際の使い所 DevOps Agentでは人間が先ほどの画像のように指示して調査させるだけではなく、Datadogなどのツールから異常検知されたことをトリガーとして自動的に調査を開始できます。 docs.aws.amazon.com 実際の運用現場では、アラートが鳴り、人間が気付いてからの初動対応は数秒レベルの即時とはいかない場合が多いですし、その時のメンバーによってシステムに対する習熟度や調査時に見る観点も違うでしょう。そのような場合にDevOps Agentが先回りして即時に自動で調査を開始し、人間に対して原因と復旧計画を提供することによってより高品質かつ、迅速な障害対応を行えると考えています。 現在DevOps Agentは現在Public Preview期間であるため、いきなり本番環境に組み込まずともチームのカオスエンジニアリング・障害訓練等からまず使ってみることもおすすめです。チームでのカオスエンジニアリングの記事は以下をご覧ください。 techblog.zozo.com おわりに セッションや展示ブースで多くのことを学べるのはもちろん、AWSのエキスパートや他社のエンジニアの方々と交流し、多くの刺激を受けられるのが現地参加の醍醐味です。今回得た知見を社内外に共有しながら、これからもAWSを活用してプロダクトとビジネスの成長に貢献していきます。 ZOZOではAWSが大好きなエンジニアを募集しています。奮ってご応募ください! corp.zozo.com
Developer Engagementブロックの @ikkou です。2025年もいよいよ終わりに近づいてきました。この季節の風物詩、「アドベントカレンダー」には皆さんも参加されましたか? ZOZOは例年アドベントカレンダーに参加し、2020年以降、記事数を100本→125本→175本→225本→275本と増加、そして今年は過去最高の計300本の記事を公開しました! 本記事ではその概要をお伝えします。 ZOZO Advent Calendar 2025 今年は合計11個のカレンダーを完走し、12月1日から25日の間に300本の記事を公開しました! 🎅 𝑯𝒂𝒑𝒑𝒚 𝑯𝒐𝒍𝒊𝒅𝒂𝒚𝒔 🎄 ZOZOの愉快なエンジニアたちによるアドベントカレンダー『ZOZO Advent Calendar 2025』の全300記事を完走しました🎉 今年も多種多様な記事が公開されました! 時勢を反映してか 80/300 前後はAI関連の記事でした! https://t.co/E9h4a6hxG5 pic.twitter.com/IcMICM0kJ3 — ZOZO Developers (@zozotech) 2025年12月25日 qiita.com 実施概要 ZOZO Advent Calendar 2025は以下の形式で運用しました。 形式 : 任意参加 運用方法 : Slackチャンネルで実施と参加を呼びかけ、各自が空いている日に登録 公開先 : ZOZO TECH BLOG、Qiita、Zenn、note、個人ブログなど 参加人数 : 130名 公開記事数 : 合計300本 もっとも多くの記事を書いたのは今年もshiozakiさんで、今年は計30本の記事を公開して3年連続で最多記録を更新しています。特に「シリーズ 5」は「C言語以外の◯言語でHelloWorld!を書いてみる」シリーズとして、ひとりで25記事を完走しています。 また、シリーズ 11では YasuhiroKimesawa さんが、スクラム関連に特化してひとりで25記事を完走しています。 そして、昨年に続き今年もチーム単位でひとつのカレンダーを担当する動きも見られました。 シリーズ 2: データ基盤ブロック シリーズ 4: カート決済SREブロック シリーズ 7: 計測システム部 シリーズ 8: 推薦系(推薦研究、MA推薦、推薦基盤) シリーズ 10: 25新卒 特定の技術領域に興味がある方は、ぜひ各シリーズの記事をチェックしてみてください。チームの雰囲気や勢いが伝わってくると思います。 アドベントカレンダーは、アウトプットの練習や執筆スキルを高める絶好の機会です。ZOZOではテックブログをアウトプットの主軸に置いていますが、「まだテックブログを書く自信が無い」「テックブログに書くにはネタが小粒」のような場合に、アドベントカレンダーは良い機会です。 運営目線では今年の見どころをDay 1の記事として公開しています。 zenn.dev 2025年の振り返り ZOZOのアドベントカレンダーでは例年その年を振り返る記事を公開しています。 開発組織の振り返り ZOZOの開発組織については、執行役員 兼 CTOの @sonots さんが例年恒例となっている「振り返りと現状」を記事にまとめています。 qiita.com 推薦基盤チームの取り組み 推薦基盤チームに特化した「振り返りと現状」も記事にまとまっています。 qiita.com 2025年の注目記事 ZOZO Advent Calendarは「バズる」ことを目的としていませんが、それでも多くの方に読まれた記事があります。特に注目された記事を2つ紹介します。 1つ目はZennで公開されたrem( @remsleep_zzz )さんによる、Reactの状態管理の歴史を振り返る記事です。技術的な深掘りと歴史的な背景の両方がバランスよく盛り込まれていて、多くの方に読まれました。 zenn.dev 2つ目はQiitaで公開されたRu Ito( rabbit_x86 )さんによる、VS Code拡張機能19選の記事です。開発効率化に直結する内容で、目に留まりやすかったことが要因と考えられます。 qiita.com 2025年の記事傾向 全300記事のタイトルをChatGPTさんに分析してもらったところ、以下のような傾向が見られました。 まず、全300記事のうち、AI関連の記事は約80本と全体の27%を占めていて、時流を色濃く反映したものになっていました。ZOZOでは7月に開発AIエージェントを全エンジニアに導入していて、その影響も大きいと考えられます。Claude Code関連の記事だけでも28本ありました。 corp.zozo.com 次点では、クラウド・SRE・運用系が20%、BigQueryを含むデータ基盤系が15%と続いています。 AI関連に関しては、進化の速度があまりにも早く、来年どのような状況になっているか想像がつきません。しかし、今年も既にそうであるように、AIを活用した開発効率化やプロダクト価値向上の取り組みが中心になると考えています。 過去のアドベントカレンダー ZOZOでは2018年から毎年アドベントカレンダーに参加しています。過去の取り組みは以下をご覧ください。 年 カレンダー 2024年 ZOZO Advent Calendar 2024 2023年 ZOZO Advent Calendar 2023 2022年 ZOZO Advent Calendar 2022 2021年 ZOZO Advent Calendar 2021 2020年 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 1 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 2 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 3 ZOZOテクノロジーズ Advent Calendar 2020 シリーズ 4 2019年 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 1 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 2 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 3 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 4 ZOZOテクノロジーズ Advent Calendar 2019 シリーズ 5 2018年 ZOZOテクノロジーズ Advent Calendar 2018 シリーズ 1 ZOZOテクノロジーズ Advent Calendar 2018 シリーズ 2 ZOZOテクノロジーズ Advent Calendar 2018 シリーズ 3 最後に ZOZOでは、プロダクト開発以外にも、アドベントカレンダーのような外部への発信も積極的に取り組んでいます。 一緒にサービスを作り上げる仲間をはじめ、エンジニアとしての技術力向上や外部発信に意欲的な方を積極的に募集しています。ご興味のある方は、以下のリンクからぜひご応募ください! corp.zozo.com
はじめに こんにちは、AI・アナリティクス本部データサイエンスブロックの大戸徳仁です。普段は、サービスや機能の現状把握・要因分析、施策の効果検証、需要予測モデルの開発・運用などを担当しています。私が所属するチームでは、「データに基づいた意思決定を支援すること」をミッションに、社内の各部門に対してデータ分析サービスを提供しています。 その取り組みの一環として、ZOZOの物流拠点「ZOZOBASE」のデータ活用に取り組んでいます。中でも、出荷計画や人員配置の判断材料となる「注文数の予測」については、予測精度が安定しない、予測工数がかかるといった課題があります。本記事では、これらの課題に対してどのようにアプローチしたのか、そしてプロジェクトを通じて得られた気づきについて紹介します。 目次 はじめに 目次 背景 課題 課題解決アプローチ 1.評価指標の設計 2.EDA(探索的データ分析) 3.予測モデルの構築 4.予測モデルの評価 リリース・仮運用 課題解決への第一歩 仮運用を通じて見えた新たな課題 次の改善フェーズに向けて まとめ 参考文献 背景 ZOZOの物流拠点であるZOZOBASE(以下、BASE)では、毎日膨大な量の注文が処理されています。その中で、日々の注文数をどれだけ正確に予測できるかは、BASEの業務を安定的に運営するうえで非常に重要です。注文数の予測は、次のような多くの意思決定の基盤となります。 日々のピッキング・出荷量の計画 出荷量に応じた人員配置や作業負荷の調整 運営体制の設計 これらの意思決定を適切に行うためにも、日々の注文数をブレなく予測できる状態が望まれます。 注文数予測は、物流現場の運営を支えるだけでなく、お客様へ商品を遅延なく届けるための体制づくりにも直結 します。 こうした背景から、過去データをもとに注文数を予測するモデルを構築し、現場の業務フローに組み込むプロジェクトを開始しました。 なお、 今回のプロジェクトは、毎日決まった時間に、翌日から7日後までの日次の注文数を予測する ことが要件です。翌日単体の予測だけでなく複数日先を同時に予測するため、トレンド・周期性・イベント影響など複数要因を適切に扱う必要があります。 課題 注文数予測が現場の重要な意思決定の基盤である一方、従来の手動方法にはいくつかの課題がありました。ここでは、モデル構築へ着手する前に明らかとなった課題を整理します。 課題1:予測に工数がかかる 従来の予測は現場担当者が手動で行っており、日々一定の工数が発生していました。予測作業そのものが現場担当者の負担となり、本来の出荷業務へ時間を割きづらい状態となります。 課題2:予測精度が安定しない 注文数は、次のような多くの要因によって日々変動します。 曜日ごとの周期性 セールやキャンペーン、クーポン施策の有無 天候や季節変動 これらが複合的に作用するため、担当者が手動で補正し続けるには限界があります。誤差が大きくなるだけでなく、そのばらつきも大きくなり、予測が安定しない状況となっていました。予測値が実績値を上回る(予測>実績)場合は、人員余りによるコスト増につながります。一方で、予測値が実績値を下回る(予測<実績)場合は、人員不足を招き、発送遅延につながる重大なリスクとなります。 課題3:属人的で再現が難しい 手動での予測では、担当者ごとに「こういう日は増えやすい」といった、経験値に基づく判断が行われており、予測プロセスが属人的となっていました。そのため、同じ条件でも担当者が変わると予測値が変わったり、予測を外した場合でもどの判断が影響したのかを振り返りづらくなったりと、改善につなげることが難しくなります。 これらの課題を解決するために、 日々の注文数を自動で予測でき、かつ精度や傾向を継続的に評価・改善できる仕組みを構築 する必要性が明確になりました。 課題解決アプローチ 本章では、前章で整理した課題を踏まえ、どのような考え方で評価指標を設計し、データを分析したうえで予測モデルを構築・評価したのかを紹介します。 評価指標の設計 EDA(探索的データ分析) 予測モデルの構築 予測モデルの評価 1.評価指標の設計 まず、モデル構築に先立ち、 どのような予測であれば「良い」と言えるのか を明確にしました。予測精度の良し悪しは評価指標の設計によって大きく左右されるため、ここを最初に定義することが重要です。 今回は、次の理由からMAE(日別の絶対誤差の平均値)を評価指標として採用しました。 誤差を「件数ベース」で把握でき、現場担当者にとって直感的 外れ値の影響を受けにくく、平常日の誤差が素直に評価できる BASEの業務では「何件ズレるか」がそのまま人員計画や作業負荷に影響する MAPEやRMSEといった他の指標もありますが、「日々の作業量を安定的にコントロールする」という目的に対しては、MAEが最も適していると判断しました。 また、BASEの業務では、日別の誤差は小さくても、 予測値が実績値を下回る状態(予測<実績)が連続すると発送遅延につながる重大なリスク となります。そのため、単なる誤差の大小だけでなく、 どの方向に外れたか も含めて評価する方針としました。 2.EDA(探索的データ分析) 評価指標を定義したうえで、次に取り組んだのがEDA(探索的データ分析)です。ここでは、目指す精度水準を達成するうえで、 注文数データの特徴や構造を把握 することを目的としています。EDAの結果、注文数の変動には「曜日による周期性」「セールやキャンペーンなどのイベント影響」「直近の注文動向」といった要因が絡み合っていることが分かりました。 ※本記事のグラフは、構造を説明するための仮想データを用いており、実際の注文データではありません。 時系列データの変動成分 時系列データは、3つの変動成分で構成されています。 トレンド成分 周期成分 残差(ノイズ)成分 変動成分を分解する方法はいくつかあり、代表的なものとしては次のようなものがあります。 移動平均法を利用した分解(加法モデルを仮定) 移動平均法を利用した分解(乗法モデルを仮定) STL分解(Seasonal Decomposition Of Time Series By Loess) 今回は、日次データを1週間周期(period=7)でSTL分解し、各成分を確認しました。STL分解は外れ値に強く、周期性の強さが時期によって変化するようなデータにも柔軟に対応できるためです。 ■トレンド 年間を通じた大きな波が見られ、中長期的な需要変動を確認できます。 ■周期性 曜日による周期性が強く、1週間のリズムが明確に現れています。また、夏から冬に向けて周期成分の振れ幅が徐々に大きくなっている構造も読み取れます。 ■残差(ノイズ) ランダムな揺らぎに加えて、イベント時には大きな変動が生じており、トレンドや周期性だけでは説明できない要素も存在します。 自己相関(ACF)と偏自己相関(PACF) 自己相関(ACF):現在の値と過去の値との相関 偏自己相関(PACF):間接的な影響を除いたうえでの、直接的な相関 例:今日の値が一昨日の値と相関しているように見えても、実際には「一昨日 → 昨日 → 今日」という形で、昨日を介した間接的な影響による相関の場合がある。偏自己相関では、こうした間接的な影響を取り除いた相関を確認できる。 ■自己相関(ACF) 注文数は「同曜日(ラグ7の倍数)」で強い相関が見られます。 ■偏自己相関(PACF) 間接的な影響を除いた場合、「同曜日(ラグ7,14,21,28)」の影響が顕著なため、週次の周期性を明示的に扱えるモデル構造が、予測精度の安定化に重要だと考えられます。 3.予測モデルの構築 予測モデルの構築では、前節までで整理した評価方針とEDAの結果を踏まえ、「どのモデルを採用するか」「どのように学習・検証するか」という2つの観点を中心に検討を進めました。ここでは、実際に行ったモデル選定と学習・検証のアプローチを紹介します。 モデル選定 今回はProphetを採用しました。主な理由は以下のとおりです。 週次や年次の周期性を標準機能として扱える トレンドの変化点を検出できる 休日やイベント情報を組み込みやすい モデル構造がシンプルで、予測根拠を現場担当者へ説明しやすい 1週間先までの複数日予測を、特別な工夫を加えずに自然に扱える これらの特性は、ZOZOの注文数データの構造と業務要件の双方と相性が良いと判断しました。 モデル構築 時系列予測では、「過去データで学習し、未来データで評価する」ことが重要です。ランダム分割すると未来情報が混入し、実運用に近い精度を正しく評価できなくなるため、今回は、学習期間を伸ばしながら評価するエクスパンディング型のクロスバリデーション法を採用しました。具体的には、次の流れでデータを扱っています。 データを時系列順に分割し、前半を学習+検証データ、後半を最終評価用データとして使用 前半データに対して、学習期間を伸ばしながら、「学習 → 直後1週間の予測 → MAE算出」を1年分(52週分)繰り返す 52週分の評価結果を平均し、年間を通した頑健性を評価 この枠組みのもとで、Prophetの性能を最大限引き出すため、Optunaを用いて次のハイパーパラメータを最適化します。 changepoint_prior_scale :トレンド成分の柔軟性 seasonality_prior_scale :季節成分の柔軟性 seasonality_mode :季節成分の種類(加法/乗法) changepoint_range :トレンド変化点候補のレンジ n_changepoints :トレンドの変化点の数 Optunaでは、学習期間を伸ばしながら1年分(52週分)の評価で得られた平均MAEを最適化指標とし、値が最も小さくなるパラメータセットを採用しました。 4.予測モデルの評価 事前に定義した評価方針をもとに、構築したモデルが業務で利用可能かどうかを、テストデータを用いて確認します。BASEの業務では、日別の誤差は小さくても、 予測値が実績値を下回る状態(予測<実績)が連続すると発送遅延につながる重大なリスク となります。そのため、日別の誤差に加えて、次の観点でもモデルを評価しました。 週単位での誤差Σ(日別の誤差)を確認 予測値が実績値を下回る(予測<実績)ケースが少ないモデルを評価 特定曜日・特定イベント期間に偏りや弱点がないか これらの観点をもとに、BASEの担当者と誤差の傾向を確認しながら、「特徴量の調整→再学習→評価」の改善サイクルを繰り返し、日々の運用で活用できる水準へとモデルをブラッシュアップしました。 ここから、次章で紹介する仮運用フェーズへ進みます。 リリース・仮運用 予測モデルが業務で利用できる水準まで仕上がったため、評価時に分割していたテスト期間も含めた全データでモデルを再学習し、日次で予測値を更新できる環境を整えました。こうして、モデルが現場で実際に活用できるかを確かめる仮運用フェーズへ移行しました。本章では、仮運用を通じて得られた気づきと、次の改善に向けた取り組み内容について紹介します。 課題解決への第一歩 仮運用を進める中で、手動予測と比較して日々の誤差のばらつきは小さくなり、予測の安定性が向上していることが確認できました。注文数を「自動で」かつ「一定の精度で」予測できるようになったことで、現場担当者は本来の出荷業務へ集中しやすい環境に近づいています。一方で、仮運用を通じて、予測対象の捉え方まで含めて精度を高めていく必要があることも見えてきました。 仮運用を通じて見えた新たな課題 BASEの業務では、注文が受注順に発送されるとは限りません。日付指定・取り寄せ・予約注文など、お客様からの注文日とBASEからの発送タイミングがずれるケースは多くあります。その結果、次のような日が発生します。 お客様からの注文数は少ないのに、発送すべき注文が多い お客様からの注文数は多いのに、発送すべき注文が少ない こうした特性を踏まえ、BASEでは「お客様から入る注文数」をそのまま扱うのではなく、「お客様へ発送可能な状態の注文数」へ変換したうえで出荷計画や人員配置をしています。仮運用を通じて、「お客様へ発送可能な状態の注文数」へ変換するロジックが従来のままでは、改善効果を十分に引き出せないことが分かってきました。 現場で最終的に必要としている数字は、「何件注文が入るか」ではなく「何件発送できるか」 という視点です。そのため、変換ロジックを含めた一連のプロセスとしての見直しが必要となります。 次の改善フェーズに向けて 前節で見えた新たな課題を踏まえ、現在は次のような取り組みを進めています。 日付指定・取り寄せ・予約注文などの比率分析 注文が「発送可能」になるまでのリードタイム分析 お客様からの注文数予測と発送可能な注文数の統合 お客様から入る注文数の予測モデルの高度化にとどまらず、現場の業務判断にそのまま活用できる形へつなげる仕組みとして、改善を継続しています。 まとめ 本記事では、BASEにおける注文数予測の課題に対して、 「業務で使える予測とは何か」 という観点から、モデル構築プロセスと仮運用を通じて得られた気づきを紹介しました。まだ改善半ばではありますが、従来は手動で行われていた注文数予測をモデル化することで、予測精度のばらつきや属人性といった課題を解消し、日々の注文数を安定して予測できる環境へと近づいています。 今回の取り組みを通じて改めて感じたのは、 データ分析や予測モデルは「高い精度を出すこと」自体が目的ではなく、データに基づいた意思決定を支援して初めて価値を発揮する という点です。予測精度が高くても、業務特性上のリスクが高まってしまったり、そもそも現場が必要としている指標と一致していなければ、実務での効果は限定的になります。事前に課題やビジネス要件を整理し、実務に即した形で改善を重ねていくことが、結果としてお客様価値やビジネスインパクトにつながると考えています。今後もBASEと連携しながら、 現場の意思決定に使える予測 を目指して、継続的に改善を進めていきます。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com 参考文献 Pythonによる時系列分析
.entry .entry-content .table-of-contents > li > ul { display: none; } はじめに こんにちは、EC基盤開発本部SRE部の金田、 花房 、松石です。普段はSREとしてZOZOTOWNのインフラ運用や開発を担当しています。 ZOZOでは gatling-operator をOSSの負荷試験ツールとして公開・運用してきました。しかし、Gatling本体の破壊的変更やメンテナー不足といった課題に直面し、新たな負荷試験ツールとして k6 の導入を進めています。 本記事では、gatling-operatorが抱えていた課題と、k6への移行に至った経緯、そしてClaude Codeを活用した既存シナリオの移行方法についてご紹介します。 目次 はじめに 目次 背景・課題 gatling-operatorとは Gatlingの破壊的変更 Gatling 3.11での変更 Gatling 3.12での変更 メンテナー不足 検討した対応策 なぜk6を採用したのか 要件 k6を採用した理由 導入にあたっての課題と工夫 共通ライブラリの実装 Gatlingのfeeder機能とは k6標準機能の課題 feederライブラリの実装 シナリオ参照方法の検討 採用したアプローチ:SharedVolume & LocalFile Git連携によるシナリオ管理 ディレクトリ構成 initContainerによるシナリオ取得 GitHub認証 Datadogによる試験結果の可視化 k6標準のレポート機能の課題 Datadogダッシュボードの採用 Datadogへのメトリクス送信 OpenTelemetry方式での送信 メトリクスへのタグ付け ダッシュボードの構成と拡張性 Claudeサブエージェントを使ったシナリオ移行 Claudeサブエージェントとは シナリオ移行での活用方法 どのように実現しているか 移行結果と効果 サブエージェントを使う上でのポイント おわりに 背景・課題 gatling-operatorとは gatling-operator は、ZOZOがOSSとして公開している負荷試験ツールです。 Gatling をKubernetes環境で実行するためのKubernetes Operatorとして実装されており、分散負荷試験の実行やレポートの自動生成などの機能を提供しています。 Gatlingの破壊的変更 gatling-operatorの運用を続ける中で、Gatling本体のバージョンアップに伴う破壊的変更が大きな課題となりました。 Gatling 3.11での変更 Gatling 3.11では、これまで提供されていた gatling.sh というGatlingを実行するためのシェルスクリプトが削除されました。代わりに、MavenやGradleといったJavaのビルドツールを使用することが必須となっています。 gatling-operatorは gatling.sh を実行することを前提に実装されていたため、この変更に対応するには大幅な修正が必要となりました。また、シナリオやリソースファイルを配置するディレクトリ構成も、ビルドツールを使った実行では基本的に固定されるようになり、後方互換の問題も生じます。 Gatling 3.12での変更 Gatling 3.12では、複数のログファイルをまとめてレポートを作成する機能が削除されました。gatling-operatorは分散実行した各Podのログファイルを集約してレポートを生成する仕組みを採用していたため、この変更は致命的な影響を与えます。 なお、この機能はEnterprise版では引き続き利用可能とのことです。しかし、gatling-operatorはOSS版のGatlingを前提に設計されており、Enterprise版を利用する設計への変更は困難です。 メンテナー不足 gatling-operatorのメンテナーは現状2名体制で運用しています。Gatlingの破壊的変更に追随するための改修工数を考えると、この体制では十分な対応が困難な状況でした。 検討した対応策 これらの課題に対して、以下の3つの対応策を検討しました。 対応策 メリット デメリット gatling-operatorの改修 既存シナリオ資産がそのまま使える 改修規模が大きく、将来性に懸念 Gatling Enterprise版への移行 メンテナンスコスト削減 金銭的な追加コストが発生 他負荷試験ツールへの移行 メンテナンスコスト削減、OSSなら追加コストなし 既存シナリオ資産の移行が必要 検討の結果、OSSとして広く利用されており、Kubernetes環境での実行もサポートされている k6 への移行を選択しました。既存シナリオの移行については、生成AIを活用することで工数を削減できると判断しました。 なぜk6を採用したのか 要件 gatling-operatorのメンテナーが不在なことや育成コストの高さから、新たな負荷試験ツールを模索しました。OSSとしてのコミュニティの大きさやKubernetesで運用する既存基盤に導入できる形が条件です。Gatlingから他の負荷試験ツールに移行するにあたって、以下の3つの要件がありました。 メンテナビリティの高さ : なるべくメンテナンスコストを抑えるために、自社で独自のツールを作成しなくても良いOSSの採用を検討。 負荷試験結果をレポートとして残せるか : Gatlingを用いた負荷試験では、Gatlingレポートが出力されるため、ZOZOではエビデンスとしてそのレポートを使用していた。そのため、移行先のツールでも結果を見られるというのは重要。 シナリオ作成の容易さ : ZOZOでは、要件によるがバックエンドとSREがシナリオを作成する。そのため、シナリオ作成の際に学習コストやレビューコストがなるべく低い言語を採用する必要がある。 k6を採用した理由 移行先を検討する際、以下のツールが候補に上がりましたが、後述する理由からk6への導入が決まりました。 Locust k6 ZOZOでは、事業部単位でSREが存在しており、負荷試験基盤を持っています。その1つとしてWEARチームの負荷試験基盤でk6の導入実績があったため、社内ナレッジを活用できることは大きなメリットでした。 techblog.zozo.com また、k6はGrafana製のOSSであり、Kubernetes上でk6を動作させるための k6-operator がすでにあります。さらに xk6 を使用することで、カスタムビルドし簡単に拡張機能などを導入できます。k6-operator、xk6などもGrafana社が作成している公式のOSSであり、メンテナンスを自社で担わなくて良いのは大きなメリットでした。k6-operatorはCRDを導入しており、マニフェストファイルを適用するだけで負荷試験が簡単に実行でき、既存のgatling-operatorと変わらずに運用できる点も魅力です。 さらに、シナリオ作成をする際にJavaScriptで記述できるのもメリットでした。負荷試験シナリオでは、複雑な処理は実装しないため、レビューや学習のコストを抑えられる点は魅力的でした。 以上の理由から、最終的にはk6をPoC的に導入することが決定しました。また実際にk6を検証する中で、足りない機能などは別途、検証・実装・議論をしつつ導入していきました。 導入にあたっての課題と工夫 共通ライブラリの実装 k6への移行を進める中で、Gatlingには存在するものの、k6には標準で用意されていない機能があることに気づきました。特に問題となったのが feeder 機能です。 Gatlingのfeeder機能とは Gatlingのfeederは、CSVファイルなどの外部データソースからテストデータを読み込み、各仮想ユーザーに順次または循環的にデータを供給する仕組みです。例えば、ログインシナリオで複数のユーザー認証情報を使い回す場合などに重宝します。 // Gatlingでのfeeder使用例 val feeder = csv( "users.csv" ).circular val scn = scenario( "Login" ) .feed(feeder) .exec(http( "login" ).post( "/auth" ).body(StringBody( """{" email ":" ${email} "," password ":" ${password} "}""" ))) ZOZOの負荷試験シナリオでは、このfeeder機能が多くのシナリオで使用されていました。ユーザー認証情報や商品ID、セッション情報など、様々なテストデータをfeederで管理していたため、k6への移行にあたって同等の機能が必須でした。 k6標準機能の課題 k6にはCSV読み込み機能はあるものの、Gatlingのfeederのような順次供給・循環利用の機能は標準で提供されていません。 // k6標準のデータ読み込み(feederのような機能はない) import { SharedArray } from 'k6/data' ; const data = new SharedArray ( 'users' , function () { return JSON . parse ( open ( './users.json' )) ; }) ; この方法では、どのVUがどのデータを使用するかの制御や、データの循環供給を自前で実装する必要がありました。 feederライブラリの実装 そこで、Gatlingのfeeder機能を再現する共通ライブラリを実装しました。このライブラリは k6_scenarios/common_lib/feeder/ ディレクトリに配置され、以下の機能を提供します。 // feederライブラリの使用例 import { csv } from '../common_lib/feeder/feeder.js' ; // CSVデータを循環的に供給するfeederを作成 const userFeeder = csv ( open ( './users.csv' )) . circular () ; export default function () { // 各VUの実行ごとに次のデータを取得 const userData = userFeeder . next () ; http . post ( ` ${ ENDPOINT } /auth/login` , JSON . stringify ({ email : userData . email , password : userData . password })) ; } 実装したfeederライブラリは、以下の供給戦略をサポートしています。 供給戦略 説明 Gatling相当 circular() データを循環的に供給(末尾に達したら先頭に戻る) csv().circular random() ランダムにデータを選択 csv().random queue() 順次供給(データが尽きたらエラー) csv().queue また、配列データや重み付きデータにも対応しています。 import { csv , array , weighted } from '../common_lib/feeder/feeder.js' ; // 配列からfeederを作成 const itemFeeder = array ([ 'item1' , 'item2' , 'item3' ]) . circular () ; // 重み付きでデータを選択 const scenarioFeeder = weighted ([ { weight : 70 , data : { type : 'browse' } } , { weight : 20 , data : { type : 'search' } } , { weight : 10 , data : { type : 'purchase' } } ]) ; このライブラリを実装したことで、既存のGatlingシナリオで使用していたfeeder機能をk6でも同様に利用できるようになりました。シナリオ移行の際も、feederの使用パターンを変換ルールとして定義することで、スムーズな移行が可能になっています。 シナリオ参照方法の検討 k6-operatorでは、シナリオファイルをRunner Podにマウントする方法として以下の3つが提供されています。 方法 概要 メリット デメリット ConfigMap シナリオをConfigMapとして登録 シンプルで手軽 容量制限あり(約1MB)、複数ファイル不向き VolumeClaim PVCにシナリオを配置 ファイルサイズ制限なし、複数ファイル対応 構成が複雑 LocalFile カスタムイメージにシナリオを内包 再現性が高い、外部依存なし 軽微な修正でも再ビルドが必要 gatling-operatorでは、シナリオを含んだコンテナイメージをビルドして実行する方式を採用していました。これはLocalFileに相当する方法です。しかし、シナリオを修正するたびにコンテナイメージのビルドとプッシュが必要となり、開発サイクルが遅くなりがちという課題がありました。 k6への移行にあたり、この開発サイクルを改善したいという狙いもあり、シナリオ参照方法を見直すことにしました。 採用したアプローチ:SharedVolume & LocalFile 検討の結果、公式ドキュメントで紹介されている3つの方法ではなく、VolumeClaimとLocalFileのハイブリッドなアプローチを採用しました。 このアプローチでは、PVC(PersistentVolumeClaim)は使用せず、Podの共有ボリューム(emptyDir)とinitContainerを組み合わせます。initContainerでGitリポジトリからシナリオファイルをクローンし、Runner Containerが共有ボリューム経由でそのファイルを参照する仕組みです。 このアプローチには以下のメリットがあります。 PVC不要でシンプル : VolumeClaimのような永続ボリュームの管理が不要で、マニフェスト管理やシステム構成がシンプルになる 自動クリーンアップ : 共有ボリュームはPod削除時にデータも削除されるため、後片付けが不要 開発サイクルの改善 : シナリオの修正はコミット&プッシュするだけで反映されるため、コンテナイメージのビルドが不要になる ブランチ指定による柔軟性 : 開発中のシナリオも開発ブランチを指定することで、すぐに試験可能 一方で、parallelismを設定してRunner Podが複数になる場合は、Pod分だけGit cloneが実行される点には注意が必要です。ただし、sparse-checkoutを使用して必要なディレクトリのみをクローンすることで、この影響を最小限に抑えています。 Git連携によるシナリオ管理 k6のシナリオファイルは、既存のgatling用シナリオと同様にGitHubリポジトリで管理しています。TestRunリソースのinitContainerで git-sync を使用し、テスト実行時にリポジトリからシナリオファイルをクローンする仕組みを実装しました。 ディレクトリ構成 zozo-benchmark/ └── k6_scenarios/ ├── common_lib/ # 共通ライブラリ │ ├── feeder/ # テストデータ読み込み用 │ └── metrics/ # メトリクス処理用 ├── sample/ # シナリオディレクトリ │ ├── testrun.yaml # TestRunマニフェスト │ └── test.js # シナリオファイル └── resources/ # テストデータ └── sample/ └── data.csv initContainerによるシナリオ取得 TestRunリソースでは、テスト実行前にinitContainerが2段階の処理を行います。 prepare-sparse-checkout : sparse-checkoutのパターンファイルを作成し、必要なディレクトリのみをクローン対象として指定 clone-data : git-syncを使用してGitHubリポジトリから指定ブランチのシナリオファイルをクローン apiVersion : k6.io/v1alpha1 kind : TestRun metadata : name : k6-sample spec : parallelism : 2 arguments : --out experimental-opentelemetry runner : volumes : - name : git-sync-config emptyDir : {} - name : scenario-data emptyDir : {} volumeMounts : - name : scenario-data mountPath : /test initContainers : - name : prepare-sparse-checkout image : busybox:1.36 command : [ "/bin/sh" , "-c" ] env : - name : TARGET_SCENARIO value : scenario_samples/simple args : - | cat <<EOF > /git-sync-config/sparse-checkout-patterns k6_scenarios/common_lib/ k6_scenarios/resources/ k6_scenarios/${TARGET_SCENARIO}/ EOF volumeMounts : - name : git-sync-config mountPath : /git-sync-config - name : clone-data image : registry.k8s.io/git-sync/git-sync:v4.4.2 args : - --repo=https://<シナリオ格納リポジトリ>.git - --link=zozo-benchmark - --depth=1 - --root=/test - --one-time - --sparse-checkout-file=/git-sync-config/sparse-checkout-patterns envFrom : - secretRef : name : k6-github-app-secrets-xxxx env : - name : GITSYNC_REF value : main # クローンするブランチを指定 volumeMounts : - name : git-sync-config mountPath : /git-sync-config readOnly : true - name : scenario-data mountPath : /test script : localFile : /test/zozo-benchmark/k6_scenarios/scenario_samples/simple/test.js この仕組みにより、シナリオファイルの修正をコミット・プッシュするだけで、次回の試験実行時に最新のシナリオが反映されます。 GITSYNC_REF でブランチを指定できるため、開発中のシナリオも開発ブランチを指定することですぐに試験できます。 GitHub認証 プライベートリポジトリからのクローンには、GitHub Appsによる認証を使用しています。 認証情報の管理には、AWS Secrets ManagerとExternal Secretsを組み合わせています。GitHub Appsの認証情報をSecrets Managerに保存し、External Secrets経由でKubernetes Secretとして読み込みます。 apiVersion : external-secrets.io/v1beta1 kind : ExternalSecret metadata : name : k6-github-app-secrets-xxxx spec : refreshInterval : "1h" secretStoreRef : name : cluster-secretstore kind : ClusterSecretStore data : - secretKey : GITSYNC_GIT_CREDENTIAL remoteRef : key : k6/github-app - secretKey : GITSYNC_GIT_CREDENTIAL_PRIVATEKEY remoteRef : key : k6/github-app-privatekey clone-dataコンテナでは、このSecretを envFrom で参照しています。 - name : clone-data image : registry.k8s.io/git-sync/git-sync:v4.4.2 envFrom : - secretRef : name : k6-github-app-secrets-xxxx git-syncはこれらの環境変数を自動的に読み取り、GitHub Appsで認証してプライベートリポジトリへのアクセスを実現しています。 Datadogによる試験結果の可視化 Gatlingからk6への移行にあたり、試験結果を可視化・保存する方法についても検討しました。負荷試験基盤を利用するエンジニアの移行ハードルを上げないよう、また、利用体験を損なわずに試験結果をこれまで通りエビデンスとして活用できるよう、以下の要件を満たす必要がありました。 Gatlingレポートと同等のメトリクスや統計情報を可視化できること 試験結果を一定の期間保存し、後から確認・比較できること 検討の結果、k6のメトリクスをDatadogに送信し、独自のダッシュボードで試験結果を可視化する方式を採用しました。以降では、Datadogダッシュボードを採用した理由と構築方法について説明します。 k6標準のレポート機能の課題 gatling-operatorでは、Gatlingにより出力されたHTMLレポートをAWS S3に保存する機能が存在し、利用者はS3上のファイルにアクセスすることで試験結果を確認できました。k6でも同様の方式で試験結果を保存・確認できないか検討しました。 k6にはHTMLレポートを出力する標準機能である handleSummary 関数や、よりリッチなHTMLレポートを出力できる benc-uk/k6-reporter が存在します。しかし、クラウドストレージにレポートを保存する仕組みは存在せず、コストをかけて作り込む必要があったため、この方式は採用しませんでした。 JSON/CSV出力機能を利用する案も検討しましたが、可視化の作り込みにコストがかかり、また上記と同様にレポートを保存する仕組みを構築する必要があったため、こちらも採用しませんでした。 Datadogダッシュボードの採用 他の案も比較検討しましたが、普段から監視ツールとしてDatadogを使用していることから、k6のメトリクスを利用した独自のDatadogダッシュボードを作成する方式を最終的に採用しました。 Datadogなら以下のようにそれぞれの要件を満たせました。 ダッシュボード機能により試験結果を可視化できること Datadogのメトリクスは長期間保存され 1 、タグ付けにより試験ごとの絞り込みと比較が可能なこと 既存のDatadog環境を活用できるため、以下のメリットがありました。 低い導入コスト :負荷試験基盤にはdatadog-agentを導入済みのためk6のメトリクス収集が容易 統合的な分析 :Datadog上で試験結果と試験中のトレースやカスタムメトリクスを紐付けて確認できる 利用者の認知負荷や学習コストの軽減 :使い慣れたダッシュボード機能で柔軟に試験結果を可視化できる Datadogへのメトリクス送信 k6の試験結果をDatadogダッシュボードで可視化するには、k6のメトリクスをDatadogに送信する必要があります。Datadogへのメトリクス送信には、負荷試験基盤に導入済みのdatadog-agentを活用しました。また、試験結果を区別できるようにメトリクスにタグを付与する工夫も行いました。 OpenTelemetry方式での送信 k6からDatadogにメトリクスを送信する方法として、 StatsD方式 と OpenTelemetry方式 が存在します。StatsD方式は拡張機能を含んだコンテナイメージをビルドする必要があったため、Kubernetesマニフェストへの設定追加のみで済むOpenTelemetry方式を採用しました。 以下の手順でk6からDatadogへのメトリクス送信を実現しました。 公式ドキュメント に従いdatadog-agentのOTLPメトリクス受信を有効化 k6のメトリクス送信設定をConfigMapとして作成 TestRunにConfigMap参照と --out experimental-opentelemetry 引数を追加 ConfigMapとTestRun設定例はそれぞれ以下の通りです。 apiVersion : v1 kind : ConfigMap metadata : name : k6-system-config data : K6_OTEL_EXPORTER_TYPE : "grpc" K6_OTEL_GRPC_EXPORTER_ENDPOINT : "datadog-agent.kube-support.svc.cluster.local:4317" K6_OTEL_SERVICE_NAME : "k6" K6_OTEL_METRIC_PREFIX : "k6_" K6_OTEL_GRPC_EXPORTER_INSECURE : "true" apiVersion : k6.io/v1alpha1 kind : TestRun metadata : name : k6-metrics-sample spec : arguments : --out experimental-opentelemetry runner : envFrom : - configMapRef : name : k6-system-config メトリクスへのタグ付け Datadogダッシュボードで試験結果を区別するにはメトリクスにタグを付与しておく必要があります。負荷試験基盤で統一的なタグを付与できるように、共通ライブラリとして performRequest というHTTPリクエストのラッパー関数を実装しました。 以下が実装した performRequest です。 commonTags はダッシュボードでの試験結果の絞り込みに利用する共通タグです。また、詳細分析で利用できるリクエスト固有のタグも付与しています。 import { check } from 'k6' ; import { commonTags } from './tags.js' ; export function performRequest ( requestName , requestFunc , expectedStatus = 200 ) { try { const estimatedPath = `/ ${ requestName } ` ; const preRequestTags = { ... commonTags , request_name : requestName , path : estimatedPath , endpoint : requestName } ; const response = requestFunc ( preRequestTags ) ; const actualPath = getPathFromUrl ( response . url ) || estimatedPath ; const statusCategory = getStatusCategory ( response . status ) ; const checkResult = check ( response , { [ ` ${ requestName } : status is ${ expectedStatus } ` ] : ( r ) => r . status === expectedStatus , [ ` ${ requestName } : response time < 5000ms` ] : ( r ) => r . timings . duration < 5000 , } , { ... commonTags , request_name : requestName , path : actualPath , status_code : response . status . toString () , status_category : statusCategory , endpoint : requestName }) ; const isSuccess = response . status === expectedStatus ; if ( ! isSuccess ) { console . error ( `✗ ${ requestName } : ${ response . status } - ${ actualPath } ` ) ; } return response ; } catch ( error ) { console . error ( ` ${ requestName } error: ${ error } ` ) ; throw error ; } } commonTags の内容は以下の通りです。 export const commonTags = { test_run_id : __ENV . K6_TEST_RUN_ID || console . warn ( 'K6_TEST_RUN_ID not set. Use environment variable for consistent ID across pods.' ) || `test-run- ${ new Date () . toISOString () . slice ( 0 , 16 ) . replace (/ [ T: ] / g , '-' )} ` , env : __ENV . K6_ENV || 'stg' , scenario : __ENV . K6_SCENARIO || 'default' , team : __ENV . K6_TEAM || 'default' , } ; performRequest を利用するには以下の2つのステップが必要です。 TestRunでの環境変数の設定 シナリオファイル内のリクエスト処理を performRequest を使用するように書き変える TestRunの環境変数とシナリオファイルの記述例はそれぞれ以下の通りです。 env : - name : K6_TEST_RUN_ID value : "member-api-stress-test-20251209" - name : K6_ENV value : "stg" - name : K6_SCENARIO value : "member-api-create-and-update-user" - name : K6_TEAM value : "member-api-team" import { performRequest } from '../common_lib/metrics/metrics.js' ; import http from 'k6/http' ; export default function () { performRequest ( 'create_user' , ( tags ) => { return http . post ( 'https://api.example.com/users' , JSON . stringify ({ name : 'Test User' , email : 'test@example.com' }) , { headers : { 'Content-Type' : 'application/json' } , tags }) ; } , 200 ) ; performRequest ( 'update_user' , ( tags ) => { return http . put ( 'https://api.example.com/users/123' , JSON . stringify ({ name : 'Updated User' }) , { headers : { 'Content-Type' : 'application/json' } , tags }) ; } , 200 ) } ; ダッシュボードの構成と拡張性 以下の画像は今回作成したDatadogダッシュボードのメイン部分です。 4つのタグにより試験結果を絞り込むことができます。 タグ名 説明 test_run_id テスト実行の識別子 scenario テストシナリオ名 team 実行チーム名 env テスト対象環境 各ウィジェットはGatlingレポートの項目を参考にしつつ、k6固有のメトリクスも確認できる構成にしました。Gatlingレポートと同等以上の試験結果の可視化を実現できたと思います。 さらに、従来のGatlingレポートが固定形式だったのに対し、Datadogダッシュボードでは以下のような柔軟な拡張が可能です。 カスタムメトリクスを組み合わせた分析 試験内容や目的に応じたダッシュボードのカスタマイズ チームごとの独自ダッシュボード作成と共有 Claudeサブエージェントを使ったシナリオ移行 k6を導入した後の課題として、Scalaで記述された既存シナリオをどのように移行していくかがありました。Gatlingシナリオが約130個もあるため、全てを手動でk6用に移行するのは困難でした。 さらに、シナリオ移行を実現するにあたって、共通のコンテキストを用いて移行することは必須でした。具体的には以下のようなルールに準拠する必要がありました。 feeder機能やDatadogにタグ付きメトリクスを送信するための共通ライブラリを使用すること シナリオをGit連携で使えるようにする一方、秘匿情報(ログイン・パスワードなど)が含まれたCSVファイルはAWS Secrets Managerから取得すること これらの要件から、Claudeサブエージェントを用いたシナリオ移行を検証しました。 Claudeサブエージェントとは Claude Codeの機能で、より専門的なタスクに用いることができ、コンテキスト管理を容易にすることが可能となります。より詳細な内容は、公式ドキュメントをご覧ください。 code.claude.com シナリオ移行での活用方法 シナリオ移行にあたって、以下のような使用方法を検証しました。まず claude コマンドでClaude Codeを起動します。 $ claude Claude Codeの起動後、サブエージェントを使用してGatlingシナリオをk6に移行したい旨の指示を送信します。その後、サブエージェントからの質問に対して回答します。以下の具体例では、 5. Type something. に対象のシナリオパス( simulations/ZozoSessionApiBasicLoadTest.scala )を入力しています。 > sub-agentを使用してGatling(Scala)シナリオをk6に移行したい ⏺ 了解しました。Gatling(Scala)シナリオをk6に移行するためのサブエージェントを起動します。 まず、移行対象のGatlingシナリオを特定するため、現在のリポジトリ構造を確認させてください。 ⏺ 多数のGatlingシナリオがあります。どのシナリオを移行したいか確認させてください。 ☐ 移行対象 どのGatlingシナリオをk6に移行しますか?特定のシナリオを指定するか、移行対象のカテゴリを教えてください。 ❯ 1. member-id-pf-sre関連 zozo-id, zozo-session-api, zozo-member-api等のシナリオ 2. 特定のファイルを指定 ファイル名またはパスで個別に指定 3. すべてのシナリオ simulations/配下の全シナリオを順次移行 4. その他 カスタムで指定 5. Type something. Enter to select · Tab/Arrow keys to navigate · Esc to cancel サブエージェントが起動し、移行処理が実行されます。 ⏺ simulations/ZozoSessionApiBasicLoadTest.scalaをk6に移行します。k6-scenario-migratorエージェントを起動します。 ⏺ k6-scenario-migrator(Migrate ZozoSessionApi to k6) ⎿ +13 more tool uses (ctrl+o to expand) · Sketching… (esc to interrupt · 2m 38s · ↓ 440 tokens) 以下が移行結果です。 ⏺ 移行完了 🎉 simulations/ZozoSessionApiBasicLoadTest.scala を k6 に正常に移行しました。 作成されたファイル | ファイル | パス | |------------|-----------------------------------------------------------------------------------| | k6シナリオ | k6_scenarios/scenario_member_id_sre/zozo_session_api/basic_load_test/test.js | | TestRun CR | k6_scenarios/scenario_member_id_sre/zozo_session_api/basic_load_test/testrun.yaml | シナリオ概要 Webシナリオ (ZOZO_SESSION_PLATFORM=web): 1. Init Session × 3回 2. Save Session × 3回 3. Get Session × 57回 Appシナリオ (ZOZO_SESSION_PLATFORM=app): 1. Init Session × 1回 2. Get Session × 100回 Gatling → k6 変換ポイント | Gatling | k6 | |-----------------------|--------------------------------| | constantUsersPerSec | constant-arrival-rate executor | | check(status.is(200)) | performRequest() + thresholds | | jsonPath().saveAs() | JSON.parse() + 変数保存 | 実行方法 kubectl apply -f k6_scenarios/scenario_member_id_sre/zozo_session_api/basic_load_test/testrun.yaml どのように実現しているか 以下のようなディレクトリ構造で、サブエージェントを作成しています。 .claude ├── agents │ └── k6-scenario-migrator.md # サブエージェントに対する指示やチェックリストを記載 ├── docs │ └── scala-to-k6-migration-guide.md # 変換ガイドライン(具体的なライブラリの使い方や変換ルールを記載) └── settings.local.json 前提として、それぞれのプロンプトはコンフルエンス上で整備した社内向けのk6ユーザーガイドラインをもとにしています。このユーザーガイドラインをコンテキストとして、サブエージェント用のプロンプトを作成しました。サブエージェント(.claude/agents/k6-scenario-migrator.md)には「何をすべきか」を指示しています。変換ガイドライン(.claude/docs/scala-to-k6-migration-guide.md)には「どう変換すべきか」の詳細ルールを記載しています。 シナリオ移行の品質を担保するため、サブエージェントでは以下の4つの観点でプロンプトを設計しました。 まず、事前質問による情報収集です。エージェント起動時にチーム名・マイクロサービス名・対象ファイルを確認させることで、後続の処理に必要な情報を漏れなく取得します。 #### 質問1: チーム名の確認 このシナリオを管轄するチーム名を教えてください。 例: member-id-pf-sre, cart-sre, search-sre #### 質問2: マイクロサービス名の確認 対象となるマイクロサービス名を教えてください。 例: zozo-id, zozo-session-api, zozo-member-api 注意: 「-api」サフィックスは除去し、「-」は「_」に変換します。 #### 質問3: Scalaシナリオファイルパスの確認 移行元のScalaシナリオファイルのパスを教えてください。 例: simulations/ZozoIdLogin.scala 次に、命名規則の明確化です。ディレクトリ名やシナリオ名の移行ルールを明示することで、一貫性のあるディレクトリ、ファイルの作成を実現しています。 #### マイクロサービス名からディレクトリ名を生成 1. 「-api」サフィックスを除去 - zozo-session-api → zozo-session 2. ハイフン「-」をアンダースコア「_」に変換 - zozo-session → zozo_session #### Scalaファイル名からシナリオ名を生成 1. キャメルケースをスネークケースに変換(ディレクトリ名用) - ZozoIdLogin → zozo_id_login 2. 環境変数: K6_SCENARIOはハイフン繋ぎに統一 - zozo_id_login → zozo-id-login - 理由: Datadogメトリクスで一貫性を保つため #### 最終的なディレクトリパス k6_scenarios/scenario_<チーム名 > /<マイクロサービス名 > / < scenario _name> / 続いて、チェックリストによる品質担保です。移行完了後に確認すべき項目をリスト化し、共通ライブラリの使用やDatadogタグの設定漏れを防いでいます。 - [ ] ` performRequest ` 関数を使用している - [ ] ` tags ` パラメータをすべてのHTTPリクエストに渡している - [ ] feederライブラリ( ` csv ` , ` array ` など)を適切に使用している - [ ] 環境変数にデフォルト値を設定している - [ ] K6 _ TEST _ RUN _ ID, K6 _ ENV, K6 _ SCENARIO, K6 _ TEAMを設定している - [ ] testrun.yamlの ` K6_SCENARIO ` とtest.jsの ` options.scenarios ` のキー名が同じ(ハイフン繋ぎ)になっている - [ ] testrun.yamlに ` cleanup: post ` を設定している(テスト終了後にPodを自動削除するため) - [ ] CSVデータに秘匿情報が含まれるかどうかを判断している - [ ] 秘匿情報を含むCSVの場合、External Secretsで管理している - [ ] 秘匿情報を含むCSVの場合、ExternalSecretマニフェスト(secrets.yaml)を作成している - [ ] 秘匿情報を含むCSVの場合、AWS Secrets ManagerにCSV形式のプレーンテキストデータを登録する必要があることをユーザーに伝えている - [ ] 秘匿情報を含まないCSVの場合、 ` k6_scenarios/resources/scenario_<チーム名>/ ` 配下に配置している - [ ] CSVデータをExternal Secretsから読み込む場合、適切にマウント・読み込み処理を実装している - [ ] CSVデータをローカルファイルから読み込む場合、正しい相対パスを使用している - [ ] CSVデータ読み込み時のエラーハンドリングが実装されている - [ ] ディレクトリ構成が ` scenario_<チーム名>/<マイクロサービス名>/<scenario_name> ` に準拠している - [ ] 相対パスでのインポートが正しい( ` ../../../common_lib/... ` ) - [ ] testrun.yamlが作成されている(**必須**) - [ ] testrun.yamlのlocalFileパスが正しい - [ ] testrun.yamlのTARGET _ SCENARIOパスが正しい - [ ] testrun.yamlのmetadata.nameが適切に設定されている - [ ] 存在しない・不要なimportは行わない - [ ] ex ` import { open } from 'k6'; ` 次に、セキュリティを考慮した分岐です。CSVデータの性質(秘匿情報の有無)に応じて処理を分岐させ、機密情報をリポジトリへ含めないようにしています。 #### パターン1: 秘匿情報を含むCSV(External Secrets使用) 対象: ログイン情報、APIトークンなどの秘匿情報 → AWS Secrets Managerに登録し、External Secretsでマウント #### パターン2: 秘匿情報を含まないCSV(ローカルファイル) 対象: 商品一覧、カテゴリ情報などの公開データ → k6 _ scenarios/resources/scenario_<チーム名 > / に配置 最後に、入力例と出力例の記載です。SREに加え開発者がこのシナリオ移行を実施する可能性があるため、移行後の動作方法などを具体的に明記し、すぐに動作確認を実施できる出力を目指しました。 ## 例 ### 入力例 ``` チーム名: member-id-pf-sre マイクロサービス名: zozo-session-api Scalaファイル: simulations/ZozoSessionProxyTest.scala ``` ### 出力例 ``` ✓ k6シナリオの生成が完了しました 生成されたファイル: - k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/test.js - k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/testrun.yaml - k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/README.md ディレクトリ構造: k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/ ├── test.js ├── testrun.yaml └── README.md 実行方法(Kubernetes): # TestRunの適用 kubectl apply -f k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/testrun.yaml # ログ確認 kubectl logs -l k6_cr=zozo-session-proxy-test --tail=100 -f # TestRunの削除 kubectl delete testrun zozo-session-proxy-test 注意: ローカル実行(k6コマンド)は使用しません。必ずKubernetes上で実行してください。 ``` 以上が、サブエージェントに記載している主なプロンプトの内容です。 サブエージェントで使用する変換ガイドラインは、社内用のk6使用方法ドキュメントを落とし込む形で作成しています。内容としては、具体的なライブラリの使用例やシナリオを移行する際の具体的なBefore/Afterのソースコードを記載しています。 下記に、ガイドラインとなるプロンプトの全文を記載します。 プロンプトの全文 # Gatling (Scala) から k6 (JavaScript) へのシナリオ移行プロンプト ## 概要 このドキュメントは、既存のGatling(Scala)で記述された負荷試験シナリオを、k6(JavaScript)に移行するための生成AIプロンプトです。 zozo-benchmarkリポジトリの共通ライブラリを活用し、効率的かつ標準化されたk6シナリオを生成します。 ## 前提条件 - zozo-benchmarkリポジトリの既存実装を参照すること - k6-operatorのTestRun CRDを使用したKubernetes環境での実行を想定 - 共通ライブラリ(feeder, metrics)を活用すること - External Secretsでシークレット管理を行うこと --- ## 移行プロンプト 以下のプロンプトを生成AIに入力してください。Scalaシナリオを添付することで、k6シナリオへ変換されます。 ``` # タスク 以下のGatling(Scala)シナリオをk6(JavaScript)シナリオに変換してください。 zozo-benchmarkリポジトリの標準に従い、共通ライブラリとベストプラクティスを適用してください。 --- # 変換ルール ## 1. 基本構造 ### 環境変数の取得 **Scala (Before)** ```scala val endpoint = sys.env.getOrElse("ENDPOINT", "xxxx") val clientToken = sys.env.getOrElse("CLIENT_TOKEN", "dummy123456789") val usersPerSec = sys.env.getOrElse("CONCURRENCY", "1").toInt val durationSec = sys.env.getOrElse("DURATION", "10").toInt ``` **JavaScript (After)** ```javascript // オプションで使用する環境変数には必ずデフォルト値を設定 const ENDPOINT = __ENV.ENDPOINT || 'xxxx'; const CLIENT_TOKEN = __ENV.CLIENT_TOKEN || 'dummy123456789'; const VUS = __ENV.VUS ? parseInt(__ENV.VUS) : 1; const RATE = __ENV.RATE ? parseInt(__ENV.RATE) : 1; const DURATION = __ENV.DURATION || '10s'; ``` ## 2. HTTPプロトコル設定とヘッダー ### HTTPプロトコルとベースURL **Scala (Before)** ```scala val httpProtocol = http .baseUrl(endpoint) .userAgentHeader("Mozilla/5.0...") .shareConnections val requestHeaders = Map( "Content-Type" -> "application/json;charset=UTF-8", "Accept-Encoding" -> "gzip, deflate", "accept" -> "application/json", "zozo-api-client-token" -> clientToken ) ``` **JavaScript (After)** ```javascript // k6ではベースURLをオプションとして設定せず、リクエストごとにフルURLを指定 // 共通ヘッダーは変数として定義し、リクエスト時に使用 const commonHeaders = { 'Content-Type': 'application/json;charset=UTF-8', 'Accept-Encoding': 'gzip, deflate', 'Accept': 'application/json', 'zozo-api-client-token': CLIENT_TOKEN, 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0' }; ``` ## 3. フィーダー(テストデータ) ### CSVデータの読み込みパターン CSVデータの読み込み方法は、**データの性質によって2つのパターン**に分かれます: #### パターン1: 秘匿情報を含むCSV(External Secrets使用) **対象データ:** - ログイン情報(ユーザー名、パスワード) - APIトークン、アクセスキー - その他の認証情報 **重要:** 秘匿情報を含むCSVファイルは、**リポジトリに含めず**、External Secretsを使用してAWS Secrets Managerから読み込みます。 **手順:** 1. **AWS Secrets ManagerにCSVデータを登録** ``` # AWS Secrets ManagerにプレーンテキストとしてCSV内容を保存 # キー: zozo-id/login_info # 値: CSV形式のプレーンテキスト user_id,password test_user_1,password123 test_user_2,password456 ``` 2. **ExternalSecretマニフェストを作成** ( ` secrets.yaml ` ) ```yaml apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: zozo-id-login-info-secrets-xxxx namespace: default spec: refreshInterval: "0" secretStoreRef: name: cluster-secretstore kind: ClusterSecretStore data: - secretKey: login_info_zozo_id remoteRef: key: zozo-id/login_info ``` 3. **testrun.yamlでSecretをマウント** ```yaml spec: runner: volumes: - name: login-secrets secret: secretName: zozo-id-login-info-secrets-xxxx volumeMounts: - name: login-secrets mountPath: /secrets readOnly: true ``` 4. **test.jsでSecretから読み込み** ```javascript import { csv } from '../../../common_lib/feeder/feeder.js'; import { open } from 'k6'; // Load login credentials from Secret (mounted at /secrets/login_info_zozo_id) let loginFeeder; try { const secretData = open('/secrets/login_info_zozo_id'); if (!secretData || secretData.trim() === '') { throw new Error('Secret file is empty'); } loginFeeder = csv(secretData, {}, 'login-info'); console.log('[INFO] Loaded login info from Secret: /secrets/login_info_zozo_id'); } catch (error) { console.error('[ERROR] Failed to load login info from Secret:', error.message); console.error('[DEBUG] Check: kubectl get secret <secret-name> -o yaml'); throw new Error('Login info data source not available. Secret is required.'); } // default関数内で使用 export default function () { const userData = loginFeeder.circular(); // userData.user_id, userData.password などが利用可能 } ``` **参照実装:** - ExternalSecretマニフェスト: ` k6_scenarios/scenario_member_id_sre/zozo_id/secrets.yaml ` - test.js実装: ` k6_scenarios/scenario_member_id_sre/zozo_id/login_scenario/test.js ` (44-55行目) --- #### パターン2: 秘匿情報を含まないCSV(ローカルファイル) **対象データ:** - 商品一覧 - カテゴリ情報 - テスト用の公開データ **配置場所:** ``` k6_scenarios/resources/scenario_<team_name>/ └── *.csv # 例: products.csv, categories.csv など # チーム別の例 k6_scenarios/resources/scenario_member_id_sre/ ├── products.csv └── categories.csv k6_scenarios/resources/scenario_cart_sre/ ├── cart_items.csv └── user_sessions.csv ``` **注意:** - 基本は ` scenario_<team_name>/ ` 直下に配置 - 整理のため、さらにサブディレクトリを作ることも可能(例: ` scenario_samples/feeder/ ` ) - サブディレクトリを作る場合は、importパスを適切に調整してください **Scala (Before)** ```scala val feeder = csv("products.csv") // シナリオ内で使用 .feed(feeder.circular) ``` **JavaScript (After)** ```javascript import { csv } from '../../../common_lib/feeder/feeder.js'; import { open } from 'k6'; // CSVファイルを読み込み(resourcesディレクトリから相対パスで参照) // パスの例: scenario_member_id_sre のチームの場合 const csvData = open('../../../resources/scenario_member_id_sre/products.csv'); const productsFeeder = csv(csvData, {}, 'products'); // default関数内で使用 export default function () { // circular(): 順番に1つずつ選択(ラウンドロビン) const product = productsFeeder.circular(); // random(): ランダムに選択 // const product = productsFeeder.random(); // perVu(): VUごとに固定データを割り当て // const product = productsFeeder.perVu(); // productには CSV の各カラムがプロパティとして含まれる // 例: product.product_id, product.name, product.price など } ``` ### Feederのメソッド - ` circular() ` : ラウンドロビンで順番に選択(Gatlingのcircularと同じ) - ` random() ` : ランダムに選択(Gatlingのrandomと同じ) - ` perVu() ` : VUごとに固定データを割り当て(Gatlingのqueueと同じ) ### AtomicIntegerの変換(ユニークID生成) **重要**: k6にはGatlingの ` AtomicInteger ` のようなスレッドセーフなカウンターがありません。 複数のVUが同時に実行される環境でユニークなIDを生成するには、VU IDとイテレーション番号を組み合わせる必要があります。 **Scala (Before)** ```scala // 初期値は環境変数のCURRENT_MAX_MEMBER_ID val memberIdCounter = new AtomicInteger(currentMaxMemberId) // カスタムfeederを作成し、member_idをインクリメントしていく val memberIdFeeder = Iterator.continually(Map("member_id" -> memberIdCounter.incrementAndGet())) var single_request = scenario("test scenario") .feed(memberIdFeeder) .exec(...) ``` **JavaScript (After)** ```javascript import exec from 'k6/execution'; const CURRENT_MAX_MEMBER_ID = __ENV.CURRENT_MAX_MEMBER_ID ? parseInt(__ENV.CURRENT_MAX_MEMBER_ID) : 0; export default function () { // 各VUに独立したID空間を割り当ててユニークなIDを生成 // VU 1: 1000001, 1000002, 1000003, ... // VU 2: 2000001, 2000002, 2000003, ... // これにより、複数のVUが同時に実行しても重複が発生しない const memberId = CURRENT_MAX_MEMBER_ID + (exec.vu.idInTest * 1000000) + exec.scenario.iterationInInstance + 1; // memberIdを使用したリクエスト performRequest('example', (tags) => { return http.post(`${ENDPOINT}/api/endpoint`, JSON.stringify({ member_id: memberId }), { headers: { ...commonHeaders }, tags } ); }, 200); } ``` **説明**: - ` exec.vu.idInTest ` : VUごとに一意のID(1, 2, 3, ...) - ` exec.scenario.iterationInInstance ` : そのVU内でのイテレーション番号(0, 1, 2, ...) - ` 1000000 ` : 各VUに100万のIDスペースを確保(VUごとに100万回以上イテレーションする場合は調整が必要) - この方法により、409 ConflictなどのID重複エラーを防止できます **生成例**: - ` CURRENT_MAX_MEMBER_ID=0 ` の場合: - VU 1, iteration 0: 1000001 - VU 1, iteration 1: 1000002 - VU 2, iteration 0: 2000001 - VU 2, iteration 1: 2000002 ## 4. HTTPリクエスト ### リクエストの変換(performRequest関数を使用) **重要**: 必ず ` performRequest ` 関数を使用してください。これにより: - Datadogへのメトリクス送信 - カスタムタグの自動付与 - 標準化されたエラーハンドリング が実現されます。 **Scala (Before)** ```scala val login = exec( http("Login") .post("/auth/v1/login") .headers(requestHeaders) .body(StringBody("""{"id": "${email}","password": "${password}"}""")) .check(status.is(200)) .check(jsonPath("$.id_token").saveAs("id_token")) .check(jsonPath("$.refresh_token").saveAs("refresh_token")) ) ``` **JavaScript (After)** ```javascript import { performRequest } from '../common_lib/metrics/metrics.js'; import http from 'k6/http'; export default function () { const userData = userFeeder.circular(); // performRequest(リクエスト名, リクエスト関数, 期待するステータスコード) const loginResponse = performRequest('login', (tags) => { const payload = JSON.stringify({ id: userData.email, password: userData.password }); return http.post(`${ENDPOINT}/auth/v1/login`, payload, { headers: { ...commonHeaders, }, tags // 必ずtagsを渡す(Datadogのカスタムタグ付与のため) }); }, 200); // レスポンスからデータを取り出す const loginData = JSON.parse(loginResponse.body); const idToken = loginData.id_token; const refreshToken = loginData.refresh_token; } ``` ### 各種HTTPメソッドの例 **GET リクエスト** ```javascript performRequest('healthcheck', (tags) => { return http.get(`${ENDPOINT}/auth/v1/healthcheck/db`, { headers: commonHeaders, tags }); }, 200); ``` **POST リクエスト(JSONボディ)** ```javascript performRequest('create_user', (tags) => { const payload = JSON.stringify({ email: userData.email, zozo_id: userData.zozo_id, password: userData.password_hash }); return http.post(`${ENDPOINT}/auth/v1/members/${userData.user_id}`, payload, { headers: commonHeaders, tags }); }, 200); ``` **PUT リクエスト** ```javascript performRequest('update_user', (tags) => { const payload = JSON.stringify({ email: userData.email, zozo_id: userData.zozo_id, password: userData.password_hash }); return http.put(`${ENDPOINT}/auth/v1/members/${userData.user_id}`, payload, { headers: commonHeaders, tags }); }, 200); ``` **DELETE リクエスト** ```javascript performRequest('delete_user', (tags) => { return http.del(`${ENDPOINT}/auth/v1/members/${userData.user_id}`, null, { headers: commonHeaders, tags }); }, 200); ``` **認証ヘッダー付きリクエスト** ```javascript performRequest('logout', (tags) => { return http.post(`${ENDPOINT}/auth/v1/logout`, null, { headers: { ...commonHeaders, 'Authorization': `Bearer ${idToken}` }, tags }); }, 200); ``` ## 5. シナリオとオプション設定 ### シナリオ設定の変換 **重要**: testrun.yamlの ` K6_SCENARIO ` 環境変数とtest.jsの ` options.scenarios ` のキー名は**必ず同じ名前(ハイフン繋ぎ)**にしてください。 理由: - ` http_req ` メトリクス: performRequest関数で ` K6_SCENARIO ` の値がタグとして付与される - ` iterations ` メトリクス: k6のエグゼキュータが ` options.scenarios ` のキー名をタグとして自動付与する - 名前が異なると、Datadogダッシュボードで2つの異なるscenario名が表示され、メトリクスが分断される **Scala (Before)** ```scala val single_request = scenario("zozo-id login") .feed(feeder.circular) .exec(captcha) .exec(login) .exec(refresh_token) .exec(validate_token) .exec(logout) setUp( single_request.inject( constantUsersPerSec(usersPerSec) during(durationSec seconds) ).protocols(httpProtocol) ) ``` **JavaScript (After)** ```javascript import { sleep } from 'k6'; // オプション設定 // 注意: scenariosのキー名は testrun.yamlのK6_SCENARIOと同じハイフン繋ぎにする export const options = { scenarios: { 'zozo-id-login': { // testrun.yamlのK6_SCENARIO="zozo-id-login"と一致させる // 一定のレートでリクエストを送信 executor: 'constant-arrival-rate', // 1秒あたりのイテレーション数 rate: RATE, // レートの適用期間 timeUnit: '1s', // 事前に確保するVU数 preAllocatedVUs: VUS, // テスト期間 duration: DURATION, // 最大VU数(必ずpreAllocatedVUs以上) maxVUs: 100, }, }, }; // セットアップ処理(テスト開始時に1回だけ実行) export function setup() { console.log('Execution parameters:', JSON.stringify({ ENDPOINT, VUS, RATE, DURATION, }, null, 2)); } // メインのテスト処理 export default function () { const userData = userFeeder.circular(); // リクエストを順番に実行 performRequest('captcha', (tags) => { return http.get(`${ENDPOINT}/auth/v1/irregular-login/detect`, { headers: commonHeaders, tags }); }, 200); const loginResponse = performRequest('login', (tags) => { const payload = JSON.stringify({ id: userData.email, password: userData.password }); return http.post(`${ENDPOINT}/auth/v1/login`, payload, { headers: commonHeaders, tags }); }, 200); const loginData = JSON.parse(loginResponse.body); const idToken = loginData.id_token; const refreshToken = loginData.refresh_token; // 以降のリクエストを続ける... // 必要に応じてsleepを追加(Think Time) // sleep(1); } // クリーンアップ処理(テスト終了時に1回だけ実行) export function teardown(data) { console.log('Test completed'); } ``` ### Executorの選択 Gatlingの ` constantUsersPerSec ` は、k6の ` constant-arrival-rate ` に対応します。 **その他のExecutorオプション** 1. **constant-vus**: 固定数のVUを一定期間実行 ```javascript executor: 'constant-vus', vus: 10, duration: '30s', ``` 2. **ramping-vus**: VU数を段階的に増減 ```javascript executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '30s', target: 10 }, { duration: '1m', target: 10 }, { duration: '30s', target: 0 }, ], ``` 3. **ramping-arrival-rate**: 到着レートを段階的に増減 ```javascript executor: 'ramping-arrival-rate', startRate: 5, timeUnit: '1s', preAllocatedVUs: 10, maxVUs: 100, stages: [ { duration: '30s', target: 50 }, { duration: '1m', target: 50 }, { duration: '30s', target: 0 }, ], ``` ## 6. Datadogメトリクスとカスタムタグ ### 環境変数でカスタムタグを設定 TestRun YAMLファイル( ` testrun.yaml ` )で以下の環境変数を設定してください: ```yaml spec: runner: env: # テスト実行ID(必須:Datadogダッシュボードでのフィルタリングに使用) - name: K6_TEST_RUN_ID value: "zozo-id-login-stg-2025-10-22" # 環境名(必須) - name: K6_ENV value: "stg" # シナリオ名(必須:test.jsのoptions.scenariosのキー名と一致させる) - name: K6_SCENARIO value: "zozo-id-login" # チーム名(必須) - name: K6_TEAM value: "member-id-pf-sre" # その他の環境変数 - name: ENDPOINT value: "xxxx" - name: VUS value: "10" - name: RATE value: "5" - name: DURATION value: "60s" ``` ### カスタムメトリクスの生成(オプション) 基本的なメトリクスは自動的にDatadogに送信されますが、カスタムメトリクスを追加する場合は以下のようにします: **注意**: カスタムメトリクスを生成する場合コストが発生するため、@platform-sreチームに相談してください。 ```javascript import { Counter, Trend, Rate, Gauge } from 'k6/metrics'; // カスタムメトリクスの定義 const errorCounter = new Counter('custom_errors'); const loginDuration = new Trend('custom_login_duration'); const successRate = new Rate('custom_success_rate'); export default function () { const startTime = Date.now(); const loginResponse = performRequest('login', (tags) => { // ... リクエスト処理 }, 200); if (loginResponse.status === 200) { successRate.add(1); loginDuration.add(Date.now() - startTime); } else { successRate.add(0); errorCounter.add(1); } } ``` ## 7. External Secretsの使用 ### シークレットの参照 TestRun YAMLファイルでExternal Secretsを参照します: ```yaml apiVersion: k6.io/v1alpha1 kind: TestRun metadata: name: zozo-id-login-test spec: parallelism: 4 script: localFile: /test/zozo-benchmark/k6_scenarios/zozo-id-login/test.js runner: image: grafana/k6:0.54.0 env: # External Secretsからシークレットを取得 - name: CLIENT_TOKEN valueFrom: secretKeyRef: name: zozo-id-client-token-secret key: token # ConfigMapから設定を取得 - name: K6_TEST_RUN_ID value: "zozo-id-login-stg-2025-10-22" envFrom: # GitHub認証用シークレット(必須) - secretRef: name: k6-github-app-secrets-xxxx # Datadog設定用ConfigMap(必須) - configMapRef: name: k6-system-config ``` ### Secretsの管理場所 シークレット情報は以下で管理されます: - **k6-github-app-secrets**: GitHub認証用(シナリオクローン用) - ` k8s/main/k6-secrets/secrets.yaml ` - **gatling-secrets**: 既存のGatlingシークレット(API tokenなど) - ` k8s/main/gatling-secrets/secrets.yaml ` ## 8. ディレクトリ構成 ### シナリオファイルの配置 ``` zozo-benchmark/ └── k6_scenarios/ ├── common_lib/ # 共通ライブラリ │ ├── feeder/ │ │ └── feeder.js │ └── metrics/ │ ├── metrics.js │ └── tags.js ├── resources/ # テストデータ(チーム別に管理) │ └── scenario_<チーム名>/ # チーム別リソースディレクトリ │ └── *.csv # 秘匿情報を含まないCSVファイル └── scenario_<チーム名>/ # チーム別シナリオディレクトリ └── <マイクロサービス名>/ # マイクロサービス別ディレクトリ └── <scenario_name>/ # シナリオ名ディレクトリ ├── testrun.yaml # TestRun マニフェスト ├── test.js # シナリオファイル └── README.md # ドキュメント(オプション) ``` **例**: member-id-pf-sreチームのzozo-oidc-provider-apiのAuthorizationCodeFlowテストの場合 ``` zozo-benchmark/ └── k6_scenarios/ ├── resources/ │ └── scenario_member_id_sre/ │ └── test_data.csv └── scenario_member_id_sre/ └── zozo_oidc_provider/ └── zozo_oidc_provider_api_authorization_code_flow_test/ ├── testrun.yaml ├── test.js └── README.md ``` **命名規則**: - **ディレクトリ名**: Scalaファイル名をスネークケース(アンダーバー繋ぎ)に変換 - 例: ` ZozoOidcProviderApiAuthorizationCodeFlowTest.scala ` → ` zozo_oidc_provider_api_authorization_code_flow_test/ ` - **K6_SCENARIOとoptions.scenariosのキー名**: ハイフン繋ぎに統一 - 例: ` zozo-oidc-provider-api-authorization-code-flow-test ` - 理由: Datadogメトリクスで一貫性を保つため ### 相対パスでのインポート ```javascript // feederライブラリのインポート import { csv, array } from '../../../common_lib/feeder/feeder.js'; // metricsライブラリのインポート import { performRequest } from '../../../common_lib/metrics/metrics.js'; // CSVファイルの読み込み(resourcesディレクトリから) // パスの例: scenario_member_id_sre のチームの場合 const csvData = open('../../../resources/scenario_member_id_sre/data.csv', 'r'); ``` **例**: member-id-pf-sreチームのzozo-oidc-providerのAuthorizationCodeFlowテストの場合 ```javascript import { csv, array } from '../../../common_lib/feeder/feeder.js'; import { performRequest } from '../../../common_lib/metrics/metrics.js'; const csvData = open('../../../resources/scenario_member_id_sre/data.csv', 'r'); ``` --- # 変換対象のScalaシナリオ 以下のScalaシナリオをk6に変換してください: [ここにScalaシナリオのコードを貼り付け] --- # 期待する出力 以下の2つのファイルを生成してください: 1. **test.js**: k6シナリオファイル - 上記のルールに従って変換 - すべてのインポートを含める - コメントで各セクションを説明 2. **testrun.yaml**: TestRun マニフェストファイル - 適切なリソース設定 - 環境変数とシークレットの設定 - initContainersの設定(シナリオクローン用) 3. **README.md** (オプション): シナリオの説明 - テスト目的 - 実行方法 - 必要な環境変数 ``` --- ## 使用例 ### 1. Scalaシナリオを準備 ```scala // simulations/ZozoIdLogin.scala class ZozoIdLogin extends Simulation { val endpoint = sys.env.getOrElse("ENDPOINT", "xxxx") // ... (既存のScalaコード) } ``` ### 2. 生成AIに入力 上記の「移行プロンプト」全体をコピーし、最後の「変換対象のScalaシナリオ」セクションにScalaコードを貼り付けて、生成AIに入力します。 ### 3. 生成されたk6シナリオを確認 生成AIが以下を出力します: - ` test.js ` : k6シナリオファイル - ` testrun.yaml ` : TestRun マニフェスト - ` README.md ` : 説明ドキュメント(オプション) ### 4. リポジトリに配置 ```bash # シナリオディレクトリを作成 # 例: member-id-pf-sreチームのzozo-idのLoginテストの場合 mkdir -p k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login # ファイルを配置 mv test.js k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login/ mv testrun.yaml k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login/ mv README.md k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login/ # CSVファイルがある場合(秘匿情報を含まないもののみ) mv *.csv k6_scenarios/resources/ ``` ### 5. テスト実行 ```bash # TestRunを適用 # 例: member-id-pf-sreチームのzozo-idのLoginテストの場合 kubectl apply -f k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login/testrun.yaml # ログ確認 kubectl get testruns kubectl logs <pod-name> # Datadogダッシュボードで結果確認 [Datadogダッシュボードのリンク] ``` --- ## 参考資料 - [ k6 公式ドキュメント ]( https://grafana.com/docs/k6/latest/ ) - [ k6-operator GitHub ]( https://github.com/grafana/k6-operator ) --- ## トラブルシューティング ### タグが付与されない場合 **問題**: Datadogダッシュボードでカスタムタグが表示されない **解決策**: 1. ` performRequest ` 関数を使用しているか確認 2. ` tags ` パラメータをHTTPリクエストに渡しているか確認 3. TestRun YAMLで環境変数(K6 _ TEST _ RUN _ ID, K6 _ ENV, K6 _ SCENARIO, K6 _ TEAM)を設定しているか確認 4. ConfigMap(k6-system-config)を参照しているか確認 ### シナリオがクローンされない場合 **問題**: TestRun実行時にシナリオファイルが見つからない **解決策**: 1. initContainersが正しく設定されているか確認 2. GitHub認証用Secret(k6-github-app-secrets)が存在するか確認 3. sparse-checkoutパターンが正しいか確認 4. ブランチ名(GITSYNC _ REF)が正しいか確認 ### メトリクスがDatadogに送信されない場合 **問題**: Datadogにメトリクスが表示されない **解決策**: 1. ConfigMap(k6-system-config)を参照しているか確認 2. OTEL関連の環境変数が設定されているか確認 3. ネットワーク接続を確認 移行結果と効果 実際にGatlingシナリオをk6に移行した結果を記載します。サブエージェントを用いて移行したシナリオを実行したところ、問題なくマイクロサービスに想定リクエストが送信されていることを確認できました。Gatlingで実現していた以下の機能がk6でも同様に動作しています。 環境変数による設定切り替え(エンドポイント、VU数、実行時間など) CSVからのテストデータ読み込み Datadogへのメトリクス送信とカスタムタグ付与 具体的な実装例を記載しただけでなく、チェックリストを使用したことでシナリオ移行の精度が上昇したと考えられます。 一方で、課題も発生しました。移行後のシナリオにて、不要なモジュールimport文を生成し、実行エラーになってしまうことがありました。 // 生成された不要なimport import { open } from 'k6' ; 移行ガイドラインに「使用禁止のimportパターン」を明示的に追記することで解決しました。 - [ ] 不要な・存在しないimportは行わない - [ ] ex ` import { open } from 'k6'; ` サブエージェントを使う上でのポイント 今回の取り組みを通じて、シナリオ移行の精度が高いサブエージェントを実装できました。ポイントは以下のとおりです。 まず、ガイドラインの整備が最重要です。サブエージェントに渡すコンテキストの質が出力品質を直接左右します。具体例を多く含めることで変換精度が向上しました。そのため、ガイドラインの完成度をどの程度高くできるかがポイントとなります。 次に、チェックリストで品質を担保することです。LLMの出力にはばらつきがあるため、チェックリストで出力の精度を担保できたと考えています。特に「やってはいけないこと」を明示すると効果的に制限できました。 最後に、完全自動化ではなく半自動化として捉えることです。単純なシナリオであれば、サブエージェントの出力をほぼそのまま使用可能なことがわかりました。一方で、最終的なレビューや実際にシナリオを実行してみるなどの動作確認はまだまだ必要に思いました。 サブエージェントによるシナリオ移行は、手動での書き換えと比較して大幅な工数削減が期待できます。同様の移行を検討している方は、まず単純なシナリオから試し、ガイドラインを徐々に改善していくアプローチがおすすめです。 おわりに 本記事では、gatling-operatorのメンテナンス課題とk6導入時の工夫、シナリオ移行における生成AIの活用事例について紹介しました。 今回の負荷試験ツール移行では以下を実現しました。 負荷試験ツールのメンテナンス負荷軽減 コンテナイメージビルドからの脱却によるシナリオ修正サイクルの改善 Datadogダッシュボードによる試験結果の可視化と分析強化 生成AIを活用したシナリオ移行の効率化 今後は、残りのGatlingシナリオのk6への移行を進めつつ、Claudeサブエージェントへのプロンプトをさらに改善し、移行精度の向上に取り組んでいきたいと考えています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co 弊社のメトリクス保存期間は1年間です。永続的に残したい場合はダッシュボードのスクリーンショットで対応します。 ↩
.table-of-contents > li > ul > li > ul { display: none; } はじめに こんにちは、SRE部 検索基盤SREブロックの 花房 です。2025年12月11日に東京の虎ノ門で開催された「 OpenSearchCon Japan 2025 」にZOZOのエンジニア5人が参加しました。本記事では、会場の様子と印象に残ったセッションについて紹介します。 はじめに OpenSearchCon Japanとは 会場の様子 セッションレポート Maximize Resource Efficiency with Separated Index and Search Workloads 課題 解決策: ワークロードの物理的分離(Reader-Writer Separation) Remote Store(S3など)でデータの共有を実現 感想 Intelligent Japanese Search With OpenSearch 感想 Lessons From Migrating To OpenSearch: Shard Design, Log Ingestion, and UI Decisions 感想 OpenSearch Agentic Memory: Intelligent Context 感想 Mastering Search Performance: Advanced Query Insights for Production OpenSearch 感想 From Chaos To Control: GitOps Disaster Recovery With the OpenSearch Operator 感想 おわりに OpenSearchCon Japanとは オープンソースの分散型検索・分析エンジンであるOpenSearchに特化した国際カンファレンスです。OpenSearchの開発者やユーザが、主なユースケースである検索・オブザーバビリティ・セキュリティアプリケーションに関する技術的な最新情報や知見を共有します。 主催はLinux Foundation・OpenSearch Software Foundation・AWSです。毎年、世界各地で開催されており、今年は日本以外にヨーロッパ・インド・北アメリカ・韓国で開催されました。日本での開催は今年が初めてでした。 OpenSearchCon Japanのキービジュアル 過去のセッションの録画や資料は以下のリンク先から確認できます。本記事の執筆時点ではまだ公開されていませんが、OpenSearchCon Japanの録画も公開されると思います。 events.linuxfoundation.org 会場の様子 今回の会場はビジョンセンター東京虎ノ門でした。5階の3部屋の内、1部屋を受付・ロビーとして、2部屋を発表会場として利用していました。 ロビーでは飲み物や軽食が提供されており、配置されていた丸いテーブルは他の参加者と話しやすい環境を作り出していたと感じました。 受付・ロビーの様子 発表会場の様子 セッションレポート 特に印象に残ったセッションを紹介します。 Maximize Resource Efficiency with Separated Index and Search Workloads SRE部 検索基盤SREブロックの 富田 です。普段はZOZOTOWNの検索基盤の運用・改善を担当しています。私はIndexingとSearchのワークロード分離に関するセッションについて紹介します。 課題 OpenSearchでは、検索(search)とインデキシング(indexing)が同じDataノードで動くのが基本でした。しかし、この仕組みは以下のような課題がありました。 searchとindexingが同じリソース(CPU、メモリ、I/O)を奪い合うため、片方の負荷がもう片方に悪影響を与える 大量のindex更新が走ると、検索レイテンシが顕著に悪化する リソースを追加したくても「検索だけ増やしたい」「書き込みだけ減らしたい」といったワークロード別のスケールが出来ない 公開資料 「Maximize Resource Efficiency with Separated Index and Search Workloads」 P.14より引用 解決策: ワークロードの物理的分離(Reader-Writer Separation) OpenSearch v3.0で導入されたReader-writer Separationは、search用ノードとindexing用ノードを物理的に分離します。 検索処理:Searchノード(Reader) インデックス作成:Dataノード(Writer) これによりSearchノードは検索だけに集中し、indexing負荷の影響を一切受けなくなるというメリットがあります。 公開資料 「Maximize Resource Efficiency with Separated Index and Search Workloads」 P.18より引用 Remote Store(S3など)でデータの共有を実現 Searchノードがインデックスを持たなくても検索できる仕組みとして、Remote Store(リモートストレージ)が前提になります。 Primary shardがセグメントをアップロード Search ReplicaがRemote StoreからPullする この仕組みによってsearchとindexingの完全分離が実現されています。 感想 ZOZOTOWNの検索基盤でも、商品インデックスにindexingによる更新が走ると検索レイテンシに悪影響を与えるという課題に何度も直面していました。 今回のセッションで紹介されたReader-Writer Separationは、まさにこの問題を根本から解決できる仕組みなので非常に期待が大きい機能だと感じました。ただしこの機能はOSS OpenSearchのみ利用可能で、AWSのAmazon OpenSearch Serviceではまだサポートされていません。 非常に良い機能だと思いますし、AWSのサービス側でもサポートされる予定と聞いたので期待しています。 (2026/01/07修正)現時点ではAWSサービスでのサポート予定はまだありません。 Intelligent Japanese Search With OpenSearch 検索基盤部 検索技術ブロックの今井です。普段は、検索システムの開発や検索精度の改善に取り組んでいます。 私からは、Amazon Web Service Japanの後藤氏によるOpenSearchを活用したインテリジェントな日本語検索のセッションを紹介します。 検索システムは主にドキュメントを格納する indexing とクエリから結果を取得する retrieval の2つで構成されているのですが、セッションでは検索精度の観点で主に retrieval を取り上げています。日本語検索の精度を改善したい方、困っている方は内容を把握しておくと良いと思います。 発表の様子 OpenSearchで利用可能な4つのretrieval手法について紹介がありました。ここでは各手法の特徴やメリット・デメリットの話がありました。 語彙検索(Lexical Search) BM25アルゴリズムによるキーワードマッチで高速かつ透明性は高いが、同義語に対応できず文脈も理解できない 密ベクトル検索(Dense Vector Search) BERTなどで意味をベクトル化し、「犬」と「ペット」のような意味的に近い言葉もマッチできるが、計算コストが高くなぜマッチしたのかの説明が困難 疎ベクトル検索(Sparse Vector Search) SPLADEなどでキーワードに重みを付け、メモリ効率が良いが日本語対応モデルが少ない 初回はラフに検索して2回目に最終結果を出すために検索するTwo-Phase Searchや効率的にスパース検索するNeural Sparse ANN Search, SEISMICの新機能も簡単に紹介もあった ハイブリッド検索 上記手法を組み合わせ、スコアベースまたはランクベースでスコアを統合 ハイブリッド検索のベクトル用インデックスとして、ドキュメント全体のベクトルを持つのか、Nested k-NN機能で1ドキュメントにネストで複数ベクトルを持つのかなどベクトル検索のためのインデックス設計の重要性についても解説がありました。 発表の様子 検索精度の向上には以下の要素も重要です。 リランキングによる結果の再評価 クエリ拡張によるシノニム展開や意図理解 LLMを活用したAgentic Search リランキングでは、OpenSearchの新機能として ベクトル検索のMMRリランキング が導入されたようです。これは関連性と多様性の両方を考慮したリランキングをMMRというランキングのバランスを取るアルゴリズムで実現する機能です。 これらの技術を組み合わせることで、ユーザーの意図を深く理解し、高精度な日本語検索を実現できると紹介されました。 感想 日本語検索に関する現在の取り組みが広く分かるセッションでした。ベクトル検索のインデックス設計の話で、Nested k-NN機能で1ドキュメントにネストで複数ベクトルを持つ手法は、ドキュメントに重要要素が複数あるユースケースではこの手法を使うと効果的だと感じました。 また、Neural Search Pipelineについての解説の中では、クエリをSearch Pipelineの中でベクトル化する機能が備わっている話があり、モデルを配置しセットアップすることで容易に利用可能になる点で印象に残りました。 公式ドキュメントの「 Semantic search using a model in Amazon SageMaker 」には、SageMakerを利用したベクトル検索の導入方法が記載されており使い方のイメージができました。 Lessons From Migrating To OpenSearch: Shard Design, Log Ingestion, and UI Decisions 検索基盤部 検索基盤ブロックの岡田です。普段はZOZOTOWNの検索機能に関する機能追加や保守運用を行っています。私からは、Sansan株式会社 藤原氏のログ基盤をセルフホストのElasticsearchからAmazon OpenSearch Serviceへ移行するプロジェクトに関するセッションを紹介します。 1日約150GBという大規模なログ流入がある環境で、移行時に実際に直面したトラブルについて具体的な原因と対応を知ることができた大変興味深いセッションでした。以下の3つの具体的な課題とその際の対応が紹介されました。 可用性の確保(Yellowステータス対策) コスト重視のシングルノード構成ではレプリカを配置できず、ステータスがYellowになる課題がありました。これに対し、インスタンスサイズを下げて台数を増やす(複数台構成・マルチAZ)ことで、コストを維持したまま、許容リスクとのバランスを取った現実的な可用性を確保していました。 高負荷への対応(CPU高騰・バッファ溢れ) 構成上の問題でシャード数が3,000を超え、特定ノードでのインデックス作成負荷によりCPU使用率が高騰してしまいました。対策として、データ量に基づき「5日でローテーション」するISM Policyを適用してシャード数を約300まで削減しました。そうして更新間隔(refresh_interval)を120秒へ緩和することで解決を図りました。 運用リスクの顕在化(Redステータスとログ欠損) さらなる負荷軽減のためにレプリカを「0」にした結果、ノード障害時にシャードが消失し、クラスターがRedステータス(機能不全)に陥りました。これにより自動復旧せず手動対応が必要となり、ログ欠損も発生してしまったとのことです。最終的には、インデックス負荷を根本から低減するために、マッピングの見直しやRemote Backed Storageの検討といったアプローチが紹介されました。 感想 本セッションを通じて痛感したのは、安定運用は継続的なチューニングによって初めて実現するという点です。ベストプラクティスは万能ではなく、実際のワークロードを観察しながら構成を磨き続ける姿勢こそが成功の鍵であることを強く認識しました。 特に、OpenSearchをアプリケーションの検索エンジンとして利用する場合、この姿勢は一層重要になります。アプリケーションのトラフィック特性やデータ構成の仕様要件に踏み込んだ丁寧な設計と、それを支える継続的なモニタリングが不可欠であることを改めて学ぶことができました。 OpenSearch Agentic Memory: Intelligent Context 検索基盤部 検索基盤ブロックの 佐藤 です。AIエージェントに「長期記憶」を持たせるための基盤として提案された OpenSearch Agentic Memory に関するセッションについて紹介します。本セッションでは、従来のLLMが抱える「金魚の記憶問題」を解決し、ユーザーとの対話を継続的に深めていくための実装アーキテクチャやデモが数多く取り上げられていました。 発表内容については以下のブログで取り上げられています。詳しくはこちらをご参照ください。 opensearch.org OpenSearchを「記憶装置」として活用し、短期記憶と長期記憶を分離する設計は非常に興味深く、AIエージェントの実用性を大きく引き上げる手法だと感じました。また、REST APIベースのフレームワーク非依存な構成により、LangChainをはじめとする既存エコシステムと柔軟に統合できる点も魅力的でした。 本セッションが焦点を当てていたのは、AIエージェントが抱える本質的な課題──「コンテキストを保持できない」という問題でした。コンテキストウィンドウの制約により長い対話履歴を保持できなかったり、セッションを跨ぐと過去情報を完全に忘れてしまったりする限界が、これまでのユーザー体験を大きく阻害していました。 これに対し、発表者らが提示した解決策は OpenSearchを用いた永続的なエージェント用メモリ「Agentic Memory」 です。 アーキテクチャは大きく以下の4つのコンポーネントで構成されています。 公開資料 「OpenSearch Agentic Memory」 P.6より引用 Conversation Memory(短期記憶) :現在のセッションに必要な会話履歴を保持 Memory Builder(情報抽出) :LLMを用いて「記憶として残すべき情報」を抽出・構造化 Long term Memory(長期記憶) :永続的な知識としてOpenSearchに保存 Activity Log(監査ログ) :記憶の読み書きを追跡し、透明性を担保 この二層構造により、AIエージェントは必要なときにだけ長期記憶からベクトル検索で関連情報を取り出し、過去のやりとりを踏まえた文脈的に豊かな応答が可能になります。 具体的な実装ノウハウとしては、以下の3つの記憶戦略が紹介されました。 Semantic Strategy :会話の中から重要な事実や知識を抽出 User Preference Strategy :ユーザーの嗜好(好み、履歴、選択基準)を蓄積 Summary Strategy :セッション全体を要約して長期的な理解を保存 また、ユーザー別・エージェント別に独立した記憶領域である「 Namespaces(名前空間) 」を作れるため、マルチテナント環境への展開にもそのまま対応できる設計となっています。 デモでは、LangChainを用いたショッピングエージェントや旅行計画エージェントが紹介され、過去の嗜好や計画を思い出しながらユーザーに提案する様子が説明されました。特定のフレームワークに依存しないREST API構成のため、既存のエージェント基盤との統合性が高い点も強調されていました。 感想 AIエージェントの賢さはモデルの性能だけでなく、「何をどのように覚え続けるか」で決まる、というメッセージが非常に腹落ちしました。短期記憶と長期記憶をOpenSearch上で分離し、LLMはあくまで「記憶を整形する役」として使う設計は、運用とスケーラビリティの観点でも現実的だと感じました。 また、既存のLLMは膨大なコンテキストを一度に入力すると精度が低下する傾向があります。しかし、この二層構造により必要な情報だけを効率よく取り出せる点も魅力的でした。特に、ユーザー嗜好や行動履歴を扱える仕組みは検索のパーソナライズ機能との相性が良く、プロダクト体験の向上にも大きく寄与すると感じました。 Mastering Search Performance: Advanced Query Insights for Production OpenSearch SRE部 検索基盤SREブロックの 花房 です。このセッションでは、「Query Insights」というクエリパフォーマンス調査用プラグインについて紹介されました。 検索クエリのパフォーマンス調査や最適化が必要になる場面は多々あります。標準機能によりスローログは出力可能ですが、出力されたログをUI上で確認したり、詳細に分析したりするのは難しいという課題があります。 Query Insightsはクエリパフォーマンス分析のための機能と専用ダッシュボードを提供してくれるプラグインです。プラグインをインストールすると、OpenSearch Dashboardsに「Query Insights Dashboards」というメニューが追加され、GUIベースで詳細な分析が可能になります。プラグインに関連するリポジトリを以下に記載しておきます。 プラグイン: https://github.com/opensearch-project/query-insights ダッシュボード: https://github.com/opensearch-project/query-insights-dashboards Query Insightsでは現在、以下の3つの機能が利用可能です。 Top N queries :レイテンシ・CPU・メモリに基づいて、リソースを大量に消費しているクエリを特定できる。さらに、Pattern Grouping機能により類似したクエリはグループ化して表示も可能。 Live queries :実行中のクエリに関する各種メトリクスをリアルタイムで確認できる。 Configuration :Top N queriesの抽出閾値や集計間隔、Query Insights自体のデータの出力先を設定できる。 感想 クエリパフォーマンスの分析データを取得・保存する場合、やはりクラスタ全体への負荷影響が気になります。しかし、このプラグインでは優先度キューや非同期処理などを活用したアーキテクチャにより影響を最小限に抑えていました。ユーザのことを考慮し、導入しやすい工夫がされていると感じました。 Query Insights DashboardsのUIは使いやすい雰囲気を感じました。今後はProfile API Visualizerへの対応も進められるようなので、さらなる利便性の向上に期待を寄せたいと思います。 From Chaos To Control: GitOps Disaster Recovery With the OpenSearch Operator 花房 です。このセッションは、OpenSearch OperatorとGitOpsを活用した障害復旧の自動化手法についての発表でした。 OpenSearch Operatorは、OpenSearchの運用管理を自動化するKubernetes Operatorです。クラスタやダッシュボードをはじめとする基本的なリソースに加え、インデックスやスナップショットなどのライフサイクル設定もKubernetesのCustom Resource (CR) として宣言的に定義できます。発表の中では、Node pools, Roles, Plugins, Security, Dashboardsの5つが設定項目の例として挙げられていました。 github.com GitOpsは、Gitリポジトリを唯一信頼できるソースとして管理し、専用ツールによってリポジトリと実環境を同期させることでデプロイや運用を自動化する手法です。発表の中では、ツールとしてArgo CDを利用していました。 github.com OpenSearch OperatorとArgo CDを組み合わせることで、インフラ構築から詳細設定までを一貫してコードで管理し、障害発生時には自動復旧する高度な仕組みを実現していました。仮にクラスタが完全に消失しても、以下のプロセスを経て自動的に再構築・復旧が完了します。 Argo CDがGitリポジトリに基づいてKubernetesにクラスタをデプロイ OpenSearch Operatorがクラスタをプロビジョニング 感想 セルフホストでの運用や障害対応はどうしても負荷が高くなってしまうものですが、この方法ならマネージド並とはいかないまでも、運用負荷をかなり抑えつつシステムの安定性も保つことができると感じました。 注意点として、スナップショットの復元におけるデータの一部損失の可能性が挙げられていました。そのようなパターンも想定し、クラスタが再構築された後はインデキシングのバッチをフックし、インデックスを作り直すところまで完全に自動化できると理想的だと考えました。 おわりに 本記事では、OpenSearchCon Japan 2025の参加レポートをお届けしました。イベントへの参加を通して得られた主な収穫・知見は以下の通りです。 ZOZOTOWNの商品検索にとって有用と考えられる新機能について、日本の開発コミュニティの方から直接説明を聞き、理解を深められた 検索・オブザーバビリティ・セキュリティアプリケーションの各領域における日本国内でのユースケースやプラクティスを知れた Query InsightsやMCPサーバなど、ユーザの利用体験を向上する拡張機能について、デモを通じて効果と可能性を実感できた 1日だけの開催でしたが、その期間の短さを感じないほどOpenSearchに関する多くの学びを得られた充実したカンファレンスでした。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからご応募ください。 corp.zozo.com
はじめに こんにちは、「ZOZOMO」の ブランド実店舗の在庫確認・在庫取り置き 機能の開発を担当しているZOZOMO部OMOブロックの木目沢です。先日、プロダクト開発メンバーとビジネスメンバー合同で 2日間の「ユーザーストーリー」ワークショップ を開催しました。 1日目は「キャンプ」を題材にユーザーストーリーの型と会話を体験し、2日目は実際のZOZOMO店舗在庫取り置きでユーザーストーリーを書き、 優先順位を決めて実際に開発する ところまで行いました。 この記事では、その2日間をまとめて振り返ります。 目次 はじめに 目次 開発チームの課題感と、ワークショップのねらい なぜ「ユーザーストーリー」なのか? 1日目:キャンプでユーザーストーリーの型を体験する メインワーク1:キャンプで“WHAT”を考える メインワーク2:WHYを考える──価値の核心を探る メインワーク3:WHAT/WHYを“実現したと言える”受け入れ基準=AC(Acceptance Criteria)をつくる メインワーク4:環境が変わればACも変わる──秋キャンプに変更 1日目の振り返り:ユーザーストーリーは“会話”のための道具 2日目:実プロダクトで優先順位を決め、その場で開発まで 実プロダクトでユーザーストーリーを出し切る プロダクトオーナー×開発チームで優先順位付け ワークショップ全体を通しての学び おわりに 開発チームの課題感と、ワークショップのねらい まず前提として、私たち OMOブロック は「開発専門の部署」です。これまでは、ビジネス部門などから依頼(要望や企画)を受けて、それを実現するための開発をする、という流れが中心でした。 もちろん、そのやり方でもプロジェクトは作れます。ただ、続ける中でいくつものモヤモヤが見えてきました。 開発メンバーのアイデアが入りづらい ──開発メンバー一人ひとりは「開発者」という属性だけでなく、それぞれ異なるバックグラウンドや視点を持っている。そうしたメンバーのアイデアが活かされにくい状況はもったいない ビジネスと開発のやり取りが「依頼」と「回答」になりがちで、 多くのミーティングが組まれたり、Slackの会話が延々と続いたりする 結果として、 いわゆる社内の「請負開発」のようになっている ゴールや背景、ユーザーにとっての価値がよく見えないまま開発してしまうことが多かった 「これって、誰のどんな課題を解決したくてやっているのか?」 「この機能を作った先に、どんなアウトカムがあるのか?」 そういった肝心な部分が曖昧なまま、「仕様どおりに作ること」に引っ張られてしまうのは開発側にとっても力を発揮しにくい状況です。ビジネス側にとっても、本当に欲しかったものとずれる原因になります。 OMOブロックが持っている技術的な知見とビジネス側のアイデアを掛け合わせ、さらに各メンバーそれぞれのバックグラウンドや視点を活かし、 一緒にインクリメントを積み上げていきたい と考えています。 「開発」「ビジネス」という役割をはっきりと分けて仕事をするのではなく、役割の垣根を超えて、全員が“プロダクトが目指す世界・ユーザー価値・アウトカム”に焦点を当てている状態 を目指したいと考えています。 最終的にはこのようなことを目指したいと考えています。 スクラムガイド2020では、プロダクトについて以下のように定義されています。 A product is a vehicle to deliver value. It has a clear boundary, known stakeholders, well-defined users or customers. A product could be a service, a physical product, or something more abstract. The Scrum Guide 2020 より ここでは、プロダクトは「手段」であり、「価値を提供すること」が目的であると示されています。 開発チームが作るだけという状況はプロダクトではなく、プロジェクトです。プロダクトを作っていくという状況をつくるべく、ZOZOMO店舗在庫取り置きではスクラムを採用しました。 そして、今回開発とビジネス間における会話のための道具として、 ユーザーストーリー を活用することにしました。 ユーザーストーリーを共通言語にして、ビジネス・開発それぞれ同じ土俵で会話できるようにする第一歩として、このワークショップを企画しました。 なぜ「ユーザーストーリー」なのか? ユーザーストーリーを一言でいうと、 「価値のアイデアを、会話しやすい形に翻訳したもの」 です。 ビジネスと開発の間には、どうしても“言語のギャップ”があります。 ビジネス側は「売上」「会員数」「体験価値」などの言葉で語る 開発側は「API」「スキーマ」「パフォーマンス」「テストケース」などの言葉で語る どちらも間違っていないのですが、別々の言語のままでは、会話のハードルがどうしても高くなります。 そこで登場するのが ユーザーストーリー です。アイデア(価値の定義)は、次のような形で表現できます。 【WHO】◯◯において。 【WHAT】XXXしたい。 なぜなら【WHY】〜のため。 【AC(Acceptance Criteria)】それは何ができれば実現したと言えるか。 このフォーマットによって、以下のような効果が期待できます。 WHAT(なにがしたいか)/WHY(背景・価値)を、ビジネスと開発とで 同じ言葉で共有できる AC(Acceptance Criteria:受け入れ基準)は技術の用語を使わず、 「なにができればWHAT/WHYが実現できているか。検証可能なものを書く」 ことで、 ビジネス側も理解しやすくなる 結果として、ユーザーストーリーを中心に 自然な会話が生まれる ユーザーストーリーはそのまま プロダクトバックログアイテム となり、実装からスプリントレビューまで一貫して利用できる つまりユーザーストーリーは、 ビジネスと開発の垣根を取り払うためのツールになりうる のです。 私たちは、この「共通言語」をきっかけに、開発だけでなくビジネスも含めたチーム全員が、 ユーザー価値とアウトカムにフォーカスする状態 を作っていきたいと考えています。 1日目:キャンプでユーザーストーリーの型を体験する ここからは、2日間の流れを順番に振り返ります。まずは、ユーザーストーリーの基本を“遊びの題材”で体験した1日目からです。 メインワーク1:キャンプで“WHAT”を考える 今回のワークのお題は、 “夏の北海道にキャンプへ行く” という設定です。3チームに分かれてこの設定をもとにユーザーストーリーを作成していきます。 このシナリオに基づいて、「キャンプで何をしたいか?」= WHAT についてブレインストーミングを実施しました。 WHOは各チームのメンバー、つまり「私たち」です。 Miroの付箋には、各チームからさまざまなWHATが出ました。 森林浴したい 渓流釣りしたい 芝生を歩きたい 焚き火したい キャンプファイヤーしながら合唱したい 水辺を散策したい ここでは“良し悪し”をつけるのではなく、まずは出し切ることが目的です。「キャンプ」という1つの体験にも、こんなに多様な望みがあるのかと、チーム内でも驚きがありました。 メインワーク2:WHYを考える──価値の核心を探る 次のステップは WHY(なぜそれをしたいのか?) を書き添えることです。 例えば、 “森林浴したい” に対しては、こんなWHYが出てきました。 デジタルデトックスのため 何もしていないことをしてみたいから リラックスしたいから WHYを書いていくほど、単なる行動(WHAT)が、 「その人にとっての価値」 に変わっていくのがわかります。 あるチームでは、「WHYが深まるほど価値が整理されていく感じがする」という感想も出ていました。 メインワーク3:WHAT/WHYを“実現したと言える”受け入れ基準=AC(Acceptance Criteria)をつくる 続いて、ユーザーストーリーの肝とも言える AC(Acceptance Criteria:受け入れ基準) です。 ACとは、ざっくり言うと WHAT/WHY を実現した、終わった、と言える客観的な基準 のことです。 ACは技術の言葉を使わず、 「なにができればWHAT/WHYが実現できているか。検証可能なものを書く」 ことがポイントです。 WHAT:森林浴したい WHY:リラックスしたいから たとえば、このWHAT/WHYに対して、あるチームが出したAC案は次の通りでした。 まずは森林と呼べる場所へ行くこと デジタルデバイスの電源を8時間以上OFFにすること リラックスしたと感じたこと(アンケートなりで確認) ACで大事な点の1つは、 ACを「技術仕様のリスト」にしないこと です。 「APIレスポンスが〜」「DBが〜」といった実装寄りの言葉ではなく、あくまで 利用者視点で『できている状態』を書きます 。そうすることで、ビジネス部門のメンバーもストーリーの完了イメージを掴みやすくなります。 この「ビジネスでも理解できるAC」を軸に、実装方法は開発側で柔軟に検討することに、 ビジネスと開発の役割分担と協調のポイント があります。 また、ユーザーストーリーワークショップの題材では、技術的な実装は関係ないため技術的な言葉を意識することなく、検証可能なものという制約に集中できるのが狙いでもありました。 客観的な形で必ず検証可能なものであることもACでは重要です。 最終的にできたユースケースはこちらです。 メインワーク4:環境が変わればACも変わる──秋キャンプに変更 ワークショップの後半では、 「急に秋の北海道キャンプに変更!」 というイベントが発生します。 WHAT/WHYは変えないまま、季節変更に伴って ACを見直し てもらいました。 夏と秋では、以下のように前提条件が大きく変わります。 気温 日照時間 できないアクティビティも出てくる その変化を踏まえ、例えば「森林浴したい」のACは、以下のように現実的な条件が追加されました。 寒さ対策で1時間ウォーキングする ここからの学びはとてもシンプルで重要です。 ACは固定ではなく、状況に応じて調整・交渉してよい ということです。 だからこそ、ビジネス側も「これなら現場目線でOK」「ここは譲れない」といったコメントをしやすくなります。 リファインメントやレビューの場で、一緒にACを調整していくイメージです。 追加されたACを含む、最終的なユースケースはこちらです。 1日目の振り返り:ユーザーストーリーは“会話”のための道具 ワークを通して印象的だったのは、 付箋そのものより、付箋をきっかけにした会話の方が重要だった ということです。 WHAT/WHY/ACを作っていく過程で、以下のようなやり取りが自然と生まれ、議論の質が上がっていきました。 「それって本当にWHYに沿ってる?」 「そのACだと何が嬉しいんだっけ?」 「それならもっと良い表現があるかも」 ユーザーストーリーは、単なるアウトプットではなく、 価値を共通理解するための“会話” のために必要な道具なのだと体感できた1日目でした。 2日目:実プロダクトで優先順位を決め、その場で開発まで 2日目は、1日目で身につけた型と感覚を、 実際のZOZOMOプロダクト開発に適用する日 です。 実プロダクトでユーザーストーリーを出し切る まずは対象となるプロダクトを1つ選び、そこに対して「こうなったらもっと良くなるはず」というアイデアを、チームメンバー全員で付箋に書き出していきました。 ここでもフォーマットは同じです。 【WHO】◯◯において。 【WHAT】XXXしたい。 なぜなら【WHY】〜のため。 【AC(Acceptance Criteria)】それは何ができれば実現したと言えるか。 ビジネス側からは「この指標をもっと改善したい」「この業務オペレーションを減らしたい」といった声が上がりました。開発側からは「このフローは技術的に改善余地がある」「この組み合わせならすぐ試せそう」という意見も出ました。 役割を超えてどんどんストーリーが出てくるのが印象的でした。 プロダクトオーナー×開発チームで優先順位付け 次に行ったのが、出てきたユーザーストーリーの 優先順位付け です。 評価の軸はシンプルに2つだけに絞りました。 価値の高さ 開発のしやすさ(実現コストの低さ) Miro上に2軸のマトリックスを用意し、プロダクトオーナーと開発メンバーが会話しながら各ストーリーの位置を決めていきます。 「このストーリーについて、価値は高いけど既存システムへの影響が大きいのでコスト高め」 「これは小さいけど、いますぐ改善するとユーザー体験の印象がかなり変わりそう」 「この2つは似ているので、まとめて1つのストーリーにしませんか?」 といった議論を通じて、 価値とコストをセットで考える感覚 を共有できたのがよかったポイントです。 このプロセスを経てマトリックスの右上(=価値が高くて、かつ開発しやすい)に位置するストーリーの中から、 「取り組むべき1つ」を決定 をしました。 このストーリーは実際にプロダクトバックログに積まれ、リファインメントを経てスプリントで実装されました。 ワークショップ全体を通しての学び 2日間を通じて学んできたユーザーストーリーですが、最後に書籍の引用からユーザーストーリーの定義を振り返ります。 『「ユーザーストーリ」はXPで提唱されたプラクティスで、 ユーザーの言葉で書かれた機能の説明である。 そのものではないが、従来の「要求仕様書」を置き換えるものだ。 このユーザーストーリはアナログを重視し、 紙のカードに書いて会話によって伝えることが推奨されている。』 (引用)アジャイル開発とスクラム~顧客・技術・経営をつなぐ協調的ソフトウェア開発マネジメント 平鍋 健児 (著), 野中 郁次郎 (著) No 584 ※ 「ユーザーストーリ」と記述されていますが、原典を尊重し、そのまま引用しております。 開発・ビジネスの役割を超え、全員がユーザー価値・アウトカムにフォーカスする 第一歩として、このワークショップは良いきっかけになりました。 おわりに ユーザーストーリーワークショップ実施から3か月が経過しました。ワークショップの効果は非常に大きく、以下のような変化が起きています。 ビジネスチームのメンバーがリファインメントへ参加するようになり、ユーザーストーリーをベースとして会話するようになった スプリントレビューにも積極的に参加してもらうことになり、多くのフィードバックを得ながらプロダクトを進められるようになった プロダクトオーナーにビジネスチームのメンバーをアサインすることになり、ビジネスと開発の距離がさらに近くなった ビジネスチームのメンバーも開発チームのメンバーもアイデアを出しやすくなった 今後もユーザーストーリーを起点に、ビジネスと開発が一緒になってプロダクトを育てていく取り組みを続けていきたいと思います。 以上、ZOZOMOユーザーストーリーワークショップ 全2日のレポート でした。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。最後までご覧いただきありがとうございました! corp.zozo.com
はじめに こんにちは、ZOZOTOWN開発本部フロントエンドエンジニアの齋藤( @Jin_pro_01 )です。 2025年11月30日に開催された「フロントエンドカンファレンス関西2025」に参加しました。本記事では、会場の様子やトークについてご紹介します。私自身LT枠での登壇もしたので、その内容についても記載しています。 フロントエンドカンファレンス関西とは フロントエンドカンファレンス関西 は、Webフロントエンド領域に関心のあるエンジニアやデザイナーを対象とした技術イベントです。カンファレンスのコンセプトは「IGNITE KANSAI(出会いが共鳴し、次の誰かを動かす)」。運営スタッフ・登壇者・参加者などすべての熱が混ざり合い、相互に影響しあうカンファレンスを目指すというテーマで開催されました。 会場の様子 今回の会場は「マイドームおおさか」の8Fでした。ワンフロアを貸し切り、2つのルームに分かれてトークが行われました。 マイドームおおさか前の看板 フロントエンドカンファレンス関西2025の看板 フロントエンドカンファレンス関西2025のバナー トークの内容 どれも非常に興味深い内容でしたが、特に印象に残ったトーク、LTをご紹介します。最後に私が今回登壇させていただいた内容についても記載します。 なぜフロントエンド技術を追うのか?なぜカンファレンスに参加するのか? speakerdeck.com sakitoさんによる基調講演では、タイトルにある2つの問いに対する答えが語られました。カンファレンス参加者が共通で考えるべき問いを、sakitoさんなりに言語化して紹介するものでした。 まず「なぜフロントエンド技術を追うのか」という問いに対して、そもそもの技術の追い方としてビルドツールの変遷を例に挙げ、"点ではなく線で追う"というアプローチが紹介されました。ツールが多く流行り廃りも激しい領域であるフロントエンド技術の歴史的背景や経緯を追いながらキャッチアップしていく方法です。その中でそのツールはどういった思想のもとなぜ生み出されたのか、どんな時に真価を発揮するのかについて理解し、サービスの持つ課題に対して最も価値を発揮する技術を選定していく必要があります。そのために、フロントエンドの技術を追っていく必要があるという話がされていました。 次に「なぜカンファレンスに参加するのか」という問いです。カンファレンスはドキュメントには載っていない、実際に技術を使ってみた現場の生の声を聞ける場所で、さらに異なる現場での課題感やその解決策について参加者同士で議論できます。技術を追うだけでは得られない誰かの生きた知見を自身の知見にするために、あるいは自社に還元するためにカンファレンスに参加するという1つの答えを提示されていました。そしてフロントエンドカンファレンスにはフロントエンド技術に興味を持ち、課題を抱えた人たちが同じ目的で集まっています。そのような議論をするのにこれ以上適切な場所はないという話があり、私自身深く納得し、何度も頷きながら聞いていました。 このカンファレンスの最初に、「なぜカンファレンスに参加するのか」という問いが言語化され共有されたことで、参加者にひとつ火が灯されたように感じました。とても印象に残る基調講演だったので、早速その日の懇親会でもこの基調講演について直接お話を伺いました。 俺流レスポンシブコーディング 2025 speakerdeck.com レスポンシブなWebサイトを作るためにどのようにコーディングするべきか、実際のコード例を交えたテクニックや考え方についての発表でした。 特に印象的だったのは、ピクセルパーフェクトではなく「デザインの意図パーフェクト」を目指すという考え方です。無限の変数の上に成り立つWebサイトでは、完全なピクセルパーフェクトは存在しません。また、CSSはスタイルの「命令書」ではなく「提案書」であるという発想も新鮮でした。定義したルールの中でブラウザに「よしなに」レスポンシブデザインを作り上げてもらう。どれも今後の開発業務に活かせる内容ばかりで、非常に勉強になりました。 このプロパティいつ使うん? 〜MDNのCSSリファレンス、全部読んでみた〜 www.arfes.jp 532個にも及ぶCSSプロパティをすべて読み、その中でも特に注目すべきCSSプロパティについて紹介する発表でした。 いつもの実装をちょっと楽にするプロパティ、悪用しても面白いプロパティ、これからの実装を大きく変え得るプロパティについてそれぞれ紹介があり、どれも興味深いものでした。Limited Availabilityのものもありましたが、実用例と合わせてまとめられており弊社のプロダクトでも活かせそうなものばかりでした。ちなみにすべてのCSSプロパティを読んだ記録はNotionにまとめられており、自分も挑戦してみたくなりました。 ソースマップはどのように元コードへの参照を保持するか www.docswell.com TypeScriptがJavaScriptにトランスパイルされた後、元のTypeScriptのどこがエラーの原因かを伝えるのがソースマップです。その内部実装が一体どのようになっているかについての発表でした。 変換されたソースマッピングの仕組みについて、5分のLTで47枚ものスライドを用いてわかりやすく詳細に解説されていました。普段あまり意識することのない領域だったため、非常に興味深かったです。 TypeScriptがブラウザで実行されるまでの流れを5分で伝えたい speakerdeck.com 私も今回LT枠で登壇させていただきました。我々の書いたTypeScriptがどのようなプロセスを経てユーザーのブラウザ上で動くのかについて発表しました。 内容としてはTypeScriptが実際にブラウザで実行されるまでの流れをビルドプロセスとし、大きくトランスパイル、バンドル、ミニファイという工程の3つに分けて説明しました。それぞれどのようなことをするのか、どのようなツールを使うのか、実際にそれらのツールを使うとどのような入力からどのような出力がされるのかを資料にまとめています。 5分という限られた時間の発表での時間配分など、技術カンファレンスでの登壇は初めてで大変緊張し思うようにいかない部分もありましたが、学びの多い貴重な機会となりました。採択いただいたことに改めて感謝申し上げます。 おわりに 今回のイベントで同じ悩みや関心を持つ人たちと直接言葉を交わし、他社の現場の生の声を聞くことで多くの知見を得られました。そして、1つの目標としていたカンファレンス登壇を達成できた嬉しい日でもありました。各発表から得た学びに加えて懇親会での交流など、非常に楽しく、学びにもなる刺激的な一日でした。 最後に、素晴らしいイベントを企画・運営してくださった運営スタッフの皆さまに心より感謝申し上げます。来年の開催も楽しみにしています! ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからご応募ください。 corp.zozo.com
こんにちは、プロダクト戦略部の土屋です。普段はFAANS(アパレル店舗で働くショップスタッフ向けの業務支援ツール)のプロダクトマネジメントを担当しています。 「新しいことをやりたいけれど、既存タスクで手一杯」プロダクト開発の現場では、こうした状況は珍しくありません。特に、特定の領域や役割にタスクが偏りやすい構造的な課題を抱えた組織では、新しいチャレンジが後回しになってしまうケースも多いのではないでしょうか。 この記事では、チーム内のリソースの偏りという組織的な課題に対して、私たちが取り組んだ「PoC専用開発レーン」の設計と運用についてご紹介します。設計思想から開発フローまで具体的な内容をお伝えできればと思います。 FAANSの開発チームについて FAANSの開発チームは、バックエンド、フロントエンド(Web/iOS/Android)、デザイン、プロダクトマネジメントの各専門領域で構成されています。PdMが要件定義と優先順位の調整を図り、デザイナーがUI/UXを設計、エンジニアが実装を担当するという、比較的オーソドックスな体制です。 具体的な開発フローとしては、まずPdMが事業側からの要望やユーザー課題を整理し、デザイナーと連携しながらUI/UXを固めていきます。デザインデータが完成次第、バックエンド・フロントエンドの各エンジニアと連携しながら仕様の細部を固めつつ実装に進みます。開発が完了したらQAによるテストを経てリリースするという流れで日々の開発が行われています。 もちろん、各エンジニアチームには通常の機能開発以外にも、技術負債の解消やライブラリのアップデート、パフォーマンス改善といった保守運用系のタスクが常にあります。また、デザインチームについては、FAANSだけでなく複数のプロダクトやプロジェクトを掛け持ちしているので、タイミングによってはリソースの確保に事前調整が必要になります。 そんな事情もあり、普段の開発では「バックエンド・フロントエンド・デザインのタイミングが揃わないとなかなか動き出せない」というのが暗黙の前提になっていました。 直面していた課題 直面していた課題を振り返ると、バックエンドの領域にタスクが継続的に集中している状況がありました。バックエンドはソフトウェア開発の屋台骨ともいえる存在であり、優先度の高い機能改修や緊急性の高い対応が日常的に発生していました。そうした背景もあり、バックエンドチームは常に目の前のタスクに追われ、新しい取り組みに十分な工数を割く余裕がなかなか生まれない状態になっていました。デザインチームについても、複数案件の並行対応や人数的な制約があり、スピード感をもって新しい挑戦に取り組むことが難しい場面も少なくありません。 一方で、フロントエンドチームには時期によって余力のあるメンバーが出てくることもあり、FAANS開発チーム全体としてはリソースに偏りが生じていました。 その結果、新しい取り組みをしたくても「バックエンドが動けない」「デザインが動けない」といった理由から後回しになるケースが常態化しつつありました。競争環境が変化し続ける中で、検証やチャレンジのタイミングが遅れることは大きなリスクにもつながります。 こうした背景があり「フロントエンド側の余力を戦略的に活用し、バックエンドやデザインに依存しない形で何か開発が進められないか」という問いが生まれてきました。この課題意識を起点に、PoC専用の開発レーンを設ける構想へとつながっていきました。 チーム 現状の課題と状況 バックエンド 優先度の高いタスクが多く、新規開発のリソース確保が難しい デザイン 複数案件の並行対応や人数的な制約がありリソース確保に調整が必要 フロントエンド タイミングによっては一部メンバーに余裕があり、比較的自由に動きやすい PoC専用開発レーンの設計 これまでの課題を踏まえると、バックエンドやデザインのリソースが空くのを待つ前提では、新しい取り組みの検証スピードを上げられません。アイデアを素早く試し、学習サイクルを回すためには、既存の開発フローとは独立した仕組みが必要でした。 そこで通常の開発ラインとは切り離し、PoCに特化した専用レーンを新たに設けることにしました。既存フローの中にPoCを組み込むと、本番開発との優先順位の競合が避けられず、緊急度の高いタスクが入るたびにPoCは先送りされてしまいます。専用のレーンを用意することで、本番開発への影響を抑えつつ、検証を並行して進められる体制を整えました。 このレーンでは、スコープを「フロントエンドのみで完結するプロトタイプ」に限定しています。モックやスタブを活用し、バックエンドが未整備でも動作する仕組みを用意することで、依存の大きい領域を待たずに検証を進められるようにしました。本来バックエンドが担う処理も、「フロントエンド側でどこまで代替できるか」という観点で柔軟に検証しています。 また、デザインの領域においても、UIの品質や体験の一貫性を損なわないよう、既存のデザインルールをベースにする最低限の設計指針を設けています。加えて、作業の効率化と初期検討の加速を目的に、AIツールの活用も推奨しています。こうした工夫により、限られた体制でも一定の品質を担保しながら、スピーディに検証を進められるように設計しています。 PoCで想定しているケースとしては、UIレベルでの新機能の仮説検証、フェイクデータを用いたUXインタラクションの確認、AI関連機能のプロトタイピングなどです。体制はPMとフロントエンドエンジニアのみというシンプルな構成にし、チーム内から「試したい機能」や「気になっている仮説」を広く募集する運用としています。 PoCの意義を定義する PoCを導入するにあたり、まず「一般的なPoCの理解」と「チームとして実施するPoC」を明確に区別するところから始めました。名前は同じでも、前提としている目的やゴールが微妙に異なり、その認識がずれると期待値の齟齬が生まれます。この差分をチームで揃え、エンジニアリングの境界をクリアにするところから着手しました。 1. 一般的なPoCの理解 一般的には、PoCは次のように捉えられがちです。 本番実装に向けた“前段階” → 小さく作って、最終的にそのままプロダクションに向かう前提で考えられることが多い。 ある程度完成度の高い検証物を作るもの → 動くものを作り、そこでユーザーの反応や技術的な実現性を検証するイメージ。 本番実装に“進むため”の判断材料 → 基本的には「GO」することを前提としたマイルストーン扱いになりやすい。 こうした一般像だと、「どの程度作る必要があるのか」「どのレベルの完成度が必要か」が曖昧なまま進み、工程が重くなるケースが多いことが課題です。 2. チームで定義したPoC これに対して、私たちはPoCの役割をより軽量で判断特化に寄せて定義し直しました。 本番実装を“やるかどうか”を判断する活動 → PoCは「やる/やらない/いつやるか」を決めるための材料集め。 “作らないことで得られる利益”も検証対象 →「これはやらないほうがいい」と低コスト・短時間で判断できることもPoCの成果。 検証物は捨てるつもりで最小限必要なものだけを作る → 動くものは必要最小でOK。目的は決定の前倒し。 正解より“判断の質”を重視 → 結果「今回は採用しない」に至った場合も“成功”と捉える。 1. 一般的なPoCの理解 2. チームで定義したPoC 目的・位置づけ 本番実装に向けた「前段階」 実装要否の「判断材料」 前提スタンス 本番化が前提 やらないのも重要な成果 アウトプット 完成度の高い動くモノ 判断ができる最小限の動くもの 重視すること 技術検証、ユーザー反応 早期の意思決定・判断の質 重要なのは、“作ること”そのものではなく、“判断に必要な材料を短期間で揃えること”です。この考え方をエンジニアと共有し、PoCでは「検証物は捨てるつもりで、判断に必要な最小限だけを作る」という原則をチーム内で統一しました。 「最小限って、どこまで?」という議論も当然ありましたが、最終的には“振る舞いだけ掴めれば十分。作りすぎはむしろ困る”という、普段の開発ではまず出てこないPoCらしい落とし所に落ち着きました。PoCで求められる完成度は、体験の“手触り”を得られるかどうかであり、本番品質を追い始めると、スピードの面でも検証の本筋から外れてしまうことがあります。 仮に検証の結果として「今回は採用しない」という判断に至ったとしても、それは失敗ではなく、「低コストで正しい選択ができた」という意味で成功と捉えています。事実、本格的な実装へ踏み込む前に見極めができれば、時間・工数ともに大きく節約できるため「やらない」という判断は大きな価値のひとつです。 また、この価値観をチームで共有しておくことで、PoCが“前向きに失敗できる場”として機能し、アイデアを出す側も過度に結果を恐れずより多くの選択肢を素早く試せるようになります。 こうした方針により、PoCは「やる・やらない・いつやるか」といった意思決定を前倒しし、本番開発の優先順位づけにも役立つ仕組みとして設計しました。 具体的な開発フロー PoC開発は大きく4つのフェーズで進める設計にしています。 最初のフェーズは「企画・要件定義」です。PdMがPoCの目的、検証したい仮説、検証観点、完了基準を定義します。ここで大事なのは、「何を検証するのか」をあらかじめ明確にしておくことです。PoCにおいてはある程度の曖昧さを許容して動き出すことがポイントではありますが、最低限何が知りたいのかはクリアにしておく必要があります。 なお、PoCのアイデア自体はエンジニアと一緒に検討しており、「こんな機能があったらいいな」「これ試してみたい」といった声を広く募集しています。現場で感じる課題やユーザーの声に近いところからアイデアが生まれることもあれば、技術ドリブンで試してみたいという動機から始まることもあります。どの入口からの提案であっても歓迎できるよう、気軽にアイデアを出せる雰囲気を大切にしています。 次のフェーズは「事前準備・スコープ設計」です。本当に検討する価値があるかを精査した上で、各プラットフォーム(Web/iOS/Android)の担当を割り当てます。技術的に検証の必要な項目があれば明文化し、大まかなスケジュールを引いておきます。 3つ目のフェーズが「開発・仮説検証」です。ここでUI実装、モック接続、動作確認を進めていきます。限られた工数の中で最大限の学びを得ることが目的なので、完璧を目指すよりも「触って確認できる状態」を優先します。できあがったUIの共有方法は各プラットフォームの担当におまかせして、柔軟かつライトに進められるよう設計しています。 最後のフェーズは「レビュー・評価」です。関係者を集めて実際に操作しながらレビューし、この機能をどうしていくか最終的に判断します。同時に、今回の検証で得られたナレッジや課題、気づきも洗い出して記録に残していきます。 得られた成果 PoC開発を進める中で、これまで手をつけづらかったUX改善にも取り組めました。検証だけで終わらず、実際にリリースされている機能もあり、この取り組みで生まれたアイデアがプロダクト品質の向上に寄与しています。 通常の開発では、KPIに直結する機能や顧客要望への対応が優先されやすく、ユーザー体験を整えるための改善はどうしても後回しになりがちです。PoCでは、優先度は高くないものの価値のあるテーマにも一定の時間とリソースを充てられており、細かな体験改善や将来のプロダクトにつながるアイデアの検討が進んでいます。こうした取り組みを通じて、中長期的な価値に寄与する領域にも少しずつ着手できている実感があります。 また、PoCによってプロトタイプという形で実際に手を動かす中で、チーム全体があらためてUXの重要性や改善の余地を実感できたことも、今後の開発方針に影響を与える大きな収穫でした。PoCは、単なる機能検証の場ではなく、自由度の高さを活かしてプロダクトの“未来の選択肢”を広げるための戦略的な取り組みとして、今後も活用していきたいと考えています。 実施して出てきた課題 PoCを始めてみると、うれしい誤算として多くのアイデアが寄せられました。ただ、数が増えるほど「どれから検証すべきか」という優先順位づけが難しくなる課題も見えてきました。さらに、寄せられる案の中にはPoCというより通常の機能改修に近いものも混ざっていて、本来の “意思決定のための検証” という意図からズレる場面も出てきました。 そもそもPoCは、開発フローの設計も含め、やりながら整えていく側面が強く、扱うテーマも抽象度が高い取り組みです。そのため、進行中に生まれる小さな曖昧さが積み重なり、PoCとして扱う範囲の線引きが見えにくくなりやすい性質があります。こうした境界の揺らぎは、優先順位の判断をより難しくする要因にもなっていました。 この課題については、いまもベストプラクティスを探っている段階です。どこまで曖昧さを残し、どこから整理していくべきかを、進めやすさや開発スピードとのバランスをとりながら検討しています。 一例として、検証テーマの粒度や、PoCで扱う論点の範囲を最初に軽く決めておくだけでも、後の判断や優先順位づけが格段にしやすくなります。ただ、初期段階で基準を固めすぎてしまうと、PoCの強みである柔軟な探索の余地をかえって狭めてしまう恐れもあります。こうしたトレードオフを踏まえ、実務で運用しやすい落とし所を模索している状況です。 PoC開発をより扱いやすく、価値につながりやすい形へ育てていくためにも、試行錯誤を続けながら、少しずつより良い仕組みに寄せていきたいと考えています。 まとめ この記事ではFAANS開発チームで取り組むPoC開発についてご紹介しました。 この取り組みはまだ発展途上ですが、チームとして小さな改善を積み重ねる中で、着実に機能し始めています。重要なのは、一度仕組みを作って終わりにするのではなく、実際の検証プロセスから得られた気づきを元に柔軟にアップデートし続けることです。早く試し、早く学び、的確に「やる/やらない」を判断できる体制は、長期的な競争力にも直結します。 今後もチーム全体で学びを共有しながら、PoCがより価値を生む仕組みになるよう改善を続けていきたいと考えています。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからご応募ください。 corp.zozo.com
.table-of-contents > li > ul { display: none; } はじめに こんにちは、EC基盤開発本部SRE部カート決済SREブロックの金田です。 普段はSREとしてZOZOTOWNのカート決済機能のリプレイスや運用を担当しています。本記事では夜間・休日にインスタンス数を0にスケールされた開発環境を、Slackから起動できるツール「sc8leon(スケールオン)」について紹介します。開発環境のゼロスケール運用で同様の課題を抱えている方の参考になれば幸いです。 目次 はじめに 目次 背景・課題 背景 課題 sc8leonの概要 機能一覧 アーキテクチャ 実装 使用技術 設定ファイル 設定ファイルの構造 依存関係の管理 マイクロサービスの起動/停止 Lambda環境でのGit操作 マニフェストの編集 GitHub Apps認証 GitHub Appsによる自動マージ AuroraClusterの起動/停止 スケジュール機能 Schedule Start(起動時間の前倒し) Schedule Stop(停止時間の延期) 自動リセット 使い方 操作手順 導入効果 まとめ 背景・課題 背景 ZOZOTOWNの開発環境は、コスト削減を目的として夜間・休日にKEDAを利用したゼロスケールを導入しています。EKS上で稼働するPodのレプリカ数を0にするとともに、関連するAWSリソース(主にAuroraCluster)も停止することでインフラコストを削減しています。 ZOZOTOWNの開発環境におけるコスト最適化については、以下の資料でも詳しく紹介しています。併せてご覧ください。 speakerdeck.com この夜間・休日の停止を検討した際、開発環境を主に使用する開発部署から以下のような懸念が挙がりました。 日跨ぎに関連する処理のリリース前に開発環境での動作確認ができない 夜間・休日に本番障害が発生した際、緊急リリースに向けた開発環境での検証ができない 早朝から業務を開始する際や休日出勤時に開発環境が使えない これらの懸念を解消することが夜間・休日停止の導入条件となったため、必要なときに誰でも環境を起動できるツールの開発が必要になりました。 課題 ZOZOTOWNのバックエンドは多数のマイクロサービスが協調して動作しています。各マイクロサービスを稼働させているEKS環境は「 CIOpsからGitOpsへ。Flux2でマイクロサービスのデプロイを爆速にした話 」で紹介しているようにFluxによるGitOpsを導入しており、Gitリポジトリの状態がクラスタに同期されます。そのため、kubectlコマンドによるオブジェクトの直接変更ができず、ゼロスケールの解除には以下の作業が必要でした。 各マイクロサービスのScaledObjectに設定されているMinReplicaを変更するマニフェスト修正 Pull Requestの作成とレビュー依頼 マージ後、Fluxによるクラスタへの同期を待機 AuroraClusterはAWSコンソールから手動で起動 この手順には以下の課題がありました。 課題 詳細 運用負荷 深夜・休日にマニフェスト修正とPR作成を行う必要があり、PRのレビュー・承認も含めて最低2名の対応が必要になる 対象の多さ 多数のマイクロサービスを起動する必要があり、手動での対応は現実的ではなく、対応漏れのリスクもある 属人化 マニフェストの修正方法やAuroraClusterの起動手順を知っている人しか対応できない 戻し忘れのリスク 起動後にMinReplicaを元に戻すPRを忘れると、翌日以降もゼロスケールが解除されたままになる これらの課題を解決するために、誰でもSlackから簡単に環境を起動でき、自動でリセットされるツール「sc8leon」を開発しました。 sc8leonの概要 sc8leonは、ゼロスケールされた開発環境をSlackから起動できるツールです。Slack Workflowから実行でき、環境の起動からリセットまでを自動化しています。 機能一覧 sc8leonは以下の5つの機能を提供しています。 機能名 説明 ユースケース Start ゼロスケールされている環境の即時起動 深夜に障害が発生し、開発環境で修正確認を行いたい Stop 起動中の環境を停止 動作確認が完了したので環境を停止する Schedule Start 起動時間のリスケジュール 翌朝6時から業務を開始するため、5:30に起動時間を設定する Schedule Stop 停止時間のリスケジュール 日跨ぎの動作確認を行うため、深夜2時まで停止時間を延長する Status 起動状況、起動/停止スケジュールの可視化 現在の環境の状態やスケジュールを確認する アーキテクチャ sc8leonのアーキテクチャ sc8leonは以下のコンポーネントで構成されています。 コンポーネント 役割 Slack Workflow ユーザーからの操作を受け付け、Lambdaを呼び出す AWS Lambda sc8leonのメイン処理を実行 Amazon EventBridge Scheduler 起動/停止/リセットのスケジュール実行 AWS Secrets Manager GitHub Appsの認証情報を保存 GitHub Apps マニフェスト変更のPR作成・マージを自動化 Flux Gitリポジトリの変更をEKSクラスタに同期 実装 使用技術 sc8leonは以下の技術スタックで実装しています。 カテゴリ 技術 言語 Go CLIフレームワーク urfave/cli 実行環境 AWS Lambda スケジューラ Amazon EventBridge Scheduler AWS SDK aws-sdk-go-v2 Git操作 go-git GitHub API go-github YAML操作 sigs.k8s.io/yaml 設定ファイル 背景で述べた「対象の多さ」という課題に対して、sc8leonでは操作対象となるマイクロサービスとAuroraClusterを設定ファイルで事前に定義しています。これにより、ユーザーは環境を指定するだけで、関連するすべてのリソースを一括で起動・停止できます。 設定ファイルは環境ごとに用意しており、 go:embed を使用してバイナリに埋め込んでいます。そのため、対象の追加・変更時は再ビルドが必要です。 //go:embed config/*.yaml var configFiles embed.FS func LoadConfig(target string ) (*TargetSetting, error ) { data, err := configFiles.ReadFile(fmt.Sprintf( "config/%s" , target)) if err != nil { return nil , err } var setting TargetSetting yaml.Unmarshal(data, &setting) return &setting, nil } 設定ファイルの構造 設定ファイルには service (マイクロサービス)と rdscluster (AuroraCluster)の2種類のリソースを定義できます。 マイクロサービスの設定例 services : - type : service namespace : zozotown-api serviceName : zozotown-api manifest : scaledObject : path : path/to/scaledobject.yaml compactSeqIndent : true deployment : path : path/to/deployment.yaml timestampKeyName : last-canary-retry-timestamp compactSeqIndent : false dependencies : - type : rdscluster name : zozotown-rds-cluster フィールド 説明 namespace Kubernetesのnamespace serviceName サービス名 manifest.scaledObject.path ScaledObjectマニフェストのファイルパス manifest.scaledObject.compactSeqIndent YAML出力時のリストインデント設定 manifest.deployment.path Deploymentマニフェストのファイルパス manifest.deployment.timestampKeyName Flaggerの更新をトリガーするためのアノテーションキー名 dependencies 依存するリソース(起動順序の制御に使用) ZOZOTOWNのEKS環境にはFlaggerも導入されています。 timestampKeyName は、Deploymentのアノテーションにタイムスタンプを付与してPodの再作成を促すために使用しています。マニフェストの変更だけではFlaggerによるカナリアデプロイがトリガーされない場合に利用します。 ZOZOTOWNでのFlagger導入については、以下の記事も参照してください。 techblog.zozo.com AuroraClusterの設定例 services : # 同一AWSアカウント内のAuroraCluster - type : rdscluster clusterID : zozotown-rds-cluster startScheduleName : zozotown-aurora-start originStartTime : cron(30 6 ? * MON-FRI *) stopScheduleName : zozotown-aurora-stop originStopTime : cron(0 0 ? * TUE-SAT *) # 別AWSアカウントのAuroraCluster - type : rdscluster awsAccountID : "123456789012" role : sc8leon-access-role clusterID : other-account-cluster startScheduleName : other-account-cluster-aurora-start originStartTime : cron(0 7 ? * MON-FRI *) stopScheduleName : other-account-cluster-aurora-stop originStopTime : cron(0 0 ? * TUE-SAT *) フィールド 説明 awsAccountID 別AWSアカウントの場合のアカウントID role 別AWSアカウントの場合のAssumeRole用ロール名 clusterID AuroraClusterのクラスタID startScheduleName 起動用EventBridge Schedulerのスケジュール名 originStartTime 元の起動時間(リセット時に復元する値) stopScheduleName 停止用EventBridge Schedulerのスケジュール名 originStopTime 元の停止時間(リセット時に復元する値) originStartTime と originStopTime は、sc8leonのリセット処理でEventBridge Schedulerのスケジュールを復元する際に使用します。 依存関係の管理 マイクロサービスには dependencies を設定でき、起動時は依存先を先に起動し、停止時は依存元を先に停止します。これにより、DBに依存するマイクロサービスが起動した際にDBがまだ起動していないといった問題を防いでいます。 マイクロサービスの起動/停止 KEDAを使用したゼロスケールでは、ScaledObjectリソースの minReplicaCount を0に設定します。cron triggerで勤務時間中のみ desiredReplicas を1以上に指定し、勤務時間外はcron triggerが無効になります。これにより、 minReplicaCount の設定に従ってPodのレプリカ数が0になります。 sc8leonでは、この minReplicaCount の値を変更することでマイクロサービスの起動/停止を制御しています。 起動 : minReplicaCount を1以上に変更(実際の実装ではKEDAのcron triggerに設定された desiredReplicas と同じ値に変更) 停止 : minReplicaCount を0に変更 FluxによるGitOpsを導入しているため、以下の流れでクラスタに反映されます。 go-gitでリポジトリをクローンし、ブランチを作成 マニフェストファイルを編集 コミット・プッシュ GitHub Apps経由でPull Requestを作成 CIの完了を待機後、Pull Requestをマージ Fluxがマージを検知し、クラスタに同期 Lambda環境でのGit操作 sc8leonでは、go-gitとgo-billyを使用してGitのストレージとファイルシステムの両方をインメモリで扱っています。 func NewGitClient(repositoryPath, repositoryName string , awscli *AWSClient) (*GitClient, error ) { tokenSrc, _ := FetchToken(awscli) token, _ := tokenSrc.Token() // ファイルシステムをインメモリで作成 fs := memfs.New() // Gitのストレージもインメモリで作成し、shallow cloneを実行 r, err := git.Clone(memory.NewStorage(), fs, &git.CloneOptions{ URL: fmt.Sprintf( "https://x-access-token:%s@github.com/st-tech/%s.git" , token.AccessToken, repositoryName), ReferenceName: plumbing.ReferenceName( "refs/heads/master" ), SingleBranch: true , Depth: 1 , }) return &GitClient{repository: r, FS: fs}, nil } ポイントは以下の3点です。 設定 説明 memory.NewStorage() Gitオブジェクト(コミット、ツリー等)をインメモリで管理 memfs.New() ワーキングツリー(ファイル)をインメモリで管理 SingleBranch: true, Depth: 1 shallow cloneにより、必要最小限のデータのみ取得 マニフェストの編集 マニフェストの編集では、YAMLをAST(抽象構文木)としてパースし、 minReplicaCount の値を直接変更しています。 func EditMinReplicaCount(node *yaml.Node, replicaCount string ) { updateMappingNode(node, [] string { "spec" }, "minReplicaCount" , replicaCount) } GitHub Apps認証 sc8leonではGitHub Appsを使用してGitHub APIを操作しています。GitHub Appsの認証情報(App ID、Installation ID、秘密鍵)はAWS Secrets Managerに保存し、Lambda関数から取得しています。 Lambda環境では、 AWS Parameters and Secrets Lambda Extension を使用してSecrets Managerから認証情報を取得しています。このExtensionはローカルHTTPエンドポイント( localhost:2773 )を提供し、Secrets Managerへのアクセスをキャッシュします。これにより、APIコール数の削減とレイテンシの改善を実現しています。 func GetSecretValueWithLambdaExtension(secretName string ) ( string , error ) { secretExtensionEP := fmt.Sprintf( "http://localhost:2773/secretsmanager/get?secretId=%s" , secretName) headers := map [ string ] string { "X-Aws-Parameters-Secrets-Token" : os.Getenv( "AWS_SESSION_TOKEN" ), } client := &http.Client{} req, _ := http.NewRequest( "GET" , secretExtensionEP, nil ) for key, value := range headers { req.Header.Set(key, value) } resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var secretValue GetSecretValueOutput json.Unmarshal(body, &secretValue) return secretValue.SecretString, nil } 取得した認証情報を使用して、GitHub AppsのInstallation Tokenを生成します。このトークンはGit操作(clone、push)とGitHub API操作(PR作成、マージ)の両方で使用します。 func FetchToken(awscli *AWSClient) (oauth2.TokenSource, error ) { appValues, _ := getGitHubAppSecret(awscli) appid, _ := strconv.ParseInt(appValues.AppID, 10 , 64 ) appTokenSource, _ := githubauth.NewApplicationTokenSource(appid, [] byte (appValues.PrivateKey)) instid, _ := strconv.ParseInt(appValues.InstallationID, 10 , 64 ) return githubauth.NewInstallationTokenSource(instid, appTokenSource), nil } Installation Tokenは有効期限が1時間であるため、処理完了後は RevokeToken でトークンを無効化し、セキュリティリスクを軽減しています。 GitHub Appsによる自動マージ 課題で述べた「運用負荷」と「属人化」を解決するために、GitHub Appsを使用してPRの作成からマージまでを自動化しています。 通常、マニフェストを管理しているリポジトリのmasterブランチにはブランチ保護ルールが設定されており、PRのマージには最低1名のApproveが必要です。しかし、GitHub Appsが生成したトークンでは、同じGitHub AppsによるApproveができないという制約があります。 この制約を回避するために、ブランチ保護ルールの「Allow specified actors to bypass required pull requests」設定を有効にしています。この設定にsc8leon用のGitHub Appsを追加することで、sc8leonが作成したPRはApproveなしでマージが可能になります。 func (c *GitHubAPIClient) CreatePullRequest(owner, repo, base, head, title, body string ) (*github.PullRequest, error ) { pr, resp, err := c.client.PullRequests.Create(c.ctx, owner, repo, &github.NewPullRequest{ Title: github.String(title), Body: github.String(body), Base: github.String(base), Head: github.String(head), }) return pr, nil } func (c *GitHubAPIClient) MergePullRequest(owner, repo string , number int ) (*github.PullRequestMergeResult, error ) { result, resp, err := c.client.PullRequests.Merge(c.ctx, owner, repo, number, "" , &github.PullRequestOptions{}) return result, nil } また、PRのマージ前にCIが完了するのを待機しています。これにより、CIでエラーが検出された場合はマージされません。 func (c *GitHubAPIClient) WaitMergeUntilWIPComplete(owner, repo, branchName string , timeout int ) error { for { result, _, _ := c.client.Checks.ListCheckRunsForRef(c.ctx, owner, repo, fmt.Sprintf( "heads/%s" , branchName), &github.ListCheckRunsOptions{CheckName: github.String( "WIP" )}) checkrun := result.CheckRuns[ 0 ] switch checkrun.GetStatus() { case "queued" , "in_progress" : time.Sleep( 5 * time.Second) default : return nil } } } AuroraClusterの起動/停止 AuroraClusterの起動/停止は、AWS SDK for Go v2を使用してRDS APIを直接呼び出しています。 func (c *AWSClient) StartRDSCluster(setting *RDSClusterSetting) ( string , error ) { rdscli, ok := c.rds[setting.AWSAccountID] if !ok { rdscli = c.rds[ "default" ] } out, err := rdscli.StartDBCluster(c.ctx, &rds.StartDBClusterInput{ DBClusterIdentifier: aws.String(setting.ClusterID), }) if err != nil { return "" , err } return *out.DBCluster.Status, nil } 複数のAWSアカウントにまたがるAuroraClusterを操作する必要があるため、設定ファイルにAWSアカウントIDとAssumeRole用のRole ARNを記載しています。これらの情報を使用して、アカウントごとにRDSクライアントを生成しています。 スケジュール機能 起動時間や停止時間のリスケジュールには、 Amazon EventBridge Schedulerのone-time schedules を使用しています。one-time schedulesは指定した時間に一度だけ実行され、 ActionAfterCompletion: Delete の設定により実行後に自動で削除されます。 func (c *AWSClient) CreateScheduleForLambda(scheduleName, scheduleExpression, lambdaFunction, payload string ) error { _, err := c.scheduler[ "default" ].CreateSchedule(c.ctx, &scheduler.CreateScheduleInput{ FlexibleTimeWindow: &schedulertypes.FlexibleTimeWindow{ Mode: schedulertypes.FlexibleTimeWindowModeOff, }, Name: aws.String(scheduleName), ScheduleExpression: aws.String(scheduleExpression), ScheduleExpressionTimezone: aws.String( "Asia/Tokyo" ), Target: &schedulertypes.Target{ Arn: aws.String(fmt.Sprintf( "arn:aws:lambda:%s:%s:function:%s" , ...)), RoleArn: aws.String(fmt.Sprintf( "arn:aws:iam::%s:role/..." , ...)), Input: aws.String(payload), }, ActionAfterCompletion: schedulertypes.ActionAfterCompletionDelete, }) return err } Schedule Start(起動時間の前倒し) 早朝から環境を使いたい場合に、起動時間を前倒しで予約する機能です。指定された時間にStart処理を実行するone-time scheduleを登録します。 func scheduleStart(env Env, startTime *time.Time) (ScheduleStartResult, error ) { // ...省略... scheduleName := "sc8leon-start" scheduleExpression := fmt.Sprintf( "at(%s)" , startTime.Format( "2006-01-02T15:04:05" )) payload := fmt.Sprintf( `{"env": "%s", "command": "start"}` , env) if err = awscli.CreateScheduleForLambda(scheduleName, scheduleExpression, "sc8leon" , payload); err != nil { return ScheduleStartResult{}, errors.Join(err, errors.New( "failed to create eventbridge schedule" )) } // リセット処理のスケジュールも登録 registerResult, err := RegisterResetFunc(env, awscli, *startTime) // ...省略... } Schedule Stop(停止時間の延期) 日跨ぎの動作確認など、通常の停止時間を超えて環境を使いたい場合に、停止時間を延期する機能です。Schedule Stopでは以下の3つのone-time scheduleを登録します。 スケジュール 実行タイミング 処理内容 停止延期処理 本来の停止時間(0:00)の30分前 マイクロサービスの minReplicaCount を維持、AuroraClusterの停止スケジュールを変更 停止処理 指定した停止時間 マイクロサービスとAuroraClusterを停止 リセット処理 翌営業日のAM10:00 設定を元の状態に戻す 停止延期処理を本来の停止時間の30分前に実行するのは、KEDAのcron triggerが0:00に minReplicaCount を0にしてPodを削除するためです。その前に minReplicaCount を1以上に変更し、Podの削除を防ぎます。また、30分という時間は、PR作成・マージ・Fluxによるクラスタへの同期が完了するのに十分な余裕を持たせています。 func scheduleStop(env Env, stopTime *time.Time) (ScheduleStopResult, error ) { // ...省略... // 停止延期処理: 本来の停止時間の30分前に実行 expandScheduleName := "sc8leon-expand-stop-time" expandScheduleExpression := fmt.Sprintf( "at(%s)" , time.Date(stopTime.Year(), stopTime.Month(), stopTime.Day(), 0 , 0 , 0 , 0 , stopTime.Location()). Add(- 30 *time.Minute).Format( "2006-01-02T15:04:05" )) expandSchedulePayload := fmt.Sprintf( `{"env": "%s", "command": "expand-stop-time", "schedule_time": "%s"}` , env, stopTime.Format( "2006/01/02 15:04:05" )) if err = awscli.CreateScheduleForLambda(expandScheduleName, expandScheduleExpression, "sc8leon" , expandSchedulePayload); err != nil { return ScheduleStopResult{}, errors.Join(err, errors.New( "failed to create eventbridge schedule" )) } // 停止処理: 指定された時間に実行 stopScheduleName := "sc8leon-stop" stopScheduleExpression := fmt.Sprintf( "at(%s)" , stopTime.Format( "2006-01-02T15:04:05" )) stopSchedulePayload := fmt.Sprintf( `{"env": "%s", "command": "stop"}` , env) if err = awscli.CreateScheduleForLambda(stopScheduleName, stopScheduleExpression, "sc8leon" , stopSchedulePayload); err != nil { return ScheduleStopResult{}, errors.Join(err, errors.New( "failed to create eventbridge schedule" )) } // リセット処理のスケジュールも登録 registerResult, err := RegisterResetFunc(env, awscli, *stopTime) // ...省略... } 停止延期処理では、マイクロサービスの minReplicaCount を維持するだけでなく、AuroraClusterのEventBridge Schedulerの停止時間も変更します。 func changeScheduleRDSCluster(clusterSetting *RDSClusterSetting, awscli *AWSClient, stopTime time.Time) (ChangeScheduleRDSClusterResult, error ) { scheduleExpression := fmt.Sprintf( "cron(%d %d ? * MON-FRI *)" , stopTime.Minute(), stopTime.Hour()) if err := awscli.UpdateEventBridgeSchedule(clusterSetting.AWSAccountID, clusterSetting.StopScheduleName, scheduleExpression); err != nil { return ChangeScheduleRDSClusterResult{}, errors.Join(err, errors.New( "failed to update eventbridge schedule" )) } return ChangeScheduleRDSClusterResult{ ScheduleName: clusterSetting.StopScheduleName, ScheduleExpression: scheduleExpression, }, nil } 自動リセット sc8leonで変更した設定は、翌営業日に自動でリセットされます。これにより、戻し忘れによるゼロスケール解除状態の継続を防いでいます。 func RegisterResetFunc(env Env, awscli *AWSClient, resetTime time.Time) (RegisterResetFuncResult, error ) { scheduleName := fmt.Sprintf( "sc8leon-reset-%s" , resetTime.Format( "20060102" )) // 既に同日のリセットスケジュールが登録されている場合はスキップ sc, err := awscli.GetSchedule( "default" , scheduleName) if err == nil && sc != nil { return RegisterResetFuncResult{AlreadyRegisterd: true , ...}, nil } // 平日はAM10:00、土日はPM10:00にリセット resetHour := 10 if resetTime.Weekday() == time.Saturday || resetTime.Weekday() == time.Sunday { resetHour = 22 } scheduleExpression := fmt.Sprintf( "at(%s)" , time.Date(..., resetHour, 0 , 0 , 0 , ...).Format( "2006-01-02T15:04:05" )) payload := fmt.Sprintf( `{"env": "%s", "command": "reset"}` , env) awscli.CreateScheduleForLambda(scheduleName, scheduleExpression, "sc8leon" , payload) return RegisterResetFuncResult{...}, nil } リセット処理では以下を実行します。 マイクロサービス : minReplicaCount を0に戻すPRを作成・マージ AuroraCluster : EventBridge Schedulerの起動/停止時間を設定ファイルに記載された元の値に戻す 使い方 sc8leonはSlack Workflowから実行できます。環境ごとに専用のSlackチャンネルが用意されており、ワークフローを選択して実行するだけで環境の起動・停止が可能です。 Slack WorkflowとLambdaを組み合わせたChatOpsの仕組みについては、以下の記事も参照してください。 techblog.zozo.com 操作手順 該当環境のSlackチャンネルでワークフローを選択(例: sc8leon-start-dev ) ポップアップで対象環境を選択し、送信ボタンを押下 ワークフローが投稿したメッセージのスレッドで「Run command」を選択 実行完了後、Slackに結果が通知される 各機能(Start / Stop / Schedule Start / Schedule Stop / Status)で同様の手順で操作できます。Schedule Start / Schedule Stopでは、起動・停止したい日時も選択します。 導入効果 sc8leonの導入により、以下の効果が得られました。 課題 導入前 導入後 運用負荷 マニフェスト修正とPR作成・レビューで最低2名の対応が必要 Slackから1人で即時実行可能 対象の多さ 多数のマイクロサービスを手動で起動、対応漏れのリスク 設定ファイルで一括管理、自動で全サービスを起動 属人化 マニフェスト修正方法を知っている人しか対応できない 誰でもSlackから操作可能 戻し忘れ MinReplicaを元に戻すPRを忘れるリスク 翌営業日に自動リセット 特に、深夜・休日の緊急対応時において開発者が自身で環境を起動できるようになったことで、SREへの依頼待ち時間がなくなり、迅速な対応が可能になりました。 まとめ 本記事では、ゼロスケールされた開発環境をSlackから起動できるツール「sc8leon」を紹介しました。 GitOpsを導入した環境では、kubectlによる直接変更ができないため、マニフェスト修正→PR作成→マージという手順が必要でした。sc8leonはこの手順を自動化し、GitHub Appsによる認証とLambda上でのインメモリGit操作を組み合わせることで、Slackからの簡単な操作で環境を起動できるようにしました。 また、EventBridge Schedulerのone-time schedulesを活用し、起動時間の前倒しや停止時間の延期といったスケジュール機能を実現しています。さらに、翌営業日に自動でリセットされるため、戻し忘れを防止できます。 GitOps環境でマニフェスト変更を自動化する際のポイントは、GitHub Appsによる認証とブランチ保護ルールのバイパス設定、そしてLambda環境でのGit操作です。これらを組み合わせることで、人手を介さずにPRの作成からマージまでを完結させることができます。 sc8leonの導入により、深夜・休日でも開発者自身が環境を起動できるようになり、SREへの依頼待ち時間がなくなりました。開発環境のゼロスケール運用において、運用負荷と利便性のバランスを取る1つの解決策として参考になれば幸いです。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
.table-of-contents > li > ul { display: none; } はじめに こんにちは、ZOZOMO部SREブロックの中村です。普段は ZOZOMO のSREを担当しています。 本記事では、ECS on FargateにPipeCDを導入してGitOpsベースのデプロイ基盤を構築した取り組みをご紹介します。デプロイ経路の複数存在による管理の煩雑さと、段階的デプロイができない課題をPipeCDでどのように解決したかを解説します。 本記事がECS on Fargateを運用していてGitOps化に興味がある方や、PipeCDの導入を検討している方の参考になれば幸いです。 目次 はじめに 目次 導入前の課題 デプロイ経路の複数存在による管理の煩雑さ 段階的デプロイができない 解決アプローチ PipeCDとは 導入構成 実装 Control Planeの構築 Control Planeの初期設定 Pipedの構築 マニフェストファイルの構成 アプリケーションの登録 デプロイの実行 Webhook連携による自動テスト実行 導入効果 デプロイ経路の一本化 段階的リリースによるリスク低減 まとめ 導入前の課題 PipeCD導入前、私たちのチームでは以下の課題を抱えていました。 デプロイ経路の複数存在による管理の煩雑さ 段階的デプロイができない デプロイ経路の複数存在による管理の煩雑さ アプリケーションのタスク定義をデプロイする経路が複数存在していました。アプリ、インフラそれぞれのデプロイの仕方によってデプロイ方法が異なり、どの経路でデプロイされたかの追跡が困難な状況でした。これにより以下の問題が発生していました。 デプロイ履歴の一元管理ができない 障害発生時の原因特定に時間がかかる 予期しないロールバックを引き起こす可能性 段階的デプロイができない 従来の環境では、新しいバージョンのアプリケーションを一度に全てのタスクにデプロイしていました。問題のあるリリースでは全ユーザーに影響するリスクがありました。 カナリアリリースの仕組みがない 問題発生時の影響範囲が大きい ロールバックに時間がかかる 解決アプローチ これらの課題を解決するために、PipeCDの導入を決定しました。 CDツールの選定にあたり、Argo CD、CodePipeline(CodeBuild + CodeDeploy)、PipeCDを比較検討しました。 観点 Argo CD CodePipeline PipeCD Kubernetes対応 サポート 非対応 サポート ECS 対応 非対応 サポート サポート GitOps 対応 非対応 対応 カナリアリリース 対応 対応 対応 Argo CDはEKSを含むKubernetes環境では広く採用されていますが、ECSへのネイティブ対応がありません。CodePipelineはECS on Fargateに対応していますが、GitOpsのようにGitの情報を正とは必ずしも言えないと思います。一方、PipeCDはECSを含むマルチプラットフォームに対応しており、単一のツールでGitOpsとカナリアリリースを実現できる点が決め手となりました。 PipeCDとは PipeCD は、マルチクラウド・マルチプラットフォームに対応した継続的デリバリー(CD)ツールです。GitOpsの原則に基づき、Gitリポジトリをデプロイにおいて信頼できる唯一の情報源(Single Source of Truth)として扱います。 主な特徴としては以下の点が挙げられます。 GitOpsベース : プルリクエストによるデプロイ操作で、変更履歴が明確に管理される マルチプラットフォーム対応 : Kubernetes、ECS、Lambda、Cloud Run、Terraformなど幅広く対応 段階的デプロイ : カナリアリリース、ブルーグリーンデプロイメントを標準サポート セキュリティ : デプロイ用の認証情報がクラスター外に出ない設計 可視化 : デプロイ状況をWeb UIでリアルタイムに確認可能 またPipeCDは、大きく分けてControl PlaneとPipedの2つのコンポーネントで構成されます。 コンポーネント 役割 Control Plane Web UI、APIサーバー、デプロイ状況の管理 Piped 実際のデプロイを実行するエージェント。GitリポジトリとECSを監視し、差分を検知してデプロイを実行 Pipedはデプロイ対象の環境(今回はECS)と同じネットワーク内に配置します。 詳細は以下の PipeCD公式ドキュメント をご参照ください。 Control Plane Piped Concepts 導入構成 今回構築した環境の全体像は以下のとおりです。 ECS on FargateへのPipeCD導入アーキテクチャ Control Plane : 専用のECSで稼働 Piped : デプロイ対象と同じVPC内のECSクラスターで稼働 マニフェスト管理 : 専用のGitHubリポジトリで管理 PipeCDへのログインには、Entra IDを用いたSAML認証を採用しました。弊社ではSSO基盤としてEntra IDを広く利用しており、一元管理されたアクセス管理を実現しています。 実装 Control Planeの構築 Control Planeは以下のコンポーネントで構成され、今回はこのような構成で作成しました。 コンポーネント 役割 今回の構成 Server Web UI、APIサーバー ECS on Fargate Ops 管理用サーバー ECS on Fargate Gateway gRPC/HTTPルーティング Envoy(サイドカー) Cache セッション・ログのキャッシュ Redis(サイドカー) Datastore デプロイ情報の永続化 Aurora MySQL Filestore ログ等のファイル保存 S3 PipeCDの利用を検証した際にそこまで膨大なセッションやログのキャッシュを持たないことから、ElastiCacheを利用するのではなく、サイドカーにRedisを立ててコスト面を抑える構成にしました。ECS上でのControl Plane構築例は PipeCD公式のデモリポジトリ を参考に実装しました。 事前準備 Control Planeを構築する前に、以下のリソースを作成しました。 Aurora MySQL : Datastore用のデータベース S3バケット : Filestore用のバケット ALB : gRPC/HTTP通信用のロードバランサー(Target GroupはgRPC用とHTTP用の2つ) Secrets Manager : 設定ファイルや暗号化キーの保存 ECSタスク構成 Control PlaneのECSタスクを、以下のコンテナで構成しました。 ┌─────────────────────────────────────────────────────┐ │ ECS Task (Control Plane) │ │ ┌─────────────┐ ┌─────────────┐ ┌───────────────┐ │ │ │ Redis │ │ Envoy │ │ PipeCD Server │ │ │ │ (cache) │ │ (gateway) │ │ │ │ │ │ port:6379 │ │ port:9090 │ │ port:9080-9083│ │ │ └─────────────┘ └─────────────┘ └───────────────┘ │ └─────────────────────────────────────────────────────┘ Redis : ログやセッション情報のキャッシュ Envoy : gRPC/HTTPリクエストのルーティング(ALBからのトラフィックを受信) PipeCD Server : Web UIとAPIを提供 設定ファイル Control Planeの設定は control-plane-config.yaml で定義します。 apiVersion : "pipecd.dev/v1beta1" kind : ControlPlane spec : datastore : type : MYSQL config : url : ${DB_USER}:${DB_PASS}@tcp(pipecd-mysql:3306) database : controlplane filestore : type : S3 config : bucket : ${ENV}-${RESOURCE_PREFIX}-pipecd-filestore region : ap-northeast-1 stateKey : ${statekey} sharedSSOConfigs : - name : cognito-azure provider : OIDC oidc : clientId : ${clientId} clientSecret : ${clientSecret} issuer : https://cognito-idp.ap-northeast-1.amazonaws.com/${userPoolId} redirectUri : https://pipecd.${DOMAIN}/auth/callback scopes : - openid - profile 設定ファイルはBase64エンコードしてSecrets Managerに登録し、ECSタスク定義から参照します。詳細は 公式ドキュメント(Configuration reference) をご参照ください。 また、Envoy(Gateway)の設定は envoy-config.yaml で定義します。EnvoyはgRPC/HTTPリクエストをPipeCD Serverの各サービスにルーティングする役割を担います。 admin : address : socket_address : address : 0.0.0.0 port_value : 9095 static_resources : listeners : - name : ingress address : socket_address : address : 0.0.0.0 port_value : 9090 filter_chains : - filters : - name : envoy.filters.network.http_connection_manager typed_config : "@type" : type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager codec_type : AUTO stat_prefix : ingress_http http_filters : - name : envoy.filters.http.grpc_web - name : envoy.filters.http.router typed_config : "@type" : type.googleapis.com/envoy.extensions.filters.http.router.v3.Router route_config : name : local_route virtual_hosts : - name : envoy domains : - '*' routes : # Piped Service(Pipedとの通信) - match : prefix : /grpc.service.pipedservice.PipedService/ grpc : {} route : cluster : grpc-piped-service # Web Service(Web UIからの通信) - match : prefix : /grpc.service.webservice.WebService/ grpc : {} route : cluster : grpc-web-service # API Service(外部APIからの通信) - match : prefix : /grpc.service.apiservice.APIService/ grpc : {} route : cluster : grpc-api-service # その他のHTTPリクエスト(静的ファイル等) - match : prefix : / route : cluster : server-http clusters : - name : grpc-piped-service http2_protocol_options : {} connect_timeout : 0.25s type : STRICT_DNS lb_policy : ROUND_ROBIN load_assignment : cluster_name : grpc-piped-service endpoints : - lb_endpoints : - endpoint : address : socket_address : address : localhost port_value : 9080 - name : grpc-web-service http2_protocol_options : {} connect_timeout : 0.25s type : STRICT_DNS lb_policy : ROUND_ROBIN load_assignment : cluster_name : grpc-web-service endpoints : - lb_endpoints : - endpoint : address : socket_address : address : localhost port_value : 9081 - name : grpc-api-service http2_protocol_options : {} connect_timeout : 0.25s type : STRICT_DNS lb_policy : ROUND_ROBIN load_assignment : cluster_name : grpc-api-service endpoints : - lb_endpoints : - endpoint : address : socket_address : address : localhost port_value : 9083 - name : server-http connect_timeout : 0.25s type : STRICT_DNS lb_policy : ROUND_ROBIN load_assignment : cluster_name : server-http endpoints : - lb_endpoints : - endpoint : address : socket_address : address : localhost port_value : 9082 Envoyの設定では、リクエストのパスに応じてPipeCD Serverの各サービスにルーティングしています。 ECSタスク定義(CloudFormation) Control PlaneのECSタスク定義は以下のように構成しました。Redis、Envoy(Gateway)、PipeCD Serverの3コンテナをサイドカー構成で動作させています。 Secretsで宣言しているCONTROL_PLANE_CONFIGとENVOY_CONFIGは、それぞれの設定ファイルをBase64エンコードした値です。これらはControl PlaneとEnvoyの起動時に利用されます。必要なリソースは別途宣言する必要があります。 ECSTaskDefinition : Type : 'AWS::ECS::TaskDefinition' Properties : Family : !Sub '${Env}-${ResourcePrefix}-pipecd-control-plane' NetworkMode : 'awsvpc' RequiresCompatibilities : - 'FARGATE' Cpu : !Ref ECSTaskCPU Memory : !Ref ECSTaskMemory ExecutionRoleArn : !GetAtt ECSTaskExecutionRole.Arn TaskRoleArn : !GetAtt ECSTaskRole.Arn ContainerDefinitions : # Redisコンテナ(キャッシュ用) - Name : 'redis' Image : 'redis:7.4-alpine' Essential : false PortMappings : - ContainerPort : 6379 Protocol : tcp Command : - 'redis-server' - '--appendonly' - 'yes' - '--maxmemory' - '128mb' - '--maxmemory-policy' - 'allkeys-lru' LogConfiguration : LogDriver : 'awslogs' Options : awslogs-group : !Ref LogsLogGroup awslogs-region : !Ref 'AWS::Region' awslogs-stream-prefix : 'redis' # Envoyコンテナ(Gateway) - Name : 'pipecd-gateway' Image : !Ref PipeCDGatewayImageURL # envoyproxy/envoy Essential : false PortMappings : - HostPort : 9090 ContainerPort : 9090 Protocol : tcp Command : - '/bin/sh -c '' echo $ENVOY_CONFIG | base64 -d >> envoy-config.yaml; envoy -c envoy-config.yaml; '' ' EntryPoint : - sh - '-c' Secrets : - Name : 'ENVOY_CONFIG' ValueFrom : !Sub '${SecretsManagerEnvoyConfigArn}:config::' LogConfiguration : LogDriver : 'awslogs' Options : awslogs-group : !Ref LogsLogGroup awslogs-region : !Ref 'AWS::Region' awslogs-stream-prefix : 'pipecd-gateway' DependsOn : - ContainerName : 'redis' Condition : 'START' # PipeCD Serverコンテナ - Name : 'pipecd-server' Image : !Ref PipeCDServerImageURL # ghcr.io/pipe-cd/pipecd Essential : true Command : - !Sub '/bin/sh -c '' echo $CONTROL_PLANE_CONFIG | base64 -d >> control-plane-config.yaml; echo $ENCRYPTION_KEY >> encryption-key; pipecd server --insecure-cookie=true --cache-address=localhost:6379 --config-file=control-plane-config.yaml --encryption-key-file=encryption-key; '' ' EntryPoint : - sh - '-c' Secrets : - Name : 'CONTROL_PLANE_CONFIG' ValueFrom : !Sub '${SecretsManagerControlPlaneConfigArn}:config::' - Name : 'ENCRYPTION_KEY' ValueFrom : !Sub '${SecretsManagerEncryptionKeyArn}:key::' LogConfiguration : LogDriver : 'awslogs' Options : awslogs-group : !Ref LogsLogGroup awslogs-region : !Ref 'AWS::Region' awslogs-stream-prefix : 'pipecd-server' DependsOn : - ContainerName : 'redis' Condition : 'START' Control Planeの初期設定 Control PlaneのECSタスクがデプロイされた後、初期設定を実施します。この手順でPipedの認証情報を取得するため、Piped構築の前に実施します。 OPSサーバーへの接続 PipeCD OPSサーバーに接続し、プロジェクトの作成やユーザー管理を実施します。OPSサーバーは外部公開していないため、SSM Session Managerを使用してポートフォワードで接続します。 ポートフォワードが確立されたら、ブラウザでlocalhostを叩き、OPSサーバーにアクセスして管理画面を開きます。 プロジェクトの作成 管理画面から「Add Project」でプロジェクトを作成します。 項目 設定値 ID プロジェクト識別子(例:your-project-dev) Description 任意の説明 Shared SSO cognito-azure(control-plane-config.yamlで定義したSSO設定名) プロジェクト作成後に表示される Static Admin Username と Static Admin Password は、初回ログインに必要なため必ずメモしておきます。 ユーザーグループの作成 Control PlaneのURLにアクセス(例: https://pipecd.your-domain.com ) 作成したプロジェクト名を入力 Static Adminの認証情報でログイン 「Settings」→「User Groups」からユーザーグループを作成 グループ名 ロール 権限 Admin Admin プロジェクト全体の管理権限 Editor Editor デプロイ・設定変更(ユーザー管理除く) Viewer Viewer 読み取り専用 権限の詳細は 公式ドキュメント(Authentication and authorization) をご参照ください。 SSOログインの確認 ログアウトし、「Login with OIDC」をクリック Entra IDの認証画面でログイン Pipedの登録 Control PlaneにPipedを登録し、Piped構築に必要な認証情報を取得します。 「Settings」→「Piped」→「Add Piped」をクリック Piped情報を入力(Name、Description) 「Save」をクリック 表示される以下の情報をメモ Piped Id : Piped設定ファイルの pipedID に使用 Base64 Encoded Piped Key : Piped設定ファイルの pipedKeyData に使用 これらの値は次の「Pipedの構築」で使用します。 Pipedの構築 PipedはControl Planeと通信し、実際のデプロイを実行するエージェントです。デプロイ対象のECSクラスターと同じVPC内にECS on Fargateで構築しました。 Pipedの設定は piped-config.yaml で定義します。 apiVersion : pipecd.dev/v1beta1 kind : Piped spec : projectID : ${PROJECT_ID} pipedID : ${PIPED_ID} pipedKeyData : ${PIPED_KEY_DATA} apiAddress : pipecd.${DOMAIN}:443 git : sshKeyData : ${SSH_KEY_DATA} repositories : - repoId : ${REPO} remote : git@github.com:hoge/${REPO}.git branch : main platformProviders : - name : ECS type : ECS config : region : ap-northeast-1 notifications : routes : - name : xxxxx_ci_notice events : - DEPLOYMENT_TRIGGERED - DEPLOYMENT_SUCCEEDED - DEPLOYMENT_FAILED - DEPLOYMENT_ROLLING_BACK receiver : xxxxx_ci_notice receivers : - name : xxxxx_ci_notice slack : hookURL : ${SLACK_WEBHOOK_URL} pipedID と pipedKeyData は、Control PlaneのWeb UIからPipedを登録した際に発行されます。詳細は 公式ドキュメント(Configuration reference) をご参照ください。 ECSタスク定義(CloudFormation抜粋) PipedのECSタスク定義は以下のように構成しました。Secretsで宣言しているCONFIG_DATAは、piped-config.yamlをBase64エンコードした状態で取得し、Pipedの起動時に利用されます。 ECSTaskDefinitionPiped : Type : 'AWS::ECS::TaskDefinition' Properties : Family : !Sub '${Env}-${ResourcePrefix}-pipecd-piped' NetworkMode : 'awsvpc' RequiresCompatibilities : - 'FARGATE' Cpu : !Ref PipeCDPipedTaskCpuType Memory : !Ref PipeCDPipedTaskMemoryType ExecutionRoleArn : !GetAtt ECSTaskExecutionRole.Arn TaskRoleArn : !GetAtt ECSTaskRole.Arn ContainerDefinitions : - Name : 'piped' Image : !Sub '${ECRRepositoryUri}:${PipeCDPipedImageTag}' Essential : true EntryPoint : - sh - -c Command : - /bin/sh -c "piped piped --config-data=$(echo $CONFIG_DATA)" Secrets : - Name : 'CONFIG_DATA' ValueFrom : !Sub '${SecretsManagerPipedConfigArn}:config::' LogConfiguration : LogDriver : 'awslogs' Options : awslogs-group : !Ref LogsLogGroup awslogs-region : !Ref 'AWS::Region' awslogs-stream-prefix : 'piped' IAMロール PipedがECSサービスをデプロイするために、タスクロールに以下の権限を付与しています。 ECSTaskRole : Type : 'AWS::IAM::Role' Properties : RoleName : !Sub '${Env}-${ResourcePrefix}-pipecd-piped-ecs-task-role' AssumeRolePolicyDocument : Version : '2012-10-17' Statement : - Effect : 'Allow' Principal : Service : 'ecs-tasks.amazonaws.com' Action : 'sts:AssumeRole' Policies : - PolicyName : 'PipedECSServiceAccess' PolicyDocument : Version : '2012-10-17' Statement : - Effect : 'Allow' Action : - ecs:CreateService - ecs:CreateTaskSet - ecs:DeleteTaskSet - ecs:DeregisterTaskDefinition - ecs:DescribeServices - ecs:DescribeTaskDefinition - ecs:DescribeTaskSets - ecs:DescribeTasks - ecs:ListClusters - ecs:ListServices - ecs:ListTaskDefinitions - ecs:ListTasks - ecs:RegisterTaskDefinition - ecs:RunTask - ecs:TagResource - ecs:UntagResource - ecs:ListTagsForResource - ecs:UpdateService - ecs:UpdateServicePrimaryTaskSet - elasticloadbalancing:DescribeListeners - elasticloadbalancing:DescribeRules - elasticloadbalancing:DescribeTargetGroups - elasticloadbalancing:ModifyListener - elasticloadbalancing:ModifyRule Resource : "*" - Effect : 'Allow' Action : - iam:PassRole Resource : - !Sub 'arn:aws:iam::${AWS::AccountId}:role/${Env}-${ResourcePrefix}-ecs-task-execution-role' - !Sub 'arn:aws:iam::${AWS::AccountId}:role/${Env}-${ResourcePrefix}-task-role' PipedステータスのONLINE確認 PipedのECSタスクが起動したら、Control PlaneでPipedのステータスを確認します。 Control PlaneのWeb UIにログイン 「Settings」→「Piped」へ移動 登録したPipedのステータスを確認 ステータスが ONLINE (緑色のインジケーター)になっていれば、Control Planeとの接続が正常に確立されています。 ステータスがOFFLINEの場合は、以下を確認してください。 Piped設定ファイルの pipedID と pipedKeyData が正しいか apiAddress がControl PlaneのURLと一致しているか ECSタスクのログでエラーが出ていないか マニフェストファイルの構成 PipeCDでECSアプリケーションを管理するために、以下のファイルを用意しました。 ファイル 役割 app.pipecd.yaml PipeCDアプリケーション定義(デプロイ戦略、参照ファイルの指定等) servicedef.yaml ECSサービス定義 taskdef.yaml ECSタスク定義 app.pipecd.yaml PipeCDのアプリケーション設定ファイルです。デプロイ対象のサービス定義・タスク定義ファイルやターゲットグループを指定します。 apiVersion : pipecd.dev/v1beta1 kind : ECSApp spec : name : hogehoge labels : env : dev team : zozo-xxxx app : hogehoge notification : mentions : - event : DEPLOYMENT_TRIGGERED - event : DEPLOYMENT_SUCCEEDED - event : DEPLOYMENT_FAILED - event : DEPLOYMENT_ROLLING_BACK input : serviceDefinitionFile : servicedef.yaml taskDefinitionFile : taskdef.yaml targetGroups : primary : targetGroupArn : arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxx:targetgroup/primary/xxxxxxxxxxxxx containerName : hogehoge containerPort : 8080 canary : targetGroupArn : arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxx:targetgroup/canary/xxxxxxxxxxxxx containerName : hogehoge containerPort : 8080 pipeline : stages : - name : ECS_CANARY_ROLLOUT with : scale : 20 - name : ECS_TRAFFIC_ROUTING with : canary : 10 - name : WAIT_APPROVAL - name : ECS_PRIMARY_ROLLOUT - name : ECS_TRAFFIC_ROUTING with : primary : 100 - name : ECS_CANARY_CLEAN 設定項目の詳細は 公式ドキュメント(Configuring ECS application) をご参照ください。 servicedef.yaml ECSサービスの定義ファイルです。 version : v1 spec : cluster : arn:aws:ecs:ap-northeast-1:xxxx:cluster/sample-cluster serviceName : hogehoge desiredCount : 6 launchType : FARGATE schedulingStrategy : REPLICA networkConfiguration : awsvpcConfiguration : subnets : - subnet-xxxx - subnet-yyyy - subnet-zzzz securityGroups : - sg-xxxx assignPublicIp : DISABLED propagateTags : SERVICE deploymentController : type : EXTERNAL taskdef.yaml ECSタスクの定義ファイルです。コンテナイメージやリソース設定を記述します。 family : hogehoge networkMode : awsvpc requiresCompatibilities : - FARGATE cpu : "256" memory : "512" executionRoleArn : arn:aws:iam::xxxx:role/ecsTaskExecutionRole taskRoleArn : arn:aws:iam::xxxx:role/hogehoge-task-role containerDefinitions : - name : app image : xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/hogehoge:latest essential : true portMappings : - containerPort : 8080 protocol : tcp logConfiguration : logDriver : awslogs options : awslogs-group : /ecs/hogehoge awslogs-region : ap-northeast-1 awslogs-stream-prefix : ecs アプリケーションの登録 マニフェストファイルをGitリポジトリにプッシュしたら、Control PlaneでアプリケーションをPipedに紐づけます。 Control PlaneのWeb UIにログイン 「Applications」→「Add」をクリック アプリケーション情報を入力 項目 設定値 Name アプリケーション名(例:hogehoge) Kind ECS Piped 登録済みのPipedを選択 Repository piped-config.yamlで定義したリポジトリを選択 Path マニフェストファイルが配置されているディレクトリパス Config Filename app.pipecd.yaml 「Save」をクリック 登録が完了すると、PipedがGitリポジトリを監視し始めます。 デプロイの実行 アプリケーションの登録後、以下の流れでデプロイが実行されます。 初回デプロイ(Quick Sync) アプリケーション登録直後は、現在のマニフェストの状態でデプロイが実行されます。「Applications」画面でデプロイの進行状況を確認できます。 通常のデプロイフロー マニフェストファイル(taskdef.yaml等)を変更 GitリポジトリにPush Pipedが変更を検知し、自動でデプロイを開始 app.pipecd.yamlで定義したパイプラインに従って段階的にデプロイ 補足: カナリアリリースの流れ app.pipecd.yamlでカナリアリリースのパイプラインを定義していた場合、以下の流れでカナリアリリースが実行されます。 ECS_CANARY_ROLLOUT: Canary用のTaskSetを作成 ECS_TRAFFIC_ROUTING: Canaryにトラフィックを流す WAIT_APPROVAL: 承認待ち(Web UIで「Approve」をクリック) ECS_PRIMARY_ROLLOUT: Primary用のTaskSetを新バージョンに更新 ECS_TRAFFIC_ROUTING: Primaryに100%のトラフィックを戻す ECS_CANARY_CLEAN: Canary用のTaskSetを削除 承認待ちの間にCanary環境で問題が見つかった場合は、Web UIから「Cancel」でロールバックできます。 Webhook連携による自動テスト実行 PipeCDの通知機能を活用して、デプロイ成功後に自動でE2Eテストを実行する仕組みを構築しました。 Pipedの設定(piped-config.yaml)で、特定のアプリケーション・環境のデプロイ成功時にWebhookを送信できます。 # piped-config.yaml notifications : routes : - name : acceptance_test_webhook events : - DEPLOYMENT_SUCCEEDED labels : app : test-app env : dev receiver : webhook_receiver receivers : - name : webhook_receiver webhook : url : ${WEBHOOK_URL} 私たちの環境では、WebhookをZapierに送信し、ZapierからGitHub Actionsのワークフローをトリガーしています。GitHub Actionsのワークフローでは、DevのAWS環境で稼働するtest-appへE2Eテストを実行しています。これにより、dev環境のtest-appデプロイ成功後にE2Eテストが自動実行される仕組みを実現しました。詳細は 公式ドキュメント(Configuring Notifications) をご参照ください。 導入効果 PipeCDの導入により、以下の効果が得られました。 デプロイ経路の一本化 段階的リリースによるリスク低減 デプロイ経路の一本化 GitOpsの原則に基づき、全てのデプロイがGitリポジトリを経由するようになりました。 デプロイ履歴がGitのコミット履歴として残る 誰が・いつ・何をデプロイしたかが明確になった PipeCDのWeb UIでデプロイ状況をリアルタイムに確認可能 段階的リリースによるリスク低減 カナリアリリースの導入により、リリースに伴うリスクを大幅に低減できました。 新バージョンの問題を早期に検知可能 問題発生時の影響範囲を限定できる 自動ロールバックにより迅速な復旧が可能 まとめ 本記事では、ECS on FargateにPipeCDを導入してGitOpsベースの段階的リリースを実現した取り組みを紹介しました。ECS on FargateでGitOps化を検討している方や、カナリアリリースを導入したい方は、ぜひPipeCDを検討してみてください。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
こんにちは、技術戦略部のwirohaです。2025年11月20日、21日に「 アーキテクチャConference 2025 」が開催されました。ZOZOはGoldスポンサーとして協賛し、スポンサーブースを出展しました。 アーキテクチャConference 2025 入口の暖簾 technote.zozo.com 本記事ではZOZOから登壇したセッションの紹介と、協賛各社やZOZOの協賛ブースの様子をまとめてお伝えします! セッションレポート 巨大モノリスのリプレイス──機能整理とハイブリッドアーキテクチャで挑んだ再構築戦略 speakerdeck.com 矢部による発表 前半、物流開発部 矢部による発表(撮影:Findyさま) アーキテクチャConference2025ではZOZOで取り組んでいるVBScriptで書かれた20年物の基幹システムのリプレイスについて、杉山との共同発表という形で発表しました。インフラ構成やアーキテクチャ実装の詳細は杉山パートに集約し、私はマネージャー視点での組織マネジメントや機能整理の進め方をメインにお話ししました。 今回の発表では、成功事例だけでなく、かなり赤裸々な話をさせていただきました。「設計文化がなかった」「等価リプレイスを選んだのに、結局『手順の書き換え』になってしまった」「売上貢献度の不明な機能が大量にあった」など、正直あまり表に出したくないような失敗談も包み隠さず話しました。これは、リプレイスプロジェクトの理想と現実のギャップ、そして組織への理解醸成がいかに重要かという点を伝えたかったからです。 発表後のAsk The Speakerには、予想以上に多くの方が来てくださいました。特に印象的だったのは、「うちも同じような状況です」「まさに今、その罠にはまっています」という声が多かったことです。レガシーシステムのリプレイスに悩んでいる方、マイクロサービス化を進めたいけれど現実的な壁にぶつかっている方など、同じ課題を抱えたエンジニアの方々と直接お話しできました。 技術的な相談だけでなく、「組織をどう説得したか」「機能整理をどう進めたか」といった、泥臭い部分への質問も多くいただきました。同じ悩みを持つ方々と直接繋がれたことが、今回の登壇で得た一番の収穫でした。 杉山による発表 後半、SRE部 杉山による発表(撮影:Findyさま) 今回のカンファレンスでは、ZOZOの基幹システムリプレイスにおける背景や現状の課題、その解決に向けたアーキテクチャ選定の考え方、そして実際のアーキテクチャ事例について紹介しました。段階的移行を可能にするストラングラーパターンや発送領域でのイベント駆動設計、アーキテクチャ選定の意図などについて詳しく説明しました。 聴講者はジュニアからシニアまで幅広い層で、アーキテクチャ図を用いた説明の際には大きくうなずきながら聞いている方も多く見られました。チームメンバーからも「近くの参加者が強く共感していた」とのフィードバックがあり、内容に対する理解や納得感を得られたと感じています。セッション後のAsk The Speakerでは、アーキテクチャの詳細を確認しに来られる方や、自社システムと比較しながら質問される方も多く、技術選定や移行戦略への関心の高さが伺えました。質問は特にストラングラーパターンやイベント駆動の設計に関するものが多く、技術的にも興味を持っていただけた手応えがありました。 今回の登壇を通じて、ZOZOが基幹システムリプレイスにおいて、イベント駆動やドメイン分割といったモダンなアーキテクチャ選定や段階的移行戦略を実践している点をポジティブに発信できたと感じています。参加者からの質問や反応からも、当社の取り組みが他社にとっても関心の高いテーマであることが確認でき、有意義なセッションとなりました。 また、社外発信としての効果に加え、説明構成や伝え方の工夫など、今後の登壇機会に向けた学びも得られました。技術的・コミュニケーション的な観点の双方で、非常に充実した機会となりました。 各社ブースレポート 基幹システム本部リプレイス推進部の岡本です。アーキテクチャカンファレンスに1日目のみ参加しました。セッションを拝聴する機会はありませんでしたが、他社ブースを回って色々な話を聞くことができました。私からは、個人的に印象に残ったブースをいくつか紹介いたします。アーキテクチャ以外の内容も多くなりますが、ご容赦ください。 株式会社ジェイテックジャパン様 以前から個人的に関心を寄せていたイベントソーシングのフレームワーク「 Sekiban 」の開発者である高丘様に、直接お話を伺うことができました。 イベントソーシングを用いたサービスを継続運用する際のリアルな難しさや、その課題解決にも繋がるアプローチの1つである 動的一貫性境界(Dynamic Consistency Boundary) について詳しく教えていただき、大変有意義な時間となりました。 株式会社ジェイテックジャパン様のブースでノベルティとしていただいたタンブラー ノベルティとしていただいたタンブラーも素敵で、早速愛用しています。 株式会社アンチパターン様 SaaSの開発・運用・販売を支援する「 SaaSus Platform 」について興味深くお話を伺いました。 特に「導入企業には競争優位性を生み出すコア機能の開発に集中してほしい」というプロダクトの想いに強く共感しました。これは私が日々取り組んでいるシステムリプレイスにおいても常に意識しているポイントです。 また、AWSアカウント活用戦略など技術的な学びも多く、今後マルチテナント設計が求められる場面でも活かしていきたいと考えています。 株式会社ログラス様 普段からログラス様の社員の方々によるドメイン駆動設計や関数型プログラミングに関する発信を拝見していたため、直接それをお伝えできたのは貴重な機会でした。 また、 新卒1期生採用の開始についての発信 も印象に残っており、その背景にある想いを伺うことができたのも良かった点です。 株式会社ログラス様のブースでノベルティとしていただいた技術同人誌 ノベルティとしていただいた技術同人誌もクオリティが高く、ぜひ弊社でも参考にしたい取り組みだと感じました。内容も個人的に興味のあるテーマが多く、楽しく読み進めています。 ZOZO展示レポート wirohaです。今回はブース展示に加えて、アーキテクチャConference連動企画で投稿したポスターを展示していただきました。 ZOZOブースの様子 ブースでは、セッションで発表した基幹システムのほか、カート決済基盤、ショップ直送基盤、検索基盤のアーキテクチャをご紹介しました。じっくりと読み込む方や質問してくださる方が多く、深い交流ができました。CTOの瀬尾もブースに立っており、長期的な方向性や意思決定の経緯についてもお話しできました。 「巨大モノリスのリプレイス 機能整理とハイブリッドアーキテクチャで挑んだ再構築戦略」のパネル 「Javaを使用しているZOZOTOWNのアーキテクチャ」のパネル ノベルティとして、スニーカー等のソールの汚れをこすって落とせる消しゴムや、遊び心あるギミック付きのボールペンなどを配布しました。どちらも大変好評で、ZOZOを知っていただく良い機会になりました。 シューズ用クリーナー消しゴムのノベルティ(撮影:Findyさま) ポスターの方は、Fulfillment by ZOZOと実店舗在庫確認・取り置きサービスのアーキテクチャを展示しました。 ポスター展示 AWSを中心としたサーバーレスアーキテクチャ DynamoDBによるOutboxパターンとCDCを用いたCQRSアーキテクチャ 各企業のアーキテクチャが並び、サービスの特性による差異を考えながら見るのが楽しいスペースになっていました。こちらも多くの方が行き交い、読んでくださっていました。 おわりに ZOZOから参加したメンバーの一部で記念撮影! 今回はじめて協賛して参加者のみなさまとお話しし、アーキテクチャへの関心の高さや熱量を感じました。普段はなかなか見られない各社のアーキテクチャを見てディスカッションできるのは、大変貴重な機会でした。みなさま、ありがとうございました! ZOZOでは、セッションでお話しした基幹業務のエンジニアや、その他の分野でもエンジニアを募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co corp.zozo.com
はじめに こんにちは。ブランドソリューション開発本部WEARバックエンド部SREブロックの和田です。 普段はWEARのSREチーム(以下、WEARSRE)に所属し、開発や運用に加えて、ここ3年ほどはマネージャーとしてチーム運営にも携わっています。 WEARSREは発足以来、クラウドを活用した課題解決に取り組んできました。その一方で、10年以上稼働し続けているオンプレミス環境の運用については、長らく他部署に任せきりの状態が続いていました。 プロダクト専任のSREチームでありながら、システム全体にアプローチできていない。この矛盾を解消すべく、私たちはオンプレミス環境への関与を少しずつ拡大し、最終的に自チームでの運用を実現しました。 本記事では、これら一連の取り組みを振り返りつつ、チームの成長と現場で得られた気づきをご紹介します。 目次 はじめに 目次 これまでの取り組み 取り残されたオンプレミス環境 解決策 1. オンプレミス環境の現状把握 2. WEAR基幹DB更改への挑戦 1. オンプレミス環境を含めた検証環境の整備 2. 外部連携APIの無停止による更改 3. フォロー体制下での運用・保守の引き継ぎ 今後の展望 終わりに これまでの取り組み WEARはサービス開始から10年以上が経過したプロダクトであり、元々はオンプレミス環境のみで構築されていました。IISを中心とした多数のWindows Serverで構成され、サービス成長と共に基盤の仮想化や増設を繰り返しながら拡大を続けてきました。 こうした状況下、システムの効率化を目指したパブリッククラウドの部分的な導入が始まり、大規模な案件をトリガーにクラウド化が加速していきます。これに合わせて発足したのがWEARSREであり、当初はクラウド環境の課題解決を主軸に活動していました。 主要な取り組みとして、膨大なトラフィックを支える画像配信システムの刷新や、APIを含む様々なワークロードの基盤共通化を実施しました。その結果、クラウド環境の大部分は再構築されました。2023年以降はクラウド環境における大規模刷新に一区切りをつけ、整備した基盤を日々改善するフェーズに入っています。 取り残されたオンプレミス環境 クラウド環境の整備が進む一方で、WEARの大部分を占めるオンプレミス環境にはアプローチができていませんでした。ここには、ユーザーデータを保持する基幹DBや主要API、100以上のバッチといったプロダクトの心臓部が稼働しています。クラウド環境に対してWEARSREが取り組む一方、これらの重要な領域は、「クラウド化が完了するまでの間、運用は従来の担当部署が継続する」という役割分担になっていました。 WEARのシステム概要図 WEARSREがこの領域に関与できていない状況は、「組織的な負荷」と「ハイブリッド環境特有の技術的課題」という2つの構造的な問題を生んでいました。 「組織的な負荷」としては当該部署への業務負荷が常態化していました。これは、あくまで移行完了までの「暫定措置」であったはずの他部署による運用・保守が長期化したことによるものです。また、部署を跨ぐがゆえの詳細なヒアリングや日程調整といったコミュニケーションコストもかかっていました。 一方で「ハイブリッド環境特有の技術的課題」も無視できないほど肥大化していました。これは、WEARのシステム基盤がクラウド・オンプレミス環境間で密接に依存していることに起因しています。オンプレミス側で問題が発生しても、体制上の切り分けによりWEARSRE単独では深部に踏み込めず、解決が難しい場面もありました。 これらの問題に拍車をかけたのは「いずれリプレイスされる」という曖昧な見込みです。いずれはなくなるものという観点からオンプレミス環境の直接的な改善が見送られがちとなり、更に事態を悪化させていました。また、当初は活発だったクラウド化に向けたリプレイスの動きもトーンダウンしており歯止めがかからない状況でした。 今後のサービス成長を鑑みると、前述した課題がボトルネックになるであろうことは明白でした。課題を解決するため、従来の体制の見直しも含めWEARSREがきちんとオンプレミス環境と向き合うことが必須であると考えました。 解決策 オンプレミス環境と向き合うにあたって、解決すべき課題はたくさんありました。段階的に課題解決するため、大きく分けて「オンプレミス環境の現状把握」「WEAR基幹DBの更改」「フォロー体制下での運用・保守の引き継ぎ」の3フェーズで進行しました。 1. オンプレミス環境の現状把握 「オンプレミス環境への関与を深める」と意気込んだものの、当時はその全容が全く見えていない状態でした。開発頻度が低いために有識者は不在で、中には名前すら聞いたことのないシステムも存在します。頼みの綱であるドキュメントも長らく更新されておらず、実態と合致しているか確証が持てない状態でした。 そこで、まずはオンプレミス環境の全体像を正確に把握すべく、以下の3点を中心に調査を開始しました。 OSのライフサイクルやハードウェア保守期限の洗い出し 各システムの仕様、システム間の依存関係の洗い出し 各システムの移行計画や開発状況の確認 続いて、これらの情報をもとに、各システムを3つに分類しました。 計画的に廃止可能なシステム: 開発チームによる移行計画が進んでおり、将来的に運用が不要となるシステム。短期的にはOS更新や保守延長が必要だが、長期的にはシステム運用そのものの廃止を見込んでいる。 廃止方針だが計画未定のシステム: 廃止を検討しているものの、複雑な外部依存により具体的な計画が立てられないシステム。改修頻度が低く利用も縮小傾向にあるため、サーバー台数の最適化やクラウドリフトによる延命を検討する。 オンプレミス継続を選択したシステム: コスト対効果等の観点から、意図的にオンプレミス維持を判断したシステム。過去にクラウド化を検討した経緯はあるが、移行コスト等を総合的に判断し、現状環境での継続運用を決めている。 分類を進めていくと、従来の廃止ロードマップだけではカバーしきれない複雑な依存関係が見えてきました。そのため、対象システムを持つ各開発チームと綿密な協議を重ねました。その過程で、廃止計画の練り直しや、凍結されていた廃止対応の再始動が進み、具体的なアクションへとつながりました。こうした現場の協力もあり、オンプレミス環境の解像度は向上し、注力すべきポイントが明確に見えてきました。 2. WEAR基幹DB更改への挑戦 「オンプレミス環境の現状把握」の中で浮上した最大の課題は、WEAR基幹DBにおけるSQL ServerのEOLとハードウェア保守期限が同時に迫っていたことです。これにより、ハードウェア刷新を含む大規模なDB更改への対応が急務となりました。 WEAR基幹DBはユーザーデータを保持し、ほぼ全システムが依存する最大規模のミドルウェアであり、オンプレミス保守の中でも最も困難な対応が予想されます。加えて、5年前に実施した前回更改時とは異なり、現在はクラウドとオンプレミスが複雑に依存し合っています。 このようなハイブリッド環境下で、従来の環境ごとの業務分担を維持することには無理がありました。そこで、WEARSREが旗振り役となり、他部署と連携しながら計画から実行までを進める方針をとりました。この決断ができた背景には、オーナーシップを持って推進する我々に対し、専門的な知見が必要な場面で都度フォローしてくれる他部署の心強いバックアップがありました。 ここではDB更改の詳細については割愛しますが、この体制ならではの取り組みを2点紹介します。 1. オンプレミス環境を含めた検証環境の整備 今回のDB更改は、新サーバーへのデータ移行(ダンプ・リストア)を伴うため、当日はWEARをメンテナンスモードにし、切り替え作業と入念な動作検証を経て再開する計画でした。 当初は準備として、DB参照先の切り替え手順確認や、発行クエリの互換性調査を中心に進めていました。クエリの互換性調査ではDMA(Data Migration Assistant)を使用し、一定期間内に発行されたクエリの中から、互換性を満たしていないものを洗い出しました。 DMAを用いたクエリ互換性の検証については以下の記事をご参照ください。 techblog.zozo.com DMAにより修正すべきクエリは特定できましたが、こうした静的な調査だけでは、切り替え後の挙動を完全に保証しきれません。切り戻しが困難な移行であるため、実環境での動作確認が不可欠です。そのため、オンプレミスとクラウドを網羅した検証環境の整備に踏み切りました。 検証環境の構築には、双方のシステムを再現し新DBへ接続する必要があります。クラウド側は比較的スムーズに進みましたが、技術的なハードルが高かったのはオンプレミス環境の再現です。他部署のサポートを仰ぎつつ、ブラックボックス化していた仕様や細かいパラメータ設定を一つひとつ洗い出す地道な作業が続きました。 結果として、サービス全域のリグレッション試験を実施できる環境が完成し、この過程でWEARSRE内にアプリケーションセットアップを自走できるナレッジも蓄積されました。完成した検証環境は、当日の予行演習や構成変更後の負荷試験など、多岐にわたり活用されました。 2. 外部連携APIの無停止による更改 5年前の更改時との大きな違いとして、外部事業者に提供する「WEAR Connect API」の存在がありました。これは、コーディネート画像を中心としたWEARのデータを外部事業者のECサイト等で活用していただくためのAPIです。 通常、DB更改時のダンプ・リストア中は全システムを停止しますが、当該APIの停止は利用企業のECサイト運営に直接影響します。先方の事業影響を最小限に抑えるため、今回は「APIを停止させない」方法を模索しました。 着目したのは、「レプリケーション停止された状態でしばらく稼働し続けるRODB群」です。WEARの基幹DBはSQL Serverのパブリッシャの役割を担うMAINDBと、サブスクライバの役割を担う複数台のRODBで構成されています。更改手順の中には、MAINDBをダンプする前にRODBに向けたレプリケーションを停止する手順が存在します。その後、RODBに変更予定はなく、そのまま廃止される予定でした。これらを有効活用することで無停止を実現できるのではないかと考えました。 結果的には、以下の条件が揃っていたため、RODBを活用した無停止運用が可能だと判断しました。 API仕様: 外部事業者のECサイト運営で必須なのは「参照系API」のみ。更新系APIは一時停止可能。 データの一貫性: MAINDBのレプリケーションを停止しても、停止直前までのデータがRODBに残っていればサービス提供に支障はない。 非機能要件: MAINDBに比べRODBのスペックは劣るが、API側のキャッシュ機構で負荷を吸収できる見込みがある。 WEARにおけるnginx TCP Load Balancer 実装としては、クラウド上にnginx TCP Load Balancerを構築し、参照系の通信をRODBへオフロードさせる構成に変更しました。当日は各作業フェーズに応じて、適宜DBの参照先を変更することで、外部事業者に影響を与えることなく更改を終えることができました。 なお、nginx TCP Load Balancerを活用した手法はZOZOTOWNでも採用されています。詳細は以下の記事をご参照ください。 techblog.zozo.com 3. フォロー体制下での運用・保守の引き継ぎ WEAR基幹DB更改後も、約1年間は他部署によるフォロー体制を維持し、実務を通じたナレッジを獲得していきました。現在では以下のような業務をWEARSREだけで完結できるようになっています。 インフラ管理: ハードウェアの予算策定、見積もり、OS/MWのバージョン管理 ネットワーク: 開発案件に伴う物理・論理構成の設計 障害対応: オンコール対応、復旧指揮、メンテナンス対応 こうした実績の積み重ねにより、フォローアップのための定例会はその役目を終えました。現在は、WEARのオンプレミス環境に関するあらゆる問い合わせや、依頼をWEARSREが一手に引き受けています。 WEARのシステム概要図(引き継ぎ後) 今後の展望 まずは、引き続きWEARSREが主体となりオンプレミスの運用・保守を安定して実施していきます。ここには、アプリケーションやミドルウェアの安定稼働の実現だけでなく、サーバー機器の選定・発注・老朽化対応といったオンプレミス特有の物理的なインフラ管理業務も含まれます。本記事で取り上げた基幹DB更改だけでなく、今後も様々な保守案件を控えています。これらを引き継ぎ後も品質を落とすことなく確実に推進し、プロダクトの安定稼働に貢献します。 次に、新たに迎え入れたオンプレミス環境の解像度を高め、より効果的かつ現実的な解決策を提案できるチームを目指します。当初は、「全てのシステムを新しい言語で書き換え、すべての基盤をクラウド化する」という方針を掲げていました。しかし、その前提を見直すべき時期に来ていると考えています。単一的なクラウド移行に固執するのではなく、コスト、パフォーマンス、移行リスクなどを総合的に鑑み、「プロダクトにとって何が真に最適か」を追求することが重要です。それぞれの環境の強みを最大限に活かしたハイブリッドなアプローチで、WEARのさらなる成長を技術面から牽引していきます。 終わりに 当初はシステムの実態が掴めず、現状把握に多くの時間を要しました。しかし、保守案件を積み重ねるにつれて、他部署からの問い合わせや機能開発で発生するインフラの構成変更を多角的に提案できるようになりました。これはチームとして大きな成長だと実感しています。 また、私個人の学びとしては、システムの刷新を急ぐあまり、オンプレミス環境の現状把握と課題解決が後手に回ってしまった点は反省すべき点です。新旧の環境を切り離して捉えてしまっていましたが、移行期間中もサービスは等しく稼働し続けます。たとえ過渡期の環境であっても、現在進行形でサービスを支えているのはそのシステムです。本件を通じて改めてそのことを実感しました。 本取り組みは、WEARSREだけの力で成し遂げられたものではありません。長年にわたり運用を支え、移行期間中も親身になってフォローしてくださった他部署の皆様に、深く感謝いたします。また、これまではクラウド技術を主軸としてきた私たちにとって、オンプレミス環境への本格的な介入は大きな挑戦でした。それでも、この変化を前向きに受け入れ、未知の領域にもかかわらず積極的に課題に取り組んでくれたWEARSREのメンバーを誇りに思います。 これからもチーム一丸となって、WEARの信頼性向上に努めてまいります。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
はじめに こんにちは。ZOZOTOWN開発本部プロダクト戦略部Tech-PMの高橋です。普段はZOZOTOWNの新機能開発、改善に関わりながらPMとしてプロダクトの開発進行を担当しています。 今回は、2023年11月にリリースした「アイテムレビュー機能」にまつわる、約2年間のプロジェクトの裏側とリリース後に直面した課題、そしてどのように改善を進めていったかをまとめました。 待望のアイテムレビュー機能、ついにリリース ZOZOTOWNでは以前から、お客様・ショップ様双方からレビュー機能の追加を求める声が多く寄せられていました。しかし、監視体制、ブランド様の意向、開発規模など、越えるべきハードルが多く、長年リリースできずにいた背景があります。 そんな中、2023年11月29日、ついに念願のレビュー機能を公開しました。リリース後Xの反応でも「やっと付いた!」「助かる!」という声が多く、ポジティブな反応は81.5%を占めていました。 リリース後に売上指標が低下 好評を得た一方で、リリース後商品詳細ページ経由の売上指標(カート投入率 / CVR / 受注金額)などが低下していることがわかりました。 ユーザーの反応は良く、ZOZOTOWNとしても待望の機能です。そのため、レビュー機能自体を即座に削除する判断ではなく、 「アイテムレビューあり」で売上数値を改善させる事 を探る改善プロジェクトがすぐに立ち上がりました。 先に結論:今のUIとリリース直後UIの比較 一見すると、大きな差異はありません。違いは商品名の下に★があるかないかだけに行き着きました。この違いに到達するまでの改善プロセスを紹介します。 改善スピード最優先の体制構築 改善方針が決まるまでのスピードが求められる状況だったため、次のように体制を再構築しました。 最小限メンバーで改善内容の検討 PM / 企画責任者 / 開発責任者 / デザイン / 分析のみに絞る 意思決定者をZOZOTOWN CPOの1名に統一 開発優先度をトップクラスに設定 改善内容を決めてから開発を開始 ZOZOTOWNは開発に関わる方がとても多く、さまざまな関係者や確認フローが存在します。すべての関係者に確認を取っているとひとつひとつの進行に時間がかかりすぎる事となります。この体制により、コミュニケーションコストが大幅に削減され、改善サイクルを高速に回すことができました。 3回に渡るABテストの実施 商品名下の★削除に行き着くまで、3回に渡るABテストを実施し、改善するパターンを見出しました。 1回目ABテスト:アイテムレビューUI「ありvsなし」 2回目ABテスト:レビュー件数が0件の時の見せ方改善 3回目ABテスト:アイテムレビューUI全体の最適化 1回目:アイテムレビューUI あり VS なし アイテムレビュー機能リリース後初めてのABテストです。レビュー機能の有無が、同一商品の売上数値にどれだけ改善寄与しているかを計るために実施しました。 Aパターン:アイテムレビュー機能あり Bパターン:アイテムレビュー機能なし 結果「なし」の勝利 最初の検証の結果、レビューUIを表示しないパターンのほうが売上指標は良いと分かりました。しかしこの段階では、機能そのものを消す判断はせず、「レビューの“見せ方”に問題がある」という可能性を考え次の検証へ進みました。 2回目:レビュー件数が0件時の見せ方改善 1回目ABテストの結果から、 レビュー件数が0件の場合、売上数値が顕著に低下することが判明 しました。 仮説 レビュー件数が0の場合でも★の表示があることで、レビューがついていない商品のレビュー点数が、「0点」として認識されてしまっているのではないか。 ABテストパターン Aパターン:アイテムレビュー機能あり Bパターン:アイテムレビュー機能なし Cパターン:レビュー0件の場合に、商品名下の★と、リストUI上の★を削除する Dパターン:レビュー0件の場合に、レビューUIごと削除する 結果 「B:レビューUIなし」が一番結果◯ CパターンvsDパターンを比較した場合、「C:0件★削除」が結果◯ 結論 レビュー件数が0件だった場合に限り、「C:0件★削除」パターンを採用 Bパターンが一番いいのは変わらないため引き続き改善検討 3回目:アイテムレビューUI全体の最適化 1回目、2回目ABテスト結果から、以下の事が分かりました。 ★レビュー点数が 4.5 以上の場合、レビューを表示したほうが売上指標に良い影響を与える レビュー件数よりも、 点数の方が重要 である レビュー件数が5件以下の商品PVが、商品全体PVの約6割 仮説 レビュー点数が低い商品の場合、★の表示は購買意欲に悪影響を及ぼしているのではないか ★の表示は、レビュー全体の平均値として出しているが、本来高いレビュー点数になりうる商品でもレビュー件数が少ない(1件〜3件など)商品で、レビュー点数が低い場合、悪影響を及ぼしているのではないか ABテストパターン Aパターン:アイテムレビュー機能あり Bパターン:アイテムレビュー機能なし Cパターン:商品名下の★をレビュー件数に関わらず削除する Dパターン:レビュー件数が3件以下の場合、商品名下の★と、リストUI上の★を削除する 結果 「Cパターン:商品名下の★をレビュー件数に関わらず削除する」 の勝利 この結果から、レビュー機能を表示したほうが、表示しない場合と比べて売上指標の向上につながることが分かりました。 最終的にたどり着いたUI 最終的には、次の仕様で改善をリリースしました。 レビュー件数が1件以上の場合 商品名下の★を削除 レビュー件数が0件の場合 商品名下の★に加えて、リスト上の★も削除 売上指標へのインパクト 2024年度にZOZOTOWNで実施したプロダクト改善によるGMV純増のうち、57.2% がレビュー機能改善による効果でした。 リリース直後の売上低下を巻き返し、改善によって大きく貢献できた取り組みとなりました。 今回の改善から得た学び 待望の機能でも、UX次第で期待とは異なる効果が起きうる ユーザーがどう「解釈」するかに細心の注意が必要。 レビュー件数より、レビュー点数の方が購買に大きく影響 件数が少ない場合、平均点が安定しないケースも。 スピードが求められる改善では、意思決定者を絞ることが重要 プロジェクトの推進力が段違いになる。 諦めず、データに基づいた検証を重ね続けること 今回の最適解は、3回のABテストの積み上げによって辿り着いたもの。 まとめ 今回の一連の改善プロセスは「ユーザーにとって本当に価値あるUIとは何か」「プロダクトの成長に必要な意思決定とは何か」を改めて深く考える機会になりました。今後もレビュー機能の価値向上と、より良い購買体験の実現に向けて改善を続けていきます。 ZOZOでは、一緒にサービスを作り上げてくれるPM(プロジェクトマネージャー)を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 hrmos.co
はじめに こんにちは、WEARフロントエンド部iOSブロックの西山です。普段はWEAR iOSチームのマネジメント兼アプリの開発を担当しています。今年のWWDC25で、新しいソフトウェアデザインのLiquid Glassが発表されました。透明感のあるUIと流動的なアニメーションが特徴的なこの新デザインは、WEARアプリに大きな影響を与えました。鋭意進行中の取り組みとして、本記事では、Liquid Glass対応を計画的に進めるための取り組みを紹介します。 目次 はじめに 目次 Liquid Glassとは 課題 UIが大きく変わることでデザイン崩れが発生 タブバー タブバーの裏側にコンテンツが透過しない タブバーとコンポーネントが被る ナビゲーションバー ボタンに枠がつく ナビゲーションバーの下に配置してあるコンポーネントとの一体感を失う その他のコンポーネント Switchが大きくなったことによるレイアウト崩れ アラート(アクションシート)の変化 デザイナー、エンジニア、QAのコスト増大が予想される Liquid Glassの学習コスト 移行戦略 開発プロセスの整備 ブランチ戦略 Xcode 26対応 Xcode 16を使用している場合の例 Liquid Glassを無効にする Liquid Glass効果を動的に切り替えられるようにする Liquid Glass対応で分岐できるようにする 対応内容を一部紹介 contentInsetAdjustmentBehavior = .never の対応 contentInsetAdjustmentBehavior = .never の問題点 extendedLayoutIncludesOpaqueBars = true の対応 まとめ Liquid Glassとは iOS 26から導入される新しいデザインで、次の特徴があります。 ガラスのような質感と透明感 光の反射/屈折のようなリアルな動き 流動性のあるアニメーション https://www.apple.com/jp/newsroom/2025/06/apple-introduces-a-delightful-and-elegant-new-software-design/ より引用 ナビゲーションバーやタブバーなどの標準コンポーネントにデザインが適用されるので既存アプリの確認が必要になります。 課題 WEARアプリの確認と現在の状況を踏まえ、次のような課題がありました。 UIが大きく変わることでデザイン崩れが発生 デザイナー、エンジニア、QAのコスト増大が予想される Liquid Glassの学習コスト UIが大きく変わることでデザイン崩れが発生 タブバーとナビゲーションバーを透過させて確認したところ複数の問題が見つかりました。 タブバー タブバーの裏側にコンテンツが透過しない Before After タブバーとコンポーネントが被る Before After ナビゲーションバー ボタンに枠がつく Before After ナビゲーションバーの下に配置してあるコンポーネントとの一体感を失う Before After その他のコンポーネント Switchが大きくなったことによるレイアウト崩れ Before After アラート(アクションシート)の変化 Before After こちらは一部で、この他にも細かいところの問題は存在しています。 デザイナー、エンジニア、QAのコスト増大が予想される WEARは、基本的に3つのOSをサポートしています。今は過渡期のためiOS 16もサポート中ですが、近々iOS 17, 18, 26のサポートになります。サポートOSは、メジャーバージョンが登場してから数ヶ月後の更新となるため、1年に1回更新されます。そのため、現時点から約2年間は、iOS 26未満をサポートする必要があり、Liquid Glassに対応すると新旧それぞれのデザインを並行管理していく必要が出てきます。それぞれのデザイン調整、開発、テストを行うことで、担当する各チームのコストが増大すると予想されます。 Liquid Glassの学習コスト 新しい概念のため、まずはLiquid Glassを理解する必要があります。既存のコンポーネントのどこをどのような形にすることでLiquid Glassとしてベストなのか見極める必要もあります。まだ登場して間もないため、ノウハウも少なく手探り状態になることは否めません。さらにデザイナーのリソースもこちらに多くを割ける状態ではありませんでした。 移行戦略 リスクと対応コストを考慮し、次の方針で進めます。 Liquid Glassを無効にするオプションをフルで利用する レイアウト修正を最優先で行う レイアウト修正を行いながら原因を把握し、デザインや実装方法を見直す レイアウト修正完了後、Liquid Glassのベストプラクティスを探る Liquid Glassの為に構造を大きく変える変更は可能であればiOS 18以前にも適用させる 1に関してですが、Appleは、Liquid Glassを無効にするオプションを用意しており、現時点では 次のメジャーバージョンリリースまで(約1年)有効 とされています。 開発プロセスの整備 修正後は新旧デザインの確認が必要になるため、効率よく進めるための状態を作る必要があります。 ブランチ戦略 普段WEAR iOSでは、developブランチを除外したGit-flowで開発しています。今回の対応では、Liquid Glass用の開発ブランチを作成することも考えられます。しかし、生存期間は長くなりマージコストの増大が懸念されるため既存と同じように進めます。そのため、旧デザインに影響を与えないように対応していく必要があります。 Xcode 26対応 当たり前ですが、Xcode 26対応は必須です。Xcode 26対応の前に先行して進めることもできますがとても非効率になります。 Xcode 16を使用している場合の例 Xcode 26でビルドが通るブランチを用意 Xcode 26で不具合の修正 Xcode 16用のブランチに切り替え Xcode 16用のブランチに修正を取り込む Xcode 16でビルドし確認 問題があれば2から繰り返し Liquid Glassを無効にする Liquid Glassを無効にする設定です。 Info.plist に UIDesignRequiresCompatibility を追加することで無効にできます。 Liquid Glass効果を動的に切り替えられるようにする こちらを参考に swizzling を使ってRelease版には影響がないよう、デバッグメニューから動的に変えられるようにしています。 zenn.dev デバッグメニューの設定 Liquid Glass対応で分岐できるようにする 同一ブランチでの開発になるため、タブバーの透過等で分岐が必要になります。不具合の修正では極力Liquid Glass用の分岐は入れたくないですが、どうしても必要になるケースも考えられます。また、Liquid Glass無効化オプションの廃止後も、iOS 18をサポートする期間があることを考慮します。 class LiquidGlass : NSObject { @objc static var isEnabled : Bool { UserDefaults.standard.isLiquidGlassEnabled } } WEARには一部Objective-Cが残っているため、Objective-Cからも参照できるようにしています。今はデバッグメニューから切り替えられる値を参照してますが、オプションが無効になってからは次のように変える予定です。 class LiquidGlass : NSObject { @objc static var isEnabled : Bool { if #available(iOS 26.0 , * ) { true } else { false } } } 対応内容を一部紹介 レイアウト修正の中で、最初に取り掛かったのはタブバー周りの対応です。タブバーの背景を透過させるだけでもLiquid Glassらしさが出ます(個人の感想です) Before After WEARのタブバーは背景色が指定されており、タブバーの裏に画面が回り込むような実装になっていない画面が多数ありました。 そういった画面の大多数はスクロールできる画面なので、 ScrollView 系を修正します。 ※WEARでは、SwiftUIの導入も徐々に進めてはいますが、まだまだUIKitがメインなのでUIKitの対応になります。 主に次の2つを利用しているところの修正が必要でした。 contentInsetAdjustmentBehavior = .never extendedLayoutIncludesOpaqueBars = true contentInsetAdjustmentBehavior = .never の対応 iOS 11以前は scrollView.contentInset を実装側で調整する必要がありましたが、iOS 11からは自動で調整してくれるようになりました。当時の選択肢としては、 never or automatic の2択が取れましたが、 WEARでは never を採用していました。その背景もあり scrollView.contentInset.top は contentInsetAdjustmentBehavior = .never を指定して調整していました。 contentInsetAdjustmentBehavior = .never の問題点 contentInset.top だけの設定であればまだ良かったのですが、今回の対応で contentInset.bottom も調整する必要があります。 never のままだと、タブバーの高さをセットする必要があり煩雑になるので、次の方法に変更しました。 contentInsetAdjustmentBehavior = automatic にする(デフォルト値なので指定なし) オートレイアウトの bottom は superview に貼る 注意点は、今まで scrollView.contentInset を参照している場所は scrollView.adjustedContentInset を参照するように変える必要があるところです。 adjustedContentInset は自動調整された後の値になっているので automatic にした際は、こちらの参照が適切になる箇所が出てきます。 extendedLayoutIncludesOpaqueBars = true の対応 こちらは、ナビゲーションバーの裏側に画面を通すために使用しており、 edgesForExtendedLayout = .top とセットで使用されています。タブバーを透過させるため bottom も追加します。 extendedLayoutIncludesOpaqueBars = true edgesForExtendedLayout = [.top, .bottom] ただこれだと旧デザインで、スクロールした最下部のアイテムがタブバーの裏に隠れてしまいます。 最下部のアイテムがタブバーの裏に隠れている そのため、次のようにします。 extendedLayoutIncludesOpaqueBars = true edgesForExtendedLayout = isLiquidGlassEnabled ? [.top, .bottom] : .top 極力分岐はしたくないと上述してましたが、早速使ってしまっています。ただこちらは、Liquid Glassのリリースをする前にタブバーを半透明にする対応をリリースし、事前に分岐を無くしておきたいと考えています。 まとめ 本記事ではWEARアプリにおけるLiquid Glass対応を進めるための取り組みを紹介しました。これからLiquid Glass対応に取り組もうとしている方の参考に少しでもなれば幸いです。まずは不具合を修正し、その後、最適なLiquid Glassの対応を模索していければと思います。 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
ZOZO開発組織の2025年11月分の活動を振り返り、ZOZO TECH BLOGで公開した記事や登壇・掲載情報などをまとめたMonthly Tech Reportをお届けします。 ZOZO TECH BLOG 2025年11月は、前月のMonthly Tech Reportを含む計6本の記事を公開しました。今夏に実施した合同勉強会で発表した内容を基にした記事は、はてなブログランキングのおすすめエントリーとして紹介されました。 🏅今週のはてなブログランキング〔2025年11月第2週〕より、おすすめエントリーを紹介します🏅 ZOZOTOWN フロントエンドにおけるディレクトリの分割戦略 - ZOZO TECH BLOG https://t.co/stuhjLPhsR hiro @hiro0218 さん — はてなブログ|思いは言葉に。 (@hatenablog) 2025年11月12日 協賛 2025年11月は「 FlutterKaigi 2025 」「 JJUG CCC 2025 Fall 」そして「 アーキテクチャConference 2025 」の3つのカンファレンスに協賛しました。 technote.zozo.com techblog.zozo.com technote.zozo.com techblog.zozo.com technote.zozo.com 登壇 Kotlin Fest 2025 11月1日に開催された「 Kotlin Fest 2025 」に、ZOZOTOWN開発2部のにしみー( @nishimy432 )が登壇しました。 techblog.zozo.com 📢 ZOZOエンジニア登壇情報 #KotlinFest 2025にZOZOTOWN開発2部のにしみー @nishimy432 が『AIとの協業で実現!レガシーコードをKotlinらしく生まれ変わらせる実践ガイド』というテーマで登壇します! 🕕時刻 18:20 - 18:40 📍場所 var(ホールB) 🔗 https://t.co/Yq8yOH9PBL ぜひお越しください! — ZOZO Developers (@zozotech) 2025年10月31日 speakerdeck.com JJUG CCC 2025 Fall 11月15日に開催された「 JJUG CCC 2025 Fall 」に、ZOZOのJavaチームより井草、半澤、宮澤の3名がセッションに登壇しました。また、スポンサーLTに平林が登壇しました。 📢 JJUG CCC 2025 Fall ZOZOエンジニア登壇情報 #1 ZOZOのJavaチームより商品基盤部の井草が『膨大なデータをどうさばく?Java × MQで作るPub/Subアーキテクチャ』というテーマで登壇します! 🕚️11:00 - 11:20 📍 Room J ぜひお越しください! https://t.co/n2PIrm0tU1 #jjug_ccc — ZOZO Developers (@zozotech) 2025年11月15日 speakerdeck.com 📢 JJUG CCC 2025 Fall ZOZOエンジニア登壇情報 #2 ZOZOのJavaチームより、ECプラットフォーム部でブロック長・テックリードを務める半澤が『ZOZOTOWNカート決済リプレイス ── モジュラモノリスという過渡期戦略』というテーマで登壇します! 🕐️13:15 - 13:35 📍 Room J #jjug_ccc — ZOZO Developers (@zozotech) 2025年11月15日 speakerdeck.com 📢 JJUG CCC 2025 Fall ZOZOエンジニア登壇情報 #3 ZOZOのJavaチームより、ZOZOMO部の宮澤が『Axon Frameworkのイベントストアを独自拡張した話』というテーマで登壇します! 🕐️13:40 - 14:00 📍 Room J ぜひお越しください! #jjug_ccc — ZOZO Developers (@zozotech) 2025年11月15日 speakerdeck.com speakerdeck.com JSConf.jp 2025 Pre Event 11月15日に開催された「 JSConf.jp 2025 Pre Event 」に、計測システム部の林が「 Redux → Recoil → Zustand → useSyncExternalStore: 状態管理の10年とReact本来の姿 」というタイトルで登壇しました。 📢 ZOZOエンジニア登壇情報 本日 11/15 開催の https://t.co/iHfqcKDcec 2025 Pre Event に計測システム部の林が『Redux → Recoil → Zustand → useSyncExternalStore:状態管理の10年とReact本来の姿』というテーマで登壇します! https://t.co/lmBQEUTSvI #zozo_engineer #jsconfjp — ZOZO Developers (@zozotech) 2025年11月15日 speakerdeck.com JSConf JP 2025 11月16日に開催された「 JSConf JP 2025 」に、計測システム部の林と、WEARフロントエンド部 テックリードの冨川( @ssssotaro )が登壇しました。 本日 11/16 に開催される『JSConf JP 2025』にて、計測システム部の林 @www_REM_zzz が『JavaScript・TypeScript安全開発カルタ』というタイトルで登壇します! 🕙 10:40-10:50 📌 Track B 🔗 https://t.co/TXH8nlUmVM #zozo_engineer #jsconfjp — ZOZO Developers (@zozotech) 2025年11月16日 本日 11/16 に開催される『JSConf JP 2025』にて、WEARフロントエンド部 テックリードの冨川 @ssssotaro が『Atomics APIを知る』というタイトルで登壇します! 🕙 10:20-10:30️ 📌 Track C 🔗 https://t.co/b16g68OSpv #zozo_engineer #jsconfjp — ZOZO Developers (@zozotech) 2025年11月16日 speakerdeck.com Flutter ZY 11月19日に開催された「 Flutter ZY 」に、ZOZOマッチの開発を担う新規事業部の池田と大野の2名が登壇しました。 speakerdeck.com speakerdeck.com 開発組織でアクセシビリティを進める技術 by SmartHR 11月19日に開催された「 開発組織でアクセシビリティを進める技術 by SmartHR 」に、ZOZOTOWN開発2部の菊地( @hiro0218 )が「 セマンティックHTMLによる アクセシビリティ品質向上の基礎 」というタイトルで登壇しました。 speakerdeck.com アーキテクチャConference 2025 11月20日に開催された「 アーキテクチャConference 2025 」1日目に、物流開発部の矢部とSRE部の杉山が「 巨大モノリスのリプレイス──機能整理とハイブリッドアーキテクチャで挑んだ再構築戦略 」というタイトルで登壇しました。 「アーキテクチャConference 2025」にて、SRE部の杉山と物流開発部の矢部が『巨大モノリスのリプレイス機能整理とハイブリッドアーキテクチャで挑んだ再構築戦略』というタイトルで登壇します! 🕑️14:05-14:45 📌 E会場 🔗 https://t.co/FlMbbz1Cxc #zozo_engineer #アーキテクチャcon_findy pic.twitter.com/4dm5rkjtzL — ZOZO Developers (@zozotech) 2025年11月20日 speakerdeck.com After JJUG CCC 2025 Fall 11月25日に開催された「 After JJUG CCC 2025 Fall 」に、ZOZOMO部の木目沢( @pilgrim_reds )が「 意外と難しいドメイン駆動設計の話 」というタイトルで登壇しました。 speakerdeck.com Mobile App with AI Meetup Tokyo 11月28日に開催された「 Mobile App with AI Meetup Tokyo 」に、技術戦略部の堀江( @Horie1024 )が「 ZOZOにおけるAI活用の現在 ~モバイルアプリ開発でのAI活用状況と事例~ 」というタイトルで登壇しました。 『Mobile App with AI Meetup Tokyo』で技術戦略部の堀江が発表した資料を公開しています!ぜひご覧ください! 『ZOZOにおけるAI活用の現在 ~モバイルアプリ開発でのAI活用状況と事例~』 https://t.co/GiwdnPlqtL #zozo_engineer #mobilewithai — ZOZO Developers (@zozotech) 2025年12月1日 speakerdeck.com フロントエンドカンファレンス関西2025 11月30日に開催された「 フロントエンドカンファレンス関西2025 」に、ZOZOTOWN開発2部の齋藤( @Jin_pro_01 )が『 TypeScriptがブラウザで実行されるまでの流れを5分で伝えたい 』というタイトルで登壇しました。 11/30に開催される『フロントエンドカンファレンス関西2025』にて、ZOZOTOWN開発2部の齋藤 @Jin_pro_01 が『TypeScriptがブラウザで実行されるまでの流れを5分で伝えたい』というタイトルで登壇します! 🕙 16:40~ 📌 ナレッジワークルーム 🔗 https://t.co/hTygsaWYSM #zozo_engineer #jsconfjp — ZOZO Developers (@zozotech) 2025年11月28日 speakerdeck.com 掲載 HHKB × ZOZOTOWNコラボレーション 11月18日の14:00から12月5日の11:59にかけて、HHKBことHappy Hacking KeyboardとZOZOTOWNのコラボレーションアイテムの受注販売を実施しました。これは昨年実施したGitHubとZOZOTOWNのコラボレーションに続くITコラボ第2弾として企画されたもので、多くの注目を集めました。 corp.zozo.com www.pfu.ricoh.com pc.watch.impress.co.jp www.itmedia.co.jp www.4gamer.net realsound.jp www.gizmodo.jp また、受注販売期間中に実施された HHKBユーザーミートアップ Vol.9 にて、HHKB × ZOZOTOWNのコラボアイテムを展示するブースを設けたほか、ZOZOコラボ記念トークセッション「 HHKB×FASHION TECHでひらく、創造性の扉 」にて、技術戦略部の諸星( @ikkou )とIT支援部の溝渕( @assu_ming )が登壇しました。 green-keys.info 以上、2025年11月のZOZOの活動報告でした! ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。 corp.zozo.com
はじめに こんにちは。Developer Engagementブロックの @wiroha です。2025年11月13日に「 FlutterKaigi 2025 」が開催されました。ZOZOはSilverスポンサーとして協賛し、スポンサーブースを出展しました。ZOZOは、2025年6月30日にFlutter製アプリ「 ZOZOマッチ 」をリリースしており、今回初めてFlutterKaigiに協賛しました! technote.zozo.com 本記事では、エンジニアが気になったセッション、協賛ブースの様子、スカラーシップスポンサーとして参加した学生支援交流イベントについてお伝えします! エンジニアが気になったセッションの紹介 Dart and Flutter MCP serverで実現するAI駆動E2Eテスト整備と自動操作 新規事業部の池田です。酒井さんによる「 Dart and Flutter MCP serverで実現するAI駆動E2Eテスト整備と自動操作 」のセッションが勉強になりました。E2Eテストは便利な反面、メンテナンスや実行コストが高くて「なかなか運用が大変…」という印象をずっと持っていたので、AIを使って効率化できるという話にはかなり惹かれました。特にClaude CodeやMCPを使って、UIの操作をAIが理解して自動で実行してくれる仕組みはすごく興味深かったです。実際のデモを見るとまだ時間がかかったり、動作が少し不安定な部分もあったりするようですが、今後モデルが進化していけば現実的に使えるレベルになりそうだなと感じました。 全体を通して、AIを使ってE2Eテストをより柔軟で効率的にしていく流れが見えてきて、今後のモデル改善やツールの進化にすごく期待したいと思いました。 アプリバイナリに対する不正対策とセキュリティ向上 新規事業部のじゅんじゅんです。FujiKinagaさんによる「 アプリバイナリに対する不正対策とセキュリティ向上 」のセッションがとても勉強になりました。 普段はアプリ開発でバイナリレベルまでセキュリティを意識する機会があまりないため、興味を持ってこのセッションを見に行きました。Firebaseではクラッシュ分析などを行っていましたが、「Firebase App Check」というツールは初めて知り、とても便利そうだと感じました。近年、サイバー攻撃を受けた企業のニュースを目にすることも増えており、このようなツールを活用して監視体制を整えることの重要性を改めて実感しました。 また、「 root_jailbreak_sniffer 」という脱獄検知パッケージも初めて知りました。モバイルアプリにおけるセキュリティ対策の幅広さを理解し、ひとつ詳しくなれた気がします。 通信の暗号化、コードの難読化、秘匿情報の管理、OSやSDKを最新の状態に保つなど、基本的な対策を徹底することの大切さも学びました。 ブースレポート 協賛ブースではZOZOマッチのサービスを知るきっかけになればと、恋みくじ企画を実施しました。技術に絡めたおみくじを楽しんでいただきました。また、ご縁がありますようにという願いをこめたノベルティ「色縁ぴつ(色鉛筆)」の配布も行いました。 実際の開発で使っているWidgetbookの展示も行いました。「実際のプロダクトで使われているものを見られる機会は珍しい」という声を多くいただき、導入のきっかけや運用方法、どんなメリットがあるのかといった質問もたくさん寄せられました。開発現場でのリアルな事例をお伝えできたのは、とても良い機会になったと思います。 また、ZOZOマッチアプリを実際の端末で体験してもらい、アニメーションやUIの細かな部分まで見ていただけました。Flutterならではの動きやデザインの表現力を実際に感じてもらえたのが嬉しかったです。 さらに、ZOZOとして初めてFlutterを採用したプロジェクトということもあり、「なぜFlutterを選んだのか?」「導入の際にどんな課題があったのか?」といった質問も多くいただきました。技術選定の背景やチームのチャレンジに興味を持ってもらえたのはとても印象的でした。 学生支援交流イベント 新規事業部のじゅんじゅんです。今回のFlutterKaigiでは、初の試みとして学生交流イベントを実施しました。イベント期間中は専用ブースを出展し、特定の時間に学生グループが順番に訪れる形式で交流しました。ブースではまず会社を紹介し、その後はカジュアルに学生の皆さんと話す流れになっていました。 ブースに訪れた多くの学生と直接話すことができ、エンジニア歴や得意な言語、開発経験などを伺うことができました。Flutterを触り始めたばかりの方から、数年使い込んでいる方までレベルも幅広く、非常に刺激的な時間でした。また、印象的だったのは関東以外の学生が大半を占めていたことです。特に関西からの参加者が多く、Flutterコミュニティの広がりを肌で感じました。 今回が初めての学生支援・交流イベントということで、どのような雰囲気になるのか不安もありましたが、結果として多くの学生と実際に言葉を交わす貴重な機会となり、想像以上に充実した時間を過ごせました。来年は、今回の経験を活かしてより多くの学生が参加できるよう、規模を拡大して開催してほしいと思います。 おわりに FlutterKaigi 2025では、最新のFlutter技術に触れられる貴重な機会となり、ZOZOとしてもFlutterコミュニティへの貢献と交流を深めることができました。今後もFlutterを活用した開発を進めていく中で、今回の学びや出会いを活かしていきたいと思います。実行委員会の皆さま、参加者の皆さま、ありがとうございました! FlutterKaigiをきっかけに、ZOZOマッチのFlutterアプリ開発にご興味のある方は、ぜひ以下のリンクから関連記事をご覧ください。 ZOZOマッチアプリのアーキテクチャと技術構成 FigmaからFlutterへ ── デザイントークン自動変換とUIカタログで実装を加速 デバッグメニューでFlutterのアプリ開発をスムーズに! ZOZOマッチアプリのメッセージ機能を支えるFlutter × GraphQLの実装 ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからご応募ください。 corp.zozo.com