【Laravel】アプリケーション全体のバックアップを簡単に取れるLaravel-backupの紹介

 この記事は Laravel 10.9.0、Laravel-backup 8.1.9 時点の記事です。

 LaravelはPHPのフレームワークです。よくwebサービスで用いられ、何かしらのデータの永続化機能もよく担当します。そういった永続化されたデータはバックアップを取って置き不測の事態でも被害を抑えるべきです。。Laravel-backupはLaravelにバックアップとリストア機能を簡単に使えられるライブラリです。この記事では Laravel-backup を使ったバックアップの仕方を紹介します。

Introduction | laravel-backup | Spatie

 Laravel-backupのインストールは例によって Composer でできます。これは次のコマンドでできます。

composer require spatie/laravel-backup

 インストールが完了するとbackup:runbackup:cleanbackup:listbackup:monitorといったバックアップ関連の Artisan コマンドが追加されます。もしデータベースが MySQL でアプリケーションを動かしているマシンに mysqldump が備わっているのであれば、この時点でbackup:runを実行するだけでデータベースのバックアップがstorage/app/アプリケーション名/日時.zipに保存されます。

 Laravel-backup の挙動は主に設定ファイルで制御します。設定ファイルは次コマンドの実行で config/backup.php にダンプされます。

php artisan vendor:publish --tag="backup-config"

 設定ファイルはそこそこ大きめの300行弱です。簡易な和訳と多少のカスタマイズを含む設定ファイル例が次です。

<?php

return [
    'backup'          => [
        /*
         * このアプリケーションの名前。この名前はバックアップの監視に使用されます。
         */
        'name'                         => env('APP_NAME', 'laravel-backup'),

        'source'                       => [
            'files'     => [
                /*
                 * バックアップに含めるディレクトリとファイルのリスト。
                 */
                'include'                       => [
                    base_path(),
                ],

                /*
                 * これらのディレクトリとファイルはバックアップから除外されます。
                 *
                 * バックアッププロセスで使用されるディレクトリは自動的に除外されます。
                 * 容量が多く、システムに関わりのないものや生成可能なものは除外しておく方が無難です。
                 */
                'exclude'                       => [
                    base_path('.idea'),
                    base_path('.git'),
                    base_path('vendor'),
                    base_path('node_modules'),
                    base_path('storage/clockwork'),
                    base_path('storage/logs'),
                ],

                /*
                 * シンボリックリンクを辿るかどうかを決定します。
                 */
                'follow_links'                  => false,

                /*
                 * 読み取り不可のフォルダを無視するかどうかを決定します。
                 */
                'ignore_unreadable_directories' => false,

                /*
                 * このパスは、結果として得られるzipファイルのディレクトリを相対的にするために使用されます
                 * 完全な絶対パスを含める場合は、`null`に設定します
                 * 例:base_path()
                 */
                'relative_path'                 => null,
            ],

            /*
             * バックアップするデータベースの接続名
             * MySQL、PostgreSQL、SQLite、Mongoデータベースがサポートされています。
             *
             * config/database.phpの接続設定に'dump'キーを追加することで、
             * 各接続のデータベースダンプの内容をカスタマイズできます。
             * 例:
             * 'mysql' => [
             *       ...
             *      'dump' => [
             *           'excludeTables' => [
             *                'table_to_exclude_from_backup',
             *                'another_table_to_exclude'
             *            ]
             *       ],
             * ],
             *
             * MySQLサーバーでInnoDBテーブルのみを使用している場合は、
             * useSingleTransactionオプションを指定してテーブルのロックを回避できます。
             *
             * 例:
             * 'mysql' => [
             *       ...
             *      'dump' => [
             *           'useSingleTransaction' => true,
             *       ],
             * ],
             *
             * 使用可能なカスタマイズオプションの完全なリストについては、https://github.com/spatie/db-dumperを参照してください。
             */
            'databases' => [
                'mysql'
            ],
        ],

        /*
         * データベースダンプを圧縮してディスク使用量を減らすことができます。
         *
         * Laravel-backupはデフォルトでSpatie\DbDumper\Compressors\GzipCompressor::classを提供しています。
         *
         * カスタム圧縮機能も作成できます。詳細は以下を参照してください:
         * https://github.com/spatie/db-dumper#using-compression
         *
         * 圧縮を無効にする場合は、nullに設定します。
         */
        'database_dump_compressor'     => null,

        /*
         * データベースダンプファイルの拡張子。
         *
         * 指定しない場合、ファイルの拡張子はMongoDBの場合は.archive、その他のデータベースの場合は.sqlになります。
         * ファイル拡張子は先頭の.なしで指定してください。
         */
        'database_dump_file_extension' => '',

        'destination'                  => [
            /*
             * バックアップzipファイルのファイル名前置詞。
             */
            'filename_prefix' => '',

            /*
             * バックアップが保存されるディスク名。
             */
            'disks'           => [
                'local',
            ],
        ],

        /*
         * 一時ファイルが保存されるディレクトリ。
         */
        'temporary_directory'          => storage_path('app/backup-temp'),

        /*
         * アーカイブの暗号化で使用するパスワード。
         * ZIPファイルにパスワードがついたりとかそんな感じです。
         * 暗号化を無効にするには、`null`に設定します。
         */
        'password'                     => env('BACKUP_ARCHIVE_PASSWORD'),

        /*
         * アーカイブの暗号化に使用される暗号化アルゴリズム。
         * `null`または`false`に設定すると、暗号化が無効になります。
         *
         * 'default'に設定すると、システムで利用可能な場合は
         * ZipArchive::EM_AES_256が使用されます。
         */
        'encryption'                   => 'default',
    ],
    /*
     * 特定のイベントが発生した際に、通知を受けることができます。アウトオブザボックスでは、「mail」と「slack」を使用できます。
     * Slackの場合は、laravel/slack-notification-channelをインストールする必要があります。
     *
     * また、独自の通知クラスを使用することもできます。
     * SpatieBackupのクラスは、`SpatieBackupNotifications`です。
     */
    'notifications'   => [
        /*
         * 特定のイベントが発生したときに通知を受け取ることができます。デフォルトでは'mail'と'slack'が使用できます。
         * Slackを使用するには、laravel/slack-notification-channelをインストールする必要があります。
         *
         * 独自の通知クラスも使用できます。ただし、クラス名は
         * `Spatie\Backup\Notifications\Notifications`クラスのいずれかに基づいている必要があります。
         */
        'notifications' => [
            // 失敗パターン
            \Spatie\Backup\Notifications\Notifications\BackupHasFailedNotification::class         => ['slack'],
            \Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFoundNotification::class => ['slack'],
            \Spatie\Backup\Notifications\Notifications\CleanupHasFailedNotification::class        => ['slack'],
            // 成功パターン
            \Spatie\Backup\Notifications\Notifications\BackupWasSuccessfulNotification::class     => ['mail'],
            \Spatie\Backup\Notifications\Notifications\HealthyBackupWasFoundNotification::class   => ['mail'],
            \Spatie\Backup\Notifications\Notifications\CleanupWasSuccessfulNotification::class    => ['mail'],
        ],

        /*
         * ここで、通知が送信されるべき通知対象を指定できます。デフォルトの通知対象は
         * この設定ファイルで指定された変数を使用します。
         */
        'notifiable'    => \Spatie\Backup\Notifications\Notifiable::class,

        'mail'          => [
            'to'   => 'your@example.com',

            'from' => [
                'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
                'name'    => env('MAIL_FROM_NAME', 'Example'),
            ],
        ],

        'slack'         => [
            'webhook_url' => env('NOTIFY_SLACK_WEBHOOK_URL'),

            /*
             * これがnullに設定されている場合、Webhookのデフォルトチャンネルが使用されます。
             */
            'channel'     => null,

            'username'    => null,

            'icon'        => null,
        ],

        'discord'       => [
            'webhook_url' => '',

            /*
             * これが空文字列の場合、Webhookの名前フィールドが使用されます。
             */
            'username'    => '',

            /*
             * これが空文字列の場合、Webhookのアバターが使用されます。
             */
            'avatar_url'  => '',
        ],
    ],
    /*
     * ここで、監視対象のバックアップを指定できます。
     * バックアップが指定された要件を満たさない場合、
     * UnHealthyBackupWasFoundイベントが発生します。
     */
    'monitor_backups' => [
        [
            'name'          => env('APP_NAME', 'laravel-backup'),
            'disks'         => ['local'],
            'health_checks' => [
                \Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays::class          => 1,
                \Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes::class => 5000,
            ],
        ],

        /*
        [
            'name' => 'name of the second app',
            'disks' => ['local', 's3'],
            'health_checks' => [
                \Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays::class => 1,
                \Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes::class => 5000,
            ],
        ],
        */
    ],

    'cleanup'         => [
        /*
         * 古いバックアップをクリーンアップするために使用されるストラテジー。
         * デフォルトのストラテジーでは、一定期間すべてのバックアップを保持します。
         * その期間が経過した後、日次バックアップのみが保持されます。その後、週次バックアップのみが保持され、以下同様です。
         *
         * デフォルトのストラテジーでは、最新のバックアップは常に削除されません。
         */
        'strategy'         => \Spatie\Backup\Tasks\Cleanup\Strategies\DefaultStrategy::class,

        'default_strategy' => [
            /*
             * バックアップを保持する日数。
             */
            'keep_all_backups_for_days'                            => 7,

            /*
             * 日次バックアップを保持する日数。
             */
            'keep_daily_backups_for_days'                          => 16,

            /*
             * 週次バックアップを保持する週数。
             */
            'keep_weekly_backups_for_weeks'                        => 8,

            /*
             * 月次バックアップを保持する月数。
             */
            'keep_monthly_backups_for_months'                      => 4,

            /*
             * 年次バックアップを保持する年数。
             */
            'keep_yearly_backups_for_years'                        => 2,

            /*
             * バックアップをクリーンアップした後、
             * このメガバイト数に達するまで最古のバックアップを削除します。
             */
            'delete_oldest_backups_when_using_more_megabytes_than' => 5000,
        ],
    ],
];

 データベースのバックアップはサービスによってはデータベース単体で行った方が経済的やもしれませんが、小さなデータベースであればアプリケーションでまとめて処理しても問題ないです。Laravel-backupは storage 以下とソースコードとデータベースなどのサービスを構成する全てを一つにまとめてバックアップするのが楽です。こうすると取り回しが楽です。一方でサービス全体を構成するデータやプログラムの容量が大きかったりバックアップ頻度に差をつけたりしたい場合にはこのオールインワン的なやり方は向きません。
 config/backup.php とは別に config/database.php に次の様にデータベースをダンプする時のオプションを設定でき、mysqldump であればこれでテーブルのロックを回避できます。もしテーブルのロックを回避できずバックアップが長時間に及ぶ場合、サービスの一部が落ちたような挙動になってしまいます。

    'connections' => [
        'mysql'  => [
            'driver'         => 'mysql',
            'url'            => env('DATABASE_URL'),
            
            /** 省略 **/
            
            // mysqldump をする時にテーブルのロックを回避する
            'dump'           => [
                'useSingleTransaction' => true,
            ]
        ],

 やりたいようにbackup:runできる様になったら定期実行を組み込んで完成です。app/Console/Kernel.phpファイルのscheduleメソッドに次の様なコードを追加することで自動実行になります。

class Kernel extends ConsoleKernel
{
    protected function schedule(Schedule $schedule): void
    {
            // 失敗しても対応するのがそれほど辛くなく、ユーザーがあまりサービスを利用していない時間帯が狙い目です
            $schedule->command('backup:run')->dailyAt('05:00');
            // バックアップが貯まりすぎたら掃除して容量を空けます
            $schedule->command('backup:clean')->dailyAt('06:00');
    }
}

 バックアップファイルは次の様な構造で保存されます。

.
├─db-dumps
│      mysql-データベース名.sql
│
└─ソースコードまでの絶対パス(ここは設定ファイルで変えられます)
        └アプリケーションのルートディレクトリ
            │  .env
            │  # 色々
            │
            ├─app
            │  ├─色々
            # 色々
            ├─storage
            │  ├─app
            │  │  │  .gitignore
            │  │  │

 復元の際には sql ファイルの実行ででデータベース全体の復元、アプリケーション内のファイル全体による上書きでユーザーが保存したファイルなどの復元などをする感じです。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG