DockerとLaravelを使って開発してみよう

May 21, 2021
執筆者
Oluyemi Olususi
寄稿者
Twilio の寄稿者によって表明された意見は彼ら自身のものです
レビュー担当者

DockerとLaravelを使って開発してみよう

この記事はOluyemi Olususiこちらで公開した記事(英語)を日本語化したものです。

あなたが所属する開発チームは最近アプリをリリースし、そのアプリが短期間で大ヒットしていることが分かりました。利用者からの需要は収まらず、開発チームは「スケールアップ」について検討を始めました。

かつて、このスケールアップ作業は「垂直スケーリング」と呼ばれ、高機能のサーバーに投資することを意味していましたが、最近のコンテナ技術の台頭により、低コストで迅速にコンテナを追加できるようになり、アプリケーションの水平スケーリングが可能になりました。

これにより、2つの大きなメリットがもたらされます。1つ目は、高機能のサーバーに追加コストをかける必要がないことです。2つ目は、現在の顧客の需要に基づいて、アプリケーションのスケールアップとスケールダウンが可能になることです。

本稿では、LaravelプロジェクトでDockerを使用する方法について紹介します。Dockerを使えば、アプリケーションの使用量の急増と急減の両方に対応してスケーリング可能なアプリケーションを開発できます。

以下のチュートリアルでは、ApacheをWebサーバーとして使用し、PostgreSQLでデータベースエンジンを提供します。著名な歴史家による有名な名言を表示するアプリケーションを作成します。

必要条件

このチュートリアルを進めるには、以下の項目が必要です。

  • PHPとLaravelの基本的な知識。
  • コンテナ、イメージ、ネットワーク、サービスなど、基本的なDocker用語の知識。Jeff Hale氏は、これらの用語について、連載記事で分かりやすく解説しています。これらの用語についてあまり知らない場合は、ぜひ読んでみてください。
  • Docker Desktop

はじめに

最初に、laravel_dockerという名前の新しいディレクトリを作成します。

mkdir laravel_docker
cd laravel_docker

このアプリケーションは、相互に通信する必要のあるさまざまなコンポーネントで構成されているため、Docker Composeを使用してサービスを定義します。laravel_dockerディレクトリのルートに、docker-compose.ymlという名前の新しいファイルを作成します。

touch docker-compose.yml

このファイルには、コンテナのビルド方法から、コンテナにアクセス可能なネットワークやボリュームまで、アプリケーションの設定で作成するコンテナのすべての設定が保持されます。

docker-compose.ymlに以下を追加します。

version: '3.8'

services:

versionはスキーマバージョンを参照し、servicesは、アプリケーションスタックを構成するコンテナのリストを定義します。servicesは、実際は単なる「本番環境のコンテナ」です。

次の各セクションでは、データベース、Apache Webサーバー、PHPのコンテナについて説明します。

データベースコンテナをビルドする

docker-compose.ymlで、servicesエントリを次のように更新します。

services:
  database:
    image: postgres
    container_name: database
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: laravel_docker
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    ports:
      - '5432:5432'

container_nameは、Docker Composeでコンテナを自動的に生成するのではなく、実行時にコンテナの名前を設定します。

imageは、コンテナのビルド元のブループリントをDockerに認識させることができます。今回は、データベースエンジンとしてPostgreSQLを使用するため、postgresを指定します。

environmentキーは、デフォルトのデータベースの名前やパスワードなど、環境変数のリストを指定します。ここでは、ユーザー名を指定していないため、データベースのユーザー名は「postgres」になります。

portsキーを使用して、ローカル開発マシンのポートをコンテナのポートにマップし、GUIツールを使用してデータベースに接続できるようにします。コロン(:)の左側に指定されているポートは、コンピューターのポートです。右側に指定されているポートは、コンテナのポートです。

PostgreSQLインスタンスを実行している場合、またはポート4306が占有されている場合は、コンピューターの別のポートを指定できます。

次に、volumeキーを使用してボリュームを宣言します。Docker公式ドキュメントによると:

ボリュームは、Dockerコンテナが生成、使用するデータを保持するための推奨メカニズムです。

コンテナの破棄または再ビルド時にデータベースが失われないようにするために、ボリュームを宣言します。

PHPとApacheコンテナをビルドする

データベースコンテナとは異なり、PHP/Apacheコンテナを設定するには、追加の指示を指定する必要があります。これを行うには、DockerfileからPHPコンテナをビルドします。ルートディレクトリlaravel_dockerに、phpという名前のディレクトリを作成します。次に、laravel_docker/phpに、Dockerfileという名前のファイルを作成します。

Dockerfileには拡張子がありません。

mkdir php
touch php/Dockerfile

laravel_docker/php/Dockerfileに、以下を追加します。

FROM php:8.0-apache

RUN apt update \
        && apt install -y \
            g++ \
            libicu-dev \
            libpq-dev \
            libzip-dev \
            zip \
            zlib1g-dev \
        && docker-php-ext-install \
            intl \
            opcache \
            pdo \
            pdo_pgsql \
            pgsql \

WORKDIR /var/www/laravel_docker

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

公式のPHP Dockerイメージには、Apacheサーバーに付属するバリエーションが用意されています。これをFROMで指定すると、ベースコンテナにApacheがインストールされます。

PHPイメージからコンテナのための骨組みを作成することに加えて、以下を実行します。

  1. Laravelが依存するPHP拡張機能をインストールします。
  2. コンテナの作業ディレクトリを/var/www/laravel_dockerに設定します。
  3. Composerをインストールします。

次に、docker-compose.ymlを次のように更新します。

services:
        # The existing database container configuration...
  php-apache:
    container_name: php-apache
    build:
      context: ./php
    ports:
        - '8080:80'
    volumes:
      - ./src:/var/www/laravel_docker
      - ./apache/default.conf:/etc/apache2/sites-enabled/000-default.conf
    depends_on:
      - database

php-apacheコンテナは、docker-compose.ymldatabaseコンテナとは異なる定義にします。イメージを指定する代わりに、ビルドコンテキストを指定します。このように、docker-composeコマンドを実行すると、phpディレクトリに宣言されたDockerfileがコンテナのビルドに使用されます。

次に、データベースコンテナの場合と同様に、ローカル開発マシンのポート8080がコンテナのポート80にマップされます。./apache/default.confに指定された仮想ホストの設定はこのポートでリッスン(待機)するため、ポート80が使用されます。

コンテナで生成されたデータを保持するため、ボリュームを宣言します。この場合、Laravelアプリケーションはphp-apacheコンテナの/var/www/laravel_dockerディレクトリに作成されます。ただし、これはローカル開発マシンのプロジェクトディレクトリのsrcディレクトリに保持されます。

Apacheによりデフォルトで有効化されている000-default.confホストとアプリケーションの仮想ホストの設定をリンクできるように、追加のボリュームが宣言されています。これまでは、デフォルトの設定を無効にしてから独自の設定を有効にし、コンテナがビルドまたは再ビルドされるたびにApacheサーバーをリロードする必要がありましたが、これにより、そのストレスが軽減されます。

その後、新しい設定キー(depends_on)を導入します。これにより、php-apacheコンテナの前にまずデータベースコンテナをビルドする必要があることをDockerに認識させることができます。

プロジェクトのルートディレクトリにsrcディレクトリを作成します。

mkdir src

php-apacheコンテナからLaravelプロジェクトをスキャフォールドすると、プロジェクトファイルがここに保持されます。次に、apacheという名前のディレクトリを作成します。その中に、default.confというファイルを作成します。

mkdir apache

touch apache/default.conf

以下をapache/default.confに追加します。

<VirtualHost *:80>
    ServerName laravel_docker
    DocumentRoot /var/www/laravel_docker/public

    <Directory /var/www/laravel_docker>
        AllowOverride All
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

これで、ようやくコンテナをビルドできます。次のコマンドを実行します。

docker-compose up -d --build

Docker Desktopアプリケーションを開くと、次のスクリーンショットで示すように、新しく作成されたコンテナが表示されます。

Docker Desktop

Laravelアプリケーションを設定する

Laravelアプリケーションを設定するには、次のコマンドを使用し、php-apacheコンテナでコンテナを開始します。

docker-compose exec php-apache /bin/bash

これにより、php-apacheコンテナでCLIが開きます。次のコマンドを使用し、Laravelアプリケーションを作成します。

composer create-project laravel/laravel .

完了後に、http://localhost:8080/に移動します。デフォルトのLaravelのウェルカムページが表示されます。

Laravel welcome page

また、srcディレクトリを見ると、Laravelプロジェクトファイルもそこに追加されていることが分かります。

src/.envで、以下のようにデータベースパラメーターを編集します。

DB_CONNECTION=pgsql
DB_HOST=database
DB_PORT=5432
DB_DATABASE=laravel_docker
DB_USERNAME=postgres
DB_PASSWORD=secret

アプリケーションを構築する

本稿では名言アプリケーションを作成します。まずはアプリケーションのモデルを作成してみましょう。php-apacheコンテナで、次のコマンドを実行します。

php artisan make:model Quote -fms

-f引数により、名言のファクトリーを作成することをartisanに認識させることができます。同様に、-mは移行用、-sデータベースシーダー用です。

src/database/migrations/YYYY_MM_DD_HHMMSS_create_quotes_table.phpで、以下のようにup関数を更新します。

public function up() {
        Schema::create(
            'quotes',
            function (Blueprint $table) {
                $table->id();
                $table->text('quote');
                $table->string('historian');
                $table->string('year');
                $table->timestamps();
            }
        );
    }

次に、src/database/seeders/QuoteSeeder.phpを開き、run関数に以下を追加します。

Quote::factory()->times(50)->create();

これにより、QuoteFactoryから名言が50回作成され、毎回データベースに保存されます。

:次のインポートを追加することを忘れないでください:

use App\Models\Quote;

src/database/seeders/DatabaseSeeder.phpで、run関数に以下を追加します。

      $this->call(
            [
                QuoteSeeder::class
            ]
        );

これにより、db:seedコマンドが実行されたときにQuoteSeederが実行されます。次に、src/database/factories/QuoteFactory.phpで、definition関数に以下を追加します。

return [
            'quote' => $this->faker->sentence(),
            'historian' => $this->faker->name(),
            'year' => $this->faker->year(),
        ];

移行を実行してデータベースをシードするには、php-apacheコンテナで次のコマンドを実行します。

php artisan migrate:fresh --seed

データベースに何が追加されたかを確認するには、次のコマンドを実行し、データベースコンテナへのコマンドラインを開きます。

docker-compose exec database /bin/bash

次に、次のコマンドを実行し、PostgreSQLサービスにログインします。

psql -U postgres laravel_docker

SQLクエリを実行し、名言テーブルのすべての項目を取得します。

SELECT * FROM quotes;

次のスクリーンショットのように出力されます。

SQL出力

準備ができたら、次のコマンドを使用して名言のリクエストを処理するコントローラーを作成します。

php artisan make:controller QuoteController

src/app/Http/Controllers/QuoteController.phpを開き、以下のように更新します。

<?php

namespace App\Http\Controllers;

use App\Models\Quote;
use Illuminate\Http\Request;

class QuoteController extends Controller
{
    public function index(){
        return view('quotes.index', ['quotes' => Quote::all()]);
    }
}

index関数では、データベースからすべての名言を取得し、ビュー(quotes/index.blade.php)に渡して、名言を表示します。現時点ではビューが存在しないため、次のコマンドを実行してビューを作成します。

mkdir resources/views/quotes
touch resources/views/quotes/index.blade.php

src/resources/views/quotes/index.blade.phpを開き、以下を追加します。

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Top Quotes</title>
    <link
        href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css"
        rel="stylesheet"
        integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6"
        crossorigin="anonymous">
    <style>
        .wrapper {
            margin: 1em auto;
            width: 95%;
        }
    </style>
</head>
<body>
<div class="wrapper">
    <h1>Top Quotes</h1>
    <table class="table table-striped table-hover table-bordered">
        <thead>
        <tr>
            <th scope="col">#</th>
            <th scope="col">Quote</th>
            <th scope="col">Historian</th>
            <th scope="col">Year</th>
        </tr>
        </thead>
        <tbody>
        @foreach ($quotes as $quote)
            <tr>
                <td>{{ $loop->index + 1 }}</td>
                <td>{{ $quote->quote }}</td>
                <td>{{ $quote->historian }}</td>
                <td>{{ $quote->year }}</td>
            </tr>
        @endforeach
        </tbody>
    </table>
</div>
<script
    src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf"
    crossorigin="anonymous">
</script>
</body>
</html>

このテンプレートでは、Bootstrapを使用して名言テーブルのスタイルを設定します。次に、foreach loopディレクティブを使用して、QuoteControllerから受信した名言をループ処理し、名言、歴史家、年をそれぞれ表示します。

最後に、ルーティングを更新し、インデックスページに名言を読み込みます。これを行うには、src/routes/web.phpを開き、以下に一致するようにルート宣言を更新します。

Route::get('/', [App\Http\Controllers\QuoteController::class, 'index']);

これにより、indexページにアクセスしたときに、ウェルカムページを返す代わりに、QuoteControllerindex関数が呼び出されます。

変更をテストする

変更をテストするには、http://localhost:8080/に移動します。次のスクリーンショットのようなページレンダリングが表示されます。

名言リスト

すべて破棄する

先程、docker-compose up -d –buildコマンドを使用して、コンテナを作成してビルドしました。ハードディスクの空き容量が少ない、アプリケーションが冗長であるなどの理由で、システムのクリーンアップが必要になる場合がありますが、Dockerでは、このための機能がすでに用意されています。コンテナと関連するすべてのネットワークを停止して削除するには、次のコマンドを実行します。

docker-compose down

次のように出力されます。

破棄した際の出力

アプリケーションをスケーリングする

サービスの1つのインスタンスでは、アプリケーションへのすべてのトラフィックを十分に処理できない場合があります。このような場合に対応できるように、Dockerでは、–scaleフラグを使用したサービスのスケーリング(サービスの複数のインスタンスの作成)をサポートしています。

たとえば、php-apacheサービスをスケールアップするには、docker-compose.ymlを開きます。ここでは、2つの変更を行います。最初に、コンテナ名の設定を削除します。これは、コンテナ名が一意である必要があるためです。同じ名前で複数のサービスを作成しようとすると、Dockerはエラーを出力します。

同様に、サービスのポート設定を変更します。これにより、新しく作成された各サービスにDockerがポートを自動的に割り当てることができます。コンテナ名と同様に、2つのサービスを同じポートにバインドすることはできません。

これらの変更を行うには、以下のようにphp-apacheサービス設定を更新します。

php-apache:
    build:
      context: ./php
    ports:
        - '8080'
    volumes:
      - ./app:/var/www/laravel_docker
      - ./apache/default.conf:/etc/apache2/sites-enabled/000-default.conf
    depends_on:
      - database

設定の更新後に、次のコマンドを実行し、php-apacheサービスのインスタンスを10個作成します。

docker-compose up --scale php-apache=10 -d

これが完了すると、次のスクリーンショットのような出力がターミナルに表示されます。

database is up-to-date

Recreating php-apache ... done
Creating laravel_docker_php-apache_2  ... done 
Creating laravel_docker_php-apache_3  ... done
Creating laravel_docker_php-apache_4  ... done 
Creating laravel_docker_php-apache_5  ... done 
Creating laravel_docker_php-apache_6  ... done 
Creating laravel_docker_php-apache_7  ... done
Creating laravel_docker_php-apache_8  ... done 
Creating laravel_docker_php-apache_9  ... done 
Creating laravel_docker_php-apache_10 ... done

すべてが機能していることをテストするには、docker-compose psを実行します。次の例のような出力が表示されるはずです。

            Name                          Command               State                Ports             
-------------------------------------------------------------------------------------------------------
database                       docker-entrypoint.sh postgres    Up      0.0.0.0:5432->5432/tcp         
laravel_docker_php-apache_1    docker-php-entrypoint apac ...   Up      80/tcp, 0.0.0.0:55966->8080/tcp
laravel_docker_php-apache_10   docker-php-entrypoint apac ...   Up      80/tcp, 0.0.0.0:55970->8080/tcp
laravel_docker_php-apache_2    docker-php-entrypoint apac ...   Up      80/tcp, 0.0.0.0:55972->8080/tcp
laravel_docker_php-apache_3    docker-php-entrypoint apac ...   Up      80/tcp, 0.0.0.0:55968->8080/tcp
laravel_docker_php-apache_4    docker-php-entrypoint apac ...   Up      80/tcp, 0.0.0.0:55974->8080/tcp
laravel_docker_php-apache_5    docker-php-entrypoint apac ...   Up      80/tcp, 0.0.0.0:55973->8080/tcp
laravel_docker_php-apache_6    docker-php-entrypoint apac ...   Up      80/tcp, 0.0.0.0:55969->8080/tcp
laravel_docker_php-apache_7    docker-php-entrypoint apac ...   Up      80/tcp, 0.0.0.0:55967->8080/tcp
laravel_docker_php-apache_8    docker-php-entrypoint apac ...   Up      80/tcp, 0.0.0.0:55971->8080/tcp
laravel_docker_php-apache_9    docker-php-entrypoint apac ...   Up      80/tcp, 0.0.0.0:55975->8080/tcp

DockerとLaravelの使用を開始する方法は以上です

イメージとDockerfileからコンテナをビルドできるだけでなく、コンテナを相互に通信させることもできるため、Laravelアプリケーションとデータベースを個別のコンテナで実行できます。

これは、水平スケーリングが可能なアプリケーションスタックを作成するための第一歩です。コンテナオーケストレーションのインフラストラクチャを使用することにより、サーバーで処理されるリクエストの数に合わせてコンテナを作成および破棄できます。

このチュートリアルのコードベース全体は、GitHubで入手できます。ぜひご確認ください。コーディングを楽しみましょう!

Oluyemiは、電気通信工学のバックグラウンドを持つテクノロジー愛好家です。ユーザーが直面する日々の問題を解決することに強い関心を持ち、プログラミングの道に進んで以来、Webとモバイルの両方のソフトウェア開発で問題解決能力を磨いてきました。

Oluyemiは、知識の共有に情熱を注ぐフルスタックのソフトウェアエンジニアであり、ブログで多数の技術記事とコンテンツをインターネットに公開しています。テクノロジーにも精通しており、趣味は新しいプログラミング言語とフレームワークを試すことです。