未来を変える日記

ETロボコンを目指して

Docker (6) VPSでWordpressを動かそう!

 Dockerシリーズ記事の最後は、自主課題で制作したVPSで動くWordpressコンテナセットの話です。

f:id:corgi-eric:20210131210313p:plain:w100
https://hub.docker.com/_/wordpress

はじめに

何を作るか?

 当方は学習用にVPSサーバー(Digital Ocean)をレンタルしてて、Ubuntuを動かしています。Dockerを利用してWordpressをVPS上で簡単に動かせたら面白そうです。
 というのも、Wordpressのコンテナ単体では成立しませんので。実現するにはDocker-composeで、Webサーバー、PHPサーバー、データベースも束ねてやる必要があり、これは良い勉強になるんじゃないか?と思った次第。

どんな構成か?

 先ず、前述の内容を視覚化。イメージが湧くようにします。

f:id:corgi-eric:20210131214207p:plain:w450
3層アーキテクチャ

上のイメージは以下の3要素から構成されます。

  • Nginx:Webサーバー。外部からアクセスを受ける窓口役。
  • PHPとWordpress:APサーバー。コンテンツ生成役。
  • MySQL:データーベース。コンテンツ元データ保管役。

 書き出してみて気が付きました。3層アーキテクチャですね。

課題は何か?

 今度はイメージを実現する上での課題点を書き出してみます。
 こんな所でしょうか。

  • Nginxの設定はどうすれば良いのか?
  • 各コンテナ間の連携は?
  • phpに必要なextensionは?
  • https化に必要な認証書はどうするのか?
  • そのままVPSにデプロイできるのか?

 課題をこなすには、ITインフラ知識が重要そうでした。

メンタ―を探す

 課題まで書き出してみると、最初に学習したDocker教材の範囲を超えたITインフラ周りの知識が必要な事がわかりました。それらを一つ一つを丁寧に勉強できれば理想。しかし、時間は限られます。
 なので、基本的には独力で進めつつ、行き詰った時に話を聞けるよう詳しい方からのアドバイスも伺えるようにしました!

menta.work

最終的な成果物

 Git Hubに公開しておきます。

 それでは、ここまでたどり着くのに引っかかった点を記載します。

WordpressとMySQLを接続

 これは簡単でした。Dockerイメージを利用するなら、docker-compose.ymlに、それぞれの環境変数を設定するだけです。

Wordpress側:

WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: ${DB_USER}
WORDPRESS_DB_PASSWORD: ${DB_PASS}
WORDPRESS_DB_NAME: ${DB_DATABASE}

MySQL側:

MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASS}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}

 Docker Hubにも記入サンプルがあります。

通信はどうしてるか?

 環境変数を指定するとコンテナ同士で通信ができる。これが、当初は謎でした。しかし、MySQLのイメージを確認すると通信Portが開いておりました。

f:id:corgi-eric:20210201045148p:plain:w350
Docker DesktopのInspect画面

 Not bindedとは、dockerfileでEXPOSE指示して開けたポートのことです。
 このようにDocker Hubのイメージは、全くの素の状態ではなく、使いやすいような設定変更が事前に施されております。

phpのソケットを設定する

 続いて、phpのコンテナとNginxを通信させます。UNIXドメインソケットというのを利用します。

ソケット?

 TCP/UDPのポートとは異なる通信の仕組みです。

 要は以下のようなファイル共有をDockerで実装してやる。

f:id:corgi-eric:20210201054232p:plain:w400
php-fpm.sockを共有

docker-compose.yml

 NgnixとPHPのコンテナにそれぞれ次の指示を加えます。

volumes:
  - php-fpm-socket:/var/run/php-fpm
PHP側の設定

 続いて、PHPのコンテナにphp-fpm.sockを準備させます。
 以下の設定ファイル(zzz-www.conf )を準備。dockerfileにてPHPのコンテナの /usr/local/etc/php-fpm.d/ に書き込みます。

[www]
listen = /var/run/php-fpm/php-fpm.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0666

 0666 はパーミッション設定。Chmodコマンドが参考になります。

 ファイル名を zzz-www.conf にする理由はこちらを参考に。

 上でowner/groupをNginxとしてますが、PHPコンテナにそのようなユーザーはおりませんので、dockerfileを利用し、予め追加してやります。

RUN addgroup nginx
RUN adduser nginx -D -G nginx
Nginx側の設定

 当方は次の2つの設定ファイルを準備して、dockerfileにてNginxコンテナの内に配置しました。

 1個目のNginx.conf はコンテナに元々書き込まれてたファイルを抜き出して変更したもの。ログの掃き出し設定と読み込む設定ファイル(wd.conf)を指定してます。
 その次の wd.conf は、以下の公式サンプルを読みつつ作成してます。

動作確認

 ここで一旦、NginxからPHPに到達できることを確認するべくlocalhostにphp infoを表示させてみます。

 先ず、次の内容の index.php ファイルを準備し、Nginxコンテナの例えば /var/www/php-test に配置。

<?php
var_dump( phpinfo() );
?>

 Nginxの設定ファイルのドキュメントディレクトリを設定。

location / {
  root /var/www/php-test;
}

 NginxとPHPコンテナを起動。localhostにアクセス。以下のようなphp info画面が表示されればソケットの設定はできています。

f:id:corgi-eric:20210201063139p:plain

HTTPS証明書を取得する

 続いて、httpではなく、httpsアクセスが通るように証明書を取得し、nginxの設定を変更します。

どうやって取得する?

 先ずは認証書を取得してみます。Let's encryptを使えば無料で取得可能です。

 証明書の取得クライアントですが、自分は下の Lego を利用しました。

 使い方は上の公式にありますが、Digital Oceanなら以下のようなコマンドで手動で使えます。WindowsでもDocker経由で気軽にLinuxコマンドが使えるから便利ですね。

docker container run --rm -it -e DO_AUTH_TOKEN=XXXXXXX \
xenolf/lego --email example@example.com --domains my.sample.com \
--domains *.sample.com run

 環境変数(-e)で設定しているAUTH_TOKENですが、利用してるドメインサーバーから入手します。Digital Oceanの場合は、以下にその方法が記載されてます。

legoをNginxコンテナに組み込む

 取得した認証書ファイルを利用するのはNginxなので、Nginxコンテナに組み込んでしまいます。
 Nginxのdockerfileに以下を記載します。legoと共にsupervisorも組み込んでいますが、これは次の「自動更新スクリプト」の所で説明します。その下の mkdirでは、認証書を保管する場所を準備してます。

RUN apk add --no-cache lego supervisor
RUN mkdir /lego
RUN mkdir /lego/accounts
RUN mkdir /lego/certificates
自動更新スクリプトを作成

 認証書は取得して終わりではなく、90日の有効期限があります。手動で再取得は面倒ですので、自動でやれるように簡単なシェルスクリプトを組んでしまいます。
 


 スクリプトの処理は以下の通り。

  • 「_.domains.crt」は有るか?タイムスタンプは?
    • 無ければ新規取得(lego_new)
    • 期限切れなら新規取得(lego_new)
    • 期限近いなら更新(lego_renew)
    • 期限が十分なら何もしない
  • lego_new / lego_renew実施時はNginxを再起動
スクリプトを定期実行

 前述のスクリプトをLinuxのCronを使って定期的に実行してやります。

 次のような設定ファイル(cron-setting)を準備します。

 0 1 * * * /tmp/shellscript.sh
 @reboot /tmp/shellscript.sh
 #

 内容は、起動時と毎日午前1時にshellscriptを実行せよ!です。
 そしてdockerfileに以下を書き足し、設定ファイルのコピーやら権限付与やらを行います。

COPY /script/cron-setting /etc/cron.d/cron-setting
COPY /script/shellscript.sh /tmp/shellscript.sh
RUN touch /tmp/shellscript.log
RUN chmod 0644 /etc/cron.d/cron-setting
RUN chmod 0744 /tmp/shellscript.sh
RUN chmod 0644 /tmp/shellscript.log
RUN crontab /etc/cron.d/cron-setting
supervisorを設定する

 さて、Nginxのコンテナにlegoを組み込み、これをcronからのシェルスクリプトで定期実行させようとしています。Docker内でNginxとcronを同時起動してやる必要があります。

 メンタからSupervisordというプロセスマネージャが使えると教えて頂きました!

 Supervisord用に組み込んだ設定ファイルはこちら。

 そして、NginxのDockerfile

COPY /app.conf /etc/supervisord/conf.d/app.conf
CMD [ "/usr/bin/supervisord", "-c", "/etc/supervisord/conf.d/app.conf" ]

 Nginxとcronを同時起動させるようにした設定ファイルを組み込み(COPY)、コンテナ起動時に Supervisord が立ち上がるように指示します(CMD)。

Nginxの設定

 まだあります。認証書が取得できたら、今度はHTTPSでサーバーが動くように設定してあげないといけません。必要な設定は以下の2つ。

  • httpアクセスは拒絶せず、httpsにリダイレクトする
  • httpsアクセス用にLet's Encryptの証明書を認識させる

 そこで、Nginxの設定ファイル(上で使った wd.conf)に以下を書き込みます。
 先ず、リダイレクト設定

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    return 301 https://$host$request_uri;
}

 続いて、httpsアクセス設定と証明書の組み込み。

server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;        
        ssl_certificate /lego/certificates/_.masa-nakajima.com.crt;
        ssl_certificate_key /lego/certificates/_.masa-nakajima.com.key;    
        server_name localhost.masa-nakajima.com;

 なお、server_name を localhost.masa-nakajima.com としています。これは、ローカル環境で使うlocalhostでは証明書の検証ができないためです。そこで、Let's encrypt からはワイルドカード認証書(*.masa-nakajima.com)取得し、ローカルマシンでは localhost.masa-nakajima.com を使ったのでした。
 
 これらの設定は、SSL Configuration Generatorを参考にさせてもらいました!

PHPのextensionを導入する

 簡単そうで超難しかったPHPへのextension組み込み。

  • 必要な extension が分からない。
  • PHPのextensionに必要なLinuxパッケージがある
  • しかし、コンテナがalpine linuxなので、パッケージ名が違う

という3点で苦しみ、メンタさんからの助言を頂きました。
結局、以下をphpのdockerfileに書き加えました。

RUN apk upgrade --update && apk add --no-cache oniguruma libpng-dev libjpeg freetype-dev libjpeg-turbo-dev libzip-dev gettext-dev\
    && docker-php-ext-configure gd --with-freetype --with-jpeg\
    && docker-php-ext-install -j$(nproc) gd 
RUN docker-php-ext-install mysqli zip gettext opcache bcmath sockets

 extension探しで参考になったのは以下あたり。

 公式にあるCore extensionsの説明とサンプル

 docker-php-extension-installerのGitHub

 Alpine Linuxのパッケージ検索

VPSへデプロイする

 駆け足で説明しましたが、いよいよ最後です。
 ローカル環境で無事動くことを確認したら、VPSへデプロイするという作業が待ってます。

先ずファイルの転送!

 3経路考えられます。それぞれ一長一短です。

  1. GitHubのPublicレポジトリからクローンする
  2. GitHubのPrivateレポジトリからクローンする
  3. SSL通信で直接転送する(scpコマンド)

 最も単純明快なのは1番なのですが、HTTPS認証書に使うアクセストークンなど公開できないファイルもあるので、これらを別な手段で送付してやる必要があります。
 2番は、公開用と非公開用でレポジトリが二重管理になるのが面倒。
 
 今回は3番でやりました。以下のようなコマンドです。

scp -r wordpress  user@ip-address:wordpress

 (-r)はディレクトリ転送オプション。
 開発環境のWindowsからWSLのUbuntuを立ち上げ、このコマンドを使いますと、VPSのUbuntuにSSL通信して、wordpressフォルダを全転送して下さいます。
 なお、事前のSSL鍵認証設定は済ませておく必要があります。


 かくして、GitHubと二重管理になることもなく、ローカルと同じファイルのクローンをVPS上に転送できたのでした。

ファイヤウォールの設定

 VPS環境でdockerが動いても、Nginxが外部と通信できないとWordpressが表示できません。そこで、ファイヤウォールの設定を変更し、新たにポート80番と443番を開けてやります。

f:id:corgi-eric:20210201212425p:plain:w400
Port 80, 443 を新たに開く

 いずれも、WebサーバーであるNginx用にDocker-compose.yml で指定したポートですね。

    ports:
      - 443:443
      - 80:80
$ sudo ufw allow 80
$ sudo ufw allow 443
$ sudo ufw status

 3行目のstatusで開放したポートを確認します。22番はSSL通信用で元々開けておいたもの。

f:id:corgi-eric:20210201212846p:plain
 
 あとは、以下の2行で無事起動するはずです。

$ docker-compose build
$ docker-compose up -d

 実際には多少の四苦八苦がありましたが、WordpressコンテナセットをVPS上にデプロイして、動かすことができました!
 ちゃんと、ブラウザのアドレスバー左側に鍵マークもあって、httpsで接続できていることが示されています。

f:id:corgi-eric:20210201213215p:plain

まとめ

 以上、Docker学習の総仕上げの自主課題として、VPSで動くWordpressコンテナセットを作成し、実際にデプロイして、動かす所までやれました。

 Udemy教材完了後から1か月近くと偉い時間はかかりましたが、教材を流して終わりでは決して得られない経験が得られたので、良かったかな??( ´艸`)

 兎にも角にも、次の基本情報処理技術者の資格学習に移れそうで、一安心です。