Docker Compose を使用して大きなシステムの中のPythonのモジュールの1つを作成する必要ができたので、勉強 and 備忘録のために本記事を作成いたしました。
目的
- 大きなシステムをチーム開発する際に、システムをモジュール分割し、互いに疎な開発を進めたい
- Docker Compose を使用することでモジュールごとにコンテナを立ち上げ、システムを開発したい
- 本記事は、そんな数あるモジュール + コンテナのうちの一つの開発環境を整えた際の備忘録
Dockerとは
Dockerの目的
チームでシステム開発を行う際に問題となるのは、開発環境の違いである。例えば、AさんはWindows、BさんはMacOSで開発を行っている場合に、AさんがWindows環境で開発したシステムをBさんのMacOSで動かすのは手間がかかるケースが多い。Dockerは、Dockerコンテナと呼ばれるアプリケーションの実行環境を各環境に構築し、コンテナ内でアプリケーションを実行することで、このような環境の違いに影響されることなく、アプリを開発、実行することができる。
Dockerコンテナを立てる手順
コンテナは以下の手順で構築することができる。
- Dockerfileを作成(自身で作成 or 公開されているものを使用)
- Dockerfile から Dockerイメージを作成
docker build -t my-app-image .
- Dockerイメージを使用してコンテナを起動
docker run my-app-image
それぞれについて軽く解説。詳しい使い方までは触れない。
Dockerfile
Dockerfile とは、Dockerイメージを構成するテキストデータであり、Dockerイメージを作成するための設計図である。以下のように、ベースとなるイメージ、依存関係、作業ディレクトリ、コンテナ起動時の実行コマンドなどを記載。
# Pythonベースのイメージを使用
FROM python:3.8
# 作業ディレクトリを設定
WORKDIR /app
# 依存関係をインストールするためのファイルをコピー
COPY requirements.txt .
# 依存関係をインストール
RUN pip install -r requirements.txt
# アプリケーションのソースコードをコピー
COPY . .
# コンテナ起動時に実行するコマンド
CMD ["python", "app.py"]
Dockerイメージ
- 役割:実際のソフトウェアが動作するために必要なファイルや設定をパッケージ化したもの。Dockerfile(設計図)に従って作成された完成品 と考えられる。「実行可能なソフトウェアのパッケージ」
- 状態:イメージは実行されていない「スナップショット」のようなもので、読み取り専用
- 内容:OS、アプリケーション、ライブラリ、依存関係などを含んでおり、これをもとにコンテナが動作
Dockerコンテナ
- 役割:Dockerコンテナは、Dockerイメージを元に実行される動的な実行環境
- 状態:コンテナは実行中のプロセスで、読み書き可能です。実行中にファイルシステムに変更が加えられたり、コンテナ内のデータを変更が可能。コンテナの変更はコンテナ内に保存されるが、イメージには反映されない
それぞれの比較(By GPT-4o)
Docker Compose
巨大なシステムを構築する際は、モジュールごとに機能を開発することが多い。例えばチャットボットのアプリは大雑把に、以下のように機能分割することができる。
- テキストの入出力やフロントエンドを担当するモジュール
- 入力されたテキストから出力テキストをバックエンドで出力するモジュール
このようなモジュールは必要なライブラリも異なるし、複数人で開発している際は担当を分けることもある。このような場合に、それぞれ異なるDockerイメージを作成して開発を進めることになる。
実際にチャットボットアプリを起動する際に、全てのモジュールのコンテナを起動して、相互で通信を行うことになるが、このようなコンテナの同時立ち上げやコンテナ同士の振る舞いなどを定義・実行するのがDocker Composeの役割である。
使い方の流れ
- モジュールごとにDockerfileを作成
- docker-compose.yml といったyml形式のファイルを用意し、モジュールごとの設定を記述
-
docker compose up -d
を実行して全てのコンテナを一斉起動
つまりチーム開発において、Docker Composeで起動できるPythonモジュールを作成する際は、以下の手順が必要になる。
- Pythonモジュールを作成
- モジュールが実行可能なDockerイメージの設計図であるDockerfileを作成(ここで requirements.lock などを読み込んでライブラリ等を揃える)
- docker-compose.yml ファイルに上記のDockerfileの起動を記載
-
docker compose up -d
コマンドで作成したモジュールが起動することを確認
実際に環境構築
これらの基礎知識を踏まえて実際にDockerを使用したPythonモジュールの環境構築に取り組んでみる。ディレクトリ構造は以下のものを想定。
project-root/
│
├── app/
│ ├── app.py
│ └── __init__.py # (必要に応じて)
│
├── requirements.lock
├── Dockerfile
└── docker-compose.yml
app.py
は以下の通り。
# app.py
import numpy as np
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return "Hello, World from Docker!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
requirements.lock
は以下の通り。
numpy
flask
Dockerfileの作成
以下のようにdockerfileを作成
# ベースイメージを指定
FROM python:3.12
# 作業ディレクトリを設定
WORKDIR /app
# requirements.txtをコンテナにコピー
COPY requirements.lock .
# 依存関係をインストール
RUN pip install --no-cache-dir -r requirements.lock
# アプリケーションのソースコードをコピー
COPY app/ ./app
# コンテナ起動時の実行コマンド
CMD ["python", "app/app.py"]
それぞれが意味していることを説明していく。
ベースイメージの指定
FROM python:3.12
- これは、Dockerイメージのベースイメージを指定するための行である。このようにしてPythonのベースイメージを指定することで、PythonインタープリタやPythonを動かすために必要な基本的なライブラリが含まれている
- ベースイメージを指定することで新しいコンテナをこのイメージ上に作り、Pythonアプリをその上で動作させることができる
作業ディレクトリの設定
WORKDIR /app
- 作成したばかりのコンテナはなんのファイルやディレクトリも含まないまっさらなファイル空間である
- WORKDIR {作業ディレクトリ} とすることで作業ディレクトリを指定することができる。Docker を起動しているホストマシンの空間とコンテナ内の空間は異なるので、仮にホストマシン上に app というディレクトリが存在しても関係ない
- もしホストとコンテナのディレクトリを関連付けたい場合はコンテナの方からホストマシンの対象ディレクトリをマウントする必要がある
requirements.lockをコンテナにコピー
COPY requirements.lock .
- ホストマシン(Dockerコマンドを走らせているローカル環境)のカレントディレクトリにある
requirements.lock
をコンテナの作業ディレクトリ/app
にコピーするコマンド - ホストマシン内のファイルは何もしない状態では使用できず、このようにしてコンテナにコピーをすることで初めて使用可能
依存関係のインストール
RUN pip install --no-cache-dir -r requirements.lock
-
RUN
はDockerイメージのビルド中にコンテナ内でコマンドを実行するツール -
pip install --no-cache-dir -r requirements.lock
でrequirements.lock
に記載されているライブラリをインストールするコマンド -
--no-cache-dir
を使用することでダウンロードしたパッケージのキャッシュを保存してサイズが増加するのを防止
アプリケーションのソースコードコピー
COPY app/ ./app
- ホストマシン上のディレクトリ
app/
の中の全てのディレクトリをコンテナの作業ディレクトリ/app
の中に/app/app
としてコピー - このコマンドを使用することによってアプリのソースコードがコピーされる
- 逆にこのコマンドを使用しないとソースコードがない空のコンテナになってしまう
コンテナ起動時の実行コマンド
CMD ["python", "app/app.py"]
-
CMD
はコンテナが起動した際に実行されるデフォルトのコマンド - このコマンドにより先ほどワーキングディレクトリにコピーした
app
ディレクトリの中のapp.py
を実行 -
CMD
はイメージのビルド時ではなく、コンテナの実行時に起動するものであることに注意
Dockerコンテナの起動
これらの条件化でDockerコンテナを起動してみる。まずはイメージのビルドを以下のコマンドで行う。
docker build -t flask-app .
-t flask-app .
の部分は、カレントディレクトリ.
にあるDockerfile
という名称のファイルを読み込みflask-app
というタグをつけるというオプションである。
ここでイメージが起動しているかどうかはdocker images
コマンドで確認できる。ビルドが成功していれば、以下のように出力される。
REPOSITORY TAG IMAGE ID CREATED SIZE
flask-app latest 09acf5d77c98 2 hours ago 1.09GB
続いて、Dockerイメージからコンテナを起動する。コンテナの起動は以下のコマンドで実行できる。
docker run -d -p 5001:5000 flask-app
-
-d
は「デタッチドモード(detached mode)」を意味し、コンテナがバックグラウンドで実行されるため、このコマンドを入力したターミナルが操作可能な状態なまま残る -
-p 5001:5000
はポートマッピングを指定するオプションであり、5001
は外部からアクセスするポート、5000
はコンテナ内でコンテナがリッスンするポート - ホストマシンのポート
5001
にhttp://localhost:5001
のようにアクセスするとコンテナ内のアプリのポート5000
にマッピングされflask-app
にアクセス可能 - このようになっている理由は、このプログラムを試していた際、ホストマシンのポート
5000
が別のプロセスで埋まっていたため
この状態で、http://localhost:5001/
をブラウザで開いてHello Docker
という文字列が表示されればコンテナの起動に成功していることがわかる。
Docker compose を使用してコンテナを起動
これまで作成してきたDockerfileをDocker composeを使用して起動する。実際の開発では、今回作成したDockerfileに加え、他のチームメンバーが開発したDockerfileもこのDocker composeで同時に起動されることになる。先ほどのproject-root
直下に以下のdocker-compose.yml
ファイルを作成。
services:
flask-app:
build: .
ports:
- "5001:5000"
environment:
- FLASK_ENV=development
# app2:
# build: .
# ports:
# - "5002:5003"
# environment:
# - FLASK_ENV=development
上記は、flask-appというタグをつけて、先ほど作成した Dockerコンテナ + flaskのPythonアプリを起動するための設定ファイルである。つまり、docker build -t flask-app .
というコマンドとdocker run -d -p 5001:5000 flask-app
という2つのコマンドを上記ファイルでは、一括で実行可能。
-
Services
:複数のコンテナ(サービス)を定義する場所 -
flask-app
:1つ目のコンテナの名称 -
build:.
:カレントディレクトリからflask-app
のDockerfileを探索してそれに従いbuild -
ports
:ポート番号の指定 -
app2
:今回は作成していない2つめのサービス。ポート番号を変えているので、flask-app
と同時に別々のポートからリクエストを受け付けることが可能
このファイルを以下のコマンドにより、読み込み実行。
docker-compose up --build
docker-compose up
コマンドは、サービスをビルドし、起動するコマンド。--build
をつけることで、イメージの再ビルドを矯正。新しいコードの変更や、Dockerfileの更新があった場合は、必ずこのオプションをつける必要がある。
-d
オプションを付けることでバックグラウンド実行が可能になり、--no-cache
でキャッシュを無視して再ビルドも可能。
参考サイト