誰もがそのドリルを知っていますよね?コード変更の作業が完了し、[できれば] ローカル マシンでテストした後、変更をサイクルの次の段階にプッシュします。ローカル テストには非常に偏りがあり、理想的には、より安定した環境で変更を検証したいと考えています (また、変更を実装したエンジニアからの視点のみに従わないでください)。
ここでは次のステップが非常に自然に思えます。変更を信頼できるステージング環境にプッシュし、変更を移動する前にパートナー (QA、PM、その他のエンジニア) に検証を手伝ってもらいます。その後、実稼働環境にプッシュするのに十分であると判断されるまで、バグ修正と再検証が行われます。素晴らしい!
ただし、ほとんどの状況では、これはまったく起こりません。さまざまな理由が考えられますが、理由に関係なく、結果として、テストや検証が十分に行われる前に、運用サーバーへの変更をプロモートしなければならないことがよくあります。
問題は…何かが壊れたらどうするかということです。実際、問題を早期に発見するにはどうすればよいでしょうか?良いニュース: いくつかのツールや手法を採用することで、実稼働環境でのテストと検証を、あなたやあなたの会社にとって安全な方法であるだけでなく、良いアイデアにすることも可能です。
実稼働環境でのテストに移る前に、メトリクスについて話しておく必要があります。出荷している変更が望ましい効果を生み出すか、望ましくない副作用を引き起こさないか、製品がまだ安定しているかなどを検証するためにメトリクスが必要です。 -確立された指標があるため、変更を展開する際には基本的に盲目的です。この記事の多くのトピックでメトリクスについて言及しますので、留意すべき 2 つの異なるタイプのメトリクスを見てみましょう。
変更を実装した後は、KPI、目標、ユーザー行動などのビジネス関連の指標を監視して、影響を評価する必要があります。変更を行う前に、影響を受けると予想される指標を特定します。同様に重要なのは、変更すべきでないものを示す指標であるガードレール指標です。これらのガードレールの予期せぬ変化は、新しい変更に問題があることを示している可能性があり、見直しが必要になります。
ビジネス指標を定義したら、技術的な指標を理解することも重要です。これらは、時間の経過とともに変更が導入されてもシステムが正常な状態を維持できるようにするための基本です。ここでは、システムの安定性、エラー率、ボリューム、マシン容量の制約などについて話します。
優れた技術指標は、ビジネス指標で観察された問題を説明したり、リグレッションの根本原因を迅速に特定したりするのにも役立ちます。たとえば、最後のバージョンの公開後、ユーザーが特定の機能にあまり関与していないことが観察されたとします。リクエストのタイムアウトやエラー率が増加すると、どのサービス/エンドポイントが問題の原因となっているかがすぐに判明する可能性があります。
ビジネスおよび技術的な指標が明確に定義されています。今、私たちは彼らを監視しなければなりません。それにはさまざまな方法がありますが、一般的な最初のステップは、メトリクスを長期にわたって追跡し、異常なスパイクを簡単に発見できるようにするダッシュボードを構築することです。ダッシュボードで、ビジネスに特に関連する可能性のある特定のセグメントに基づいてデータを迅速にフィルタリングできれば、さらに良いでしょう。ダッシュボードをアクティブに監視することは、新しい変更がシステムにもたらした影響を迅速に視覚化する良い方法です。一部の企業では、アクティブな監視が非常に重要であると考えており、問題をできるだけ早く検出して対処するために 24 時間 365 日の監視シフトを設けていることもあります。
メトリクスを監視するもう 1 つの良い方法は、自動検出とアラートを使用することです。主要なメトリクスについては、何か問題があると思われる場合、アラートによってリアルタイムの通知が提供されます。機能のロールアウトを開始し、プロセスの開始から数分後に、エラー率が特定のしきい値を超えて増加しているというアラートを受け取ったとします。この早期通知により、本番環境での変更のさらなる伝播を防ぎ、多くの問題を回避できます。
最後に、どのような状況でどれだけの情報が必要なのかを念頭に置くことが重要です。ダッシュボードは製品やシステムのパフォーマンスを視覚的に把握するのに非常に便利ですが、1,000 種類の異なるグラフを追加すると、明確さよりも混乱が大きくなります。同様に、1 日に 1,000 件のアラートを受信した場合、それらを調査して対処することは不可能であり、無視されることになります。
メトリクスが定義され、適切なモニタリングが行われ、素晴らしいです。次に、問題を回避し、問題を早期に検出し、本番環境への影響を最小限に抑えるのに役立ついくつかのツールと戦略を見てみましょう。運用環境の設定方法によっては、これらの一部は他のものよりも実装が難しく、組み合わせてもあまり意味がない場合もあります。ただし、ここに挙げた各項目は、安全で安定した生産環境に近づくのに役立つ可能性があります。
自動テストは、プロジェクトが軌道から外れると無視されることが多いですが、開発を促進し、本番環境への変更をより安全かつ迅速に行うことができます。問題が早期に発見されるほど、より早く修正できるため、プロセスにかかる全体的な時間が短縮されます。変更を元に戻し、修正し、再度プッシュするプロセスは通常、非常にストレスがかかり、貴重な時間が奪われる可能性があります。
単体テスト、統合テスト、エンドツーエンド テストで 100% のテスト カバレッジを目指すことは、ほとんどのプロジェクトにとって理想的かもしれません。代わりに、労力と利益に基づいてテストの優先順位を付けます。指標はこれを導くことができます。中核的なビジネス機能をカバーすることは、影響の少ないニッチな機能よりも重要である可能性が高いですよね?コア機能から始めて、システムの進化に応じて拡張します。
実稼働環境への公開プロセスには、実稼働環境にデプロイする前にテスト スイートの実行を含める必要があります。テストが失敗すると公開が一時停止され、運用上の問題が回避されます。翌日になって完全に機能不全に陥っていることが判明するよりは、機能のリリースを遅らせる方が望ましいでしょう。
ドッグフーディングは、最終ユーザーに届く前に内部テストのために機能をリリースするプロセスです。ドッグフーディング中、この機能は本番環境で利用できるようになりますが、内部ユーザー (従業員、チーム メンバーなど) のみが利用できます。このようにして、外部ユーザーに影響を与えることなく、実際の運用データを使用して、新機能が期待どおりに動作するかどうかをテストおよび検証できます。
ドッグフーディングにはさまざまな戦略があります。概要を簡略化するために、それらを 2 つの大きなバケットにグループ化できます。
カナリア リリースは、運用環境での変更をすべてのサーバーに一度にロールアウトするのではなく、その変更を一部のサーバーで利用できるようにし、しばらく監視するリリース プロセスです。変更が安定していることが証明された後にのみ、変更は実稼働環境にプッシュされます。
これは、新機能や危険な変更をテストするための最も強力なツールの 1 つであり、運用環境で何かが壊れる可能性が低くなります。ユーザーのグループで変更をテストすることで、問題が検出された場合はロールアウト プロセスを停止/元に戻すことができ、ほとんどのユーザーへの影響を回避できます。
DevOps プラクティスである Blue Green Deployment は、2 つのサーバー クラスター (Blue と Green) を使用し、それらの間で運用トラフィックを切り替えることによってダウンタイムを防ぐことを目的としています。機能のロールアウト中、変更は 1 つのセット (緑) に公開され、もう 1 つのセット (青) は変更されません。問題が発生した場合は、以前のバージョンで実行され続けていたため、トラフィックをすぐに Blue サーバーに戻すことができます。
ブルー グリーン デプロイメントは、前に説明したカナリア リリースと対比されることがよくあります。この議論の詳細には立ち入りませんが、どのツールが私たちの作業により適しているかを判断する際に役立つように、これについて言及することが重要です。
キル スイッチはソフトウェア エンジニアリングの文脈で生まれたものではないため、その使用法を理解する最良の方法は、元の目的と設計を振り返ることです。産業で使用される機械のキルスイッチは、非常に単純な操作 (通常は単純なボタンまたはオン/オフ スイッチ) によってできるだけ早く機械を遮断する安全機構です。これらは緊急事態のために存在し、あるインシデント (たとえば、機械の故障) がさらに悪いこと (怪我や死亡) を引き起こすことを防ぐために存在します。
ソフトウェア エンジニアリングでは、キル スイッチも同様の目的を果たします。つまり、システムを稼動し続けるために特定の機能を失う (または強制終了する) ことを受け入れます。この実装は、高レベルでは条件チェック (以下のコード スニペットを参照) であり、通常は特定の変更または機能のエントリ ポイントに追加されます。
if (feature_is_enabled('feature_x')) {xNewBehaviour();} else {xOldBehaviour();}
たとえば、新しいサードパーティ API への移行を出荷しているとします。テストではすべてが正常で、カナリア リリースでは安定しており、変更は本番環境に 100% ロールアウトされます。しばらくすると、新しい API が量に苦戦し始め、リクエストが失敗し始めます (技術的な指標を覚えていますか?)。キル スイッチがあるため、API リクエストを即座に古い API に戻すことができ、以前のバージョンに戻したり、ホットフィックスをすぐに配布したりする必要はありません。
技術的に言えば、キルスイッチは実際には機能切り替え (別名機能フラグ) の特定の使用例です。この話題に進むにつれて、機能切り替えのもう 1 つの大きな利点について言及する価値があります。それは、トランクベースの開発を可能にすることです。機能切り替えのおかげで、不完全またはまだテストされていない場合でも、新しいコードを安全に運用環境にプッシュできます。
上に例示したコードを見て、古い動作と新しい動作が同時にアプリケーション内に存在する、これが実際に良いパターンなのか疑問に思った人もいるでしょう。これは私たちがコードベースに望む最終状態ではない可能性が高いことに同意します。そうしないと、コードのすべての部分が if/else 句で囲まれてしまい、すぐにコードが読めなくなってしまいます。
ただし、古い動作を急いで削除する必要はありません。はい、コードが使用されなくなったらすぐにクリーンアップして、技術的負債を避けたいと思うのは非常に誘惑的です。ただし、機能切り替えの下でしばらくそのままにしておくのも問題ありません。場合によっては、新機能が安定するまでに時間がかかることがありますが、バックアップ オプションを用意しておくと、短期間であっても元の機能に戻す必要がある場合に備えて安全なメカニズムになります。
各リリースのライフサイクルは異なるため、古いコードを削除する適切な時期を追跡することをお勧めします。コードをクリーンな状態に保ち、メンテナンスのオーバーヘッドを軽減すると、コード内で機能を無効にしているにもかかわらず、無効になってからの時間が経過しているため、機能が壊れている可能性があるという逆の状況を回避できます。
より安全な変更を実装するための私のお気に入りのテクニックの 1 つは、シャドウ テストまたはシャドウ モードとして知られています。これは、古い動作と新しい動作の両方を実行して結果を比較することで構成されますが、必要に応じて新しい動作の副作用の一部を無効にします。この簡単な例を見てみましょう。
int sum(int a, int b) {int currentResult = currentMathLib.sum(a, b);int newResult = newMathLib.sum(a, b);logDivergences(a, b, currentResult, newResult);return currentResult;}void logSumDivergences(int a, int b, int currentResult, int newResult) {if (currentResult != newResult) {logger.warn( 'Divergence detected when executing {0} + {1}: {2} != {3}',a, b, currentResult, newResult);}}
両方の合計演算が実行されますが、新しい演算は相違を比較してログに記録するためにのみ使用されます。この手法は、複雑なシステムの変更を監視する場合に特に役立ち、古い動作と新しい動作の間にある程度の同等性が期待されます。もう 1 つの優れた使用例は、あまり馴染みのない製品に変更を加える必要がある場合、または意図した変更によってどのようなエッジ ケースが影響を受けるかがよくわからない場合です。
より複雑なシナリオでは、シャドウ テストを有効にする前に、いくつかの副作用を無効にする必要がある場合があります。たとえば、ユーザーをサインアップして DB に保存し、ユーザー ID を返す新しいバックエンド API を実装しているとします。完全なプロセスを実行するためにシャドウ DB を配置することもできますが、「登録成功」メールをバックエンド API ごとに 1 回ずつ、計 2 回送信するのは決して得策ではありません。また、同じ例では、返されたユーザー ID を単純に比較するだけではあまり役に立たないため、より詳細な比較ロジックが必要になります。
最後に、何を監視およびテストする必要があるか、同等が達成されない場合にどのような基準が適用されるかを理解することが重要です。一部の重要なシナリオでは、結果がまったく同じになるまでシャドウ テストを繰り返す必要があります。また、新しい実装によって損失を上回る追加の利点が得られる場合には、数%の乖離があっても問題ない場合もあります。
たとえ堅牢な保護手段があったとしても、システムが不安定になる可能性があります。問題が発生した場合、何が起こっているのかを適切な詳細レベルで理解できる必要があります。そうでないと、効率的な修正を行うことが非常に困難になる可能性があります。ここでログが窮地を救ってくれます。
ログ記録は新しい概念ではなく、実装が簡単なソリューションが多数存在しますが、効果的なログを確保するのは困難です。多くの場合、ログは不明確であったり、過度に複雑であったり、欠落していたり、無関係なエントリであふれていたりするため、トラブルシューティングが困難になります。ただし、ログは問題に対処するためだけではありません。適切なロギングは、新機能や変更の有効性を検証するのに役立ちます。ログ エントリをサンプリングすることで、ユーザー ジャーニーを追跡し、システムが意図したとおりに機能することを確認できます。
コードを運用環境に送信するのは危険な場合もありますが、プロセスをより安全にするための戦略が数多くあります。問題を特定したとしても、何が許容できるのか、何が許容できないのかを知ることも重要です。すべての失敗がロールバックを引き起こすわけではありません。重大なセキュリティ上の欠陥を修正したい場合、または新しい規制に準拠しようとしている場合はどうなるでしょうか?明確な基準を持ち、変更がどれほど重要であるかを理解することは、問題が発生した場合にいつ中止するか続行するかを決定するために非常に重要です。最初に戻ると、主な指標は意思決定プロセスに役立つものです。
皆さん、安全に着陸してください!