継続的インテグレーション、デリバリー、およびデプロイメントの紹介
紹介
ソフトウェアの開発とリリースは、特にアプリケーション、チーム、デプロイメントインフラストラクチャが複雑さを増すにつれて、複雑なプロセスになることがあります。プロジェクトが拡大するにつれて、課題はより顕著になることがよくあります。迅速かつ一貫した方法でソフトウェアを開発、テスト、リリースするために、開発者と組織はこれらのプロセスを管理し自動化するために、関連しているが異なる3つの戦略を作り出しました。
継続的インテグレーションは、個々の開発者の作業を主なリポジトリに1日に何度も統合することに焦点を当て、統合バグを早期に発見し、協力的な開発を加速させることを目的としています。継続的デリバリーは、デプロイメントまたはリリースプロセスにおける摩擦を減らすことに関心を持ち、ビルドをデプロイするために必要なステップを自動化して、いつでも安全にコードをリリースできるようにします。継続的デプロイメントは、コード変更が行われるたびに自動的にデプロイすることによって、これをさらに一歩進めます。
このガイドでは、これらの戦略のそれぞれについて、互いにどのように関連しているか、そしてそれらをアプリケーションライフサイクルに組み込むことで、ソフトウェア開発とリリースの実践がどのように変わるかを議論します。さまざまなオープンソースのCI/CDプロジェクトの違いをより良く理解するために、私たちのCI/CDツール比較をご覧ください。
継続的インテグレーションとは何か、そしてそれがなぜ役立つのか?
継続的インテグレーションは、開発者が自分のコードを共有リポジトリのメインブランチに早期かつ頻繁に統合することを奨励するプラクティスです。開発サイクルの最後に機能を孤立して構築し統合するのではなく、コードは各開発者によって1日に何度も共有リポジトリに統合されます。
統合のコストを最小限に抑えるために、早期に考慮することが重要です。開発者は、新しいコードと既存のコードの境界での対立を早期に発見できるため、その対立はまだ比較的簡単に調整できます。一旦対立が解決すれば、新しいコードが既存のコードベースの要件を尊重しているという自信を持って作業を続けることができます。
頻繁にコードを統合することは、それ自体では新しいコードや機能の品質についての保証を提供するものではありません。多くの組織では、手動プロセスが使用されるため、統合はコストがかかります。これにより、コードが基準を満たしていること、バグを導入していないこと、既存の機能を壊していないことを確認します。頻繁な統合は、オートメーションへのアプローチが既存の品質保証措置と一致しない場合に摩擦を生む可能性があります。
この統合プロセス内の摩擦に対処するために、実際には継続的インテグレーションは堅牢なテストスイートと、それらのテストを実行するための自動化システムに依存しています。開発者がコードをメインリポジトリにマージすると、自動化プロセスが新しいコードのビルドを開始します。その後、新しいビルドに対してテストスイートが実行され、統合問題が導入されていないかを確認します。ビルドまたはテストフェーズが失敗した場合、チームに警告が送信され、ビルドを修正するための作業を行うことができます。
継続的インテグレーションの最終目標は、統合をシンプルで繰り返し可能なプロセスにすることで、統合コストを削減し、欠陥に早期に対応できるようにすることです。システムが堅牢で自動化されており迅速であることを確保し、頻繁な反復とビルド問題への対応を奨励するチーム文化を育てることがCIの成功の根本です。
継続的デリバリーとは何か、そしてそれがなぜ役立つのか?
継続的デリバリーは継続的インテグレーションの拡張です。ソフトウェアデリバリープロセスの自動化に焦点を当てており、チームがいつでも自信を持ってコードを本番環境にデプロイできるようにします。コードベースが常にデプロイ可能な状態にあることを保証することで、ソフトウェアのリリースは特別なイベントではなくなり、複雑な儀式なしで行うことができます。チームは複雑な調整や後期のテストなしで、必要なときにいつでもリリースできると自信を持つことができます。継続的インテグレーションと同様に、継続的デリバリーも効果的であるためには、技術的および組織的な改善の組み合わせが必要なプラクティスです。
技術の側面では、継続的デリバリーは、テストとデプロイメントプロセスを自動化するために、デプロイメントパイプラインに多く依存しています。デプロイメントパイプラインは、ビルドに対して一連の順次ステージとして、ますます厳しいテストスイートを実行する自動化されたシステムです。これは、継続的インテグレーションが終了する場所から始まるため、信頼できる継続的インテグレーションの設定は、継続的デリバリーを実装するための前提条件となります。
各段階で、ビルドはテストに失敗し、チームにアラートを発します。または、テストに合格し、次の段階への自動昇格が行われます。ビルドがパイプラインを通過するにつれて、後の段階では本番環境をできる限り忠実に模した環境にビルドがデプロイされます。この方法により、ビルド、デプロイメントプロセス、および環境を同時にテストすることができます。パイプラインは、いつでも単一のステップで本番環境にデプロイできるビルドで終了します。
継続的デリバリーの組織的側面は、「デプロイ可能性」を主要な懸念事項として優先することを奨励します。これは、機能が構築され、コードベースの他の部分にフックされる方法に影響を与えます。機能が不完全であっても、いつでも安全に本番環境にデプロイできるように、コードの設計に配慮する必要があります。この分野を支援する技術がいくつか登場しています。
継続的デリバリーは、コードをリポジトリにチェックインする作業と、十分にテストされた機能的なビルドを本番インフラにリリースするかどうかを決定する作業の間のステップを自動化するため、非常に魅力的です。 コードの品質と正確性を確保するためのステップは自動化されていますが、最終的にリリースする内容についての決定は、組織に最大の柔軟性を持たせるために任されています。
継続的デプロイメントとは何か、そしてそれがなぜ役立つのか?
継続的デプロイメントは、フルテストサイクルを通過した各ビルドを自動的にデプロイする継続的デリバリーの拡張です。人間のゲートキーパーが何をいつ本番環境にデプロイするかを決定するのを待つ代わりに、継続的デプロイメントシステムはデプロイメントパイプラインを正常に通過したすべてのものをデプロイします。新しいコードが自動的にデプロイされる際には、新機能は後で条件付きで有効にすることも、一部のユーザー向けに有効にすることもできます。自動デプロイにより、機能や修正が顧客に迅速に提供され、範囲の限られた小さな変更が奨励され、現在本番環境にデプロイされているものについての混乱を避けるのに役立ちます。
この完全自動化されたデプロイサイクルは、何がリリースされるかについて自動化システムに対する制御を手放すことを心配する組織にとって不安の源となる可能性があります。自動デプロイメントが提供するトレードオフは、その対価に対してあまりにも危険と見なされることがあります。
他のグループは、このアプローチを活用して、常にベストプラクティスが遵守されるようにしています。コードのデプロイの前に最終的な手動検証がない場合、開発者は自分のコードが適切に設計されていることと、テストスイートが最新であることを保証する責任を負わなければなりません。これにより、メインリポジトリに何を、いつコミットするのか、またプロダクションに何を、いつリリースするのかに関する意思決定が、開発チームのための単一の決定ポイントに集約されます。
継続的デプロイメントにより、組織は一貫した早期フィードバックの恩恵を受けることができます。機能はすぐにユーザーに提供され、不具合や役に立たない実装は、チームが生産性のない方向に多大な労力を投入する前に早期に発見することができます。機能が役に立たないという迅速なフィードバックを受けることで、チームは影響が minimalな領域にさらにエネルギーを費やすのではなく、焦点を変えることができます。
継続的プロセスのための重要概念と実践
継続的インテグレーション、デリバリー、およびデプロイメントは、それぞれの関与の範囲において異なりますが、各々の成功に不可欠な概念や実践がいくつかあります。
小さな反復的な変更
継続的インテグレーションを採用する際の最も重要な実践の一つは、小さな変更を奨励することです。開発者は、より大きな作業を小さな部分に分け、それを早期にコミットすることを実践すべきです。抽象化によるブランチやフィーチャーフラグといった特別な技術(下記参照)は、進行中のコード変更からメインブランチの機能を保護するのに役立ちます。
小さな変更は、統合問題の可能性と影響を最小限に抑えます。初期の段階で共有ブランチにコミットし、その後、開発全体を通じて継続的にコミットすることで、統合のコストが軽減され、無関係な作業が定期的に同期されます。
トランクベース開発
トランクベースの開発では、作業はリポジトリのメインブランチ(「トランク」)で行われるか、頻繁に共有リポジトリにマージされます。 短命のフィーチャーブランチは、小さな変更を表す限り許可されており、できるだけ早くマージされるべきです。
トランクベースの開発の背後にある考えは、上で述べた小さく逐次的な変更の概念に反する大きなコミットを避けることです。コードは早い段階で仲間に利用可能となるため、その範囲が小さいうちにコンフリクトを解決できます。
リリースは、メインブランチまたはその目的のためにトランクから作成されたリリースブランチから行われます。リリースブランチでは開発が行われず、メインブランチが唯一の真実のソースであることに専念するためです。
ビルディングとテストのフェーズを迅速に保つ
各プロセスは、正しさを検証するために自動化されたビルドとテストに依存しています。ビルドとテストのステップは頻繁に実行する必要があるため、これらのプロセスを合理化して、これらのステップに費やす時間を最小限に抑えることが重要です。
ビルド時間の増加は大きな問題として扱うべきです。なぜなら、各コミットがビルドを開始するため、その影響が複合的だからです。継続的なプロセスは開発者にこれらの活動に日々従事させるため、これらの分野での摩擦を減らすことは非常に価値があります。
可能な場合は、テストスイートの異なるセクションを並行して実行することで、ビルドをパイプラインを通じて早く進めることができます。また、各タイプのテストの比率が意味を持つことを確認することにも注意が必要です。単体テストは通常非常に速く、維持管理のオーバーヘッドが最小限です。それに対して、受け入れテストはしばしば複雑で壊れやすいです。これを考慮すると、単体テストに多く依存し、相当量の統合テストを実施し、より複雑なテストの数を最小限に抑えるのが良いアイデアです。
デプロイメントパイプライン全体の一貫性
継続的な配信またはデプロイメントの実装はリリースの適切さをテストすることを目的としているため、プロセスの各ステップ — ビルド自体、デプロイ環境、そしてデプロイプロセス自体 — の一貫性を維持することが重要です。
- コードはパイプラインの最初に1回だけビルドするべきです:生成されたソフトウェアは保存され、再ビルドなしで後のプロセスにアクセスできる必要があります。各フェーズでまったく同じアーティファクトを使用することで、異なるビルドツールの結果として不整合が導入されていないことを確信できます。
- 展開環境は一貫しているべきです: 設定管理システムは、さまざまな環境を制御できます。また、環境の変更は展開パイプライン自体を通して行われ、正確性と一貫性が確保されるべきです。レガシー状態がテストの整合性を損なわないように、各テストサイクルでクリーンな展開環境を準備する必要があります。ステージング環境は、本番環境とできるだけ一致させて、ビルドが昇格される際の未知の要因を減らすべきです。
- 各環境でビルドを展開するためには一貫したプロセスを使用する必要があります: 各デプロイメントは自動化されるべきであり、各デプロイメントは同じ中央のツールと手順を使用するべきです。 アドホックなデプロイメントは排除され、パイプラインツールのみを使用してデプロイすることが推奨されます。
デプロイメントとリリースを切り離す
コードの展開をユーザーへのリリースから分離することは、継続的デリバリーとデプロイメントの非常に強力な部分です。コードは、最初はアクティブ化したりユーザーにアクセス可能にしたりすることなく、プロダクションにデプロイすることができます。それから、組織はデプロイメントとは独立して新しい機能や特徴をリリースする時期を決定します。
これにより、ビジネスの意思決定と技術的プロセスを分離することで、組織は大きな柔軟性を得ることができます。コードがすでにサーバー上にある場合、デプロイメントはもはやリリースプロセスの微妙な部分ではなくなり、リリース時の参加者の数と関与する作業量を最小限に抑えることができます。
機能をリリースすることなく、チームが機能を担当するコードをデプロイするのを助けるテクニックはいくつか存在します。機能フラグは、環境変数の値に基づいてコードを実行するかどうかをチェックする条件付きロジックを設定します。抽象化による分岐は、プロセスの入力と出力の間に抽象化レイヤーを作成することで、開発者がプロセスを徐々に書き直すことを可能にします。これらのテクニックを組み込むための慎重な計画は、これらの2つのプロセスを分離する能力を与えてくれます。
テストの種類
継続的インテグレーション、デリバリー、およびデプロイメントは、各コード変更の効果と正確性を判断するために自動化テストに大きく依存しています。これらのプロセスでは、特定の解決策に対する信頼を確保するために、さまざまな種類のテストが必要です。
以下のカテゴリは決して網羅的なリストを表すものではありませんが、それぞれのタイプの正確な定義については意見が分かれるものの、これらの広範なテストカテゴリは、さまざまなコンテキストでコードを評価する方法を表しています。
スモークテスト
スモークテストは、主要な機能と基本的な実装および環境の前提条件を確認するために設計された特別な初期チェックの一種です。スモークテストは、より完全なテストスイートを実行する前の健全性チェックとして、各テストサイクルの最初に一般的に実行されます。
このタイプのテストの背後にあるアイデアは、実装における大きな赤旗をキャッチする手助けをし、さらなるテストが不可能または価値がないことを示す問題に注意を向けることです。スモークテストはあまり広範ではありませんが、非常に迅速であるべきです。変更がスモークテストに失敗した場合、コアなアサーションが壊れたことを示す初期の信号であり、問題が解決されるまでテストにさらに時間をかけるべきではないことを示しています。
文脈に特化したスモークテストは、新しいフェーズのテストを開始する際に、基本的な仮定と要件が満たされていることを確認するために使用されます。たとえば、スモークテストは統合テストの前やステージングサーバーへのデプロイの前に使用できますが、テストする条件はそれぞれのケースで異なります。
単体テスト
ユニットテストは、コードの個々の要素を孤立させ、高度にターゲットを絞った方法でテストする責任を担っています。個々の関数やクラスの機能はそれぞれ単独でテストされます。外部依存関係は、テストを対象となるコードのみに完全に集中させるために、スタブまたはモックの実装に置き換えられます。
ユニットテストは、より複雑なコンテキストに配置される前に、個々のコードコンポーネントの内部的一貫性と正確性を検証するために不可欠です。テストの範囲が限られており、依存関係が排除されることで、欠陥の原因を特定しやすくなります。また、さまざまな入力や再現が難しい可能性のあるコードの分岐をテストする最良の時期でもあります。多くの場合、スモークテストの後、ユニットテストは変更が加えられた際に最初に実行されるテストです。
ユニットテストは通常、個々の開発者が変更を提出する前に自分のワークステーションで実行します。しかし、継続的インテグレーションサーバーは、統合テストを始める前の安全策として、これらのテストをほぼ常に再実行します。
統合テスト
単体テストの後、統合テストはコンポーネントをまとめてアセンブリとしてテストすることによって実施されます。単体テストはコードの機能を孤立して検証するのに対し、統合テストはコンポーネントが相互にインターフェースする際に協力していることを確認します。このタイプのテストは、コンポーネント間の相互作用によって明らかになるまったく異なる種類のバグを検出する機会があります。
通常、統合テストはコードが共有リポジトリにチェックインされると自動的に実行されます。継続的インテグレーションサーバーはコードをチェックアウトし、必要なビルドステップ(通常はビルドが成功したことを確認するための簡易テスト)を実行し、その後ユニットテストと統合テストを実行します。モジュールは異なる組み合わせで接続され、テストされます。
統合テストは、共有作業のために重要です。なぜなら、それらはプロジェクトの健全性を守るからです。変更は既存の機能を壊さないこと、そして他のコードと期待通りに相互作用することを証明しなければなりません。統合テストの二次的な目的は、変更がクリーンな環境にデプロイできることを確認することです。これは、開発者自身のマシンでは行われない最初のテストサイクルであることが多いので、このプロセス中に未知のソフトウェアや環境依存関係も発見される可能性があります。また、これは新しいコードが実際の外部ライブラリ、サービス、データに対してテストされる最初の機会でもあります。
システムテスト
統合テストが実施されると、システムテストと呼ばれる別のテストレベルを開始できます。多くの点で、システムテストは統合テストの延長として機能します。システムテストの焦点は、コンポーネントのグループが一つの統一された全体として正しく機能することを確認することです。
コンポーネント間のインターフェースに焦点を当てるのではなく、システムテストは通常、ソフトウェア全体の外部機能を評価します。このテストセットは構成要素を無視し、統合されたソフトウェアを一つのまとまりとして評価します。この違いのために、システムテストは通常、ユーザーまたは外部からアクセス可能なインターフェースに焦点を当てます。
受け入れテスト
受け入れテストは、ソフトウェアを納品する前に実施される最後のテストの一つです。受け入れテストは、ソフトウェアがビジネスまたはユーザーの視点からすべての要件を満たしているかどうかを判断するために使用されます。これらのテストは、元の仕様に基づいて構築されることがあり、期待される機能性や使いやすさに対してインターフェースをテストすることがよくあります。
受け入れテストは、しばしばリリース後に延長されることがある、より複雑なフェーズです。自動受け入れテストは、設計の技術的要件が満たされていることを確認するために使用できますが、手動検証も通常は重要な役割を果たします。
受け入れテストは、作成したビルドを本番システムを模したステージング環境に展開することから始まることがよくあります。ここから、自動テストスイートを実行し、内部ユーザーがシステムにアクセスして、必要なように機能するかどうかを確認できます。ソフトウェアをリリースしたり、ユーザーにベータアクセスを提供したりした後、ソフトウェアの実際の使用における機能を評価し、追加のフィードバックを収集することで、さらなる受け入れテストが実施されます。
追加用語
上記の幅広いアイデアについて話し合った一方で、継続的インテグレーション、デリバリー、デプロイメントを学ぶにつれて出会う可能性のある関連する概念がたくさんあります。あなたが目にする可能性のあるいくつかの用語を定義しましょう:
- ブルーグリーンデプロイメント:ブルーグリーンデプロイメントは、プロダクションに似た環境でコードをテストし、最小限のダウンタイムでコードをデプロイする戦略です。二つのプロダクション可能な環境が維持され、コードは非アクティブなセットにデプロイされ、テストが行われます。リリースの準備が整ったら、プロダクショントラフィックは新しいコードを持つサーバーにルーティングされ、瞬時に変更が利用可能になります。
- 抽象による分岐: 抽象による分岐は、ソースコードリポジトリ内で長期にわたる開発ブランチを持たずに、アクティブなプロジェクトで主要なリファクタリング操作を実行する手法です。これは、継続的インテグレーションの慣行によって推奨されていません。既存の実装の中に抽象レイヤーを構築して展開することで、新しい実装を抽象の背後に並行して構築できるようにします。
- ビルド(名詞): ビルドとは、ソースコードから作成された特定のバージョンのソフトウェアを指します。言語によっては、これはコンパイルされたコードや、一貫した解釈されたコードのバンドルである場合があります。
- カナリアリリース: カナリアリリースは、変更を限られたユーザーのサブセットにリリースするための戦略です。このアイデアは、問題が発生した場合の影響を最小限に抑えつつ、プロダクションワークロードで正しく動作することを確認することです。
- ダークローンチ: ダークローンチとは、ユーザーエクスペリエンスに影響を与えずに、実際のトラフィックを受け取るコードを本番環境にデプロイすることを指します。新しい変更は既存の実装と並行して展開され、同じトラフィックがテストのために両方の場所にルーティングされることがよくあります。古い実装はユーザーインターフェースに接続されたままですが、裏では新しいコードが本番環境で実際のユーザーリクエストを使用して正確性を評価することができます。
- デプロイメントパイプライン: デプロイメントパイプラインは、ソフトウェアをリリースの準備が整ったかどうか評価するために、ますます厳格なテストとデプロイメントのシナリオを通じて移動させるコンポーネントのセットです。このパイプラインは、通常、プロダクションへの自動デプロイを行うか、手動で行うオプションを提供することで終了します。
- フィーチャーフラグまたはフィーチャートグル: フィーチャーフラグは、環境変数の値に基づいて実行するかどうかを決定する条件付きロジックの背後に新機能を展開する技術です。このフラグを適切に設定することで、新しいコードを本番環境にデプロイし、アクティブ化せずに運用できます。フィーチャーフラグには、特定のユーザーグループが新機能にアクセスできるようにするロジックが含まれることが多く、新しいコードを段階的に展開するメカニズムを作ります。
- プロモート: 継続的なプロセスの文脈において、プロモートとはソフトウェアビルドを次のテスト段階に移動させることを意味します。
- 浸漬テスト: 浸漬テストは、長期間にわたり、重要な生産または生産に近い負荷の下でソフトウェアをテストすることを含みます。
結論
このガイドでは、継続的インテグレーション、継続的デリバリー、および継続的デプロイメントについて紹介し、これらがどのようにして十分にテストされたソフトウェアを安全かつ迅速に構築およびリリースするために使用できるかを議論しました。これらのプロセスは広範な自動化を活用し、欠陥を早期に修正するためにコードの共有を促進します。これらのソリューションを実装するために必要な技術、プロセス、およびツールは大きな投資を必要としますが、適切に設計され、正しく使用されたシステムの利点は計り知れません。
どのCI/CDソリューションがあなたのプロジェクトに適しているかを調べるには、私たちのCI/CDツール比較ガイドを参考にして、より多くの情報を得てください。