docker-composeで建てたコンテナにアタッチしてリモートデバッグする方法

## 実現したこと
クリックで、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2672529/372e5c9d-2482-814a-32ab-18452f0b3c6a.png)
フロントエンド、バックエンド、DBのコンテナが起動。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2672529/fce88bae-8896-f32a-553f-ad57e1046a30.png)
ブラウザからエンドポイントへリクエストを送り、ブレークポイントを置いたコードで停止して変数などを確認できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2672529/0ad98acb-0432-275d-83a3-3ff20fc04dec.png)
## デバッグ環境構築前の構成
以下のようにフロントエンド、バックエンド、DBサーバー用のファイルをディレクトリで分けて、それぞれのDockerfileでフロントエンド、バックエンド、DBそれぞれのコンテナを構成し、qaapp配下のdocker-compose.ymlファイルでdocker-composeによるコンテナの起動を構成していました
バックエンドではフレームワークとしてFastAPIを採用していて、今回はバックエンドに対してデバッグ環境を構築しました。
参考までに、作成しようとしていたものは、qaappというディレクトリ名からも察せられるかもしれませんが、自分で作った問題と答えを投稿できる一問一答管理システムです。
```
qaapp/
  ├─ frontend/
  │    │ ︙
  │    └ Dockerfile
  ├─ backend/
  │    │ ︙
  │    └ Dockerfile
  ├─ db/
  │    │ ︙
  │    └ Dockerfile
  └─ docker-compose.yml
```

```yml:qaapp/docker-compose.yml
services:
  db:
    container_name: qaapp_db
    build:
      context: ./db
    ports:
      - 13306:3306
    command: --default-authentication-plugin=mysql_native_password
    networks:
      - qaapp

  frontend:
    container_name: qaapp_frontend
    build:
      context: ./frontend
    ports:
      - 8080:80
    networks:
      - qaapp
  
  backend:
    container_name: qaapp_backend
    build:
      context: .
      dockerfile: ./backend/Dockerfile
    volumes:
      - type: bind
        source: ./backend/src
        target: /app/src
      - type: bind
        source: ./backend/main.py
        target: /app/main.py
    tty: true
    ports:
      - 8090:80
    networks:
      - qaapp

networks:
  qaapp:
    driver: bridge
```
```dockerfile:qaapp/backend/Dockerfile
FROM ubuntu:jammy

RUN mkdir app && mkdir app/src

COPY ./backend/Pipfile.lock app
COPY ./backend/run.sh app

RUN apt-get -y update \
    && apt -y update\
    && apt -y install curl \
    && apt-get -y install python3 python3-pip\
    && pip3 install pipenv

RUN cd app && pipenv sync

EXPOSE 80
EXPOSE 8040

CMD cd app && pipenv run uvicorn main:app --host 0.0.0.0 --port 80 --reload
```
## モチベーション
開発途中で効率を上げるためにデバッグを実施したくなりました。
すでにdocker-composeとDockerfileによるコンテナの作成を完了していたので、これらのコンテナに対してattachしてデバッグできるようにすることを目指そうと考えました。

## 大まかな構成
1. `.vscode/tasks.json`で`docker-compose run`を実行するtaskを作成
1. `.vscode/launch.json`で`preLaunchTask`で1で作成したtaskを指定ししたデバッグ実行設定を作成
1. dubugモードでバックエンドアプリケーションを実行するdocker-composeファイルを作成

以下でそれぞれの手順について説明します。
### .vscode/tasks.jsonでdocker-compose runを実行するtaskを作成
```json:.vscode/tasks.json
{
	"version": "2.0.0",
	"tasks": [
		{
			"label": "run qaapp",
			"type": "docker-compose",
			"dockerCompose": {
				"up": {
					"detached": true,
					"build": false,
				},
				"files": [
					"${workspaceFolder}/docker-compose.local.debug.yml"
				]
			}
		}
	]
}
```
filesプロパティで手順3で作成するデバッグ用のdocker-composeファイル(docker-compose.local.debug.yml)を指定しています。

### .vscode/launch.jsonでpreLaunchTaskで1で作成したtaskを指定ししたデバッグ実行設定を作成
```json:.vscode/launch.json
{
    "configurations": [
        {
            "request": "attach",
            "type": "python",
            "name": "Attach qaapp backend",
            "preLaunchTask": "run qaapp",
            "connect": {
                "port": 8040,
                "host": "localhost"
            },
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}/backend/",
                    "remoteRoot": "/app/"
                }
            ],
            "justMyCode": false
        }
    ]
}
```
connect.portがデバッグでattachするためのportです。こちらのportはもともと開放していなかったので、テウン3でデバッグ用のdocker-composeファイル(docker-compose.local.debug.yml)｀にこのportを開く設定を追加します。


### dubugモードでバックエンドアプリケーションを実行するdocker-composeファイルを作成
```diff_yaml:docker-compose.local.debug.yml
services:
  db:
    container_name: qaapp_db
    build:
      context: ./db
    ports:
      - 13306:3306
    command: --default-authentication-plugin=mysql_native_password
    networks:
      - qaapp

  frontend:
    container_name: qaapp_frontend
    build:
      context: ./frontend
    ports:
      - 8080:80
    networks:
      - qaapp
  
  backend:
    container_name: qaapp_backend
    build:
      context: .
      dockerfile: ./backend/Dockerfile
    volumes:
      - type: bind
        source: ./backend/src
        target: /app/src
      - type: bind
        source: ./backend/main.py
        target: /app/main.py
    tty: true
    ports:
      - 8090:80
+     - 8040:8040
    networks:
      - qaapp
+   command: [
+     "sh",
+     "-c",
+     "cd app && pipenv install debugpy && pipenv run python -m debugpy --wait-for-client --listen 0.0.0.0:8040 -m uvicorn main:app --reload --host 0.0.0.0 --port 80"
+   ]

networks:
  qaapp:
    driver: bridge
```
もともとのdocker-composeファイルとの差分で表示しています。
追加したのは手順2の説明で触れたデバッグのためのport開放設定と、デバッグモードでattachを待ち受ける設定でバックエンドアプリケーションを実行するようにDockerfileのCMDを上書きする設定を追加しています。
`pipenv run python -m debugpy --wait-for-client --listen 0.0.0.0:8040 -m uvicorn main:app --reload --host 0.0.0.0 --port 80`
上のコマンドがポイントです。
## 参考文献
[vscodeのドキュメント]
https://code.visualstudio.com/docs/containers/reference
tasks.jsonでdocker-compose upする方法を知るために使用
https://code.visualstudio.com/docs/containers/debug-python
vscodeでpythonデバッグを実施するための基本を知るために使用
https://code.visualstudio.com/docs/containers/debug-common
launch.jsonで指定できる各種プロパティの意味を知るために使用
https://qiita.com/kaitolucifer/items/dc58efebd72d72a8feb2
pythonデバッグの基本を知るために使用
https://github.com/tiangolo/fastapi/issues/707
クリティカルなissueがFastAPIのリポジトリに上がっていて、最も参考になった