Dockerの公式イメージのカスタマイズ
前回はDockerの入門としてWord Press環境を構築してみました。
今回は、前回省略したDockerの基本的なことを学びながら、公式のapacheイメージ(httpd)に、PHPとSSLを追加したオリジナルのイメージを作成してみたいと思います。
そこにMariaDBを連携させ、WordPressはWordPressの公式サイトからダウンロードしたPHPファイル群をバインドさせて使います。
コンテナ
まずDockerを運用していてわかりづらいのはコンテナという言葉の意味です。
同時に(コンテナ)イメージという言葉がでてきますが、こちらはよくディスクイメージと呼ばれるものと同じでなじみがあると思います。
docker docsに説明があるようにコンテナはそのイメージを使って生成される、Docker上で動くアプリケーションのプロセスです。
通常のDVDとOS、アプリケーションにあてはめると、DockerはOS、DockerのイメージはDVD、Dockerのコンテナはアプリケーションになります。
DVDから読みださないとOS上にはアプリケーションが存在しません。同じように、Dockerイメージから読みださないとDockerコンテナも存在しません。
イメージの取得
Dockerの環境はすでにインストールしてある前提です。
もしインストールしていないようでしたらインストールしてください。Debian環境でしたら、Word Press環境を構築した際の記事を参考にしていただければと思います。
イメージはダウンロードして利用します。そのためのコマンドが「docker pull」です。引数にイメージ名を指定します。タグ名がある時は:のあとに付け加えます。タグもイメージを識別するための名前の一部です。通常はバージョンが入っていることが多いです。省略するとlatestという最新を意味する文字列が入っているものとして扱われます。
イメージを見つける際は「docker search」を利用します。
pullでダウンロードされたイメージは「docker images」コマンドで確認することができます。
イメージを削除する際は、「docker rmi」コマンドで指定します。コンテナが残っている状態で削除したい場合は--forceを加えて実行します。
httpd(apache2)をイメージを検索して取得してみます。併せて、あとから使うmariadbと、削除の練習用にhello-worldイメージも取得します。
searchではタグ名にalpineが含まれているものも見つかりますが、これはベースをAlpine Linuxにした軽量版です。
$ docker search apache NAME DESCRIPTION ... ------- ------------------ httpd The Apache HTTP... tomcat Apache Tomcat i... $ docker pull httpd ... $ docker pull mariadb ... $ docker pull hello-world ... $ docker images NAME TAG ----------- ---------- httpd latest ... mariadb latest ... hello-world latest ... ... $ docker rmi hello-world ...
rmiに渡すパラメータは、docker imagesコマンドで表示される、IMAGE IDを指定することも可能です。
イメージの起動
イメージを起動するには「docker run」を使います。
dオプションでバックグラウンド化し、-pオプションでコンテナ内ポートと実行環境ポートの引き当てを行っています。ここではコンテナ内の80番(http)ポートを、実行環境のPCの8000番に結びつけています。Webサーバーの場合ならホストのIPアドレスの8000番宛てにブラウザでアクセスすると、コンテナで稼働しているページが見られます。
$ docker run -d -p 8000:80 httpd ...
稼働しているプロセスを確認するには、docker psを使います。ここで表示される名前やIDはイメージのものではなく、コンテナのものになります。元となったイメージ名はIMAGEというエントリーに表示されます。これにdocker ps -aとすると停止中のコンテナまで表示されます。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES deba105935af httpd "httpd-foreground" 5 minutes ago Up 5 minutes 0.0.0.0:8000->80/tcp pedantic_neumann
プロセスを停止するにはコンテナIDか名前を指定してdocker stopを使います。
$ docker stop deba105935af
stopしてもコンテナは削除されません(停止時に削除したい場合は起動時に--rmオプションをつけます)。削除する場合はdocker rmを使い、コンテナIDをコンテナ名を引数に渡します。
イメージからコンテナを作成したあとで、docker history イメージ名:タグとすると、コンテナを作成する際に実行したコマンド群が表示されます。たいていのコマンドは長くて省略されてしまいますが、--no-runcとすると省略されません。
公式apache(httpd)イメージの変更
Dockerのapache(httpd)のイメージを変更し、PHPとSSLに対応させます。
イメージはレイヤー構造で加えていった変更を蓄積して成り立っています。「docker commit コンテナ名 イメージ名」として変更を加えていくこともできますが、トライ&エラーで修正していった変更点を覚えておいてDockerfileに記述してイメージを作成するとこの構造がスッキリしますし、変更時も楽です。
Dockerfileは元となるイメージを指定して、変更点を記述し別のイメージを作るためのファイルで、いうなれば設計図です。
まずは、httpdコンテナを起動させて変更点を探っていきます。この時、コンテナ名を指定しないとランダムな値になってしまうので、nameオプションでコンテナ名を指定すると、execで指定する際に便利です。
$ docker run -d -p 8000:80 --name="httpd-org" httpd
DockerのイメージにはベースとなるOSが存在します。OSの違いによってコマンドが変わるのでまずそれを確認します。方法は、Qiita:「Linux OSの種類とバージョンの調べ方」によると、cat /etc/*releaseとするそうです。
docker execでcatを実行する時、ワイルドカードがあると意図するようにならないので、bash -cを仲介させます。
$ docker exec httpd-org bash -c "cat /etc/*release" PRETTY_NAME="Debian GNU/Linux 10 (buster)" NAME="Debian GNU/Linux" VERSION_ID="10" VERSION="10 (buster)" VERSION_CODENAME=buster ID=debian HOME_URL="https://www.debian.org/" SUPPORT_URL="https://www.debian.org/support" BUG_REPORT_URL="https://bugs.debian.org/"
公式のhttpdイメージのベースはDebian10のようでした。DebianのパッケージマネージャーaptでPHPをインストールします。通常のDebianはaptの使用を推奨していますが、docker docsではapt-getを使っているのでそちらを利用します。
またインストール作業時に途中で止まらないように、-yオプション、余計なものをインストールしないために、--no-install-recommendsオプションをつけています。
apt-getでのインストールでは問題が起きることは少ないと思いますので、Dockerfileに書いてイメージを作成してみます。
Dockerfile
FROM httpd
RUN apt-get update; apt-get install -y --no-install-recommends vim procps openssl php php-mbstring php-mysql
ここからイメージを作成するには、「docker build」 を使います。-tオプションでイメージ名とタグをつけ、そのあとの引数でDockerfileのあるディレクトリを指定します。
コンテナ内でファイルを確認、編集したりプロセスを確認するため、vimやprocps(psやpkillコマンド)インストールしていますが、最終ビルドの時は外します。
指定した名前でイメージができますので、ここからコンテナを作って設定の修正をしていきます。-dオプションはデタッチで、バックグラウンドで処理させています。
コンテナが作成された後、設定ファイルを編集し、httpdを再起動させようとするとアプリとして機能しているプロセスがなくなるのが原因か、exit(0)でコンテナが終了してしまうようです。-dオプションのかわりに、-i(入力待機)オプションにして、別のターミナルから編集すればうまくいくのかもしれませんが、都度終了させることにして、停止によって設定が初期状態に戻らないように/usr/local/apache2/confをホストにコピーしてバインドマウントして利用します。
バインドマウントとはホストのディレクトリをコンテナ内のディレクトリに結び付ける事をいいます。
コピーをするにはコンテナが起動してる状態でdocker cpを使います。
$ docker cp httpd-org:/usr/local/apache2/conf/ ./
コピー終わったらバインドマウントの設定を加えて再起動します。再起動のたびにコンテナを削除(docker rm)するのは面倒なので--rmオプションをつけます。
バインド時は絶対パスで記述しないといけないので、$(pwd)で相対パスから変換しています。
$ docker run --rm -dp 8000:80 --mount type=bind,src=$(pwd)/conf,dst=/usr/local/apache2/conf --name="httpd-org" httpd-org:1.0
これで修正環境が整ったので、PHPから設定をしていきます。Qiita:「Apacheのhttpd.confにPHPを設定する方法」を参考にconf/extra/httpd-php73.confを作成します。
まず、コンテナ内からPHPライブラリの場所を探します。
$ docker exec httpd-org find / -name 'libphp*' /usr/lib/apache2/modules/libphp7.3.so
設定ファイルはDebianに慣れていれば探すまでもないかもしれませんが、php.iniをキーワードに検索します。
続いて、PHP用の設定ファイルをホストのconf/extra/httpd-php73.confとして作成します。
httpd-php73.conf
# MIMEタイプの追加 AddType application/x-httpd-php .php # モジュールの読み込み # 先ほど調べたモジュールの場所を記入します LoadModule php7_module "/usr/lib/apache2/modules/libphp7.3.so" # php.iniファイルの場所 PHPIniDir "/etc/php/7.3/apache2/php.ini"
作成したhttpd-php73.confを読み込む記述をhttpd.confにします。合わせてDirectoryIndexにindex.phpを加えてドメイン名でアクセスした際にindex.phpを表示させるようにします。
httpd.conf
... Include /usr/local/apache2/conf/extra/httpd-php73.conf ... # index.phpを追加 DirectoryIndex index.php index.html ...
終わったらコンテナを再起動します。起動しない場合は変更した箇所が原因なので修正します。
エラー時ログ確認したい場合は、docker logs コンテナ名とすればいいのですが、--rmオプションをつけているとコンテナ終了時にログも一緒に無くなってしまいます。悩ましいところです。
一旦--rmをはずして実行をすると筆者の場合次のようなエラーログがでていました。
$ docker logs httpd-org
[Sat Oct 16 06:37:47.286596 2021] [php7:crit] [pid 1:tid 140410776724608] Apache is running a threaded MPM, but your PHP Module is not compiled to be threadsafe. You need to recompile PHP.
httpd.conf
... # コメントアウト #LoadModule mpm_event_module modules/mod_mpm_event.so ... # コメントアウトを外す LoadModule mpm_prefork_module modules/mod_mpm_prefork.so ...
今度は起動できたので、docker execで、lsコマンドをコンテで実行してみてローカルに作成したhttpd-php73.confがあるか確認してみます。
$ docker run --rm -dp 8000:80 --mount type=bind,src=$(pwd)/conf,dst=/usr/local/apache2/conf --name="httpd-org" httpd-org:1.0
...
$ docker exec httpd-org ls /usr/local/apache2/conf/extra
...
httpd-mpm.conf
httpd-multilang-errordoc.conf
httpd-php73.conf
httpd-ssl.conf
...
index.phpを作って表示テストをしてみます。コンテナ内のドキュメントルートは/usr/local/apache2/htdocs/で、ここに後からWordPressのディレクトリをバインドさせますが、ここではホストのファイルをdocker cpでコピーします(終了時にコンテナ内のファイルは消えます)。
index.php
<?php
phpinfo(); ?>
表示できました。
SSL化
続いてSSL化の設定です。まずSSL用の鍵と証明書のセットですが、今回は稼働テストなので、ホストマシンのものを借りることにします。confディレクトリにcertsデイレクトリを作成し/etc/ssl/certs/ssl-cert-snakeoil.pemと、/etc/ssl/private/ssl-cert-snakeoil.keyをコピーしてきます。ホストマシンがDebianでOpenSSLが入っていればおそらく存在すると思います。
本番環境を考えている方は、拡張子が.pemの方が証明書、.keyの方が秘密鍵になるので置き換えて設定してください。
テスト環境でホストにそれらがなかった場合はOpenSSLで自己署名CAを作って配置します。CA証明書をブラウザに手動で取り込んでエラーのないSSL通信をしたいという時もそのような作業が必要になります。
conf/extra/httpd-ssl.confにコピーしてきた鍵のパスとServerNameを記述します。
ServerNameには証明のCommon Name(CN)を記載します。Subject Alternative Nameにも同じ値が入っていると思います。snakeoil証明書はホストマシンのホスト名で登録されていると思います。
不明な場合はGlobalSignGMO:「保存した証明書ファイルの内容を確認する方法」によると次のコマンドで確認できます。
# openssl x509 -text -noout -in /etc/ssl/certs/ssl/ssl-cert-snakeoil.pem ... Issuer: CN = deb-wordpress ... X509v3 Subject Alternative Name: DNS:deb-wordpress ...
httpd-ssl.conf
...
ServerName deb-wordpress
...
SSLCertificateFile "/usr/local/apache2/conf/certs/ssl-cert-snakeoil.pem"
...
SSLCertificateKeyFile "/usr/local/apache2/conf/certs/ssl-cert-snakeoil.key"
...
正規の証明書と秘密鍵を持っていて本番環境を設定している場合は中間CAの証明書もcertディレクトリにコピーして、「SSLCertificateChainFile」の箇所にその証明書へのパスを記入してください。
さらに、httpd.confにあるLoadModule ssl_moduleの行と、httpd-ssl.confを読み込む行のコメントアウトを解除します。
httpd.conf
LoadModule ssl_module modules/mod_ssl.so Include conf/extra/httpd-ssl.conf
またエラーが出てしまいました。
AH00526: Syntax error on line 92 of /usr/local/apache2/conf/extra/httpd-ssl.conf: SSLSessionCache: 'shmcb' session cache not supported (known names: ). Maybe you need to load the appropriate socache module (mod_socache_shmcb?).
「LoadModule socache_shmcb_module modules/mod_socache_shmcb.so」の行のコメントアウトの解除も必要でした。
これでSSL通信の準備ができたのでポートを変えて起動し、ブラウザよりアクセスしてみます。snakeoil証明書では証明書へのパスが見つからないので「保護されていない通信」となってしまいますが、それでもSSL通信はできています。
ここまでの作業では使いませんでしたが、稼働中のコンテナの中でbashを使いたい時はdocker execに-itオプションをつけてbashコマンドを実行します。-iは標準入力がない場合もそれを待ち受けるもの、-tは疑似ターミナルを結びつけるものです。また、今回はコンテナ内部の編集をcommitしないのでそのような事はないと思いますが、バインドしていない部分が原因でコンテナがexit(0)や他のエラーコードで起動できない(起動後すぐに終了してしまう)場合には、docker run -it イメージ名 bashとすることで、そのようなコンテナに入ることができます。
$ docker exec -it httpd-org bash $ docker run --rm -it httpd-org:1.1 bash
イメージの作成
ここまでの作業をCodeCampus:「Dockerfileの書き方と使い方」を参考に、Dockerfileにまとめます。元となるイメージをFROMに指定し、ホスト側のファイルを新しいイメージに移すCOPYと、コマンドを実行してイメージを修正するRUN、コンテナが作成された後のコマンドCMDを設定します。本来CMDのエントリーはひとつだけで、すでに親のhttpdで設定されていますが、上書きできるそうです。CMDの内容は元のイメージと同じようにしています。これは元のイメージを起動した後、docker history イメージ名とすると確認できます。
Dockerfile
FROM httpd RUN apt-get update; apt-get install -y --no-install-recommends openssl php php-mbstring php-mysql COPY conf/httpd.conf /usr/local/apache2/conf/httpd.conf COPY conf/extra/httpd-ssl.conf /usr/local/apache2/conf/extra/httpd-ssl.conf COPY conf/extra/httpd-php73.conf /usr/local/apache2/conf/extra/httpd-php73.conf COPY conf/certs /usr/local/apache2/conf/cert CMD ["httpd-foreground"]
イメージをビルドして稼働させてみます。先ほどまではホストのディレクトリをバインドしていましたが、ビルドの過程で設定ファイルをコピーしたので不要になります。
$ docker build -t httpd-org:1.1 . ... $ docker run --rm -dp 443:443 --name="httpd-org" httpd-org:1.1 ...
実は「EXPOSE 443」というポートを開ける記述を忘れていたのですが、問題なくうごきました。docker runでポートを指定したからOKという事なのでしょうか?
MariaDb連携
作成したオリジナルのhttpdをDocker ComposeでMariaDBと連携してみたいと思います。
WordPressの方はWORD PRESS.ORGのダウンロードページから、.tar.gzをダウンロードして展開して(wordpressディレクトリが作成されます)、httpdの公開ディレクトリにバインドします。
その際にwordpressディレクトリ内にあるwp-config-sample.phpを、wp-config.phpと名前を変更して、編集します。データベース名やユーザー名、パスワードを、docker-compose.ymlファイルに記述するMariaDBの設定と同じにすることと、ホスト名を「Dockerのサービス名:ポート」とするところが肝です。
wp-config.php
/** WordPress のためのデータベース名 */
define( 'DB_NAME','wpdb' );
/** MySQL データベースのユーザー名 */
define( 'DB_USER', 'wordpress' );
/** MySQL データベースのパスワード */
define( 'DB_PASSWORD', 'wppass' );
/** MySQL のホスト名 */
define( 'DB_HOST', 'db:3306' );
/** データベースのテーブルを作成する際のデータベースの文字セット */
define( 'DB_CHARSET', 'utf8mb4' );
...
Docker Compose用のdocker-compse.ymlファイルを作成します。versionはDocker Engineのバージョンに合わせて変更します。詳しくは公式のCompose fileに載っています。
Docker Composeの説明に関しては前回の記事で紹介していますので、ここでは省略させていただきます。
docker-compose.yml
version: "3.7" services: db: image: mariadb volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: rtwppass MYSQL_DATABASE: wpdb MYSQL_USER: wordpress MYSQL_PASSWORD: wppass web: depends_on: - db volumes: - ./wordpress:/usr/local/apache2/htdocs image: httpd-org:1.1 ports: - "443:443" restart: always volumes: db_data:
Docker Composeを起動し、Word Pressの挙動を確認します。問題なく動くようです。
参考にさせていただいたサイトの皆様、ありがとうございました。