※この記事は、2025 Speee Advent Calendar 4日目の記事です。昨日の記事はこちら
こんにちは。株式会社Speee DX開発基盤グループに所属している川田と申します。
この記事では、開発組織全体の生産性向上を目指す中で、私たちの問い合わせ対応リードタイムがボトルネックになっていたという課題に対し、過去の対応を振り返って調査・対応手順を体系化した取り組みについてお話しします。その結果、対応の属人化解消の見込みが立ち、さらなる改善への足がかりができました。
DX開発基盤グループは、DX事業本部のプロダクト開発・運用において共通して必要となる基盤を各部署の開発組織へ提供しています。また、プロダクトを安定稼働させるための監視の仕組みの運用やノウハウの普及を通じて、プロダクト群の品質向上・開発コストの削減・開発速度の向上に貢献する部署です。
例えば、私たちは主要なプロダクトごとにKubernetesクラスタを作成し、その中でウェブサイトを配信するアプリケーションを動かしています。DX開発基盤グループは、それらのクラスタを管理し、リソースの構成や主要なメトリクスの監視機構を共通化したり、プロダクト個別の実態に合わせて調整が必要な箇所を調整したりしています。
プロダクトごとに、本番環境とは別に、ルーティングなどのインターフェイスについては本番と同様の状態にした複数の検証用環境を用意し、開発物の動作確認を行っています。
まだ本番にリリースしていない機能の動作確認を行うため、また、本番環境と比べて一環境あたりの計算リソースの割り当てを小さくしているため、検証環境がダウンしたり、アプリケーションのデプロイが詰まったりといった事故が比較的頻繁に発生します。
DX開発基盤グループでは、主に各部署の開発組織のメンバーから問い合わせを受けていますが、直近では検証環境へのデプロイ失敗に関する問い合わせの割合が高く、3ヶ月で20件弱にのぼりました。
これらの障害が発生すると、各部署の開発者は検証環境が復旧するまで動作確認ができず、開発が止まってしまいます。そのため、DX開発基盤グループとしては優先度を上げて対処する必要がありますが、都度探索的な調査が必要となり、対応完了までに時間がかかっていました。
つまり、私たちの問い合わせ対応リードタイムが、開発組織全体の生産性のボトルネックになっているという課題がありました。
また、DX開発基盤グループ側から見ても、進行中の作業が中断され、本来の開発タスクにかけられる工数が圧迫されていました。
もちろん、根本原因の解消を目指すことも重要です。しかし、原因は多岐にわたるため、その再発防止に取り組む時間を捻出するためにも、まずは対応リードタイムを短縮し、工数をかけずに調査・復旧を行えるようにすることが先決だと考えました。
「検証環境の復旧対応のリードタイムを短縮する」という課題を解決するため、まずは経験則から、対応工数の内訳について仮説を立てました。
その結果、「何から見ていいか定めておらず、症状から見て怪しいところをしらみ潰しに調べるところからスタートしている」というやり方をしていることで、無駄な調査・解釈に時間を費やしてしまっていることが、リードタイムを大きく膨らませているのではないかという仮説を立てました。
これを受けて、過去の対応を整理・類型化することで、調査の型のようなものを作り出そうと考えました。
DX開発基盤グループでは、すべての依頼対応の窓口を一つのSlackチャンネルに絞っており、対応時の記録についてもすべてそこにまとめています。
そこで、直近20件のデプロイ失敗に関する問い合わせの解決までの流れを見返し、以下の点についてすべて振り返り、まとめる作業を行いました。
その結果、必要な調査も対応も、ある程度の共通点があることが確認できました。そこで次に、「どのような順番で調査をしたらよいか」「調査の結果をどう解釈し、次に行う調査は何にすべきか」を整理するため、以下のようなフローチャートを作成しました。

※ほぼ同じ根本原因を持つ問い合わせとして「検証環境が502エラーで見れない」もあったため、統合しています
※一部、根本原因が大きく異なるものは除外しています
各対応に番号を振り、このフローチャートに沿って各対応をした場合にどのステップをたどるかを表現しています。
この図をまとめたことで、2つのことに気づきました。
特に後者について整理し直し、Kubernetes Eventsの解釈の仕方をまとめることで、シンプルな調査・対応ガイドを作れるイメージがありました。そこで、以下のような「検証環境復旧チャート」を作成しました。
検証環境に問題が発生したら、直近のKubernetes Eventsを確認し、以下のパターンに当てはまるか確認します。
Taintとは、Kubernetesのノードに付与し、特定の条件を満たすPod以外はスケジュールできないようにする、ラベルのようなものです。
Kubernetes Eventsの中に、
NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE... ... Warning FailedScheduling Pod/... 0/... nodes are available: ... node(s) had untolerated taint {...のようなものが出ていた場合は、Taintがすべてのノードに付与されてしまっており、Podがスケジュールできなくなっています。
一部のTaintは、ノードコントローラーがノードのリソースに異常を検知した際に、異常の内容に応じて自動的に付与されます。私たちはTaintを取り扱うようなシステムを自前で構築していないため、ノードに付与されているTaintは基本的にノード本体の異常を示しています。
参考:https://kubernetes.io/ja/docs/concepts/scheduling-eviction/taint-and-toleration/#taint%E3%82%92%E5%9F%BA%E3%81%AB%E3%81%97%E3%81%9F%E6%8E%92%E9%99%A4
そのため、Taintの種類によって対処は異なります。私たちが直近で経験したのは以下の2種類です。
これはノードのマシンのディスク容量が逼迫していることを示します。EKSにおいては、ノードとしてEC2インスタンスが使われており、そこにアタッチされているEBSボリュームの容量が不足しています。
私たちのアプリケーションは、コンテナ上のストレージを大量に消費するような挙動はしないことが多いため、主にコンテナイメージの保存においてディスク容量を消費します。
そもそもコンテナイメージ一つ一つがそれなりに大きく、その上、検証環境では複数の環境を一つのKubernetesクラスタ上に構築しており、一つのノードに本番よりも多くのコンテナイメージが必要となることから、この問題が発生しやすくなっています。
対処としては、EC2インスタンスにアタッチするEBSボリュームの容量を増やします。
これは、Kubernetesコントロールプレーン上のノードコントローラーが、ノード上のkubeletと疎通できていないことを示します。
現状、私たちが遭遇したケースについて原因の特定には至っていませんが、大量のネットワークトラフィックが発生したときに起きる傾向があります(ネットワークI/Oが詰まり、kubeletとノードコントローラー間の疎通ができなくなっている可能性があります)。
対処としては、ノードの再起動を行うのが確実で手っ取り早いです。
私たちはKubernetesクラスターにExternal Secrets Operatorを導入しています。これは、Kubernetes上のSecretリソースを、外部の秘匿情報管理システムから同期したデータをもとに作成する仕組みです。私たちは、AWS Systems ManagerのParameter Storeで外部APIキーなどの秘匿情報をまとめて管理し、External Secretカスタムリソースの設定に従ってKubernetes上のSecretリソースへ同期しています。
Kubernetes Eventsの中に、
NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE... ... Warning UpdateFailed externalsecret/... error processing spec.data[...] (key: ...), err: Secret does not exist
のようなものが出ていた場合は、同期先のParameterの取得に失敗しています。遭遇したケースの多くは、Parameterの作成が失敗していることが原因でした。
対処として、Systems Manager Parameterのリソースが正常に作成されているか確認・修正します。
Kubernetes Eventsの中に、
NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE... ... Warning FailedCreatePodSandBox ... Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "...": plugin type="aws-cni" name="aws-cni" failed (add): add cmd: failed to assign an IP address to container
のようなものが出ていた場合は、Amazon VPC CNIプラグインが提供するaws-nodeデーモンセットが管理するIPアドレスプールが枯渇し、新規のPodに対して割り当てるIPアドレスがなくなっている状態を示しています。
前述のとおり、検証環境では複数環境が少数のノードを共有するため、一環境あたりのPod数が多いアプリケーションにおいては、この状況に陥りやすいです。
aws-nodeデーモンが管理するIPアドレス数の上限は、EC2インスタンスにアタッチされているENIの数と、それぞれのENIにAWSから割り当てられるIPアドレス数の上限によって決まります。デフォルトでは、ENIはプライマリIPアドレスを除いて、個別のセカンダリIPアドレスが割り当てられますが、VPC CNIプラグインの設定により、ENIはセカンダリIPアドレスの代わりにIPアドレス範囲を割り当てることができます。これにより、一つのENIへルーティングされるIPアドレス数を増やすことができ、結果としてaws-nodeデーモンセットが管理するIPアドレス数の上限を引き上げることができます。
参考:https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/cni-increase-ip-addresses.html
私たちはこの設定をまだ全プロダクトの検証環境に適用していないため、この問題に遭遇した箇所から順に適用しています。
Kubernetes Eventsの中に、
NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE... ... Warning BackOff Pod/... Back-off restarting failed container sandbox in pod ...
のようなものが出ていた場合は、Podの起動に繰り返し失敗しています。また、
NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE... ... Warning Unhealthy Pod/... ... probe failed: HTTP probe failed with statuscode: ...
のようなものがその前に出ていた場合は、Pod内で動作するコンテナのヘルスチェック(Liveness/Readiness/Startup Probes)に失敗しており、何らかの理由でコンテナが正常に起動できていない状態を示しています。
まずはコンテナのログを確認し、non-zero exitらしきエラーとともにプロセスが終了していそうな場合は、アプリケーション側に問題があります。
実際に私たちが経験したケースでは、Ruby on Railsのアプリケーションにおいて、環境別のsettingsに設定のズレがあったり、アプリケーションで参照している環境変数が、PodにenvFromとして設定しているSecretやConfigMapリソースに存在しなかったりといったミスがありました。
一方、ログ等に特にエラーがなく、HTTPサーバーの起動処理が完了する前に急にPodが落ちているような場合は、CPUスロットリングが原因である可能性があります。
一部のコンテナイメージは、起動直後に高いCPU負荷がかかるものがあり、これを検証環境の少数のノードに対して複数環境分のPodで一斉に起動しようとすると、ノード全体でCPU時間が枯渇し、CPUスロットリングが発生します。その結果、各Podの起動処理が大幅に遅延し、LivenessProbeのinitialDelaySecondsが経過するまでにHTTPサーバーの起動が完了せず、ヘルスチェックに落ちてPodを再起動、またコンテナ起動がやり直しになりCPU負荷がかかる…といったループに陥ることがあります。
対処として、一旦手動でDeploymentリソースを削除し、Podを少数ずつ立ち上げ直すことで、CPU負荷を分散させつつ起動していきます。
Kubernetes Eventsの中に、
NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE... ... Warning Failed Pod/... Failed to pull image "...": ...
のようなものが出ていた場合は、Pod内で使用するコンテナイメージのプルに失敗しており、ECR上にあるはずのコンテナイメージが消えている、またはそもそもプッシュされていないことが考えられます。
これが発生した場合、各プロダクト共通で提供しているデプロイの仕組みの、今まで経験してこなかったエッジケースを踏んでいる可能性が高く、デプロイ開始から完了までの流れが正常に動作しているかを点検し、問題を探すこととなります。
幸い、この整理を行ってから記事執筆までの間、検証環境のダウンは発生しておらず、実際の効果測定はこれからです。しかし、すでにいくつかの改善の兆しが見えています。
まず、対応の属人化が解消される見込みが立ちました。 検証環境復旧チャートとして行うべき対応を言語化したことで、これまで特定のメンバーに依存していた復旧対応を、他のメンバーでも実施できるようになります。実際、別の領域を担当しているDX開発基盤メンバーにレビューいただいたところ、「対応のイメージが湧いた、これなら自分でもできそう」というフィードバックをもらえました。対応できる人が増えれば、問い合わせから復旧までのリードタイムは自然と短縮されていくはずです。
また、さらなる改善に向けた土台ができました。 「対応」「結果の解釈」「分岐」の内容をより厳密に定めていくことで、将来的にはこのチャートをベースに、既知のパターンに当てはまるかの調査と、そのパターンにおける問題解消のための対応を、Devin等のエージェント型のLLMアプリケーションに任せることも視野に入ります。少ない開発工数で、中途半端な手書きのスクリプトよりもロバストな解釈ができるのではないかという仮説を持っており、当初のリードタイム短縮にとどまらず、自動化まで狙えるのではないかと考えています。
もし検証環境の復旧が自動化できれば、DX開発基盤グループの工数は浮きますし、検証環境が壊れてしまった各部署の開発組織にとっても、復旧までのリードタイムがDX開発基盤メンバーの稼働状況に左右されなくなります。開発組織全体の生産性向上という当初の目標に向けて、着実に前進できていると感じています。
Speeeでは一緒にサービス開発を推進してくれる熱い仲間を募集しています!
新卒の方はこちらより本選考に申し込みが可能です!キャリア採用の方はこちらのFormよりカジュアル面談も気軽にお申し込みいただけます!
Speeeでは様々なポジションで募集中なので「どんなポジションがあるの?」と気になってくれてた方は、こちらチェックしてみてください!
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。