1歩ずつカスタマイズしながら作るDocker + Nginx + Flask リモートデバッグ環境(後半)

# 目標

- 簡単なウェブサービスの構成を仮想環境で作成する
- ウェブサービスの構成は
    - フロント: nginx
    - バックエンド: WSGI(mod_wsgi) + Flask
- 仮想環境はDockerを利用してgitを通じて他の開発者と共有できるようにする
- ホストPCのOSに依存しないPythonのデバッグ環境を実現する
    - デバッグにはVSCodeのリモートデバッグを利用

# このドキュメントを読んで出来る事

目標を前半と後半にわけます。このページでは後半部分を書いています。

- 前半: [投稿済](https://qiita.com/glarpycat/items/2724712840e703861901)
    - 仮想環境のセットアップ(Hyper-V, Docker)
    - nginxコンテナ作成
    - apacheコンテナ作成
    - ウェブサービスの骨組みを作成
- 後半: ← ここ
    - ウェブアプリ作成(Flask)
    - リモートデバッグの設定

# 必要な環境
- Windows10 Pro
- Hyper-V
- Docker for Windows

# 新規にインストールするパッケージ
- VSCode拡張機能
    - Python
    - Japanese Language Pack for VS Code(任意)

# ウェブアプリ作成(Flask)

ウェブアプリを作成していきます。全体の流れは以下の通りです。まずコンテナ内で必要なファイルを作成・修正して動作確認後、Dockerfile等に内容を反映します。

## 全体の流れ

1. コンテナ内でセットアップ
2. ブラウザで動作確認
3. Dockerfile にセットアップ内容を記述

## コンテナ内でセットアップ

```powershell:別ターミナルで実行
PS> docker container exec -it wbb3 bash
```

```bash:必要なパッケージをインストール
root# apt update
root# apt install -y libapache2-mod-wsgi-py3 python3.6 python3.6-dev python3-pip
```

```bash:Pythonモジュールのインストール
root# mkdir /var/www/flask
root# cd /var/www/flask
root# pip3 install pipenv
root# pipenv install flask
...
This system supports the C.UTF-8 locale which is recommended.
You might be able to resolve your issue by exporting the
following environment variables:

    export LC_ALL=C.UTF-8
    export LANG=C.UTF-8
```
インストール中にエラーが出たので対応してインストールを続ける :point_down: 

```bash:エラー対応とPythonモジュールのインストール(続き)
root# export LC_ALL=C.UTF-8
root# export LANG=C.UTF-8
root# pipenv install flask
root# pipenv install --system --dev
```

```bash:WSGIアプリ作成
root# mkdir /var/www/flask/wsgi
root# vi /var/www/flask/wsgi/app.py
```
ファイルの中身は :point_down:

```python:/var/www/flask/wsgi/app.py
from datetime import datetime
from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return 'Hello'

@app.before_first_request
def before_first_request():
    print('### Restarted, first request @ {} ###'.format(datetime.utcnow()))

application = app
```

```bash:Apacheの設定変更(WSGI対応)
root# vi /etc/apache2/mods-available/wsgi.conf
```
ファイルの中身は :point_down: 

```apache:/etc/apache2/mods-available/wsgi.conf
<IfModule mod_wsgi.c>
    WSGIScriptAlias /wsgi /var/www/flask/wsgi/app.py
</IfModule>
```

## 動作確認

```bash:WSGIの動作確認
root# apachectl graceful
```

以下のURLにアクセス → :ok_hand: :ok: 
http://localhost/app/wsgi/

## Dockerfile に変更内容を反映

```dockerfile:flask/Dockerfile
FROM ubuntu

# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive

# Apache
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_PID_FILE /var/run/apache2.pid
ENV APACHE_RUN_DIR /var/run/apache2
ENV APACHE_LOG_DIR /var/log/apache2
ENV APACHE_LOCK_DIR /var/lock/apache2

# Python pipenv
ENV LC_ALL C.UTF-8
ENV LANG C.UTF-8

# Install packages
RUN apt-get update \
    && apt-get install -y --no-install-recommends apt-utils dialog 2>&1 \
    && apt-get install -y git iproute2 procps lsb-release \
    && apt-get install -y apache2 libapache2-mod-wsgi-py3 \
    && apt-get install -y python3.6 \
    && apt-get install -y python3.6-dev \
    && apt-get install -y python3-pip \
    && pip3 install pipenv

# Apache
RUN  echo ServerName $HOSTNAME > /etc/apache2/conf-available/fqdn.conf \
    && a2enconf fqdn
COPY ./etc/apache2 /etc/apache2

# Install other packages
RUN apt-get install -y vim curl

# Install Python modules
WORKDIR /var/www/flask
COPY ./Pipfile Pipfile
COPY ./Pipfile.lock Pipfile.lock
RUN set -ex && pipenv install --system --dev

# Clean up
RUN apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*

EXPOSE 80
CMD ["apachectl", "-D", "FOREGROUND"]
```

```powershell:WSGI関連ファイルのコピー
PS> docker container cp wbb3:/var/www/flask/Pipfile flask
PS> docker container cp wbb3:/var/www/flask/Pipfile.lock flask
PS> docker container cp wbb3:/var/www/flask/wsgi flask
PS> mkdir flask\etc\apache2\mods-available
PS> docker container cp wbb3:/etc/apache2/mods-available/wsgi.conf flask\etc\apache2\mods-available
```

```dockerfile:docker-compose.yml
# 略
    ports:
      - 5000:80
# --- 追加 ---
    volumes:
      - ./flask/wsgi:/var/www/flask/wsgi
# --- ここまで ---
```

## 動作確認

```powershell:再ビルドとコンテナの起動
PS> docker-compose down
PS> docker-compose up --build
```
:bangbang: 多くのパッケージをインストールするので初回はコンテナの起動まで時間がかかります。
- http://localhost/ → :ok:
- http://localhost/app/wsgi/ → :ok: 

# リモートデバッグの設定

VSCode からデバッグ出来るようにセットアップします。全体の流れは今までとほぼ同じですが、リモートデバッグ用のポート設定だけ先にします。

## 全体の流れ

1. ポート設定(リモートデバッグ用)
2. 再ビルドとコンテナの起動
3. セットアップ(コンテナ内)
    1. Pythonモジュールのインストール
    2. VSCodeのセットアップ
    3. デバッグの動作確認
4. Dockerfile にセットアップ内容を記述
5. 動作確認

## ポート設定(リモートデバッグ用)

```dockerfile:変更前flask/Dockerfile
EXPOSE 80
```

```dockerfile:変更後flask/Dockerfile
EXPOSE 80 5678
```

```dockerfile:変更前docker-compose.yml
ports:
  - 5000:80
```

```dockerfile:変更後docker-compose.yml
ports:
   - 5000:80
   - 5678:5678
```
## 再ビルドとコンテナの起動

```powershell:再ビルドとコンテナの起動
PS> docker-compose down
PS> docker-compose up --build
```

## セットアップ(コンテナ内)
起動中のコンテナのシェルに接続してリモートデバッグ用のセットアップをします。

```powershell:別ターミナル
PS> docker container exec -it wbb3 bash
```

### Pythonモジュールのインストール

開発用モジュールなので`--dev` オプションをつける。
:bangbang: ptvsdの最新バージョン(4.3.2)で、Flask + mod_wsgi環境下のリモートデバッグが正常に開始できなかったので、ネットで動作報告があった4.1.4にバージョンを固定しています。リモートデバッグはVSCodeとptvsdのバージョンにセンシティブかも :frowning2:

```powershell:リモートデバッグのセットアップ
root# pipenv install --dev ptvsd==4.1.4
root# pipenv install --system --dev
root# vi wsgi/app.py
```
ファイルの中身は :point_down: 

```python:flask/wsgi/app.py
from datetime import datetime
from flask import Flask
import ptvsd

app = Flask(__name__)

@app.route("/")
def index():
    return 'Hello'

@app.before_first_request
def before_first_request():
    print('### Restarted, first request @ {} ###'.format(datetime.utcnow()))

application = app

try:
    ptvsd.enable_attach()
except OSError as e:
    print(e)
```

ブレークポイントが正常に動作するように、アタッチ先を単一プロセス/スレッドにする。

```bash
root# vi /etc/apache2/mods-available/mpm_event.conf
```

ファイルの中身は :point_down: 

```apache:/etc/apache2/mods-available/mpm_event.conf
<IfModule mpm_event_module>
        StartServers             1
        MinSpareThreads          1
        MaxSpareThreads          1
        ThreadLimit              1
        ThreadsPerChild          1
        MaxRequestWorkers        1
        MaxConnectionsPerChild   0
</IfModule>
```

### 動作確認
デバッグ用コード以外は正常に動作している事を確認する。

```bash:Apacheの再起動
root# apachectl graceful
```
ブラウザで確認。http://localhost/app/wsgi/ → :ok: 

### VSCodeのセットアップ
下記コマンドを実行しVSCodeの拡張機能をインストール。(すでにインストールしている場合は不要)

- Pythonモジュール
- Japanese Language Pack for VS Code(※任意)

```powershell:VSCode拡張機能のインストール
PS> code --install-extension ms-python.python
PS> code --install-extension ms-ceintl.vscode-language-pack-ja
```

### リモートデバッグの設定

下記コマンドを実行して、`webapp-docker` フォルダをVSCodeで開く。(GUIから`webapp-docker`フォルダを右クリック→`Open with Code`でもOK)

```powershell:
PS> code .
```

VSCode内でサイドバーに表示されているapp.pyを開く
![vscode-remote-debug-oepn-app-screenshot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494172/c55fa91a-4624-652c-e039-fb02749d0f9c.jpeg)
アクティビティバーのデバッグアイコンを選択
![vscode-remote-debug-screenshot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494172/6cd6f378-84cc-a29c-c182-4e531bbda798.jpeg)

歯車アイコンをクリック
![vscode-remote-debug-add-setting.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494172/215a190d-3277-8b1d-140e-66dc6978a896.jpeg)

メニューの`Remote Attach`を選択
![vscode-remote-debug-add-attach.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494172/f3ebed8f-e436-83c2-3cd4-f50e29eb9573.jpeg)

`Enter`キーを入力。(デフォルト値を使用)
![vscode-remote-debug-attach-step1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494172/49e0e51b-195d-8f25-301a-0cf43e80d7ee.jpeg)

`Enter`キーを入力。(デフォルト値を使用)
![vscode-remote-debug-attach-step2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494172/2671f2a2-c5a9-0e6a-494f-0cb6d882e6ee.jpeg)

設定ファイルが開くのでをファイルの内容を修正し保存。ファイルの中身は :point_down: 

```json:.vscode/launch.json
{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: アタッチ",
            "type": "python",
            "request": "attach",
            "port": 5678,
            "host": "localhost",
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}",
                    "remoteRoot": "/var/www"
                }
            ]
        }
    ]
}
```

### デバッグの動作確認

ブレークポイントを設定後、リモートのデバッグサーバーにアタッチ。
:bangbang:アタッチ出来ない時はブラウザをリロード後に再挑戦

![vscode-remote-debug-add-breakpoint.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494172/8cb92218-b694-b1db-f397-a1d31f66dd39.png)

ブラウザでアクセスして、ブレークポイントで停止する事を確認する。
http://localhost/app/wsgi/

![vscode-remote-debug-break.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494172/dadc4acd-f7b3-090a-374d-b5147ea43aa3.png)

## Dockerfile に変更内容を反映

```powershell:WSGI関連ファイルのコピー
PS> docker container cp wbb3:/var/www/flask/Pipfile flask
PS> docker container cp wbb3:/var/www/flask/Pipfile.lock flask
PS> docker container cp wbb3:/var/www/flask/wsgi flask
PS> docker container cp wbb3:/etc/apache2/mods-available/mpm_event.conf 
 flask\etc\apache2\mods-available
```

## 動作確認

```powershell:再ビルドとコンテナの起動
PS> docker-compose down
PS> docker-compose up --build
```

下記URLにブラウザでアクセス。
http://localhost/
http://localhost/app/wsgi/

VSCodeからリモートデバッグを実行して動作確認する。
http://localhost/app/wsgi/

# まとめ

最終的なフォルダ構成は以下になります。

```powershell:フォルダ構成
PS> tree /F
C:.
│  docker-compose.yml
│
├─.vscode
│      launch.json
│
├─flask
│  │  Dockerfile
│  │  Pipfile
│  │  Pipfile.lock
│  │
│  ├─etc
│  │  └─apache2
│  │      └─mods-available
│  │              mpm_event.conf
│  │              wsgi.conf
│  │
│  └─wsgi
│          app.py
│
└─nginx
    │  Dockerfile
    │
    └─etc
        └─nginx
            └─conf.d
                    default.conf
```

作成した内容はBitbucketにアップしています。
リポジトリ
https://bitbucket.org/glarpycat/docker-remote-debugging-with-vscode/src/master/