運用中の案件でCI/CDパイプラインを構築してみた
Date2026/08/22 Last Modified2026/08/22
担当している案件(案件Aとする)にて、CI/CDパイプラインが実現したので、そこに至るまでの手順について記録しておきたいと思います。
CI/CDを導入するメリットについて
案件Aにはいくつかの問題がありました。
- テスト手順がわかりにくい
- 本番デプロイの手順が明確に定義されていない
- 確認手順が多すぎる
AIエージェントが普及しても属人化が改善しそうになかったのは、リスクを伴う変更を行うときの作業手順が明確になっておらず、引き継ぐ立場からしても不安が多いという面が強かったからです。
この引き継ぎの難しさを解消するためにCI/CDを導入しました。
つまるところ、手順を明確化して自動化できるフローに描き直したということになります。
整理し終えた結果、本番デプロイの日にTodoリストを用意して、大急ぎで作業をしなくてはならないというプレッシャーで胃を痛める必要がなくなりました。
導入前
- テスト環境でのユニットテスト
- 静的解析
- 慎重な差分チェック
- 本番環境にSSH接続して
git pull - npmビルド
composer install- migration
- ApacheやPHP-FPMのリロード
- Laravelキャッシュ更新
- リリースベースが管理できない
- 過去のドキュメントが入り混じる
導入後
- GitHub Actionsで自動テスト
- dry-runで差分チェック可能
- workflow実行でデプロイ完了
- 過去のリリースベースへの復帰が簡単
- ドキュメント管理も自動化
作業手順の目次
- 事前準備
- Runnerの構築
- SSH権限設計
- CDの構築
- CI/CD実行
1. 事前準備
ここでは、CI/CDを達成するための前準備についてまとめます。
自動検証する対象を選定
CIには「コード変更を安全に統合できるかを自動検証する工程」という意味合いがありますが、具体的に何を実行するかは明確に定義されておらず、プロジェクトに応じて決められる部分です。
GitHub Actionsでは、runs-onでjobを実行するRunnerを指定し、そのRunner上でworkflowの各stepを順番に実行します。
ソースコードが必要な場合は通常actions/checkoutでRunner上へcheckoutし、その環境でビルドやテストを実行します。
GitHub-hosted runnerは原則jobごとに新しい実行環境が用意されますが、self-hosted runnerでは同じ実行環境を再利用します。
案件Aでは、コードの検証とドキュメントの更新をCIの目標として、以下の工程にしました。
- PHPUnit
- PHPStan
- Laravel Pint
- Composer validation / platform requirements
- Vite build
- MySQLを使用したintegration test
- ドキュメントチェック
workflowの作成
上記に挙げたものをphp-testsとmysql-testsに分解してci.ymlにまとめます。
また独自に設置したドキュメントチェックは、Pythonを起動してプロジェクトのドキュメント構造をチェックし、違反していないかを報告させるようにしています。
この構造を一致させることで、AIエージェントが定型のドキュメントを構築できるようになり、ドキュメント基盤と連携して更新するときも出力が安定します。
2. Runnerの構築
GitHub Actionsを利用するためにはRunnerが必要です。
本番環境へのSSH接続にIP制限がある場合、Runnerから本番環境までのネットワーク経路を確保する必要があります。
今回は接続可能なローカルPCをself-hosted runnerとして利用しました。
別案として、AWSであればOIDCで短期AWS認証情報を取得し、SSM Run Commandなどを利用することでSSHそのものを使わない構成も考えられます。
Runnerを設置して結び付ける
Runnerの構築はそこまで難しい手順ではありません。
リポジトリ専用のRunnerを置く場合、リポジトリのSettingsからRunnerを選択し、手順に従ってインストールするのみで良いです。
インストールが完了したら、deploy用のworkflowでそのRunnerにruns-onを定義します。
WorkflowがLinuxコマンドを前提としている場合、Windows runnerではコマンド差異による失敗が発生することがあります。その場合はWSL/Linux上へRunnerを構築するか、WorkflowをPowerShell等へ対応させます。
3. SSH権限設定
ここではデプロイ用のユーザーの接続方式と権限について整理していきます。
デプロイのフローは以下のようになります。
GitHub Actions → Runner → インスタンス
Runnerからインスタンスへ接続するときにはSSH接続を利用します。
案件Aではstaging / productionごとにGitHub Environmentを作成し、SSH接続情報をEnvironment Secretsとして管理しています。
deploy jobが対象Environmentを参照することで、そのEnvironmentに設定されたSecretsを利用します。
秘密鍵の設定
接続経路がActions → インスタンスになるので、Actions側に秘密鍵を持たせる必要があります。
(従来のgit pull形式ではインスタンス側に秘密鍵をセットしていました。)
ローカルPCなどの信頼できる環境で秘密鍵を生成し、SSH_KEYとしてEnvironmentsに登録します。
また公開鍵はインスタンスに置く必要があるので、鍵の内容だけコピーして~/.ssh/authorized_keysに登録します。
登録が完了したら、SSH接続チェック用のworkflowも新規に作成します。
実際にActions内で実行し、接続が成功することを確かめます。
デプロイ用ユーザーの作成
GitHub Actionsから実行される処理の権限を限定するため、専用のdeploy userを作成します。
releaseの配置、migration、Laravel cache生成、current切替などはdeploy userが実行します。
アプリケーションコードはdeploy userが管理し、Apache/PHP-FPMから必要なファイルを読み取れるようweb groupを設定します。
storageなどWebサーバーから書込みが必要な場所だけ適切な書込権限を付与し、PHP-FPM reloadについてはdeploy userへ対象コマンドだけ限定してsudoを許可します。
- release配置
- migration
- cache生成
- current切替
- PHP-FPM reload
4. CDの構築
CDと相性の良いリリースベース管理の構成の準備から始めます。
currentシンボリックリンクを原子的に切り替えることで、稼働中のreleaseを短時間で切り替えるatomic deployment方式
例示すると、以下のような構成になります。
これを採用することで、過去にデプロイしたリリースを保持し、既知の正常なリリースへ簡単に切り戻せるようになります。
案件Aではrelease directoryをcommit SHA単位で管理しているため、どのcommitがデプロイされたかも追跡できます。
シンボリックリンクを以前のreleaseへ戻してもDB migrationは自動的には元に戻りません。
そのためmigrationは旧releaseとの後方互換性を考慮するか、必要に応じて別途DBの復旧手順を用意します。
稼働中のディレクトリを直接更新するin-place deploymentでは、コード更新、依存関係更新、cache生成などの途中状態をユーザーが参照してしまう可能性があります。
release directory方式では新releaseを別ディレクトリで完成させてからcurrentを切り替えるため、この時間を大幅に短縮できます。
なお、運用中のディレクトリをsymlink化することにより、稼働中であってもvhostsを変更する必要がないため、切り替えでバタバタすることが無く障害が起こる可能性も低いです。
workflowの作成
dry-runとdeployが選択可能なworkflowファイルを作成します。
dry-runでは、環境を変更することなく差分を確認することができる処理で、デプロイの事前テストのような位置づけにあります。
deployでは、実際にデプロイ作業をするときの内容をすべて再現します。
5. CI/CDの実行
GitHubに最新のソースを反映したら、GitHubのActions画面からworkflowを選択するだけでデプロイが可能です。
まずはdry-runをして、完了すればDeployを行いましょう。
まとめ
まだいくつか課題はありますが、ひとまず目標であった自動テスト&デプロイ手順の簡略化については達成することができました。
現在はローカルPCをself-hosted runnerとして利用していますが、将来的には個人PCではなく、CI/CD専用の管理されたRunner環境へ移行したいと考えています。
複数repositoryでRunnerを共用する場合は、Runner Groupやアクセス可能なrepositoryを制限し、影響範囲を分離する必要があります。