インフラ●●●○○

CI/CD

CI/CD ・ しーあいしーでぃー

変更をこまめに統合して自動で検証し、いつでも安全に本番へ届けられる状態を保つ開発の仕組み。

✦ 30秒で試す — 航海1日に何十回も本番に出すほうが、事故が少ない同じ期間で、統合の回数を減らしてまとめて合流すると、衝突の総数は?予想してみる

概要

CI/CD は、コードの変更が利用者に届くまでの道のりを自動化する実践の総称です。CI(Continuous Integration: 継続的インテグレーション)は、開発者が変更を Git の共有の幹(main ブランチ)へ1日に何度も統合し、そのたびに自動でビルドとテストを走らせて、壊れた変更をすぐ見つける実践です。CD(Continuous Delivery / Continuous Deployment: 継続的デリバリー/継続的デプロイ)は、検証を通った変更を、いつでも本番へ出せる状態に保ち、実際に届けるところまでを自動化する実践です。

この自動化された一連の流れを「パイプライン」と呼びます。GitHub Actions、GitLab CI/CD、CircleCI、Jenkins などで組むのが今の主流で、「push すると数分後には検証が終わり、本番に反映されている」という開発の流れは、規模を問わずチームの標準装備になりました。

ただ、CI/CD の中心にあるのはツールではなく習慣です。CI の I は「統合」で、自動テストを回すことより、変更を溜めずに小さく頻繁に混ぜることのほうが本質です。ツールを入れても、各自が何週間も別のブランチで作業していれば、それは CI ではありません。

なぜ生まれたか

かつての開発では、各開発者が数週間から数か月分の変更を手元やブランチに抱え込み、リリース前にまとめて統合していました。すると大量の変更が一斉にぶつかり、「統合地獄(integration hell)」と呼ばれる修羅場になります。同じファイルを別々に書き換えていた衝突の解消に何日もかかり、ようやくビルドが通っても、どの変更がテストを壊したのか分かりません。リリース前の数週間が火消しに消えていきました。この苦しさから、「統合が痛いなら、毎日・毎回やって痛みを小さくしよう」という逆転の発想が生まれます。言葉としては 1991年に Grady Booch が使い、1990年代後半に Kent Beck らのエクストリーム・プログラミング(XP)が「1日に何度も統合する」実践として定着させ、2000年に Martin Fowler が記事「Continuous Integration」で広めました。

デプロイにも同じ悪循環がありました。手順書を見ながら深夜に手作業でサーバへファイルを配置するリリースはミスが起きやすく、怖いからこそ回数が減り、回数が減るから1回あたりの変更が膨らんでさらに怖くなる。これを断ち切るため、2010年に Jez Humble と David Farley が『継続的デリバリー』で「いつでもリリースできる状態を保つ」デプロイメントパイプラインの考え方をまとめ、同じ頃 IMVU の Timothy Fitz らは、テストを通った変更を人手を介さず本番へ出す継続的デプロイを実践して報告しました。デプロイを自動化して日常にすれば、変更は小さく保たれ、原因の特定は容易になり、問題があってもすぐ戻せます。「リリースは怖い儀式」から「1日に何度でも行う日常」への転換が、CD の本質です。

溜めてから統合する(長命なブランチ)main数週間、main から離れて変更を溜める衝突が多い原因候補 6件こまめに統合する(トランクベース開発)main数時間以内の小さな変更を、1日に何度も main へ戻す衝突はまれ原因候補 1件
統合を溜めるか、こまめに統合するか — 長く分かれたブランチほど、合流の衝突と、壊れたときに疑う範囲が大きくなる

詳細

パイプラインの流れ

典型的なパイプラインは、Git への push やプルリクエストをきっかけに動き出します。まずコードを取得してビルドし、静的解析(lint)と自動テストを実行します。すべて通ったらデプロイできる成果物(近年はコンテナイメージが主流)を作ってレジストリに登録し、そこからステージング環境への配置、必要なら承認をはさんで、本番へのデプロイへ進みます。どこか1段でも失敗すればパイプラインは止まり、開発者にすぐ通知されます。「壊れたものは決して先へ進まない」関所の連なりが、パイプラインの本質です。

CI(統合のたびに検証)CD(届ける)git pushビルド静的解析自動テスト成果物イメージを登録ステージング失敗停止して通知直した変更をまた push承認本番へデプロイ監視で確かめる異常なら前の版へ戻す
CI/CD パイプライン — 関所を通った変更だけが本番へ進む。テストで落ちれば止まり、本番で異常が出れば前の版へ戻す

この関所の最初の段、つまり CI が本当に効くかどうかは、統合の間隔で決まります。変更を溜めるほど、そのあいだに main へ入った他人の変更と同じ部品を触っている確率が上がり、テストが落ちたときに疑うべき変更の数も増えます。下の部品で、3人のブランチを好きなタイミングで統合してみてください。溜めてから押したときと、こまめに押したとき、最後に「CI(コミットごとに統合)」にしたときで、衝突の数と原因探しにかかる手戻りの時間を比べられます。

⚡ 体験: 溜めてから統合するか、こまめに統合するか
main(共有の幹)— 灰色はほかのチームの変更。統合 0 件衝突の解消ビルドテスト統合の関所Aさん押して統合 ↑Bさん押して統合 ↑Cさん押して統合 ↑
統合のしかた
統合待ち0 件衝突(累計)0 箇所手戻りの時間0.0 秒main に届くまで—

3人がそれぞれのブランチにコミットを積んでいきます。レーンを押すと、そのブランチを main へ統合します。まずは何件か溜まるのを待ってから押してみてください。

トランクベース開発 — 統合の間隔を縮める

CI を実際に成り立たせるブランチの運用が、トランクベース開発です。各自のブランチは数時間から長くても1〜2日で main(トランク)に戻し、長命なブランチを作りません。まだ完成していない機能も main に入れてしまい、フィーチャーフラグで利用者から隠しておきます。統合のたびにコードレビューを受けるので、レビューも小さく速く回ります。「main はいつでもビルドが通り、テストが緑である」ことをチーム全員で守り、壊したら最優先で直す(あるいはすぐ戻す)のが規律です。

逆に、機能ごとに何週間も生きるブランチを作る運用では、パイプラインのツールがあっても統合地獄は小さな形で残ります。Google などが行っている開発者調査(DORA)でも、短命なブランチで頻繁に統合するチームほど、デプロイの頻度と安定性の両方が高い傾向が繰り返し報告されています。

デリバリーとデプロイの違い

CD には2つの流儀があります。継続的デリバリーは「いつでも本番に出せる状態を常に保つ」ところまでを自動化し、最後の本番反映だけは人がボタンを押す方式です。継続的デプロイは、その最後の判断も自動化し、テストを通った変更が人手を介さず本番まで流れる方式です。どちらを選ぶかは事業の性質しだいで、公開の時期を制御したい(広報と合わせる、規制上の承認が要る)場合はデリバリー、変更の流量を最大にしたい場合はデプロイが向きます。大切なのは、どちらも「本番に出せない期間を作らない」ことを目指している点です。

安全に届けるためのデプロイ戦略

自動でどんどん本番に流すからこそ、失敗したときの被害を抑える工夫が発達しました。いちばん基本的なのはローリングアップデートで、ロードバランサの後ろに並んだサーバを1台ずつ新しい版へ入れ替えていきます。追加の台数がほとんど要らない代わりに、入れ替えの途中は新旧の版が混ざり、戻すにも時間がかかります。新旧2系統の環境を用意して一気に切り替えるブルーグリーンデプロイは、切り替えも切り戻しも一瞬です。一部の利用者にだけ新しい版を当てて指標を見ながら広げるカナリアリリースは、被害の範囲をもっとも小さく保てます。

どの戦略も、デプロイ後の異常をすぐ見つける監視との連携が前提です。エラー率の急増を検知して自動でロールバックするところまで組めば、パイプラインは「届ける」だけでなく「守る」仕組みになります。

実務での勘所と落とし穴

CI/CD の価値はテストの質に大きく左右されます。テストが薄ければ「壊れたコードが高速で本番に届くだけの装置」になりかねません。逆にテストが遅すぎるとパイプラインが渋滞し、統合の間隔が延びて CI の意味が薄れます。10分以内に結果が返ることを目安に、テストの粒度と並列化を設計するのが定石です。ときどき理由なく落ちる不安定なテスト(flaky test)は、「どうせまた誤報だ」と赤を無視する習慣を生むので、見つけ次第隔離して直します。

パイプラインの設定ファイル(GitHub Actions なら .github/workflows/ の YAML)もコードとしてリポジトリで管理され、レビューと履歴管理の対象になります。この「手順をコードにして自動で実行する」考え方をインフラの構成に広げたものが IaC で、両者を組み合わせれば、アプリもインフラも push とレビューで更新される開発基盤になります。

最後に、パイプラインは本番へ書き込める強い権限を持つので、攻撃者にとって格好の標的です。デプロイ用の認証情報はシークレット管理の仕組みで渡し、ログに出さないこと、長期の鍵を置く代わりに OIDC で実行のたびに短命な認証情報をクラウドから受け取る方式を使うこと、外部から取り込むアクションや依存パッケージを版やハッシュで固定することが、サプライチェーン攻撃への備えになります。本番へのデプロイを誰が承認できるかも、権限の設計として決めておきます。

試問

3人の開発者がそれぞれのブランチ(自分の作業場所)に、ときどきコミット(変更のまとまり)を積んでいきます。レーンを押すと、そのブランチに溜まったコミットをまとめて共有の main へ統合し、衝突の解消・ビルド・テストの関所を通します。main はほかのチームの変更でも進み続けています。

✦ 命題挑戦中

自分でレーンを押して統合し、10 件以上を main に届けつつ、届くまでの待ち時間を平均 5 秒以内に保て

  • 自分で押して、10 件以上を main に届ける(いま 0 件)
  • main に届くまでの待ち時間を、平均 5 秒以内に保つ
⚡ 体験: 溜めてから統合するか、こまめに統合するか
main(共有の幹)— 灰色はほかのチームの変更。統合 0 件衝突の解消ビルドテスト統合の関所Aさん押して統合 ↑Bさん押して統合 ↑Cさん押して統合 ↑
統合待ち0 件衝突(累計)0 箇所手戻りの時間0.0 秒main に届くまで—

3人がそれぞれのブランチにコミットを積んでいきます。レーンを押すと、そのブランチを main へ統合します。まずは何件か溜まるのを待ってから押してみてください。