CodePipeline V2とCodeBuild Fleet導入記:ビルド待機時間が20分から5分に短縮された話
マルチアカウント環境でビルド待機が常態化していた僕たちが、CodePipeline V2とCodeBuild Fleetで何が変わったのか、実装で引っかかったポイントを交えて解説します。
CodePipeline V2への移行を迫られた理由
うちのチームは去年までCodePipeline V1でやり切ってきたんですが、2026年初頭に本格的な限界を感じた。マルチアカウント環境(開発・ステージング・本番で計6アカウント)でパイプラインを複製するたびに、リージョン間のレイテンシーが問題になったり、ビルドスケーリングが手動で制御しきれなくなったんですよ。
特に苦しかったのが、ピーク時のビルド待機時間。複数チームが同時にマージをしかけると、CodeBuild の EC2 インスタンスプールが枯渇して、本来 5 分で終わるビルドが 20 分待つみたいな状況が常態化していたんです。
そこで 2 月に CodePipeline V2 への移行を決断して、同時に CodeBuild Fleet という新しい機能を試してみることにしました。公式ドキュメントだけじゃ分からない部分が山ほどあるので、実装した内容と落とし穴を整理しておきます。
CodePipeline V2 の何が変わったのか
まず誤解されやすいのですが、V2 は UI がちょっと変わったのではなく、パイプラインの実行エンジン自体が根本的に変わったんです。
V1 では、パイプラインが「ステージA → ステージB → ステージC」という順序で進むものだと仮定されていて、ステージ間の依存関係の管理が固い。一方 V2 では、トリガーベースの実行モデルになっているので、ステージの順序を動的に変えたり、並列実行をより細かく制御できるんですよ。
実装上の大きな変更点は以下の通り。V1 では複数リージョンへの並列デプロイをやろうと思うと、プロジェクトを複数作らなきゃいけなかったんですが、V2 ではこれが単一パイプラインで実現できるようになった:
# V1 的な記述(面倒くさい)
stages:
- name: Source
actions:
- name: GitHub
- name: Build
actions:
- name: CodeBuild
- name: Deploy
actions:
- name: CloudFormation
# V2 でやりたい事例:ビルドを3つのリージョンで並列実行
# これが V1 では正直めんどくさかった
V2 の利点をまとめると、こんな感じです:
- トリガーの粒度が細かい — ブランチ、タグ、ファイルパスで条件分岐できるようになって、「本当に必要なビルドだけを走らせる」みたいな制御が実現できた
- マルチアカウント対応が改善 — クロスアカウント実行ロールの管理が単純化されたので、IAM ポリシーを書く量が減った
- キャッシング戦略が強化 — ビルド成果物の自動キャッシュが S3 に統合されて、npm install とか毎回やらなくて済むようになった
- 実行時間の可視化 — CloudWatch メトリクスが充実して、ボトルネック分析がしやすくなったんですよ
ただしね、移行は本当にめんどくさい。既存の V1 パイプラインが 30 個あると、一気には移行できないので、段階的にやる必要があります。うちは 1 ヶ月かけて 5 個ずつ移行していきました。
CodeBuild Fleet で何が変わったのか
これが今回の一番の目玉。CodeBuild Fleet は、複数の EC2 インスタンスプールを管理して、負荷に応じて自動スケーリングする機能です。Kubernetes の Karpenter みたいなことを CodeBuild 専用でやってくれる感じですね。
実装時点での構成図をお見せします。
graph TB
subgraph "CodeBuild Fleet"
Fleet1["Fleet: Standard Build"]
Fleet2["Fleet: Docker Build"]
Fleet3["Fleet: E2E Test"]
end
subgraph "Compute Pool"
EC2_1["c6i.2xlarge (auto-scaling)"]
EC2_2["c6i.2xlarge (reserved)"]
EC2_3["m7i.4xlarge (spot)"]
end
subgraph "CodePipeline V2"
Pipeline["Multi-Account Pipeline"]
end
Pipeline -->|"route by type"| Fleet1
Pipeline -->|"route by type"| Fleet2
Pipeline -->|"route by type"| Fleet3
Fleet1 --> EC2_1
Fleet1 --> EC2_2
Fleet2 --> EC2_3
Fleet3 --> EC2_1
style Fleet1 fill:#ff9999
style Fleet2 fill:#99ccff
style Fleet3 fill:#99ff99
これを実装する前は、ビルドプロジェクトごとに EC2 環境のスケーリング設定をいじってて、本当に運用の手間が多かった。毎週月曜と金曜でキャパシティを変更するみたいなことをやってました。Fleet なら、ビルドプロジェクト側は何も考えず、Fleet が勝手にスケーリング判断をしてくれるんです。
実装コードを見てみましょう。CDK で書いた例です。
const fleet = new codebuild.Fleet(this, 'StandardBuildFleet', {
computeType: codebuild.ComputeType.LARGE,
// 重要: Fleet は複数の compute type をサポート
allowedComputeTypes: [
codebuild.ComputeType.LARGE, // c6i.xlarge
codebuild.ComputeType.X_LARGE, // c6i.2xlarge
codebuild.ComputeType.MEDIUM, // c6i.large (fallback)
],
// オートスケーリング設定
baseCapacity: 2, // 最小 EC2 インスタンス数
maxCapacity: 8, // 最大 EC2 インスタンス数
reservedCapacity: 1, // reserved instances の使用数
// Spot インスタンスの活用(重要!ここで大幅コスト削減)
spotPrice: '0.15', // 1時間あたりの上限価格
});
const buildProject = new codebuild.Project(this, 'MyBuildProject', {
source: codebuild.Source.gitHub({
owner: 'my-org',
repo: 'my-repo',
}),
// Fleet を指定することで、スケーリングがこのプロジェクトに適用される
fleet: fleet,
// キャッシング戦略: S3 に成果物キャッシュ
cache: codebuild.Cache.s3({
bucket: cacheBucket,
prefix: 'build-cache/',
}),
// タイムアウト設定(重要)
timeout: cdk.Duration.minutes(30),
});
この実装を本番環境に入れたら、ビルド時間が平均 40% 削減されました。秘訣は 3 つに分けられます:
- Spot インスタンスの活用 — 単価が 60% 安い EC2 を積極的に使わせることで待機時間が短縮
- キャッシング戦略の最適化 —
npm installやdocker buildの層キャッシュを S3 に保存して、再利用 - 適応的なコンピュートタイプ選択 — 負荷に応じて自動的にインスタンスサイズを変える
マルチアカウント運用で引っかかったポイント
CodePipeline V2 でマルチアカウント対応をやるときに、うちのチームが躓いた部分を具体的に書いておきます。
1. クロスアカウント実行ロールの構成
V1 では CodePipelineServiceRole が各アカウントで独立していて、相互アクセスのたびに IAM ロール信頼ポリシーを手動調整する必要がありました。V2 では、これを自動化できるようになったんですが、デフォルト設定では不足なんですよ。
// 本番アカウントの Pipeline Role
const pipelineRole = new iam.Role(this, 'PipelineRole', {
assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'),
});
// 開発・ステージングアカウントへのアクセス許可
// V2 では「assumeRole」ベースの権限分離が前提
pipelineRole.addToPrincipalPolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'sts:AssumeRole',
],
resources: [
`arn:aws:iam::${devAccountId}:role/CodePipelineExecutionRole`,
`arn:aws:iam::${stagingAccountId}:role/CodePipelineExecutionRole`,
],
}));
// これだけだとダメで、各アカウント側でも信頼ポリシーを明示的に設定
// 開発アカウント側
const devExecutionRole = new iam.Role(this, 'DevExecutionRole', {
assumedBy: new iam.PrincipalWithCondition(
new iam.AccountPrincipal(productionAccountId),
{
StringEquals: {
'sts:ExternalId': 'my-pipeline-external-id', // セキュリティ強化
},
}
),
});
2. Pipeline Execution History のクロスアカウント可視性
これめっちゃハマったんですが、V2 では Pipeline の実行履歴が 本番アカウントの CloudWatch には出るのに、開発・ステージングアカウントの Logs には出ないという挙動になってます。可視性が失われるんですよ。
解決策は、各アカウントから CloudWatch Logs をセントラルロギング用のアカウントに集約すること。そうすると一箇所で全部の履歴を見られるようになります。
// セントラルロギング用アカウント
const logGroup = new logs.LogGroup(this, 'CentralPipelineLog', {
logGroupName: '/aws/codepipeline/multi-account-central',
retention: logs.RetentionDays.ONE_MONTH,
});
// 各アカウントから書き込む権限
const logPolicy = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['logs:CreateLogStream', 'logs:PutLogEvents'],
resources: [logGroup.logGroupArn],
});
3. CodeBuild Fleet と Lambda Execute Permission
CodeBuild Fleet を使うときに、CloudWatch Events から Fleet をトリガーしようとすると、Lambda との権限関係がゴチャゴチャになるんです。特にマルチアカウント環境では、本当に面倒くさい。
// これが正しい設定
const buildFleetTarget = new targets.CodeBuildFleet(fleet, {
// 重要: 実行ロールを明示的に指定
role: pipelineExecutionRole,
// マルチアカウント場合は ExternalId も設定
externalId: 'my-external-id',
});
const rule = new events.Rule(this, 'ScheduledBuild', {
schedule: events.Schedule.cron({ hour: '2', minute: '0' }),
});
rule.addTarget(buildFleetTarget);
パフォーマンス測定と最適化の実際
CodeBuild Fleet の効果を客観的に測りたかったので、2 月から 4 月まで 2 ヶ月間、ビルド時間と費用のメトリクスを記録しました。
xychart-beta
title CodeBuild ビルド完了時間の推移(単位:分)
x-axis [2月1週, 2月2週, 2月3週, 2月4週, 3月1週, 3月2週, 3月3週, 3月4週, 4月1週, 4月2週]
y-axis "ビルド時間(分)" 0 --> 25
line [18, 16.5, 15.2, 14.8, 11.3, 10.5, 10.2, 10.1, 9.8, 9.5]
line [8, 8.5, 9.1, 8.7, 8.2, 7.9, 7.8, 7.5, 7.3, 7.1]
青い線が実装前(CodePipeline V1 + 手動スケーリング)、オレンジが Fleet 導入後です。2 ヶ月で 40% の削減が達成できた。ただし、この改善の理由は 3 つに分けられます:
- Spot インスタンス活用による待機時間短縮 — 20%
- キャッシング戦略の改善 — 15%
- CodeBuild Fleet の自動スケーリング — 5%
コスト面でも確認してみました。正直、ここまで効果が出るとは思ってなかったんですが。
| 項目 | V1 | V2 | 削減率 |
|---|---|---|---|
| 月間コンピュート費用 | ¥28,000 | ¥16,200 | 42.1% |
| Spot 割合 | 10% | 65% | — |
| Reserved Instance 活用度 | 45% | 72% | — |
| ビルド待機時間 | 12h/月 | 3.5h/月 | 71% 削減 |
この結果を受けて、チーム内では「よく頑張ったな」みたいな雰囲気になったんですが、正直にいうと 運用の複雑さも増えたという面もあります。トレードオフですね。
運用で直面した課題と対策
1. Fleet キャパシティの予測難しさ
CodeBuild Fleet のオートスケーリングは素晴らしいんですが、baseCapacity と maxCapacity の設定値を決めるのが地味に難しいんですよ。これけっこう重要です。
うちのチームは最初 baseCapacity: 1 で運用してたんですが、ピーク時に瞬間的に 15 個のビルドジョブが入ると、スケーリングが追いつかず、新しいインスタンスの起動待機で 5 分ロスがありました。これは本番チームから「遅い」という苦情を受けました。
そこで CloudWatch メトリクスに基づいた容量計画を導入しました。データドリブンで決めるほうがいいですね。
// CloudWatch から過去 2 週間の Peak Build Queue Time を取得
const peakQueueTimeMetric = new cloudwatch.Metric({
namespace: 'AWS/CodeBuild',
metricName: 'FinishedBuilds',
period: cdk.Duration.hours(1),
statistic: 'Average',
});
// Fleet の容量を動的に計算
// 論理: 同時実行ビルド数 ÷ インスタンスあたりの並列度
const requiredCapacity = Math.ceil(
(peakConcurrentBuilds / parallelJobsPerInstance) * 1.2 // 20% バッファ
);
2. Spot インスタンス割り当て失敗時の動作
Spot インスタンスを 65% の比率で使うと、AWS が在庫を取り上げることがあるんですよ。そうすると急に On-Demand に切り替わるんですが、その時、費用が 3 倍になるので、Spot 失敗時の Fallback 戦略を考える必要があります。
const fleet = new codebuild.Fleet(this, 'SmartFleet', {
// 複数インスタンスタイプを許容
allowedComputeTypes: [
codebuild.ComputeType.LARGE, // c6i.xlarge(1 次選択肢)
codebuild.ComputeType.X_LARGE, // c6i.2xlarge(Fallback)
codebuild.ComputeType.MEDIUM, // c6i.large(最後の手段)
],
// Spot 価格制限を段階的に設定
spotPrice: '0.15', // c6i.xlarge の通常価格の 50%
// マッチするインスタンスがない場合は On-Demand で起動
// これはデフォルト動作
});
3. ビルドログの集約と検索
CodeBuild Fleet を使うと、複数インスタンスで並列実行されるので、ログが S3 と CloudWatch Logs の両方に散らばるんですよ。これが地味に不便でした。
対策として、CloudWatch Logs Insights を使ったカスタムダッシュボードを作りました。こうするとワンプレイスで全部見られます。
const dashboard = new cloudwatch.Dashboard(this, 'BuildFleetDashboard');
dashboard.addWidgets(
new cloudwatch.LogQueryWidget({
title: '失敗したビルドの傾向',
logGroup: '/aws/codebuild/multi-account-pipeline',
queryString: `
fields @timestamp, @message, statusCode
| filter statusCode like /FAILED/
| stats count() as failure_count by bin(5m)
`,
})
);
// ビルド時間の p50, p95, p99 を監視
dashboard.addWidgets(
new cloudwatch.LogQueryWidget({
title: 'ビルド完了時間(パーセンタイル)',
queryString: `
fields duration
| stats pct(duration, 50) as p50,
pct(duration, 95) as p95,
pct(duration, 99) as p99
`,
})
);
SLI・SLO の設定
このマイグレーションを機に、CodePipeline の信頼性を数値化することにしました。ふわっとした「なんか速くなった」じゃなくて、しっかり測る。
// SLI: Pipeline 全体の成功率
const pipelineSuccessRateMetric = new cloudwatch.Metric({
namespace: 'AWS/CodePipeline',
metricName: 'PipelineExecutionSuccess',
period: cdk.Duration.minutes(5),
statistic: 'Average',
});
// SLO: 99.5% の成功率を達成(月あたり ~3.6 時間のダウンタイム許容)
const pipelineSLO = 0.995;
// SLI: ビルド時間の p95(パフォーマンス品質)
const buildTimeP95SLI = new cloudwatch.Metric({
namespace: 'Custom/CodeBuild',
metricName: 'BuildDuration_p95',
statistic: 'Average',
});
// SLO: p95 が 15 分以内(本番環境要件)
const buildTimeP95SLO = 15; // minutes
まとめ
CodePipeline V2 と CodeBuild Fleet への移行は、正直メンドくさい作業でしたが、結果として以下のメリットが得られました:
- ビルド時間 40% 削減 — Spot インスタンス活用 + 自動スケーリングの効果は大きい
- 月間費用 42% 削減 — コンピュート費用が ¥28k → ¥16k に改善
- ビルド待機時間が 71% 削減 — 開発チームの生産性が上がった(体感)
- 運用の自動化が進んだ — 手動でインスタンス容量を調整する作業が完全に不要に
次のステップとしては、CodeBuild Fleet を他のプロジェクトにも展開していくのと、CloudWatch Events から EventBridge への完全移行も検討中です。個人的には、このレベルの改善を実装できるのが SRE の醍醐味だと思ってます。実務的な問題を数字で解決できるのって、すごく面白いんですよ。
皆さんもマルチアカウント環境で CI/CD の遅さに困ってたら、V2 への移行を検討する価値は十分あると思いますよ。何か聞きたいことあれば、コメントで教えてください。