CI/CD
この部の 4 / 5 章 ・ 全体で 73 / 76 章 ・ 読了目安 40 分
- CI が落ちた時に自分で原因を切り分けられる
- デプロイ戦略ごとの向き不向きを説明できる
- 安全に戻す手段を先に用意できる
前の章で、コードを動かす「場所」の話をしました。この章は、そこへコードを届ける仕組みの話です。
現場に入ると、PR を出した瞬間に何かが自動で走り始めます。緑のチェックが付けばマージでき、 赤くなると止まります。あれが CI です。そしてマージした後、誰も手を動かしていないのに 新しいコードが本番で動き出します。あれが CD です。
この2つは「なんとなく動いている魔法」ではありません。壊れた時に直せる人になるのが、この章の目標です。
CI は何のためにあるか
CI(Continuous Integration / 継続的インテグレーション)を一言で言うと、 「手元では動いた」をチームで潰す仕組みです。
こういう経験は、CI がないチームでは日常的に起きます。
A さん「マージしました!ローカルでは動いてます」
B さん「main が起動しないんですが…」
A さん「あ、新しく入れたライブラリ、package.json に入れ忘れてました」
これは A さんが不注意なのではありません。人間は必ず忘れます。 テストを流し忘れる、lint を掛け忘れる、生成コードのコミットを忘れる。 1日に何十回もあるマージのうち、1回忘れれば main は壊れます。
CI は、その注意力を機械に肩代わりさせる装置です。
誤解しないでほしいのは、CI が守っているのはあなたではなく main だということです。 目的はミスを見つけて咎めることではなく、main ブランチが常に動く状態を保つことにあります。 main が壊れると他の全員が仕事を止め、「自分の変更が原因なのか main が既に壊れているのか」が 分からなくなって、チーム全体が調査に引きずり込まれます。CI はそれを防ぐための共有資産です。
CI が回っていると何が変わるか
| CI がない | CI がある |
|---|---|
| 壊れたことに気づくのが数日後 | 数分後 |
| 「誰の変更が原因か」を人間が探す | その PR だと即座に分かる |
| リリース直前にまとめて壊れる | 壊れた瞬間に1件ずつ直る |
| マージが怖いので PR が巨大になる | 小さく安全にマージできる |
最後の行が実は一番効いています。戻せる・すぐ分かる仕組みがあるから、小さく速く出せるのです。
CI で何を回すか
CI で走らせるものは、だいたいこの5種類です。
| 種類 | 何を見るか | 例 |
|---|---|---|
| lint / format | 書き方の統一、危ないパターン | ESLint、go vet、golangci-lint |
| 型チェック | 型の整合性 | tsc --noEmit |
| テスト | 振る舞いが壊れていないか | Vitest / Jest、go test |
| ビルド | そもそも成果物が作れるか | next build、go build、Docker イメージ |
| セキュリティスキャン | 既知の脆弱性、秘密情報の混入 | npm audit、govulncheck、シークレット検出 |
Go では -race を回す
Go のテストは、必ず競合検出を付けて回してください。
go test -race ./...データ競合(複数の goroutine が同じ変数を同時に触る)は、
普通に実行すると 100 回に 99 回は通ってしまいます。 通ってしまうから本番まで届き、
本番の負荷で初めて壊れます。-race を付けると、その場で検出できます。
代償として実行時間が数倍になるので、「PR では -race あり、ローカルでは無し」のように
使い分けるチームもあります。
型チェックとテストは別物
TypeScript の型チェックは「形が合っているか」しか見ません。
calculateTotal が合計ではなく差額を返していても、型が number なら通ります。
- 型: 存在しないプロパティを触っていないか、null の可能性を握り潰していないか
- テスト: 計算結果が正しいか、境界で壊れないか
両方必要です。 どちらかで代用はできません。
速さが命
CI について、新人のうちに覚えてほしい一番大事なことです。
遅い CI は、誰も待たなくなる。
CI が30分掛かるチームで何が起きるか。
1. PR を出す
2. 30分待てないので、別の作業に切り替える
3. 戻ってきたら赤い。原因を直して push
4. また30分
5. 「もういいや、後で見る」→ 確認せずにマージする文化が生まれる
CI が遅いと、CI を無視する習慣が育ちます。そうなると CI はコストだけ払って 効果はゼロの存在になります。速さは快適さの問題ではなく、CI が機能するかどうかの問題です。
目安として、PR で回る CI は10分以内を目標にしてください。5分ならさらに良いです。
10分を超えたら、それは「マシンが遅い」ではなく「設計を見直す時期が来た」というサインです。 並列化・キャッシュ・ジョブの分割で、たいていは半分以下になります。
放置すると15分になり、20分になり、ある日「CI はいつも遅いもの」という 諦めがチームに定着します。そこから戻すのは非常に大変です。
パイプラインの設計
速いものから落とす
CI のジョブには順番の設計があります。原則はこれです。
速くて、失敗しやすいものを先に走らせる。
lint(10秒)→ 型チェック(40秒)→ 単体テスト(2分)→ 結合テスト(6分)
lint で落ちる PR に、6分の結合テストを走らせる意味はありません。 早く落ちれば、フィードバックが早く返り、CI マシンの時間も節約できます。
この考え方を fail fast(早く失敗する) と言います。
並列に走らせるもの、直列にするもの
一方で、何でも直列にすると遅くなります。
| 関係 | 例 | やり方 |
|---|---|---|
| 互いに依存しない | lint / 型チェック / 単体テスト | 並列に走らせる |
| 前段の成果物が必要 | ビルド → E2E テスト | 直列(needs で繋ぐ) |
| 落ちても他を止めたくない | 複数バージョンでのテスト | マトリクス + fail-fast: false |
GitHub Actions ならこう書きます。
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx tsc --noEmit
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
# ここだけ直列: 上が全部通ってからビルドする
build:
needs: [lint, typecheck, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run buildneeds を書かないジョブは自動的に並列で走ります。
「lint → 型 → テスト」と並べたくなりますが、互いに独立なら並列が正解です。
順番の話は「どれを先に見るか」ではなく、「どこで打ち切るか」の設計です。
キャッシュ
CI の時間の大半は、たいてい依存関係のインストールです。
毎回 node_modules をゼロから作れば、それだけで数分掛かります。
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm # package-lock.json のハッシュをキーにキャッシュ
- uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true # go.sum をキーにモジュールとビルドキャッシュを再利用キャッシュ設計のポイントはキーの選び方です。
- キーが細かすぎる(例: コミットハッシュ)→ 毎回ミスして意味がない
- キーが粗すぎる(例: ブランチ名だけ)→ 古い依存が残って謎の失敗を生む
正解は「依存を決めているファイルのハッシュ」です。
npm なら package-lock.json、Go なら go.sum、それぞれのハッシュをキーにします。
直す前に計測する
「なんとなく遅い」で手を付けると、効かない場所を最適化しがちです。 GitHub Actions なら、ジョブの画面で各ステップの所要時間が見られます。 まず一番長いステップを特定してください。 たいてい犯人は 「依存インストール」「Docker イメージのビルド」「E2E テスト」のどれかです。
10秒のステップを5秒にしても意味はありません。6分のステップを2分にするのが仕事です。
PR で回すもの / main で回すもの
全部を毎回回す必要はありません。
| タイミング | 回すもの |
|---|---|
| PR(毎 push) | lint、型、単体テスト、ビルド。速さ優先 |
| main マージ後 | 上に加えて結合テスト、イメージ push、ステージングへのデプロイ |
| 夜間(定期) | E2E フルセット、依存の脆弱性スキャン、負荷テスト |
「重いけれど毎回は要らない」ものを夜間に逃がすのが、CI を10分以内に収める定石です。
CI が落ちた時の切り分け
赤いチェックが付いた時にどうするか。ここが実務で一番差の出るところです。
手順
1. 「自分の変更が原因か」「main が既に壊れているか」を切り分ける
2. ログを開き、【最初の】エラーを読む(最後ではない)
3. そのエラーをローカルで再現する
4. 再現しないなら、環境差を疑う
まず「main は生きているか」を見る
これを最初にやってください。 自分の PR のログを読み込む前です。
- main のワークフロー実行が緑か赤か
- 他の人の PR も同じところで落ちていないか
main が既に赤いなら、あなたの変更は無関係かもしれません。 そこに気づかず2時間デバッグする、というのが新人が最もよく溶かす時間です。
main が壊れている場合、あなたがやるべきことは自分のコードを直すことではなく、 チームに知らせることです。
main の CI が失敗しているようです(〇〇のテストが失敗)。 直近のマージは PR #123 に見えます。 私の PR も同じところで落ちているので、いったん待機します。
逆に、自分が main を壊してしまった時は、隠したり黙って直そうとしないでください。 壊れている間、チーム全員が影響を受けます。やることは3つです。 すぐ知らせる。戻すか直すかを判断する(迷ったら revert が早い)。落ち着いてから原因を共有する。
main を壊すこと自体は悪ではありません。放置することが悪です。
ログは最初のエラーを読む
CI のログは数千行あります。新人がやりがちなのは、一番下までスクロールして 最後のエラーを読むことです。これはたいてい間違いです。
...
ERROR src/order/total.ts:12 Type 'string' is not assignable to type 'number' ← 本当の原因
ERROR src/order/index.ts:3 Cannot find module './total' ← 巻き添え
ERROR build failed with 2 errors ← ただの結果
npm ERR! code ELIFECYCLE ← ただの結果
npm ERR! Exit status 1 ← 最後の行
最後の行(Exit status 1)には何の情報もありません。
最初のエラーが原因で、後続はその余波というのが基本形です。
ログを開いたら、まず上に向かって「最初の ERROR / FAIL」を探してください。
GitHub Actions のログ画面では、ブラウザの検索(Cmd+F)で error や FAIL を
探すのが速いです。
ローカルで再現する
原因の当たりが付いたら、CI が実行しているのと同じコマンドを手元で叩きます。
# ワークフローの run: に書いてあるコマンドをそのまま実行する
npm ci # ← npm install ではない。lock ファイル通りに入れる
npm run lint
npx tsc --noEmit
npm testnpm install ではなく npm ci を使うのが重要です。
install は lock ファイルを更新してしまうことがあり、CI と違う依存で動いてしまいます。
「手元では通るのに CI では落ちる」の典型的な原因がこれです。
再現しないなら環境差を疑う
手元で通って CI で落ちるなら、コードではなく環境が違います。よくある差はこの5つです。
| 差 | 具体例 | 対策 |
|---|---|---|
| バージョン | 手元 Node 22 / CI は Node 20 | .node-version、go.mod の go ディレクティブを揃える |
| OS | 手元 macOS / CI は Linux | ファイル名の大文字小文字、パス区切り、改行コード |
| タイムゾーン | 手元 JST / CI は UTC | 日付テストで固定 TZ を指定する |
| ロケール | 文字列のソート順、数値の桁区切り | ロケール依存の比較を避ける |
| 依存の解決 | lock を無視して入れている | npm ci を使う |
macOS はファイル名の大文字小文字を区別しませんが、Linux は区別します。
import { Button } from './button'(実体は Button.tsx)は
手元では通り、CI では落ちます。 これは非常によく踏みます。
再現しない時の犯人は、だいたい次の3つに絞られます。
- 環境差(上の表)
- テストの実行順依存 — 前のテストが残したデータに依存している。CI は並列やシャッフルで 順番が変わり、そこで露見する
- 時刻・乱数・外部通信 — テストの中で
new Date()や実 API を叩いている
3番目が疑わしければ、それは CI の問題ではなくテストの設計の問題です。 時刻と乱数は注入して固定します。
Flaky なテスト
Flaky(フレーキー)テストとは、同じコードなのに通ったり落ちたりするテストです。 たいていの原因は、待ち時間の甘さ、実行順への依存、共有リソースの取り合いです。
やってはいけないのは、リトライ回数を増やして黙らせることです。
# これで「直った」ことにしない
- run: npm test -- --retry 3Flaky を retry で誤魔化すと、次に起きるのはこれです。
- 落ちても「また flaky だろう」と誰も見なくなる
- 本物のバグが flaky に紛れて見逃される
- 気づいた時には、CI の赤が意味を持たなくなっている
正しい対処は、原因を直すか、直せないなら一時的に skip して、チケットを切って 期限を決めて直すことです。retry は延命であって治療ではありません。
あなたの PR で CI が赤くなりました。最初にやるべきことは?
CD とデプロイ戦略
CI が通ったコードを、実際に動かす環境へ届けるのが CD です。
- 継続的デリバリー(Continuous Delivery) — いつでも出せる状態を保つ。出す判断は人間
- 継続的デプロイ(Continuous Deployment) — main にマージされたら自動で本番まで出る
多くのチームは「ステージングまでは自動、本番は人が承認」から始めます。 いきなり全自動にする必要はありません。戻せる仕組みが揃ってから進めるものです。
3つのデプロイ戦略
| 戦略 | やり方 | 切り戻しの速さ | コスト | DB スキーマとの相性 |
|---|---|---|---|---|
| ローリング | 古い Pod を少しずつ新しいものに置き換える | 中(再度ローリングで戻す) | 低い(追加リソースは少し) | 新旧が同時に動くので互換必須 |
| ブルーグリーン | 新環境を丸ごと立て、ルーティングを一気に切り替える | 速い(向き先を戻すだけ) | 高い(本番2セット分) | 切替の瞬間だけ新旧が混ざる |
| カナリア | まず数%のトラフィックを新版へ流し、様子を見て広げる | 速い(比率を0に戻す) | 中 | 新旧が長く同時に動くので互換必須 |
Kubernetes の Deployment は、デフォルトがローリングです。何も設定しなければこれになります。
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0 # 落としてから作らない = 常に処理能力を保つ
maxSurge: 1 # 一時的に1つ多く立てるどれを選ぶか
- ローリング — 既定値。ほとんどのバックエンドサービスはこれで十分
- ブルーグリーン — 切替を一瞬で終わらせたい、切り戻しを最速にしたい時。コストと引き換え
- カナリア — 影響が読み切れない変更(性能特性が変わる、外部依存が増える)を、実トラフィックで確かめたい時
ここが設計上、最も事故が起きるポイントです。
ローリング中は、v1 の Pod と v2 の Pod が同じデータベースを同時に触ります。 つまり v2 だけが知っている新しいカラムに v1 が対応できないと、その数分間で壊れます。
「新しいコードが出れば古いコードは消える」と考えると必ず踏みます。 新旧は必ず一定時間、共存します。
ヘルスチェックが切り戻しの前提
Kubernetes のローリング更新は、新しい Pod が「準備完了」になるまで古い Pod を消しません。 その判定が readiness probe です。
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5ここが中身のないエンドポイント(常に 200 を返すだけ)だと、 起動に失敗しているコンテナが「準備完了」と判定され、壊れた版が全台に広がります。
ヘルスチェックは「プロセスが生きているか」ではなく、 「リクエストを処理できる状態か」を返してください(DB に繋がっている、必要な設定が読めている)。
ロールバックを先に用意する
開発の流れで「出す」より「戻せる」ことの方が重要だと書きました。CD ではそれが具体的な設計になります。
戻せないデプロイをしない
デプロイの計画を立てる時、必ず先にこの問いに答えてください。
これが本番で壊れていたら、何分で元に戻せますか。
答えられないなら、まだ出す準備ができていません。
# Kubernetes なら、直前のリビジョンに戻すのは一撃
kubectl rollout undo deployment/my-service
# 履歴を確認する
kubectl rollout history deployment/my-serviceコンテナイメージには必ず一意なタグ(コミットハッシュなど)を付けてください。
latest だけで運用していると、「戻したい版がどれか分からない」という状況になります。
DB マイグレーションは戻せないことがある
ここがロールバックの最大の弱点です。
アプリのコンテナは1コマンドで戻せますが、データベースは違います。
-- これを実行した後、「戻す」とはどういう意味でしょうか
ALTER TABLE users DROP COLUMN nickname;カラムを消したら、そこに入っていたデータは消えます。 アプリを前の版に戻しても、データは戻りません。
だから、スキーマ変更は「戻す」のではなく 「常に前方互換を保つ」 形で進めます。 これを expand→contract(拡張してから縮小) と呼びます。
expand → contract
nickname カラムを display_name に改名したい、という例で見ます。
一気にやると、ローリング中の古い Pod が nickname を探して壊れます。
正しくは3回に分けてデプロイします。
| 段階 | やること | この時点で動くもの |
|---|---|---|
| 1. expand | display_name を追加する(nickname は残す) | 旧コードも新コードも動く |
| 2. 両方書く | アプリが両方のカラムに書き、読むのは新しい方。既存データを移送する | 旧コードも新コードも動く |
| 3. contract | 旧コードが完全に消えたのを確認してから nickname を削除 | 新コードのみ |
ポイントは、各段階が単独でロールバック可能なことです。 段階2で問題が起きても、段階1の状態にアプリを戻せば動きます。
新人が最もやりがちな事故がこれです。
「カラムを消す SQL」と「そのカラムを使わなくしたコード」を同じ PR で同時に出すと、 ローリング中の数分間、まだ古いコードが動いている Pod がカラムを探して落ちます。
ALTER TABLE を含む変更を書く時は、必ず自分に聞いてください。
「この瞬間、古いコードはまだ動いているか?」 答えは、ほぼ常に「はい」です。
デプロイと機能公開を分ける
ここまでの話の総仕上げです。
新人のうちは「デプロイする = 新機能がユーザーに見える」と考えがちですが、 実務ではこの2つを分離します。
- デプロイ = 新しいコードを本番の環境に置くこと
- リリース(機能公開) = そのコードの機能をユーザーに見せること
分離する道具がフィーチャーフラグです。
// コードは本番に出ているが、機能はまだオフ
if (await featureFlags.isEnabled('new-checkout', { userId })) {
return renderNewCheckout();
}
return renderLegacyCheckout();これができると、次のことが可能になります。
| できること | 意味 |
|---|---|
| 未完成の機能をマージできる | 巨大な長期ブランチを作らずに済む(マージ地獄の回避) |
| 一部のユーザーにだけ出せる | 社内メンバー → 5% → 全体、と段階を踏める |
| デプロイせずに戻せる | 障害時、フラグをオフにするだけ。数秒で復旧 |
| 公開タイミングを事業側が決められる | エンジニアの深夜リリースが不要になる |
3行目が特に強力です。切り戻しにデプロイが要らないというのは、 障害対応の速度がまったく変わることを意味します。
フィーチャーフラグは便利ですが、放置すると分岐がコードに永久に残ります。 フラグが50個あるコードベースは、どの経路が本番で動いているのか誰にも分からなくなります。
フラグを足す時は、同時に「全体公開後に削除する」チケットを切ってください。
// TODO(PROJ-456): 全体公開が完了したらこの分岐を削除する足す時に、消す計画も決める。 これがフラグ運用の唯一のコツです。
やってはいけないこと
CI を skip してマージ
「急ぎのリリースなので、CI を待たずにマージしました」
急いでいる時ほど、確認を飛ばした変更は壊れます。そして急いでいる時ほど、 壊れた時のダメージが大きいです。
管理者権限で必須チェックを回避できる設定があっても、それは 「本番が既に燃えていて、修正を1分でも早く出すしかない」ような場面のためのものです。 日常的に使うものではありません。
Flaky を retry で誤魔化す
前述の通りです。再実行して通ったから OK、を習慣にしないでください。 それは「CI の赤に意味がない」状態への第一歩です。
金曜の夕方に大きなデプロイをする
開発の流れで触れた話が、ここでも効いてきます。
問題は「金曜だから」ではなく、問題が起きた時に対応できる人が揃っているかです。
| 場面 | 判断 |
|---|---|
| 金曜16時に大きめの機能をデプロイ | 月曜に延ばす |
| 金曜16時に本番障害の修正をデプロイ | 出す(放置する方が危険) |
| 月曜10時に大きめの機能をデプロイ | 良い。1週間かけて様子を見られる |
デプロイの後は最低30分は張り付いてエラー率とレイテンシを見ます。 その30分を確保できない時間帯には出さない、と考えると分かりやすいです。
秘密情報を CI ログに出す
これは事故のレベルが違います。CI のログは、リポジトリを見られる人が全員読めます。
# 絶対にやらない
- run: echo "DB_PASSWORD=$DB_PASSWORD"
- run: env # 環境変数を全部出す
- run: curl -v https://api.example.com -H "Authorization: Bearer $TOKEN" # -v はヘッダを出すset -x を付けたシェルスクリプトも、実行するコマンドをそのまま出力するので危険です。
正しい扱いはこうです。
- name: Deploy
env:
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }} # secrets から注入する
run: ./scripts/deploy.sh # ログには値が出ないうっかりトークンをログや commit に出してしまった時、ログを消して安心してはいけません。 その値は既に外に出たものとして扱います。
やることは、削除ではなくローテーション(無効化して新しい値を発行)です。 そして、すぐ報告してください。隠すと、後から悪用された時に原因究明が不可能になります。
これは新人が責められる種類のミスではありません。報告が遅れることだけが問題です。
users テーブルの不要になったカラムを削除し、同時にそのカラムを参照しないようアプリを修正した PR を、Kubernetes 上のサービスにローリング更新でデプロイします。何が起きますか。
ケーススタディ: 20分の CI を6分にする
C さんのチームでは、CI が20分掛かっていました。
npm ci 4分 (キャッシュなし)
lint 1分
型チェック 1分
単体テスト 3分
E2E テスト 8分 (PR ごとに全シナリオ)
Docker ビルド 3分 (毎回フルビルド)
全部が直列でした。手を入れた順序はこうです。
- 計測する — 一番長いのは E2E の8分、次が
npm ciの4分 - キャッシュを入れる —
setup-nodeのcache: npmで 4分 → 40秒 - 並列化する — lint / 型 / 単体テストを並列に。5分 → 3分(一番遅いものの時間)
- E2E を移す — PR では主要導線だけ(2分)、フルセットは main マージ後と夜間へ
- Docker のレイヤキャッシュ — 依存インストール層を分けて 3分 → 1分
結果は 20分 → 6分 です。特別な技術は一つも使っていません。
重要なのは順番です。計測せずに Docker から手を付けていたら、3分が1分になっただけで 「あまり変わらないね」で終わっていました。
ケーススタディ: retry で隠れていたバグ
D さんのチームでは、決済まわりの結合テストが週に2〜3回落ちていました。
再実行すると通るので、いつしか --retry 2 が付けられ、誰も気にしなくなりました。
半年後、本番で「まれに二重課金される」という報告が上がります。 調査すると、原因はテストが指摘していたものと同じでした。
- テストは、並列実行時に同じ注文 ID が2回処理されると落ちていた
- それは flaky ではなく、冪等性の欠陥をテストが正しく検出していた
- retry で通っていたのは、たまたま並列の順番が変わっただけ
「たまに落ちる」は「たまに壊れる」です。 テストが正しくて、コードが間違っている 可能性を、いつも最初に考えてください。
実務の落とし穴まとめ
- CI が遅い — 10分を超えると誰も待たなくなり、確認せずマージが始まる
- ログの最後を読む — 原因は最初のエラー。最後の行はただの結果
- main の状態を見ずにデバッグする — 自分は無関係かもしれない
npm installで再現しようとする — CI はnpm ci。依存が変わって再現しない- flaky を retry で黙らせる — 本物のバグが赤に紛れて見逃される
- スキーマ変更とコード変更を同時に出す — ローリング中の古い Pod が落ちる
- 戻し方を決めずにデプロイする — 「何分で戻せますか」に答えられないなら出さない
- フラグを消さない — どの経路が本番で動いているか誰も分からなくなる
- 秘密情報をログに出す — 消しても手遅れ。無効化して報告する
まとめ
- CI は 「手元では動いた」をチームで潰す仕組み。人間の注意力に頼らないための機械
- CI が守っているのはあなたではなく main。壊したら隠さず即座に知らせる
- 回すのは lint / 型 / テスト / ビルド / セキュリティスキャン。Go は
-raceを付ける - 速さが命。10分を超えたら設計を見直す。速いものから落とし、独立なものは並列、依存はキャッシュ
- CI が落ちたら、まず main が生きているかを切り分ける。次にログの最初のエラーを読む
- 再現しないなら環境差(バージョン / OS / タイムゾーン / ロケール / 依存の解決)を疑う
- デプロイ戦略はローリング / ブルーグリーン / カナリア。新旧は必ず一定時間共存する
- 戻し方を先に用意する。DB は戻せないので expand→contract で段階を分ける
- デプロイと機能公開を分ける。フラグをオフにするだけで戻せる状態が最強
- flaky を retry で誤魔化さない。「たまに落ちる」は「たまに壊れる」
公式ドキュメント
迷ったら一次情報に戻ってください。
| 対象 | リンク |
|---|---|
| GitHub Actions ドキュメント(日本語) | https://docs.github.com/ja/actions |
| DORA(デプロイ指標の調査) | https://dora.dev/ |
| Google Cloud Deploy | https://cloud.google.com/deploy/docs?hl=ja |
次の章では、出したものが本当に動いているかをどう知るか——監視・アラート・ そしてオンコールで呼ばれた夜に何をするかを扱います。