5,000ドルの代償:深夜の緊急事態
ある火曜日の午前2時、私の電話が鳴り響きました。訓練ではなく、本番環境の緊急事態でした。デスクに駆け寄り原因を突き止めると、ジュニアデベロッパーが誤ってAWSのシークレットアクセスキーを公開リポジトリにコミットしていたのです。わずか10分足らずで自動ボットがキーをスキャンし、暗号資産マイニングのために膨大なEC2インスタンス群を立ち上げていました。AWSの請求額はすでに4,500ドルを超えていました。
キーを無効化し、履歴を削除しましたが、教訓は明白でした。手動のレビューだけに頼って、すべてのミスを防ぐことは不可能です。疲労は溜まり、ミスは避けられません。開発者のマシン上で動作し、シークレットや不完全なコードがワークステーションから外に出るのを阻止するゲートキーパーが必要です。そのゲートキーパーこそが「pre-commit」です。
課題:「修正:リンターエラー」のコミットループ
ワークフローを標準化する前、私たちのGit履歴は恥の墓場のようでした。よくあるパターンです。1つの意味のある機能追加コミットの後に、「リンター修正」「フォーマットを再度修正」「これが最後、約束する」といったメッセージが4つ続くのです。これは乱雑で、コストもかかります。
開発者が小さな構文エラーをプッシュするたびに、CI/CDパイプラインがトリガーされます。もしGitHub ActionsやJenkinsのビルドに10分かかる場合、改行一つ忘れただけで、貴重な時間とコンピューティングクレジットを無駄にすることになります。これらのチェックを「左へシフト(シフトレフト)」させ、コードがローカルマシンを離れる前に実行することで、毎週何時間ものエンジニアリング時間を節約できます。
Gitフックとは何か?
Gitには「フック(hooks)」と呼ばれる組み込みの仕組みがあります。これは、pre-commit、commit-msg、pre-pushなどの特定のイベント中に自動的に実行されるスクリプトです。しかし、生のフックを管理するのは面倒です。これらはバージョン管理されない.git/hooksディレクトリに保存されるため、20人の開発チーム全体でルールを同期させるのはほぼ不可能です。
pre-commitフレームワークは、その状況を変えてくれます。これはPythonベースのマネージャーで、シンプルな設定ファイルを使用してすべてを処理します。Node.js、Go、Rustなどで動作するツールの環境を管理し、チームの全員が全く同じチェックを実行できるようにします。
セットアップ:守護者の構築
ジュニアからシニアのDevOpsマインドセットへの移行には、日常的な作業の自動化が不可欠です。コードの健全性を支える3つの柱である「フォーマット」「ロジックのリンティング」「セキュリティ」を処理する設定を構築しましょう。
1. インストール
システムにパッケージをインストールします。pipも使えますが、macOSユーザーならHomebrewが便利です。
pip install pre-commit
# またはMacユーザーの場合
brew install pre-commit
2. 設定ファイル
プロジェクトのルートに.pre-commit-config.yamlファイルを作成します。このファイルは、品質基準の「信頼できる唯一の情報源(Source of Truth)」となります。以下は、私がPythonベースのマイクロサービスで使用している、実戦で鍛えられた設定例です。
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 23.11.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
rev: 6.1.0
hooks:
- id: flake8
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
3. なぜこれらのフックを使うのか?
- 掃除屋(The Janitors):
trailing-whitespaceとend-of-file-fixerは、diffをきれいに保ちます。空白の調整だけのためにプルリクエストが50行も変更されることはもうありません。 - Black: 「妥協なき」フォーマッターです。シングルクォートかダブルクォートかという議論に終止符を打ち、自動的に選択を統一します。
- Flake8: ロジックの偵察担当です。未使用のインポートや未定義の変数を、実行時にクラッシュする前にフラグを立てて知らせてくれます。
- detect-secrets: 最も重要なツールです。APIキーのようなエントロピーの高い文字列をスキャンします。漏洩の可能性を見つけると、即座にコミットを中止させます。
4. 有効化
設定ファイルを作成しただけでは何も起こりません。Gitに登録する必要があります。ターミナルで以下を実行してください。
pre-commit install
これで、git commitを実行するとフックが起動します。チェックに失敗した場合、pre-commitはプロセスを停止します。多くの場合、フォーマットの問題は自動的に修正されます。その場合は変更を再度ステージ(add)して、もう一度コミットを試みるだけです。
シークレット検知のワークフロー
detect-secretsフックは、ベースラインファイルを使用して、本物の漏洩と、テストスイート内のダミーキーのような既知の「安全な」文字列を区別します。以下を実行して初期化します。
detect-secrets scan > .secrets.baseline
フックが誤検知(False positive)をした場合は、ベースラインを更新します。このひと手間は、壊滅的なデータ漏洩を防ぐためのわずかな代償です。
レガシーコードのクリーンアップ
コードが合格するかどうかを確認するために、コミットを待つ必要はありません。既存のプロジェクトにpre-commitを導入する場合は、すべてのファイルに対して一括で実行します。
pre-commit run --all-files
注意:数年前のプロジェクトであれば、500以上のエラーが出るかもしれません。がっかりしないでください。本番環境で問題が起きる前に、今これらの問題を表面化させる方がはるかに良いのです。
なぜこれがDevOpsにとって重要なのか
pre-commitの肝はフィードバックループにあります。開発者が入力から10秒後にバグに気づけば、すぐに修正できます。もし20分後にCIビルドの失敗で知らされたら、すでに集中力(フロー)は途切れています。コンテキストスイッチは生産性の静かなる殺人者です。
標準化も大きなメリットです。「自分のマシンでは動いた」という言い訳はもう通用しません。開発者のラップトップ上のリンティング環境は、ビルドサーバーで実行されているものと同一になります。
結論
pre-commitの設定は、最も投資対効果の高いアクションの一つです。コード品質の維持を、面倒な作業から自動化されたバックグラウンドプロセスへと変えてくれます。私たちのチームで午前2時のトラブルが激減したのは、よくあるエラーを自動化によって根絶したからです。まずは基本的なフォーマットから始めて、徐々に拡張していきましょう。将来のあなたが、今の平穏な心に感謝することでしょう。

