依存関係の管理

<ph type="x-smartling-placeholder"></ph> 問題を報告する ソースを表示 夜間 · 7.3 · 7.2 · 7.1 · 7.0 · 6.5

前のページを見ていくと、1 つのテーマが何度も繰り返されています。 コードの依存関係を管理するのは簡単ですが、 困難になります。依存関係にはさまざまな種類があるが、 タスクへの依存関係(例: 「リリースをマークする前にドキュメントを push する」 「 「コードをビルドするには、最新バージョンのコンピュータ ビジョン ライブラリが必要です」)。 コードベースの他の部分に内部依存関係がある場合があります。 場合によっては、別のチームが所有するコードやデータに外部から依存している (組織内かサードパーティか)を問いません。いずれにせよ、「 という問題は、IT セキュリティの分野で繰り返し 依存関係の管理は、おそらく最もサーバーレスな 基本的な役割を担います。

モジュールと依存関係の取り扱い

Bazel などのアーティファクト ベースのビルドシステムを使用するプロジェクトは、次のセットに分割されます。 BUILD を介して相互の依存関係を表現するモジュールを含む、複数のモジュール できます。これらのモジュールと依存関係を適切に整理すると、 ビルドシステムのパフォーマンスだけでなく、 維持することです

きめ細かいモジュールと 1:1:1 ルールの使用

アーティファクト ベースのビルドを構築する際にまず浮かぶのは、 個々のモジュールに含める機能の量を決定できます。Bazel では、 module は、 java_library または go_binary。極端な話として、プロジェクト全体が 単一のモジュールに含めるには、1 つの BUILD ファイルをルートに配置し、 そのプロジェクトのソースファイルをすべて 再帰的にひとまとめにしますもう一方の 事実上、ほぼすべてのソースファイルを独自のモジュールに 各ファイルを、依存する他のすべてのファイルを BUILD ファイルにリストすることを要求します。

ほとんどのプロジェクトはこれらの極端な範囲に位置し、選択には パフォーマンスと保守性のトレードオフが 考慮されます各モジュールに 1 つのモジュールを プロジェクト全体という意味で、BUILD ファイル 外部依存関係を追加するときに使用しますが、その場合、ビルドシステムには必ず 常にプロジェクト全体を一度にビルドします。つまり、このセグメントの ビルドの一部を並列処理または分散でき 構築済みであることを確かめます。ファイルごとに 1 つのモジュールは、その逆で、ビルドシステムは ビルドのステップをキャッシュし、スケジュールできます。ただし、 エンジニアが依存関係のリストを管理するのに どのファイルがどのファイルを参照しているかを 変更できます

正確な粒度は言語によって異なりますが、多くの場合、 Google は、一般的なモジュールの 1 つよりもはるかに小さいモジュールを優先する傾向があります。 タスクベースのビルドシステムで記述します。標準的な本番環境バイナリは Google は何万ものターゲットに依存していることが多く、中規模の企業でさえ コードベース内で数百のターゲットを 所有できますたとえば、 パッケージの強力な組み込み概念がある Java。通常、各ディレクトリは 1 つのパッケージ、ターゲット、BUILD ファイルを含む(Pants、別のビルドシステム) (1:1:1 ルールと呼んでいます)。パッケージングが弱い言語 通常は、BUILD ファイルごとに複数のターゲットを定義します。

ビルド ターゲットを小さくするメリットは、規模が大きくなるにつれて顕著になります。これは、 分散ビルドが高速化され、ターゲットを再構築する頻度が低くなります。 テストが登場すると、利点はさらに魅力的になります。なぜなら、 ターゲットを細かく設定することで、ビルドシステムが 特定のテストによって影響を受ける可能性がある一部のテストのみを実行する あります。Google では、Google Cloud での使用より小規模な 目標を掲げているにもかかわらず、このマイナス面の BUILD ファイルを自動管理してデベロッパーの負担を軽減するツール。

これらのツールの一部(buildifierbuildozer など)は、 Bazel による buildtools ディレクトリ

モジュールの可視性の最小化

Bazel などのビルドシステムでは、各ターゲットで可視性(つまり、 プロパティを使用して、他のターゲットがそれに依存するかどうかを決定します。プライベート ターゲット その BUILD ファイル内でのみ参照できます。ターゲットは、より広範囲の 明示的に定義された BUILD ファイルのリストのターゲットに対する可視性、または すべてのターゲットに公開されます

ほとんどのプログラミング言語と同様に、通常は可視性を最小限に抑えることが 必要があります。通常、Google のチームは、 これらのターゲットは、Google のあらゆるチームが利用できる、広く使用されているライブラリを表します。 コードを使用する前に他の人と調整する必要があるチームは、 お客様のターゲットの許可リストを維持します。各 社内の実装ターゲットは、ディレクトリのみに制限される ほとんどの BUILD ファイルのターゲットは 1 つだけで、 非公開です。

依存関係の管理

モジュールは互いに参照できる必要があります。データ量を少なくするデメリットは コードベースをきめ細かなモジュールに分割するには、依存関係と (ただし、ツールを使えば自動化できます)。これらを表現することは、 通常は、依存関係が BUILD ファイルのコンテンツの大部分になります。

内部依存関係

きめ細かいモジュールに分割された大規模なプロジェクトでは、ほとんどの依存関係が 内部である可能性が高い。つまり、同じマシンで定義され、構築された別のターゲット できます。内部依存関係が外部依存関係と異なる点は、 ビルド済みアーティファクトとしてダウンロードするのではなく、ソースからビルドされていることを 表示されます。また、これは、すべての Pod に「バージョン」という概念がない ターゲットとそのすべての内部依存関係が常に 1 つのターゲット リポジトリ内の同じ commit/リビジョンでビルドされます。解決すべき問題として 慎重に扱う必要がありますが、 推移的依存関係(図 1)を示します。ターゲット A がターゲット B に依存しており、 共通のライブラリ ターゲット C に依存します。ターゲット A がクラスを使用できるようにする 定義されているでしょうか。

推移的依存関係

図 1. 推移的依存関係

基盤となるツールに関する限り、問題ありません。両方 B と C は、ビルド時にターゲット A にリンクされるため、 C は A に認識されます。Bazel は何年も前からこれを許可していましたが、Google が成長するにつれて、 問題が発生し始めました。たとえば B が、24 時間以内の 必要があります。その後、B の C への依存関係が削除された場合、A と B への依存関係を介して C を使用していたターゲットが破損します。実質的には、 パブリック コントラクトの一部になり、それらの依存関係は 変更されました。つまり、時間の経過とともに依存関係が蓄積され、Google で構築される 遅くなり始めました。

Google は最終的に「厳格な推移的 (Bazel で依存関係モード)。このモードでは、Bazel はターゲットが次の試行を試行したかどうかを検出します。 直接依存せずにシンボルを参照します。その場合、 というシェルコマンドを使用して 確認します。この変更を Google のコードベース全体にロールアウトし、 何百万ものビルド ターゲットをリファクタリングして、 数年にわたる取り組みでしたが、それだけの価値がありました。Google の ターゲットの不要な依存関係が少なくなったため、はるかに高速になりました。 エンジニアは、心配することなく、不要な依存関係を削除できる 依存するターゲットの破棄について学びました。

通常どおり、厳格な推移的依存関係を適用すると、トレードオフが発生します。その結果 よく使用するライブラリをリストする必要があるため、ビルドファイルの内容が冗長になる 多くの場所に明示的に取り込んだり、 BUILD ファイルに依存関係を追加する作業により多くの時間を費やす必要がありました。それ以来 このトイルを軽減するツールを開発しました。 開発せずに BUILD ファイルに追加することもできます。 できます。しかし、そのようなツールがなくても、トレードオフは適切に行われることがわかりました。 BUILD ファイルに依存関係を明示的に追加する 一時的なコストですが、暗黙的な推移的依存関係を処理すると、 継続的に問題が発生します。Bazel 厳格な推移的依存関係を Java コードにネイティブに実装されています

外部依存関係

依存関係が内部用でない場合は、外部用である必要があります。外部依存関係は、 ビルドシステムの外部でビルドおよび保存されたアーティファクトに 保存されたデータも参照できます「 依存関係がアーティファクト リポジトリ(通常は インターネット経由で転送されます。また、ソースから構築されるのではなく、そのまま使用されます。次のいずれか 外部依存関係と内部依存関係の最大の違いは 外部依存関係にはバージョンがあり、それらのバージョンは ソースコードが表示されます。

自動と手動の依存関係管理

ビルドシステムを使用すると、外部依存関係のバージョンを管理できる 手動または自動で行えます手動で管理した場合、ビルドファイルは アーティファクト リポジトリからダウンロードするバージョンを明示的にリストする セマンティック バージョン文字列がよく使用されます。 として 1.1.4 にします。自動的に管理される場合、ソースファイルには ビルドシステムは常に最新バージョンをダウンロードします。対象 たとえば、Gradle では依存関係のバージョンを「1.+」と宣言して、 依存関係のマイナー バージョンやパッチ バージョンについては、 メジャー バージョンは 1 です。

依存関係の自動管理は、小規模プロジェクトには便利ですが、 規模の大きなプロジェクトや 1 人でも 1 人でも作業できます。自動的に マネージド依存関係が維持される点が、バージョンをアップデートするタイミングを 更新しました。外部の関係者によるセキュリティ侵害や 更新(セマンティック バージョニングの使用を主張している場合も含む)が伴うため、 変更された内容を簡単に検出できない 動作状態にロールバックできますビルドが壊れなくても 追跡できない微妙な動作やパフォーマンスの変化などが考えられます。

これに対して、依存関係を手動で管理する場合はソースの変更が必要になるため、 簡単に検出してロールバックできます。また、 古い依存関係を使用してビルドするには、古いバージョンのリポジトリを確認してください。 Bazel では、すべての依存関係のバージョンを手動で指定する必要があります。均等 手動でのバージョン管理のオーバーヘッドは、 実現します。

1 つのバージョン ルール

通常、ライブラリのバージョンごとに異なるアーティファクトは、 理論上は、同じ外部 IP アドレスの異なるバージョンが 依存関係の両方をビルドシステムで異なる名前で宣言することはできません。 これにより、各ターゲットは、必要な依存関係のバージョンを選択できます。 あります。実際には多くの問題が発生するため、Google は厳格な 1 つのバージョンのルール コードベースですべてのサードパーティ依存関係を モニタリングできます

複数バージョンを許可する際の最大の問題は、ダイヤモンドの依存関係 あります。ターゲット A がターゲット B と外部 IP アドレスの v1 に依存しているとします。 ライブラリです。ターゲット B が後でリファクタリングされて、同じ v2 に ターゲット A は 2 つの依存関係に暗黙的に依存するため、 同じライブラリの異なるバージョンに 使用できます実質的に、タグを追加するのは ターゲットから複数のバージョンを持つサードパーティ ライブラリへの新しい依存関係。 なぜならターゲットのユーザーは すでに別のプラットフォームに依存している可能性があるからです できます。1 つのバージョン ルールに従うと、特定のバージョンに target は、サードパーティのライブラリへの依存関係を追加する 同じバージョンにあるものなので 問題なく共存できます

一時的な外部依存関係

外部依存関係の推移的依存関係には、 困難です。Maven Central などの多くのアーティファクト リポジトリは、 他のアーティファクトの特定のバージョンへの依存関係を 使用できます。Maven や Gradle などのビルドツールは、多くの場合、それぞれを再帰的にダウンロードします。 デフォルトで推移的依存関係があります。つまり、依存関係を 1 つ追加すると、 プロジェクトによっては、数十のアーティファクトがダウンロードされ、 確認できます。

これはとても便利です。新しいライブラリへの依存関係を追加する際に、 ライブラリの推移的依存関係をそれぞれ追跡するのは大変な労力です。 すべて手動で追加できますただし、大きなデメリットもあります。 同じサードパーティ ライブラリの異なるバージョンに依存している可能性があります。 必然的に単一バージョン ルールに違反し、 解決します。ターゲットが、2 つの外部ライブラリを使用して 同じ依存関係の異なるバージョンを使用する場合、どの依存関係を あります。また、外部依存関係を更新すると、外部依存関係を 新しいバージョンが pull し始めると、コードベース全体で無関係な障害が発生します。 競合するバージョンが存在する場合があります。

このため、Bazel は推移的依存関係を自動的にダウンロードしません。 残念ながら、解決策はありません。Bazel の代替手段は、 このグローバル ファイルには、リポジトリの外部接続の モジュール全体を通じて依存関係に使用される明示的なバージョンを できます。幸い Bazel が提供するツールを使うと 一連の Maven の推移的依存関係を含むファイルを生成する アーティファクトですこのツールを 1 回実行して、最初の WORKSPACE ファイルを生成できます。 そのファイルを手動で更新してバージョンを 必要があります。

この場合も、利便性と拡張性のどちらかを選択できます。小 プロジェクトでは、推移的依存関係の管理について心配する必要がない方がよい場合があります。 自己回帰モデルを使用することで 確認します。この戦略の魅力は、組織がイノベーションを起こすにつれ コードベースが増大し、競合や予期しない結果はますます増大します あります。規模が大きくなると、依存関係を手動で管理するコストが大きくなる 自動依存関係によって引き起こされる問題に対処するコストよりも あります。

外部依存関係を使用してビルド結果をキャッシュに保存する

多くの場合、外部依存関係は、Google Compute Engine や おそらくソースコードを提供せずに済んでいます。一部 組織は、独自のコードの一部を 他のコードがサードパーティとして依存できるようにし、 内部依存関係よりもはるかに効率的ですこれによって理論上、アーティファクトが ビルドには時間がかかりますが、ダウンロードは高速です。

しかし、これには多くのオーバーヘッドと複雑さが伴います。 各アーティファクトのビルドと アーティファクト リポジトリに保管されます。クライアントは、その状態を維持し、 できます。デバッグ作業もかなり困難になります。 システムのさまざまな部分が、データセンターの ソースツリーのビューも一貫していません。

ビルドに時間がかかるアーティファクトの問題を解決するには、 前述のように、リモート キャッシュをサポートするビルドシステムを使用する。そのような ビルドシステムは、すべてのビルドの結果のアーティファクトを特定の場所に保存する エンジニア間で共有されます。そのため、開発者が依存するアーティファクトが 第三者によって最近ビルドされた場合、ビルドシステムは モデルに任せることができます。これにより、Google Cloud のインフラストラクチャが提供する アーティファクトに直接依存しながらも、ビルドがそのまま 常に同じソースからビルドされているかのように一貫性が保たれます。これが 構成が可能です。また、リモート サーバーを使用して Bazel を 作成します。

外部依存関係のセキュリティと信頼性

サードパーティのソースからのアーティファクトへの依存には、本質的にリスクがあります。こちらの サードパーティ ソース(アーティファクト リポジトリなど)が ダウンロードできない場合、ビルド全体が停止する可能性があります。 使用します。セキュリティ上のリスクもあります。サードパーティのシステムが 攻撃者によって侵害された場合、攻撃者は参照されている URL を アーティファクトを作成し、任意のコードをインジェクト 組み込むことができますどちらの問題も、作成したアーティファクトをミラーリングすることで軽減できます。 管理しているサーバーに依存し、ビルドシステムによるアクセスをブロックします。 サードパーティのアーティファクトリポジトリに デプロイできますトレードオフは このようなミラーを維持するには労力とリソースが必要となり、 プロジェクトの規模によって異なります。セキュリティ問題は 完全に防止できます。そのため、各 API のハッシュを サードパーティ アーティファクトがソース リポジトリで指定されると、ビルドが アーティファクトが改ざんされた場合に失敗します。もう一つの選択肢は プロジェクトの依存関係をベンダリングすることで問題を回避できます。プロジェクトが ベンダーが依存してソース管理にチェックインし、 プロジェクトのソースコードをソースまたはバイナリとして提供します。これは実質的に、 すべての外部依存関係が内部リソースに変換されることを 確認します。Google は社内でこの手法を使用し、すべてのサードパーティ ルートの third_party ディレクトリに、Google 全体で参照されるライブラリ 直接参照できます。ただし、これが Google で機能するのは、Google の ソース管理システムは極めて大規模な monorepo を処理するためにカスタムビルドされているため、 ベンダリングは、すべての組織にとって選択肢となるとは限りません。