LaravelアプリをDockerfile, ECR, ECS, RDSを使用してデプロイする。


こんにちは。
KENと申します。

簡単な自己紹介

2020年4月から渋谷のとある企業でWebエンジニアをしてます、エンジニア一年生です。
エンジニアの研修でDockerを使用して開発環境を構築し、それをAWSの各種サービスを使用してコンテナデプロイをしました。
初めての経験で、コンテナデプロイ関連の記事も少なかったため、今回は自分の頭の整理を兼ねて記事を書くことにしました。

概要

今回の記事では、Laravelが動く環境をdockerで作成し、簡単なアプリを制作し、それをAWSのECR, ECS, RDS等のサービスを使用してデプロイするまでの流れについて書いていきます。

対象読者

Docker, AWSで何かアプリを制作、デプロイしてみたい人。
Dockerコンテナをデプロイしてみたい人。

環境

Docker version 19.03.8
docker-compose version 1.25.4

DockerでLaravelが動く環境を作る

基本的にこのフェーズはこの記事を参考にさせて頂きましたので、この記事を参考に進めてください。

【初心者向け】20分でLaravel開発環境を爆速構築するDockerハンズオン

一部ファイル構成、docker-compose.ymlの記述が上記サイトと異なる部分がありますので、その点について説明します。

今回の最終的なディレクトリ構成は以下です。

.
├── README.md
├── docker
│   ├── mysql
│   │   └── my.cnf
│   ├── nginx
|   |   |__ Dockerfile
│   │   └── default.conf
│   └── php
│       ├── Dockerfile
│       └── php.ini
├── docker-compose.yml
├── logs
│   ├── access.log
│   ├── error.log
│   ├── mysql-error.log
│   ├── mysql-query.log
│   ├── mysql-slow.log
│   └── php-error.log
└── src(アプリケーションのルートディレクトリ)
    └── readme.md

docker-compose.yml

version: "3"
services:
  app:
    build:
      context: .
      dockerfile: ./docker/php/Dockerfile
      args:
        - TZ=${TZ}
    ports:
      - ${APP_PORT}:8000
    volumes:
      - ./src:/work
      - ./logs:/var/log/php
      - ./docker/php/php.ini:/usr/local/etc/php/php.ini
    working_dir: /work
    environment:
      - DB_CONNECTION=mysql
      - DB_HOST=db
      - DB_DATABASE=${DB_NAME:-homestead}
      - DB_USERNAME=${DB_USER:-homestead}
      - DB_PASSWORD=${DB_PASS:-secret}
      - TZ=${TZ:-Asia/Tokyo}

  web:
    build:
      context: .
      dockerfile: ./docker/nginx/Dockerfile
    depends_on:
      - app
    ports:
      - ${WEB_PORT:-80}:80
    volumes:
      - ./src:/work
      - ./logs:/var/log/nginx
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    environment:
      - TZ=${TZ:-Asia/Tokyo}

  db:
    image: mysql:8.0
    volumes:
      - db-store:/var/lib/mysql
      - ./logs:/var/log/mysql
      - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
    environment:
      - MYSQL_DATABASE=${DB_NAME:-homestead}
      - MYSQL_USER=${DB_USER:-homestead}
      - MYSQL_PASSWORD=${DB_PASS:-secret}
      - MYSQL_ROOT_PASSWORD=${DB_PASS:-secret}
      - TZ=${TZ:-Asia/Tokyo}
    ports:
      - ${DB_PORT:-13306}:3306

volumes:
  db-store:

docker/php/Dockerfile

FROM php:7.4-fpm-alpine
COPY ./src /work
COPY ./docker/php/php.ini /usr/local/etc/php/php.ini
LABEL maintainer="ucan-lab <[email protected]>"

SHELL ["/bin/ash", "-oeux", "pipefail", "-c"]

# tinker(psysh)
ARG PSYSH_DIR=/usr/local/share/psysh
ARG PSYSH_PHP_MANUAL=$PSYSH_DIR/php_manual.sqlite
ARG PHP_MANUAL_URL=http://psysh.org/manual/ja/php_manual.sqlite

# timezone
ARG TZ=Asia/Tokyo

# composer
ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_HOME /composer

RUN apk update && \
  apk add --update --no-cache --virtual=.build-dependencies \
    autoconf=~2.69 \
    gcc=~9.2 \
    g++=~9.2 \
    make=~4.2 \
    tzdata=2020a-r0 \
    git=~2.24 && \
  apk add --update --no-cache \
    icu-dev=~64.2 \
    libzip-dev=~1.5 \
    oniguruma-dev=~6.9 && \
  cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
  echo ${TZ} > /etc/timezone && \
  pecl install xdebug && \
  git clone https://github.com/phpredis/phpredis.git /usr/src/php/ext/redis && \
  apk del .build-dependencies && \
  docker-php-ext-install intl pdo_mysql mbstring zip bcmath redis && \
  docker-php-ext-enable xdebug && \
  mkdir $PSYSH_DIR && curl $PHP_MANUAL_URL -o $PSYSH_PHP_MANUAL && \
  curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer && \
  composer config -g process-timeout 3600 && \
  composer config -g repos.packagist composer https://packagist.jp && \
  composer global require hirak/prestissimo

docker/nginx/Dockerfile

FROM nginx:1.17-alpine
COPY ./docker/nginx/default.conf /etc/nginx/conf.d/default.conf
COPY ./src /work
EXPOSE 80

docker/nginx/default.conf

server {
    listen 80;
    root /work/public;
    index index.php;
    charset utf-8;
    error_log /var/log/nginx/error_test.log;

    location / {
        root /work/public;
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

ECRのリポジトリを作成

AWSのコンソール画面にログインして、次の画像の画面にアクセスしてください。
ここで2つのリポジトリを作ります。

nginxのイメージを保存するリポジトリとphp-fpmのイメージを保存するリポジトリの二つです。

ECRにDockerイメージをPushする

次にそれぞれのリポジトリにDockerイメージをPushしていきます。
下の画像のように、プッシュコマンドの表示というボタンがあるので、それを押して出てきたコマンドに従って進めてください。
AWS CLIがインストールされていることが前提です。

ECSのタスク定義を作成

次にタスク定義を作成します。
タスク定義とは、アプリケーションを構成する 1 つ以上(最大 10 個)のコンテナを記述するJSON形式のテキストファイルです。
アプリケーションの設計図のようなものです。

下記の画面で新しいタスク定義の作成をクリックしてください。

今回はタスクの起動タイプをEC2に指定します。

続いてタスク定義名を決め、コンテナの追加をクリックします。

下の画像のようにappコンテナとwebコンテナを作成してください。
まずはappコンテナの定義です。

コマンドの蘭に、コンテナ起動時に実行したいコマンドを入力しておきます。
あとは環境変数も指定します。私は本番環境では.env.productionを使用しているので、APP_Env=productionと指定しています。

続いてWebコンテナを定義しましょう。

こちらも作業ディレクトリと環境変数を指定します。

Webコンテナのネットワーク設定のリンクにはapp(appコンテナのこと)と記述しましょう。
docker/nginx/default.confでfastcgi_pass app:9000;という記述があるので、ここと合わせるためappと記述します。

これでタスク定義を作成してください!

ECSのクラスターを作成

次にクラスターを作成します。
今回はEC2Linux + ネットワーキングを選択して作成します。

EC2インスタンスタイプはt2.micro、後はご自身のキーペアを選択してください。

これでクラスターを作成します。

タスクの起動

先ほど作成したタスク定義の画面に行ってください。
そこからアクションをクリックして、タスクの実行をクリックします。

次の画面でタスク定義とクラスターをそれぞれご自身が先ほど作成したものを選択し、実行をクリックします。

(すみません、この画像ではタスク定義とクラスター名がsample-appになっていません。この記事の流れではこの二つは本来sample-appという名前になります。)

続いてクラスターの画面に行き、先ほど実行したタスクのステータスがRUNNINGになっていることを確認してください。

これでEC2インスタンスのパブリックIPアドレスにアクセスすれば/srcディレクトリ以下に作成したアプリのトップページが表示されるはずです。

RDSの作成 

データベースを使用するアプリの場合はRDSを作成しましょう。
RDSの画面を開いたらデータベースの作成をクリックします。

標準作成で今回はMySQLを使用します。(Dockerの開発環境もMySQlを使用しているので)

無料利用枠で、マスターユーザー名マスターパスワードを決めます。

追加設定でアプリに利用するデータベースも作成しておきましょう。
今回はsample_appという名前でデータベースを作成しておきます。

これで作成をクリックしてください!

作成されたデータベースは、RDSダッシュボードの「データベース」で確認できます。

RDSの設定

EC2インスタンスからDBインスタンスにアクセスするために、
「セキュリティグループのルール」から使用中のセキュリティーグループをクリックし、インバウンドの編集でアドレス入力の箇所にEC2のプライベートIPアドレス/32を入力してください。

RDSで構築したMySQLは、日本語の保存ができないそうです。
日本語で保存できるように設定を変更します。パラメータグループの設定を、日本語データを保存できるように変更する必要があるみたいです!
詳しくはググってみてください。ここでは割愛します。

PHPのコンテナ内でマイグレーション

では最後です。
/srcディレクトリの下に、env.productionを作成します。
ECSのタスク定義で、appコンテナ、webコンテナ両方にAPP_ENV=productionという記述をしたので、本番環境ではこの.env.productionが参照されます。
記述は.envの内容とほとんど同じで大丈夫ですが、一部変更します。
下記を参考に記述してください。

DB_CONNECTION=mysql
DB_HOST=RDSのエンドポイントを記述
DB_PORT=3306
DB_DATABASE=sample_app(RDSの追加設定で作成したデータベース名)
DB_USERNAME=root
DB_PASSWORD=RDS作成の際に設定したマスターパスワード

ではsshでEC2インスタンスに接続してください。
ssh接続するには、EC2インスタンスのセキュリティーグループのインバウンドで22番ポートのアクセスを受け入れる設定ができている必要があります。
下記手順で進めていってください。

sshが機能するには、キーが公開されていないことが必要なので、キーペアのアクセス権を変更する。

chmod 400 xxx.pem(ご自身のキーペア)

sshでEC2インスタンスのパブリックDNSに接続する

ssh -i "xxx.pem" [email protected]

EC2インスタンスに接続できたら、MySQLのソフトウェアをインストールする。

sudo yum -y install mysql

MySQLに接続する(rootは今回のマスターユーザー名、xxxの所にはエンドポイントを記述する)

mysql -u root -p -h XXX.xxx.ap-northeast-1.rds.amazonaws.com

MySQLへ接続できたことが確認できたら、:qで接続を終了します。

次にdocker psで今起動しているコンテナのIDを確認し、下記のコマンドでapp(phpが動いているコンテナ)の内部に入りましょう。

docker exec -i -t コンテナID

そして下記コマンドでマイグレーションを実行します。

php artisan migrate

無事マイグレーションが完了し、テーブルが作成されていることが確認できたら、EC2インスタンスのパブリックIPアドレスにアクセスしてみましょう。
トップページが表示されるだけでなく、新規登録機能など、DBが絡む機能も動くようになっているはずです。

以上が、LaravelアプリをECR, ECS, RDSを使用してデプロイする手順です。

最後までお付き合いくださりありがとうございました!!

補足

説明が至らない所、私の認識が間違っていて、おかしな説明がされている所があるかもしれません。
何かありましたらコメント等で教えて頂きますと幸いです。