はじめに
前の記事では、Docker + CentOS 8 に nginx と PHP(php-fpm)をインストールしたコンテナを作成しました。
この記事では、さらに MySQL を Docker + CentOS 8 にインストールしたコンテナを作成して、nginx と PHP(php-fpm)のコンテナと連携します。
イメージとしては、[Web(nginx)] -> [App(PHP)] -> [DB(MySQL)] といったコンテナのつながりになります。
なお、前の記事で解説した部分のコードも掲載しますが、説明は最低限にします。もし分からないところが出てきた場合は、前の記事をご参照ください。
- はじめに
- フォルダ構成
- nginx の設定
- PHP(php-fpm) の設定
- MySQL の設定
- Docker Compose の設定
- コンテナの起動
- データベース設定
- 動作確認
- コンテナの終了
- おわりに
フォルダ構成
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 構文でユーザーを追加できなくなったのでご注意ください。今までは暗黙的にできていたものが、できなくなったようです。
以下のようにしてユーザーに権限を付与します。外部からもアクセスできるようにしています。
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';
認証方式の詳細は、以下の記事を参照してください。
動作確認のためにデータベースも作成しておきます。終わったらログアウトします。
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
にアクセスします。
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(); }
なお、上記コードはセキュリティを考慮していない最低限のものになるので、実際にこのサンプルコードを使用しないでください。実際のコードは徳丸本などの参考にしてください。
体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践
- 作者:徳丸 浩
- 発売日: 2018/06/21
- メディア: 単行本
ブラウザで、http://localhost:8080/dbconfirm.php
にアクセスします。
無事に表示されました。
これですべての動作確認が終了しました。
コンテナの終了
作業が終わったら、下記コマンドで忘れずにコンテナを終了・削除します。
$ 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 を固定することが多いでしょうから、そういう訳にも行かないのでしょうが。
この情報がお役に立てば幸いです。