Docker Composeで3層ウェブアーキテクチャを構成する
こんにちは。データサイエンスチームのtmtkです。
この記事では、3層ウェブアプリケーションを構築することを通してDocker Composeの使い方をお伝えします。
はじめに
Docker Composeは複数のDockerコンテナを一つのホストマシンに立ち上げることができるツールです。公式ドキュメントでは用途の例として開発環境の構築やCI、CDへの応用が挙げられています。
3層ウェブアプリケーションは、ウェブサーバ、アプリケーションサーバ、データベースサーバの3層から構築されるウェブアプリケーションです。
この記事では、3層ウェブアプリケーションをDocker Composeで構築する手順を追うことで、DockerやDocker Composeの使い方を体得することを目的にしています。
(今回構築する3層ウェブアプリケーションの全体像)
実習
まず、Ubuntu 18.04が入っているサーバを用意します。AWSのEC2インスタンスをつかってもいいですし、手元のパソコンで仮想マシンを構築してもいいです。
Dockerのインストール
Dockerの公式ドキュメントのとおりにDockerをインストールします。
sudo apt-get update sudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ gnupg-agent \ software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) \ stable" sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io
Post-installation steps for Linuxも実行しておきます。
sudo groupadd docker sudo usermod -aG docker $USER exit
公式ドキュメントのとおり、Docker Composeをインストールします。
sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose
Docker Composeを使う
ここで、Docker Composeの作業用のディレクトリを作っておきます。
mkdir dockercompose_techblog cd dockercompose_techblog
データベースサーバを立てる
データベースサーバとして、MySQLが動くコンテナを構築します。
Docker Hubのmysqlのページを参照しながら、MySQLサーバを構築します。まずはページの説明どおりにMySQLのコンテナを動かしてみます。以下のコマンドで、MySQL 5.7のコンテナにdatabaseという名前をつけ、ルートユーザのパスワードを「my-secret-pw」にして立ち上げます。
docker run --name database -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:5.7
コンテナ内で作業するために、コンテナ内でBashを立ち上げます。
docker exec -it database /bin/bash
コンテナ内でMySQLに接続し、データベースの操作方法を確認します。
mysql --user=root --password=my-secret-pw
mysql> CREATE DATABASE test_database; mysql> USE test_database; mysql> CREATE TABLE test_table (name VARCHAR(255), english INT, mathematics INT); mysql> INSERT INTO test_table (name, english, mathematics) VALUES ("Ichiro", 10, 10), ("Jiro", 20, 40), ("Saburo", 30, 90); mysql> SELECT * FROM test_table;
+--------+---------+-------------+ | name | english | mathematics | +--------+---------+-------------+ | Ichiro | 10 | 10 | | Jiro | 20 | 40 | | Saburo | 30 | 90 | +--------+---------+-------------+ 3 rows in set (0.00 sec)
test_database
という名前でデータベースを作成し、そこにtest_table
というテーブルを作成し、上のようなデータを入れることができました。動作確認ができたので、コンテナ内のMySQLとBashから抜けます。
mysql > exit exit
立ち上げたDockerのコンテナを終了しておきます。
docker kill database
先ほどのDocker Hubのページに以下のような説明があります。要約すると、/docker-entrypoint-initdb.d
以下に置いた.sh
, .sql
, .sql.gz
ファイルはコンテナ起動時に実行されるということが書いてあります。
Initializing a fresh instance
When a container is started for the first time, a new database with the specified name will be created and initialized with the provided configuration variables. Furthermore, it will execute files with extensions .sh, .sql and .sql.gz that are found in /docker-entrypoint-initdb.d. Files will be executed in alphabetical order. You can easily populate your mysql services by mounting a SQL dump into that directory and provide custom images with contributed data. SQL files will be imported by default to the database specified by the MYSQL_DATABASE variable.
この情報をつかって、先ほど試したMySQLのコンテナイメージをベースに、自分専用のコンテナイメージを作成します。具体的には、コンテナが立ち上がるとtest_database
データベース、test_table
テーブル、それに上と同様のデータが作成されるようにします。
データベースサーバ用のディレクトリを作成します。
mkdir database cd database
先ほど試した操作を再現するシェルスクリプトを作成します。init.sh
として次のような内容のファイルをつくります。
#! /bin/bash COMMAND=' CREATE DATABASE test_database; USE test_database; CREATE TABLE test_table (name VARCHAR(255), english INT, mathematics INT); INSERT test_table (name, english, mathematics) VALUES ("Ichiro", 10, 10), ("Jiro", 20, 40), ("Saburo", 30, 90);' mysql --user=root \ --password=my-secret-pw \ --execute="$COMMAND"
このシェルスクリプトがコンテナの起動時に実行されるように、Dockerfileを作成します。Dockerfile
として次の内容のファイルを作ります。
FROM mysql:5.7 COPY init.sh /docker-entrypoint-initdb.d/
このDockerfile
からコンテナイメージをビルドします。
docker build . -t database
先ほど立ち上げたdatabase
という名前のMySQLサーバは、docker kill
で終了はしているものの、削除はされていないため、docker container rm
コマンドで削除します。
docker container rm database
先ほどビルドしたコンテナイメージからコンテナを起動します。
docker run --name database -e MYSQL_ROOT_PASSWORD=my-secret-pw -d database
データベースコンテナが準備できました。親ディレクトリに戻っておきます。
cd ..
アプリケーションサーバの構築
ここからはアプリケーションサーバを構築していきます。その前に、先ほど起動したデータベースサーバへの接続方法を確認しておきましょう。
データベースサーバのIPアドレスを確認するため、docker network
コマンドを使います。ネットワークの一覧を確認します。
docker network ls
database
コンテナはこの中でbridge
ネットワークにあります。IPアドレスを調べます。
docker network inspect bridge
このコマンドの出力を調べると、私の環境ではdatabase
コンテナのIPアドレスは172.17.0.2
となっていました。Pythonからこのデータベースに接続する方法を確認します。
Python 3からデータベースに接続するために、必要なものをインストールします。
sudo apt install python3-pip python3-dev default-libmysqlclient-dev sudo pip3 install mysqlclient==1.4.2.post1
Python 3処理系を起動し、データベースに接続するテストを行います。
python3
import MySQLdb db_settings = {"host": "172.17.0.2", "user": "root", "passwd": "my-secret-pw", "db": "test_database", "charset": "utf8mb4"} db_conn = MySQLdb.connect(**db_settings) cursor = db_conn.cursor() query = "SELECT name, english, mathematics FROM test_table" cursor.execute(query) ret = cursor.fetchall() ret exit()
(('Ichiro', 10, 10), ('Jiro', 20, 40), ('Saburo', 30, 90))
データベースサーバへの接続とデータの取得が確認できました。
アプリケーションサーバで実行するプログラムを作成します。アプリケーションサーバのコンテナ用の作業ディレクトリを作成し、移動します。
mkdir app_server cd app_server
ここに、app_server.py
として以下の内容のファイルを作成します。
""" Make HTML page with data from database """ import logging from flask import Flask import MySQLdb app = Flask(__name__) logging.basicConfig(format="%(asctime)s [%(levelname)s] %(message)s", level=logging.INFO) logger = logging.getLogger(__name__) @app.route("/") def root(): """ Root page """ db_setting = { "host": "database", "user": "root", "passwd": "my-secret-pw", "db": "test_database", "charset": "utf8mb4" } data = [] try: db_conn = MySQLdb.connect(**db_setting) try: cursor = db_conn.cursor() query = "SELECT name, english, mathematics FROM test_table" cursor.execute(query) data = cursor.fetchall() finally: db_conn.close() except Exception as err: logger.error("%s %s", type(err), err) body = "<table><tr><th>name</th><th>english</th><th>mathmatics</th></tr>{}</table>".format( "".join([ "<tr><td>{}</td><td>{}</td><td>{}</td>".format(name, english, mathematics) for name, english, mathematics in data ]) ) page = "<html><head><title>Test Page</title></head><body>{body}</body></html>\n" return page.format(body=body)
HTTPリクエストがあるとデータベースサーバに接続し、データを取得し、それをテーブルに載せたHTMLを返すFlaskによるウェブアプリケーションです。これを実行するコンテナを定義するDockerfileを作成します。Dockerfile
として以下の内容を保存します。
FROM ubuntu:18.04 RUN sed -i.bak -e "s%http://archive.ubuntu.com/ubuntu/%http://jp.archive.ubuntu.com/ubuntu/%g" /etc/apt/sources.list RUN apt update && apt upgrade -y && apt install python3 python3-pip python3-dev libmysqlclient-dev -y RUN pip3 install mysqlclient==1.4.2.post1 flask==0.12.1 gunicorn==19.9.0 COPY . /app WORKDIR /app CMD while true; do gunicorn --bind 0.0.0.0:80 app_server:app ; done
アプリケーションサーバのコンテナの準備ができました。親ディレクトリに戻ります。
cd ..
Docker Composeを試す
ここまででデータベースサーバとアプリケーションサーバが準備できました。まだウェブサーバが準備できていませんが、ここまでできた内容でDocker Composeを試してみます。
docker-compose.yml
として、以下を保存します。
version: "2" services: database: build: context: database networks: - default environment: - MYSQL_ROOT_PASSWORD=my-secret-pw app_server: build: context: app_server links: - database networks: - default
アプリケーションサーバからデータベースサーバにアクセスできるよう、
app_server links: - database
とネットワークの設定をしています。
Docker Composeを使って、データベースサーバとアプリケーションサーバの二つのコンテナをビルド・起動します。
docker-compose build docker-compose up -d
各コンテナが吐き出すログはdocker-compose logs
で確認できます。
docker-compose logs
アプリケーションサーバにアクセスしてみます。アプリケーションサーバのIPアドレスを知るために、再びdocker network
コマンドを使います。docker network ls
コマンドを実行すると、dockercompose_techblog_default
というネットワークがあることがわかると思います。
次のコマンドの出力をみれば、アプリケーションサーバのIPアドレスがわかります。
docker network inspect dockercompose_techblog_default
私の環境の場合は172.18.0.3でした。cURLコマンドかブラウザでアプリケーションサーバの動作を確認します。
curl http://172.18.0.3 # or firefox http://172.18.0.3
<html><head><title>Test Page</title></head><body><table><tr><th>name</th><th>english</th><th>mathmatics</th></tr><tr><td>Ichiro</td><td>10</td><td>10</td><tr><td>Jiro</td><td>20</td><td>40</td><tr><td>Saburo</td><td>30</td><td>90</td></table></body></html>
アプリケーションサーバが正常に動作していることがわかります。
ウェブサーバを構築する
3層ウェブアプリケーションを実現するため、ウェブサーバを構築します。作業用ディレクトリをつくり、移動します。
mkdir web_server cd web_server
Docker Hubにあるnginxのイメージを使うことにします。nginxの設定ファイルは/etc/nginx/conf.d/default.conf
にあるので、ホスト側に設定ファイルをコピーしておきます。
docker run --name nginx -d nginx:1.15 docker cp nginx:/etc/nginx/conf.d/default.conf default.conf.bak
コピーした設定ファイルを確認すると、以下のような内容になっていることがわかります。
less default.conf.bak
server { listen 80; server_name localhost; #charset koi8-r; #access_log /var/log/nginx/host.access.log main; location / { root /usr/share/nginx/html; index index.html index.htm; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} }
nginxをリバースプロキシとして動作させる設定ファイルを作ります。このファイルをコピーします。
cp default.conf.bak default.conf
default.conf
の
location / { root /usr/share/nginx/html; index index.html index.htm; }
の部分を
location / { proxy_pass http://app_server; }
と編集します。こうすることで、アプリケーションサーバへのアクセスをウェブサーバが代理することができます。
最後に、Dockerfileを作成します。以下の内容でDockerfile
を作成します。
FROM nginx:1.15 COPY default.conf /etc/nginx/conf.d/default.conf
これでウェブサーバの準備ができました。親ディレクトリに戻ります。
cd ..
3層ウェブアプリケーションをDocker Composeで構築する
さきほど作成したdocker-compose.yml
を編集し、ウェブサーバを追加することで、3層ウェブアプリケーションを実現します。docker-compose.yml
にweb_server
の項目を追加し、以下のようにします。
version: "2" services: database: build: context: database networks: - default environment: - MYSQL_ROOT_PASSWORD=my-secret-pw app_server: build: context: app_server links: - database networks: - default web_server: build: context: web_server links: - app_server ports: - "80:80" networks: - default
ホストへのポート80番のアクセスをweb_server
コンテナに転送する設定にしています。
コンテナたちをビルド・起動します。
docker-compose build docker-compose up -d
cURLやブラウザからウェブサーバにアクセスし、動作確認をします。
curl http://localhost # or firefox http://localhost
無事に動作が確認できました。
docker-compose logs
でログを確認すると、ウェブサーバへのアクセスがnginxのアクセスログとして取得できていることが確認できます。
web_server_1 | 172.18.0.1 - - [20/Feb/2019:06:31:29 +0000] "GET / HTTP/1.1" 200 257 "-" "curl/7.58.0" "-"
コンテナ一つ一つのログをみるには、docker ps
コマンドでコンテナの名前を確認し、docker logs
コマンドを使います。以下はweb_server
のログを見る例です。
docker ps docker logs dockercompose_techblog_web_server_1
作業終了
作業を終了するため、Docker Composeから立ち上げたコンテナを終了します。
docker-compose down
念のため、起動したまま残っているコンテナがないか確認します。
docker ps
不要なコンテナが残っていた場合は、docker kill
で終了させることができます。
docker kill *some_container_name*
Dockerを使っているとコンテナイメージがディスクを圧迫することがあります。docker container prune
やdocker image prune
を使うと、終了したが削除されていないコンテナや、使われてないDockerイメージを削除できるので、必要に応じて使うといいでしょう。
まとめ
この記事では、Docker Composeで3層ウェブアプリケーションを作成する手順を説明しました。Dockerのコマンドはたくさんあってややこしいですが、この記事で実際的な使い方とともに説明したことが少しでも役に立てば幸いです。
テックブログ新着情報のほか、AWSやGoogle Cloudに関するお役立ち情報を配信中!
Follow @twitterデータ分析と機械学習とソフトウェア開発をしています。 アルゴリズムとデータ構造が好きです。
Recommends
こちらもおすすめ
-
相関係数は外れ値の影響をうけやすい?Pythonで確認してみた。
2018.2.28
-
AWS初心者におすすめの勉強法と運用のポイント
2019.5.30
Special Topics
注目記事はこちら
データ分析入門
これから始めるBigQuery基礎知識
2024.02.28
AWSの料金が 10 %割引になる!
『AWSの請求代行リセールサービス』
2024.07.16