デプロイ後の手動チェックが失敗する理由(そして代わりに何をすべきか)

新しいバージョンをデプロイした。ブラウザを開き、いくつかのページをクリックし、データベースが応答することを確認する。すべて問題ないように見える。タブを閉じて、次の作業に移る。

週に一度のデプロイであれば、これでうまくいく。しかし、デプロイが毎日、あるいは1日に複数回行われるようになると、手動チェックは信頼できなくなる。同じ人が毎回まったく同じ検査を、何も見逃さずに繰り返すことはできない。急いでいるために省略されるチェックもある。次のミーティングが始まるからと、慌てて済ませられるチェックもある。「前回は大丈夫だった」という理由で、問題ないと決めつけられるチェックもある。

問題は、人が不注意だからではない。問題は、手動の繰り返しがスケールしないことにある。そして、誰かが1つのエンドポイントを確認し忘れたために、問題のあるデプロイがすり抜けてしまった場合、その代償はチーム全体が支払うことになる。

デプロイ後手動チェックの真のコスト

デプロイ後の手動チェックは、チームがしばしば過小評価する隠れたリスクを生み出す。

第一に、手動チェックは記憶に依存する。チェックを行う人は、すべての手順を覚えていなければならない。どのエンドポイントを叩くか、どのようなレスポンスを期待するか、どのデータベースクエリを検証するか、どのバックグラウンドジョブを確認するか。記憶は、特にプレッシャーがかかっている状況では信頼できない。

第二に、人によってチェック内容が異なる。あるエンジニアはログインフローと検索機能をチェックするかもしれない。別のエンジニアはチェックアウトプロセスと管理パネルをチェックするかもしれない。どちらもすべてをチェックしているわけではなく、お互いが残りをカバーしていると思い込んでいる。

第三に、手動チェックは証拠を残さない。手動チェックがパスしても、何がチェックされたのか、レスポンスは何だったのか、チェックが十分に徹底されていたのかどうかの記録は残らない。1時間後に何かが壊れたとしても、チェック結果を振り返って何が変わったのかを理解することはできない。

第四に、手動チェックはボトルネックを生み出す。チェックの方法を知っている人がゲートキーパーになってしまう。デプロイはその人を待つことになる。リリースは遅延する。そして、その人が不在の場合、チームはチェックなしでデプロイするか、まったくデプロイしないかの選択を迫られる。

デプロイ後チェックの自動化:核となるアイデア

解決策は単純だ。パイプラインが新しいバージョンのデプロイを完了した後、すぐに事前定義された一連のチェックを実行するようにする。これらのチェックは毎回同じ方法で実行され、誰も何を検査すべきかを覚えておく必要はない。

次のフローチャートは、ここで説明する手動アプローチと自動アプローチを対比させたものです。

flowchart TD subgraph Manual[手動デプロイ後チェック] A[新しいバージョンをデプロイ] --> B[ブラウザを開く] B --> C[ページをクリックして回る] C --> D[データベースを確認] D --> E[すべて問題ないと決めつける] E --> F[タブを閉じて次へ] F --> G[後日、見逃した問題が発覚] end subgraph Automated[自動デプロイ後チェック] H[新しいバージョンをデプロイ] --> I[パイプラインがスモークテストを起動] I --> J[パイプラインがシンセティックトランザクションを実行] J --> K{すべてのチェックがパス?} K -->|Yes| L[リリースの健全性を確認] K -->|No| M[すぐにチームにアラート] M --> N[特定の障害を調査] end Manual -.->|信頼性が低い| O[チームが代償を払う] Automated -.->|信頼性が高い| P[チームに証拠が残る]

これは、すべての機能をテストすることではない。デプロイ後も最も重要な機能が引き続き動作することを検証することである。チェックは、アプリケーションが生きていて、到達可能で、ユーザーにとって最も重要なレベルで正しく動作していることを確認する。

パイプラインはこれらのチェックを自動的に実行する。すべてのチェックがパスすれば、チームは新しいバージョンが健全であるという証拠を得られる。いずれかのチェックが失敗すれば、パイプラインはすぐにチームにアラートを送り、どの部分に注意が必要かを正確に把握できる。

以下は、そのような自動チェックスクリプトの最小限の例です。

#!/bin/bash
# post-deploy-check.sh
# デプロイ後にアプリケーションの健全性を検証するために実行します。

BASE_URL="https://your-app.example.com"
PASS=0
FAIL=1

# スモークテスト:メインエンドポイントを確認
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL")
if [ "$HTTP_CODE" -ne 200 ]; then
  echo "FAIL: メインエンドポイントが $HTTP_CODE を返しました"
  exit $FAIL
fi
echo "PASS: メインエンドポイントに到達可能です"

# シンセティックトランザクション:ユーザーログインフローをシミュレート
LOGIN_RESPONSE=$(curl -s -X POST "$BASE_URL/api/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"test_user","password":"test_pass"}')

if echo "$LOGIN_RESPONSE" | grep -q '"token"'; then
  echo "PASS: ログインフローは正常に動作します"
else
  echo "FAIL: ログインフローが失敗しました"
  exit $FAIL
fi

echo "すべてのチェックがパスしました。デプロイは健全です。"
exit $PASS

まず始めるべき2種類の自動チェック

スモークテスト

スモークテストは、デプロイ後自動チェックの最もシンプルな形態です。アプリケーションが実行中であり、基本的なリクエストに応答していることを検証します。

スモークテストでは、以下を確認するかもしれません。

  • アプリケーションがネットワーク接続を受け入れられるか?
  • メインエンドポイントが200ステータスコードを返すか?
  • 新しいバージョンがデータベースに到達できるか?
  • 重要なAPIルートが応答しているか?

これらのチェックは簡単に書けます。デプロイステップの後にパイプラインが呼び出すスクリプトにすぎません。いずれかのチェックが失敗した場合、パイプラインは停止し、チームに通知します。

スモークテストは包括的である必要はありません。起動しなかったサービス、壊れたデータベース接続、欠落した設定など、明らかな障害をキャッチできれば十分です。

シンセティックトランザクション

シンセティックトランザクションは、さらに一歩進んで、実際のユーザーが行う操作をステップバイステップでシミュレートします。

例えば、Eコマースアプリケーションの場合、シンセティックトランザクションは次のように動作します。

  1. 商品一覧ページを開く
  2. 特定の商品をクリックする
  3. カートに追加する
  4. チェックアウトに進む
  5. 配送先情報を入力する
  6. 注文を確定する

各ステップで、レスポンスが正しいことをチェックします。カートページがエラーを返したり、チェックアウトの読み込みに失敗したりした場合、シンセティックトランザクションは失敗し、パイプラインは何かがおかしいことを認識します。

シンセティックトランザクションはスモークテストよりもセットアップが必要です。ユーザーの行動を模倣するスクリプトを書く必要があります。しかし、スモークテストでは見逃してしまう、壊れたワークフローやマルチステッププロセスでのデータ欠落といった問題をキャッチできます。

まず何を自動化するか

すべてを一度に自動化しようとしないでください。壊れた場合に最も大きな痛みを引き起こす機能から始めましょう。

チームに問いかけてください。「もしデプロイしてこの機能が動かなくなったら、ユーザーはどれくらい早く気づくだろうか?どれくらいの損害が発生するだろうか?」

その答えが、何を最初に自動化すべきかを教えてくれます。ほとんどのアプリケーションでは、以下のようになります。

  • 認証とログイン
  • 検索機能
  • コアトランザクションフロー(チェックアウト、支払い、予約)
  • データ送信フォーム
  • 他のサービスが依存するAPIエンドポイント

1つか2つのシナリオから始めましょう。毎回のデプロイ後に一貫して実行します。何が壊れ、何が重要かを見極めながら、時間をかけて追加していきます。

不安定なテストや無関係なシナリオのために頻繁に失敗する大規模なスイートよりも、毎回実行される小さなチェックセットの方がはるかに価値があります。

結果を見えるようにする

自動チェックは、その結果が可視化され、アクションにつながる場合にのみ有用です。

パイプラインはすべてのチェックの結果を記録する必要があります。結果をデプロイログと一緒に保存します。チームが確認できるダッシュボードに表示します。

すべてのチェックがパスした場合、チームはデプロイが健全であるという明確な証拠を得られます。チェックが失敗した場合、チームはシステムのどの部分を調査すべきかを即座に把握できます。

この可視性は、チームの運用方法を変えます。デプロイがうまくいったかどうかを推測する代わりに、データを得られます。誰かの記憶に頼る代わりに、記録があります。ユーザーからの苦情を通じて問題を発見する代わりに、デプロイから数分以内に問題をキャッチできます。

次のステップ:検証から意思決定へ

デプロイ後の自動チェックが日常的になれば、次の論理的なステップは、それをゲートとして使用することです。チェックが失敗した場合、パイプラインは自動的にリリースがユーザーに到達するのを停止できます。チェックがパスした場合、リリースは続行されます。

これは、人間の判断を排除することではありません。毎回同じ手動検証を人間が繰り返す必要性を取り除くことです。チームは何をチェックするかを決定します。パイプラインはそれらのチェックを一貫して実行します。そしてチームは、ルーチンではなく例外に集中できます。

開始するための実践的なチェックリスト

本格的な自動化システムを構築する前に、小さく始めましょう。

  • アプリケーションにおいて、ユーザー向けの最も重要な3つの機能を特定する
  • メインエンドポイントが正しく応答するかをチェックするスモークテストスクリプトを1つ書く
  • コアユーザーフローをシミュレートするシンセティックトランザクションスクリプトを1つ書く
  • これらのチェックをデプロイパイプラインのデプロイステップ直後に追加する
  • いずれかのチェックが失敗した場合にチームに通知するようパイプラインを設定する
  • 1週間チェックを実行し、誤検知やギャップを記録する
  • 学んだことに基づいてチェックを調整する
  • 次の週にさらに1つのシナリオを追加する

具体的な takeaways

デプロイ後の手動チェックは、機能しなくなるまでは機能します。チームが1日に2回以上デプロイするようになった瞬間、あるいは誰かが1つの確認を忘れたために問題のあるデプロイがすり抜けてしまった瞬間、自動化が必要になります。

基本的な可用性を確認するスモークテストから始めましょう。重要なユーザーフローにはシンセティックトランザクションを追加します。結果を見えるようにします。そして、パイプラインに繰り返し処理を任せることで、チームは本当に人間の注意を必要とするものに集中できるようにします。

目標は、人間による監視を排除することではありません。目標は、誰かが同じことを100回目も確認しなければならなかったために発生する、そういった種類の監視を排除することです。