はじめに
先日、こちらの記事でLaravelの学習環境を構築しました。しかし、MySQLのバージョン8を使用したとはいえ、いつもMySQLばかりでは面白くないので、今回はこの環境をPostgreSQLで作り直してみたいと思います。MySQLとは違って、PostgreSQLでDBサーバーを構築する時はひと手間工夫が必要でしたので、しっかり覚書していきたいと思います。
また、下記の記事でも書きましたが、私の開発環境はParallels上のUbuntu Desktop仮想マシンに全て寄せており、ローカルマシンにはVSCodeしかインストールしていません。今回もこのスタンスを維持する為にDBクライアントツールはいつもの"SQLToos"拡張機能を使うつもりでしたが、PostgreSQLにはpgAdminという素晴らしいツールがある事を知りましたので、これをサーバーモード(Webブラウザで利用できるモード)で用意したいと思います。WordPressのようにMySQLを使わないといけない理由が特にない限り、私にはPostgreSQLが合っているのかもしれないと思った次第です。
2022/05/01 追記
本記事で紹介したLaravel学習環境の構築に関して、Laravelトップページの表示確認まではOKでしたが、その後、自分で作成した画面を表示しようとしても404 Page Not Foundになってしまう現象を確認しました。その後の調査で原因が分かり、一部定義を追加・変更しましたので追記します。追記箇所には日付入りで赤の補足説明を入れておきます。
環境情報
前述のLaravel学習環境の構築記事で載せた図を再掲します。今回の記事では、図の赤枠部分を構築し直します。図ではコンテナ3はコンテナが1つ構成に見えますが、実際にはコンテナは3つ構成(APPサーバーとDBサーバー、そしてpgAdminサーバー)になる予定です。
環境構築手順
今回の記事ではMySQLをPostgreSQLに変更する事で生じた変更点を中心に記述したいと思います。その為、前述のLaravel学習環境の構築記事と重複する部分は記載を流用するか割愛させて頂く事が多々ありますが、その点はご了承頂ければと思います。
事前に色々と調査・検討した結果、次のような構成で環境構築したいと思います。
- APPサーバーは、Apache + PHPのDockerイメージを基に、Composerを追加インストールしてコンテナ作成
- DBサーバーは、PostgreSQLのDockerイメージを基にコンテナ作成
- pgAdminサーバーは、pgAdminのDockerイメージを基にコンテナ作成
- APPサーバーのコンテナにアクセスし、Composerコマンドを使用してLaravelプロジェクトを作成
- APPサーバーのコンテナにアクセスし、LaravelからPostgreSQLへのDB接続設定を行う
上記まで作業を終え、Laravelのトップページが表示される事、LaravelからPostgreSQLへDB接続できる事、そしてpgAdminのトップページが表示されて無事ログインしてPostgreSQLのデータベース内容の確認が出来れば、今回の目的を達成した事にしたいと思います。
各種定義ファイルの作成
今回作成する各種定義ファイルに関しては、MySQLからPostgreSQLにする為に定義を追加・変更した部分を中心に解説したいと思います。尚、pgAdminの使い方に関しては、この記事では特に深掘りしません。ご了承下さい。
まずは定義ファイルを配置したツリー構造です。
今回、MySQLからPostgreSQLにするにあたって追加・変更になった部分は赤枠部分となります。
バインドマウントでPostgreSQLとpgAdminのデータ永続化用に用意したディレクトリが追加・変更になっています。
また、PostgreSQLはMySQLと違って、docker-compose.yml上ではスーパーユーザーしか定義できません。その為、作業用ユーザーを作成したい場合には、別途シェルスクリプトを用意する必要があります。この点に関しては後述します。
ちなみに、data/pgadminディレクトリは所有権を5050:5050で作成して下さい。詳細は後述しますが、そうしないとpgAdminのインストールに失敗します。
2022/05/01追記
上記画像の定義ファイルを配置したツリー構造に対して、以下を追加・変更しました。
- 追加 → build/app/000-default.conf
- 変更 → build/app/Dockerfile
それでは各種定義ファイルの中身を見ていきます。
devcontainer.json
今回は"SQLTools"拡張機能ではなく、pgAdminサーバーを立てますので、extensionsから"SQLTools"拡張機能の記述を削除しました。
{
"name": "laravel",
"dockerComposeFile": "../docker-compose.yml",
"service": "app",
"workspaceFolder": "/var/www/app",
"extensions": [
"bmewburn.vscode-intelephense-client"
],
"remoteUser": "${localEnv:USER}"
}
2022/05/01追記
000-default.confを追加定義しましたので、その内容を追記します。
000-default.conf
apacheの元々の定義を基にして追記しましたので、余計なコメントが含まれていますが、コメント部分は無くても大丈夫です。
DocumentRootの箇所は元々Dockerfile(Appサーバー用)にて、sedコマンドで記述を書き換えていましたが、今回の問題で他にも追記しないといけなくなったので、confファイルを置き換える方式に変更しました。
肝心の問題箇所は、Directoryディレクティブ部分です。この定義が無いとドキュメントルート配下にあるLaravel用の.htaccessファイルが読み込まれない状態となっており、これが本事象の原因の1つとなっていました。
<VirtualHost *:80>
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
# specifies what hostname must appear in the request's Host: header to
# match this virtual host. For the default virtual host (this file) this
# value is not decisive as it is used as a last resort host regardless.
# However, you must set it for any further virtual host explicitly.
#ServerName www.example.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/app/public
<Directory /var/www/app/public/>
AllowOverride All
</Directory>
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
</VirtualHost>
Dockerfile(APPサーバー用)
18〜19行目のRUNの内容が変更になりました。まず、19行目でPostgreSQL用PDO(pdo_pgsql)をインストールするようにしました。更に、18行目でlibpq-devを追加でインストールするようにしました。pdo_pgsqlはlibpq-devに依存しているようです。こちらの方の記事が大変参考になりました。
2022/05/01 追記
下記にDockerfile(APPサーバー用)の変更内容を記載します。
sedコマンドの実行箇所は、000-default.conf自体を直接置き換えるようにしました。000-default.confの記載内容は前述の通りです。
後、a2enmodというモジュールが有効になっていない事も原因の1つでしたので、その記述を追加しました。
FROM php:8.1-apache
ARG USER_UID
ARG USER_NAME
ARG GROUP_GID
ARG GROUP_NAME
RUN groupadd -g ${GROUP_GID} ${GROUP_NAME} \
&& useradd -m -s /bin/bash -u ${USER_UID} -g ${GROUP_GID} ${USER_NAME} \
&& apt-get update \
&& apt-get install -y sudo \
&& echo "${USER_NAME} ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/${USER_NAME} \
&& chmod 0440 /etc/sudoers.d/${USER_NAME}
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN apt-get update \
&& apt-get install -y git libpq-dev \
&& docker-php-ext-install pdo_pgsql
#2022/05/01 mod
#RUN sed -i 's!/var/www/html!/var/www/app/public!g' /etc/apache2/sites-available/000-default.conf
ADD 000-default.conf /etc/apache2/sites-available/
RUN a2enmod rewrite
USER ${USER_NAME}
Dockerfile(DBサーバー用)
1行目が変更になり、15〜16行目が追加になりました。
1行目では使用するDockerイメージをPostgreSQLのバージョン14.2(2022/04/30時点の最新バージョン)にしています。
15〜16行目では、DBサーバーOSのロケールを日本語_日本、文字コードをUTF-8に変更し、環境変数に設定しています。こうする事で、PostgreSQLが日本語_日本、UTF-8に対応し、OS上でのコマンド応答メッセージ等も日本語表記になります。こちらのURL(PostgreSQLの公式Dockerイメージ)の「Locale Customization」という見出しで説明がされています。
FROM postgres:14.2
ARG USER_UID
ARG USER_NAME
ARG GROUP_GID
ARG GROUP_NAME
RUN groupadd -g ${GROUP_GID} ${GROUP_NAME} \
&& useradd -m -s /bin/bash -u ${USER_UID} -g ${GROUP_GID} ${USER_NAME} \
&& apt-get update \
&& apt-get install -y sudo \
&& echo "${USER_NAME} ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/${USER_NAME} \
&& chmod 0440 /etc/sudoers.d/${USER_NAME}
RUN localedef -i ja_JP -c -f UTF-8 -A /usr/share/locale/locale.alias ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
create_normal_user.sh
こちらは新たに作成したシェルスクリプトです。
ここでは、psqlコマンドを使用してDBサーバーコンテナのPostgreSQLに作業用ユーザーの追加および権限付与をしています。ここで使用している環境変数は、後述のdocker-compose.ymlのdbコンテナ部分で定義している環境変数経由で.envの定義内容を間接的に参照しています。シェルスクリプト自体はシンプルな内容です。
どちらかというと、ここで大切なのはこのシェルスクリプトがDBサーバーコンテナの/docker-entrypoint-initdb.dディレクトリとバインドマウントしていて、コンテナ内にこのシェルスクリプトを配置しているという事です。PostgreSQLのDockerイメージは、初回のコンテナ生成後のみ(まだデータベースが作成されていない状態の時のみ)に/docker-entrypoint-initdb.dディレクトリ内のシェルスクリプトを実行してくれるような設計になっています。この仕組みを利用して、今回私はPostgreSQLに作業用ユーザーを追加するようにしました。前述したPostgreSQLの公式Dockerイメージの「Initialization scripts」という見出しで説明がされています。念押しで記載しますが、PostgreSQLのデータベースをバインドマウンド等で永続化している場合には、一度データベースを消さない限り、コンテナ再生成してもこのシェルスクリプトは実行されませんので、その点をご認識頂ければと思います。
#!/bin/bash
set -e
psql --username ${POSTGRES_USER} --dbname ${POSTGRES_DB} <<-EOSQL
CREATE USER ${POSTGRES_NORMAL_USER} WITH PASSWORD '${POSTGRES_NORMAL_PASSWORD}';
GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO ${POSTGRES_NORMAL_USER};
EOSQL
.env
こちらは色々と定義を追加しました。
- Docker環境外からpgAdminへWebブラウザでアクセスできるようにする為、DBTOOL_FORWARD_PORTを新たに定義
- PostgreSQLのスーパーユーザー名を独自の名前にしたかった為、DB_ROOT_USERを新たに定義
- PostgreSQLの作業用ユーザーを追加する為、DB_USERを新たに定義
- PostgreSQLの作業用ユーザーを追加する為、DB_PASSWORDを新たに定義
PostgreSQLのDBサーバーコンテナ生成時に必須なのはDB_ROOT_PASSWORDだけです。DB_ROOT_USER、DB_DATABASEは定義を省略できます。省略するとデフォルト名が使用されるようです。前述したPostgreSQLの公式Dockerイメージの「Environment Variables」という見出しで説明がされています。
また、DB_USERとDB_PASSWORDは、コンテナ生成時にPostgreSQLの作業用ユーザーも追加したかった為、私が勝手に定義しました。前述のcrete_normal_user.shで使用しています。コンテナ生成後に自分で追加するという方はスクリプトファイルも含め定義不要です。
APP_FORWARD_PORT={WebブラウザからのAPPサーバーアクセス用フォワードポート}
DBTOOL_FORWARD_PORT={WebブラウザからのpgAdminサーバーアクセス用フォワードポート}
DB_ROOT_USER={PostgreSQLのスーパーユーザー名}
DB_ROOT_PASSWORD={PostgreSQLのスーパーユーザーパスワード}
DB_DATABASE={PostgreSQLのLaravel学習用のデータベース名}
DB_USER={PostgreSQLの作業用ユーザー名}
DB_PASSWORD={PostgreSQLの作業用ユーザーパスワード}
DBTOOL_LOGIN_EMAIL={pgAdminのログインメールアドレス}
DBTOOL_LOGIN_PASSWORD={pgAdminのログインパスワード}
USER_UID={DockerホストOSの一般ユーザーのUID}
USER_NAME={DockerホストOSの一般ユーザーのユーザー名}
GROUP_GID={DockerホストOSの一般ユーザーのGID}
GROUP_NAME={DockerホストOSの一般ユーザーのグループ名}
docker-compose.yml
まず、dbコンテナの定義部分に関してです。
volumesでバインドマウント定義を追加・変更しました。1つはPostgreSQLデータベース用の定義に変更。もう1つは前述のシェルスクリプト配置用の定義を追加。
それから、environmentではPostgreSQLデータベースの生成に必要な各種情報を環境変数に追加・変更定義しました。
appコンテナの定義部分は変更ありません。
次に、pgadminコンテナの定義部分に関してです。
ここは今回新規追加した定義です。
pgadminイメージに関しては、特にカスタマイズする必要が無かった為、Dockerfileは別途定義せず、imageで直接pgAdminの最新イメージを指定しています。
volumesではpgAdminの設定内容が永続化されるようにバインドマウントしています。
pgAdminはサーバーモードでインストールしますので、WebブラウザからpgAdminへアクセスできるように、portsでポートフォワードを設定しています。
environmentでは、pgAdminのログイン情報を設定しています。PostgreSQLデータベースへの接続情報ではありませんのでご注意下さい。
version: '3'
services:
db:
build:
context: ./build/db/
dockerfile: Dockerfile
args:
USER_UID: ${USER_UID}
USER_NAME: ${USER_NAME}
GROUP_GID: ${GROUP_GID}
GROUP_NAME: ${GROUP_NAME}
container_name: laravel_db
hostname: laravel_db
networks:
- net
volumes:
- ./data/postgres:/var/lib/postgresql/data
- ./script/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
restart: always
environment:
TZ: Asia/Tokyo
POSTGRES_USER: ${DB_ROOT_USER}
POSTGRES_PASSWORD: ${DB_ROOT_PASSWORD}
POSTGRES_DB: ${DB_DATABASE}
POSTGRES_NORMAL_USER: ${DB_USER}
POSTGRES_NORMAL_PASSWORD: ${DB_PASSWORD}
app:
depends_on:
- db
build:
context: ./build/app/
dockerfile: Dockerfile
args:
USER_UID: ${USER_UID}
USER_NAME: ${USER_NAME}
GROUP_GID: ${GROUP_GID}
GROUP_NAME: ${GROUP_NAME}
container_name: laravel_app
hostname: laravel_app
networks:
- net
volumes:
- ./data/app:/var/www/app
ports:
- ${APP_FORWARD_PORT}:80
restart: always
environment:
TZ: Asia/Tokyo
pgadmin:
depends_on:
- db
image: dpage/pgadmin4:latest
container_name: laravel_pgadmin
hostname: laravel_pgadmin
networks:
- net
volumes:
- ./data/pgadmin:/var/lib/pgadmin
ports:
- ${DBTOOL_FORWARD_PORT}:80
restart: always
environment:
TZ: Asia/Tokyo
PGADMIN_DEFAULT_EMAIL: ${DBTOOL_LOGIN_EMAIL}
PGADMIN_DEFAULT_PASSWORD: ${DBTOOL_LOGIN_PASSWORD}
networks:
net:
name: laravel_net
コンテナの生成
各種定義ファイルの作成が完了したので、コンテナの生成をします。
今回もVSCodeの[Compose Up]メニューを使用します。
VSCodeの左側タブで"エクスプローラー"タブに切り替え、docker-compose.ymlファイルを右クリックし、[Compose Up]を選択。すると、コンテナの生成が始まり、進捗状況がターミナルウィンドウに表示されます。終わるまで待ちます。
ターミナルウィンドウにエラー表示なくコンテナの生成が終わったら、無事コンテナが生成されているか確認してみましょう。
VSCodeの左側タブで"Docker"タブに切り替えます。カスタムイメージとコンテナとネットワークが生成され、コンテナが起動しています。
Laravelプロジェクトの生成
それでは、Laravelプロジェクトの生成をしたいと思います。
まず、VSCodeでリモートコンテナにアクセスします。
VSCodeの画面左下[><]→[Reopen in Container]を選択。コンテナ内に入れます。
VSCodeの画面左下[><]より、Laravelコンテナに入れた事が確認できます。ただし、まだLaravelプロジェクトは生成していませんので、エクスプローラーは空の状態です。
VSCodeでターミナルウィンドウを開きます。カレントディレクトリが"/var/www/app"になっている事を確認して下さい。
リモートコンテナへアクセス時は、カレントディレクトリが"/var/www/app"となるようにdevcontainer.jsonで定義しています。もし、カレントディレクトリが異なる場合には、"/var/www/app"ディレクトリに移動してから次の手順を実行して下さい。
VSCodeのターミナルウィンドウで、次のコマンドを実行します。Composer経由でLaravelプロジェクトが生成されます。結構時間がかかりますので、のんびりと待ちましょう。
$ composer create-project laravel/laravel . --prefer-dist
Laravelプロジェクト生成完了時、最後に下記の確認をされます。恐らく、Laravel自体のGit履歴情報が含まれているので、それを削除するかどうかという事だと思います。私は不要でしたので、'Y'をタイプしました。Gitリポジトリでの管理はDocker関連の各種定義ファイルと一緒に自分で管理したい為。
Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? Y
ターミナルウィンドウにエラー表示なくLaravelプロジェクトの生成が終わったら、Laravelがちゃんと動作しているか確認してみましょう。ブラウザを起動し、下記形式のURLへアクセスして下さい。
http://{APPサーバーのIPアドレスまたはホスト名}:{.envファイルのAPP_FORWARD_PORTの設定値}/
下記画像のようなLaravelのデフォルト画面が表示されればOKです。
LaravelからPostgreSQLへのDB接続設定
Laravelの環境情報設定ファイル(.env)にPostgreSQLへのDB接続情報を設定します。
設定値は、前述したコンテナ生成用の.envファイルを基にして下さい。
:
(中略)
:
DB_CONNECTION=pgsql
DB_HOST=db
DB_PORT=5432
DB_DATABASE={コンテナ生成用.envファイルのDB_DATABASEの設定値}
DB_USERNAME={コンテナ生成用.envファイルのDB_USERの設定値}
DB_PASSWORD={コンテナ生成用.envファイルのDB_PASSWORDの設定値}
:
(中略)
:
設定できたら、下記コマンドを実行して、データベースをマイグレーションします。
下記のような実行結果が表示されれば、LaravelからPostgreSQLへのDB接続は成功しています。
$ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_000000_create_users_table (5.94ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated: 2014_10_12_100000_create_password_resets_table (2.73ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated: 2019_08_19_000000_create_failed_jobs_table (3.79ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated: 2019_12_14_000001_create_personal_access_tokens_table (5.69ms)
MySQLでLaravel学習環境を構築した時の記事にも書きましたが、Laravelの.envファイルのDB_PASSWORD設定値に記号を含めていると、下記エラーとなりマイグレーションに失敗します。
SQLSTATE[HY000] [1045] Access denied for user
もし、パスワードに記号を含んでいる場合には、設定値をダブルクォーテーションで括る必要があります。皆様、お気を付け下さい。
マイクレーションに成功したら、pgAdminでデータベースの中身を除いてみましょう。
まず、下記形式のURLへアクセスして下さい。
http://{APPサーバーのIPアドレスまたはホスト名}:{.envファイルのDBTOOL_FORWARD_PORTの設定値}/
下記画像のようなpgAdminのログイン画面が表示されます。
言語で"Japanese"を選択し、Email AddressおよびPasswordを入力して、[Login]ボタンをクリックして下さい。Email AddressおよびPasswordには下記を入力して下さい。
Email Address:{コンテナ生成用.envファイルのDBTOOL_LOGIN_EMAILの設定値}
Email Password:{コンテナ生成用.envファイルのDBTOOL_LOGIN_PASSWORDの設定値}
pgAdminのログイン画面が表示されない方へ。
思い当たる原因が2つあります。
1つはpgAdminログイン用のEmail Addressです。この入力項目ですが、Usernameでも良さそうですが、Email Address形式にしないとダメです。下記のようなダミーアドレスで大丈夫です。
もう1つはpgAdminのインストールに失敗している可能性があります。冒頭でも少し触れましたが、pgAdmin用バインドマウントディレクトリの所有権が5050:5050になっているか確認してみて下さい。私も最初全然表示されず、かなりの時間を使って原因を調べました。pgAdmin4 Vesion4.16からは、バインドマウントしてpgAdminデータを永続化する場合、バインドマウントディレクトリの所有権を5050:5050に設定しないと動作しない仕様に変わったようです。下記URLに説明がありますので確認してみて下さい。いや〜、全然原因が分からずに苦戦しました。皆様もインストールするpgAdminのバージョン確認はお忘れなく。
下記のような画面が表示されれば、ログイン成功です。
詳細は割愛しますが、赤枠部分からDB接続情報を設定して、PostgreSQLに接続してみましょう。
下記画像のような画面が表示されれば、PostgreSQLに接続できています。赤枠部分にある通り、先程マイグレーションして作成したテーブル群も確認できました。中々カッコいいGUIですね。
最後に
データベースをMySQLからPostgreSQLに変更するだけでしたが、結構定義ファイルの追加・変更箇所が多くて、思ったよりも時間がかかってしまいました。その甲斐あってか、こんなに使い勝手の良さそうなpgAdminもWebブラウザで利用できるようになり、大変満足しております。
PostgreSQL、気に入りました。
お疲れ様でした!