あるSEのつぶやき・改

ITやシステム開発などの技術に関する話題を、取り上げたりしています。

Docker+CentOS8にnginx,PHP,MySQLをインストールしたコンテナを作成する

はじめに

前の記事では、Docker + CentOS 8 に nginx と PHP(php-fpm)をインストールしたコンテナを作成しました。

www.aruse.net

この記事では、さらに MySQL を Docker + CentOS 8 にインストールしたコンテナを作成して、nginx と PHP(php-fpm)のコンテナと連携します。

イメージとしては、[Web(nginx)] -> [App(PHP)] -> [DB(MySQL)] といったコンテナのつながりになります。

なお、前の記事で解説した部分のコードも掲載しますが、説明は最低限にします。もし分からないところが出てきた場合は、前の記事をご参照ください。

フォルダ構成

Docker の設定ファイルなどのフォルダ構成は、以下の通りです。

root
├── db
│   └── Dockerfile
│   └── my.cnf
├── docker-compose.yml
├── php
│   ├── Dockerfile
│   └── www.conf
├── share
│   ├── mysql_data             # MySQL のデータを永続化するディレクトリ
│   └── src                    # web と php で共有する永続化ディレクトリ
│       ├── dbconfirm.php
│       └── index.php
└── web
    ├── Dockerfile
    └── nginx.conf

Web の設定

Web の Dockerfile の設定は以下のようになります。

Dockerfile

FROM centos:8
RUN dnf -y update
RUN dnf install -y nginx
COPY nginx.conf /etc/nginx/nginx.conf
ENTRYPOINT /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

CentOS 8では yum コマンドが廃止され、dnf コマンドが導入されたのでそれに合わせて dnf コマンドを使用しています。yum コマンドも dnf コマンドのラッパーとして使用できますが、文法もほぼ変わらないようなので dnf コマンドを使用する方がよいかと思います。

ENTRYPOINTでは、nginx をフォアグラウンドで実行するように定義しています。これは、Docker にプロセスを停止されないために必要です。

以下の nginx の設定ファイルをコンテナにコピーして上書きすることで、PHP(php-fpm) 対応をしています。

nginx.conf

# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

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

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

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

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
         listen       80 default_server;
         listen       [::]:80 default_server;
         server_name  _;
         root         /usr/share/nginx/html;

         # Load configuration files for the default server block.
         include /etc/nginx/default.d/*.conf;

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

         error_page 404 /404.html;
             location = /40x.html {
         }

         error_page 500 502 503 504 /50x.html;
            location = /50x.html {
         }

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

PHP(php-fpm) の設定

PHP(php-fpm)はプロセスとして起動するので、Docker のコンテナとして独立させる必要があります。

PHP(php-fpm) の Dockerfile は以下のようになります。

Dockerfile

FROM centos:8
RUN dnf -y update
RUN dnf install -y php php-fpm mysql php-mysqlnd
RUN mkdir -p /run/php-fpm
COPY www.conf /etc/php-fpm.d/www.conf
RUN chown nginx:nginx /var/lib/php/session

PHP 7.2 では MySQL がらみのパッケージがphp-mysqlndに変更になっているようですね。また、MySQL の接続確認をするため mysql パッケージをインストールしています。

PHP(php-fpm)の設定ファイルは以下のようになっています。

www.conf

[www]
user = nginx
group = nginx
listen = 9000
listen.acl_users = apache,nginx
;listen.allowed_clients = 127.0.0.1
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
slowlog = /var/log/php-fpm/www-slow.log
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
php_value[session.save_handler] = files
php_value[session.save_path]    = /var/lib/php/session
php_value[soap.wsdl_cache_dir]  = /var/lib/php/wsdlcache

デフォルトと異なるのは、user, group が nginx になっていること、listen がポート番号になっていること、listen.allowed_clients をコメントアウトしていることです。

MySQL の設定

CentOS 8 からは MySQL 8 が標準リポジトリに追加されたのでリポジトリの追加はありません。

CentOS 7 では標準リポジトリから外れた MySQL が戻ってきたのは素直にうれしいですが、なにがあったのでしょうね?

CentOS 8 に MySQL 8 をインストールしてコンテナを作成しますが、自分でコンテナイメージを作成するより公式のコンテナイメージを使用したほうがよいかもしれません。

というのも、自分でコンテナを作成するとprivilegedでないと動作しないとか、情報が極端に少ないという問題があるためです。CentOS 8 でなければいけない理由があれば別ですが、進んで苦労することもないのかなと思います。

MySQL の Dockerfile は以下のとおりです。

Dockerfile

FROM centos:8
RUN dnf -y update

# install server & client
RUN dnf install -y @mysql

COPY my.cnf /etc/my.cnf

EXPOSE 3306

@mysqlという変わった名前のパッケージをインストールしていますが、これはMySQL のサーバーのmysql-serverとクライアントのmysqlを同時にインストールしています。

EXPOSEは開放するポート番号を指定しています。

my.cnf は以下の通りです。

my.cnf

[mysqld]
user=mysql
default-authentication-plugin=mysql_native_password
lower_case_table_names=0

Docker Compose の設定

docker-compose.yml の設定は以下のとおりです

docker-compose.yml

version: '3'
services:
  web:
    build:
      context: ./web/.
    ports:
      - "8080:80"
    links:
      - php
    volumes:
      - ./share/src:/usr/share/nginx/html
  php:
    build:
      context: ./php/.
    command: php-fpm -F
    links:
      - mysql
    volumes:
      - ./share/src:/usr/share/nginx/html
  mysql:
    build:
      context: ./db/.
    command: /usr/sbin/mysqld --user=mysql --initialize #初回起動時
    #command: /usr/sbin/mysqld --user=mysql #2回目以降起動時
    privileged: true
    volumes:
      - ./share/mysql_data:/var/lib/mysql

MySQL の部分は補足が必要ですね。

以下の部分は、MySQL をフォアグラウンドで実行することを記述しています。いろいろな方法を試しましたが、どうやらこの方法しかないようです。(my.cnfと設定が連動しています)

    command: /usr/sbin/mysqld --user=mysql

以下の部分は、privilegedでコンテナを実行することを記述しています。本来なら、privilegedは好ましくないのですが、MySQL のサーバーを公式ではなく自分でコンテナを作成する場合は仕方がないようです。これもいろいろな方法を試しましたが、他に方法は見つかりませんでした。

    privileged: true

以下の部分は、データの永続化の設定ですね。コンテナを終了して削除しても、データが残るようにしています。

    volumes:
      - ./share/mysql_data:/var/lib/mysql

以下の部分なのですが、今日(2020/08/09)現在、以下のように記述しないと動作しなくなりました。以前は2回目以降の記述だけでOKだったのですが、今は初回起動時は--initializeを付けて、2回目以降は外す必要があります。実行タイミングにあわせて、コメントアウトの調整をしてください。

    command: /usr/sbin/mysqld --user=mysql --initialize #初回起動時
    #command: /usr/sbin/mysqld --user=mysql #2回目以降起動時

コンテナの起動

docker-compose.ymlのあるディレクトリで、下記コマンドを実行してコンテナをまとめて起動します。

$ docker-compose up -d
Creating network "wordpress_default" with the default driver
Creating wordpress_mysql_1 ... done
Creating wordpress_php_1   ... done
Creating wordpress_web_1   ... done

下記コマンドでコンテナが起動しているか確認します。

$ docker-compose ps
      Name                     Command               State           Ports        
----------------------------------------------------------------------------------
wordpress_mysql_1   /usr/sbin/mysqld --user=my ...   Exit 0                       
wordpress_php_1     php-fpm -F                       Up                           
wordpress_web_1     /bin/sh -c /usr/sbin/nginx ...   Up       0.0.0.0:8080->80/tcp

起動に失敗している場合は、State が Exit になります。

wordpress_mysql_1 が Exit となってますね。これは初期処理が終了してコンテナが終了したので問題ありません。

ここでコンテナのログで、MySQL の root アカウントのパスワードを確認します。eUj=lfhu1Nq>となっていますね。後で使うので控えておきます。

$ docker-compose logs
Attaching to wordpress_web_1, wordpress_php_1, wordpress_mysql_1
mysql_1  | 2020-08-09T07:37:23.241420Z 0 [System] [MY-013169] [Server] /usr/sbin/mysqld (mysqld 8.0.17) initializing of server in progress as process 1
mysql_1  | 2020-08-09T07:37:30.757619Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: eUj=lfhu1Nq>
mysql_1  | 2020-08-09T07:37:34.773475Z 0 [System] [MY-013170] [Server] /usr/sbin/mysqld (mysqld 8.0.17) initializing of server has completed

一度、コンテナをすべて終了します。

$ docker-compose down
Stopping wordpress_web_1 ... done
Stopping wordpress_php_1 ... done
Removing wordpress_web_1   ... done
Removing wordpress_php_1   ... done
Removing wordpress_mysql_1 ... done
Removing network wordpress_default

ここで、先ほどのdocker-compose.ymlの2回目のコメントアウトを変更します。

    #command: /usr/sbin/mysqld --user=mysql --initialize #初回起動時
    command: /usr/sbin/mysqld --user=mysql #2回目以降起動時

コンテナを再度起動します。

$ docker-compose up -d
Creating network "wordpress_default" with the default driver
Creating wordpress_mysql_1 ... done
Creating wordpress_php_1   ... done
Creating wordpress_web_1   ... done

コンテナが起動したか確認します。

$ docker-compose ps
      Name                     Command               State          Ports        
---------------------------------------------------------------------------------
wordpress_mysql_1   /usr/sbin/mysqld --user=mysql    Up      3306/tcp            
wordpress_php_1     php-fpm -F                       Up                          
wordpress_web_1     /bin/sh -c /usr/sbin/nginx ...   Up      0.0.0.0:8080->80/tcp

問題なく起動していますね。

データベース設定

MySQL のコンテナにログインして、データベースの設定を行います。

先ほどのdocker-compose psでコンテナ名が分かったので、MySQL のコンテナに以下のコマンドでログインします。

$ docker exec -it wordpress_mysql_1 /bin/bash

下記コマンドを実行して、データベースの初期化を行います。コマンド実行時に、root アカウントのパスワードを聞かれるので、先ほど保存しておいたeUj=lfhu1Nq>を使用します。

# mysql_secure_installation

基本的に聞かれたことに回答していくだけですが、root のパスワードはここでは「Mysql@1234」にします。

次に、ユーザーを追加します。

下記コマンドで MySQL にログインします。

# mysql -u root -p

以下のようにして、サーバーごとにユーザーを追加します。

mysql> CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'Mysql@1234';
mysql> CREATE USER 'mysql'@'%' IDENTIFIED BY 'Mysql@1234';

なお、MySQL 8 から GRANT 構文でユーザーを追加できなくなったのでご注意ください。今までは暗黙的にできていたものが、できなくなったようです。

www7390uo.sakura.ne.jp

以下のようにしてユーザーに権限を付与します。外部からもアクセスできるようにしています。

mysql> GRANT ALL ON *.* TO 'mysql'@'localhost';
mysql> GRANT ALL ON *.* TO 'mysql'@'%';
mysql> FLUSH PRIVILEGES;

また、PHPからアクセスするため、ユーザーの認証方式も変更しておきます。

mysql> ALTER USER 'mysql'@'%' IDENTIFIED WITH mysql_native_password BY 'Mysql@1234';

認証方式の詳細は、以下の記事を参照してください。

qiita.com

動作確認のためにデータベースも作成しておきます。終わったらログアウトします。

mysql> CREATE DATABASE Users;
mysql> quit;
# exit

動作確認

まず、[App(PHP)] -> [DB(MySQL)] の動作確認を行います。

ログインするコンテナ名を確認します。

$ docker-compose ps
      Name                     Command              State          Ports        
--------------------------------------------------------------------------------
wordpress_mysql_1   /usr/sbin/mysqld --user=root    Up      3306/tcp            
wordpress_php_1     php-fpm -F                      Up                          
wordpress_web_1     /bin/sh -c /usr/sbin/nginx      Up      0.0.0.0:8080->80/tcp

PHP のコンテナにログインします。

$ docker exec -it wordpress_php_1 /bin/bash

リモートのデータベースコンテナ(mysql)にログインします。

# mysql -h mysql -u mysql -p
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.13 Source distribution

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 
mysql>  quit;
# exit

無事にログインできました。

次は、[Web(nginx)] -> [App(PHP)] の動作確認をします。

docker-compose.ymlのあるルートディレクトリから、以下のコマンドを実行します。

$ cd share/src
$ echo "<?php phpinfo(); ?>" > index.php

ブラウザでhttp://localhost:8080/index.phpにアクセスします。

f:id:fnyablog:20191006195139p:plain:w480

phpinfo() が正しく表示されました。

最後に、[Web(nginx)] -> [App(PHP)] -> [DB(MySQL)] の動作確認をします。

先ほどのindex.php と同じディレクトリにdbconfirm.phpというファイルを作成します。

dbconfirm.php

<?php
$dsn = 'mysql:dbname=Users;host=mysql';
$user = 'mysql';
$pass = 'Mysql@1234';

try {
  $dbh = new PDO($dsn, $user, $pass);
  echo "接続成功";
} catch (PDOException $e) {
  echo "接続失敗:" . $e->getMessage();
}

なお、上記コードはセキュリティを考慮していない最低限のものになるので、実際にこのサンプルコードを使用しないでください。実際のコードは徳丸本などの参考にしてください。

ブラウザで、http://localhost:8080/dbconfirm.phpにアクセスします。

f:id:fnyablog:20191006195744p:plain:w480

無事に表示されました。

これですべての動作確認が終了しました。

コンテナの終了

作業が終わったら、下記コマンドで忘れずにコンテナを終了・削除します。

$ docker-compose down
Stopping wordpress_web_1   ... done
Stopping wordpress_php_1   ... done
Stopping wordpress_mysql_1 ... done
Removing wordpress_web_1   ... done
Removing wordpress_php_1   ... done
Removing wordpress_mysql_1 ... done
Removing network wordpress_default

おわりに

Docker + CentOS 8 に、nginx, PHP(php-fpm), MySQL をインストールしてコンテナ作成する方法は以上となります。

この方法はネットにもほとんどないマイナーなものだったようで、非常に苦労しました。

環境が許すなら、独自にコンテナを作成するよりは、公式から提供されているコンテナを利用するほうがよいかもしれませんね。

とはいえ、開発環境は OS を固定することが多いでしょうから、そういう訳にも行かないのでしょうが。

この情報がお役に立てば幸いです。