2つのDockerコンテナ同士にお話させる
ここに2つのDockerコンテナ、AとBがあったとします。Aコンテナでデータを処理したあと、Bコンテナにデータを送ります。受け取ったBコンテナは、また別の処理をし、Aコンテナへデータを返却します。この一連の流れを実現するにはどうすればいいか、実例で説明します。
説明の前に重要なポイントを洗い出します。
-
Bコンテナにアクセスするためのアドレスの指定を間違えずに行う。最重要です。
-
docker-compose.yml ファイルで2つのコンテナを起動すると、同一のネットワークが割り当てられます。
-
同じネットワーク内のコンテナは、お互いの
サービス名
とネットワーク内のポート番号
を使って通信を行うことができます。
-
-
Bコンテナが起動した後に、Aコンテナを起動させる。
-
Bコンテナはサーバーとして、Aコンテナからのデータを受け取るので、先に起動する必要があります。
-
docker-compose.yml ファイルで
depends_on
で起動の順序を指定します。
-
-
Bコンテナから値の返却があるまで一定時間待ちます。
- 一定時間待つ設定をしないと、値が返却される前にコンテナの起動が終了します。
それでは各コンテナの実装の流れを見ていきましょう。
Aコンテナの作成
Aコンテナの役割は、YouTubeの動画の文字起こしを取得することです。
-
YouTubeの動画のURLを指定します
-
URLからidを取得します。
-
id をもとに、Pythonのライブラリを使って動画の文字起こしを取得します。
-
文字起こしのテキストデータをBコンテナへ送信します。
-
Bコンテナで別の処理をしたテキストデータを受け取ります。
-
受け取ったテキストをファイルに保存します。
以上の流れを実装したスクリプトは次のようになります。
import urllib.parse
from youtube_transcript_api import YouTubeTranscriptApi
import requests
url = "https://www.youtube.com/watch?v=CJjSOzb0IYs"
parsed_url = urllib.parse.urlparse(url)
query_params = urllib.parse.parse_qs(parsed_url.query)
video_id = query_params.get("v") # returns an array including the value next to "v=..."
if video_id:
video_id = video_id[0]
transcript_list = YouTubeTranscriptApi.get_transcript(video_id)
transcription = ""
for t in transcript_list:
transcription += " "+ t["text"]
with open("transcription.txt", "w") as file:
file.write(transcription)
# Send the request and wait for the response until 5 sec
try:
posted = requests.post("http://punctuate_text:5000/punctuate", data={"text": transcription}, timeout=5)
posted.raise_for_status() # Raise an exception for non-2xx responses
if posted.status_code == 200:
with open("output.txt", "w") as file:
file.write(posted.text)
except requests.exceptions.RequestException as e:
print("Error occurred:", e)
else:
print("No video ID found.")
⭐️ 最も注目して欲しいのは次の1行です。
posted = requests.post("http://punctuate_text:5000/punctuate", data={"text": transcription}, timeout=5)
-
punctuate_text
: 後で出てくるdocker-composeファイルで指定したBコンテナのサービス名です。 -
5000
: 同じくdocker-composeファイルで指定したネットワーク内のポート番号です。 -
/punctuate
: BコンテナのFlaskサーバーで指定したルートアドレスです。
コンテナ作成のDockerfileは次の通りです。
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python3", "app.py"]
Bコンテナの作成
Bコンテナの役割は、Aコンテナから送られてきた文字起こしに句点を打つことです。
-
PytyonのFlaskを使ってサーバーを作成します。
-
句点作成用のルートを作成します。
-
ルートに対してPOSTされたテキストデータを受け取ります。
-
テキストに対し、Pythonの自然言語処理ライブラリSpaCyを使い、句点を打ちます。
-
句点付きテキストを返却します。
-
サーバーのポート番号5000を指定します。
以上の流れを実装したスクリプトは次のようになります。
from flask import Flask, request
import spacy
app = Flask(__name__)
nlp = spacy.load("en_core_web_sm")
@app.route('/punctuate', methods=['POST'])
def punctuate_text():
text = request.form.get('text')
doc = nlp(text)
punctuated_text = ""
for sentence in doc.sents:
punctuated_text += sentence.text + ". "
return punctuated_text
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
コンテナ作成のDockerfileは次の通りです。
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -U pip setuptools wheel
RUN pip install --no-cache-dir -r requirements.txt
RUN python -m spacy download en_core_web_sm
COPY . .
CMD ["python3", "app.py"]
Docker Composeファイルの作成
ファイル全体は次のようになります
# docker-compose.dev.yml
version: "3.8"
services:
# Container A
get_transcription:
build:
context: ./python
container_name: transcription
depends_on:
-
volumes:
- ./python:/app
# Container B
punctuate_text:
build:
context: ./punctuate
container_name: punctuate
ports:
- 8000:5000
Aコンテナの記述で重要な箇所
depends_on:
- punctuate_text
- Bコンテナのサービス名を指定することで、Bコンテナの後にAコンテナを起動することができます。
Bコンテナの記述で重要な箇所
ports:
- 8000:5000
-
5000
という番号がネットワーク内での番号になります。BコンテナのFlaskサーバーのポート番号と同一です。
docker-composeコマンドの実行
2つのコンテナを起動します。
# docker-compose -f docker-compose.dev.yml up -d
まとめ
- コンテナにアクセスするためのアドレスは、docker-composeファイルのサービス名とポート番号で決まります。
- コンテナ起動の順序に注意する。
- サーバーからの応答を待つための時間を設定する。
おわりに
Dockerがアドレスの解決を動のように行っているのか分からず、解決するのに時間がかかりました。
Discussion