More Related Content
Similar to Continuous delivery chapter13 (20)
Continuous delivery chapter13
- 2. 13.1 導入(1/2)
• 本章で説明する内容
– コンテキストが切り替わるときにもアプリ
ケーションを常にリリース可能に保つための
方法
– 巨大なアプリケーションのコンポーネント化
• コンポーネント分割の方法
• 複数コンポーネントからなる巨大プロジェクトの
ビルド方法
• その管理方法
- 3. 13.1 導入(2/2)
• 言葉の定義
– コンポーネント
• それなりに大規模なコード
• よく作られたAPIを提供
• 別の実装にも切り替えられる
• モジュールとも呼ぶ
• Windows: DLL, UNIX: SOファイル, Java: JARファイル..
– コンポーネントベースのシステム
• いくつかの部品に分解可能
• それぞれのふるまいが定義されている
• 他のコンポーネントと最小限のやりとり
- 4. 13.2 アプリケーションをリリース
可能な状態に保つ(1/3)
• 通常、下記の手法で開発を行うとリリー
ス間隔が長くなりがち
リリー
trunk ス
開発 bugfix 開発
release 1
• 「常にリリース可能な状態」を保ててい
ない
- 5. 13.2 アプリケーションをリリース
可能な状態に保つ(2/3)
• バージョン管理システムのブランチを使うこと
で、メインラインを常にリリース可能な状態に保
つことが可能
リリー
trunk ス
マー
ジ
feature 1 feature 2
開発 / bugfix 開発
• しかし、これはあくまで次善の策
– ブランチ上での作業が継続的に統合されていない
- 6. 13.2 アプリケーションをリリース
可能な状態に保つ(3/3)
• 全員がメインライン上で作業する方式を
推奨
• ただし、常にリリース可能状態は保つ
– そのための4つの作戦
• 新機能は完成するまで隠す
• すべての変更を細かくインクリメンタルに行い、
個々の変更をすべてリリース可能な状態にする
• 抽象化によるブランチを使って、大規模な変更を
コードベースに加える
• 変更の頻度が異なる部分をコンポーネント化し
て、アプリケーションから切り離す
- 7. 13.2.1 新機能は完成するまで隠せ
(1/2)
• 開発中の機能を本番に反映しつつ、それ
を利用できないようにする
– 開発中の機能に個別のURIを与え、機能のリ
リースまではWebサーバの設定でアクセス拒
否
– 古い機能と、開発中の新しい機能を設定で切
り替えれるように作る
- 8. 13.2.1 新機能は完成するまで隠せ
(2/2)
• 未完成の機能もメインラインで開発するメ
リット
– システム全体の統合やテストをありのままの状態
でいつでも実行できる
– 依存関係の整理や統合のフェーズをプロジェクト
の計画に含める必要がなくなる
• この方法での開発は
– 準備や検討などそれなりに大変
– 新機能を開発途中でもリリースできるという利点
を考慮すると、その価値はある
– ブランチ開発より優れている
- 9. 13.2.2 すべての変更はインクリメン
タルに(1/2)
• 大規模な変更を加えるとき、以下をやりがち
– ブランチを作り
– アプリが動かなくなるくらいがっつり変更を加え
– その後、正しく動くように細かい部分を含めて修
正する(メインラインにマージする)
– 上記手法は、確かに開発を高速化するかもしれな
い
– が、すべて正しく修正するのは大変で難しい
– したがって、変更量が大規模であればあるほど、
実際はブランチを作るべきではない
- 10. 13.2.2 すべての変更はインクリメン
タルに(2/2)
• 大規模な変更を細かく分割して、小さな
変更をインクリメンタルに進める
– 要件を小さなタスクに分解していく過程と類
似
– 分析が重要
– 間違いが尐なくなる
– 作業をどう進めるか(本当に進めるべきか)
を判断できる
– いつでもやめれる(?)
- 11. 13.2.3 抽象化によるブランチ
(1/3)
• 大規模な変更を加えたいが、小さくてインク
リメンタルな段階に分解できない場合
– 1. 既存システムにおいて、変更したい部分を抽象
化
– 2. 既存システムをリファクタリングし、その抽象
化レイヤを使うようにする
– 3. 新しい実装をする(ただし、完成までは実行パ
スに含まれないように)
– 4. 抽象化レイヤを書き換え、新しい実装に処理を
委譲
– 5. 古い実装を削除
– 6. 不要なら抽象化レイヤも削除
- 12. 13.2.3 抽象化によるブランチ
(2/3)
• この方法で困難な2点
– 対象のコードベースから、エントリポイント
を分離すること
– 開発中の機能に対して施すべき変更を管理す
ること
- 13. 13.2.3 抽象化によるブランチ
(3/3)
• アプリに大規模な変更を加えるとき、包
括的な自動受け入れテストスイートの存
在が重要
– ユニットテストやコンポーネントテストは、
この目的で使うには粒度が細か過ぎる
– 業務的な機能が壊れないようにするための助
けにならない
- 15. 13.3 依存関係(2/2)
• 言葉の定義
– コンポーネントとライブラリ
• ライブラリ
– ソフトウェアパッケージの中で、自分たちで制御しようのないも
の(どれを使うかの選択しかできない)
• コンポーネント
– ソフトウェアの部品としてアプリが依存しているもの
– 自分達が開発する
– ビルド時の依存関係と実行時の依存関係
• ビルド時の依存関係
– アプリをコンパイルしたときにあらわれるもの
• 実行時の依存関係
– アプリを実行して機能を使うときにあらわれる
- 16. 13.3.1 依存地獄(1/3)
• 別名:DLL地獄
– 実行時に発生するライブラリの依存関係
– あるアプリケーションが、特定のバージョン
の何かに依存しているのに、別のバージョン
をデプロイしてしまった場合、あるいはそも
そもデプロイしなかった場合に起こる
• 例: 初期windowsは異なるバージョンのDLLを使うア
プリの共存が不可能 .NET, GACで改善
• 例: Linuxは命名規約で回避
- 17. 13.3.1 依存地獄(2/3)
• OS全体の依存関係
– 静的にコンパイルしたまともなアプリを使う
– コンパイル時に依存関係を1つのアセンブリ
にまとめれば実行時の依存関係はほぼなくな
る
– ただし、バイナリが肥大化するし、特定のOS
バージョンに密結合するので、おすすめしな
い
- 18. 13.3.1 依存地獄(3/3)
• 動的言語の場合
– 依存するフレームワークやライブラリも、ア
プリと一緒に出荷することで依存関係を解決
する
• 例: Rails
• Javaの場合
– あるクラスの複数のバージョンを同じJVM内
で使えないという深刻な問題がある
– OSGiフレームワーク形式を使って解決する
JAR1 ver1
アプリ JAR3
JAR2 ver2
- 19. 13.3.2 ライブラリを管理する
(1/3)
• ビルドが再現性のあるものであることが
重要
• 適切な方法が2つ
– ライブラリもバージョン管理にチェックイン
する
• 最もシンプル
• 小規模なプロジェクトならこれで十分
– 使うライブラリを宣言した上でMaven, Ivyを使
い、組織内のリポジトリからライブラリをダ
ウンロードして使う
- 20. 13.3.2 ライブラリを管理する
(2/3)
• ライブラリチェックインの問題点
– リポジトリの肥大化
– 同一プラットフォーム上で、別のプロジェク
トを共存させないといけなくなった場合
– プロジェクト間の依存関係を手動で管理する
のは厳しい
- 21. 13.3.2 ライブラリを管理する
(3/3)
• 依存関係の管理を自動化する仕組みを提供す
るツール(Maven, Ivy)を使う
– 必要なライブラリとそのバージョンを、プロジェ
クトに設定できる
– 他プロジェクトとの依存関係をよろしく解決して
くれる
• 自前の成果物リポジトリを持つ
– Artifactory, Nexusなど
– 自社内のプロジェクトで、どのライブラリのどの
バージョンが使えるか制御できる
- 23. 13.4.1 コードベースをコンポーネン
トに分割する方法(1/6)
• コンポーネントに分割することが、開発
プロセスを効率化する理由
– 1. 大きな問題を、より小さくわかりやすい問
題に分割できる
– 2. コンポーネントが、システム中の更新頻度
が違う部分、ライフサイクルが違う部分をあ
らわす
– 3. ソフトウェアの設計や保守の段階で、その
責務を明確化することにつながる
– 4. ビルドプロセスやデプロイメントプロセス
を最適化する際に、自由をくれる
- 24. 13.4.1 コードベースをコンポーネン
トに分割する方法(2/6)
• 大半のコンポーネントの特徴
– 何らかの形式でAPIを公開していること
• APIは、外部の協調オブジェクトとの情報交換を表
している
• また、結合度合いも表している
– コンポーネント間の依存は考慮しないといけ
ない
• 結合が強いと、別々に分割して、個別にビルドや
デプロイメントをするのが難しくなる
- 25. 13.4.1 コードベースをコンポーネン
トに分割する方法(3/6)
• コンポーネント化すべき場面
– 1. コードベースの一部を個別にデプロイする必要がある
– 2. コードベースをコアとプラグイン群に分割し、一部を別
の実装に切り替えたり、ユーザ側での拡張性を高めたりし
たい
– 3. そのコンポーネントが別のシステムへのインタフェース
を提供する
– 4. コードのコンパイルとリンクに時間がかかる
– 5. 開発ツールでプロジェクトを開くのに時間がかかる
– 6. コードベースが大きくなりすぎて、1つのチームで手に
負えない
– 後半3点は著者の主観だが、すべて理由としては妥当だし、
最後の項目は最も重要
- 26. 13.4.1 コードベースをコンポーネン
トに分割する方法(4/6)
• チームの力を最大限に発揮できるのは
– 10人前後のメンバーで構成
– 各自がコードベースの特定の部分を隅々まで
知り尽くしている
• 特定の部分:機能的な区切りでも、それ以外の何
かの基準による区切でもよい
- 27. 13.4.1 コードベースをコンポーネン
トに分割する方法(5/6)
• 10人以上必要な場合
– システムを疎結合なコンポーネントに分割し、
チームも分割する
– ただし、コンポーネント毎のチーム分けは勧めな
い
• 要件の分割はコンポーネントの境界と沿わない
• コミュニケーションコストの浪費
• プロジェクト全体で何がベストか見えなくなりがち
– 各チームが1つのストーリーの流れを扱うのがよ
い
• 必要なコンポーネントはすべて手をいれられる権限を
与える
• 統合チームに責任を押し付けることがなくなる
- 28. 13.4.1 コードベースをコンポーネン
トに分割する方法(6/6)
• コンポーネント分割するにあたっての、強力
でお手軽なツールは無い
– 説明してきたより良い設計を考えるだけ
– その際の罠
• 何もかもコンポーネント化してしまうこと
• 1つのコンポーネントにすべてを任せること
• コンウェイの法則
– システムを設計する組織は、その組織のコミュニ
ケーション構造をそっくりまねた設計を生み出す
• チーム編成には注意
- 29. コンポーネントを使うからといって必
ずしもN層アーキテクチャになるわけ
ではない
• N層アーキテクチャとは
– アプリケーションを表示、業務処理、データ等、複数の階層で
分割する分散アプリケーション設計手法
• メリット
– 経験の浅い開発者が集まった巨大なチームでも密結合な泥団子
を作らずにすむ
– キャパシティやスケーラビリティの面で有利
• デメリット
– 階層が物理的に離れている場合、リクエストを処理する待ち時
間が増える
• 複雑なキャッシュの仕組みの導入につながる
• 保守やデバッグがしづらい
• コードは理解しにくく、各層間の依存関係のせいで変更もしにくい
• N層化はコンポーネントベースの開発と同義ではなく、直交
する概念
- 30. 13.4.2 コンポーネントをパイプライ
ン化する(1/4)
• 基本的には、何か変更がコミットされる
たびに、すべてをビルドし、テストする
のが良い
– 時間がかかりすぎるようになったら、そのと
き対応する
– 巨大で複雑なシステムでも対応可能
- 31. 13.4.2 コンポーネントをパイプライ
ン化する(2/4)
• パイプラインに分割したほうがやりやすい場面
– アプリケーションの一部が、他の部分とは異なるラ
イフサイクルをたどる
– アプリケーション内で機能的に分かれた部分をそれ
ぞれ別チームが担当しており、個々のチームで専用
のコンポーネントを使っている
– あるコンポーネントが他とは異なる技術やビルドプ
ロセスを使っている
– 共有コンポーネントが他のプロジェクトでも使われ
ている
– あるコンポーネントが比較的安定しいて、変更頻度
が低い
– ビルドに時間がかかり、コンポーネントごとに分け
た方が速い
- 32. 13.4.2 コンポーネントをパイプライ
ン化する(3/4)
• 各コンポーネントあるいはコンポーネン
ト群のビルドには独自のパイプラインが
必要
• パイプラインの動き
– 必要に応じてコードをコンパイルする
– ひとつあるいは複数のバイナリを、あらゆる
環境へのデプロイメントに対応できるよう組
み立てる
– ユニットテストを実行する
– 受け入れテストを実行する
– 手動テストをする場面ではそれに対応する
- 33. 13.4.2 コンポーネントをパイプライ
ン化する(4/4)
• 各バイナリが自身のリリースプロセスを
通過すれば、次のインテグレーションビ
ルドに進む準備ができたことになる
• DLLやJARの単位でパイプラインを作るべき、
と主張しているわけではない
• 運用するビルド数はできるだけ尐ない方
がよい
- 34. 13.4.3 インテグレーションパイプラ
イン(1/3)
• 適切なバイナリを組み合わせてデプロイ
用のパッケージを作成
• できあがったアプリケーションを疑似本
番環境にデプロイしてスモークテストを
実行
• 受け入れテストを実行
• 例、図13-1 (p.427)
- 35. 13.4.3 インテグレーションパイプラ
イン(2/3)
• デプロイメントパイプラインに関する一
般原則2つ
– 素早いフィードバック
– ビルドの状況を関係者全員が見れる
• 失敗した場合、なぜ失敗したかが正確にわかる必
要
– インテグレーションビルド側から、元になった各コンポ
ーネントのバージョンをたどれる必要
– コンポーネント開発側からも、どのバージョンのコンポ
ーネントがインテグレーションパイプラインで成功した
か見れる必要
- 36. 13.4.3 インテグレーションパイプラ
イン(3/3)
• インテグレーションパイプラインの実行間隔
が離れていて、その間に複数のコンポーネン
トに多くの変更があるのは好ましくない状況
– どの変更がアプリを壊したのかわかりにくい
• 最もシンプルな解決策
– 各コンポーネントのうまく動いているバージョン
について、考えうるすべての組み合わせをビルド
する
• 人手が必要ない&賢いアルゴリズムを考える必要もな
い
• 次善の策
– スケジューリングを使って、定期的にビルドする
- 38. 13.5.1 依存グラフを作成する
(1/4)
• 例、図13-2 (p429)
上流 レポート
下流
エンジン
フレームワー 資産管理アプ
決済エンジン
ク リケーション
CDS プライシング
ライブラリ エンジン
他社
それぞれパイプラインを持っていて、
自身あるいは上流に変更があったら、
パイプラインが起動
- 39. 13.5.1 依存グラフを作成する
(2/4)
• この例のビルド時に起こりうるシナリオ
– 1. 資産管理アプリケーションを変更
• 資産管理アプリケーションだけ再ビルド
– 2. レポートエンジンを変更
• レポートエンジンを再ビルドし、すべてのテストを通す
• その後、資産管理アプリケーションを再ビルド
– 3. CDSプライシングライブラリに変更
• プライシングエンジンの再ビルド
• その後、資産管理アプリケーションの再ビルド
– 4. フレームワークに変更
• フレームワークを再ビルド
• 直近の下流にあるすべての依存コンポーネントを再ビルド
• 上記がすべてできたら、資産管理アプリケーションを再ビル
ド
– 1つでも失敗していたら、資産管理アプリケーションは再ビルド
しない
- 40. 13.5.1 依存グラフを作成する
(3/4)
• 続き
– 5. フレームワークとプライシングエンジンに
変更
• グラフ全体の再ビルドが必要
• 理想は、すべてのビルドが成功すること
• もし、決済エンジンのビルドが失敗したら?
– 資産管理アプリケーションを新しいフレームワークでは
ビルドできない
– しかし、CDSプライシングライブラリは動作検証済みな
ので、これだけ新しいバージョンで、資産管理アプリ
ケーションをビルドしたい
» が、そんなバージョンのプライシングエンジンは存
在しない
- 41. 13.5.1 依存グラフを作成する
(4/4)
• この例の最も重要な制約は、
– 資産管理アプリケーションをビルドする際に
使えるフレームワークのバージョンが1つだ
けということ
• これは典型的な「菱形依存」
- 42. 13.5.2 依存グラフをパイプライン化
する(1/2)
• 図13-3 (p432)
– フィードバックを早くするために、各プロジェク
トのパイプライン内でコミットステージが完了し
た時点で、依存プロジェクトのパイプラインを立
ち上げる(受け入れテストの完了を待たない)
– すべてのトリガーは自動(手動テスト、本番デプ
ロイを除く)
– アプリケーションのあるバージョンをビルドする
ときに使った各コンポーネントをたどれることが
重要
– どのバージョンのコンポーネントの組み合わせが
統合に成功したかを知れる
• 図13-4, 図13-5 (p433)
- 43. 13.5.2 依存グラフをパイプライン化
する(2/2)
• 大幅な変更をコンポーネントに加える必要がある場合
は新しいリリースを作る
– 図13-6 (p434)
– APIの後方互換性がない新バージョンを開発する際、開発
をはじめる前に1.0系のリリース用ブランチを作ってから
開発する
– 下流の資産管理アプリケーションは、1.0ブランチから
作ったバイナリを使える
– Bugfixは1.0で行い、trunkにマージする
– 資産管理アプリケーションが新バージョンに対応した時点
で、trunkに切り替える
– 継続的インテグレーションの観点では次善の策
– だが、コンポーネントが疎結合であることが、統合を先延
ばしにしたときのリスクを抑えている
- 44. 13.5.3 いつビルドすべきか?
(1/2)
• 上流の依存を最新版に保ち、最新の機能
とバグフィックスを使いたいが、すべて
の依存を最新にする統合にはコストがか
かる
– 動かなくなった部分を修正しないといけない
ので
- 45. 13.5.3 いつビルドすべきか?
(2/2)
• 依存関係更新の頻度を決める際の検討事項
– 依存コンポーネントの最新版をどの程度信頼できるか?
• 数が尐なく、自分たちで開発しているなら、修正も素早く行え
るので、頻繁に統合すべき
• 社内の別のチームの場合、個別のパイプラインにすべき。追従
するか、しないかを都度判断できる
• サードパーティのライブラリは、明らかな必要性があるときの
みにすべき
– コンポーネントの変更にどの程度関与でき、どの程度知れ
るかがポイント
• 程度が低いほど、信頼性は下がる
– 基本的には、依存コンポーネントの新バージョンの統合を
継続的に行えるのがベスト
• コストがかかる
• 素早いフィードバックを得るには、統合を頻繁に行う必要があ
る
• が、頻繁に行うと自分たちの関与しないところで問題が発生し
うる
• 両者のバランスをとるための解決策の1つとして「慎重な楽天
- 46. 13.5.4 慎重な楽天主義
• 依存グラフに、3つの状態を導入
– static, gruarded, fluid
– staticな上流の依存を変更しても新しいビルドは
引き起こさない
– fluidな上流の依存が変更されたら常にビルドを実
行する
• ビルドが失敗すると、依存コンポーネントは
「guarded」になり、動作確認済みの既知のバージョン
に固定される
– guardedな依存はstaticと同じふるまいをする
– 開発チームには通知が飛ぶ
– 例、図13-7 (p437)
- 47. 13.5.5 循環依存
• 最もやっかいな問題
– 依存グラフに閉路が含まれる場合に起こる
– 一番シンプルな例
• コンポーネントAはコンポーネントBに依存してい
るが、
• コンポーネントBもコンポーネントAに依存してい
る
• 最初の起動時に致命的な問題
• プロジェクトの開始時点では循環していない
• おすすめしないが解決策は図13-8 (p438)
- 49. 13.6.1 成果物リポジトリの活用法
(1/3)
• 成果物リポジトリに再生成できないもの
を含めてはいけない
– 気兼ねなく削除でき、それによって取り戻せ
ない何かがあってはいけない
– あらゆるバイナリを再生成するために必要な
すべてをバージョン管理する
- 50. 13.6.1 成果物リポジトリの活用法
(2/3)
• バイナリをどの程度保持し続けるにし
ろ、ハッシュ(バイナリのMD5)も保持して
おく必要がある
– それによりバージョン管理システム内のどの
リビジョンを使って作成したバイナリかわか
る
– 保存は、ビルドシステムでもよいし、バー
ジョン管理システムでもよい
• ハッシュ管理は構成管理の一貫として重要な位置
を占める
- 51. 13.6.1 成果物リポジトリの活用法
(3/3)
• 最もシンプルな成果物リポジトリ
– ディスク上のディレクトリ構造として構成し
たもの
• バイナリと、それを作るソースのバージョンを関
連づけられる必要がある
• パイプラインごとにディレクトリを作り、その中
でビルド番号ごとのディレクトリを作る
• ビルドの成果物はすべてこのディレクトリに保管
する
- 52. 13.6.2 デプロイメントパイプライン
と成果物リポジトリのやりとり
(1/2)
• デプロイメントパイプラインで以下を実装
– ビルドの成果物を成果物リポジトリに保存
– 後で使うときに、それを取り出す
• コンパイル、ユニットテスト、受け入れテスト、
手動受け入れテスト、本番 というパイプライン
を例とする
– コンパイルでバイナリができるので、それを成果物
リポジトリに保存
– ユニットテスト/受け入れテストでは、このバイナ
リを取得してテストを行う。レポートは成果物リポ
ジトリに保存
– 手動受け入れテストでは、このバイナリを取得し、
テスト環境にデプロイして手動テストする
– リリースでは、このバイナリを本番環境にデプロイ
する
- 53. 13.6.2 デプロイメントパイプライン
と成果物リポジトリのやりとり
(2/2)
• 成果物リポジトリは、
– 共有ファイルシステム上に保存する方法
– Nexus, Artifactoryといったリポジトリ管理ツー
ルも使える
- 54. 13.7 Mavenを使って依存関係を管理
する
• Maven
– Javaプロジェクト用のビルド管理システム
– 依存関係の管理が得意
– 外部のライブラリとの依存関係/アプリケー
ション内のコンポーネント間の依存関係 を
抽象化してどちらもほぼ同様に扱えるように
する
–略
- 55. 13.8 まとめ
• チームでの開発をできる限り効率的に進めつ
つ、アプリケーションを常にリリース可能な
状態に保つ方法を議論
– すべての変更を小さくインクリメンタルなものに
分割し、尐しずつメインラインにチェックインす
る方法
– アプリケーション自体をコンポーネントに分割す
る方法
• 基本的には、コンポーネントを個別にビルドする必要
はない
• ある一定ラインを超えたら、コンポーネントや依存関
係にもとづいたパイプライン、成果物管理を用いない
と、効率的なデリバリーや素早いフィードバックが実
現できなくなる