はじめに
前回は、
- Bind Mountで自分のHTMLをnginxから配信する
- Dockerfileを作る
-
docker buildで自作Imageを作る - 自作ImageからContainerを起動する
- Bind MountとDockerfileの違いを確認する
ところまで試しました。
ここまでの操作では、Containerを起動するたびに、
sudo docker run -d --name web -p 8080:80 my-nginx
のようなコマンドを入力していました。
Containerが1つだけなら、それほど大変ではありません。
しかし、Webアプリとデータベース、キャッシュサーバーなど、複数のContainerを組み合わせるようになると、
- どのImageを使うのか
- どのポートを公開するのか
- どのVolumeを使うのか
- どのContainer同士をつなぐのか
といった設定が増えていきます。
そこで今回は、Docker Compose を使って複数Containerをまとめて管理してみます。
最終的には、
Raspberry Pi
└── Docker
└── docker-lab
├── web Container
└── redis Container
という構成を作り、Container同士がサービス名で通信できることまで確認します。
Docker Composeとは
Docker Composeは、複数のContainer構成を compose.yaml という設定ファイルにまとめて管理するための仕組みです。
例えば、これまでContainerを起動するときは、
sudo docker run -d --name web -p 8080:80 my-nginx
のように、起動時の設定をコマンドラインへ直接書いていました。
Docker Composeを使うと、こうした設定をファイルへ記述できます。
services:
web:
build: .
ports:
- "8080:80"
そして、
sudo docker compose up -d
を実行するだけで、ファイルに書いた内容をもとにContainerを起動できます。
compose.yamlを作る
前回まで使っていた ~/docker-lab ディレクトリで作業します。
cd ~/docker-lab
compose.yaml を作成します。
nano compose.yaml
まずは次の内容にします。
services:
web:
build: .
ports:
- "8080:80"
ここでは web というサービスを1つ定義しています。
compose.yamlの内容を確認する
まず、
services:
の下に、Composeで管理するサービスを定義します。
今回は、
web:
という名前のサービスを作っています。
次に、
build: .
は、現在のディレクトリにあるDockerfileを使ってImageをBuildするという意味です。
前回は、
sudo docker build -t my-nginx .
と手動でBuildしていました。
Composeでは build: . と書いておけば、必要に応じてDockerfileからImageを作ってくれます。
最後に、
ports:
- "8080:80"
で、
Raspberry Pi :8080
↓
Container :80
というポート転送を設定しています。
前回の、
-p 8080:80
と同じ役割です。
Docker ComposeでContainerを起動する
まず、前回まで使っていた web Containerが残っている場合は削除します。
sudo docker rm -f web
次にComposeで起動します。
sudo docker compose up -d
実行すると、DockerfileからImageがBuildされ、Compose用のNetworkも作成されました。
今回の出力では、
Image docker-lab-web Built
Network docker-lab_default Created
Container docker-lab-web-1 Started
という流れが確認できました。
Composeで起動したContainerを確認する
状態を確認します。
sudo docker compose ps
今回の環境では、次のようになりました。
NAME IMAGE SERVICE STATUS PORTS
docker-lab-web-1 docker-lab-web web Up 0.0.0.0:8080->80/tcp
これまで自分で付けていた web というContainer名とは少し違い、
docker-lab-web-1
という名前になっています。
Composeでは、プロジェクト名やサービス名をもとにContainer名が自動的に付けられます。
ブラウザから確認する
別PCのブラウザから、
http://Raspberry PiのIPアドレス:8080
へアクセスします。
前回Dockerfileへ組み込んだHTMLが表示されれば成功です。
ここまでの時点では、やっていることは docker run と大きく変わらないように見えます。
しかしComposeの便利さは、複数Containerを扱うときに分かりやすくなります。
Redis Containerを追加する
次に、同じ compose.yaml へRedisを追加します。
nano compose.yaml
内容を次のように変更します。
services:
web:
build: .
ports:
- "8080:80"
redis:
image: redis:latest
これで、
web
redis
という2つのサービスをComposeで管理する構成になりました。
2つのContainerを起動する
設定を保存したら、もう一度起動します。
sudo docker compose up -d
Redis Imageを持っていなかったため、初回はDocker Hubから redis:latest が取得されました。
その後、
sudo docker compose ps
を実行します。
今回の環境では、
NAME IMAGE SERVICE STATUS PORTS
docker-lab-redis-1 redis:latest redis Up 6379/tcp
docker-lab-web-1 docker-lab-web web Up 0.0.0.0:8080->80/tcp
となりました。
これで、
Raspberry Pi
└── Docker
├── web Container
└── redis Container
という2Container構成になりました。
Redisが動いていることを確認する
Redis Containerの中で redis-cli を実行してみます。
sudo docker compose exec redis redis-cli ping
結果は、
PONG
となりました。
Redisが正常に動いています。
ここで使用した、
docker compose exec
は、起動中のComposeサービス内でコマンドを実行するためのコマンドです。
今回の場合は、
redisサービスのContainer内で
redis-cli pingを実行する
という意味になります。
ComposeがNetworkも作っている
最初に docker compose up -d を実行したとき、
Network docker-lab_default Created
という表示がありました。
ComposeはContainerだけではなく、今回のサービス同士が通信するためのNetworkも自動的に作っています。
イメージとしては次のようになります。
Raspberry Pi
└── Docker
└── docker-lab_default Network
├── web
└── redis
このNetworkの中では、Container同士が通信できます。
web Containerの中に入ってみる
Container同士の通信を確認するため、まず web Containerの中へ入ってみます。
sudo docker compose exec web sh
すると、プロンプトが次のように変わりました。
#
この状態では、SSH接続しているRaspberry Pi本体ではなく、web Containerの中にいます。
redisという名前で通信先を探す
web Container内から、次のコマンドを実行します。
getent hosts redis
今回の環境では、
172.18.0.3 redis
と表示されました。
ここで重要なのは、
redis
という名前だけで、Redis ContainerのIPアドレスを取得できていることです。
ContainerのIPアドレスを直接書かなくていい
Containerは作り直すと、IPアドレスが変わることがあります。
もしアプリケーション側へ、
172.18.0.3
のようなIPアドレスを直接書いてしまうと、Containerを作り直したときに接続できなくなる可能性があります。
Composeでは、同じNetwork上のサービスをサービス名で参照できます。
今回であれば、
web
redis
というサービス名があります。
そのため、アプリケーション側ではRedisへの接続先を、
redis
と指定できます。
イメージとしては、
web Container
│
│ redis という名前で接続
▼
Docker Composeの名前解決
│
▼
redis Container
となります。
確認が終わったら、
exit
でContainerから抜けます。
Composeはdocker runの省略版なのか
ここまで触ってみると、
Docker Composeは長い
docker runを短く書くためのものなのか?
と思いました。
Containerが1つだけなら、その理解でもそれほど違和感はありません。
しかし、Containerが複数になると意味が変わってきます。
例えば今回の構成では、
web Container
redis Container
Network
Port
Build設定
を1つの compose.yaml へまとめています。
もし今後さらに、
Webアプリ
Redis
Database
管理画面
とサービスが増えても、1つのComposeプロジェクトとして管理できます。
つまりComposeは単にコマンドを短くするだけではなく、
複数Containerで構成された1つのアプリケーションを定義する
ための仕組みと考えると分かりやすそうです。
Composeでまとめて停止・削除する
Composeで作った環境は、まとめて停止・削除できます。
sudo docker compose down
今回の環境では、
Container docker-lab-redis-1 Removed
Container docker-lab-web-1 Removed
Network docker-lab_default Removed
となりました。
Containerだけではなく、Composeが作成したNetworkも削除されています。
確認します。
sudo docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
何も表示されなくなりました。
compose.yamlがあれば再び作れる
ContainerやNetworkを削除しても、compose.yaml は残っています。
そのため、再び、
sudo docker compose up -d
を実行すれば、同じ構成を作り直せます。
ここでも、第1回から何度も出てきた、
作る
↓
使う
↓
消す
↓
また作る
というDockerの考え方が出てきました。
Composeでは、この対象がContainer1つではなく、
複数Container
+
Network
+
各種設定
というアプリ全体になったように感じます。
次は本当にContainer同士を使うアプリを作る
ここまででは、
web Container
redis Container
を同時に起動しましたが、web 側はまだnginxの静的HTMLを表示しているだけです。
つまり、Redis Containerを実際のアプリケーションから利用しているわけではありません。
そこで次は、nginxの代わりにPythonのWebアプリを作ります。
ブラウザでアクセスするたびに、
このページは 1 回表示されました。
次に更新すると、
このページは 2 回表示されました。
というように数字が増える簡単なアクセスカウンターを作ります。
構成は次のようになります。
PCブラウザ
│
│ :8080
▼
Raspberry Pi
│
▼
web Container
Python / Flask
│
│ redis:6379
▼
redis Container
│
└── アクセス回数を保存
これなら、Composeで複数Containerを使う意味をより具体的に確認できます。
Python + Redisのアクセスカウンターを作る
まず現在のCompose環境を停止します。
sudo docker compose down
今回使用するファイルは次の4つです。
docker-lab/
├── app.py
├── requirements.txt
├── Dockerfile
└── compose.yaml
app.pyを作る
Pythonアプリを作成します。
nano app.py
内容は次のようにしました。
from flask import Flask
import redis
app = Flask(__name__)
r = redis.Redis(
host="redis",
port=6379,
decode_responses=True
)
@app.route("/")
def index():
count = r.incr("hits")
return f"""
<h1>Hello Docker!</h1>
<p>このページは {count} 回表示されました。</p>
"""
app.run(host="0.0.0.0", port=5000)
重要なのは、Redisへの接続先です。
host="redis"
IPアドレスではなく、Composeで定義したサービス名 redis を指定しています。
先ほど getent hosts redis で確認した名前解決を、実際のアプリケーションから利用します。
requirements.txtを作る
Pythonアプリで使用するライブラリを定義します。
nano requirements.txt
内容は次の2つです。
flask
redis
Pythonアプリ用のDockerfileを作る
前回nginx用に作ったDockerfileを、Pythonアプリ用へ書き換えます。
nano Dockerfile
内容は次のようにしました。
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD ["python", "app.py"]
処理の流れは、
Python公式Image
↓
/appを作業ディレクトリにする
↓
requirements.txtをコピー
↓
FlaskとRedisライブラリをインストール
↓
app.pyをコピー
↓
python app.pyを実行
となっています。
compose.yamlを書き換える
次に compose.yaml を変更します。
nano compose.yaml
内容は次のようにしました。
services:
web:
build: .
ports:
- "8080:5000"
depends_on:
- redis
redis:
image: redis:latest
今回はPythonアプリがContainerの5000番ポートで動くため、
ports:
- "8080:5000"
となっています。
つまり、
Raspberry Pi :8080
↓
web Container :5000
という通信になります。
また、
depends_on:
- redis
として、web が redis に依存する構成であることも定義しています。
Python + RedisをComposeで起動する
起動します。
sudo docker compose up -d --build
今回はDockerfileを書き換えたため、--build を付けています。
Buildでは、
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install ...
COPY app.py .
というDockerfileの内容に従ってPythonアプリ用Imageが作成されました。
起動後、状態を確認します。
sudo docker compose ps
今回の環境では、
NAME IMAGE SERVICE STATUS PORTS
docker-lab-redis-1 redis:latest redis Up 6379/tcp
docker-lab-web-1 docker-lab-web web Up 0.0.0.0:8080->5000/tcp
となりました。
ブラウザからアクセスしてみる
別PCのブラウザから、
http://Raspberry PiのIPアドレス:8080
へアクセスします。
すると、
Hello Docker!
このページは 1 回表示されました。
と表示されました。
何度かブラウザを更新すると、
このページは 2 回表示されました。
このページは 3 回表示されました。
と数字が増えていきます。
これで、Web ContainerからRedis Containerへ実際に通信できていることが確認できました。
今回の処理の流れ
ブラウザへアクセスしたときの処理を整理すると、
PCブラウザ
│
│ :8080
▼
Raspberry Pi
│
▼
Docker
│
▼
web Container
│
│ Flask
│
│ r.incr("hits")
▼
redis
というサービス名で接続
│
▼
redis Container
│
└── hitsを+1
となります。
今回初めて、2つのContainerが協力して1つのアプリケーションを動かす構成になりました。
Docker Composeを使って分かったこと
今回Docker Composeを使ってみて、単なる docker run の省略ではないことが少し分かってきました。
複数Containerを1つの設定ファイルで管理できる
今回の構成は、
services:
web:
...
redis:
...
という1つの compose.yaml にまとまっています。
そのため、
sudo docker compose up -d
でまとめて起動し、
sudo docker compose down
でまとめて削除できます。
Networkも自動で作られる
Composeは、
docker-lab_default
というNetworkも自動的に作ってくれました。
同じNetwork上では、
web
redis
というサービス名でContainer同士が通信できます。
IPアドレスを直接管理しなくていい
Webアプリでは、
host="redis"
と指定するだけでRedisへ接続できました。
Containerを作り直してIPアドレスが変わっても、アプリ側では redis というサービス名を使い続けることができます。
まとめ
今回はDocker Composeを使って、
-
compose.yamlを作る - ComposeからDockerfileをBuildする
- 複数Containerを起動する
- Composeが作るNetworkを確認する
- サービス名でContainer同士を名前解決する
- Python + Redisの2Containerアプリを動かす
ところまで試しました。
第1回では、
Image
↓
Container
というDockerの基本を確認しました。
第2回では、
Dockerfile
↓
自作Image
↓
Container
まで進みました。
そして今回は、
compose.yaml
↓
複数Container
+
Network
↓
1つのアプリとして動く
というところまで進みました。
Composeを使うことで、複数のContainerを別々に管理するのではなく、1つのアプリケーション構成として扱えるという感覚が少し分かってきました。
次回
現在、アクセス回数はRedis Containerの中に保存されています。
例えば現在、
このページは 3 回表示されました。
となっていたとしても、
sudo docker compose down
でRedis Containerを削除すると、そのデータも一緒に消えてしまいます。
再び、
sudo docker compose up -d
で起動してブラウザへアクセスすると、カウンターは再び1から始まります。
しかし、データベースなどでは、
Containerは作り直したいが、保存したデータまで消えては困る
というケースがあります。
次回は Docker Volume を使って、
Containerは削除
↓
データは残す
↓
新しいContainerから再利用
というデータ永続化を試します。
次回:【Docker編 第4回】Docker VolumeでContainerを削除してもデータを残す