AWSサービスを意識したDockerで作るLEMP開発環境

はじめまして。株式会社アドグローブWeb&アプリ事業部の近藤です。
みなさんは、普段、開発環境はどのように用意してますか?
私が担当するプロジェクトでは、よくDockerを使っており、本番環境をAWSで動かすことが多いので、本記事では「普段どのようにDocker開発しているか」を簡単にご紹介します。
どなたかの参考になれば幸いです。

目次

本記事で出来ること

  • 開発環境と本番環境の環境差異を少なくできる
  • 開発環境にS3の互換サービスを構築することでAWS側に環境を用意する必要がなくなる

全体ファイル構成

ファイルは Gitlab でも公開しています。
良かったら参考にしてください。
Gitlab:https://gitlab.com/adglobe/blog/t-kondo/sample-1

以下に全体的なディレクトリやファイル構成を示します。
一つずつファイルの中身を説明していきましょう。

.
├── docker-aws/
│   ├── nginx/
│   |   ├── Dockerfile
│   |   └── config/
│   |       └── nginx.conf
│   └── php/
│       ├── Dockerfile
│       └── config/
│           ├── php-fpm.conf
│           ├── php.ini
│           └── www.conf
├── docker-local/
│   ├── nginx/
│   |   ├── Dockerfile
│   |   └── config/
│   |       └── nginx.conf
│   ├── php/
│   |   ├── Dockerfile
│   |   └── config/
│   |       ├── php-fpm.conf
│   |       ├── php.ini
│   |       └── www.conf
│   ├── mysql/
│   |   ├── Dockerfile
│   |   └── config/
│   |       ├── mysql.cnf
│   |       └── mysqld.cnf
│   └── minio/
│       ├── Dockerfile
│       ├── data/
│       ├── export/
│       └── policies/
├── src/
├── buildspec.yml
└── docker-compose.yml

各ファイル説明

docker-aws/

AWS ECS にデプロイする際に必要となるECRイメージの定義ファイルです。
ミドルウェア(Nginx, PHP)別にディレクトリを分けてDockerfileを管理してます。

docker-aws/nginx/Dockerfile

Nginxを作成するためのDockerfileです。
AWS上で稼働させるためイメージは amazonlinux:2 を利用することが多いのですが、FROM amalinux:2 と指定する方法ではデプロイの度に yum update を走らせてしまうことになり、細かいミドルウェアのバージョンが変化してしまうため、ベースイメージ(DOCKER_REPOSITORY_URI_NGINX)を作成してそれを指定するようにしています。

ARG DOCKER_REPOSITORY_URI_NGINX
FROM ${DOCKER_REPOSITORY_URI_NGINX}:latest

RUN mkdir -p /code
COPY ./src/public /code/public
COPY ./docker-aws/nginx/config/nginx.conf /etc/nginx/nginx.conf

ベースイメージ(DOCKER_REPOSITORY_URI_NGINX)について

以下のDockerfileを作成してECRにプッシュしておき、 DOCKER_REPOSITORY_URI_NGINX は、ECRのリポジトリURLを指定します。

FROM amazonlinux:2

RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

RUN yum -y update
RUN amazon-linux-extras enable nginx1

ENV NGINX_VERSION="1.20.0"
RUN yum -y install --enablerepo=amzn2extra-nginx1 \
    nginx-${NGINX_VERSION}

RUN yum clean all

EXPOSE 80
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]

docker-aws/nginx/config/nginx.conf

Nginx設定ファイルです。
下記の設定では、以下のことをしてます。

  • ECSを意識してエラーログは標準出力
  • php-fpmを8080ポートで起動させるので「127.0.0.1:8080」を指定

まるまるコピーするのではなく、間違いがないようにオリジナルファイルをコピーしてきて必要箇所を修正するのが良いと思います。

user nginx;
worker_processes auto;
worker_rlimit_nofile 50000;
error_log /dev/stderr;
pid /run/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 4096;
    use epoll;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" $request_time';

    map $http_user_agent $log_ua {
        ~ELB-HealthChecker 0;
        default 1;
    }

    access_log  /dev/stdout main if=$log_ua;
    error_log   /dev/stderr;

    sendfile                  on;
    tcp_nopush                on;
    tcp_nodelay               on;
    keepalive_timeout         60;
    client_header_timeout     60;
    client_body_timeout       60;
    send_timeout              60;
    reset_timedout_connection on;
    types_hash_max_size       2048;
    client_max_body_size      512M;

    server_tokens   off;

    gzip            on;
    gzip_vary       on;
    gzip_disable    "msie6";
    gzip_proxied    any;
    gzip_min_length 1024;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    include      /etc/nginx/mime.types;
    default_type application/octet-stream;

    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80;
        server_name  _;
        root         /code/public;
        index        index.php;

        include /etc/nginx/default.d/*.conf;

        location = /favicon.ico {
            empty_gif;
            access_log off;
            log_not_found off;
        }

        location / {
            try_files $uri $uri/ /index.php$is_args$args;
            location ~ \.php$ {
                fastcgi_pass  127.0.0.1:8080;
                fastcgi_index index.php;
                include       fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_param PATH_INFO $fastcgi_path_info;
            }
        }
    }
}

docker-aws/php/Dockerfile

PHPを作成するためのDockerfileです。
AWS上で稼働させるためイメージは amazonlinux:2 を利用することが多いですが、FROM amalinux:2 と指定する方法ではデプロイの度に yum update が走ってしまい細かいミドルウェアのバージョンが変化してしまうため、ベースイメージ(DOCKER_REPOSITORY_URI_PHP)を作成してそれを指定するようにしています。

ARG DOCKER_REPOSITORY_URI_PHP
FROM ${DOCKER_REPOSITORY_URI_PHP}:latest

RUN mkdir -p /code
COPY ./src /code
COPY ./docker-aws/php/config/php.ini /etc/php.ini
COPY ./docker-aws/php/config/php-fpm.conf /etc/php-fpm.conf
COPY ./docker-aws/php/config/www.conf /etc/php-fpm.d/www.conf

RUN chmod 777 -R /code/storage

ベースイメージ(DOCKER_REPOSITORY_URI_PHP)について

以下のDockerfileを作成してECRにプッシュしておき、 DOCKER_REPOSITORY_URI_PHP は、ECRのリポジトリURLを指定します。

FROM amazonlinux:2

RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

RUN yum -y update
RUN amazon-linux-extras enable php8.0

ENV PHP_VERSION="8.0.13"
RUN yum -y install --enablerepo=amzn2extra-php8.0 \
    php-${PHP_VERSION}

RUN yum -y install php-devel-${PHP_VERSION} \
    php-pdo-${PHP_VERSION} \
    php-fpm-${PHP_VERSION} \
    php-mysqlnd-${PHP_VERSION} \
    php-mbstring-${PHP_VERSION} \
    php-xml-${PHP_VERSION} \
    php-pear-${PHP_VERSION}

RUN yum clean all

EXPOSE 8080
CMD ["/usr/sbin/php-fpm", "--nodaemonize"]

docker-aws/php/config/php-fpm.conf

オリジナルファイルをコピーしてきます。
設定の修正があればしておきましょう。

docker-aws/php/config/php.ini

オリジナルファイルをコピーしてきます。
設定の修正があればしておきましょう。

docker-aws/php/config/www.conf

オリジナルファイルをコピーしてきます。
設定の修正があればしておきましょう。

docker-local/

開発環境向けのDocker定義ファイルです。
ミドルウェア(Nginx、PHP、MySQL)別にディレクトリを分けてDockerfileを管理してます。

docker-local/nginx/Dockerfile

開発環境用のNginxを作成するためのDockerfileです。
本番環境はAWSで動かすため、OS は AmazonLinux2 を指定しています。
Nginxのインストールバージョンに差異が出ないよう、バージョンを指定しています。

FROM amazonlinux:2

RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

RUN yum -y update
RUN amazon-linux-extras enable nginx1

ENV NGINX_VERSION="1.20.0"
RUN yum -y install --enablerepo=amzn2extra-nginx1 \
    nginx-${NGINX_VERSION}

RUN yum clean all

WORKDIR /code

EXPOSE 80
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]

docker-local/nginx/config/nginx.conf

開発環境用のNginx設定ファイルです。
環境差分を無くすため、docker-aws/nginx/config/nginx.conf と同設定ですが、開発環境はdocker-compose.ymlで定義するため、phpコンテナの指定部分のみを書き換えます。

-                fastcgi_pass  127.0.0.1:8080;
+                fastcgi_pass  php:8080;

docker-local/php/Dockerfile

開発環境用のPHPを作成するためのDockerfileです。
本番環境はAWSで動かすため、OS は AmazonLinux2 を指定しています。
PHPのインストールバージョンに差異が出ないよう、バージョンを指定しています。

FROM amazonlinux:2

RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

RUN yum -y update
RUN amazon-linux-extras enable php8.0

ENV PHP_VERSION="8.0.13"
RUN yum -y install --enablerepo=amzn2extra-php8.0 \
    php-${PHP_VERSION}

RUN yum -y install php-devel-${PHP_VERSION} \
    php-pdo-${PHP_VERSION} \
    php-fpm-${PHP_VERSION} \
    php-mysqlnd-${PHP_VERSION} \
    php-mbstring-${PHP_VERSION} \
    php-xml-${PHP_VERSION} \
    php-pear-${PHP_VERSION}

RUN yum clean all

WORKDIR /code

EXPOSE 9000
CMD ["/usr/sbin/php-fpm", "--nodaemonize"]

docker-local/php/config/php-fpm.conf

オリジナルファイルをコピーしてきます。
設定の修正があればしておきましょう。

docker-local/php/config/php.ini

オリジナルファイルをコピーしてきます。
設定の修正があればしておきましょう。

docker-local/php/config/www.conf

オリジナルファイルをコピーしてきます。
設定の修正があればしておきましょう。

docker-local/mysql/Dockerfile

開発環境用のDB(MySQL)を作成するためのDockerfileです。
AWSでは Aurora MySQL を利用することが多いので、MariaDBではなくMySQLをインストールします。
Aurora MySQL とのバージョン差異を最小化するために、AWS環境構築時点の Aurora MySQL バージョンを調べて同じバージョンの MySQL を指定します。

FROM mysql:5.7

docker-local/minio/Dockerfile

Amazon S3互換のオブジェクトストレージ環境を作成するためのDockerfileです。

FROM minio/minio:RELEASE.2020-11-19T23-48-16Z

古いバージョンを指定してますが、新しいバージョンだと ListObjects でエラーになるので仕方なく(なんでだろう...)。

src/

PHPフレームワークをここに入れています。
弊社ではLaravelを利用することが多いです。

buildspec.yml

CodePipelineで利用するCodeBuildの設定ファイルです。
ECS を利用するため、ECRへDockerイメージをプッシュするところまで行います。

version: 0.2

env:
  variables:
    DOCKER_BUILDKIT: "1"
phases:
  install:
    runtime-versions:
      php: 8.0
  pre_build:
    commands:
      - aws --version
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com
      - REPOSITORY_URI_NGINX=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_NAME_NGINX}
      - REPOSITORY_URI_PHP=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_NAME_PHP}
      - IMAGE_TAG=${CODEBUILD_RESOLVED_SOURCE_VERSION}
  build:
    commands:
      - echo Build started on `date`
      # PHP Build
      - cd src/
      - composer install
      - cd ..
      # Nginx container Build
      - echo Building the Nginx Docker image...
      - docker build -t ${REPOSITORY_URI_NGINX}:latest -f ./docker-aws/nginx/Dockerfile --build-arg DOCKER_REPOSITORY_URI_NGINX=${DOCKER_REPOSITORY_URI_NGINX} .
      - docker tag ${REPOSITORY_URI_NGINX}:latest ${REPOSITORY_URI_NGINX}:${IMAGE_TAG}
      # PHP container Build
      - echo Building the PHP Docker image...
      - docker build -t ${REPOSITORY_URI_PHP}:latest -f ./docker-aws/php/Dockerfile --build-arg DOCKER_REPOSITORY_URI_PHP=${DOCKER_REPOSITORY_URI_PHP} .
      - docker tag ${REPOSITORY_URI_PHP}:latest ${REPOSITORY_URI_PHP}:${IMAGE_TAG}
      - echo Build completed on `date`
  post_build:
    commands:
      - echo Pushing the Docker images...
      # DB migration
      - cd src/
      - php artisan migrate --force
      - cd ..
      # Nginx container Push
      - docker push ${REPOSITORY_URI_NGINX}:${IMAGE_TAG}
      - docker push ${REPOSITORY_URI_NGINX}:latest
      # PHP container Push
      - docker push ${REPOSITORY_URI_PHP}:${IMAGE_TAG}
      - docker push ${REPOSITORY_URI_PHP}:latest
      - echo Writing image definitions file...
      - echo "[{\"name\":\"${CONTAINER_NAME_NGINX}\",\"imageUri\":\"${REPOSITORY_URI_NGINX}:${IMAGE_TAG}\"},{\"name\":\"${CONTAINER_NAME_PHP}\",\"imageUri\":\"${REPOSITORY_URI_PHP}:${IMAGE_TAG}\"}]"
      - echo "[{\"name\":\"${CONTAINER_NAME_NGINX}\",\"imageUri\":\"${REPOSITORY_URI_NGINX}:${IMAGE_TAG}\"},{\"name\":\"${CONTAINER_NAME_PHP}\",\"imageUri\":\"${REPOSITORY_URI_PHP}:${IMAGE_TAG}\"}]" > imagedefinitions.json
artifacts:
  files:
    - imagedefinitions.json

docker-aws/nginx/Dockerfile、docker-aws/php/Dockerfile の設定で登場する環境変数「DOCKER_REPOSITORY_URI_NGINX」「DOCKER_REPOSITORY_URI_PHP」を渡してます。

docker-compose.yml

version: '3'

services:
  nginx:
    container_name: ${COMPOSE_PROJECT_NAME}-nginx
    build:
      context: ./docker-local/nginx/
    ports:
      - "${WEB_PORT-80}:80"
    volumes:
      - ./src/:/code
      - ./docker-local/nginx/config/nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - php
    networks:
      - common

  php:
    container_name: ${COMPOSE_PROJECT_NAME}-php
    build:
      context: ./docker-local/php/
    environment:
      APP_ENV: local
      DB_HOST: mysql
      DB_DATABASE: ${DB_DATABASE}
      DB_USERNAME: ${DB_USERNAME}
      DB_PASSWORD: ${DB_PASSWORD}
      DB_PORT: 3306
      AWS_ACCESS_KEY_ID: ${MINIO_ACCESS_KEY}
      AWS_SECRET_ACCESS_KEY: ${MINIO_SECRET_KEY}
    volumes:
      - ./src/:/code
      - ./docker-local/php/config/php.ini:/etc/php.ini
      - ./docker-local/php/config/php-fpm.conf:/etc/php-fpm.conf
      - ./docker-local/php/config/www.conf:/etc/php-fpm.d/www.conf
    networks:
      - common

  mysql:
    platform: linux/x86_64
    container_name: ${COMPOSE_PROJECT_NAME}-mysql
    build:
      context: ./docker-local/mysql/
    hostname: localhost
    environment:
      HOSTNAME: localhost
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_USER: ${DB_USERNAME}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      TZ: Asia/Tokyo
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_bin
    ports:
      - "${DB_PORT-3306}:3306"
    volumes:
      - ./docker-local/mysql/config/mysql.cnf:/etc/mysql/conf.d/mysql.cnf
      - ./docker-local/mysql/config/mysqld.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf
      - dbstorage:/var/lib/mysql
    networks:
      - common

  minio:
    container_name: ${COMPOSE_PROJECT_NAME}-minio
    build:
      context: ./docker-local/minio/
    ports:
      - "${MINIO_WEB_PORT-9000}:9000"
    volumes:
      - ./docker-local/minio/data:/data
      - ./docker-local/minio/export:/export
      - ./docker-local/minio/policies:/policies
    environment:
      MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
      MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
      TZ: Asia/Tokyo
    command: -c "
      mkdir -p /data/.minio.sys/buckets;
      cp -r /policies/* /data/.minio.sys/;
      cp -r /export/* /data/;
      /usr/bin/minio server /data;"
    entrypoint: sh
    networks:
      - common

volumes:
  dbstorage:
    driver: local

networks:
  common:
    driver: bridge