AWS EC2でDocker環境構築/PHP5.4とMySQL5.7

備忘録です。AWS EC2インスタンスの中で、PHP5.4とMySQL5.7が動くDocker環境を構築しました。

スポンサーリンク

なんでPHP5.4なん?

MySQL5.7のほうはともかく、PHPが何故5.4なのか。今時、PHPのバージョンは7とか8とかなのに。

自分で自分の好きなアプリを作りたい場合は、それぞれのサービスのバージョンは最新のものでいいと思います。ですが受託開発の場合は、客先環境に縛りがあったり、開発チームの縛りがあったりして、古いバージョンに合わせないといけないケースが多々あります。

どこにどうやって環境を構築するか

いわゆるLAMPやXAMPPのような環境は、どこにでも構築できます。WindowsでもMacOSでもLinuxでもなんでもござれ、です。

だったら、普段から慣れ親しんだWindowsの開発用PC(兼、遊び用PC)の中に構築するのが一番ラクって話になります。ですが、安易にその方法を採ってしまうと、あとで泣きを見ることになります。

今回の案件の本番環境はLinuxです。実際はOSがなんであろうと、その手前で動くApacheやPHPやMySQLなどがOSの違いを吸収してくれるので、開発用テスト環境をWindowsで構築してもいいです。いいはずです。

が、どこに罠があるかわかりません。改行コードの違い、文字コードの違い、依存モジュールのバージョンの違い、等々。それを未然に防ぐには、やはり本番環境と同じLinuxでいきたいところです。

ここで、考え得る選択肢は5つありました。

  • Windows内のVirtualBoxでLinux
  • Windows内のVirtualBoxでLinux、さらにその中でDocker
  • Docker Desktop for Windows
  • AWS EC2のLinuxインスタンス(以前から構築済)
  • AWS EC2のLinuxインスタンス(以前から構築済)、さらにその中でDocker

Windows + VirtualBox

Windows+VirtualBox

一番ラクそうな方法ですが、却下

Oracle VM VirtualBoxはWindowsで動作するとても優秀な仮想環境エミュレーターです。設定や起動などの操作がとてもわかりやすく、CentOSやUbuntuなどの主要なLinuxディストリビューションをisoイメージから簡単にインストールすることができ、さらにVirtualBoxの動作自体も非常にコンパクトです。

こんないいことずくめのVirtualBox。もちろん一番最初に候補に挙がりましたが、一つだけ難点があります。僕は普段デスクトップPCで作業を行っているのですが、出張時だけはノートPCで作業をします。なので、デスクトップPCでVirtualBoxの環境を構築してしまうと、出張時は作業ができなくなってしまうのです。

一応その回避策もあります。一つは、出張時だけ仮想環境の仮想ディスクイメージ等のファイル一式をノートPCにコピーする方法。ノートPCにもVirtualBoxをインストールしておけばこの方法が可能ですが、なにせファイルのサイズがバカでかい。1GB、2GB、平気でイッちゃいます。出張直前の準備のとき、「さて、そろそろファイルコピーするか」ってコピーを始めたら、数十分とか数時間とかかかって、やべぇ電車の時間に間に合わないうぎゃー! ってことになりかねません。このような事態に陥るかもしれないという恐怖と常に隣り合わせというのは、とても心臓に悪いです。

もう一つの方法は、自宅にサーバーを置いて、外部からアクセス可能にするという方法。この方法自体は不可能ではないですが、構成がいろいろ面倒です。自宅のルーターにポートフォワードの設定をするのもなんかイヤだし、自宅サーバーという案はあまり採りたくありません。

というわけで、せっかく優秀なVirtualBoxなんですが、却下となりました。

Windows + VirtualBox + Docker

Windows+VirtualBox+Docker

VirtualBoxが既に却下なので、これも却下です。

以前、別案件でこの構成をしたことがあったんですが、今回の案件にはそぐわないので、却下。

Docker Desktop for Windows

Docker Desktop for Windows

Windowsで直接Dockerとな。へぇ、便利な時代になったもんだねぇ。でも、却下

理由はさっきと同じです。結局、普段から使ってるデスクトップPCにインストールする方法では、どう頑張っても出張時に柔軟な対応ができません。

さらにこのDocker Desktop for Windowsは、Hyper-Vのもとで動作します。このHyper-V、いろいろと鬱陶しいんですよ。なんか別のアプリが動作しなくなったり、ホストPC(=普段使ってるデスクトップPC)のネットワーク設定がいろいろごちゃごちゃになったり。

Hyper-Vに限らず、Microsoftのアプリというのは、基本的にWindowsの普段使っている設定等を大幅に侵食してきます。IISなんかがその最たる例ですね。その点さきほどのVirtualBoxは、Windowsを侵食せずに、単体でコンパクトに動作してくれます。

というわけで、Docker Desktop for Windowsという選択は、却下オブ却下です。

AWS EC2

AWS EC2

サーバーをクラウドに置いてしまえば、出張問題は解決です。しかも僕は既にAWS EC2のインスタンスを持っていて、固定IPもドメインも割り当てています。これでいいんじゃないかと思ったんですが、惜しい! 却下

というのも、このインスタンスでは既にPHP7系が動作しており、今回の案件で必要なPHP5.4とはバージョンが違います。一応バージョンを切り分ける方法もあるとは思いますが、そこでごにょごにょ思い悩むのはあまり得策ではありません。悩むだけならまだしも、既存の環境を壊してしまったら大変です。

AWS EC2で一般的に提供されているAmazon Linux AMI特有の変なクセも避けたいところです。Amazon Linux AMIはRedHatをベースにしているとのことですが、いろいろとディレクトリ構成などが違っていて、ググって調べた一般的なLinuxの情報をもとにコマンドを叩いても全然動作しない、ということがザラに起きます。

というか、既存の環境もそのせいで変な状態になってしまい、もはや半分メンテナンス不可能な状態になってしまっています。僕が悪いといえばそれまでなんですが、とにかく何をググるにしても、「Linuxの」ではなく「AWSの」情報から引っ張ってこないといけないので、なかなか面倒です。

んだば、もう一つEC2インスタンスを立てればええやん、って思うかもしれませんが、それこそ金の無駄です。なんやかんや月額かかりますし。

AWS EC2 + Docker

AWS EC2 + Docker

というわけで、ベストプラクティスはこれ。採用

EC2インスタンスの直下には、先ほど説明した通りの半分壊れかけの環境がギリギリで動いていますが、その中でさらにDocker環境を構築すれば、完全に独立したクリーンな環境ができあがります。

ほんと、Dockerって便利っすね。

ここで、ぼくのスペック

僕の普段の仕事はほぼ100%、Windowsデスクトップ(かつ、スタンドアロン)アプリの開発です。要するに、WebとかLinuxとかに疎いんすよ。疎いって言ってもまぁ適当にググって頑張ればなんとかできる程度の知識と経験はありますが、常日頃からそういうのに触ってる系のプログラマーに比べたら、かなり疎いほうです。

Dockerは単体で動かしたことは一度ありましたが、Docker Composeってのがあるんですね、へぇ。なんか便利そうだけど、初めてだし、ちゃんとうまくできるかなぁ。

というわけで、今回の環境構築は、僕のDocker Composeのお勉強という側面もありました。

プログラマのためのDocker教科書 第2版 インフラの基礎知識&コードによる環境構築の自動化

ファイル構成

ファイル構成

適当なワークディレクトリ(上図の場合、Docker)の下のファイル構成はこんな感じです。一つ一つ見ていきます。

docker-compose.yml

version '3'
services:
  mysql:
    build: ./mysql/
    volumes:
      - mysql-store:/var/lib/mysql
    ports:
      - 3506:3306
    environment:
      - MYSQL_ROOT_PASSWORD=xxxxxxxx #ルートユーザーのパスワード
      - MYSQL_DATABASE=xxxxxx #データベース名
      - MYSQL_USER=xxxxxx #ユーザー名
      - MYSQL_PASSWORD=xxxxxx #ユーザーのパスワード
  php:
    build: ./php/
    volumes:
      - ./php/php.ini:/usr/local/etc/php/php.ini
      - ./php/html:/var/www/html
    ports:
      - 8085:80
    extra_hosts:
      - "hosts.docker.internal:host-gateway"
    depends_on:
      - mysql
volumes:
  mysql-store:

mysqlのところでは、mysql-store:という名前付きボリュームを定義しています。名前付きボリュームは、末尾のvolumes:にも上記のように記述する必要があります。このように名前付きボリュームで指定しておかないと、アクセス権限に引っかかって上手く動作しなくなることがあります。

MySQLは通常、ポート3306で接続をLISTENします。ホストのポート3506をDockerコンテナ内のポート3306に通すために、ports: – 3506:3306 としています。ホストの待ち受けポートも同じ3306にしてもいいんですが、「この開発案件のDockerはこれ」と区別するために、このように別の番号で待ち受けるようにしています。

php(というか、Apache+PHP)のところでも、ホストのポート8085をDockerコンテナ内のポート80に通すようにしています。これにより、http://ホストのグローバルIP:8085/ でDockerコンテナ内のコンテンツにアクセスできるようになります。

phpのextra_hosts:のところの記述は、Docker内からホストにアクセスする際に必要な記述です。Dockerコンテナが起動されたとき、ホストとDockerコンテナを含む小さなローカルネットワークが自動的に構成されます。例えばこんな感じです。

  • ホスト -- 192.168.204.1
  • Dockerコンテナ1 -- 192.168.204.4
  • Dockerコンテナ2 -- 192.168.204.5

しかしこのIPアドレスはDocker起動ごとに可変で、どんなアドレスになるかわかりません。特に、Dockerコンテナ内からホストにアクセスしたいとき、そのIPアドレス(またはホスト名)がわからないと非常に困ります。そこで、「"hosts.docker.internal"という特別なホスト名はホストマシンを指す」ように指示するのが、このextra_hosts:の記述の内容になります。

ちなみに、Docker Desktop for WindowsやMacOS用のDockerでは、このextra_hostsの記述は必要ありません。何もしなくても、"hosts.docker.internal"が自動的にホストマシンを指してくれます。Linux版Dockerにはそのような機能が無いので、extra_hostsの記述が必要というわけです。

mysql/DockerFile

FROM mysql:5.7
COPY ./my.cnf /etc/mysql/conf.d/my.cnf
CMD ["mysqld"]

docker-compose.ymlで定義したmysqlサービスの構成方法を記述します。ベースとなる公式コンテナイメージはmysql:5.7。ちょこっとだけ設定ファイルmy.cnfの内容を加え、サービスを起動します。

mysql/my.cnf

[mysqld]
character-set-server=utf8
[mysql]
default-character-set=utf8
[client]
default-character-set=utf8

MySQLの文字コードをUTF8にするよう指示しておきます。

php/Dockerfile

FROM php:5.4-apache
RUN apt-get update && apt-get install -y libonig-dev && \
  docker-php-ext-install pdo_mysql mysqli mbstring
RUN pecl install xdebug-2.3.3
RUN docker-php-ext-enable xdebug

phpサービスは、Apache+PHP5.4の公式コンテナイメージをベースにします。mysqliやmbstringなどの各種モジュールも併せてインストールします。さらに、xdebugもインストールしておきます。

実は、細かいことは僕もよくわかってません。こう書いたら上手くいったので、まぁこれでいいや、と。

php/html/

ホストマシンのこのディレクトリが、Dockerコンテナ内の/var/www/html/にマッピングされます。つまりアレです、ドキュメントルートです。最初は中身は空です。

php/php.ini

[xdebug]
xdebug.remote_enable=1
xdebug.remote_autostart=1
xdebug.remote_host=host.docker.internal
xdebug.remote_port=9001
xdebug.remote_connect_back=0
xdebug.remote_handler="dbgp"
[Date]
date.timezone="Asia/Tokyo"
[mbstring]
mbstring.internal_encoding="UTF-8"
mbstring.language="Japanese"

php.iniに追記する内容をここに置いておきます。特に、xdebugに関する記述は重要です(後述します)。

PHPのデバッグ

プログラミングにデバッグは欠かせません。ブレークポイントを張ることができて変数をウォッチすることができる状況とそうでない状況では、5億倍くらい開発効率が違います

PHPの場合、PHPにxdebugをインストールしておけば、リモートデバッグもできるようになります。xdebugはいい意味で「枯れた技術」ですが、開発環境が何であってもxdebugはとにかくxdebugの作法に則ってデバッグ情報を通知するだけ、という良いシンプルさがあります。

xdebugは、PHPが動作するマシンから開発マシンに対して、ポート9000番(デフォルト)で接続してきます。

xdebug

例えばこのような状況の場合、php.iniの内容は次のようになります。

[xdebug]
xdebug.remote_host=192.168.0.20
xdebug.remote_port=9000

基本的にはこれでいいのですが、実際の状況はこんなに簡単ではありません。まず開発用PC(自分のPC)は、自宅ルータ内にあります。自宅のインターネット環境はフレッツだとか何だとかのプロバイダに左右されるので、グローバルIPは固定ではありません。さらにWebサーバー側はAWS内のさらにDocker内にあります。

この接続の矢印が逆向きだったらまだ簡単なのですが、xdebugってやつは、「むこうから」接続してきます。これをうまくアドレス解決してあげなくてはいけません。

xdebugの経路

左側の自宅のルーターは通常、セキュリティの観点から、外部からの接続は全て遮断しています。というか、家庭用のルーターの初期設定は大体そうなっています。このルーターの設定を変えて、外部からのポート9000番への接続だけを通して自分のPCへ流すようにポートフォワードの設定をしてやれば、一応外部からの接続は受け入れられ、④の経路は確保できます。ですが、この設定はあまりやりたくありません。セキュリティ的にイヤですし、「用が済んだらポートを閉じる」という作業をついつい忘れてしまいそうだからです。

仮にそれを許すとしても、インターネットプロバイダから付与されるグローバルIPアドレスが可変だとどうしようもありません。パソコンを起動するたび(自宅ルーターがインターネットに接続するたび)にグローバルIPアドレスはころころ変わります。それを固定にする有料のオプションがあったような気もしますが、無かったような気もします。有ったとしても、金はかけたくありません。

というわけで、この方法だと、③の経路が確保できません。①と②はDockerやAWSの設定次第でどうにでもなるとしても、結局③で詰んでしまいます。

こういう場合、SSHトンネルという方法が、問題を解決してくれます。

SSHトンネル

あらかじめ、左側の自宅PCからAWSに対して、SSH接続を行っておきます。この向きの矢印の接続は、単純にAWSのグローバルIPを指定すればいいだけなので、簡単です。

このSSH接続の際、「SSHの接続先(AWS EC2インスタンス)のポート9000番に来た接続は、SSHの接続元(自宅PC)のポート9000番に流しなさい」というSSHトンネルの指定をすることができます。

SSHトンネルの中を通る接続に関しては、アドレス解決の必要はありません。あらかじめ掘ってあるトンネルの「出元」と「出先」は既にわかっているわけですから、そこを逆流していけばいいだけです。SSH接続にはこのようなトンネル機能があります。便利ですね。

ちなみに、SSHトンネルを使うケースの多くは、上図のような逆流(リモートフォワード)ではなく、トンネルを掘った方向と同じ方向の接続(ローカルフォワード)を行うというケースです。暗号化されたSSH通信の中を通すことでセキュリティを高めるというのがその目的です。

SSHトンネルを紹介している文献の中には、あまり使われない逆流のほうの設定方法を解説していない場合があります。僕はこっちのほうがむしろ有用だと思うんですけどねぇ。


で、トンネルの掘り方ですが、SSHクライアントソフトのPuTTYでの設定方法を紹介します。

Source Portは、①の接続先(トンネルの終端)で待ち受けるポート番号です。つまり、xdebugの接続先ポートです。

Destinationは、自宅PCの待ち受けポートです。自宅PCから見て自宅PCはlocalhostなので、localhost:9000のように指定します。

そして、Remoteを選択します。これで、リモートフォワード(逆流)になります。

あとは、この設定でSSH接続をして、PuTTYは閉じずに放置しておきます。どうせターミナルで何か操作をすることもあるでしょうから、一石二鳥かと思います。

PuTTY以外のSSHクライアントソフトでも同様の設定が可能なはずですが、ここでは一般的な設定方法(SSHコマンドの指定方法)は割愛します。


さて、ここで一つ問題があります。

sshdの設定

SSHトンネルは、AWS EC2インスタンスのポート9000番への接続をリモートフォワードします。このAWS EC2インスタンスは、VPCから見れば172.31.17.123であり、Docker Networkから見れば192.168.xxx.1です。ですがSSHトンネルの終点は172.31.17.123のほうなので、リモートフォワードは172.31.17.123:9000への接続に対してしか発動しません

一方のxdebugは192.168.xxx.1:9000に接続しにいきます。つまり、このままではリモートフォワードは発動しません。どのネットワークからの接続でもいいようにするためにSource Portを0.0.0.0:9000とでもしたいところですが、そういう設定はできないようになっています。

これの解決策としては、AWS EC2で稼働しているsshdの設定を変更するという方法があります。sshdの設定は/etc/ssh/sshd_configにありますので、そこで

GatewayPorts yes

としておいて、sshdを再起動しましょう。おそらく初期設定では「#GatewayPorts No」となっていると思うので、コメントアウトを外してNoをYesに変えればOKです。sshdの再起動は、「systemctl restart sshd」「service sshd restart」などで行います。

これで、xdebugがどのネットワークからAWS EC2に接続しようとも、とにかくAWS EC2インスタンスの9000番ポートへの接続は全部SSHトンネルのリモートフォワードが発動するようになります。

環境構築でいつも思うこと

さらっと一連流しましたが、これだけで2日潰してしまいました。まぁ、そもそも僕がDocker Composeを触るのが初めてで、そのお勉強も兼ねていたので、想定の範囲内です。

一番苦労したのはxdebugの設定でした。一度SSHトンネルを使用したことはあったんですが、すっかりそのことを忘れてて、どうしたらいいのか途方に暮れてました。それを思い出したのも束の間、今度はDocker内からリモートフォワードが発動できないという事態に。

もう少し手前の作業では、hosts.docker.internalの指定にも悩んでました。ググった情報をもとにやってるはずなのにうまくいかない…と思ったら、「Linux版Dockerでは使えません」ってなってて、うがががー。

ネットワークの経路というのも、自分が「多分こうだろう」と想像しているものと実際のモノが違ったりすると、全く動いてくれません。何が間違ってるのかもわからず、ひたすら沼に…。

沼

この手の環境構築というのは、ググれば確かにたくさん情報は出てくるのですが、人によって状況は千差万別です。

OSには大きく分けてLinux、Windows、MacOSがあり、同じLinuxでもRedHat系とDebian系がある。今はだいぶ慣れてきましたが、最初の頃は「apt-getってやってるのに動かん!(正解はyum)」という段階でも沼ってました。特にAmazon Linux AMIは、RedHat系と言いながらもいろんな変なカスタマイズがされているので、ググった情報とディレクトリ構造が全然違ったりして、よく沼ります

PHPもバージョンの差がいろいろあって沼です。記事内のxdebugの設定のところで「xdebug.remote_host」という項目を説明しましたが、これはxdebug2の場合です。xdebug3の場合はこれが「xdebug.client_host」になります。えっ、remoteとclientって逆やん。なんで名前変えたの…。そりゃぁ、どっちを主語にするかによってremoteとclientは逆になるけど、そんな真逆の変更をしなくてもいいのに…。とにかく、ググった情報がxdebug2のモノなのかxdebug3のモノなのかを見間違えると、沼直行です。

今回はApacheを採用しましたが、WebサーバーにはnginxやIISなどのいろんなバリエーションがあります。

開発環境もいろいろです。PHPの場合、PhpStomeが主流のようですが、VS Code+PHP Debugという選択もあります。僕はそのどちらでもなくて、Visual Studio(Codeじゃないほう)+PHP Tools for Visual Studioという組み合わせを使っています。

要するに、「OSの違い × Webサーバーの違い × なんやかんやのバージョンの違い × etc…」という膨大な数の組み合わせそれぞれに対して、問題の解決方法が違ってくるということです。この記事で紹介した方法も、別の人の状況下では全く役に立たなかったりします。

ググれば確かにそれっぽい情報は出てきますが、自分が構築しようとしている環境と全く同じとは限りません。環境が少しでも違うと、コマンド1つもまともに通りません。

このような沼を解決するためには、「ああ、この文献はapt-getで書いてあるけど、自分の場合はyumだな」みたいな読み替えができなければいけません。つまり、両方の作法を知っておかなければならないということです。

特に僕のような「普段はWindowsオンリー」な人間にとっては、年に1回あるかないかの環境構築は、なかなか骨の折れる作業です。1年も経てば世の中の技術は大きく変わってるのに、自分はそれに全然追従できていない。その都度新しいことを勉強するだけの多大な時間を要して、なんとか少しずつ身に着けていく。

僕はWindows畑の人間なのでアレなんですが、こういうのに慣れてる人って、簡単にサクサクっとイケるもんなんですかね? というか、単に僕が老化してるだけですかね??

スポンサーリンク