太陽がまぶしかったから

C'etait a cause du soleil.

PHP 初心者による Laravel 入門 その4 MySQL の Docker コンテナ間接続

PHP Laravel で MySQL コンテナに接続したい

 前回まででテンプレートの使い方を学習していたが、具体的なデータ操作がないと飽きてしまうので、先に MySQL のコンテナを作成してPHP Laravel のコンテナから 接続できるようにしたくなった。

 複数の Docker コンテナを管理するには docker-compose が便利。 docker-compose.yml の定義から複数のコンテナを作成してリンク定義が行える。今回の docker-compose.yml は以下の通りに作成する(password は任意に変更)。

version: '2'
services:
  mysql:
    image: "mysql:5.7"
    container_name: docker_mysql
    environment:
      MYSQL_ROOT_PASSWORD: root_password
      MYSQL_DATABASE: myapp_db
      MYSQL_USER: myapp_user
      MYSQL_PASSWORD: password
    volumes:
      - ./db/mysql_data:/var/lib/mysql
    command: >
      mysqld
        --character-set-server=utf8
        --collation-server=utf8_bin
        --skip-character-set-client-handshake
    ports:
      - "3306:3306"
  phpapp:
    build: ./app/
    container_name: docker_app
    ports:
      - "50000:8000"
    links:
      - mysql
  cli:
    image: "mysql:5.7"
    container_name: docker_cli
    environment:
      MYSQL_ROOT_PASSWORD: notuse
    links:
      - mysql

 docker-compose では imagedocker pull の役割をする。 mysql コンテナは myapp_dbmyapp_user で接続する初期設定を行った MySQL の本体。volumes の定義を行うことで、ローカル側のファイルシステムに MySQL のデータを格納してデータの永続化をさせている。

 phpapp は Laravel コンテナ作成時の Dockerfile ディレクトリを指定。Docker 同士のDBテスト接続のために、 cli も作成しておく。それぞれの links の定義で Docker コンテナ間の接続許可を定義している。

MySQL の起動確認とリモート接続確認

 docker-compose で一括バックグラウンド起動。

$ docker-compose up -d

 起動された MySQL コンテナにの sh に入ってローカルの MySQL に接続。

$ docker exec -it docker_mysql bash
root@0bf0c8305cc0:/# mysql -u root -p
Enter password: 

 データベース一覧を取得して、環境変数に設定したDBが作成されていることを確認。

mysql> show databases
    -> ;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| myapp_db           |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.05 sec)

 作成ユーザーと作成DBで改めて接続。

root@0bf0c8305cc0:/# mysql -u myapp_user -p  myapp_db
Enter password: 

 MySQL コンテナの仮想 IP アドレスを取得。

root@0bf0c8305cc0:/# exit
$ docker inspect docker_mysql | grep IPAddress

 cli コンテナに接続して MySQL コンテナへのリモート接続を確認。

$ docker exec -it docker_cli bash
root@4d53c5870591:/# mysql -h 172.19.0.x -u myapp_user -p myapp_db
Enter password: 

 ここままで Docker コンテナ同士でのDB接続ができることを確認した。全てがうまく行ってる前提であれば必要ないけど、ひとつひとつ確認していった方が安全。自分の場合はマウントしたボリュームにファイルが残っていると環境変数を設定してもDBが作られない事象にちょっとハマった。

テストデータの作成とローカル接続

 Port 転送設定をしているため、ホストマシンのSQLクライアントから 127.0.0.1:3306 で接続可能。テストテーブルとテストデータを作成しておく。

create table company (
  `company_id` bigint(20) unsigned primary key auto_increment,
  `company_name` text not null,
  `mail_address` text
);

insert into company
  (company_name, mail_address)
values
  ('現代観光株式会社', 'aaa@bbb'),
  ('現代日本株式会社', 'abc@bbb'),
  ('現代空間株式会社', 'aa@babb'),
  ('株式会社現代視覚', 'aaca@babb'),
  ('株式会社幽霊的身体', 'acaa@bcbb')
;

 MySQL の Docker イメージで日本語を扱えるようにするために、 mysqld コマンドでの文字コード指定を docker-compose.yml 内に定義している。ちなみに utf8_unicode_ciMySQLの照合順序 - Qiita で挙げられているような面白曖昧検索になるので使うな派。

 自分でビルドするイメージなら Dockerfile にコマンドを書き込んでいけばよいのだけど、公式イメージを外からカスマイズするための前提知識が辛かった。断片情報を集めるよりも、 library/mysql - Docker Hub の公式情報を読み込むべき。

Cent OS の Laravel コンテナから MySQL に接続する

 Laravel のデータベース定義は config/database.php にあるので、こちらをよしなに編集するだけ……だとうまくいかなくて、 .env 内の以下サンプル定義を削除しないとそちらが優先されるようだ。

DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

 .env を編集するだけではキャッシュが優先されるため、 artisan のキャッシュクリアコマンドを実行しておく。

docker exec docker_app php artisan config:cache

 ここまでの準備をすれば Laravel から MySQL に接続できると思ったが、 could not find Driver が発生。 phpinfo() を確認すると、PDO drivers が sqlite にのみ対応。 Laravel アプリ作成時点で php72-php-mysql が入っていなかったのが問題だったようだ。Dockerfile の yum を編集して Docker イメージをビルドし直し。無事に接続。

Laravel のクエリビルダーを利用する

 Controller の POST メソッドで MySQL データの取得をテストしてみる。

    public function doPost(Request $req, Response $res) {
        $target = $req->input('target') ?? '';
        $items = DB::table('company')->where('company_name', 'like', '%' . $target . '%')->get();

        $param = [
            'target' => $target,
            'items' => $items
        ];
        return view('hello.top', $param);
    }

 DB:table でテーブル名と where 句を指定して、get() で取得。レコードオブジェクトの配列が作成されるので、 blade テンプレートに受け渡す。

@section ('table')
<table>
    <tr>
        <th>company_id</th>
        <th>company_name</th>
        <th>mail_address</th>
    </tr>
        
    @forelse ($items as $i)  
    <tr>
        <td>{{ $i->company_id }}</td>
        <td>{{ $i->company_name }}</td>
        <td>{{ $i->mail_address }}</td>
    </tr>
        
    @empty
    <tr><td colspan="3">なーんもない</td></tr>
    @endforelse
</table>
@endsection

 前回学習した テンプレート内の繰り返し構文でレコードオブジェクトを繰り返し表示。

f:id:bulldra:20180504082352j:plain

 とりあえず超簡単な検索機能が実装できた。Web + DB のコンテナ間通信が実現できるとやってる感がでてきてよい感じ。ゆっくりながらも進んでいこう。