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コンテナで別の処理をしたテキストデータを受け取ります。

- 受け取ったテキストをファイルに保存します。

以上の流れを実装したスクリプトは次のようになります。

```python
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行です。__

```python
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は次の通りです。

```docker
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を指定します。

以上の流れを実装したスクリプトは次のようになります。

```python
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は次の通りです。

```docker
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ファイルの作成

ファイル全体は次のようになります

```tsx
# 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コンテナの記述で重要な箇所

```docker
depends_on:
- punctuate_text
```

- Bコンテナのサービス名を指定することで、Bコンテナの後にAコンテナを起動することができます。

### Bコンテナの記述で重要な箇所

```docker
ports:
  - 8000:5000
```

- `5000` という番号がネットワーク内での番号になります。BコンテナのFlaskサーバーのポート番号と同一です。

## docker-composeコマンドの実行

2つのコンテナを起動します。

```docker
# docker-compose -f docker-compose.dev.yml up -d
```

## まとめ

- コンテナにアクセスするためのアドレスは、docker-composeファイルのサービス名とポート番号で決まります。
- コンテナ起動の順序に注意する。
- サーバーからの応答を待つための時間を設定する。

## おわりに

Dockerがアドレスの解決を動のように行っているのか分からず、解決するのに時間がかかりました。 

[GitHubでソースコードをみることができます。](https://github.com/kay-adamof/get-punctuated-transcription)