Laravelで簡単にマルチテナント環境を構築できるhyn/multi-tenantパッケージを利用して、マルチテナント環境を構築する手順を解説しています。

hyn/multi-tenantパッケージでは1つのテナントに対して1つのデータベースが割り当てられます。テナントの追加時にデータベースを自動で作成してくれるのでこちらが何か処理を加えることはありません。

各テナントにはサブドメインが割り当てられます。Laravelをインストールするサーバのドメインがexample.comでテナント名にtestとつけたら、test.example.comでアクセスすることができます。

データベースが個別に存在するのでユーザもテナント毎に独立して管理することができます。

Laravelのコードは1つなので、複数のテナントで1つのコードを共有することになります。

公式をマニュアルを参考にしていますが、マニュアルは構築の流れを一貫して説明しているものではないので、参考にしたページも合わせて載せていきます。

構築環境

MACを利用して動作確認を行なっています。PHPは7.2以上でないとhyn/multi-tenantパッケージがインストールできません。Laravelは5.8、データベースにはMySQL5.7を使用しています。

マルチテナントを実現するHynのバージョンは5.4です。

Laravelのインストール

composerを使用してLaravelのインストールを行います。通常のLaravelインストールと同じです。


$ composer create-project --prefer-dist laravel/laravel tenancy

インストレーション

インストールに関しては、マニュアルページのInstallationをクリックして表示される手順通りに実行してください。

Hyn5.4 Installation
Hyn5.4 Installation

データベースの設定

mysqlコマンドでMySQLに接続します。本環境ではrootユーザを使いパスワードを設定していないため下記のコマンドで接続できます。接続するユーザとパスワードは各環境に合わせてください。


$ mysql -u root

データベースの作成

tenancyデータベースを作成します。このtenancyデータベースにはテナント名やテナントの識別子などテナントの管理情報を保存します。


mysql> CREATE DATABASE IF NOT EXISTS tenancy;
Query OK, 1 row affected (0.04 sec)

ユーザの作成

MySQLへの接続用にユーザの作成を行います。名前はtenancyでパスワードは任意のものを設定してください。ここではpasswordを設定しています。


mysql> CREATE USER IF NOT EXISTS tenancy@localhost IDENTIFIED BY 'password';
Query OK, 0 rows affected (0.07 sec)

MySQLユーザへの権限の付与

テナントごとにデータベースの作成するためデータベースの作成権限も必要となります。ここではALL PRIVILEGES権限をWITH GRANT OPTIONで与えています。


mysql> GRANT ALL PRIVILEGES ON *.* TO tenancy@localhost WITH GRANT OPTION;
Query OK, 0 rows affected (0.03 sec)

database.phpファイルへの接続情報の追加

tenacyデータベース用の独自のsystemを追加します。パスワードは作成したtenancyのパスワードを設定してください。


    'connections' => [
        'system' => [
            'driver' => 'mysql',
            'host' => env('TENANCY_HOST', '127.0.0.1'),
            'port' => env('TENANCY_PORT', '3306'),
            'database' => env('TENANCY_DATABASE', 'tenancy'),
            'username' => env('TENANCY_USERNAME', 'tenancy'),
            'password' => env('TENANCY_PASSWORD', 'password'),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'strict' => true,
            'engine' => null,
        ],

上記の設定だけではテナント毎に作成されるデータベースへの接続情報が不足していますが、テナント毎のConnectionについては自動で設定されます。そのため、テナント用の設定は必要ありません。テナントデータベースへの接続については、マニュアルの以下の文章に記述されています。

There is no need to configure thetenantconnection in the database.php configuration file. This connection is set up automatically during runtime.

.envファイルからDBに関する接続の情報を削除します。また、database.phpファイルを開いて、DB_CONNECTIONの設定をsystemに変更します。


'default' => env('DB_CONNECTION', 'system'),

パッケージのインストール

composerコマンドを使ったパッケージのインストールを行います。


$ composer require "hyn/multi-tenant:5.4.*"

multi-tenantパッケージで利用する設定ファイルの作成を行うためにphp arisan vendor:publishコマンドを実行します。設定ファイルのtenancy.phpやマイグレーションファイルが作成されます。


$ php artisan vendor:publish --tag=tenancy
Copied Directory [/vendor/hyn/multi-tenant/assets/migrations] To [/database/migrations]
Copied File [/vendor/hyn/multi-tenant/assets/configs/tenancy.php] To [/config/tenancy.php]
Copied File [/vendor/hyn/multi-tenant/assets/configs/webserver.php] To [/config/webserver.php]
Publishing complete.

マイグレーションファイルが作成されたので、php artisan migrateコマンドでテーブルの作成を行います。


$ php artisan migrate --database=system
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.19 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0.17 seconds)
Migrating: 2017_01_01_000003_tenancy_websites
Migrated:  2017_01_01_000003_tenancy_websites (0.08 seconds)
Migrating: 2017_01_01_000005_tenancy_hostnames
Migrated:  2017_01_01_000005_tenancy_hostnames (0.32 seconds)
Migrating: 2018_04_06_000001_tenancy_websites_needs_db_host
Migrated:  2018_04_06_000001_tenancy_websites_needs_db_host (0.09 seconds)

テーブル作成後にデータベースの管理ツールであるTablePlusを使ってMySQLのtenantデータベースにアクセスするとhostnamesテーブルとwebsitesテーブルが作成されていることが確認できます。

作成されたテーブルの確認
作成されたテーブルの確認

ここまでで初期設定は完了です。

テナントの作成

テナント作成時のテーブル

テナントを作成すると同時にデータベースにテーブルが作成されますがテーブルを作成するためには前準備が必要となります。

テナント用のテーブルを作成するマイグレーションファイルはデフォルトではmigrations/tenant以下に保存する必要があります。

場所についてはマニュアルのMigrationsに記載されています。

テナント用のマイグレーションファイルの保管場所
テナント用のマイグレーションファイルの保管場所

設定ファイルのtenancy.phpファイルを確認するとmigrations/tenantに設定されています。また設定の上部にはこのパラメータについての説明が記述されています。


/**
 * The tenant migrations to be run during creation of a tenant. Specify a directory
 * to run the migrations from. If specified these migrations will be executed
 * whenever a new tenant is created.
 *
 * @info set to false to disable auto migrating.
 *
 * @warn this has to be an absolute path, feel free to use helper methods like
 * base_path() or database_path() to set this up.
 */
'tenant-migrations-path' => database_path('migrations/tenant'),
コメントについては要約するとディレクトリを指定すれば新しいテナントが作成される時にマイグレーションが実行されます。自動でマイグレーションを行わない場合はfalseを設定します。と記述されています。

このtenantディレクトリはパッケージをインストールしても自動で存在されないので作成する必要があります。また各データベースでユーザ管理を行う場合は、usersとpassword_resetsのテーブルもこのディレクトリにコピーします。


$ cd database/migrations
$ mkdir tenant
$ cp 2014_01_01_00000* tenant

MySQLの文字制限の問題

テナントを作成する場合は、各テナントにユニークなIDが自動的に振られます。このUUID(Unique User ID)はデータベースのユーザとデータベース名に使用されます。

UUIDの説明
UUIDの説明

しかし、MySQLでデフォルトのランダムなUUIDで作成しようとすると32文字制限によって下記のエラーが発生します。

SQLSTATE[HY000]: General error: 1470 String ‘cb0ec8cb-73fe-4907-b53d-1e44340a699d’ is too long for user name (should be no longer than 32) (SQL: CREATE USER IF NOT EXISTS `cb0ec8cb-73fe-4907-b53d-1e44340a699d`@’127.0.0.1′ IDENTIFIED BY ‘424eada65b8631c43377933cdf5d1166’)

上記の要件については、マニュアルの必要要件に記述されています。

MySQLの文字制限について
MySQLの文字制限について

tanacy.phpファイル内で、uuid-limit-length-to-32の設定値を変更する必要があります。


/**
 * Enable this flag in case you're using a driver that does not support
 * database username or database name with a length of more than 32 characters.
 *
 * This should be enabled for MySQL, but not for MariaDB and PostgreSQL.
 */
'uuid-limit-length-to-32' => env('LIMIT_UUID_LENGTH_32', true),
32文字以上を持つユーザ名とデータベース名をサポートしないドライバーを使っている場合はenable(=true)に。MySQLはenable(=true)にしなければなりません。

テナント作成のコード

必要な設定は完了したので、テナントの作成を行います。テナントの作成についてはマニュアルのCreating tenantsに記述されています。

テナントの作成ページ
テナントの作成ページ

上記のそのままコピーしてweb.phpファイルにペーストします。fqdnの箇所のみ変更を行い、testをテナント名として作成します。


use Hyn\Tenancy\Models\Hostname;
use Hyn\Tenancy\Contracts\Repositories\HostnameRepository;
use Hyn\Tenancy\Models\Website;
use Hyn\Tenancy\Contracts\Repositories\WebsiteRepository;

Route::get('create_tenant',function(){

	$website = new Website;

	app(WebsiteRepository::class)->create($website);

	$hostname = new Hostname;

	$hostname->fqdn = 'test.localhost';

	$hostname = app(HostnameRepository::class)->create($hostname);

	app(HostnameRepository::class)->attach($hostname, $website);

	return redirect('/');

});

開発サーバをphp artisan serveコマンドで起動します。


$ php artisan serve
Laravel development server started: 

ブラウザからhttp://localhost:8000/create_tenantにアクセスすると新規のtenantが追加されます。

TablePlusでhostnamesとwebsitesテーブルの中身を確認します。

hostnamesテーブルには、fqdnのtest.localhostが追加されていることが確認できます。

hostnamesテーブルを確認
hostnamesテーブルを確認

websitesテーブルを確認するとUUIDを確認することができます。

websitesテーブルを確認
websitesテーブルを確認

このUUIDがデータベース名になっているので、MySQL上にこのUUIDの名前がついたデータベースが作成されていることを確認し接続します。usersテーブルとpassword_resetsテーブルが作成されていることが確認できます。

テナントデータベースへのアクセス
テナントデータベースへのアクセス

もう一つluceosでテナントを作成してみましょう。web.phpのfqdnで設定する値を変更するだけです。


$hostname->fqdn = 'luceos.localhost';

作成すれば同じようにデータベースが作成されます。

テナントへの接続

ブラウザを起動して、作成したluceosのURL(luceos.localhost:8000)でアクセスを行なってみましょう。Laravelのページが表示されることを確認することができます。

テナントへのアクセス
テナントへのアクセス

ユーザの登録

テナントごとにユーザ登録ができるのか確認するためにユーザ認証機能を追加します。


$ php artisan make:auth

各テナントのデータベースに接続するためにUserモデルにUsesTenantConnectionトレイトを追加する必要があります。


use Hyn\Tenancy\Traits\UsesTenantConnection;

class User extends Authenticatable
{
    use Notifiable,UsesTenantConnection;

トレイトについてはマニュアルのModelsに記述されています。

ユーザモデルへのTraitの追加
ユーザモデルへのTraitの追加

もしトレイトを忘れてユーザを登録を行なった場合は、system経由でデータベースに接続が行われるため、tenancyデータベースにusersテーブルを作成していればそこにユーザが作成されます。(本文書でもphp artisan migrate –database=systemを実行した時usersテーブルを作成しているので、その場合はそのテーブルにユーザが作成されます)usersテーブルがtenancyデータベースになければエラーが出力されます。

テナントluceosでユーザ登録を行います。

luceosのユーザ登録が画面
luceosのユーザ登録が画面

luceosのデータベースを確認すると登録したユーザが作成されていることを確認することができます。testのデータベースのusersテーブルをみても作成したユーザ情報はありません。

luceosでユーザ登録
luceosでユーザ登録

テナントtestでも同様にユーザ作成を行なってください。テナントごとにユーザを作成することができます。

まとめ

本文書ではマルチテナントを作成し、それぞれのデータベースのuserテーブルにユーザが登録するところまで確認することができました。実際にこのパッケージを使ってマルチテナントを作成するためには、テナントを作成するための処理やユーザのアクセス権限などいろいろな設定を行なっていく必要があります。