Skip to content

内部実装

コミット 2df3b8c (v0.40.7) のソースから読んだ。ここでの主張はすべてファイルと行を指す。

コードマップ

main の入口は小さい。main.go:15cmd.NewPackCommand で cobra ルートを組み (main.go:19)、SoftError なら exit 2、それ以外は exit 1 で終わる (main.go:27)。本体は 3 層に分かれる。

パス責務
cmd/cobra ルートコマンドの組み立て。
internal/commands/サブコマンドごとのフラグ定義と入力検証。
pkg/client/公開 API。Client.Build (pkg/client/build.go:308) がビルドをオーケストレートする。
internal/build/フェーズをコンテナとして走らせる lifecycle 実行エンジン。
pkg/dist/buildpack と builder のディスク上メタデータ型。
internal/builder/builder イメージのインメモリ表現。

中核データ構造

BuildOptions (pkg/client/build.go:85) は pack build の全入力を運ぶ。ImageBuilder (どちらも必須)、AppPathBuildpacksExtensionsPublishPullPolicyTrustBuilder クロージャ、キャッシュ設定。CLI 層と client 層の契約だ。

LifecycleOptions (internal/build/lifecycle_executor.go:72) は lifecycle 実行の全パラメータを持つ。UseCreatorUseCreatorWithExtensions (trust 分岐)、LifecycleImageCacheNetwork、uid/gid、CreationTimeBuildOptions から pkg/client/build.go:637 で組まれる。

LifecycleExecution (internal/build/lifecycle_execution.go:35) は 1 回のビルド実行の状態だ。ネゴシエートされた platformAPI、ランダム名の layers / app ボリューム、マウントパス、オプション、tmp ディレクトリ。フェーズメソッドのレシーバになる。

dist.Order (pkg/dist/dist.go:41) は buildpack の検出順序だ。OrderOrderEntry のスライスで (pkg/dist/dist.go:43)、各エントリは ModuleRefGroup を持ち (pkg/dist/dist.go:57)、ModuleRefModuleInfoOptional フラグを足したものだ。detect フェーズがこの順序を解決し、適用する buildpack を選ぶ。

builder.Builder (internal/builder/builder.go:71) は builder イメージのインメモリ表現だ。orderorderExtensions、lifecycle ディスクリプタ、追加 buildpack / extension、uid/gid、stack id を持つ。pack が ephemeral builder を作るときは、保存前にここへ buildpack を足す。

追う価値のあるパス

Run の分岐がもっとも影響の大きいコードだ。キャッシュを解決し bridge ネットワークを作った後、UseCreator で分かれる。

go
if !l.opts.UseCreator {
    if l.platformAPI.LessThan("0.7") {
        // DETECT then ANALYZE
    } else {
        // ANALYZE then DETECT
    }
    // ... restore, build (or extend), export in separate containers
}
return l.Create(ctx, buildCache, launchCache, phaseFactory)

untrusted パス (internal/build/lifecycle_execution.go:240) は各フェーズを個別のコンテナで走らせ、root が要るフェーズだけを信頼コンテナで実行し、それ以外は CNB ユーザに降格する。trusted パスは l.Create (internal/build/lifecycle_execution.go:349) に落ち、すべてを単一コンテナで実行する。

Detect (internal/build/lifecycle_execution.go:482) はフェーズコンテナの組み立て方を示す。NewPhaseConfigProvider には、コンテナ前に走る操作 (EnsureVolumeAccess、続いて builder の uid/gid で app をボリュームへコピーする CopyDir) が渡され、extension があるときは analyzed.tomlgenerated/CopyOutToMaybe で吸い出す post-container 操作が渡される (internal/build/lifecycle_execution.go:504)。フェーズは phaseFactory.New(configProvider).Run(ctx) で走る。

驚いた点

pack は「ビルドツール」ではなく「プラットフォーム」だ。ビルドロジックは別の lifecycle バイナリにあり、pack は builder と lifecycle イメージをコンテナとして orchestrate するだけだ。github.com/buildpacks/lifecycle/api の import (internal/build/lifecycle_executor.go:9) は、自分でビルドするためではなく、Platform API 契約とファイルフォーマットを共有するためにある。

ビルドごとに pack.local-network-<rand> という使い捨ての bridge ネットワークと、使い捨ての layers / app ボリュームを作る (internal/build/lifecycle_execution.go:217)。クリーンアップは defer + リトライで行う。

trusted builder の警告は security-sensitive だ。untrusted builder とボリュームマウントの組み合わせは、機微データ露出について明示的な警告を出す (internal/commands/build.go:136)。そして trust の判定が、root フェーズを他のビルドと同じコンテナで走らせるかどうかを決める。

出典

  1. buildpacks/pack リポジトリ
  2. buildpacks/lifecycle リポジトリ