Jenkins

Mac mini + Docker + Jenkins環境を構築し、Laravelのテストを実行する(その2)

前回はMac miniにDocker + Jenkins環境を構築まで行いました。

今回は以下の要件のLaravelのサンプルプロジェクトをGitに登録しテストを実行する設定までを纏めました。
プロジェクトのファイル構成は以下のような感じです。

/LaravelSampe
  - /.docker // Dockerfileなどを格納
  - /src  // Laravelプロジェクトを格納
  - docker-compose.yml
  - docker-compose.override.jenkins.yml // Jenkins環境で動作するためのdocker-compose.overrideファイル

Jenkinsのパイプラインは以下のよう設定するようになります。

  1. Gitリポジトリから開発環境一式をクローンする
  2. Jenkins環境内でDocker環境をビルドし起動する
  3. Laravelの開発環境側のDocker内でcomposerでLaravelのファイル一式をインストールする
  4. php artisan test」でテストを実行する
  5. Dockerをダウンする

時間は0時になった時に自動でテストを行うようにします。

1. ジョブの作成

1. まずは「新規ジョブ作成」をクリックします。

2. 次に「ジョブ名入力」に任意の名前を入力して「パイプライン」を選択して「OK」で次の画面に遷移します。

3. 「説明」にジョブの説明を入力します。
また下のチェック欄は以下のようになります。

Do not allow concurrent builds
(同時ビルドを許可しない)
意味: このチェックボックスをONにすると、同じジョブが同時に複数回実行されることを禁止します。もしジョブがすでに実行中の場合、新しいビルドトリガーが発生しても、現在のビルドが完了するまで新しいビルドはキューに入れられて待機します。

使用する状況: ほとんどのビルド、テスト、デプロイジョブでONにすることが推奨されます。特に、データベースや共有ファイルシステムなど、リソースの競合が発生する可能性のあるジョブや、ビルド結果の再現性が重要なジョブでは必須です。Docker環境の起動・停止を含むジョブでは、同時実行によるポート競合やコンテナ名の重複などの問題を防ぐためにONにするべきです。
Do not allow the pipeline to resume if the controller restarts
(コントローラーが再起動した場合にパイプラインの再開を許可しない)
意味: このチェックボックスをONにすると、Jenkinsマスター(コントローラー)がシャットダウン、クラッシュ、アップグレードなどで再起動した場合に、実行中だったPipelineジョブが中断され、再開されないようにします。再起動後、そのビルドは中断された(または失敗した)状態として記録されます。

使用する状況: デプロイなど、途中で中断されると深刻な問題を引き起こす可能性のある、最も重要なPipelineでONにすることが推奨されます。状態を持つジョブや、外部システムに影響を与える可能性のあるジョブでも有効です。今回のLaravelテストのパイプラインのようにDocker環境の操作を含む場合も、予期せぬ再開による問題を防ぐためにONにすることを検討すると良いでしょう。
GitHub project意味: このオプションは、このJenkinsジョブがGitHub上の特定のプロジェクトに関連付けられていることを示します。ここにGitHubプロジェクトのURLを記述すると、JenkinsはGitHubとの連携を強化できます。

使用する状況: GitHubのWebhook連携: GitHubリポジトリへのプッシュをトリガーとしてJenkinsジョブを自動実行する際に、このURLを設定することでGitHub側でJenkinsのジョブを認識しやすくなります。
ビルドステータスの表示: JenkinsがGitHubのPull Requestやコミットに対してビルドの成功/失敗ステータスをフィードバックする際に利用されます。
GitHub Enterpriseとの連携: プライベートなGitHub Enterprise環境と連携する場合にも使われます。

注意点: 公開されていないGitリポジトリ(例: 自己ホスト型Gitサーバー)の場合は、GitHubとの直接連携ではないため、この項目は通常使用しません。
Pipeline speed/durability override
(パイプラインの速度/堅牢性の上書き)
意味: Jenkins Pipelineの実行における**パフォーマンス(速度)堅牢性(耐障害性)**のバランスを調整するための設定です。JenkinsがPipelineの実行状態をディスクに書き込む頻度と方法を制御します。

選択肢: Maximum survivability/durability but slowest: 最も堅牢なモードで、Jenkinsマスターが予期せず停止しても、Pipelineの状態がほぼ完全にディスクに保存され、中断したステップから正確に再開できる可能性が非常に高いです。ディスクI/Oが多く、パフォーマンスは最も遅くなります。
Survivable but not atomic (または類似名): 中間的なバランスで、ある程度の堅牢性を持ちつつ、パフォーマンスを向上させます。
Performance optimized but least durable (または類似名): 最も高速なモードですが、Jenkinsマスターが停止した場合に実行中のPipelineの状態が失われ、再開できなくなる可能性が非常に高いです。

使用する状況: デプロイなど中断が許されない重要なPipelineでは「Maximum survivability/durability」を選び、テストなど再実行が容易なPipelineでは「Performance optimized」を選ぶことがあります。今回のLaravelテストのパイプラインでは、安定性と信頼性を重視し「Maximum survivability/durability but slowest」を選択することが推奨されます。
Preserve stashes from completed builds?
(完了したビルドからスタッシュを保持するか?)
意味: Jenkins Pipelineの stash ステップで一時的に保存されたファイルが、ビルド完了後もJenkinsマスター上に保持されるかどうかを制御します。

使用する状況: ONにする場合: 特定のビルドでstashされたファイルをデバッグや監査目的で後から確認する必要がある、非常に特殊なケースに限られます。

OFFにする場合: ほとんどの場合でOFFにすることが推奨されます。stashされたファイルはディスクスペースを消費するため、不要なデータの蓄積を防ぐために、ビルド完了後に削除するのが一般的です。今回のLaravelテストのパイプラインでは、stashを使用していないため、OFFのままで問題ありません。
Throttle builds?
(ビルドを調整するか?)
意味: このジョブの実行頻度を制限する機能です。特定の期間(例: 1時間、1日)内に許可するビルドの最大数を設定できます。

使用する状況: 外部サービス(APIなど)にレートリミットがあるジョブで、その制限に引っかからないように実行頻度を制限する場合。
ビルドやテストが非常に時間がかかり、大量のリソースを消費するため、サーバーの過負荷を防ぎたい場合。
毎日0時に実行するような定期実行ジョブの場合、Cronトリガーで既に頻度が制御されているため、通常はONにする必要はありません。
ビルドのパラメータ化意味: このチェックボックスをONにすると、ジョブの実行時にユーザーが値を入力できる変数(パラメータ)を定義できるようになります。これにより、同じジョブでも実行するたびに異なる動作をさせることができます。

使用する状況: 環境の切り替え: デプロイ先を開発/ステージング/本番から選択する場合。
特定のバージョン/ブランチの指定: 特定のGitブランチやコミットを指定してビルド/デプロイしたい場合。

オプション機能のON/OFF: テストの種類を選択したり、特定の機能を有効/無効にしたりする場合。
手動実行時などに柔軟性を持たせたい場合に非常に便利です。
古いビルドの破棄意味: 過去のビルド履歴がJenkinsサーバーのディスクスペースを占有するのを防ぐために、古いビルドの履歴を自動的に削除する機能です。

設定項目: Days to keep builds: ビルド履歴を保持する日数。
Max # of builds to keep: 保持するビルド履歴の最大数。

使用する状況: すべてのジョブで有効にすることが強く推奨されます。ビルド履歴は非常に多くのディスクスペースを消費するため、適切に設定しないとJenkinsサーバーのディスクがすぐに満杯になってしまいます。特に、毎日実行されるテストジョブなど、頻繁にビルドされるジョブでは重要です。

設定例: 「Days to keep builds」を 30(1ヶ月)、「Max # of builds to keep」を 50 などに設定し、必要に応じて調整します。

以下のスクショのように設定しておきます。

Triggers」はパイプラインスクリプトに記述するためチェックは無しで構いません。

パイプライン」は以下のように記述するようにします。

pipeline {
    agent any // 任意の利用可能なエージェントで実行

    environment {
        GIT_REPO_URL = 'https://git.XXXX/git/XXXXX/LaravelSample.git' // GitリポジトリがあるURL
        BRANCH = 'main'  // ビルド対象となるブランチ名
        DOCKER_DIR = '.' // docker-compose.yml が存在するディレクトリ
        ENV_FILE = '.env.example' // テストで使用する.envファイル
        LARAVEL_APP_SERVICE_NAME = 'web_app' // dockerのPHPのイメージ名(docker-composeに記述)

        // Docker Compose Override ファイルの定義
        DOCKER_COMPOSE_OVERRIDE_SOURCE = 'docker-compose.override.jenkins.yml'
        DOCKER_COMPOSE_OVERRIDE_TARGET = 'docker-compose.override.yml'
    }

    triggers {
        cron '0 0 * * *' // 毎日夜中0時に実行
    }

    stages {
        stage('Checkout Source Code') {
            steps {
                script {
                    // 最初の環境をクリーンにする
                    echo "Cleaning workspace..."
                    cleanWs()
                    
                    echo "Checking out source code from ${GIT_REPO_URL}..."
                    // Git 認証情報が必要な場合は credentialsId を指定
                    // credentialsId: 'your-git-credentials-id'
                    git url: GIT_REPO_URL, branch: BRANCH
                }
            }
        }

        stage('Prepare Docker Compose Override') {
            steps {
                script {
                    echo "Preparing Docker Compose override file..."
                    dir("${DOCKER_DIR}") { // docker-compose.yml と同じディレクトリで操作
                        if (fileExists(env.DOCKER_COMPOSE_OVERRIDE_SOURCE)) {
                            echo "Copying ${env.DOCKER_COMPOSE_OVERRIDE_SOURCE} to ${env.DOCKER_COMPOSE_OVERRIDE_TARGET}..."
                            sh "cp ${env.DOCKER_COMPOSE_OVERRIDE_SOURCE} ${env.DOCKER_COMPOSE_OVERRIDE_TARGET}"
                        } else {
                            error "Error: ${env.DOCKER_COMPOSE_OVERRIDE_SOURCE} not found in ${env.DOCKER_DIR}."
                        }
                    }
                }
            }
        }

        stage('Build and Start Docker Environment') {
            steps {
                script {
                    echo "Navigating to Docker directory: ${DOCKER_DIR}"
                    dir("${DOCKER_DIR}") {
                        echo "Building and starting Docker containers..."
                        // --build オプションで、コンテナの再ビルドを確実に実行
                        sh "docker-compose up -d --build"
                    }
                }
            }
        }

        stage('Verify Mount and Update Laravel Dependencies') { 
            steps {
                script {
                    echo "Verifying /app mount in ${LARAVEL_APP_SERVICE_NAME} container..."
                    sh "docker exec ${LARAVEL_APP_SERVICE_NAME} ls -la /app/" 

                    echo "Updating Laravel dependencies and optimizing..."
                    sh "docker exec ${LARAVEL_APP_SERVICE_NAME} composer install --no-interaction --prefer-dist"
                    
                    sh "docker exec ${LARAVEL_APP_SERVICE_NAME} cp ${ENV_FILE} .env"
                    
                    sh "docker exec ${LARAVEL_APP_SERVICE_NAME} php artisan key:generate --ansi"
                    
                    sh "docker exec ${LARAVEL_APP_SERVICE_NAME} php artisan config:clear || true" 
                    sh "docker exec ${LARAVEL_APP_SERVICE_NAME} php artisan cache:clear || true" 
                    sh "docker exec ${LARAVEL_APP_SERVICE_NAME} php artisan route:clear || true" 
                    sh "docker exec ${LARAVEL_APP_SERVICE_NAME} php artisan view:clear || true" 
                    sh "docker exec ${LARAVEL_APP_SERVICE_NAME} php artisan optimize:clear || true" 
                }
            }
        }
        
        stage('Run Database Migrations (if needed for tests)') {
            steps {
                script {
                    echo "Running database migrations..."
                    sh "docker exec ${LARAVEL_APP_SERVICE_NAME} php artisan migrate --force"
                }
            }
        }

        stage('Run Laravel Tests') {
            steps {
                script {
                    echo "Running Laravel tests..."
                    sh "docker exec ${LARAVEL_APP_SERVICE_NAME} php artisan test"
                }
            }
        }

        stage('Clean up Docker Environment') {
            steps {
                script {
                    echo "Stopping and removing Docker containers..."
                    dir("${DOCKER_DIR}") {
                        sh "docker-compose down"
                    }
                }
            }
        }
    }

    post {
        always {
            echo "Pipeline finished. Status: ${currentBuild.result}"
        }
        failure {
            echo "Pipeline failed! Check logs for details."
        }
    }
}

長いスクリプトですが、上記で説明した実行手順を踏んでおり1つ1つを見ればわかるかと思います。
「パイプライン」を設定後に「Save」を押下して保存します。

ビルド実行」を押下してパイプラインが実行できるか確認します。

実行が完了すると緑のマークとなり、失敗すると赤いバツのアイコンが表示されます。

これでLaravelのテストまでの設定が完了しました。
またサーバーへのデプロイなども設定ができたら記載していこうと思います。